mirror of
https://github.com/dokku/dokku.git
synced 2025-12-16 12:07:45 +01:00
Previously, we would always set the port mapping during a dockerfile build, making it difficult for users to override mappings. We also only _sometimes_ updated the detected port mapping, further confusing issues when users were migrating from Dockerfile to Buildpacks for builds. Now, we always detect the port mapping during the build process, and only use that detected port mapping if an override is not specified. This greatly simplifies the experience around port mapping, as now a user can create an app, set a port mapping, and that first deploy will respect the port mapping without an additional deploy. The builder always has the best context for what the app should be listening on, and thus we can always specify a "default" port mapping at this stage. Users can override this map as desired later. This change also results in the removal of a ton of internal code that is now centralized in the ports plugin. Closes #4067
981 lines
28 KiB
Bash
Executable File
981 lines
28 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -eo pipefail
|
|
[[ $DOKKU_TRACE ]] && set -x
|
|
|
|
has_tty() {
|
|
declare desc="return 0 if we have a tty"
|
|
if [[ "$DOKKU_DISABLE_TTY" == "true" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if [[ "$(LC_ALL=C /usr/bin/tty || true)" == "not a tty" ]]; then
|
|
return 1
|
|
else
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
dokku_apps() {
|
|
declare desc="prints list of all local apps"
|
|
declare FILTER="$1"
|
|
local INSTALLED_APPS="$(plugn trigger app-list "$FILTER")"
|
|
if [[ -z "$INSTALLED_APPS" ]]; then
|
|
dokku_log_fail "You haven't deployed any applications yet"
|
|
fi
|
|
echo "$INSTALLED_APPS"
|
|
}
|
|
|
|
dokku_version() {
|
|
if [[ -f "$DOKKU_LIB_ROOT/STABLE_VERSION" ]]; then
|
|
DOKKU_VERSION=$(cat "${DOKKU_LIB_ROOT}/STABLE_VERSION")
|
|
elif [[ -f "$DOKKU_LIB_ROOT/VERSION" ]]; then
|
|
DOKKU_VERSION=$(cat "${DOKKU_LIB_ROOT}/VERSION")
|
|
else
|
|
dokku_log_fail "Unable to determine dokku's version"
|
|
fi
|
|
echo "dokku version ${DOKKU_VERSION}"
|
|
}
|
|
|
|
dokku_log_quiet() {
|
|
declare desc="log quiet formatter"
|
|
if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
|
|
echo "$*"
|
|
fi
|
|
}
|
|
|
|
dokku_log_info1() {
|
|
declare desc="log info1 formatter"
|
|
echo "-----> $*"
|
|
}
|
|
|
|
dokku_log_info2() {
|
|
declare desc="log info2 formatter"
|
|
echo "=====> $*"
|
|
}
|
|
|
|
dokku_log_info1_quiet() {
|
|
declare desc="log info1 formatter (with quiet option)"
|
|
if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
|
|
echo "-----> $*"
|
|
fi
|
|
}
|
|
|
|
dokku_log_info2_quiet() {
|
|
declare desc="log info2 formatter (with quiet option)"
|
|
if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
|
|
echo "=====> $*"
|
|
fi
|
|
}
|
|
|
|
dokku_col_log_info1() {
|
|
declare desc="columnar log info1 formatter"
|
|
printf "%-6s %-18s %-25s %-25s %-25s\n" "----->" "$@"
|
|
}
|
|
|
|
dokku_col_log_info1_quiet() {
|
|
declare desc="columnar log info1 formatter (with quiet option)"
|
|
if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
|
|
printf "%-6s %-18s %-25s %-25s %-25s\n" "----->" "$@"
|
|
fi
|
|
}
|
|
|
|
dokku_col_log_info2() {
|
|
declare desc="columnar log info2 formatter"
|
|
printf "%-6s %-18s %-25s %-25s %-25s\n" "=====>" "$@"
|
|
}
|
|
|
|
dokku_col_log_info2_quiet() {
|
|
declare desc="columnar log info2 formatter (with quiet option)"
|
|
if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
|
|
printf "%-6s %-18s %-25s %-25s %-25s\n" "=====>" "$@"
|
|
fi
|
|
}
|
|
|
|
dokku_col_log_msg() {
|
|
declare desc="columnar log formatter"
|
|
printf "%-25s %-25s %-25s %-25s\n" "$@"
|
|
}
|
|
|
|
dokku_col_log_msg_quiet() {
|
|
declare desc="columnar log formatter (with quiet option)"
|
|
if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
|
|
printf "%-25s %-25s %-25s %-25s\n" "$@"
|
|
fi
|
|
}
|
|
|
|
dokku_log_verbose_quiet() {
|
|
declare desc="log verbose formatter (with quiet option)"
|
|
if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
|
|
echo " $*"
|
|
fi
|
|
}
|
|
|
|
dokku_log_verbose() {
|
|
declare desc="log verbose formatter"
|
|
echo " $*"
|
|
}
|
|
|
|
dokku_log_exclaim_quiet() {
|
|
declare desc="log exclaim formatter"
|
|
if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
|
|
echo " ! $*"
|
|
fi
|
|
}
|
|
|
|
dokku_log_exclaim() {
|
|
declare desc="log exclaim formatter"
|
|
echo " ! $*"
|
|
}
|
|
|
|
dokku_log_warn_quiet() {
|
|
declare desc="log warning formatter"
|
|
if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
|
|
echo " ! $*" 1>&2
|
|
fi
|
|
}
|
|
|
|
dokku_log_warn() {
|
|
declare desc="log warning formatter"
|
|
echo " ! $*" 1>&2
|
|
}
|
|
|
|
dokku_log_exit_quiet() {
|
|
declare desc="log exit formatter"
|
|
if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
|
|
echo "$@" 1>&2
|
|
fi
|
|
exit 0
|
|
}
|
|
|
|
dokku_log_exit() {
|
|
declare desc="log exit formatter"
|
|
echo "$@" 1>&2
|
|
exit 0
|
|
}
|
|
|
|
dokku_log_fail_quiet() {
|
|
declare desc="log fail formatter"
|
|
if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
|
|
echo " ! $*" 1>&2
|
|
fi
|
|
exit "${DOKKU_FAIL_EXIT_CODE:=1}"
|
|
}
|
|
|
|
dokku_log_fail() {
|
|
declare desc="log fail formatter"
|
|
echo " ! $*" 1>&2
|
|
exit "${DOKKU_FAIL_EXIT_CODE:=1}"
|
|
}
|
|
|
|
dokku_log_stderr() {
|
|
declare desc="log stderr formatter"
|
|
echo "$@" 1>&2
|
|
}
|
|
|
|
dokku_log_event() {
|
|
declare desc="log dokku events"
|
|
logger -t dokku-event -i -- "$@"
|
|
}
|
|
|
|
dokku_log_plugn_trigger_call() {
|
|
declare desc="log plugn trigger calls"
|
|
|
|
local l_hook="$1"
|
|
shift
|
|
dokku_log_event "INVOKED: ${l_hook}( $* ) NAME=$NAME FINGERPRINT=$FINGERPRINT DOKKU_PID=$DOKKU_PID"
|
|
}
|
|
|
|
dokku_container_log_verbose_quiet() {
|
|
declare desc="log verbose container output (with quiet option)"
|
|
local CID=$1
|
|
shift
|
|
|
|
OIFS=$IFS
|
|
IFS=$'\n'
|
|
local line
|
|
for line in $("$DOCKER_BIN" container logs "$CID" 2>&1); do
|
|
dokku_log_verbose_quiet "$line"
|
|
done
|
|
IFS=$OIFS
|
|
}
|
|
|
|
fn-is-valid-app-name() {
|
|
declare desc="verify that the app name matches naming restrictions"
|
|
local APP="$1"
|
|
[[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on"
|
|
if [[ "$APP" =~ ^[a-z].* ]] || [[ "$APP" =~ ^[0-9].* ]]; then
|
|
if [[ ! $APP =~ [A-Z] ]] && [[ ! $APP =~ [:] ]] && [[ ! $APP =~ [_] ]]; then
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
fn-is-valid-app-name-old() {
|
|
declare desc="verify that the app name matches the old naming restrictions"
|
|
local APP="$1"
|
|
[[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on"
|
|
if [[ "$APP" =~ ^[a-z].* ]] || [[ "$APP" =~ ^[0-9].* ]]; then
|
|
if [[ ! $APP =~ [A-Z] ]] && [[ ! $APP =~ [:] ]]; then
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
is_valid_app_name() {
|
|
declare desc="verify that the app name matches naming restrictions"
|
|
local APP="$1"
|
|
|
|
if fn-is-valid-app-name "$APP"; then
|
|
return
|
|
fi
|
|
|
|
dokku_log_fail "App name must begin with lowercase alphanumeric character, and cannot include uppercase characters, colons, or underscores"
|
|
}
|
|
|
|
is_valid_app_name_old() {
|
|
declare desc="verify that the app name matches the old naming restrictions"
|
|
local APP="$1"
|
|
|
|
if fn-is-valid-app-name-old "$APP"; then
|
|
return
|
|
fi
|
|
|
|
dokku_log_fail "App name must begin with lowercase alphanumeric character, and cannot include uppercase characters, or colons"
|
|
}
|
|
|
|
verify_app_name() {
|
|
declare desc="verify app name format and app existence"
|
|
declare APP="$1"
|
|
|
|
if "$PLUGIN_CORE_AVAILABLE_PATH/common/common" --quiet verify-app-name "$APP"; then
|
|
return 0
|
|
fi
|
|
|
|
DOKKU_FAIL_EXIT_CODE=20 dokku_log_fail "App $APP does not exist"
|
|
}
|
|
|
|
verify_image() {
|
|
declare desc="verify image existence"
|
|
local IMAGE="$1"
|
|
if "$DOCKER_BIN" image inspect "$IMAGE" &>/dev/null; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
get_app_image_repo() {
|
|
declare desc="central definition of image repo pattern"
|
|
local APP="$1"
|
|
local IMAGE_REPO="dokku/$APP"
|
|
echo "$IMAGE_REPO"
|
|
}
|
|
|
|
get_deploying_app_image_name() {
|
|
declare desc="return deploying image identifier for a given app, tag tuple. validate if tag is presented"
|
|
local APP="$1"
|
|
local IMAGE_TAG="$2"
|
|
local IMAGE_REPO="$3"
|
|
|
|
IMAGE_TAG="$(get_running_image_tag "$APP" "$IMAGE_TAG")"
|
|
local IMAGE_REMOTE_REPOSITORY=$(plugn trigger deployed-app-repository "$APP")
|
|
local NEW_IMAGE_REPO=$(plugn trigger deployed-app-image-repo "$APP")
|
|
|
|
[[ -n "$NEW_IMAGE_REPO" ]] && IMAGE_REPO="$NEW_IMAGE_REPO"
|
|
[[ -z "$IMAGE_REPO" ]] && IMAGE_REPO=$(get_app_image_repo "$APP")
|
|
|
|
local IMAGE="${IMAGE_REMOTE_REPOSITORY}${IMAGE_REPO}:${IMAGE_TAG}"
|
|
verify_image "$IMAGE" || dokku_log_fail "App image ($IMAGE) not found"
|
|
echo "$IMAGE"
|
|
}
|
|
|
|
get_app_image_name() {
|
|
declare desc="return image identifier for a given app, tag tuple. validate if tag is presented"
|
|
local APP="$1"
|
|
local IMAGE_TAG="$2"
|
|
IMAGE_REPO="$3"
|
|
|
|
if [[ -z "$IMAGE_REPO" ]]; then
|
|
IMAGE_REPO=$(get_app_image_repo "$APP")
|
|
fi
|
|
|
|
if [[ -n "$IMAGE_TAG" ]]; then
|
|
local IMAGE="$IMAGE_REPO:$IMAGE_TAG"
|
|
verify_image "$IMAGE" || dokku_log_fail "App image ($IMAGE) not found"
|
|
else
|
|
local IMAGE="$IMAGE_REPO:latest"
|
|
fi
|
|
echo "$IMAGE"
|
|
}
|
|
|
|
get_app_scheduler() {
|
|
declare desc="fetch the scheduler for a given application"
|
|
declare APP="$1"
|
|
|
|
"$PLUGIN_CORE_AVAILABLE_PATH/common/common" --quiet scheduler-detect "$APP"
|
|
}
|
|
|
|
get_running_image_tag() {
|
|
declare desc="retrieves current deployed image tag for a given app"
|
|
local APP="$1" IMAGE_TAG="$2"
|
|
|
|
local NEW_IMAGE_TAG=$(plugn trigger deployed-app-image-tag "$APP")
|
|
[[ -n "$NEW_IMAGE_TAG" ]] && IMAGE_TAG="$NEW_IMAGE_TAG"
|
|
[[ -z "$IMAGE_TAG" ]] && IMAGE_TAG="latest"
|
|
|
|
echo "$IMAGE_TAG"
|
|
}
|
|
|
|
get_image_builder_type() {
|
|
declare desc="returns the image builder_type"
|
|
declare IMAGE="$1" APP="$2"
|
|
|
|
if [[ -z "$IMAGE" ]]; then
|
|
echo "dockerfile"
|
|
return
|
|
fi
|
|
|
|
BUILDER_TYPE="$("$DOCKER_BIN" image inspect --format '{{ index .Config.Labels "com.dokku.builder-type" }}' "$IMAGE")"
|
|
if [[ -n "$BUILDER_TYPE" ]]; then
|
|
echo "$BUILDER_TYPE"
|
|
return
|
|
fi
|
|
|
|
if is_image_herokuish_based "$IMAGE" "$APP"; then
|
|
echo "herokuish"
|
|
return
|
|
fi
|
|
|
|
echo "dockerfile"
|
|
}
|
|
|
|
is_image_cnb_based() {
|
|
declare desc="returns true if app image is based on cnb"
|
|
declare IMAGE="$1"
|
|
|
|
if [[ "$("$PLUGIN_CORE_AVAILABLE_PATH/common/common" --quiet image-is-cnb-based "$IMAGE")" != "true" ]]; then
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
is_image_herokuish_based() {
|
|
declare desc="returns true if app image is based on herokuish or a buildpack-like env"
|
|
declare IMAGE="$1" APP="$2"
|
|
|
|
if [[ "$("$PLUGIN_CORE_AVAILABLE_PATH/common/common" --quiet image-is-herokuish-based "$IMAGE" "$APP")" != "true" ]]; then
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
get_docker_version() {
|
|
CLIENT_VERSION_STRING="$("$DOCKER_BIN" version --format "{{ .Client.Version }}")"
|
|
echo "$CLIENT_VERSION_STRING"
|
|
}
|
|
|
|
fn-is-compose-installed() {
|
|
declare desc="check if the compose docker plugin is installed"
|
|
local COMPOSE_INSTALLED
|
|
COMPOSE_INSTALLED="$(docker info -f '{{range .ClientInfo.Plugins}}{{if eq .Name "compose"}}true{{end}}{{end}}')"
|
|
|
|
if [[ "$COMPOSE_INSTALLED" == "true" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
is_number() {
|
|
declare desc="returns 0 if input is a number"
|
|
local NUMBER=$1
|
|
local NUM_RE='^[0-9]+$'
|
|
if [[ $NUMBER =~ $NUM_RE ]]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
is_abs_path() {
|
|
declare desc="returns 0 if input path is absolute"
|
|
local TEST_PATH=$1
|
|
if [[ "$TEST_PATH" == /* ]]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
parse_args() {
|
|
declare desc="top-level cli arg parser"
|
|
local next_index=1
|
|
local skip=false
|
|
local args=("$@")
|
|
local flags
|
|
|
|
for arg in "$@"; do
|
|
if [[ "$skip" == "true" ]]; then
|
|
next_index=$((next_index + 1))
|
|
skip=false
|
|
continue
|
|
fi
|
|
|
|
case "$arg" in
|
|
--quiet)
|
|
export DOKKU_QUIET_OUTPUT=1
|
|
;;
|
|
--trace)
|
|
export DOKKU_TRACE=1
|
|
;;
|
|
--force)
|
|
export DOKKU_APPS_FORCE_DELETE=1
|
|
;;
|
|
--app)
|
|
export DOKKU_APP_NAME=${args[$next_index]}
|
|
skip=true
|
|
;;
|
|
esac
|
|
|
|
if [[ "$skip" == "true" ]]; then
|
|
flags="${flags} ${arg}"
|
|
elif [[ "$arg" == "--app" ]]; then
|
|
flags="${flags} ${arg}=${args[$next_index]}"
|
|
elif [[ "$arg" =~ ^--.* ]]; then
|
|
flags="${flags} ${arg}"
|
|
fi
|
|
|
|
next_index=$((next_index + 1))
|
|
done
|
|
|
|
if [[ -z "$DOKKU_GLOBAL_FLAGS" ]]; then
|
|
export DOKKU_GLOBAL_FLAGS="$(echo -e "${flags}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
copy_from_image() {
|
|
declare desc="copy file from named image to destination"
|
|
declare IMAGE="$1" SRC_FILE="$2" DST_FILE="$3"
|
|
local WORK_DIR=""
|
|
|
|
local DOCKER_CREATE_LABEL_ARGS="--label=com.dokku.app-name=$APP"
|
|
|
|
if verify_image "$IMAGE"; then
|
|
if ! is_abs_path "$SRC_FILE"; then
|
|
if is_image_cnb_based "$IMAGE"; then
|
|
WORKDIR="/workspace"
|
|
elif is_image_herokuish_based "$IMAGE" "$APP"; then
|
|
WORKDIR="/app"
|
|
else
|
|
WORKDIR="$("$DOCKER_BIN" image inspect --format '{{.Config.WorkingDir}}' "$IMAGE")"
|
|
fi
|
|
|
|
if [[ -n "$WORKDIR" ]]; then
|
|
SRC_FILE="${WORKDIR}/${SRC_FILE}"
|
|
fi
|
|
fi
|
|
|
|
TMP_FILE_COMMAND_OUTPUT=$(mktemp "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX")
|
|
trap "rm -rf '$TMP_FILE_COMMAND_OUTPUT' &>/dev/null || true" RETURN
|
|
|
|
local CID=$("$DOCKER_BIN" container create "${DOCKER_CREATE_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS "$IMAGE")
|
|
"$DOCKER_BIN" container cp "$CID:$SRC_FILE" "$TMP_FILE_COMMAND_OUTPUT" 2>/dev/null || true
|
|
"$DOCKER_BIN" container rm --force "$CID" &>/dev/null
|
|
plugn trigger scheduler-register-retired "$APP" "$CID"
|
|
|
|
# docker cp exits with status 1 when run as non-root user when it tries to chown the file
|
|
# after successfully copying the file. Thus, we suppress stderr.
|
|
# ref: https://github.com/dotcloud/docker/issues/3986
|
|
if [[ ! -s "$TMP_FILE_COMMAND_OUTPUT" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
# workaround for CHECKS file when owner is root. seems to only happen when running inside docker
|
|
dos2unix -l <"$TMP_FILE_COMMAND_OUTPUT" >"$DST_FILE"
|
|
|
|
# add trailing newline for certain places where file parsing depends on it
|
|
if [[ "$(tail -c1 "$DST_FILE")" != "" ]]; then
|
|
echo "" >>"$DST_FILE"
|
|
fi
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
get_app_container_ids() {
|
|
declare desc="returns list of docker container ids for given app and optional container_type"
|
|
local APP="$1"
|
|
local CONTAINER_TYPE="$2"
|
|
[[ -f $DOKKU_ROOT/$APP/CONTAINER ]] && DOKKU_CIDS+=$(<"$DOKKU_ROOT/$APP/CONTAINER")
|
|
|
|
if [[ -n "$CONTAINER_TYPE" ]]; then
|
|
local CONTAINER_PATTERN="$DOKKU_ROOT/$APP/CONTAINER.$CONTAINER_TYPE.*"
|
|
if [[ $CONTAINER_TYPE == *.* ]]; then
|
|
local CONTAINER_PATTERN="$DOKKU_ROOT/$APP/CONTAINER.$CONTAINER_TYPE"
|
|
[[ ! -f $CONTAINER_PATTERN ]] && echo "" && return 0
|
|
fi
|
|
else
|
|
local CONTAINER_PATTERN="$DOKKU_ROOT/$APP/CONTAINER.*"
|
|
fi
|
|
|
|
shopt -s nullglob
|
|
local DOKKU_CID_FILE
|
|
for DOKKU_CID_FILE in $CONTAINER_PATTERN; do
|
|
local DOKKU_CIDS+=" "
|
|
local DOKKU_CIDS+=$(<"$DOKKU_CID_FILE")
|
|
local DOKKU_CIDS+=" "
|
|
done
|
|
shopt -u nullglob
|
|
echo "$DOKKU_CIDS"
|
|
}
|
|
|
|
get_app_running_container_ids() {
|
|
declare desc="return list of running docker container ids for given app and optional container_type"
|
|
local APP="$1" CONTAINER_TYPE="$2"
|
|
local CIDS
|
|
|
|
! (is_deployed "$APP") && dokku_log_fail "App $APP has not been deployed"
|
|
CIDS=$(get_app_container_ids "$APP" "$CONTAINER_TYPE")
|
|
|
|
for CID in $CIDS; do
|
|
(is_container_status "$CID" "Running") && local APP_RUNNING_CONTAINER_IDS+="$CID "
|
|
done
|
|
|
|
echo "$APP_RUNNING_CONTAINER_IDS"
|
|
}
|
|
|
|
get_app_running_container_types() {
|
|
declare desc="return list of running container types for given app"
|
|
local APP=$1 CONTAINER_TYPES
|
|
|
|
! (is_deployed "$APP") && dokku_log_fail "App $APP has not been deployed"
|
|
|
|
CONTAINER_TYPES="$(find "$DOKKU_ROOT/$APP" -maxdepth 1 -name "CONTAINER.*" -print0 2>/dev/null | xargs -0)"
|
|
if [[ -n "$CONTAINER_TYPES" ]]; then
|
|
CONTAINER_TYPES="${CONTAINER_TYPES//$DOKKU_ROOT\/$APP\//}"
|
|
CONTAINER_TYPES="$(tr " " $'\n' <<<"$CONTAINER_TYPES" | awk -F. '{ print $2 }' | sort | uniq | xargs)"
|
|
fi
|
|
|
|
echo "$CONTAINER_TYPES"
|
|
}
|
|
|
|
is_deployed() {
|
|
declare desc="return 0 if given app has a running container"
|
|
local APP="$1"
|
|
|
|
"$PLUGIN_CORE_AVAILABLE_PATH/common/common" --quiet is-deployed "$APP"
|
|
}
|
|
|
|
is_container_status() {
|
|
declare desc="return 0 if given docker container id is in given state"
|
|
local CID=$1
|
|
local TEMPLATE="{{.State.$2}}"
|
|
local CONTAINER_STATUS=$("$DOCKER_BIN" container inspect --format "$TEMPLATE" "$CID" 2>/dev/null || true)
|
|
|
|
if [[ "$CONTAINER_STATUS" == "true" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
dokku_build() {
|
|
declare desc="build phase"
|
|
declare APP="$1" IMAGE_SOURCE_TYPE="$2" SOURCECODE_WORK_DIR="$3"
|
|
|
|
local IMAGE=$(get_app_image_name "$APP")
|
|
local RELEASED_IMAGE_ID="$(docker image ls --filter "label=com.dokku.image-stage=release" --filter "label=com.dokku.app-name=$APP" --format "{{.ID}}")"
|
|
if plugn trigger builder-build "$IMAGE_SOURCE_TYPE" "$APP" "$SOURCECODE_WORK_DIR"; then
|
|
return
|
|
fi
|
|
|
|
if [[ -n "$RELEASED_IMAGE_ID" ]]; then
|
|
dokku_log_warn "Retagging old image $RELEASED_IMAGE_ID as $IMAGE"
|
|
"$DOCKER_BIN" image tag "$RELEASED_IMAGE_ID" "$IMAGE" 2>/dev/null || true
|
|
else
|
|
dokku_log_warn "Removing invalid image tag $IMAGE"
|
|
"$DOCKER_BIN" image remove --force --no-prune "$IMAGE" &>/dev/null || true
|
|
fi
|
|
|
|
dokku_log_fail "App build failed"
|
|
}
|
|
|
|
dokku_release() {
|
|
declare desc="release phase"
|
|
declare APP="$1" IMAGE_SOURCE_TYPE="$2" IMAGE_TAG="$3"
|
|
|
|
local IMAGE=$(get_app_image_name "$APP" "$IMAGE_TAG")
|
|
if is_image_cnb_based "$IMAGE"; then
|
|
IMAGE_SOURCE_TYPE="pack"
|
|
fi
|
|
|
|
plugn trigger builder-release "$IMAGE_SOURCE_TYPE" "$APP" "$IMAGE_TAG"
|
|
}
|
|
|
|
cmd-deploy() {
|
|
declare desc="deploy phase"
|
|
declare APP="$1" IMAGE_TAG="$2" PROCESS_TYPE="$3"
|
|
|
|
verify_app_name "$APP"
|
|
local DOKKU_SCHEDULER=$(get_app_scheduler "$APP")
|
|
plugn trigger scheduler-deploy "$DOKKU_SCHEDULER" "$APP" "$IMAGE_TAG" "$PROCESS_TYPE"
|
|
}
|
|
|
|
release_and_deploy() {
|
|
declare desc="main function for releasing and deploying an app"
|
|
source "$PLUGIN_AVAILABLE_PATH/config/functions"
|
|
|
|
local APP="$1"
|
|
local IMAGE_TAG="${2:-latest}"
|
|
local IMAGE=$(get_app_image_name "$APP" "$IMAGE_TAG")
|
|
|
|
if verify_image "$IMAGE"; then
|
|
IMAGE_SOURCE_TYPE="$(get_image_builder_type "$IMAGE" "$APP")"
|
|
local DOKKU_APP_SKIP_DEPLOY="$(config_get "$APP" DOKKU_SKIP_DEPLOY || true)"
|
|
local DOKKU_GLOBAL_SKIP_DEPLOY="$(config_get --global DOKKU_SKIP_DEPLOY || true)"
|
|
|
|
local DOKKU_SKIP_DEPLOY=${DOKKU_APP_SKIP_DEPLOY:="$DOKKU_GLOBAL_SKIP_DEPLOY"}
|
|
|
|
dokku_log_info1 "Releasing $APP..."
|
|
dokku_release "$APP" "$IMAGE_SOURCE_TYPE" "$IMAGE_TAG"
|
|
|
|
if [[ "$DOKKU_SKIP_DEPLOY" != "true" ]]; then
|
|
local DOKKU_SCHEDULER=$(get_app_scheduler "$APP")
|
|
dokku_log_info1 "Deploying $APP via the $DOKKU_SCHEDULER scheduler..."
|
|
cmd-deploy "$APP" "$IMAGE_TAG"
|
|
dokku_log_info2 "Application deployed:"
|
|
plugn trigger domains-urls "$APP" urls | sed "s/^/ /"
|
|
else
|
|
dokku_log_info1 "Skipping deployment"
|
|
fi
|
|
|
|
echo
|
|
fi
|
|
}
|
|
|
|
dokku_receive() {
|
|
declare desc="receives an app kicks off deploy process"
|
|
source "$PLUGIN_AVAILABLE_PATH/config/functions"
|
|
|
|
local APP="$1"
|
|
local IMAGE=$(get_app_image_name "$APP")
|
|
local IMAGE_SOURCE_TYPE="$2"
|
|
local TMP_WORK_DIR="$3"
|
|
|
|
docker_cleanup "$APP"
|
|
|
|
local DOKKU_CNB_EXPERIMENTAL="$(config_get "$APP" DOKKU_CNB_EXPERIMENTAL || true)"
|
|
if [[ "$DOKKU_CNB_EXPERIMENTAL" == "1" ]]; then
|
|
IMAGE_SOURCE_TYPE="pack"
|
|
fi
|
|
|
|
DOKKU_QUIET_OUTPUT=1 config_set --no-restart "$APP" DOKKU_APP_TYPE="$IMAGE_SOURCE_TYPE"
|
|
if ! dokku_build "$APP" "$IMAGE_SOURCE_TYPE" "$TMP_WORK_DIR"; then
|
|
return 1
|
|
fi
|
|
plugn trigger release-and-deploy "$APP"
|
|
}
|
|
|
|
docker_cleanup() {
|
|
declare desc="cleans up all exited/dead containers and removes all dangling images"
|
|
declare APP="$1" FORCE_CLEANUP="$2"
|
|
local DOKKU_APP_SKIP_CLEANUP
|
|
|
|
"$PLUGIN_CORE_AVAILABLE_PATH/common/common" --quiet docker-cleanup "$APP"
|
|
}
|
|
|
|
get_available_port() {
|
|
declare desc="returns first currently unused port > 1024"
|
|
dokku_log_warn "Deprecated: please use the 'ports-get-available' plugin trigger instead"
|
|
plugn trigger ports-get-available
|
|
}
|
|
|
|
dokku_auth() {
|
|
declare desc="calls user-auth plugin trigger"
|
|
export SSH_USER=${SSH_USER:=$USER}
|
|
export SSH_NAME=${NAME:="default"}
|
|
export DOKKU_COMMAND="$1"
|
|
|
|
local user_auth_count=$(find "$PLUGIN_PATH"/enabled/*/user-auth 2>/dev/null | wc -l)
|
|
|
|
# no plugin trigger exists
|
|
if [[ $user_auth_count == 0 ]]; then
|
|
return 0
|
|
fi
|
|
|
|
# this plugin trigger exists in the core `20_events` plugin
|
|
if [[ "$user_auth_count" == 1 ]] && [[ -f "$PLUGIN_PATH"/enabled/20_events/user-auth ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if ! plugn trigger user-auth "$SSH_USER" "$SSH_NAME" "$@"; then
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
_ipv4_regex() {
|
|
declare desc="ipv4 regex"
|
|
echo "([0-9]{1,3}[\.]){3}[0-9]{1,3}"
|
|
}
|
|
|
|
_ipv6_regex() {
|
|
declare desc="ipv6 regex"
|
|
local RE_IPV4="$(_ipv4_regex)"
|
|
local RE_IPV6="([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" # TEST: 1:2:3:4:5:6:7:8
|
|
local RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,7}:|" # TEST: 1:: 1:2:3:4:5:6:7::
|
|
local RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" # TEST: 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8
|
|
local RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" # TEST: 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8
|
|
local RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" # TEST: 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8
|
|
local RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" # TEST: 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8
|
|
local RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" # TEST: 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8
|
|
local RE_IPV6="${RE_IPV6}[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" # TEST: 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8
|
|
local RE_IPV6="${RE_IPV6}:((:[0-9a-fA-F]{1,4}){1,7}|:)|" # TEST: ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::
|
|
local RE_IPV6="${RE_IPV6}fe08:(:[0-9a-fA-F]{1,4}){2,2}%[0-9a-zA-Z]{1,}|" # TEST: fe08::7:8%eth0 fe08::7:8%1 (link-local IPv6 addresses with zone index)
|
|
local RE_IPV6="${RE_IPV6}::(ffff(:0{1,4}){0,1}:){0,1}${RE_IPV4}|" # TEST: ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses)
|
|
local RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}:${RE_IPV4}" # TEST: 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33
|
|
echo "$RE_IPV6"
|
|
}
|
|
|
|
get_ipv4_regex() {
|
|
declare desc="returns ipv4 regex"
|
|
local RE_IPV4="$(_ipv4_regex)"
|
|
# Ensure the ip address continues to the end of the line
|
|
# Fixes using a wildcard dns service such as sslip.io which allows for *.<ip address>.sslip.io
|
|
echo "${RE_IPV4}\$"
|
|
}
|
|
|
|
get_ipv6_regex() {
|
|
declare desc="returns ipv6 regex"
|
|
local RE_IPV6="$(_ipv6_regex)"
|
|
# Ensure the ip address continues to the end of the line
|
|
# Fixes using a wildcard dns service such as sslip.io which allows for *.<ip address>.sslip.io
|
|
echo "${RE_IPV6}\$"
|
|
}
|
|
|
|
get_dockerfile_exposed_ports() {
|
|
declare desc="return all exposed ports from passed file path"
|
|
dokku_log_warn "Deprecated: this function should not be used in app code"
|
|
|
|
local DOCKERFILE_PORTS=$(grep -E "^EXPOSE " "$1" | awk '{ print $2 }' | xargs) || true
|
|
echo "$DOCKERFILE_PORTS"
|
|
}
|
|
|
|
get_exposed_ports_from_image() {
|
|
declare desc="return all exposed ports from passed image name"
|
|
dokku_log_warn "Deprecated: this function should not be used in app code"
|
|
|
|
local IMAGE="$1"
|
|
verify_image "$IMAGE"
|
|
# shellcheck disable=SC2016
|
|
local DOCKER_IMAGE_EXPOSED_PORTS="$("$DOCKER_BIN" image inspect --format '{{range $key, $value := .Config.ExposedPorts}}{{$key}} {{end}}' "$IMAGE")"
|
|
echo "$DOCKER_IMAGE_EXPOSED_PORTS"
|
|
}
|
|
|
|
get_entrypoint_from_image() {
|
|
declare desc="return .Config.Entrypoint from passed image name"
|
|
local IMAGE="$1"
|
|
verify_image "$IMAGE"
|
|
local DOCKER_IMAGE_ENTRYPOINT="$("$DOCKER_BIN" image inspect --format '{{range .Config.Entrypoint}}{{.}} {{end}}' "$IMAGE")"
|
|
echo "ENTRYPOINT $DOCKER_IMAGE_ENTRYPOINT"
|
|
}
|
|
|
|
get_cmd_from_image() {
|
|
declare desc="return .Config.Cmd from passed image name"
|
|
local IMAGE="$1"
|
|
verify_image "$IMAGE"
|
|
local DOCKER_IMAGE_CMD="$("$DOCKER_BIN" image inspect --format '{{range .Config.Cmd}}{{.}} {{end}}' "$IMAGE")"
|
|
DOCKER_IMAGE_CMD="${DOCKER_IMAGE_CMD/\/bin\/sh -c/}"
|
|
echo "CMD $DOCKER_IMAGE_CMD"
|
|
}
|
|
|
|
extract_directive_from_dockerfile() {
|
|
declare desc="return requested directive from passed file path"
|
|
local FILE_PATH="$1"
|
|
local SEARCH_STRING="$2"
|
|
local FOUND_LINE=$(grep -E "^${SEARCH_STRING} " "$FILE_PATH" | tail -n1) || true
|
|
echo "$FOUND_LINE"
|
|
}
|
|
|
|
get_container_ports() {
|
|
declare desc="returns published ports from app containers"
|
|
local APP="$1"
|
|
local APP_CIDS="$(get_app_container_ids "$APP")"
|
|
local cid
|
|
|
|
for cid in $APP_CIDS; do
|
|
local container_ports="$("$DOCKER_BIN" container port "$cid" | awk '{ print $3 "->" $1}' | awk -F ":" '{ print $2 }')"
|
|
done
|
|
|
|
echo "$container_ports"
|
|
}
|
|
|
|
get_json_value() {
|
|
declare desc="return value of provided json key from a json stream on stdin"
|
|
# JSON_NODE should be expresses as either a top-level object that has no children
|
|
# or in the format of .json.node.path
|
|
local JSON_NODE="$1"
|
|
|
|
cat | jq -r "${JSON_NODE} | select (.!=null)" 2>/dev/null
|
|
}
|
|
|
|
strip_inline_comments() {
|
|
declare desc="removes bash-style comment from input line"
|
|
local line="$1"
|
|
local stripped_line="${line%[[:space:]]#*}"
|
|
|
|
echo "$stripped_line"
|
|
}
|
|
|
|
is_val_in_list() {
|
|
declare desc="return true if value ($1) is in list ($2) separated by delimiter ($3); delimiter defaults to comma"
|
|
local value="$1" list="$2" delimiter="${3:-,}"
|
|
local IFS="$delimiter" val_in_list=false
|
|
|
|
for val in $list; do
|
|
if [[ "$val" == "$value" ]]; then
|
|
val_in_list=true
|
|
fi
|
|
done
|
|
|
|
echo "$val_in_list"
|
|
}
|
|
|
|
fn-in-array() {
|
|
declare desc="return true if value ($1) is in list (all other arguments)"
|
|
|
|
local e
|
|
for e in "${@:2}"; do
|
|
[[ "$e" == "$1" ]] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
remove_val_from_list() {
|
|
declare desc="remove value ($1) from list ($2) separated by delimiter ($3) (delimiter defaults to comma) and return list"
|
|
local value="$1" list="$2" delimiter="${3:-,}"
|
|
list="${list//$value/}"
|
|
list="${list//$delimiter$delimiter/$delimiter}"
|
|
list="${list/#$delimiter/}"
|
|
list="${list/%$delimiter/}"
|
|
echo "$list"
|
|
}
|
|
|
|
add_val_to_list() {
|
|
declare desc="add value ($1) to list ($2) separated by delimiter ($3) (delimiter defaults to comma) and return list"
|
|
local value="$1" list="$2" delimiter="${3:-,}"
|
|
list+="${delimiter}$value"
|
|
echo "$list"
|
|
}
|
|
|
|
merge_dedupe_list() {
|
|
declare desc="combine lists ($1) separated by delimiter ($2) (delimiter defaults to comma), dedupe and return list"
|
|
local input_lists="$1" delimiter="${2:-,}"
|
|
|
|
local merged_list="$(tr "$delimiter" $'\n' <<<"$input_lists" | sort | uniq | xargs)"
|
|
echo "$merged_list"
|
|
}
|
|
|
|
acquire_app_deploy_lock() {
|
|
declare desc="acquire advisory lock for use in deploys"
|
|
local APP="$1"
|
|
local LOCK_TYPE="${2:-waiting}"
|
|
local APP_DEPLOY_LOCK_FILE="$DOKKU_ROOT/$APP/.deploy.lock"
|
|
local LOCK_WAITING_MSG="$APP currently has a deploy lock in place. Waiting..."
|
|
local LOCK_FAILED_MSG="$APP currently has a deploy lock in place. Exiting..."
|
|
|
|
acquire_advisory_lock "$APP_DEPLOY_LOCK_FILE" "$LOCK_TYPE" "$LOCK_WAITING_MSG" "$LOCK_FAILED_MSG"
|
|
}
|
|
|
|
release_app_deploy_lock() {
|
|
declare desc="release advisory lock used in deploys"
|
|
local APP="$1"
|
|
local APP_DEPLOY_LOCK_FILE="$DOKKU_ROOT/$APP/.deploy.lock"
|
|
|
|
release_advisory_lock "$APP_DEPLOY_LOCK_FILE"
|
|
}
|
|
|
|
acquire_advisory_lock() {
|
|
declare desc="acquire advisory lock"
|
|
local LOCK_FILE="$1" LOCK_TYPE="$2" LOCK_WAITING_MSG="$3" LOCK_FAILED_MSG="$4"
|
|
local LOCK_FD="200"
|
|
local SHOW_MSG=true
|
|
|
|
eval "exec $LOCK_FD>$LOCK_FILE"
|
|
if [[ "$LOCK_TYPE" == "waiting" ]]; then
|
|
while [[ $(
|
|
flock -n "$LOCK_FD" &>/dev/null
|
|
echo $?
|
|
) -ne 0 ]]; do
|
|
if [[ "$SHOW_MSG" == "true" ]]; then
|
|
echo "$LOCK_WAITING_MSG"
|
|
SHOW_MSG=false
|
|
fi
|
|
sleep 1
|
|
done
|
|
else
|
|
if ! flock -n "$LOCK_FD" &>/dev/null; then
|
|
dokku_log_warn "$LOCK_FAILED_MSG"
|
|
dokku_log_fail "Run 'apps:unlock' to release the existing deploy lock"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
release_advisory_lock() {
|
|
declare desc="release advisory lock"
|
|
local LOCK_FILE="$1"
|
|
local LOCK_FD="200"
|
|
|
|
flock -u "$LOCK_FD" && rm -f "$LOCK_FILE" &>/dev/null
|
|
}
|
|
|
|
suppress_output() {
|
|
declare desc="suppress all output from a given command unless there is an error"
|
|
local TMP_COMMAND_OUTPUT
|
|
TMP_COMMAND_OUTPUT=$(mktemp "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX")
|
|
trap "rm -rf '$TMP_COMMAND_OUTPUT' >/dev/null" RETURN
|
|
|
|
"$@" >"$TMP_COMMAND_OUTPUT" 2>&1 || {
|
|
local exit_code="$?"
|
|
cat "$TMP_COMMAND_OUTPUT"
|
|
return "$exit_code"
|
|
}
|
|
return 0
|
|
}
|
|
|
|
fn-migrate-config-to-property() {
|
|
declare desc="migrates deprecated config variables to property counterpart"
|
|
declare PLUGIN="$1" KEY="$2" CONFIG_VAR="$3" GLOBAL_CONFIG_VAR="$4"
|
|
|
|
# todo: refactor to remove config_unset usage
|
|
if ! declare -f -F config_unset >/dev/null; then
|
|
source "$PLUGIN_AVAILABLE_PATH/config/functions"
|
|
fi
|
|
|
|
if ! declare -f -F fn-plugin-property-write >/dev/null; then
|
|
source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
|
|
fi
|
|
|
|
if [[ -n "$GLOBAL_CONFIG_VAR" ]]; then
|
|
local value=$(plugn trigger config-get-global "$GLOBAL_CONFIG_VAR" || true)
|
|
if [[ -n "$value" ]]; then
|
|
dokku_log_info1 "Migrating deprecated global $GLOBAL_CONFIG_VAR to $PLUGIN $KEY property."
|
|
fn-plugin-property-write "$PLUGIN" --global "$KEY" "$value"
|
|
DOKKU_QUIET_OUTPUT=1 config_unset --global "$GLOBAL_CONFIG_VAR" || true
|
|
fi
|
|
fi
|
|
|
|
for app in $(dokku_apps "false"); do
|
|
local value=$(plugn trigger config-get "$app" "$CONFIG_VAR" || true)
|
|
if [[ -n "$value" ]]; then
|
|
dokku_log_info1 "Migrating deprecated $CONFIG_VAR to $PLUGIN $KEY property for $app."
|
|
fn-plugin-property-write "$PLUGIN" "$app" "$KEY" "$value"
|
|
DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$app" "$CONFIG_VAR" || true
|
|
fi
|
|
done
|
|
}
|