Files
dokku/plugins/common/functions
Jose Diaz-Gonzalez 5d5d56c560 refactor: remove public exposure of DOKKU_APP_TYPE in favor of builder detected property
The DOKKU_APP_TYPE has long since ceased to be the correct way to specify the builder for the application. It's only usage has been during the detection phase, specifically to ensure that the herokuish plugin injects the correct docker arguments during the build. As such, it is safe to migrate away to a property in a patch release.

Users that seek to set a specific builder should use 'dokku builder:set $APP selected' instead.

Refs #7863
2025-08-21 23:59:41 -04:00

955 lines
27 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_FORCE_TTY" == "true" ]]; then
return 0
fi
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-force-arm64-image() {
declare IMAGE="$1"
local image_architecture="$("$DOCKER_BIN" image inspect --format '{{.Architecture}}' "$IMAGE")"
if [[ "$image_architecture" == "amd64" ]] && [[ "$(dpkg --print-architecture 2>/dev/null || true)" != "amd64" ]]; then
return 0
fi
return 1
}
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
}
fn-plugn-trigger-exists() {
declare desc="checks if a plugn trigger exists"
declare TRIGGER_NAME="$1"
local exists
exists="$("$PLUGIN_CORE_AVAILABLE_PATH/common/common" --quiet plugn-trigger-exists "$TRIGGER_NAME" || true)"
if [[ "$exists" == "true" ]]; then
return
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"
if ! "$PLUGIN_CORE_AVAILABLE_PATH/common/common" copy-from-image "$APP" "$IMAGE" "$SRC_FILE" "$DST_FILE"; then
return 1
fi
}
copy_dir_from_image() {
declare desc="copy a directory from named image to destination"
declare IMAGE="$1" SRC_DIR="$2" DST_DIR="$3"
if ! "$PLUGIN_CORE_AVAILABLE_PATH/common/common" copy-dir-from-image "$APP" "$IMAGE" "$SRC_DIR" "$DST_DIR"; then
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
plugn trigger builder-set-property "$APP" "detected" "$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"
}
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_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_LIB_ROOT/data/apps/$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_LIB_ROOT/data/apps/$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
echo "$DOKKU_PID" >"$LOCK_FILE"
}
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")
"$@" >"$TMP_COMMAND_OUTPUT" 2>&1 || {
local exit_code="$?"
cat "$TMP_COMMAND_OUTPUT"
rm -rf "$TMP_COMMAND_OUTPUT" >/dev/null
return "$exit_code"
}
rm -rf "$TMP_COMMAND_OUTPUT" >/dev/null
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
}