#!/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 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


# source in app env to get DOKKU_CHECKS_WAIT and any other necessary vars
[[ -f "$DOKKU_ROOT/ENV" ]] && source $DOKKU_ROOT/ENV

# 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
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: running simple container check..."
    rm -rf $TMPDIR

    # simple default check to see if the container stuck around
    # for more thorough checks, create a CHECKS file
    DOKKU_DEFAULT_WAIT=$((WAIT+TIMEOUT))
    dokku_log_info1 "Waiting for $DOKKU_DEFAULT_WAIT seconds ..."
    sleep $DOKKU_DEFAULT_WAIT

    docker ps -q --no-trunc | grep -q "$DOKKU_APP_CONTAINER_ID" || dokku_log_fail "App container failed to start!!"
    dokku_log_info1 "Default check successful!" && exit 0
fi


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 $TIMEOUT"


exec < "$FILENAME"
while read CHECK_URL EXPECTED ; do
  # Ignore empty lines and lines starting with #
  # shellcheck disable=SC1001
  [[ -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

  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}

    HEADERS="-H Host:$URL_HOSTNAME"
  else
    URL_HOSTNAME=localhost
    URL_PATHNAME=$CHECK_URL
  fi

  # 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!"

