diff --git a/dokku b/dokku index ea291e8ffbb..467d0f759cb 100755 --- a/dokku +++ b/dokku @@ -86,7 +86,6 @@ case "$1" in port=$(docker port $id 5000 | sed 's/[0-9.]*://') ipaddr=127.0.0.1 fi - echo $ipaddr > "$DOKKU_ROOT/$APP/IP" # if we can't post-deploy successfully, kill new container kill_new() { @@ -98,16 +97,17 @@ case "$1" in # run checks first, then post-deploy hooks, which switches Nginx traffic trap kill_new INT TERM EXIT dokku_log_info1 "Running pre-flight checks" - pluginhook check-deploy $id $APP $port ${ipaddr:-localhost} + pluginhook check-deploy $APP $port $ipaddr $id + trap - INT TERM EXIT # now using the new container echo $id > "$DOKKU_ROOT/$APP/CONTAINER" + echo $ipaddr > "$DOKKU_ROOT/$APP/IP" echo $port > "$DOKKU_ROOT/$APP/PORT" echo "http://$(< "$DOKKU_ROOT/HOSTNAME"):$port" > "$DOKKU_ROOT/$APP/URL" dokku_log_info1 "Running post-deploy" pluginhook post-deploy $APP $port $ipaddr - trap - INT TERM EXIT # kill the old container if [[ -n "$oldid" ]]; then diff --git a/plugins/checks/check-deploy b/plugins/checks/check-deploy index 5e0105f3a66..f7338abe652 100755 --- a/plugins/checks/check-deploy +++ b/plugins/checks/check-deploy @@ -1,76 +1,163 @@ #!/usr/bin/env bash -# Hook to check server against list of checks specified in CHECKS file. Each -# check is a relative path and, optionally, expected content. +# 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: # -# For example: # / My Amazing App # /stylesheets/index.css .body # /scripts/index.js $(function() # /images/logo.png # -# Waits 5 seconds, giving server time to start, before running the checks. For -# shorter/longer wait, change the DOKKU_CHECKS_WAIT environment variable (value -# in seconds). +# 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 minutes. +# +# You can change these by setting WAIT and TIMEOUT to different values, for +# example: +# +# WAIT=30 # Wait 1/2 minute +# TIMEOUT=60 # Timeout after a minute +# set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$(dirname $0)/../common/functions" + +APP="$1"; DOKKU_APP_LISTEN_PORT="$2"; DOKKU_APP_LISTEN_IP="$3"; DOKKU_APP_CONTAINER_ID="$4" +if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -f "$DOKKU_ROOT/$APP/PORT" ]]; then + DOKKU_APP_LISTEN_PORT=$(< "$DOKKU_ROOT/$APP/PORT") +fi +if [[ -z "$DOKKU_APP_LISTEN_IP" ]] && [[ -f "$DOKKU_ROOT/$APP/IP" ]]; then + DOKKU_APP_LISTEN_IP=$(< "$DOKKU_ROOT/$APP/IP") +fi +if [[ -z "$DOKKU_APP_CONTAINER_ID" ]] && [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]]; then + DOKKU_APP_CONTAINER_ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") +fi -CONTAINERID="$1"; APP="$2"; PORT="$3" ; HOSTNAME="${4:-localhost}" # source in app env to get DOKKU_CHECKS_WAIT and any other necessary vars [[ -f "$DOKKU_ROOT/ENV" ]] && source $DOKKU_ROOT/ENV -[[ -f "$DOKKU_ROOT/$APP/ENV" ]] && source $DOKKU_ROOT/$APP/ENV -# echo "DOKKU_CHECKS_WAIT is $DOKKU_CHECKS_WAIT" -FILENAME="$DOKKU_ROOT/$APP/CHECKS" + +# Wait this many seconds (default 5) for server to start before running checks. WAIT="${DOKKU_CHECKS_WAIT:-5}" +# Wait this many seconds (default 30) for each response. +TIMEOUT="${DOKKU_CHECKS_TIMEOUT:-30}" + # try to copy CHECKS from container if not in APP dir & quit gracefully if it doesn't exist # 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 [[ ! -f "$FILENAME" ]] ; then - echo " check-deploy: $FILENAME not found. attempting to retrieve it from container ..." - TMPDIR=$(mktemp -d /tmp/CHECKS.XXXXX) - docker cp $CONTAINERID:/app/CHECKS $TMPDIR 2> /dev/null || true - if [[ ! -s "${TMPDIR}/CHECKS" ]] ; then - echo " CHECKS file not found in container. skipping checks." +TMPDIR=$(mktemp -d /tmp/CHECKS.XXXXX) +docker cp $DOKKU_APP_CONTAINER_ID:/app/CHECKS $TMPDIR 2> /dev/null || true +if [[ ! -s "${TMPDIR}/CHECKS" ]] ; then + dokku_log_verbose "CHECKS file not found in container: skipping checks" rm -rf $TMPDIR exit 0 - else - echo " CHECKS file found in container" - FILENAME=${TMPDIR}/CHECKS - - cleanup() { - echo " removing CHECKS file copied from container" - rm -rf $TMPDIR - } - trap cleanup EXIT - fi fi -echo "Waiting $WAIT seconds ..." + +FILENAME=${TMPDIR}/CHECKS + +cleanup() { + rm -rf $TMPDIR + if [[ $DOKKU_TRACE ]] ; then + dokku_log_info1 "Begin server log ..." + docker logs $DOKKU_APP_CONTAINER_ID + dokku_log_info1 "End server log\n" + fi +} +trap cleanup EXIT + + +# Reads name/value pairs, sets the WAIT and TIMEOUT variables +exec < "$FILENAME" +while read LINE ; do + # Name/value pair + if [[ "$LINE" =~ ^.+= ]] ; then + TRIM=${LINE%#*} + NAME=${TRIM%=*} + VALUE=${TRIM#*=} + [[ "$NAME" = "WAIT" ]] && WAIT=$VALUE + [[ "$NAME" = "TIMEOUT" ]] && TIMEOUT=$VALUE + fi +done + + +dokku_log_info1 "Waiting for $WAIT seconds ..." sleep $WAIT + # -q do not use .curlrc (must come first) # --compressed Test compression handled correctly # --fail Fail on server errors (4xx, 5xx) # --location Follow redirects -CURL_OPTIONS="-q --compressed --fail --location --max-time 30" +CURL_OPTIONS="-q --compressed --fail --location --max-time $TIMEOUT" -while read PATHNAME EXPECTED; do + +exec < "$FILENAME" +while read CHECK_URL EXPECTED ; do # Ignore empty lines and lines starting with # - [[ -z "$PATHNAME" || "$PATHNAME" =~ ^# ]] && continue + [[ -z "$CHECK_URL" || "$CHECK_URL" =~ ^\# ]] && continue + # Ignore variables + [[ "$CHECK_URL" =~ ^.+= ]] && continue + + if [[ "$CHECK_URL" =~ ^https?: ]] ; then + URL_PROTOCOL=${CHECK_URL%:*} + CHECK_URL=${CHECK_URL#*:} + else + URL_PROTOCOL="http" + fi - URL="http://$HOSTNAME:$PORT$PATHNAME" + 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 + UNPREFIXED=${CHECK_URL#//} + URL_HOSTNAME=${UNPREFIXED%%/*} + URL_PATHNAME=${UNPREFIXED#$URL_HOSTNAME} - echo "checking with: curl $CURL_OPTIONS $URL" - HTML=$(curl $CURL_OPTIONS $URL) - if [[ -n "$EXPECTED" && ! "$HTML" =~ $EXPECTED ]] ; then - echo -e "\033[31m\033[1m$URL: expected to but did not find: \"$EXPECTED\"\033[0m" - exit 1 + HEADERS="-H Host:$URL_HOSTNAME" else - echo -e "\033[32m\033[1m$URL => \"$EXPECTED\"\033[0m" + URL_HOSTNAME=localhost + URL_PATHNAME=$CHECK_URL fi -done < "$FILENAME" -echo -e "\033[32m\033[1mAll checks successful!\033[0m" + # This URL will show up in the messages + LOG_URL="$URL_PROTOCOL://$URL_HOSTNAME$URL_PATHNAME" + # And how we formulate the CURL request + CURL_ARGS="$CURL_OPTIONS $URL_PROTOCOL://$DOKKU_APP_LISTEN_IP:$DOKKU_APP_LISTEN_PORT$URL_PATHNAME $HEADERS" + + dokku_log_verbose "$LOG_URL => \"$EXPECTED\"" + [[ $DOKKU_TRACE ]] && dokku_log_verbose "$ curl $CURL_ARGS" + + # Capture HTTP response or CURL error message + if OUTPUT=$(curl -# $CURL_ARGS 2>&1) ; then + # OUTPUT contains the HTTP response + if [[ ! "$OUTPUT" =~ $EXPECTED ]] ; then + dokku_log_fail "$LOG_URL: expected to but did not find: \"$EXPECTED\"" + exit 1 + fi + else + # Failed to connect/no response, OUTPUT contains error message + dokku_log_fail "$OUTPUT" + exit 2 + fi +done + + +dokku_log_info1 "All checks successful!" + diff --git a/tests/unit/apps.bats b/tests/unit/apps.bats old mode 100644 new mode 100755