Files
dokku/plugins/common/functions
Jose Diaz-Gonzalez a1c768b9b3 refactor: drop web installer in favor of setup via cli
This makes the installation a bit more secure by ensuring a user does not accidentally expose a way for unauthorized users to add new ssh keys to the system.

Additionally, this removes the extra HOSTNAME file to make the initial install process easier (that file was not modifiable by any dokku commands.

Closes #2247
2021-08-07 16:36:41 -04:00

1156 lines
34 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"
local INSTALLED_APPS=$(find "$DOKKU_ROOT" -follow -maxdepth 1 -mindepth 1 -type d ! -name '.*' -printf "%f\n" 2>/dev/null | sort) || (dokku_log_fail "You haven't deployed any applications yet")
[[ $INSTALLED_APPS ]] && 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"
local VALID_NEW=false
local VALID_OLD=false
if fn-is-valid-app-name "$APP" 2>/dev/null; then
VALID_NEW=true
fi
if fn-is-valid-app-name-old "$APP" 2>/dev/null; then
VALID_OLD=true
fi
if [[ "$VALID_NEW" == "false" ]] && [[ "$VALID_OLD" == "false" ]]; then
dokku_log_fail "App name must begin with lowercase alphanumeric character, and cannot include uppercase characters, colons, or underscores"
fi
[[ ! -d "$DOKKU_ROOT/$APP" ]] && DOKKU_FAIL_EXIT_CODE=20 dokku_log_fail "App $APP does not exist"
return 0
}
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"
IMAGE_REPO="$3"
local IMAGE_REMOTE_REPOSITORY=$(plugn trigger deployed-app-repository "$APP")
local NEW_IMAGE_TAG=$(plugn trigger deployed-app-image-tag "$APP")
local NEW_IMAGE_REPO=$(plugn trigger deployed-app-image-repo "$APP")
[[ -n "$NEW_IMAGE_REPO" ]] && IMAGE_REPO="$NEW_IMAGE_REPO"
[[ -n "$NEW_IMAGE_TAG" ]] && IMAGE_TAG="$NEW_IMAGE_TAG"
[[ -z "$IMAGE_REPO" ]] && IMAGE_REPO=$(get_app_image_repo "$APP")
[[ -z "$IMAGE_TAG" ]] && IMAGE_TAG="latest"
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"
local DOKKU_APP_SCHEDULER DOKKU_GLOBAL_SCHEDULER DOKKU_SCHEDULER
if [[ "$APP" == "--global" ]]; then
APP=""
fi
source "$PLUGIN_AVAILABLE_PATH/config/functions"
[[ -n "$APP" ]] && DOKKU_APP_SCHEDULER="$(config_get "$APP" DOKKU_SCHEDULER || true)"
DOKKU_GLOBAL_SCHEDULER="$(config_get --global DOKKU_SCHEDULER || true)"
DOKKU_SCHEDULER=${DOKKU_APP_SCHEDULER:="$DOKKU_GLOBAL_SCHEDULER"}
if [[ -z "$DOKKU_SCHEDULER" ]]; then
DOKKU_SCHEDULER="docker-local"
fi
echo "$DOKKU_SCHEDULER"
}
get_running_image_tag() {
declare desc="retrieve current image tag for a given app. returns empty string if no deployed containers are found"
local APP="$1"
local CIDS=($(get_app_container_ids "$APP"))
local RUNNING_IMAGE_TAG=$("$DOCKER_BIN" container inspect --format '{{ .Config.Image }}' "${CIDS[0]}" 2>/dev/null | awk -F: '{ print $2 }' || echo '')
echo "$RUNNING_IMAGE_TAG"
}
is_image_cnb_based() {
declare desc="returns true if app image is based on cnb"
declare IMAGE="$1"
if [[ -z "$IMAGE" ]]; then
return 1
fi
CNB_STACK_ID="$("$DOCKER_BIN" image inspect --format '{{ index .Config.Labels "io.buildpacks.stack.id" }}' "$IMAGE")"
if [[ -n "$CNB_STACK_ID" ]]; then
return 0
fi
return 1
}
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"
local DOKKU_APP_USER
local USER_VALUE
if [[ -z "$IMAGE" ]]; then
return 1
fi
if is_image_cnb_based "$IMAGE"; then
return 0
fi
if [[ -n "$APP" ]]; then
DOKKU_APP_USER=$(config_get "$APP" DOKKU_APP_USER || true)
fi
DOKKU_APP_USER=${DOKKU_APP_USER:="herokuishuser"}
# due to how the build process works, all herokuish images have the Environment variable USER=$DOKKU_APP_USER
USER_VALUE="$("$DOCKER_BIN" image inspect --format '{{range .Config.Env}}{{if eq . "USER='"$DOKKU_APP_USER"'"}}{{println .}}{{end}}{{end}}' "$IMAGE")"
[[ "$USER_VALUE" == "" ]] && return 1
return 0
}
get_docker_version() {
CLIENT_VERSION_STRING="$("$DOCKER_BIN" version --format "{{ .Client.Version }}")"
echo "$CLIENT_VERSION_STRING"
}
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
;;
--detach)
dokku_log_warn_quiet "Deprecated: use run:detached instead of using the --detach flag"
export DOKKU_DETACH_CONTAINER=1
;;
--trace)
export DOKKU_TRACE=1
;;
--rm-container | --rm)
dokku_log_warn_quiet "Deprecated: all run containers are now removed on exit by default"
;;
--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 2>&1 || 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_running() {
declare desc="return 0 if given docker container id is in running state"
declare deprecated=true
declare CID="$1"
local CONTAINER_STATUS
dokku_log_warn "Deprecated: common#is_container_status"
CONTAINER_STATUS=$("$DOCKER_BIN" container inspect --format '{{.State.Running}}' "$CID" || true)
if [[ "$CONTAINER_STATUS" == "true" ]]; then
return 0
fi
return 1
}
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
}
is_app_running() {
declare desc="return 0 if given app has a running container"
declare deprecated=true
local APP="$1"
dokku_log_warn "Deprecated: ps#fn-ps-is-app-running"
local APP_RUNNING_CONTAINER_IDS=$(get_app_running_container_ids "$APP" 2>/dev/null)
if [[ -n "$APP_RUNNING_CONTAINER_IDS" ]]; then
return 0
else
return 1
fi
}
dokku_build() {
declare desc="build phase"
declare APP="$1" IMAGE_SOURCE_TYPE="$2" SOURCECODE_WORK_DIR="$3"
plugn trigger builder-build "$IMAGE_SOURCE_TYPE" "$APP" "$SOURCECODE_WORK_DIR"
}
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"
source "$PLUGIN_AVAILABLE_PATH/config/functions"
verify_app_name "$APP"
local DOKKU_SCHEDULER=$(get_app_scheduler "$APP")
plugn trigger scheduler-deploy "$DOKKU_SCHEDULER" "$APP" "$IMAGE_TAG"
}
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")
local DOKKU_DOCKERFILE_PORTS
if verify_image "$IMAGE"; then
if is_image_herokuish_based "$IMAGE" "$APP"; then
local IMAGE_SOURCE_TYPE="herokuish"
else
local IMAGE_SOURCE_TYPE="dockerfile"
DOKKU_DOCKERFILE_PORTS=$(config_get "$APP" DOKKU_DOCKERFILE_PORTS || true)
if [[ -z "$DOKKU_DOCKERFILE_PORTS" ]]; then
local DOCKER_IMAGE_PORTS=$(get_exposed_ports_from_image "$IMAGE")
[[ -n "$DOCKER_IMAGE_PORTS" ]] && config_set --no-restart "$APP" DOKKU_DOCKERFILE_PORTS="$DOCKER_IMAGE_PORTS"
fi
fi
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..."
plugn trigger pre-deploy "$APP" "$IMAGE_TAG"
dokku_release "$APP" "$IMAGE_SOURCE_TYPE" "$IMAGE_TAG"
if [[ "$DOKKU_SKIP_DEPLOY" != "true" ]]; then
dokku_log_info1 "Deploying $APP..."
cmd-deploy "$APP" "$IMAGE_TAG"
dokku_log_info2 "Application deployed:"
get_app_urls urls "$APP" | 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"
dokku_build "$APP" "$IMAGE_SOURCE_TYPE" "$TMP_WORK_DIR"
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
source "$PLUGIN_AVAILABLE_PATH/config/functions"
if [[ "$FORCE_CLEANUP" != "true" ]]; then
[[ -n "$APP" ]] && DOKKU_APP_SKIP_CLEANUP="$(config_get "$APP" DOKKU_SKIP_CLEANUP || true)"
if [[ -n "$DOKKU_SKIP_CLEANUP" ]] || [[ -n "$DOKKU_APP_SKIP_CLEANUP" ]]; then
dokku_log_info1 "DOKKU_SKIP_CLEANUP set. Skipping dokku cleanup"
return
fi
fi
dokku_log_info1 "Cleaning up..."
local DOKKU_SCHEDULER=$(get_app_scheduler "$APP")
if [[ "$APP" == "--global" ]]; then
APP=""
fi
plugn trigger scheduler-docker-cleanup "$DOKKU_SCHEDULER" "$APP" "$FORCE_CLEANUP"
if [[ -n "$APP" ]]; then
# delete all non-running containers
# shellcheck disable=SC2046
"$DOCKER_BIN" container rm $("$DOCKER_BIN" container list --all --filter "status=exited" --filter "label=$DOKKU_CONTAINER_LABEL" --filter "label=com.dokku.app-name=$APP" --quiet) &>/dev/null || true
# delete all dead containers
# shellcheck disable=SC2046
"$DOCKER_BIN" container rm $("$DOCKER_BIN" container list --all --filter "status=dead" --filter "label=$DOKKU_CONTAINER_LABEL" --filter "label=com.dokku.app-name=$APP" --quiet) &>/dev/null || true
# delete danging images
# shellcheck disable=SC2046
"$DOCKER_BIN" image rm $("$DOCKER_BIN" image list --filter 'dangling=true' --filter "label=com.dokku.app-name=$APP" --quiet) &>/dev/null || true
# delete unused images
"$DOCKER_BIN" image prune --all --filter "label=com.dokku.app-name=$APP" --force &>/dev/null || true
else
# delete all non-running containers
# shellcheck disable=SC2046
"$DOCKER_BIN" container rm $("$DOCKER_BIN" container list --all --filter "status=exited" --filter "label=$DOKKU_CONTAINER_LABEL" --quiet) &>/dev/null || true
# delete all dead containers
# shellcheck disable=SC2046
"$DOCKER_BIN" container rm $("$DOCKER_BIN" container list --all --filter "status=dead" --filter "label=$DOKKU_CONTAINER_LABEL" --quiet) &>/dev/null || true
# delete danging images
# shellcheck disable=SC2046
"$DOCKER_BIN" image rm $("$DOCKER_BIN" image list --filter 'dangling=true' --quiet) &>/dev/null || true
fi
}
get_available_port() {
declare desc="returns first currently unused port > 1024"
while true; do
local port=$(shuf -i 1025-65535 -n 1)
if ! nc -z 0.0.0.0 "$port"; then
echo "$port"
return 0
else
continue
fi
done
}
dokku_auth() {
declare desc="calls user-auth plugin trigger"
export SSH_USER=${SSH_USER:=$USER}
export SSH_NAME=${NAME:="default"}
# this plugin trigger exists in the core `20_events` plugin
if [[ $(find "$PLUGIN_PATH"/enabled/*/user-auth 2>/dev/null | wc -l) == 1 ]]; 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"
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"
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_app_raw_tcp_ports() {
declare desc="extracts raw tcp port numbers from DOCKERFILE_PORTS config variable"
source "$PLUGIN_AVAILABLE_PATH/config/functions"
local APP="$1"
local DOCKERFILE_PORTS="$(config_get "$APP" DOKKU_DOCKERFILE_PORTS)"
for p in $DOCKERFILE_PORTS; do
if [[ ! "$p" =~ .*udp.* ]]; then
p=${p//\/tcp/}
raw_tcp_ports+="$p "
fi
done
local raw_tcp_ports="$(echo "$raw_tcp_ports" | xargs)"
echo "$raw_tcp_ports"
}
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_app_urls() {
declare desc="print an app's available urls"
declare URL_TYPE="$1" APP="$2"
local urls
urls=$(plugn trigger app-urls "$APP" "$URL_TYPE")
if [[ -n "$urls" ]]; then
echo "$urls" | sort
else
internal_get_app_urls "$URL_TYPE" "$APP" | sort
fi
}
internal_get_app_urls() {
declare desc="print an app's available urls"
source "$PLUGIN_AVAILABLE_PATH/certs/functions"
source "$PLUGIN_AVAILABLE_PATH/config/functions"
local APP="$2"
local URLS_FILE="$DOKKU_ROOT/$APP/URLS"
local DOKKU_PROXY_PORT_MAP=$(config_get "$APP" DOKKU_PROXY_PORT_MAP || true)
if [[ -s "$URLS_FILE" ]]; then
local app_urls="$(grep -v -E "^#" "$URLS_FILE")"
if [[ -n "$DOKKU_PROXY_PORT_MAP" ]]; then
local port_map app_vhost
local app_vhosts=$(plugn trigger domains-list "$APP")
for port_map in $DOKKU_PROXY_PORT_MAP; do
local scheme="$(awk -F ':' '{ print $1 }' <<<"$port_map")"
local listen_port="$(awk -F ':' '{ print $2 }' <<<"$port_map")"
for app_vhost in $app_vhosts; do
if [[ "$listen_port" != "80" ]] && [[ "$listen_port" != "443" ]]; then
port_urls+=" $scheme://$app_vhost:$listen_port "
else
port_urls+=" $scheme://$app_vhost "
fi
done
done
fi
local port_urls="$(echo "$port_urls" | xargs)"
local URLS="$(merge_dedupe_list "$port_urls $app_urls" " ")"
case "$1" in
url)
if is_ssl_enabled "$APP"; then
echo "$URLS" | tr ' ' '\n' | grep https | head -n1
else
echo "$URLS" | tr ' ' '\n' | head -n1
fi
;;
urls)
echo "$URLS" | tr ' ' '\n' | sort
;;
esac
else
if [[ -s "$DOKKU_ROOT/VHOST" ]]; then
while read -r VHOST || [[ -n "$VHOST" ]]; do
internal_get_app_url_with_vhost "$APP" "$VHOST"
done <"$DOKKU_ROOT/VHOST"
else
internal_get_app_url_with_vhost "$APP" "$(hostname -f)"
fi
fi
}
internal_get_app_url_with_vhost() {
declare APP="$1" VHOST="$2"
local RAW_TCP_PORTS="$(get_app_raw_tcp_ports "$APP")"
local DOKKU_PROXY_PORT_MAP=$(config_get "$APP" DOKKU_PROXY_PORT_MAP || true)
local SCHEME="http"
local SSL="$DOKKU_ROOT/$APP/tls"
if [[ -e "$SSL/server.crt" && -e "$SSL/server.key" ]]; then
local SCHEME="https"
fi
if [[ "$(plugn trigger proxy-is-enabled "$APP")" == "false" ]]; then
if [[ -n "$RAW_TCP_PORTS" ]]; then
local APP_CONTAINER_PORTS="$(get_container_ports "$APP")"
local app_port
for app_port in $APP_CONTAINER_PORTS; do
echo "$VHOST:$app_port (container)"
done
else
local DOKKU_APP_WEB_LISTENERS PORT
DOKKU_APP_WEB_LISTENERS="$(plugn trigger network-get-listeners "$APP" "web" | xargs)"
for DOKKU_APP_WEB_LISTENER in $DOKKU_APP_WEB_LISTENERS; do
PORT="$(echo "$DOKKU_APP_WEB_LISTENER" | cut -d ':' -f2)"
echo "$SCHEME://$VHOST:$PORT (container)"
done
shopt -u nullglob
fi
elif [[ -n "$DOKKU_PROXY_PORT_MAP" ]]; then
local port_map
for port_map in $DOKKU_PROXY_PORT_MAP; do
local scheme="$(awk -F ':' '{ print $1 }' <<<"$port_map")"
local listen_port="$(awk -F ':' '{ print $2 }' <<<"$port_map")"
echo "$scheme://$VHOST:$listen_port"
done
elif [[ -n "$RAW_TCP_PORTS" ]]; then
for p in $RAW_TCP_PORTS; do
echo "http://$VHOST:$p"
done
else
echo "$SCHEME://$VHOST"
fi
}
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 git/tar 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 git/tar 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
}