This makes standard use of shellcheck work without needing to provide extra configuration anywhere. Also remove use of inline 'shellcheck disable' calls that are already defined in the .shellcheckrc and don't need to be set inline.
236 lines
9.0 KiB
Bash
Executable File
236 lines
9.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Hook to check server against list of checks specified in CHECKS file.
|
|
#
|
|
# The CHECKS file may contain empty lines, comments (lines starting with #),
|
|
# settings (NAME=VALUE) and check instructions.
|
|
#
|
|
# The format of a check instruction is a path, optionally followed by the
|
|
# expected content. For example:
|
|
#
|
|
# / My Amazing App
|
|
# /stylesheets/index.css .body
|
|
# /scripts/index.js $(function()
|
|
# /images/logo.png
|
|
#
|
|
# To check an application that supports multiple hostnames, use relative URLs
|
|
# that include the hostname, for example:
|
|
#
|
|
# //admin.example.com Admin Dashboard
|
|
# //static.example.com/logo.png
|
|
#
|
|
# You can also specify the protocol to explicitly check HTTPS requests.
|
|
#
|
|
# The default behavior is to wait for 5 seconds before running the first check,
|
|
# and timeout each check to 30 seconds.
|
|
#
|
|
# By default, checks will be retried 5 times.
|
|
|
|
# You can change these by setting WAIT, TIMEOUT and ATTEMPTS to different values, for
|
|
# example:
|
|
#
|
|
# WAIT=30 # Wait 1/2 minute
|
|
# TIMEOUT=60 # Timeout after a minute
|
|
# ATTEMPTS=10 # retry checks 10 times
|
|
#
|
|
set -eo pipefail
|
|
[[ $DOKKU_TRACE ]] && set -x
|
|
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
|
|
source "$PLUGIN_AVAILABLE_PATH/checks/functions"
|
|
source "$PLUGIN_AVAILABLE_PATH/config/functions"
|
|
|
|
trigger-scheduler-docker-local-check-deploy() {
|
|
declare desc="scheduler-docker-local check-deploy plugin trigger"
|
|
declare trigger="check-deploy"
|
|
declare APP="$1" DOKKU_APP_CONTAINER_ID="$2" DOKKU_APP_CONTAINER_TYPE="$3" DOKKU_APP_LISTEN_PORT="$4" DOKKU_APP_LISTEN_IP="$5" CONTAINER_INDEX="$6"
|
|
|
|
local DOKKU_SCHEDULER=$(get_app_scheduler "$APP")
|
|
if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then
|
|
return
|
|
fi
|
|
|
|
if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -f "$DOKKU_ROOT/$APP/PORT" ]]; then
|
|
local DOKKU_APP_LISTEN_PORT=$(<"$DOKKU_ROOT/$APP/PORT")
|
|
fi
|
|
if [[ -z "$DOKKU_APP_LISTEN_IP" ]] && [[ -f "$DOKKU_ROOT/$APP/IP" ]]; then
|
|
local DOKKU_APP_LISTEN_IP=$(<"$DOKKU_ROOT/$APP/IP")
|
|
fi
|
|
if [[ -z "$DOKKU_APP_CONTAINER_ID" ]]; then
|
|
local DOKKU_APP_CIDS=($(get_app_container_ids "$APP"))
|
|
local DOKKU_APP_CONTAINER_ID=${DOKKU_APP_CIDS[0]}
|
|
fi
|
|
|
|
# source global and in-app envs to get DOKKU_CHECKS_WAIT and any other necessary vars
|
|
eval "$(config_export global)"
|
|
eval "$(config_export app "$APP")"
|
|
|
|
if [[ "$(is_app_proctype_checks_skipped "$APP" "$DOKKU_APP_CONTAINER_TYPE")" == "true" ]]; then
|
|
dokku_log_info2_quiet "Zero downtime checks have been skipped ($DOKKU_APP_CONTAINER_TYPE.$CONTAINER_INDEX)"
|
|
exit 0
|
|
fi
|
|
|
|
# Wait this many seconds (default 5) for server to start before running checks.
|
|
local WAIT="${DOKKU_CHECKS_WAIT:-5}"
|
|
# Wait this many seconds (default 30) for each response.
|
|
local TIMEOUT="${DOKKU_CHECKS_TIMEOUT:-30}"
|
|
# use this number of retries for checks
|
|
local ATTEMPTS="${DOKKU_CHECKS_ATTEMPTS:-5}"
|
|
|
|
local CHECK_DEPLOY_TMP_WORK_DIR=$(mktemp -d "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX")
|
|
local CHECKS_FILENAME=${CHECK_DEPLOY_TMP_WORK_DIR}/CHECKS
|
|
local IMAGE_TAG="$(get_running_image_tag "$APP")"
|
|
local IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG")
|
|
copy_from_image "$IMAGE" "CHECKS" "$CHECKS_FILENAME" 2>/dev/null || true
|
|
|
|
checks_check_deploy_cleanup() {
|
|
declare desc="cleans up CHECK_DEPLOY_TMP_WORK_DIR and print container output"
|
|
local id="$1"
|
|
rm -rf "$CHECK_DEPLOY_TMP_WORK_DIR" &>/dev/null || true
|
|
if [[ $id ]]; then
|
|
dokku_log_info2_quiet "Start of $APP container output ($DOKKU_APP_CONTAINER_TYPE.$CONTAINER_INDEX)"
|
|
dokku_container_log_verbose_quiet "$id"
|
|
dokku_log_info2_quiet "End of $APP container output ($DOKKU_APP_CONTAINER_TYPE.$CONTAINER_INDEX)"
|
|
fi
|
|
}
|
|
trap "checks_check_deploy_cleanup $DOKKU_APP_CONTAINER_ID" RETURN INT TERM EXIT
|
|
|
|
# We allow custom check for web instances only
|
|
if [[ ! -s "${CHECKS_FILENAME}" ]] || [[ "$DOKKU_APP_CONTAINER_TYPE" != "web" ]]; then
|
|
rm -rf "$CHECK_DEPLOY_TMP_WORK_DIR" &>/dev/null || true
|
|
|
|
# simple default check to see if the container stuck around
|
|
local DOKKU_DEFAULT_CHECKS_WAIT="${DOKKU_DEFAULT_CHECKS_WAIT:-10}"
|
|
dokku_log_verbose "Waiting for $DOKKU_DEFAULT_CHECKS_WAIT seconds ($DOKKU_APP_CONTAINER_TYPE.$CONTAINER_INDEX)"
|
|
sleep "$DOKKU_DEFAULT_CHECKS_WAIT"
|
|
|
|
! (is_container_status "$DOKKU_APP_CONTAINER_ID" "Running") && dokku_log_fail "App container failed to start ($DOKKU_APP_CONTAINER_TYPE.$CONTAINER_INDEX)"
|
|
local container_restarts="$("$DOCKER_BIN" container inspect --format "{{ .RestartCount }}" "$DOKKU_APP_CONTAINER_ID")"
|
|
if [[ $container_restarts -ne 0 ]]; then
|
|
"$DOCKER_BIN" container update --restart=no "$DOKKU_APP_CONTAINER_ID" &>/dev/null || true
|
|
"$DOCKER_BIN" container stop "$DOKKU_APP_CONTAINER_ID" || true
|
|
dokku_log_fail "App container failed to start ($DOKKU_APP_CONTAINER_TYPE.$CONTAINER_INDEX)"
|
|
fi
|
|
|
|
trap - EXIT
|
|
dokku_log_verbose "Default container check successful ($DOKKU_APP_CONTAINER_TYPE.$CONTAINER_INDEX)" && exit 0
|
|
fi
|
|
|
|
local NEW_CHECKS_FILENAME="${CHECK_DEPLOY_TMP_WORK_DIR}/NEW_CHECKS"
|
|
template_checks() {
|
|
eval "$(config_export app "$APP" --merged)"
|
|
sigil -f "$CHECKS_FILENAME" | cat -s >"$NEW_CHECKS_FILENAME"
|
|
}
|
|
|
|
template_checks
|
|
CHECKS_FILENAME="$NEW_CHECKS_FILENAME"
|
|
|
|
# Reads name/value pairs, sets the WAIT and TIMEOUT variables
|
|
exec <"$CHECKS_FILENAME"
|
|
local line
|
|
local NAME
|
|
local VALUE
|
|
while read -r line; do
|
|
line=$(strip_inline_comments "$line")
|
|
# Name/value pair
|
|
if [[ "$line" =~ ^.+= ]]; then
|
|
NAME=${line%=*}
|
|
VALUE=${line#*=}
|
|
[[ "$NAME" == "WAIT" ]] && local WAIT=$VALUE
|
|
[[ "$NAME" == "TIMEOUT" ]] && local TIMEOUT=$VALUE
|
|
[[ "$NAME" == "ATTEMPTS" ]] && local ATTEMPTS=$VALUE
|
|
fi
|
|
done
|
|
|
|
exec <"$CHECKS_FILENAME"
|
|
local CHECK_URL
|
|
local EXPECTED
|
|
local FAILEDCHECKS=0
|
|
while read -r CHECK_URL EXPECTED; do
|
|
# Ignore empty lines and lines starting with #
|
|
[[ -z "$CHECK_URL" || "$CHECK_URL" =~ ^\# ]] && continue
|
|
# Ignore if it's not a URL in a supported format
|
|
! [[ "$CHECK_URL" =~ ^(http(s)?:)?\/.* ]] && continue
|
|
|
|
if [[ "$CHECK_URL" =~ ^https?: ]]; then
|
|
local URL_PROTOCOL=${CHECK_URL%:*}
|
|
local CHECK_URL=${CHECK_URL#*:}
|
|
else
|
|
local URL_PROTOCOL="http"
|
|
fi
|
|
|
|
if [[ "$CHECK_URL" =~ ^//.+ ]]; then
|
|
# To test a URL with specific host name, we still make request to localhost,
|
|
# but we set Host header to $SEND_HOST.
|
|
#
|
|
# The pattern is
|
|
# //SEND_HOST/PATHNAME
|
|
local UNPREFIXED=${CHECK_URL#//}
|
|
local URL_HOSTNAME=${UNPREFIXED%%/*}
|
|
local URL_PATHNAME=${UNPREFIXED#$URL_HOSTNAME}
|
|
|
|
local HEADERS="-H Host:$URL_HOSTNAME"
|
|
else
|
|
local URL_HOSTNAME=localhost
|
|
local URL_PATHNAME=$CHECK_URL
|
|
fi
|
|
|
|
# -q Do not use .curlrc (must come first)
|
|
# --compressed Test compression handled correctly
|
|
# --fail Fail on server errors (4xx, 5xx)
|
|
# --location Follow redirects
|
|
# --noproxy Do not use http_proxy env variable
|
|
local CURL_OPTIONS="-q --compressed --fail --location --noproxy $DOKKU_APP_LISTEN_IP --max-time $TIMEOUT"
|
|
|
|
# Set X-Forwarded-Proto header if TLS is enabled.
|
|
local SSL="$DOKKU_ROOT/$APP/tls"
|
|
if [[ -e "$SSL/server.crt" && -e "$SSL/server.key" ]]; then
|
|
local CURL_OPTIONS+=" -H X-Forwarded-Proto:https"
|
|
fi
|
|
|
|
# LOG_URL is used in verbose output below
|
|
local LOG_URL="$URL_PROTOCOL://$URL_HOSTNAME$URL_PATHNAME"
|
|
# CURL_ARGS includes the behinds the scenes options for hitting the container service directly
|
|
local CURL_ARGS="$CURL_OPTIONS $URL_PROTOCOL://$DOKKU_APP_LISTEN_IP:$DOKKU_APP_LISTEN_PORT$URL_PATHNAME $HEADERS"
|
|
|
|
dokku_log_verbose "CHECKS expected result: $LOG_URL => \"$EXPECTED\" ($DOKKU_APP_CONTAINER_TYPE.$CONTAINER_INDEX)"
|
|
|
|
local ATTEMPT=0
|
|
local SUCCESS=0
|
|
until [[ $SUCCESS == 1 || $ATTEMPT -ge $ATTEMPTS ]]; do
|
|
local ATTEMPT=$((ATTEMPT + 1))
|
|
dokku_log_verbose "Attempt $ATTEMPT/$ATTEMPTS. Waiting for $WAIT seconds ($DOKKU_APP_CONTAINER_TYPE.$CONTAINER_INDEX)"
|
|
sleep "$WAIT"
|
|
|
|
# Capture HTTP response or CURL error message
|
|
if OUTPUT=$(curl -# $CURL_ARGS 2>&1); then
|
|
# OUTPUT contains the HTTP response
|
|
# shellcheck disable=SC2076
|
|
if [[ "$OUTPUT" =~ "$EXPECTED" ]]; then
|
|
SUCCESS=1
|
|
break
|
|
fi
|
|
dokku_log_warn "$LOG_URL: expected to but did not find: \"$EXPECTED\" ($DOKKU_APP_CONTAINER_TYPE.$CONTAINER_INDEX)"
|
|
else
|
|
# Failed to connect/no response, OUTPUT contains error message
|
|
dokku_log_warn "$OUTPUT"
|
|
fi
|
|
|
|
dokku_log_warn "Check attempt $ATTEMPT/$ATTEMPTS failed ($DOKKU_APP_CONTAINER_TYPE.$CONTAINER_INDEX)"
|
|
done
|
|
|
|
if [[ $SUCCESS -ne 1 ]]; then
|
|
FAILEDCHECKS=$((FAILEDCHECKS + 1))
|
|
fi
|
|
done
|
|
|
|
if [[ $FAILEDCHECKS -gt 0 ]]; then
|
|
dokku_log_fail "Could not start due to $FAILEDCHECKS failed checks ($DOKKU_APP_CONTAINER_TYPE.$CONTAINER_INDEX)"
|
|
exit 1
|
|
fi
|
|
|
|
trap - EXIT
|
|
dokku_log_verbose "All checks successful ($DOKKU_APP_CONTAINER_TYPE.$CONTAINER_INDEX)"
|
|
}
|
|
|
|
trigger-scheduler-docker-local-check-deploy "$@"
|