diff --git a/.github/commands/ci-setup b/.github/commands/ci-setup index 43c3ac6f6..f756adff8 100755 --- a/.github/commands/ci-setup +++ b/.github/commands/ci-setup @@ -2,19 +2,23 @@ set -eo pipefail set -x +DOKKU_DOMAIN=${DOKKU_DOMAIN:="dokku.me"} + echo "=====> set hostname" hostname internal_ip="$(ifconfig eth0 | grep 'inet ' | awk '{print $2}')" -sudo hostname "dokku.me" -echo "dokku.me" | sudo tee /etc/hostname +sudo hostname "$DOKKU_DOMAIN" +echo "$DOKKU_DOMAIN" | sudo tee /etc/hostname hostname sudo cat /etc/hostname -echo "=====> resolve dokku.me" +echo "=====> resolve $DOKKU_DOMAIN" sudo apt -qq -y --no-install-recommends install net-tools -# dokku.me now resolves to 10.0.0.2. add 10.0.0.2/24 to eth0 -ifconfig -sudo ip addr add 10.0.0.2/24 broadcast 10.0.0.255 dev eth0 +if [[ "$DOKKU_DOMAIN" == "dokku.me" ]]; then + # dokku.me now resolves to 10.0.0.2. add 10.0.0.2/24 to eth0 + ifconfig + sudo ip addr add 10.0.0.2/24 broadcast 10.0.0.255 dev eth0 +fi echo "=====> install ci-dependencies" make ci-dependencies diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index afd700308..6bfa69123 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,6 +106,7 @@ jobs: uses: docker/setup-buildx-action@v2 - name: build docker image + timeout-minutes: 20 run: | docker buildx build \ --platform ${{ matrix.architecture }} \ diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 84135ae54..0553e9a20 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -68,9 +68,6 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 1.1.0 => 94e0aab03ca135d11a35e5bfc14e6746dc56e7e9 - env: - # keep in sync with tests/shellcheck-exclude - SHELLCHECK_OPTS: -e SC1001 -e SC1003 -e SC1090 -e SC1091 -e SC1117 -e SC2029 -e SC2030 -e SC2031 -e SC2034 -e SC2046 -e SC2064 -e SC2068 -e SC2086 -e SC2119 -e SC2120 -e SC2128 -e SC2148 -e SC2153 -e SC2154 -e SC2155 -e SC2162 -e SC2174 -e SC2179 -e SC2191 -e SC2199 -e SC2207 -e SC2219 -e SC2220 -e SC2230 -e SC2231 -e SC2235 -e SC2267 -e SC2295 shfmt: name: shfmt diff --git a/.shellcheckrc b/.shellcheckrc index eed21d662..dffeb4f39 100644 --- a/.shellcheckrc +++ b/.shellcheckrc @@ -1,9 +1,33 @@ +# SC1001 - This \/ will be a regular '/' in this context - https://github.com/koalaman/shellcheck/wiki/SC1001 +# SC1003 - Want to escape a single quote? echo 'This is how it'\''s done' - https://github.com/koalaman/shellcheck/wiki/SC1003 +# SC1090 - Can't follow non-constant source. Use a directive to specify location - https://github.com/koalaman/shellcheck/wiki/SC1090 # SC1091 - Not following: FILE: does not exist (No such file or directory) - https://github.com/koalaman/shellcheck/wiki/SC1091 -# - used for sourcing function files throughout the codebase +# SC1117 - Backslash is literal in "\n". Prefer explicit escaping: "\\n" - https://github.com/koalaman/shellcheck/wiki/SC1117 +# SC2029 - Note that, unescaped, this expands on the client side - https://github.com/koalaman/shellcheck/wiki/SC2029 +# SC2030 - Modification of var is local (to subshell caused by pipeline). - https://github.com/koalaman/shellcheck/wiki/SC2030 +# SC2031 - var was modified in a subshell. That change might be lost. - https://github.com/koalaman/shellcheck/wiki/SC2031 # SC2034 - VAR appears unused - https://github.com/koalaman/shellcheck/wiki/SC2034 -# - used for declaring desc and deprecated function variables +# SC2046 - Quote this to prevent word splitting - https://github.com/koalaman/shellcheck/wiki/SC2046 # SC2064 - Use single quotes, otherwise this expands now rather than when signalled. - https://github.com/koalaman/shellcheck/wiki/SC2064 -# - used for traps +# SC2068 - Double quote array expansions to avoid re-splitting elements - https://github.com/koalaman/shellcheck/wiki/SC2068 +# SC2086 - Double quote to prevent globbing and word splitting - https://github.com/koalaman/shellcheck/wiki/SC2086 +# SC2119 - Use 'function "$@"' if function's $1 should mean script's $1 - https://github.com/koalaman/shellcheck/wiki/SC2119 +# SC2120 - Function references arguments, but none are ever passed - https://github.com/koalaman/shellcheck/wiki/SC2120 +# SC2128 - Expanding an array without an index only gives the first element - https://github.com/koalaman/shellcheck/wiki/SC2128 +# SC2148 - Tips depend on target shell and yours is unknown. Add a shebang - https://github.com/koalaman/shellcheck/wiki/SC2148 +# SC2153 - Possible misspelling: DOKKU_ROOT may not be assigned, but DOKKU_PORT is - https://github.com/koalaman/shellcheck/wiki/SC2153 +# SC2154 - Var is referenced but not assigned - https://github.com/koalaman/shellcheck/wiki/SC2154 # SC2155 - Declare and assign separately to avoid masking return values - https://github.com/koalaman/shellcheck/wiki/SC2155 -# - used throughout the codebase -disable=SC1091,SC2034,SC2064,SC2155 +# SC2162 - read without -r will mangle backslashes - https://github.com/koalaman/shellcheck/wiki/SC2162 +# SC2174 - When used with -p, -m only applies to the deepest directory - https://github.com/koalaman/shellcheck/wiki/SC2174 +# SC2179 - Use array+=("item") to append items to an array - https://github.com/koalaman/shellcheck/wiki/SC2179 +# SC2191 - The = here is literal. To assign by index, use ( [index]=value ) with no spaces. To keep as literal, quote it - https://github.com/koalaman/shellcheck/wiki/SC2191 +# SC2199 - Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @) - https://github.com/koalaman/shellcheck/wiki/SC2199 +# SC2207 - Prefer mapfile or read -a to split command output (or quote to avoid splitting) - https://github.com/koalaman/shellcheck/wiki/SC2207 +# SC2219 - Instead of 'let expr', prefer (( expr )) - https://github.com/koalaman/shellcheck/wiki/SC2219 +# SC2220 - Invalid flags are not handled. Add a *) case - https://github.com/koalaman/shellcheck/wiki/SC2220 +# SC2231 - Quote expansions in this for loop glob to prevent wordsplitting, e.g. "$dir"/*.txt - https://github.com/koalaman/shellcheck/wiki/SC2231 +# SC2235 - Use { ..; } instead of (..) to avoid subshell overhead - https://github.com/koalaman/shellcheck/wiki/SC2235 +# SC2267 - GNU xargs -i is deprecated in favor of -I{} - https://github.com/koalaman/shellcheck/wiki/SC2267 +# SC2295 - Expansions inside ${..} need to be quoted separately, otherwise they match as patterns - https://github.com/koalaman/shellcheck/wiki/SC2295 +disable=SC1001,SC1003,SC1090,SC1091,SC1117,SC2029,SC2030,SC2031,SC2034,SC2046,SC2064,SC2068,SC2086,SC2119,SC2120,SC2128,SC2148,SC2153,SC2154,SC2155,SC2162,SC2174,SC2179,SC2191,SC2199,SC2207,SC2219,SC2220,SC2231,SC2235,SC2267,SC2295 diff --git a/.stickler.yml b/.stickler.yml deleted file mode 100644 index de214fa2c..000000000 --- a/.stickler.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- -linters: - shellcheck: - shell: bash - exclude: SC2034,SC1090,SC2191 - golint: - -files: - ignore: - - 'debian/*' - - 'docs/*' - - 'tests/*' - - '*/vendor/*' diff --git a/Makefile b/Makefile index 7e91b9560..7c3e16e66 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ DOKKU_VERSION ?= master TARGETARCH ?= amd64 DOCKER_IMAGE_LABELER_VERSION ?= 0.5.0 +DOCKER_CONTAINER_HEALTHCHECKER_VERSION ?= 0.6.4 HEROKUISH_VERSION ?= 0.6.0 LAMBDA_BUILDER_VERSION ?= 0.4.0 NETRC_VERSION ?= 0.6.0 @@ -11,6 +12,7 @@ PROCFILE_VERSION ?= 0.15.0 SIGIL_VERSION ?= 0.9.0 SSHCOMMAND_VERSION ?= 0.16.0 DOCKER_IMAGE_LABELER_URL ?= https://github.com/dokku/docker-image-labeler/releases/download/v${DOCKER_IMAGE_LABELER_VERSION}/docker-image-labeler_${DOCKER_IMAGE_LABELER_VERSION}_linux_${TARGETARCH}.tgz +DOCKER_CONTAINER_HEALTHCHECKER_URL ?= https://github.com/dokku/docker-container-healthchecker/releases/download/v${DOCKER_CONTAINER_HEALTHCHECKER_VERSION}/docker-container-healthchecker_${DOCKER_CONTAINER_HEALTHCHECKER_VERSION}_linux_${TARGETARCH}.tgz LAMBDA_BUILDER_URL ?= https://github.com/dokku/lambda-builder/releases/download/v${LAMBDA_BUILDER_VERSION}/lambda-builder_${LAMBDA_BUILDER_VERSION}_linux_${TARGETARCH}.tgz NETRC_URL ?= https://github.com/dokku/netrc/releases/download/v${NETRC_VERSION}/netrc_${NETRC_VERSION}_linux_${TARGETARCH}.tgz PLUGN_URL ?= https://github.com/dokku/plugn/releases/download/v${PLUGN_VERSION}/plugn_${PLUGN_VERSION}_linux_${TARGETARCH}.tgz @@ -18,7 +20,7 @@ PROCFILE_UTIL_URL ?= https://github.com/josegonzalez/go-procfile-util/releases/d SIGIL_URL ?= https://github.com/gliderlabs/sigil/releases/download/v${SIGIL_VERSION}/gliderlabs-sigil_${SIGIL_VERSION}_linux_${TARGETARCH}.tgz SSHCOMMAND_URL ?= https://github.com/dokku/sshcommand/releases/download/v${SSHCOMMAND_VERSION}/sshcommand_${SSHCOMMAND_VERSION}_linux_x86_64.tgz STACK_URL ?= https://github.com/gliderlabs/herokuish.git -PREBUILT_STACK_URL ?= gliderlabs/herokuish:latest-20 +PREBUILT_STACK_URL ?= gliderlabs/herokuish:latest-22 DOKKU_LIB_ROOT ?= /var/lib/dokku PLUGINS_PATH ?= ${DOKKU_LIB_ROOT}/plugins CORE_PLUGINS_PATH ?= ${DOKKU_LIB_ROOT}/core-plugins @@ -135,7 +137,7 @@ plugin-dependencies: plugn procfile-util plugins: plugn procfile-util docker sudo -E dokku plugin:install --core -dependencies: apt-update docker-image-labeler lambda-builder netrc sshcommand plugn procfile-util docker help2man man-db sigil dos2unix jq parallel +dependencies: apt-update docker-image-labeler docker-container-healthchecker lambda-builder netrc sshcommand plugn procfile-util docker help2man man-db sigil dos2unix jq parallel $(MAKE) -e stack apt-update: @@ -161,6 +163,11 @@ docker-image-labeler: tar xzf /tmp/docker-image-labeler_latest.tgz -C /usr/local/bin mv /usr/local/bin/docker-image-labeler-${TARGETARCH} /usr/local/bin/docker-image-labeler +docker-container-healthchecker: + wget -qO /tmp/docker-container-healthchecker_latest.tgz ${DOCKER_CONTAINER_HEALTHCHECKER_URL} + tar xzf /tmp/docker-container-healthchecker_latest.tgz -C /usr/local/bin + mv /usr/local/bin/docker-container-healthchecker-${TARGETARCH} /usr/local/bin/docker-container-healthchecker + lambda-builder: wget -qO /tmp/lambda-builder_latest.tgz ${LAMBDA_BUILDER_URL} tar xzf /tmp/lambda-builder_latest.tgz -C /usr/local/bin diff --git a/bootstrap.sh b/bootstrap.sh index 8613cbf1d..d5de53341 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -28,7 +28,7 @@ ensure-environment() { echo "Preparing to install $DOKKU_TAG from $DOKKU_REPO..." fi - hostname -f >/dev/null 2>&1 || { + hostname -f &>/dev/null || { log-fail "This installation script requires that you have a hostname set for the instance. Please set a hostname for 127.0.0.1 in your /etc/hosts" } @@ -220,19 +220,15 @@ install-dokku-from-deb-package() { [[ -n $DOKKU_NGINX_ENABLE ]] && echo "dokku dokku/nginx_enable string $DOKKU_NGINX_ENABLE" | sudo debconf-set-selections if [[ -n $DOKKU_CHECKOUT ]]; then - # shellcheck disable=SC2086 apt-get -qq -y $NO_INSTALL_RECOMMENDS install "dokku=$DOKKU_CHECKOUT" else - # shellcheck disable=SC2086 apt-get -qq -y $NO_INSTALL_RECOMMENDS install dokku fi } main() { export DOKKU_DISTRO DOKKU_DISTRO_VERSION - # shellcheck disable=SC1091 DOKKU_DISTRO=$(. /etc/os-release && echo "$ID") - # shellcheck disable=SC1091 DOKKU_DISTRO_VERSION=$(. /etc/os-release && echo "$VERSION_ID") export DEBIAN_FRONTEND=noninteractive diff --git a/contrib/dokku-docker-bin.sh b/contrib/dokku-docker-bin.sh index 3bc362322..9619f4651 100755 --- a/contrib/dokku-docker-bin.sh +++ b/contrib/dokku-docker-bin.sh @@ -4,5 +4,4 @@ set -eo pipefail DOKKU_DOCKER_CONTAINER_NAME=${DOKKU_DOCKER_CONTAINER_NAME:=dokku} # TODO: handle cases where we need a tty -# shellcheck disable=SC2086 docker exec $DOKKU_DOCKER_ENV -i "$DOKKU_DOCKER_CONTAINER_NAME" dokku "$@" diff --git a/contrib/dokku_client.sh b/contrib/dokku_client.sh index 32a3dde63..590d3d0dc 100755 --- a/contrib/dokku_client.sh +++ b/contrib/dokku_client.sh @@ -49,7 +49,7 @@ fn-dokku-host() { declare DOKKU_GIT_REMOTE="$1" DOKKU_HOST="$2" if [[ -z "$DOKKU_HOST" ]]; then - if [[ -d .git ]] || git rev-parse --git-dir >/dev/null 2>&1; then + if [[ -d .git ]] || git rev-parse --git-dir &>/dev/null; then DOKKU_HOST=$(git remote -v 2>/dev/null | grep -Ei "^${DOKKU_GIT_REMOTE}\s" | head -n 1 | cut -f1 -d' ' | cut -f2 -d '@' | cut -f1 -d':' 2>/dev/null || true) fi fi @@ -109,7 +109,7 @@ main() { fi if [[ -z "$APP" ]]; then - if [[ -d .git ]] || git rev-parse --git-dir >/dev/null 2>&1; then + if [[ -d .git ]] || git rev-parse --git-dir &>/dev/null; then set +e APP=$(git remote -v 2>/dev/null | grep -Ei "^${DOKKU_GIT_REMOTE}\s" | grep -Ei "dokku@$DOKKU_REMOTE_HOST" | head -n 1 | cut -f2 -d'@' | cut -f1 -d' ' | cut -f2 -d':' 2>/dev/null) set -e @@ -190,7 +190,6 @@ main() { [[ -n "$APP_ARG" ]] && [[ "$APP_ARG" == "--global" ]] && unset APP [[ -n "$@" ]] && [[ -n "$APP" ]] && app_arg="--app $APP" # echo "ssh -o LogLevel=QUIET -p $DOKKU_PORT -t dokku@$DOKKU_REMOTE_HOST -- $app_arg $@" - # shellcheck disable=SC2068,SC2086 ssh -o LogLevel=QUIET -p $DOKKU_PORT -t dokku@$DOKKU_REMOTE_HOST -- $app_arg $@ || { ssh_exit_code="$?" echo " ! Failed to execute dokku command over ssh: exit code $?" 1>&2 diff --git a/contrib/images/digitalocean/in_parts/012-dokku-packages b/contrib/images/digitalocean/in_parts/012-dokku-packages index 768c44924..193327852 100755 --- a/contrib/images/digitalocean/in_parts/012-dokku-packages +++ b/contrib/images/digitalocean/in_parts/012-dokku-packages @@ -33,7 +33,8 @@ pkgs=(apache2-utils debconf ) -dokku_pkgs=(docker-image-labeler +dokku_pkgs=(docker-docker-container-healthchecker + docker-image-labeler gliderlabs-sigil lambda-builder netrc diff --git a/contrib/images/digitalocean/in_parts/100-image-check b/contrib/images/digitalocean/in_parts/100-image-check index a14bb87c1..fde0fede7 100755 --- a/contrib/images/digitalocean/in_parts/100-image-check +++ b/contrib/images/digitalocean/in_parts/100-image-check @@ -31,7 +31,6 @@ cmdExists() { function getDistro { if [ -f /etc/os-release ]; then # freedesktop.org and systemd - # shellcheck disable=SC1091 . /etc/os-release OS=$NAME VER=$VERSION_ID @@ -41,7 +40,6 @@ function getDistro { VER=$(lsb_release -sr) elif [ -f /etc/lsb-release ]; then # For some versions of Debian/Ubuntu without lsb_release command - # shellcheck disable=SC1091 . /etc/lsb-release OS=$DISTRIB_ID VER=$DISTRIB_RELEASE @@ -255,11 +253,9 @@ function checkUsers { if [[ "${u[0]}" == "${user}" ]]; then if [[ ${u[1]} == "!" ]] || [[ ${u[1]} == "!!" ]] || [[ ${u[1]} == "*" ]]; then echo -en "\e[32m[PASS]\e[0m User ${user} has no password set.\n" - # shellcheck disable=SC2030 ((PASS++)) else echo -en "\e[41m[FAIL]\e[0m User ${user} has a password set on their account. Only system users are allowed on the image.\n" - # shellcheck disable=SC2030 ((FAIL++)) STATUS=2 fi @@ -292,7 +288,6 @@ function checkUsers { STATUS=2 else echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has empty private key file in \e[93m${key}\e[0m\n" - # shellcheck disable=SC2030 ((WARN++)) if [[ $STATUS != 2 ]]; then STATUS=1 @@ -352,11 +347,9 @@ function checkFirewall { ufwa=$(ufw status | head -1 | sed -e "s/^Status:\ //") if [[ $ufwa == "active" ]]; then FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n" - # shellcheck disable=SC2031 ((PASS++)) else FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n" - # shellcheck disable=SC2031 ((WARN++)) fi elif [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]] || [[ $OS == "AlmaLinux" ]]; then @@ -451,7 +444,6 @@ function checkUpdates { sleep 2 apt-get --just-print upgrade | grep -i security | awk '{print $2}' | awk '!seen[$0]++' echo -en - # shellcheck disable=SC2031 ((FAIL++)) STATUS=2 else diff --git a/contrib/release-dokku b/contrib/release-dokku index cff7282c0..c63fdc8eb 100755 --- a/contrib/release-dokku +++ b/contrib/release-dokku @@ -9,19 +9,16 @@ readonly DOKKU_GIT_REV="$(git rev-parse HEAD)" trap "rm -rf '$TMP_WORK_DIR' >/dev/null" RETURN INT TERM EXIT log-info() { - # shellcheck disable=SC2034 declare desc="Log info formatter" echo "$*" } log-error() { - # shellcheck disable=SC2034 declare desc="Log error formatter" echo "! $*" 1>&2 } log-fail() { - # shellcheck disable=SC2034 declare desc="Log fail formatter" log-error "$*" exit 1 @@ -186,8 +183,12 @@ fn-replace-version() { fn-repo-merged-requests() { declare desc="Retrieves all pull request ids since a given release of dokku" - declare TAG="$1" - git log "v${TAG}..HEAD" --oneline | grep "Merge pull request" | cut -d' ' -f 5 | cut -d '#' -f2 + declare TAG="$1" IS_PATCH="$2" + if [[ "$IS_PATCH" == "true" ]]; then + git log "v${TAG}..HEAD" --oneline | grep "Merge pull request" | cut -d' ' -f 5 | cut -d '#' -f2 + else + curl -u "$RELEASE_GITHUB_USERNAME:$RELEASE_GITHUB_API_TOKEN" --fail -sS "https://api.github.com/search/issues?q=repo:dokku/dokku+milestone:v${NEXT_VERSION}+is:pr&per_page=100" | jq '.items[].number' + fi } fn-repo-push-tags() { @@ -204,7 +205,7 @@ fn-repo-push-tags() { fn-repo-update() { declare desc="Updates files within the repository for a given release" - declare IS_RELEASE="$1" CURRENT_VERSION="$2" NEXT_VERSION="$3" + declare IS_RELEASE="$1" CURRENT_VERSION="$2" NEXT_VERSION="$3" IS_PATCH="$4" log-info "(release-dokku) Replacing ${CURRENT_VERSION} with ${NEXT_VERSION}" for f in plugins/*/plugin.toml; do @@ -241,13 +242,13 @@ fn-repo-update() { fi log-info "(release-dokku) Adding HISTORY.md, committing and tagging" - fn-repo-update-history-and-commit "$CURRENT_VERSION" "$NEXT_VERSION" + fn-repo-update-history-and-commit "$CURRENT_VERSION" "$NEXT_VERSION" "$IS_PATCH" git tag "v${NEXT_VERSION}" } fn-repo-update-history-and-commit() { declare desc="Updates the history file and commits the changes" - declare CURRENT_VERSION="$1" NEXT_VERSION="$2" + declare CURRENT_VERSION="$1" NEXT_VERSION="$2" IS_PATCH="$3" local PULL_REQUEST_ID TITLE AUTHOR TYPE CHANGELOG_TEXT local COMMIT_MESSAGE HISTORY HISTORY_CONTENTS HISTORY_BUG HISTORY_DEPENDENCY HISTORY_DEPRECATION HISTORY_DOCUMENTATION HISTORY_ENHANCEMENT HISTORY_BC_BREAK HISTORY_REFACTOR HISTORY_REMOVAL HISTORY_OTHER HISTORY_TESTS ISSUE_FILE @@ -255,7 +256,7 @@ fn-repo-update-history-and-commit() { mkdir -p /tmp/github-data - for PULL_REQUEST_ID in $(fn-repo-merged-requests "$CURRENT_VERSION"); do + for PULL_REQUEST_ID in $(fn-repo-merged-requests "$CURRENT_VERSION" "$IS_PATCH"); do ISSUE_FILE="/tmp/github-data/${PULL_REQUEST_ID}.json" if [[ ! -f "$ISSUE_FILE" ]]; then while true; do @@ -402,7 +403,7 @@ fn-build-docker-image() { main() { declare RELEASE="$1" - local CURRENT_VERSION NEXT_VERSION IS_RELEASE + local CURRENT_VERSION NEXT_VERSION IS_PATCH IS_RELEASE local VALID_RELEASE_LEVELS=("major" "minor" "patch" "betafish" "build") if [[ "$RELEASE" == '--trace' ]]; then @@ -433,9 +434,14 @@ main() { NEXT_VERSION="$(fn-version-next "$CURRENT_VERSION" "$RELEASE")" IS_RELEASE="$(fn-is-release "$RELEASE")" + IS_PATCH="false" + if [[ "$RELEASE" == "patch" ]]; then + IS_PATCH="true" + fi + echo "v${NEXT_VERSION}" >build/next-version - fn-repo-update "$IS_RELEASE" "$CURRENT_VERSION" "$NEXT_VERSION" + fn-repo-update "$IS_RELEASE" "$CURRENT_VERSION" "$NEXT_VERSION" "$IS_PATCH" fn-build-dokku "$IS_RELEASE" "$NEXT_VERSION" || log-fail "Error building package" fn-extract-package "$IS_RELEASE" "dokku_${NEXT_VERSION}_armhf.deb" || log-fail "Error extracting deb package" fn-extract-package "$IS_RELEASE" "dokku_${NEXT_VERSION}_arm64.deb" || log-fail "Error extracting deb package" diff --git a/contrib/release-plugin b/contrib/release-plugin index e0cbc55f2..f034414b2 100755 --- a/contrib/release-plugin +++ b/contrib/release-plugin @@ -7,19 +7,16 @@ readonly TMP_WORK_DIR="$(mktemp -d "/tmp/dokku-plugin-release.XXXXXX")" trap "rm -rf '$TMP_WORK_DIR' >/dev/null" RETURN INT TERM EXIT log-info() { - # shellcheck disable=SC2034 declare desc="Log info formatter" echo "$*" } log-error() { - # shellcheck disable=SC2034 declare desc="Log error formatter" echo "! $*" 1>&2 } log-fail() { - # shellcheck disable=SC2034 declare desc="Log fail formatter" log-error "$*" exit 1 diff --git a/debian/config b/debian/config index e4bcc25be..99bba1b23 100755 --- a/debian/config +++ b/debian/config @@ -3,7 +3,6 @@ set -eo pipefail [[ $TRACE ]] && set -x if [[ -e /usr/share/debconf/confmodule ]]; then - # shellcheck disable=SC1091 . /usr/share/debconf/confmodule fi diff --git a/debian/control b/debian/control index 146b6c40d..2d9dba1e8 100644 --- a/debian/control +++ b/debian/control @@ -3,8 +3,8 @@ Version: 0.30.11 Section: web Priority: optional Architecture: amd64 -Depends: apache2-utils, locales, git, cpio, curl, man-db, netcat, sshcommand (>= 0.12.0), docker-engine-cs (>= 17.05.0) | docker-engine (>= 17.05.0) | docker-io (>= 17.05.0) | docker.io (>= 17.05.0) | docker-ce (>= 17.05.0) | docker-ee (>= 17.05.0) | moby-engine, docker-compose-plugin | moby-compose, docker-image-labeler (>= 0.2.2), lambda-builder, net-tools, netrc, software-properties-common, parallel, procfile-util (>= 0.11.0), python-software-properties | python3-software-properties, rsync, rsyslog, dos2unix, jq, unzip -Recommends: herokuish (>= 0.3.4), bash-completion, dokku-update, dokku-event-listener +Depends: apache2-utils, locales, git, cpio, curl, man-db, netcat, sshcommand (>= 0.12.0), docker-engine-cs (>= 17.05.0) | docker-engine (>= 17.05.0) | docker-io (>= 17.05.0) | docker.io (>= 17.05.0) | docker-ce (>= 17.05.0) | docker-ee (>= 17.05.0) | moby-engine, docker-compose-plugin | moby-compose, docker-container-healthchecker (>= 0.6.4), docker-image-labeler (>= 0.2.2), lambda-builder, net-tools, netrc, software-properties-common, parallel, procfile-util (>= 0.11.0), python-software-properties | python3-software-properties, rsync, rsyslog, dos2unix, jq, unzip +Recommends: herokuish (>= 0.7.0), bash-completion, dokku-update, dokku-event-listener Pre-Depends: gliderlabs-sigil, nginx (>= 1.8.0) | openresty, dnsutils, cgroupfs-mount | cgroup-lite, plugn (>= 0.3.0), sudo, python3, debconf Maintainer: Jose Diaz-Gonzalez Description: Docker-powered PaaS that helps build and manage the lifecycle of applications diff --git a/debian/postinst b/debian/postinst index 0663ca651..1b755a173 100755 --- a/debian/postinst +++ b/debian/postinst @@ -3,12 +3,10 @@ set -eo pipefail [[ $TRACE ]] && set -x if [[ -e /usr/share/debconf/confmodule ]]; then - # shellcheck disable=SC1091 . /usr/share/debconf/confmodule fi if [[ -r /etc/default/dokku ]]; then - # shellcheck disable=SC1091 source /etc/default/dokku fi diff --git a/debian/postrm b/debian/postrm index 6c15a4d09..78f3b83de 100755 --- a/debian/postrm +++ b/debian/postrm @@ -3,7 +3,6 @@ set -eo pipefail [[ $TRACE ]] && set -x if [[ -e /usr/share/debconf/confmodule ]]; then - # shellcheck disable=SC1091 . /usr/share/debconf/confmodule fi diff --git a/debian/preinst b/debian/preinst index e4dfdef5e..fb7104341 100755 --- a/debian/preinst +++ b/debian/preinst @@ -3,7 +3,6 @@ set -eo pipefail [[ $TRACE ]] && set -x if [[ -e /usr/share/debconf/confmodule ]]; then - # shellcheck disable=SC1091 . /usr/share/debconf/confmodule fi diff --git a/docker/etc/sudoers.d/dokku-docker b/docker/etc/sudoers.d/dokku-docker index 8150da4a5..6dfc5d175 100644 --- a/docker/etc/sudoers.d/dokku-docker +++ b/docker/etc/sudoers.d/dokku-docker @@ -1 +1 @@ -dokku ALL=NOPASSWD:SETENV:/usr/bin/docker,/usr/bin/docker-image-labeler,/usr/bin/pack,/usr/bin/crontab +dokku ALL=NOPASSWD:SETENV:/usr/bin/docker,/usr/bin/docker-container-healthchecker,/usr/bin/docker-image-labeler,/usr/bin/pack,/usr/bin/crontab diff --git a/docker/usr/local/bin/docker-container-healthchecker b/docker/usr/local/bin/docker-container-healthchecker new file mode 100755 index 000000000..8453942f2 --- /dev/null +++ b/docker/usr/local/bin/docker-container-healthchecker @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $TRACE ]] && set -x + +main() { + declare desc="re-runs docker-container-healthchecker commands as sudo" + local DOCKER_CONTAINER_HEALTHCHECKER_BIN="" + if [[ -x "/usr/bin/docker-container-healthchecker" ]]; then + DOCKER_CONTAINER_HEALTHCHECKER_BIN="/usr/bin/docker-container-healthchecker" + fi + + if [[ -z "$DOCKER_CONTAINER_HEALTHCHECKER_BIN" ]]; then + echo "! No docker-container-healthchecker binary found" 1>&2 + exit 1 + fi + + sudo -E "$DOCKER_CONTAINER_HEALTHCHECKER_BIN" "$@" +} + +main "$@" diff --git a/docs/advanced-usage/plugin-management.md b/docs/advanced-usage/plugin-management.md index 99438774e..ea0b80b09 100644 --- a/docs/advanced-usage/plugin-management.md +++ b/docs/advanced-usage/plugin-management.md @@ -5,13 +5,13 @@ ``` plugin:disable # Disable an installed plugin (third-party only) plugin:enable # Enable a previously disabled plugin -plugin:install [--core|git-url [--committish tag|branch|commit|--name custom-plugin-name]] # Optionally download git-url (with custom tag/committish) & run install trigger for active plugins (or only core ones) +plugin:install [--core|--git-url] [--committish branch|commit|tag] [--name custom-plugin-name] # Optionally download git-url (and pin to the specified branch/commit/tag) & run install trigger for active plugins (or only core ones) plugin:installed # Checks if a plugin is installed plugin:install-dependencies [--core] # Run install-dependencies trigger for active plugins (or only core ones) plugin:list # Print active plugins plugin:trigger . # Trigger an arbitrary plugin hook plugin:uninstall # Uninstall a plugin (third-party only) -plugin:update [name [committish]] # Optionally update named plugin from git (with custom tag/committish) & run update trigger for active plugins +plugin:update [name [branch|commit|tag]] # Optionally update named plugin from git (and pin to the specified branch/commit/tag) & run update trigger for active plugins ``` ```shell @@ -165,6 +165,12 @@ An optional commit SHA-like object may be specified. dokku plugin:update postgres 2.0.0 ``` +Any future invocation of `plugin:update` will respect the previously specified SHA-like object. To follow a particular branch again, specify that branch: + +```shell +dokku plugin:update postgres main +``` + ### Uninstalling a plugin Third party plugins can be uninstalled using the `plugin:uninstall` command: diff --git a/docs/appendices/0.31.0-migration-guide.md b/docs/appendices/0.31.0-migration-guide.md new file mode 100644 index 000000000..a5636568b --- /dev/null +++ b/docs/appendices/0.31.0-migration-guide.md @@ -0,0 +1,36 @@ +# 0.31.0 Migration Guide + +## Changes + +- Herokuish build cache is now mounted from a docker volume - eg. `cache-node-js-app` - instead of the local filesystem. All existing app cache will be cleared upon upgrading past 0.29.0. +- The `vector` container integration now mounts config to `/etc/vector` instead of the path `/etc/vector/vector.json`, allowing users the ability to provide extra configuration for Vector Sinks. To take advantage of the new functionality, the vector container should be stopped (via `dokku logs:vector-stop`) and then started (via `dokku logs:vector-start`). +- The `traefik` integration now mounts config to `/data` instead of the path `/acme.json`, fixing permissions issues under certain architectures. To take advantage of the new functionality, the traefik container should be stopped (via `dokku traefik:stop`) and then started (via `dokku traefik:start`). +- Users no longer need to clear the `source-image` git property when transitioning from image-based deploys (`git:from-image` and `git:load-image`) to other deployment methods (git push, `git:from-archive`, `git:sync`). +- For deploys via the `git:from-image` and `git:load-image` commands, the `CHECKS` file is now extracted from the configured `WORKDIR` property of the image. For all other deploys - git push, `git:from-archive`, `git:sync` - will have the `CHECKS` extracted directly from the source code. The filename in both cases is `CHECKS` and cannot be modified. +- Port mappings are now auto-detected during the build process. Users may override detected port mappings via the `ports` plugin. A default port mapping of `http:80:5000` will be used if none is otherwise specified. +- Users building docker images that run Dokku will need to use a new sudoer wrapper for the `docker-container-healthchecker` binary to work correctly. A reference version has been placed in the `docker` skeleton directory. This should only impact platform developers, and users of our Docker image will already have the file available. +- The default image used for Herokuish Buildpack builds is now `gliderlabs/herokuish:latest-22`, and is based on both the `heroku-22` stack as well as Ubuntu 22.04. Users that wish to stick with the old, `heroku-20`/Ubuntu 20.04 builder may specify `gliderlabs/herokuish:latest-20` as their builder image. Please see the [herokuish buildpack deployment documentation](/docs/deployment/builders/herokuish-buildpacks.md#customizing-the-buildpack-stack-builder) for more information on how to specify a custom buildpack stack builder. +- The default image used for Cloud Native Buildpack builds is now `heroku/builder:22`, and is based on both the `heroku-22` stack as well as Ubuntu 22.04. Users that wish to stick with the old, `heroku-20`/Ubuntu 20.04 builder may specify `heroku/buildpacks:20` as their builder image. Please see the [herokuish buildpack deployment documentation](/docs/deployment/builders/herokuish-buildpacks.md#customizing-the-buildpack-stack-builder) for more information on how to specify a custom buildpack stack builder. + +## Deprecations + +- The `proxy:ports*` commands have been replaced with the new `ports` plugin. Users will be able to use the old `proxy:ports*` commands for a single minor release, and they will be removed in the next minor release. +- The `common#get_available_port()` function has been deprecated and will be removed in the next release. Users should avoid interacting with this function and instead use the `ports-get-available` plugin trigger for fetching an available port. +- The `proxy-configure-ports` plugin trigger has been deprecated and will be removed in the next release. Users should instead trigger the `ports-configure` plugin trigger. +- The `common#get_dockerfile_exposed_ports()` function is deprecated and will be removed in the next release. There is no replacement for this as it's only use in Dokku core was in the `builder-dockerfile` plugin. +- The `common#get_exposed_ports_from_image()` function is deprecated and will be removed in the next release. There is no replacement for this as it's only use in Dokku core was during the build process. +- The environment variable `DOKKU_PROXY_PORT_MAP` has been migrated to the properties system. Direct changes to the value will be ignored, and users should interact with port maps via the `ports` plugin. +- The `CHECKS` file is deprecated in favor of defining healthchecks in the `app.json` file. The [docker-container-healthchecker](https://github.com/dokku/docker-container-healthchecker) tool can be used to generate healthcheck entries in `app.json` format from existing `CHECKS` files. See the [zero-downtime deploy documentation](/docs/deployment/zero-downtime-deploys.md) for more information on how the new zero downtime check format works. +- ARM support is now deprecated, and will be removed in the next minor release. ARM releases are not tested in CI and do not get testing locally. Users of ARM installations on platforms such as the Raspberry PI should consider migrating to ARM64. ARM64 will continue to be supported by Dokku, and there are no plans to remove support for ARM64. + +## Un-Deprecations + +- The bare `apps` and `config` cli aliases are no longer deprecated. This better follows Heroku's output and is more useful to users. The subcommands will be treated as the primary, documented versions, while the aliases are there for convenience. + +## Removals + +- The variable `RAW_TCP_PORTS` is no longer exposed for `nginx.conf.sigil` templating. +- The environment variable `DOKKU_DOCKERFILE_PORTS` is no longer in use. Users wishing to change port mappings should instead use the `ports` plugin. +- The `common#get_app_raw_tcp_ports()` function has been removed in the next release. Users should instead use the `ports-get` plugin trigger for fetching ports for an app. +- The plugin trigger `network-compute-ports` has been removed. It's only use was in the `scheduler-docker-local` plugin, for exposing ports. It's functionality is now implemented via the `ports` plugin. +- The plugin trigger `network-get-port` has been removed. It's only use was in the `scheduler-docker-local` plugin, for recording ports. It's functionality is now implemented via the `ports` plugin. diff --git a/docs/configuration/environment-variables.md b/docs/configuration/environment-variables.md index c4498062f..464211d4a 100644 --- a/docs/configuration/environment-variables.md +++ b/docs/configuration/environment-variables.md @@ -111,12 +111,10 @@ The following config variables have special meanings and can be set in a variety | `DOKKU_DISABLE_APP_AUTOCREATION` | none | `dokku config:set` | Disables automatic creation of a non-existent app on deploy. | | `DOKKU_DOCKER_STOP_TIMEOUT` | `10` | `dokku config:set` | Configurable grace period given to the `docker stop` command. If a container has not stopped by this time, a `kill -9` signal or equivalent is sent in order to force-terminate the container. Both the `ps:stop` and `apps:destroy` commands _also_ respect this value. If not specified, the docker defaults for the [docker stop command](https://docs.docker.com/engine/reference/commandline/stop/) will be used.| | `DOKKU_DOCKERFILE_CACHE_BUILD` | none | `dokku config:set` | | -| `DOKKU_DOCKERFILE_PORTS` | dockerfile ports | `dokku config:set` | | | `DOKKU_DOCKERFILE_START_CMD` | none | `dokku config:set` | | | `DOKKU_PARALLEL_ARGUMENTS`. | none | `dokku config:set` | Allows passing custom arguments to parallel for `ps:*all` commands | | `DOKKU_PROXY_PORT` | automatically assigned | `/etc/environment`
`~dokku/.dokkurc`
`~dokku/.dokkurc/*`
`dokku config:set` | | | `DOKKU_PROXY_SSL_PORT` | automatically assigned | `/etc/environment`
`~dokku/.dokkurc`
`~dokku/.dokkurc/*`
`dokku config:set` | | -| `DOKKU_PROXY_PORT_MAP` | automatically assigned | `dokku proxy:ports-add`
`dokku proxy:ports-remove`, `dokku proxy:ports-clear` | | | `DOKKU_SKIP_ALL_CHECKS` | none | `dokku config:set` | | | `DOKKU_SKIP_CLEANUP` | | `/etc/environment`
`~dokku/.dokkurc`
`~dokku/.dokkurc/*` | When a deploy is triggered, if this is set to a non-empty value, then old docker containers and images will not be removed. | | `DOKKU_SKIP_DEFAULT_CHECKS` | | `dokku config:set` | | diff --git a/docs/configuration/ssl.md b/docs/configuration/ssl.md index 42664a749..abb0f9f62 100644 --- a/docs/configuration/ssl.md +++ b/docs/configuration/ssl.md @@ -134,7 +134,7 @@ Certain versions of nginx have bugs that prevent [HTTP/2](https://nginx.org/en/d ### SSL Port Exposure -When your app is served from port `80` then the `/home/dokku/APP/nginx.conf` file will automatically be updated to instruct nginx to respond to ssl on port 443 as a new cert is added. If your app uses a non-standard port (perhaps you have a dockerfile deploy exposing port `99999`) you may need to manually expose an ssl port via `dokku proxy:ports-add https:443:99999`. +When your app is served from port `80` then the `/home/dokku/APP/nginx.conf` file will automatically be updated to instruct nginx to respond to ssl on port 443 as a new cert is added. If your app uses a non-standard port (perhaps you have a dockerfile deploy exposing port `99999`) you may need to manually expose an ssl port via `dokku ports:add https:443:99999`. ## Other diff --git a/docs/deployment/builders/cloud-native-buildpacks.md b/docs/deployment/builders/cloud-native-buildpacks.md index 23d5c6ab9..0e300ede4 100644 --- a/docs/deployment/builders/cloud-native-buildpacks.md +++ b/docs/deployment/builders/cloud-native-buildpacks.md @@ -136,7 +136,7 @@ project2.toml > New as of 0.23.0 -The default stack builder in use by CNB buildpacks in Dokku is based on `heroku/buildpacks`. Users may desire to switch the stack builder to a custom version, either to update the operating system or to customize packages included with the stack builder. This can be performed via the `buildpacks:set-property` command. +The default stack builder in use by CNB buildpacks in Dokku is based on `heroku/builder:22`. Users may desire to switch the stack builder to a custom version, either to update the operating system or to customize packages included with the stack builder. This can be performed via the `buildpacks:set-property` command. ```shell dokku buildpacks:set-property node-js-app stack paketobuildpacks/build:base-cnb @@ -150,7 +150,7 @@ dokku buildpacks:set-property node-js-app stack A change in the stack builder value will execute the `post-stack-set` trigger. -Finally, stack builders can be set or unset globally as a fallback. This will take precedence over a globally set `DOKKU_CNB_BUILDER` environment variable (`heroku/buildpacks` by default). +Finally, stack builders can be set or unset globally as a fallback. This will take precedence over a globally set `DOKKU_CNB_BUILDER` environment variable (`heroku/builder:22` by default). ```shell # set globally diff --git a/docs/deployment/builders/dockerfiles.md b/docs/deployment/builders/dockerfiles.md index 12874f1b5..62a1cc590 100644 --- a/docs/deployment/builders/dockerfiles.md +++ b/docs/deployment/builders/dockerfiles.md @@ -29,7 +29,7 @@ Dokku will only select the `dockerfile` builder if both the `herokuish` and `pac If an application was previously deployed via buildpacks, the following commands should be run before a Dockerfile deploy will succeed: ```shell -dokku config:unset --no-restart node-js-app DOKKU_PROXY_PORT_MAP +dokku proxy:clear node-js-app ``` ### Changing the `Dockerfile` location @@ -114,10 +114,10 @@ Dockerfile2 For security reasons - and as per [Docker recommendations](https://github.com/docker/docker/issues/13490) - Dockerfile-based deploys have variables available only during runtime. -For users that require customization in the `build` phase, you may use build arguments via the [docker-options plugin](/docs/advanced-usage/docker-options.md): +For users that require customization in the `build` phase, you may use build arguments via the [docker-options plugin](/docs/advanced-usage/docker-options.md). All environment variables set by the `config` plugin are automatically exported during a docker build, and thus `--build-arg` only requires setting a key without a value. ```shell -dokku docker-options:add node-js-app build '--build-arg NODE_ENV=production' +dokku docker-options:add node-js-app build '--build-arg NODE_ENV' ``` Once set, the Dockerfile usage would be as follows: diff --git a/docs/deployment/builders/herokuish-buildpacks.md b/docs/deployment/builders/herokuish-buildpacks.md index 947c36b97..60b295f65 100644 --- a/docs/deployment/builders/herokuish-buildpacks.md +++ b/docs/deployment/builders/herokuish-buildpacks.md @@ -163,7 +163,7 @@ dokku buildpacks:set-property node-js-app stack A change in the stack builder value will execute the `post-stack-set` trigger. -Finally, stack builders can be set or unset globally as a fallback. This will take precedence over a globally set `DOKKU_IMAGE` environment variable (`gliderlabs/herokuish:latest-20` by default). +Finally, stack builders can be set or unset globally as a fallback. This will take precedence over a globally set `DOKKU_IMAGE` environment variable (`gliderlabs/herokuish:latest-22` by default). ```shell # set globally @@ -213,18 +213,18 @@ dokku buildpacks:report ``` =====> node-js-app buildpacks information - Buildpacks computed stack: gliderlabs/herokuish:v0.5.23-20 - Buildpacks global stack: gliderlabs/herokuish:latest-20 + Buildpacks computed stack: gliderlabs/herokuish:v0.7.0-22 + Buildpacks global stack: gliderlabs/herokuish:latest-22 Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git - Buildpacks stack: gliderlabs/herokuish:v0.5.23-20 + Buildpacks stack: gliderlabs/herokuish:v0.7.0-20 =====> python-sample buildpacks information - Buildpacks computed stack: gliderlabs/herokuish:latest-20 - Buildpacks global stack: gliderlabs/herokuish:latest-20 + Buildpacks computed stack: gliderlabs/herokuish:latest-22 + Buildpacks global stack: gliderlabs/herokuish:latest-22 Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git,https://github.com/heroku/heroku-buildpack-python.git Buildpacks stack: =====> ruby-sample buildpacks information - Buildpacks computed stack: gliderlabs/herokuish:latest-20 - Buildpacks global stack: gliderlabs/herokuish:latest-20 + Buildpacks computed stack: gliderlabs/herokuish:latest-22 + Buildpacks global stack: gliderlabs/herokuish:latest-22 Buildpacks list: Buildpacks stack: ``` @@ -301,7 +301,7 @@ false If an application was previously deployed via Dockerfile, the following commands should be run before a buildpack deploy will succeed: ```shell -dokku config:unset --no-restart node-js-app DOKKU_PROXY_PORT_MAP +dokku ports:clear node-js-app ``` ### Using a specific buildpack version diff --git a/docs/deployment/methods/git.md b/docs/deployment/methods/git.md index 16b98a72f..e69d17aaa 100644 --- a/docs/deployment/methods/git.md +++ b/docs/deployment/methods/git.md @@ -160,19 +160,6 @@ dokku git:from-image node-js-app dokku/node-js-getting-started:latest "Camila" " If the image is a private image that requires a docker login to access, the `registry:login` command should be used to log into the registry. See the [registry documentation](/docs/advanced-usage/registry-management.md#logging-into-a-registry) for more details on this process. -Building an app from an image will result in the following files being extracted from the source image (with all custom paths specified for each file being respected): - -- `app.json` -- `nginx.conf.sigil` -- `Procfile` - -In the case where the repository is later modified to manually add any of the above files and deployed via `git push`, the files will still be extracted from the initial source image. To avoid this, please clear the `source-image` git property. It will be set back to the original source image on any subsequent `git:from-image` calls. - -```shell -# sets an empty value -dokku git:set node-js-app source-image -``` - Finally, certain images may require a custom build context in order for `ONBUILD ADD` and `ONBUILD COPY` statements to succeed. A custom build context can be specified via the `--build-dir` flag. All files in the specified `build-dir` will be copied into the repository for use within the `docker build` process. The build context _must_ be specified on each deploy, and is not otherwise persisted between builds. ```shell @@ -248,19 +235,6 @@ The `git:load-image` command can optionally take a git `user.name` and `user.ema docker image save dokku/node-js-getting-started:latest | ssh dokku@dokku.me git:load-image node-js-app dokku/node-js-getting-started:latest "Camila" "camila@example.com" ``` -Building an app from an image will result in the following files being extracted from the source image (with all custom paths specified for each file being respected): - -- `app.json` -- `nginx.conf.sigil` -- `Procfile` - -In the case where the repository is later modified to manually add any of the above files and deployed via `git push`, the files will still be extracted from the initial source image. To avoid this, please clear the `source-image` git property. It will be set back to the original source image on any subsequent `git:load-image` calls. - -```shell -# sets an empty value -dokku git:set node-js-app source-image -``` - Finally, certain images may require a custom build context in order for `ONBUILD ADD` and `ONBUILD COPY` statements to succeed. A custom build context can be specified via the `--build-dir` flag. All files in the specified `build-dir` will be copied into the repository for use within the `docker build` process. The build context _must_ be specified on each deploy, and is not otherwise persisted between builds. ```shell diff --git a/docs/deployment/zero-downtime-deploys.md b/docs/deployment/zero-downtime-deploys.md index c4e437797..57738dbaa 100644 --- a/docs/deployment/zero-downtime-deploys.md +++ b/docs/deployment/zero-downtime-deploys.md @@ -13,7 +13,7 @@ checks:skip [process-type(s)] Skip zero-downtime checks for all proc By default, Dokku will wait `10` seconds after starting each container before assuming it is up and proceeding with the deploy. Once this has occurred for all containers started by an application, traffic will be switched to point to your new containers. Dokku will also wait a further `60` seconds *after* the deploy is complete before terminating old containers in order to give time for long running connections to terminate. In either case, you may have more than one container running for a given application. -You may both create user-defined checks for web processes using a `CHECKS` file, as well as customize any and all parts of this experience using the checks plugin. +You may both create user-defined checks for web processes using the `healthchecks` key in the `app.json` file, as well as customize any and all parts of this experience using the checks plugin. > Web checks are performed via `curl` on Dokku host. Some application code - such > as the Django framework - checks for specific hostnames or header values, these @@ -42,7 +42,7 @@ There are certain settings that can be configured via environment variables: - `DOKKU_DEFAULT_CHECKS_WAIT`: (default: `10`) If no user-defined checks are specified - or if the process being checked is not a `web` process - this is the period of time Dokku will wait before checking that a container is still running. - `DOKKU_DOCKER_STOP_TIMEOUT`: (default: `10`) Configurable grace period given to the `docker stop` command. If a container has not stopped by this time, a `kill -9` signal or equivalent is sent in order to force-terminate the container. Both the `ps:stop` and `apps:destroy` commands *also* respect this value. If not specified, the Docker defaults for the [`docker stop` command](https://docs.docker.com/engine/reference/commandline/stop/) will be used. -The following settings may also be specified in the `CHECKS` file, though are available as environment variables in order to ease application reuse. +The following settings may also be specified in the `app.json` file, though are available as environment variables in order to ease application reuse. - `DOKKU_CHECKS_WAIT`: (default: `5`) Wait this many seconds for the container to start before running checks. - `DOKKU_CHECKS_TIMEOUT`: (default: `30`) Wait this many seconds for each response before marking it as a failure. @@ -135,97 +135,45 @@ dokku checks:report node-js-app --checks-disabled-list ## Customizing checks +> New as of 0.31.0 + If your application needs a longer period to boot up - perhaps to load data into memory, or because of slow boot time - you may also use Dokku's `checks` functionality to more precisely check whether an application can serve traffic or not. -Checks are run against the detected `web` process from your application's `Procfile`. For non-web processes, Dokku will fallback to the aforementioned process uptime check. +Healthchecks are run against all process from your application's `Procfile`. When no healthcheck is defined, Dokku will fallback to a process uptime check. -To specify checks, add a `CHECKS` file to the root of your project directory. The `CHECKS` file should be plain text and may contain: +One or more healthchecks can be defined in the `app.json` file - see the [deployment task documentation](/docs/advanced-usage/deployment-tasks.md) for more information on how this is extracted - under the `healthchecks.web` path: -- check instructions -- settings (NAME=VALUE) -- comments (lines starting with #) -- empty lines - -> For Dockerfile and Docker Image based deploys, the file *must* be in the `WORKDIR` directory of the built image. `/app` is used by default as the root container directory for buildpack-based deploys. - -### Check instructions - -The format of a check instruction is a path or relative URL, optionally followed by the expected content: - -``` -/about Our Amazing Team +```json +{ + "healthchecks": { + "web": [ + { + "type": "startup", + "name": "web check", + "description": "Checking if the app responds to the /health/ready endpoint", + "path": "/health/ready", + "attempts": 3 + } + ] +} ``` -The `CHECKS` file can contain multiple checks: +A healthcheck entry takes the following properties: -``` -/ My Amazing App -/stylesheets/index.css .body -/scripts/index.js $(function() -/images/logo.png -``` +- `attempts`: (default: `3` seconds) Number of retry attempts to perform on failure. +- `command`: (default `''` - empty string) Command to execute within container. +- `content`: (default: `''` - empty string) Content to search in http path check output. +initialDelay default: 0, unit: seconds) Number of seconds to wait after a container has started before triggering the healthcheck. +- `name`: (default: autogenerated) The name of the healthcheck. If unspecified, it will be autogenerated from the rest of the healthcheck information. +- `path`: (default: `/` - for http checks): An http path to check. +- `timeout`: (default: `5` seconds): Number of seconds to wait before timing out a healthcheck. +- `type`: (default: `""` - none): Type of the healthcheck. Options: liveness, readiness, startup. +- `uptime`: (default: `""` - none): Amount of time the container must be alive before the container is considered healthy. Any restarts will cause this to check to fail, and this check does not respect retries. +- `wait`: (default: `5` seconds): Number of seconds to wait between healthcheck attempts. -To check an application that supports multiple hostnames, use relative URLs that include the hostname: +> Warning: Healthchecks are implemented by specific scheduler plugins, and not all plugins support all options. Please consult the scheduler documentation for further details on what is supported. -``` -//admin.dokku.me Admin Dashboard -//static.dokku.me/logo.png -``` - -You can also specify the protocol to explicitly check HTTPS requests: - -``` -https://admin.dokku.me Admin Dashboard -https://static.dokku.me/logo.png -``` - -While a full URL may be used in order to invoke checks, if you are using relative URLs, the port *must* be omitted. - -> Changed as of 0.22.5 - -Please note that dollar sign bracket characters (`{` and `}`) must be escaped when used within a `CHECKS` file. Escaping follows golang template rules. The proper way to do this is via one of the following methods: - -``` -# escaping the `{` character -# using double-quotes -{{"{"}} - -# using raw string constants -{{`{`}} - -# escaping the `}` character -# using double-quotes -{{"}"}} - -# using raw string constants -{{`}`}} -``` - -### Templating Checks Files - -> New as of 0.22.5 - -An app's `CHECKS` file is sent through a single pass of the [`sigil`](https://github.com/gliderlabs/sigil/) templating tool. This enables usage of Golang templating within application `CHECKS` files. In addition to general templating access, access to app environment variables is also allowed via the `var` function: - -``` -{{ var "SOME_ENV_VAR" }} -``` - -This may be useful if certain zero-downtime checks require access to an app-specific value, such as a domain name. - -### Check settings - -The default behavior is to wait for `5` seconds before running the checks, to timeout the checks after `30` seconds, and to attempt the checks `5` times. If the checks fail `5` times, the deployment is considered failed and the old container will continue serving traffic. - -You can change the default behavior by setting `WAIT`, `TIMEOUT`, and `ATTEMPTS` to different values in the `CHECKS` file: - -``` -WAIT=30 # Wait 1/2 minute -TIMEOUT=60 # Timeout after a minute -ATTEMPTS=10 # Attempt checks 10 times - -/ My Amazing App -``` +See the [docker-container-healthchecker](https://github.com/dokku/docker-container-healthchecker) documentation for more details on how healthchecks are interpreted. ## Manually invoking checks @@ -312,182 +260,3 @@ dokku checks:run node-js-app web.3 -----> Running pre-flight checks Invalid container id specified (APP.web.3) ``` - -## Example: Successful Rails deployment - -In this example, a Rails application is successfully deployed to Dokku. The initial round of checks fails while the server is starting, but once it starts they succeed and the deployment is successful. `WAIT` is set to `10` because our application takes a while to boot up. `ATTEMPTS` is set to `6`, but the third attempt succeeds. - -### Successful `CHECKS` file - -``` -WAIT=10 -ATTEMPTS=6 -/check.txt simple_check -``` - -For this check to work, we've added a line to `config/routes.rb` that simply returns a string: - -``` -get '/check.txt', to: proc {[200, {}, ['simple_check']]} -``` - -### Successful deploy output - -> Note: The output has been trimmed for brevity. - -```shell -git push dokku master -``` - -``` ------> Cleaning up... ------> Building node-js-app from herokuish... ------> Adding BUILD_ENV to build environment... ------> Ruby app detected ------> Compiling Ruby/Rails ------> Using Ruby version: ruby-2.0.0 - -..... - ------> Discovering process types - Procfile declares types -> web ------> Releasing node-js-app... ------> Deploying node-js-app... ------> Running pre-flight checks ------> Attempt 1/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/check.txt => "simple_check" - ! -curl: (7) Failed to connect to 172.17.0.155 port 5000: Connection refused - ! Check attempt 1/6 failed. ------> Attempt 2/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/check.txt => "simple_check" - ! -curl: (7) Failed to connect to 172.17.0.155 port 5000: Connection refused - ! Check attempt 2/6 failed. ------> Attempt 3/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/check.txt => "simple_check" ------> All checks successful! -=====> node-js-app container output: - => Booting Thin - => Rails 4.2.0 application starting in production on http://0.0.0.0:5000 - => Run `rails server -h` for more startup options - => Ctrl-C to shutdown server - Thin web server (v1.6.3 codename Protein Powder) - Maximum connections set to 1024 - Listening on 0.0.0.0:5000, CTRL+C to stop -=====> end node-js-app container output ------> Running post-deploy ------> Configuring myapp.dokku.me... ------> Creating http nginx.conf - Reloading nginx ------> Shutting down old container in 60 seconds -=====> Application deployed: - http://myapp.dokku.me -``` - -## Example: Failing Rails deployment - -In this example, a Rails application fails to deploy. The reason for the failure is that the PostgreSQL database connection fails. The initial checks will fail while we wait for the server to start up, just like in the above example. However, once the server does start accepting connections, we will see an error 500 due to the PostgreSQL database connection failure. - -Once the attempts have been exceeded, the deployment fails and we see the container output, which shows the PostgreSQL connection errors. - -### Failing `CHECKS` file - -``` -WAIT=10 -ATTEMPTS=6 -/ -``` - -> The check to the root url `/` would normally access the database. - -### Failing deploy output - -> Note: The output has been trimmed for brevity. - -```shell -git push dokku master -``` - -``` ------> Cleaning up... ------> Building node-js-app from herokuish... ------> Adding BUILD_ENV to build environment... ------> Ruby app detected ------> Compiling Ruby/Rails ------> Using Ruby version: ruby-2.0.0 - -..... - -Discovering process types -Procfile declares types -> web -Releasing node-js-app... -Deploying node-js-app... -Running pre-flight checks ------> Attempt 1/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/ => "" - ! -curl: (7) Failed to connect to 172.17.0.188 port 5000: Connection refused - ! Check attempt 1/6 failed. ------> Attempt 2/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/ => "" - ! -curl: (7) Failed to connect to 172.17.0.188 port 5000: Connection refused - ! Check attempt 2/6 failed. ------> Attempt 3/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/ => "" - ! -curl: (22) The requested URL returned error: 500 Internal Server Error - ! Check attempt 3/6 failed. ------> Attempt 4/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/ => "" - ! -curl: (22) The requested URL returned error: 500 Internal Server Error - ! Check attempt 4/6 failed. ------> Attempt 5/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/ => "" - ! -curl: (22) The requested URL returned error: 500 Internal Server Error - ! Check attempt 5/6 failed. ------> Attempt 6/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/ => "" - ! -curl: (22) The requested URL returned error: 500 Internal Server Error -Could not start due to 1 failed checks. - ! Check attempt 6/6 failed. -=====> node-js-app container output: - => Booting Thin - => Rails 4.2.0 application starting in production on http://0.0.0.0:5000 - => Run `rails server -h` for more startup options - => Ctrl-C to shutdown server - Thin web server (v1.6.3 codename Protein Powder) - Maximum connections set to 1024 - Listening on 0.0.0.0:5000, CTRL+C to stop - Started GET "/" for 172.17.42.1 at 2015-03-26 21:36:47 +0000 - Is the server running on host "172.17.42.1" and accepting - TCP/IP connections on port 5431? - PG::ConnectionBad (could not connect to server: Connection refused - Is the server running on host "172.17.42.1" and accepting - TCP/IP connections on port 5431? - ): - vendor/bundle/ruby/2.0.0/gems/activerecord-4.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:651:in `initialize' - vendor/bundle/ruby/2.0.0/gems/activerecord-4.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:651:in `new' - vendor/bundle/ruby/2.0.0/gems/activerecord-4.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:651:in `connect' - vendor/bundle/ruby/2.0.0/gems/activerecord-4.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:242:in `initialize' - vendor/bundle/ruby/2.0.0/gems/activerecord-4.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:44:in `new' - vendor/bundle/ruby/2.0.0/gems/activerecord-4.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:44:in `postgresql_connection -=====> end node-js-app container output -/usr/bin/dokku: line 49: 23409 Killed dokku deploy "$APP" -To dokku@dokku.me:myapp - ! [remote rejected] dokku -> master (pre-receive hook declined) -error: failed to push some refs to 'dokku@dokku.me:myapp' -``` diff --git a/docs/development/plugin-creation.md b/docs/development/plugin-creation.md index 6cbd9b627..edebfc649 100644 --- a/docs/development/plugin-creation.md +++ b/docs/development/plugin-creation.md @@ -103,7 +103,7 @@ if ! command -v "nginx" &>/dev/null; then fi # `which` example -if ! which nginx >/dev/null 2>&1; then +if ! which nginx &>/dev/null; then dokku_log_fail "Missing nginx, install it" fi ``` diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index 796dcae93..1f75aef43 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -104,6 +104,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `app-json-get-content` + +- Description: Outputs the contents of the app-json file, if any +- Invoked by: Deployment checks +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `app-maybe-create` - Description: Creates an app (gated by whether this is globally enabled or not) @@ -297,6 +312,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `certs-force` + +- Description: Echos `true` if a cert should be simulated for the app, no output otherwise +- Invoked by: +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `check-deploy` - Description: Allows you to run checks on a deploy before Dokku allows the container to handle requests. @@ -599,6 +629,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x echo 'derp.dkr.ecr.us-east-1.amazonaws.com' ``` +### `deploy-source-set` + +- Description: Used to set metadata about how the app is being deployed +- Invoked by: `git:from-archive`, `git:from-image`, `git:load-image`, `git:sync`, and all git push commands +- Arguments: `$APP $SOURCE_TYPE $METADATA` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `docker-args-build` > Warning: Deprecated, please use `docker-args-process-build` instead @@ -1006,21 +1051,6 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` -### `network-compute-ports` - -- Description: Computes the ports for a given app container -- Invoked by: `internally triggered by proxy-build-config within proxy implementations` -- Arguments: `$APP $PROC_TYPE $IS_HEROKUISH_CONTAINER $CONTAINER_INDEX` -- Example: - -```shell -#!/usr/bin/env bash - -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x - -# TODO -``` - ### `network-config-exists` - Description: Returns whether the network configuration for a given app exists @@ -1066,21 +1096,6 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` -### `network-get-port` - -- Description: Return the port for a given app container -- Invoked by: `internally triggered by a deploy` -- Arguments: `$APP $PROCESS_TYPE $CONTAINER_ID $IS_HEROKUISH_CONTAINER` -- Example: - -```shell -#!/usr/bin/env bash - -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x - -# TODO -``` - ### `network-get-property` - Description: Return the value for an app's network property @@ -1226,6 +1241,97 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x nginx -t ``` +### `ports-clear` + +- Description: Clears the ports for a given app without triggering further restarts or rebuilds +- Invoked by: internally +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `ports-configure` + +- Description: Configures the initial proxy ports +- Invoked by: `internally triggered by proxy plugins` +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `ports-get` + +- Description: Returns a list of port mappings, newline delimited +- Invoked by: Various networking plugins +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `ports-get-available` + +- Description: Prints out an available port greater than 1024 +- Invoked by: Various networking plugins +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `ports-get-property` + +- Description: Return the value for an app's ports property +- Invoked by: +- Arguments: `$APP $KEY` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +APP="$1"; PROPERTY="$2" + +# TODO +``` + +### `ports-set-detected` + +- Description: Allows builders to specify detected port mappings for a given app +- Invoked by: Builder plugins +- Arguments: `$APP [$PORT_MAPPING...]` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `post-app-clone` - Description: Allows you to run commands after an app was cloned. @@ -1388,7 +1494,7 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x - Description: This trigger should be used to do stuff to containers after they are created but before they are started. They are explicitely for commands that may involve network traffic, and _not_ for commands that are self-contained, such as chown or tar. - Invoked by: `dokku run`, `dokku ps:rebuild`, `dokku deploy` -- Arguments "app|service" "$CONTAINER_ID" "$APP|$SERVICE" "$PHASE" +- Arguments: "app|service" "$CONTAINER_ID" "$APP|$SERVICE" "$PHASE" ```shell #!/usr/bin/env bash @@ -1498,8 +1604,8 @@ popd &>/dev/null ### `post-proxy-ports-update` -- Description: Allows you to run commands once the proxy port mappings for an app have been updated. It also sends the invoking command. This can be "add", "clear" or "remove". -- Invoked by: `dokku proxy:ports-add`, `dokku proxy:ports-clear`, `dokku proxy:ports-remove` +- Description: Allows you to run commands once the port mappings for an app have been updated. It also sends the invoking command. This can be "add", "clear" or "remove". +- Invoked by: `dokku ports:add`, `dokku ports:clear`, `dokku ports:remove` - Arguments: `$APP` `action name` - Example: @@ -1715,7 +1821,7 @@ APP="$1"; IMAGE_TAG="$2"; IMAGE=$(get_app_image_name $APP $IMAGE_TAG) dokku_log_info1 "Installing GraphicsMagick..." CMD="cat > gm && \ - dpkg -s graphicsmagick >/dev/null 2>&1 || \ + dpkg -s graphicsmagick &>/dev/null || \ (apt-get update -qq && apt-get -qq -y --no-install-recommends install graphicsmagick && apt-get clean)" CID=$(docker run $DOKKU_GLOBAL_RUN_ARGS -i -a stdin $IMAGE /bin/bash -c "$CMD") @@ -1810,6 +1916,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `procfile-exists` + +- Description: Checks if a procfile exists for the specified app +- Invoked by: `internally` +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `proxy-build-config` - Description: Builds the proxy implementation configuration for a given app @@ -1842,7 +1963,9 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x ### `proxy-configure-ports` -- Description: Configures the proxy port mapping +> Warning: Deprecated, please use `ports-configure` instead + +- Description: Configures the port mapping - Invoked by: `internally triggered by proxy plugins` - Arguments: `$APP` - Example: diff --git a/docs/getting-started/troubleshooting.md b/docs/getting-started/troubleshooting.md index 49f9953b1..0b876d5e1 100644 --- a/docs/getting-started/troubleshooting.md +++ b/docs/getting-started/troubleshooting.md @@ -72,12 +72,12 @@ For example, if you have an `EXPOSE` directive like so: EXPOSE 8000 ``` -The proxy port mapping will be `http:8000:8000`. +The port mapping will be `http:8000:8000`. To avoid this issue, either of the following can be done: -- Remove `EXPOSE` directive: This will require respecting the `$PORT` environment variable (automatically set by Dokku). Once that change is deployed, the port mapping should be cleared via the `dokku proxy:ports-clear $APP` command (where `$APP` is your app name). -- Update the port mapping: Updating the port mapping to redirect port `80` to your app's exposed port via `dokku proxy:ports-set $APP http:80:$EXPOSED_PORT` can also fix the issue. This will also allow certificate management and the letsencrypt plugin to work correctly. +- Remove `EXPOSE` directive: This will require respecting the `$PORT` environment variable (automatically set by Dokku). Once that change is deployed, the port mapping should be cleared via the `dokku ports:clear $APP` command (where `$APP` is your app name). +- Update the port mapping: Updating the port mapping to redirect port `80` to your app's exposed port via `dokku ports:set $APP http:80:$EXPOSED_PORT` can also fix the issue. This will also allow certificate management and the letsencrypt plugin to work correctly. See the [port management documentation](/docs/networking/port-management.md) for more information on how Dokku exposes ports for applications and how you can configure these for your app. @@ -233,16 +233,14 @@ If this solves the issue temporarily, longer term you should consider [configuri ### My application deploys properly, but won't load in browser "connection refused" -This could be a result of a bad proxy configuration (`http:5000:5000` may be incorrect). Run `dokku proxy:report myapp` to check if your app has the correct proxy configuration. It should show something like the following. +This could be a result of a bad proxy configuration (`http:5000:5000` may be incorrect). Run `dokku ports:report node-js-app` to check if your app has the correct proxy configuration. It should show something like the following. ``` -=====> myapp proxy information - Proxy enabled: true - Proxy port map: http:80:5000 https:443:5000 - Proxy type: nginx +=====> node-js-app ports information + Port map: http:80:5000 https:443:5000 ``` -Set `dokku proxy:ports-set front http:80:5000` to get proxy correctly configured for http endpoint. +Set `dokku ports:set node-js-app http:80:5000` to get proxy correctly configured for http endpoint. ### I deployed a new app but now subdomains are miss-routed diff --git a/docs/getting-started/upgrading/index.md b/docs/getting-started/upgrading/index.md index 0279ee147..9332e8443 100644 --- a/docs/getting-started/upgrading/index.md +++ b/docs/getting-started/upgrading/index.md @@ -18,6 +18,7 @@ Docker releases updates periodically to their engine. We recommend reading their Before upgrading, check the migration guides to get comfortable with new features and prepare your deployment to be upgraded. +- [Upgrading to 0.31](/docs/appendices/0.31.0-migration-guide.md) - [Upgrading to 0.30](/docs/appendices/0.30.0-migration-guide.md) - [Upgrading to 0.29](/docs/appendices/0.29.0-migration-guide.md) - [Upgrading to 0.28](/docs/appendices/0.28.0-migration-guide.md) diff --git a/docs/networking/network.md b/docs/networking/network.md index 899a97ded..30f898de4 100644 --- a/docs/networking/network.md +++ b/docs/networking/network.md @@ -173,6 +173,13 @@ dokku network:set node-js-app attach-post-deploy other-test-network dokku network:set node-js-app initial-network global-network ``` +Multiple networks can also be specified for the `attach-post-create` and `attach-post-deploy` phases. + +```shell +# one or more networks can be specified +dokku network:set node-js-app attach-post-create test-network test-network-2 +``` + Setting the `attach` network property to an empty value will de-associate the container with the network. ```shell diff --git a/docs/networking/port-management.md b/docs/networking/port-management.md index d8946b220..10d17a7ac 100644 --- a/docs/networking/port-management.md +++ b/docs/networking/port-management.md @@ -1,37 +1,52 @@ # Port Management -> New as of 0.5.0, Enhanced in 0.6.0 +> New as of 0.31.0, replaces the previous `proxy:ports*` commands ``` -proxy:ports # List proxy port mappings for an app -proxy:ports-add :: [::...] # Add proxy port mappings to an app -proxy:ports-clear # Clear all proxy port mappings for an app -proxy:ports-remove [|::...] # Remove specific proxy port mappings from an app -proxy:ports-set :: [::...] # Set proxy port mappings for an app +ports:list # List port mappings for an app +ports:add :: [::...] # Add port mappings to an app +ports:clear # Clear all port mappings for an app +ports:remove [|::...] # Remove specific port mappings from an app +ports:set :: [::...] # Set port mappings for an app ``` -In Dokku 0.5.0, port proxying was decoupled from the `nginx-vhosts` plugin into the proxy plugin. Dokku 0.6.0 introduced the ability to map host ports to specific container ports. In the future this will allow other proxy software - such as HAProxy or Caddy - to be used in place of nginx. - ## Usage > Warning: Mapping alternative ports may conflict with the active firewall installed on your server or hosting provider. Such software includes - but is not limited to - AWS Security Groups, iptables, and UFW. Please consult the documentation for those softwares as applicable. > > Users should also avoid setting the `PORT` environment variable. Dokku will use port mappings to set this value. Overriding this manually may cause issues in application routing. -> New as of 0.6.0 - -You can now configure `host -> container` port mappings with the `proxy:ports-*` commands. This mapping is currently supported by the built-in nginx-vhosts plugin. +You can now configure `host -> container` port mappings with the `ports:*` commands. This mapping is currently supported by the built-in nginx-vhosts plugin. By default, buildpack apps and dockerfile apps **without** explicitly exposed ports (i.e. using the `EXPOSE` directive) will be configured with a listener on port `80` (and additionally a listener on 443 if ssl is enabled) that will proxy to the application container on port `5000`. Dockerfile apps **with** explicitly exposed ports will be configured with a listener on each exposed port and will proxy to that same port of the deployed application container. -> Note: This default behavior **will not** be automatically changed on subsequent pushes and must be manipulated with the `proxy:ports-*` commands detailed below. +> Note: This default behavior **will not** be automatically changed on subsequent pushes and must be manipulated with the `ports:*` commands detailed below. + +### Port Scheme + +The proxy port scheme is as follows: + +- `SCHEME:HOST_PORT:CONTAINER_PORT` + +The scheme metadata can be used by proxy implementations in order to properly handle proxying of requests. For example, the built-in `nginx-vhosts` proxy implementation supports the `http`, `https`, `grpc` and `grpcs` schemes. +For the `grpc` and `grpcs` see [nginx blog post on grpc](https://www.nginx.com/blog/nginx-1-13-10-grpc/). + +Developers of proxy implementations are encouraged to use whatever schemes make the most sense, and ignore configurations which they do not support. For instance, a `udp` proxy implementation can safely ignore `http` and `https` port mappings. + +To change the proxy implementation in use for an application, use the `proxy:set` command: + +```shell +# no validation will be performed against +# the specified proxy implementation +dokku proxy:set node-js-app nginx +``` ### Listing port mappings -To inspect the port mapping for a given application, use the `proxy:ports` command: +To inspect the port mapping for a given application, use the `ports:list` command: ```shell -dokku proxy:ports node-js-app +dokku ports:list node-js-app ``` ``` @@ -62,15 +77,13 @@ curl http://node-js-app.dokku.me:8080 curl: (7) Failed to connect to node-js-app.dokku.me port 8080: Connection refused ``` -However, we can use the `proxy:ports-add` command to add a second external port mapping - `8080` - to our application's port `5000`. +However, we can use the `ports:add` command to add a second external port mapping - `8080` - to our application's port `5000`. ```shell -dokku proxy:ports-add node-js-app http:8080:5000 +dokku ports:add node-js-app http:8080:5000 ``` ``` ------> Setting config vars - DOKKU_PROXY_PORT_MAP: http:80:5000 http:8080:5000 -----> Configuring node-js-app.dokku.me...(using built-in template) -----> Creating http nginx.conf Reloading nginx @@ -98,15 +111,13 @@ Hello World! ### Setting all port mappings at once -Port mappings can also be force set using the `proxy:ports-set` command. +Port mappings can also be force set using the `ports:set` command. ```shell -dokku proxy:ports-set node-js-app http:8080:5000 +dokku ports:set node-js-app http:8080:5000 ``` ``` ------> Setting config vars - DOKKU_PROXY_PORT_MAP: http:80:5000 http:8080:5000 -----> Configuring node-js-app.dokku.me...(using built-in template) -----> Creating http nginx.conf Reloading nginx @@ -114,21 +125,21 @@ dokku proxy:ports-set node-js-app http:8080:5000 ### Removing a port mapping -A port mapping can be removed using the `proxy:ports-remove` command if it no longer necessary: +A port mapping can be removed using the `ports:remove` command if it no longer necessary: ```shell -dokku proxy:ports-remove node-js-app http:80:5000 +dokku ports:remove node-js-app http:80:5000 ``` Ports may also be removed by specifying only the `host-port` value. This effectively acts as a wildcard and removes all mappings for that particular host port. ```shell -dokku proxy:ports-remove node-js-app http:80 +dokku ports:remove node-js-app http:80 ``` ## Port management by Deployment Method -> Warning: If you set a proxy port map but _do not have a global domain set_, Dokku will reset that map upon first deployment. +> Warning: If you set a port map but _do not have a global domain set_, Dokku will reset that map upon first deployment. ### Buildpacks @@ -156,10 +167,10 @@ The application would be exposed to the user at `node-js-app.dokku.me:1234`. If ```shell # add a port mapping to port 80 -dokku proxy:ports-add node-js-app http:80:1234 +dokku ports:add node-js-app http:80:1234 # remove the incorrect port mapping -dokku proxy:ports-remove node-js-app http:1234:1234 +dokku ports:remove node-js-app http:1234:1234 ``` #### Applications not using EXPOSE @@ -169,24 +180,40 @@ Any application that does not use an `EXPOSE` directive will result in Dokku def - modify your application to support the `PORT` environment variable. - switch to using an `EXPOSE` directive in your Dockerfile. -#### Switching between `EXPOSE` usage modes +### Displaying ports reports for an app -When switching between `EXPOSE` usage modes, it is important to reset your port management. The following two commands can be used to reset your state and redeploy your application. +You can get a report about the app's ports status using the `ports:report` command: ```shell -# assuming your application is called `node-js-app` -dokku config:unset --no-restart node-js-app DOKKU_DOCKERFILE_PORTS PORT -dokku proxy:ports-clear node-js-app +dokku ports:report ``` -### Docker Image +``` +=====> node-js-app ports information + Port map detected: http:80:5000 + Port map: http:80:5000 https:443:5000 +=====> python-sample ports information + Port map detected: http:80:5000 + Port map: http:80:5000 +=====> ruby-sample ports information + Port map detected: http:80:5000 + Port map: http:80:5000 +``` -When deploying an image, we will use `docker inspect` to extract the `ExposedPorts` configuration and if defined, use that to populate port mapping. If this behavior is not desired, you can override that configuration variable with the following commands. +You can run the command for a specific app also. ```shell -# assuming your application is called `node-js-app` -dokku config:set node-js-app DOKKU_DOCKERFILE_PORTS="1234/tcp 80/tcp" -dokku proxy:ports-clear node-js-app +dokku ports:report node-js-app ``` -All other port-related behavior is the same as when deploying via Dockerfile. +``` +=====> node-js-app ports information + Port map detected: http:80:5000 + Port map: http:80:5000 https:443:5000 +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku ports:report node-js-app --ports-map +``` diff --git a/docs/networking/proxies/caddy.md b/docs/networking/proxies/caddy.md index 0eaf00eb6..fa1e70112 100644 --- a/docs/networking/proxies/caddy.md +++ b/docs/networking/proxies/caddy.md @@ -103,7 +103,7 @@ You can use these modifiers as follows: dokku caddy:logs --tail --num 10 ``` -The above command will show logs continually from the vector container, with an initial history of 10 log lines +The above command will show logs continually from the caddy container, with an initial history of 10 log lines ### Changing the Caddy log level diff --git a/docs/networking/proxies/haproxy.md b/docs/networking/proxies/haproxy.md index d4e64d8c6..94d57faa7 100644 --- a/docs/networking/proxies/haproxy.md +++ b/docs/networking/proxies/haproxy.md @@ -15,7 +15,7 @@ haproxy:stop # Stops the haproxy server ## Requirements -Using the `caddy` plugin integration requires the `docker-compose-plugin` for Docker. See [this document](https://docs.docker.com/compose/install/) from the Docker documentation for more information on the installation process for the `docker-compose-plugin`. +Using the `haproxy` plugin integration requires the `docker-compose-plugin` for Docker. See [this document](https://docs.docker.com/compose/install/) from the Docker documentation for more information on the installation process for the `docker-compose-plugin`. ## Usage diff --git a/docs/networking/proxies/nginx.md b/docs/networking/proxies/nginx.md index 2bb1ecca6..2c85783c5 100644 --- a/docs/networking/proxies/nginx.md +++ b/docs/networking/proxies/nginx.md @@ -376,9 +376,8 @@ Unsetting this value is the same as enabling custom nginx config usage. {{ .PROXY_PORT }} Non-SSL nginx listener port (same as `DOKKU_PROXY_PORT` config var) {{ .PROXY_SSL_PORT }} SSL nginx listener port (same as `DOKKU_PROXY_SSL_PORT` config var) {{ .NOSSL_SERVER_NAME }} List of non-SSL VHOSTS -{{ .PROXY_PORT_MAP }} List of port mappings (same as `DOKKU_PROXY_PORT_MAP` config var) -{{ .PROXY_UPSTREAM_PORTS }} List of configured upstream ports (derived from `DOKKU_PROXY_PORT_MAP` config var) -{{ .RAW_TCP_PORTS }} List of exposed tcp ports as defined by Dockerfile `EXPOSE` directive (**Dockerfile apps only**) +{{ .PROXY_PORT_MAP }} List of port mappings (same as the `map` ports property) +{{ .PROXY_UPSTREAM_PORTS }} List of configured upstream ports (derived from the `map` ports property) {{ .SSL_INUSE }} Boolean set when an app is SSL-enabled {{ .SSL_SERVER_NAME }} List of SSL VHOSTS ``` @@ -506,7 +505,7 @@ See the [proxy documentation](/docs/networking/proxy-management.md) for more inf ### Managing Proxy Port mappings -See the [proxy documentation](/docs/networking/proxy-management.md#proxy-port-mapping) for more information on how to manage ports proxied for your app. +See the [ports documentation](/docs/networking/port-management.md) for more information on how to manage ports proxied for your app. ### Regenerating nginx config diff --git a/docs/networking/proxies/openresty.md b/docs/networking/proxies/openresty.md new file mode 100644 index 000000000..65299f462 --- /dev/null +++ b/docs/networking/proxies/openresty.md @@ -0,0 +1,164 @@ +# OpenResty Proxy + +> New as of 0.31.0 + +Dokku can provide integration with the [OpenResty](https://openresty.org/) proxy service by utilizing the Docker label-based integration implemented by [openresty-docker-proxy](https://github.com/dokku/openresty-docker-proxy). + +``` +openresty:report [] [] # Displays a openresty report for one or more apps +openresty:logs [--num num] [--tail] # Display openresty log output +openresty:set () # Set or clear an openresty property for an app +openresty:show-config # Display openresty compose config +openresty:start # Starts the openresty server +openresty:stop # Stops the openresty server +``` + +## Requirements + +Using the `openresty` plugin integration requires the `docker-compose-plugin` for Docker. See [this document](https://docs.docker.com/compose/install/) from the Docker documentation for more information on the installation process for the `docker-compose-plugin`. + +## Usage + +> Warning: As using multiple proxy plugins on a single Dokku installation can lead to issues routing requests to apps, doing so should be avoided. As the default proxy implementation is nginx, users are encouraged to stop the nginx service before switching to OpenResty. + +The OpenResty plugin has specific rules for routing requests: + +- OpenResty integration is exposed via docker labels attached to containers. Changes in labels require either app deploys or rebuilds. +- While OpenResty will respect labels associated with other containers, only `web` containers have OpenResty labels injected by the plugin. +- Only `http:80` and `https:443` port mappings are supported at this time. +- Requests are routed as soon as the container is running and passing healthchecks. + +### Switching to OpenResty + +To use the OpenResty plugin, use the `proxy:set` command for the app in question: + +```shell +dokku proxy:set node-js-app openresty +``` + +This will enable the docker label-based OpenResty integration. All future deploys will inject the correct labels for OpenResty to read and route requests to containers. Due to the docker label-based integration used by OpenResty, a single deploy or rebuild will be required before requests will route successfully. + +```shell +dokku ps:rebuild node-js-app +``` + +Any changes to domains or port mappings will also require either a deploy or rebuild. + +### Starting OpenResty container + +OpenResty can be started via the `openresty:start` command. This will start a OpenResty container via the `docker compose up` command. + +```shell +dokku openresty:start +``` + +### Stopping the OpenResty container + +OpenResty may be stopped via the `openresty:stop` command. + +```shell +dokku openresty:stop +``` + +The OpenResty container will be stopped and removed from the system. If the container is not running, this command will do nothing. + +### Showing the OpenResty compose config + +For debugging purposes, it may be useful to show the OpenResty compose config. This can be achieved via the `openresty:show-config` command. + +```shell +dokku openresty:show-config +``` + +### Customizing the OpenResty container image + +While the default OpenResty image is hardcoded, users may specify an alternative by setting the `image` property with the `--global` flag: + +```shell +dokku openresty:set --global image dokku/openresty-docker-proxy:0.5.6 +``` + +#### Checking the OpenResty container's logs + +It may be necessary to check the OpenResty container's logs to ensure that OpenResty is operating as expected. This can be performed with the `openresty:logs` command. + +```shell +dokku openresty:logs +``` + +This command also supports the following modifiers: + +```shell +--num NUM # the number of lines to display +--tail # continually stream logs +``` + +You can use these modifiers as follows: + +```shell +dokku openresty:logs --tail --num 10 +``` + +The above command will show logs continually from the openresty container, with an initial history of 10 log lines + +### SSL Configuration + +The OpenResty plugin only supports automatic ssl certificates from it's letsencrypt integration. Managed certificates provided by the `certs` plugin are ignored. + +#### Enabling letsencrypt integration + +By default, letsencrypt is disabled and https port mappings are ignored. To enable, set the `letsencrypt-email` property with the `--global` flag: + +```shell +dokku openresty:set --global letsencrypt-email automated@dokku.sh +``` + +After enabling, the OpenResty container will need to be restarted and apps will need to be rebuilt. All http requests will then be redirected to https. + +#### Customizing the letsencrypt server + +The letsencrypt integration is set to the production letsencrypt server by default. To change this, set the `letsencrypt-server` property with the `--global` flag: + +```shell +dokku openresty:set --global letsencrypt-server https://acme-staging-v02.api.letsencrypt.org/directory +``` + +After enabling, the OpenResty container will need to be restarted and apps will need to be rebuilt to retrieve certificates from the new server. + +## Displaying OpenResty reports for an app + +You can get a report about the app's OpenResty config using the `openresty:report` command: + +```shell +dokku openresty:report +``` + +``` +=====> node-js-app openresty information + Openresty image: dokku/openresty-docker-proxy:0.5.6 + Openresty letsencrypt email: automated@dokku.sh +=====> python-app openresty information + Openresty image: dokku/openresty-docker-proxy:0.5.6 + Openresty letsencrypt email: automated@dokku.sh +=====> ruby-app openresty information + Openresty image: dokku/openresty-docker-proxy:0.5.6 + Openresty letsencrypt email: automated@dokku.sh +``` + +You can run the command for a specific app also. + +```shell +dokku openresty:report node-js-app +``` + +``` +=====> node-js-app openresty information + Openresty image: dokku/openresty-docker-proxy:0.5.6 + Openresty letsencrypt email: automated@dokku.sh +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku openresty:report node-js-app --openresty-letsencrypt-email +``` diff --git a/docs/networking/proxies/traefik.md b/docs/networking/proxies/traefik.md index 9a793870f..ceff53c77 100644 --- a/docs/networking/proxies/traefik.md +++ b/docs/networking/proxies/traefik.md @@ -102,7 +102,7 @@ You can use these modifiers as follows: dokku traefik:logs --tail --num 10 ``` -The above command will show logs continually from the vector container, with an initial history of 10 log lines +The above command will show logs continually from the traefik container, with an initial history of 10 log lines ### Changing the Traefik log level diff --git a/docs/networking/proxy-management.md b/docs/networking/proxy-management.md index a716952fb..4366acbe4 100644 --- a/docs/networking/proxy-management.md +++ b/docs/networking/proxy-management.md @@ -73,15 +73,12 @@ dokku proxy:report =====> node-js-app proxy information Proxy enabled: true Proxy type: nginx - Proxy port map: http:80:5000 https:443:5000 =====> python-sample proxy information Proxy enabled: true Proxy type: nginx - Proxy port map: http:80:5000 =====> ruby-sample proxy information Proxy enabled: true Proxy type: nginx - Proxy port map: http:80:5000 ``` You can run the command for a specific app also. @@ -94,7 +91,6 @@ dokku proxy:report node-js-app =====> node-js-app proxy information Proxy enabled: true Proxy type: nginx - Proxy port map: http:80:5000 https:443:5000 ``` You can pass flags which will output only the value of the specific information you want. For example: @@ -105,22 +101,7 @@ dokku proxy:report node-js-app --proxy-type #### Proxy Port Scheme -The proxy port scheme is as follows: - -- `SCHEME:HOST_PORT:CONTAINER_PORT` - -The scheme metadata can be used by proxy implementations in order to properly handle proxying of requests. For example, the built-in `nginx-vhosts` proxy implementation supports the `http`, `https`, `grpc` and `grpcs` schemes. -For the `grpc` and `grpcs` see [nginx blog post on grpc](https://www.nginx.com/blog/nginx-1-13-10-grpc/). - -Developers of proxy implementations are encouraged to use whatever schemes make the most sense, and ignore configurations which they do not support. For instance, a `udp` proxy implementation can safely ignore `http` and `https` port mappings. - -To change the proxy implementation in use for an application, use the `proxy:set` command: - -```shell -# no validation will be performed against -# the specified proxy implementation -dokku proxy:set node-js-app nginx -``` +See the [port scheme documentation](/docs/networking/port-management.md#port-scheme) for more information on the port mapping scheme used by dokku. ### Proxy port mapping @@ -136,7 +117,7 @@ From Dokku versions `0.5.0` until `0.11.0`, enabling or disabling an application Custom plugins names _must_ have the suffix `-vhosts` or scheduler overriding via `proxy:set` may not function as expected. -At this time, the following dokku commands are used to implement a complete proxy implementation. +At this time, the following dokku commands are used to interact with a complete proxy implementation. - `domains:add`: Adds a given domain to an app. - triggers: `post-domains-update` @@ -158,13 +139,13 @@ At this time, the following dokku commands are used to implement a complete prox - triggers: `proxy-disable` - `proxy:enable`: Enables the proxy configuration for an app. - triggers: `proxy-enable` -- `proxy:ports-add`: Adds one or more port mappings to an app +- `ports:add`: Adds one or more port mappings to an app - triggers: `post-proxy-ports-update` -- `proxy:ports-clear`: Clears out all port mappings for an app. +- `ports:clear`: Clears out all port mappings for an app. - triggers: `post-proxy-ports-update` -- `proxy:ports-remove`: Removes one or more port mappings from an app. +- `ports:remove`: Removes one or more port mappings from an app. - triggers: `post-proxy-ports-update` -- `proxy:ports-set`: Sets all port mappings for an app. +- `ports:set`: Sets all port mappings for an app. - triggers: `post-proxy-ports-update` Proxy implementations may decide to omit some functionality here, or use plugin triggers to supplement config with information from other plugins. diff --git a/docs/processes/process-management.md b/docs/processes/process-management.md index 1527ccb29..9d3e5fbae 100644 --- a/docs/processes/process-management.md +++ b/docs/processes/process-management.md @@ -140,7 +140,6 @@ For initial app deploys, Dokku will default to starting a single `web` process f There are also a few other exceptions for the `web` process. -- Custom checks defined by a `CHECKS` file only apply to the `web` process type. - By default, the built-in nginx proxy implementation only proxies the `web` process (others may be handled via a custom `nginx.conf.sigil`). - See the [nginx request proxying documentation](/docs/networking/proxies/nginx.md#request-proxying) for more information on how nginx handles proxied requests. - Only the `web` process may be bound to an external port. diff --git a/docs/processes/scheduled-cron-tasks.md b/docs/processes/scheduled-cron-tasks.md index fc7c59859..c53a97d8b 100644 --- a/docs/processes/scheduled-cron-tasks.md +++ b/docs/processes/scheduled-cron-tasks.md @@ -3,8 +3,9 @@ > New as of 0.23.0 ``` -cron:list # List scheduled cron tasks for an app -cron:report [] [] # Display report about an app +cron:list [--format json|stdout] # List scheduled cron tasks for an app +cron:report [] [] # Display report about an app +cron:run [--detach] # Run a cron task on the fly ``` ## Usage @@ -62,6 +63,32 @@ cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5 @daily node index.js cGhwPT09dHJ1ZT09PSogKiAqICogKg== * * * * * true ``` +The output can also be displayed in json format: + +```shell +dokku cron:list node-js-app --format json +``` + +``` +[{"id":"cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5","app":"node-js-app","command":"node index.js","schedule":"@daily"}] +``` + +#### Executing a cron task on the fly + +Cron tasks can be invoked via the `cron:run` command. This command takes an `app` argument and a `cron id` (retrievable from `cron:list` output). + +```shell +dokku cron:run node-js-app cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5 +``` + +By default, the task is run in an attached container - as supported by the scheduler. To run in a background detached container, specify the `--detach` flag: + +```shell +dokku cron:run node-js-app cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5 --detach +``` + +All one-off cron executions have their containers terminated after invocation. + #### Displaying reports You can get a report about the cron configuration for apps using the `cron:report` command: diff --git a/docs/template.html b/docs/template.html index ce49b3a88..1832e64ae 100644 --- a/docs/template.html +++ b/docs/template.html @@ -178,6 +178,7 @@ Caddy Proxy Haproxy Proxy Nginx Proxy + Openresty Proxy Traefik Proxy Advanced Usage diff --git a/dokku b/dokku index 23570f28d..fdef028b2 100755 --- a/dokku +++ b/dokku @@ -4,7 +4,6 @@ shopt -s nullglob export DOCKER_BIN=docker if [[ -r /etc/default/dokku ]]; then - # shellcheck disable=SC1091 source /etc/default/dokku fi @@ -31,9 +30,8 @@ fi export DOKKU_HOST_ROOT=${DOKKU_HOST_ROOT:=$DOKKU_ROOT} export DOKKU_DISTRO -# shellcheck disable=SC1091 DOKKU_DISTRO=$( - . /etc/os-release >/dev/null 2>&1 || true + . /etc/os-release &>/dev/null || true echo "$ID" ) @@ -41,8 +39,8 @@ DOKKU_DISTRO=$( # configuration can ever override the DOCKER_BIN value export DOCKER_BIN=${DOCKER_BIN:="docker"} -export DOKKU_IMAGE=${DOKKU_IMAGE:="gliderlabs/herokuish:latest-20"} -export DOKKU_CNB_BUILDER=${DOKKU_CNB_BUILDER:="heroku/buildpacks:20"} +export DOKKU_IMAGE=${DOKKU_IMAGE:="gliderlabs/herokuish:latest-22"} +export DOKKU_CNB_BUILDER=${DOKKU_CNB_BUILDER:="heroku/builder:22"} export DOKKU_LIB_ROOT=${DOKKU_LIB_PATH:="/var/lib/dokku"} export PLUGIN_PATH=${PLUGIN_PATH:="$DOKKU_LIB_ROOT/plugins"} @@ -109,12 +107,10 @@ fi if [[ -n "$SSH_ORIGINAL_COMMAND" ]]; then export -n SSH_ORIGINAL_COMMAND if [[ $1 =~ config-* ]] || [[ $1 =~ docker-options* ]]; then - # shellcheck disable=SC2086 xargs $0 <<<$SSH_ORIGINAL_COMMAND exit $? else set -f - # shellcheck disable=SC2086 $0 $SSH_ORIGINAL_COMMAND set +f exit $? @@ -147,6 +143,9 @@ execute_dokku_cmd() { nginx | nginx:*) local PLUGIN_NAME=${PLUGIN_NAME/nginx/nginx-vhosts} ;; + openresty | openresty:*) + local PLUGIN_NAME=${PLUGIN_NAME/openresty/openresty-vhosts} + ;; traefik | traefik:*) local PLUGIN_NAME=${PLUGIN_NAME/traefik/traefik-vhosts} ;; diff --git a/plugins/20_events/app-json-get-content b/plugins/20_events/app-json-get-content new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/app-json-get-content @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/certs-force b/plugins/20_events/certs-force new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/certs-force @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/install b/plugins/20_events/install index d1eea5198..9a352e198 100755 --- a/plugins/20_events/install +++ b/plugins/20_events/install @@ -14,7 +14,6 @@ trigger-events-install() { # This can be done unconditionally as mkdir -p # exits gracefully if the path already exists - # shellcheck disable=SC2174 mkdir -m 775 -p "$DOKKU_LOGS_DIR" case "$DOKKU_DISTRO" in arch | debian | raspbian) @@ -64,7 +63,7 @@ $DOKKU_LOGS_DIR/*.log { compress delaycompress postrotate - reload rsyslog >/dev/null 2>&1 || true + reload rsyslog &>/dev/null || true endscript create 664 syslog dokku } diff --git a/plugins/20_events/ports-clear b/plugins/20_events/ports-clear new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/ports-clear @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/ports-configure b/plugins/20_events/ports-configure new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/ports-configure @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/ports-get b/plugins/20_events/ports-get new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/ports-get @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/ports-get-available b/plugins/20_events/ports-get-available new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/ports-get-available @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/ports-set-detected b/plugins/20_events/ports-set-detected new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/ports-set-detected @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/procfile-exists b/plugins/20_events/procfile-exists new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/procfile-exists @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/app-json/Makefile b/plugins/app-json/Makefile index db0133b5d..bff87b1a8 100644 --- a/plugins/app-json/Makefile +++ b/plugins/app-json/Makefile @@ -1,5 +1,5 @@ SUBCOMMANDS = subcommands/report subcommands/set -TRIGGERS = triggers/app-json-process-deploy-parallelism triggers/core-post-deploy triggers/core-post-extract triggers/install triggers/post-app-clone-setup triggers/post-app-rename triggers/post-app-rename-setup triggers/post-create triggers/post-delete triggers/post-deploy triggers/pre-deploy triggers/report +TRIGGERS = triggers/app-json-process-deploy-parallelism triggers/app-json-get-content triggers/core-post-deploy triggers/core-post-extract triggers/install triggers/post-app-clone-setup triggers/post-app-rename triggers/post-app-rename-setup triggers/post-create triggers/post-delete triggers/post-deploy triggers/pre-deploy triggers/report BUILD = commands subcommands triggers PLUGIN_NAME = app-json diff --git a/plugins/app-json/src/triggers/triggers.go b/plugins/app-json/src/triggers/triggers.go index 1b49fdc4d..04580d2e2 100644 --- a/plugins/app-json/src/triggers/triggers.go +++ b/plugins/app-json/src/triggers/triggers.go @@ -22,6 +22,9 @@ func main() { appName := flag.Arg(0) processType := flag.Arg(1) err = appjson.TriggerAppJSONProcessDeployParallelism(appName, processType) + case "app-json-get-content": + appName := flag.Arg(0) + err = appjson.TriggerAppJSONGetContent(appName) case "core-post-deploy": appName := flag.Arg(0) err = appjson.TriggerCorePostDeploy(appName) diff --git a/plugins/app-json/triggers.go b/plugins/app-json/triggers.go index b6e117162..4f33d12c8 100644 --- a/plugins/app-json/triggers.go +++ b/plugins/app-json/triggers.go @@ -37,6 +37,28 @@ func TriggerAppJSONProcessDeployParallelism(appName string, processType string) return nil } +// TriggerAppJSONGetContent outputs the contents of the app-json file, if any +func TriggerAppJSONGetContent(appName string) error { + if !hasAppJSON(appName) { + fmt.Print("{}") + return nil + } + + b, err := os.ReadFile(getProcessSpecificAppJSONPath(appName)) + if err != nil { + return fmt.Errorf("Cannot read app.json file: %v", err) + } + + content := strings.TrimSpace(string(b)) + if content == "" { + fmt.Print("{}") + return nil + } + + fmt.Print(content) + return nil +} + // TriggerCorePostDeploy sets a property to // allow the app to be restored on boot func TriggerCorePostDeploy(appName string) error { diff --git a/plugins/apps/functions.go b/plugins/apps/functions.go index 13cb9105e..60cfb6e4c 100644 --- a/plugins/apps/functions.go +++ b/plugins/apps/functions.go @@ -1,7 +1,6 @@ package apps import ( - "bytes" "errors" "fmt" "os" @@ -95,53 +94,6 @@ func destroyApp(appName string) error { return nil } -func listImagesByAppLabel(appName string) ([]string, error) { - command := []string{ - common.DockerBin(), - "image", - "list", - "--quiet", - "--filter", - fmt.Sprintf("label=com.dokku.app-name=%v", appName), - } - - var stderr bytes.Buffer - listCmd := common.NewShellCmd(strings.Join(command, " ")) - listCmd.ShowOutput = false - listCmd.Command.Stderr = &stderr - b, err := listCmd.Output() - - if err != nil { - return []string{}, errors.New(strings.TrimSpace(stderr.String())) - } - - output := strings.Split(strings.TrimSpace(string(b[:])), "\n") - return output, nil -} - -func listImagesByImageRepo(imageRepo string) ([]string, error) { - command := []string{ - common.DockerBin(), - "image", - "list", - "--quiet", - imageRepo, - } - - var stderr bytes.Buffer - listCmd := common.NewShellCmd(strings.Join(command, " ")) - listCmd.ShowOutput = false - listCmd.Command.Stderr = &stderr - b, err := listCmd.Output() - - if err != nil { - return []string{}, errors.New(strings.TrimSpace(stderr.String())) - } - - output := strings.Split(strings.TrimSpace(string(b[:])), "\n") - return output, nil -} - // creates an app if allowed func maybeCreateApp(appName string) error { if err := appExists(appName); err == nil { diff --git a/plugins/apps/src/commands/commands.go b/plugins/apps/src/commands/commands.go index 0e38ebbd9..b37257f76 100644 --- a/plugins/apps/src/commands/commands.go +++ b/plugins/apps/src/commands/commands.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" + "github.com/dokku/dokku/plugins/apps" "github.com/dokku/dokku/plugins/common" ) @@ -37,7 +38,15 @@ func main() { cmd := flag.Arg(0) switch cmd { - case "apps", "apps:help": + case "apps": + args := flag.NewFlagSet("apps", flag.ExitOnError) + args.Usage = usage + args.Parse(os.Args[2:]) + + if err := apps.CommandList(); err != nil { + common.LogFailWithError(err) + } + case "apps:help": usage() case "help": command := common.NewShellCmd(fmt.Sprintf("ps -o command= %d", os.Getppid())) diff --git a/plugins/apps/triggers.go b/plugins/apps/triggers.go index c980b3203..9a82d9363 100644 --- a/plugins/apps/triggers.go +++ b/plugins/apps/triggers.go @@ -101,19 +101,5 @@ func TriggerPostDelete(appName string) error { common.LogWarn(err.Error()) } - imagesByAppLabel, err := listImagesByAppLabel(appName) - if err != nil { - common.LogWarn(err.Error()) - } - - imageRepo := common.GetAppImageRepo(appName) - imagesByRepo, err := listImagesByImageRepo(imageRepo) - if err != nil { - common.LogWarn(err.Error()) - } - - images := append(imagesByAppLabel, imagesByRepo...) - common.RemoveImages(images) - return nil } diff --git a/plugins/builder-dockerfile/builder-build b/plugins/builder-dockerfile/builder-build index 4b57522ab..c5b17b9d9 100755 --- a/plugins/builder-dockerfile/builder-build +++ b/plugins/builder-dockerfile/builder-build @@ -23,10 +23,6 @@ trigger-builder-dockerfile-builder-build() { pushd "$SOURCECODE_WORK_DIR" &>/dev/null - # extract first port from Dockerfile - suppress_output dos2unix Dockerfile - local DOCKERFILE_PORTS=$(get_dockerfile_exposed_ports Dockerfile) - [[ -n "$DOCKERFILE_PORTS" ]] && config_set --no-restart "$APP" DOKKU_DOCKERFILE_PORTS="$DOCKERFILE_PORTS" plugn trigger pre-build-dockerfile "$APP" [[ "$DOKKU_DOCKERFILE_CACHE_BUILD" == "false" ]] && DOKKU_DOCKER_BUILD_OPTS="$DOKKU_DOCKER_BUILD_OPTS --no-cache" @@ -38,8 +34,10 @@ trigger-builder-dockerfile-builder-build() { declare -a ARG_ARRAY eval "ARG_ARRAY=($DOCKER_ARGS)" + eval "$(config_export app "$APP")" "$DOCKER_BIN" image build "${DOCKER_BUILD_LABEL_ARGS[@]}" $DOKKU_GLOBAL_BUILD_ARGS "${ARG_ARRAY[@]}" ${DOKKU_DOCKER_BUILD_OPTS} -t $IMAGE . + plugn trigger ports-set-detected "$APP" "$(fn-builder-dockerfile-get-detect-port-map "$APP" "$IMAGE" "$SOURCECODE_WORK_DIR/Dockerfile")" plugn trigger post-build-dockerfile "$APP" } diff --git a/plugins/builder-dockerfile/internal-functions b/plugins/builder-dockerfile/internal-functions index a230f67b3..21e36208b 100755 --- a/plugins/builder-dockerfile/internal-functions +++ b/plugins/builder-dockerfile/internal-functions @@ -88,3 +88,50 @@ fn-builder-dockerfile-dockerfile-path() { fn-plugin-property-get-default "builder-dockerfile" "$APP" "dockerfile-path" "" } + +fn-builder-dockerfile-get-ports-from-dockerfile() { + declare desc="return all exposed ports from passed file path" + declare DOCKERFILE_PATH="$1" + + suppress_output dos2unix "$DOCKERFILE_PATH" + local ports="$(grep -E "^EXPOSE " "$DOCKERFILE_PATH" | awk '{ print $2 }' | xargs)" || true + echo "$ports" +} + +fn-builder-dockerfile-get-ports-from-image() { + declare desc="return all exposed ports from passed image name" + declare IMAGE="$1" + + # shellcheck disable=SC2016 + local ports="$("$DOCKER_BIN" image inspect --format '{{range $key, $value := .Config.ExposedPorts}}{{$key}} {{end}}' "$IMAGE" | xargs)" || true + echo "$ports" +} + +fn-builder-dockerfile-get-detect-port-map() { + declare desc="extracts and echos a port mapping from the app" + declare APP="$1" IMAGE="$2" DOCKERFILE_PATH="$3" + + local detected_ports=$(fn-builder-dockerfile-get-ports-from-dockerfile "$DOCKERFILE_PATH") + + if [[ -z "$detected_ports" ]]; then + local detected_ports=$(fn-builder-dockerfile-get-ports-from-image "$IMAGE") + fi + + if [[ -n "$detected_ports" ]]; then + local port_map="" + for p in $detected_ports; do + if [[ "$p" =~ .*udp.* ]]; then + p=${p//\/udp/} + port_map+="udp:$p:$p " + else + p=${p//\/tcp/} + port_map+="http:$p:$p " + fi + done + echo "$port_map" | xargs + else + # ensure we have a port mapping + plugn trigger ports-configure "$APP" + echo "http:$(plugn trigger ports-get-property "$APP" proxy-port):5000" + fi +} diff --git a/plugins/builder-herokuish/builder-build b/plugins/builder-herokuish/builder-build index 401ab58d6..a275bc0d1 100755 --- a/plugins/builder-herokuish/builder-build +++ b/plugins/builder-herokuish/builder-build @@ -3,6 +3,16 @@ set -eo pipefail [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_AVAILABLE_PATH/config/functions" +fn-builder-herokuish-ensure-cache() { + declare APP="$1" + + existing_cache="$("$DOCKER_BIN" volume ls --quiet --filter label=com.dokku.app-name=test --filter label=com.dokku.builder-type=herokuish)" + if [[ "$existing_cache" != "cache-$APP" ]]; then + "$DOCKER_BIN" volume rm "cache-$APP" &>/dev/null || true + "$DOCKER_BIN" volume create "--label=org.label-schema.schema-version=1.0" "--label=org.label-schema.vendor=dokku" "--label=com.dokku.app-name=$APP" "--label=com.dokku.builder-type=herokuish" "cache-$APP" >/dev/null + fi +} + trigger-builder-herokuish-builder-build() { declare desc="builder-herokuish builder-build plugin trigger" declare trigger="builder-build" @@ -21,8 +31,6 @@ trigger-builder-herokuish-builder-build() { fi local IMAGE=$(get_app_image_name "$APP") - local DOKKU_APP_CACHE_DIR="$DOKKU_ROOT/$APP/cache" - local DOKKU_APP_HOST_CACHE_DIR="$DOKKU_HOST_ROOT/$APP/cache" local DOCKER_COMMIT_LABEL_ARGS=("--change" "LABEL dokku=" "--change" "LABEL org.label-schema.schema-version=1.0" "--change" "LABEL org.label-schema.vendor=dokku" "--change" "LABEL com.dokku.image-stage=build" "--change" "LABEL com.dokku.builder-type=herokuish" "--change" "LABEL com.dokku.app-name=$APP") local DOCKER_RUN_LABEL_ARGS=("--label=dokku" "--label=org.label-schema.schema-version=1.0" "--label=org.label-schema.vendor=dokku" "--label=com.dokku.image-stage=build" "--label=com.dokku.builder-type=herokuish" "--label=com.dokku.app-name=$APP") local CID TAR_CID @@ -49,7 +57,6 @@ trigger-builder-herokuish-builder-build() { "$DOCKER_BIN" container commit "${DOCKER_COMMIT_LABEL_ARGS[@]}" "$TAR_CID" "$IMAGE" >/dev/null DOKKU_SKIP_IMAGE_CLEANUP_REGISTRATION=1 plugn trigger scheduler-register-retired "$APP" "$TAR_CID" - [[ -d $DOKKU_APP_CACHE_DIR ]] || mkdir -p "$DOKKU_APP_CACHE_DIR" plugn trigger pre-build-buildpack "$APP" "$SOURCECODE_WORK_DIR" local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$BUILDER_TYPE") @@ -60,8 +67,8 @@ trigger-builder-herokuish-builder-build() { eval "ARG_ARRAY=($DOCKER_ARGS)" local DOKKU_CONTAINER_EXIT_CODE=0 - if ! CID=$("$DOCKER_BIN" container create "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS -v "$DOKKU_APP_HOST_CACHE_DIR:/cache" --env=CACHE_PATH=/cache "${ARG_ARRAY[@]}" "$IMAGE" /build); then - DOKKU_SKIP_IMAGE_CLEANUP_REGISTRATION=1 plugn trigger scheduler-register-retired "$APP" "$TAR_CID" + fn-builder-herokuish-ensure-cache "$APP" + if ! CID=$("$DOCKER_BIN" container create "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS -v "cache-$APP:/cache" --env=CACHE_PATH=/cache "${ARG_ARRAY[@]}" "$IMAGE" /build); then plugn trigger scheduler-register-retired "$APP" "$CID" dokku_log_warn "Failure during app build" return 1 @@ -70,7 +77,6 @@ trigger-builder-herokuish-builder-build() { plugn trigger post-container-create "app" "$CID" "$APP" "build" "$DOCKER_BIN" container start "$CID" >/dev/null || DOKKU_CONTAINER_EXIT_CODE=$? if ! "$DOCKER_BIN" container attach "$CID"; then - DOKKU_SKIP_IMAGE_CLEANUP_REGISTRATION=1 plugn trigger scheduler-register-retired "$APP" "$TAR_CID" plugn trigger scheduler-register-retired "$APP" "$CID" dokku_log_warn "Failure during app build" return 1 @@ -78,15 +84,17 @@ trigger-builder-herokuish-builder-build() { DOKKU_CONTAINER_EXIT_CODE="$("$DOCKER_BIN" container wait "$CID" 2>/dev/null || echo "$DOKKU_CONTAINER_EXIT_CODE")" if [[ "$DOKKU_CONTAINER_EXIT_CODE" -ne 0 ]]; then - DOKKU_SKIP_IMAGE_CLEANUP_REGISTRATION=1 plugn trigger scheduler-register-retired "$APP" "$TAR_CID" plugn trigger scheduler-register-retired "$APP" "$CID" dokku_log_warn "Failure during app build" return 1 fi "$DOCKER_BIN" container commit "${DOCKER_COMMIT_LABEL_ARGS[@]}" "$CID" "$IMAGE" >/dev/null - DOKKU_SKIP_IMAGE_CLEANUP_REGISTRATION=1 plugn trigger scheduler-register-retired "$APP" "$TAR_CID" plugn trigger scheduler-register-retired "$APP" "$CID" + + # ensure we have a port mapping + plugn trigger ports-configure "$APP" + plugn trigger ports-set-detected "$APP" "http:$(plugn trigger ports-get-property "$APP" proxy-port):5000" plugn trigger post-build-buildpack "$APP" "$SOURCECODE_WORK_DIR" } diff --git a/plugins/builder-herokuish/install b/plugins/builder-herokuish/install index 00efe10cf..357b692f7 100755 --- a/plugins/builder-herokuish/install +++ b/plugins/builder-herokuish/install @@ -1,13 +1,28 @@ #!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" set -eo pipefail [[ $DOKKU_TRACE ]] && set -x +fn-builder-herokuish-remove-old-cache() { + for app in $(dokku_apps "false" 2>/dev/null); do + local DOKKU_APP_CACHE_DIR="$DOKKU_ROOT/$APP/cache" + local DOKKU_APP_HOST_CACHE_DIR="$DOKKU_HOST_ROOT/$APP/cache" + if [[ ! -d "$DOKKU_APP_CACHE_DIR" ]]; then + continue + fi + + "$DOCKER_BIN" container run --rm --label=dokku --label=org.label-schema.schema-version=1.0 --label=org.label-schema.vendor=dokku "--label=com.dokku.app-name=$APP" -v "$DOKKU_APP_HOST_CACHE_DIR:/cache" "$DOKKU_IMAGE" "find /cache -depth -mindepth 1 -maxdepth 1 -exec rm -Rf {} ;" + rm -rf "$DOKKU_APP_CACHE_DIR" + done +} + trigger-builder-herokuish-install() { declare desc="installs the builder-herokuish plugin" declare trigger="install" fn-plugin-property-setup "builder-herokuish" + fn-builder-herokuish-remove-old-cache } trigger-builder-herokuish-install "$@" diff --git a/plugins/builder-herokuish/post-app-rename-setup b/plugins/builder-herokuish/post-app-rename-setup index 2c96153a8..26c5a6dbb 100755 --- a/plugins/builder-herokuish/post-app-rename-setup +++ b/plugins/builder-herokuish/post-app-rename-setup @@ -12,7 +12,7 @@ trigger-builder-herokuish-post-app-clone-setup() { local OLD_CACHE_DIR="$DOKKU_ROOT/$OLD_APP/cache" local OLD_CACHE_HOST_DIR="$DOKKU_HOST_ROOT/$OLD_APP/cache" - if [[ -d "$OLD_CACHE_DIR" ]] && ! rmdir "$OLD_CACHE_DIR" >/dev/null 2>&1; then + if [[ -d "$OLD_CACHE_DIR" ]] && ! rmdir "$OLD_CACHE_DIR" &>/dev/null; then local DOCKER_RUN_LABEL_ARGS="--label=com.dokku.app-name=$NEW_APP" "$DOCKER_BIN" container run "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS --rm --volume "$OLD_CACHE_HOST_DIR:/cache" "dokku/$OLD_APP" chmod 777 -R /cache fi diff --git a/plugins/builder-lambda/builder-build b/plugins/builder-lambda/builder-build index eb13cd21b..0c6d708d8 100755 --- a/plugins/builder-lambda/builder-build +++ b/plugins/builder-lambda/builder-build @@ -20,7 +20,6 @@ trigger-builder-lambda-builder-build() { plugn trigger pre-build-lambda "$APP" - # shellcheck disable=SC2086 lambda-builder build --generate-image --write-procfile --image-env=DOCKER_LAMBDA_STAY_OPEN=1 --label=org.label-schema.schema-version=1.0 --label=org.label-schema.vendor=dokku --label=com.dokku.image-stage=build --label=com.dokku.builder-type=lambda "--label=com.dokku.app-name=$APP" $DOKKU_GLOBAL_BUILD_ARGS --port 5000 --tag "$IMAGE" --working-directory "$SOURCECODE_WORK_DIR" if [[ ! -f "$SOURCECODE_WORK_DIR/lambda.zip" ]]; then dokku_log_warn "Compressed lambda.zip not detected, failed to build lambda function" @@ -37,6 +36,9 @@ trigger-builder-lambda-builder-build() { cp "$SOURCECODE_WORK_DIR/Procfile" "${DOKKU_LIB_ROOT}/data/builder-lambda/$APP/$GIT_REV.Procfile" fi + # ensure we have a port mapping + plugn trigger ports-configure "$APP" + plugn trigger ports-set-detected "$APP" "http:$(plugn trigger ports-get-property "$APP" proxy-port):5000" plugn trigger post-build-lambda "$APP" } diff --git a/plugins/builder-pack/builder-build b/plugins/builder-pack/builder-build index 773ebfd80..c3b3f4b2f 100755 --- a/plugins/builder-pack/builder-build +++ b/plugins/builder-pack/builder-build @@ -35,6 +35,9 @@ trigger-builder-pack-builder-build() { pack build "$IMAGE" --builder "$DOKKU_CNB_BUILDER" --path "$SOURCECODE_WORK_DIR" --default-process web "${ENV_ARGS[@]}" docker-image-labeler --label=dokku --label=org.label-schema.schema-version=1.0 --label=org.label-schema.vendor=dokku --label=com.dokku.image-stage=build --label=com.dokku.builder-type=pack --label=com.dokku.app-name=$APP "$IMAGE" + # ensure we have a port mapping + plugn trigger ports-configure "$APP" + plugn trigger ports-set-detected "$APP" "http:$(plugn trigger ports-get-property "$APP" proxy-port):5000" plugn trigger post-build-pack "$APP" "$SOURCECODE_WORK_DIR" } diff --git a/plugins/builder/functions.go b/plugins/builder/functions.go index eb90e10ab..df7d2f863 100644 --- a/plugins/builder/functions.go +++ b/plugins/builder/functions.go @@ -1,11 +1,64 @@ package builder import ( + "bytes" + "errors" + "fmt" "io/ioutil" "os" "path" + "strings" + + "github.com/dokku/dokku/plugins/common" ) +func listImagesByAppLabel(appName string) ([]string, error) { + command := []string{ + common.DockerBin(), + "image", + "ls", + "--quiet", + "--filter", + fmt.Sprintf("label=com.dokku.app-name=%v", appName), + } + + var stderr bytes.Buffer + listCmd := common.NewShellCmd(strings.Join(command, " ")) + listCmd.ShowOutput = false + listCmd.Command.Stderr = &stderr + b, err := listCmd.Output() + + if err != nil { + return []string{}, errors.New(strings.TrimSpace(stderr.String())) + } + + output := strings.Split(strings.TrimSpace(string(b[:])), "\n") + return output, nil +} + +func listImagesByImageRepo(imageRepo string) ([]string, error) { + command := []string{ + common.DockerBin(), + "image", + "ls", + "--quiet", + imageRepo, + } + + var stderr bytes.Buffer + listCmd := common.NewShellCmd(strings.Join(command, " ")) + listCmd.ShowOutput = false + listCmd.Command.Stderr = &stderr + b, err := listCmd.Output() + + if err != nil { + return []string{}, errors.New(strings.TrimSpace(stderr.String())) + } + + output := strings.Split(strings.TrimSpace(string(b[:])), "\n") + return output, nil +} + func removeAllContents(basePath string) error { dir, err := ioutil.ReadDir(basePath) if err != nil { diff --git a/plugins/builder/triggers.go b/plugins/builder/triggers.go index be1bfe560..2b4cc240b 100644 --- a/plugins/builder/triggers.go +++ b/plugins/builder/triggers.go @@ -129,5 +129,23 @@ func TriggerPostAppRenameSetup(oldAppName string, newAppName string) error { // TriggerPostDelete destroys the builder property for a given app container func TriggerPostDelete(appName string) error { - return common.PropertyDestroy("builder", appName) + if err := common.PropertyDestroy("builder", appName); err != nil { + return err + } + + imagesByAppLabel, err := listImagesByAppLabel(appName) + if err != nil { + common.LogWarn(err.Error()) + } + + imageRepo := common.GetAppImageRepo(appName) + imagesByRepo, err := listImagesByImageRepo(imageRepo) + if err != nil { + common.LogWarn(err.Error()) + } + + images := append(imagesByAppLabel, imagesByRepo...) + common.RemoveImages(images) + + return nil } diff --git a/plugins/caddy-vhosts/certs-force b/plugins/caddy-vhosts/certs-force new file mode 100755 index 000000000..a01fd5d5a --- /dev/null +++ b/plugins/caddy-vhosts/certs-force @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/caddy-vhosts/internal-functions" + +trigger-caddy-vhosts-certs-force() { + declare desc="caddy-vhosts certs-force plugin trigger" + declare trigger="certs-force" + declare APP="$1" + + if [[ "$(plugn trigger proxy-type "$APP")" != "caddy" ]]; then + return + fi + + if [[ -n "$(fn-caddy-letsencrypt-email)" ]]; then + echo true + fi +} + +trigger-caddy-vhosts-certs-force "$@" diff --git a/plugins/caddy-vhosts/command-functions b/plugins/caddy-vhosts/command-functions index 9636ef451..226ffd300 100755 --- a/plugins/caddy-vhosts/command-functions +++ b/plugins/caddy-vhosts/command-functions @@ -137,8 +137,8 @@ cmd-caddy-start() { } cmd-caddy-stop() { - declare desc="Starts the caddy server" - declare cmd="caddy:start" + declare desc="Stops the caddy server" + declare cmd="caddy:stop" [[ "$1" == "$cmd" ]] && shift 1 if ! fn-is-compose-installed; then diff --git a/plugins/caddy-vhosts/docker-args-process-deploy b/plugins/caddy-vhosts/docker-args-process-deploy index 4fe047894..65a9c9796 100755 --- a/plugins/caddy-vhosts/docker-args-process-deploy +++ b/plugins/caddy-vhosts/docker-args-process-deploy @@ -8,7 +8,7 @@ trigger-caddy-vhosts-docker-args-process-deploy() { declare desc="nginx-vhosts core-post-deploy plugin trigger" declare trigger="docker-args-process-deploy" declare APP="$1" IMAGE_SOURCE_TYPE="$2" IMAGE_TAG="$3" PROC_TYPE="$4" CONTAINER_INDEX="$5" - local app_domains caddy_domains is_app_listening letsencrypt_email output proxy_container_port proxy_host_port port_map proxy_port_map proxy_scheme proxy_schemes scheme tls_internal + local app_domains caddy_domains is_app_listening letsencrypt_email output proxy_container_port proxy_host_port port_map proxy_scheme proxy_schemes scheme tls_internal local proxy_container_http_port proxy_container_http_port_candidate proxy_host_http_port_candidate local proxy_container_https_port proxy_container_https_port_candidate proxy_host_https_port_candidate local STDIN=$(cat) @@ -30,15 +30,14 @@ trigger-caddy-vhosts-docker-args-process-deploy() { fi # ensure we have a port mapping - plugn trigger proxy-configure-ports "$APP" + plugn trigger ports-configure "$APP" # gather port mapping information # we only support proxying a single port for http and https listeners # so this block parses the port mappings and tries to find the correct # mapping to expose is_app_listening="false" - proxy_port_map="$(plugn trigger config-get "$APP" DOKKU_PROXY_PORT_MAP)" - for port_map in $proxy_port_map; do + while read -r port_map; do proxy_scheme="$(awk -F ':' '{ print $1 }' <<<"$port_map")" proxy_host_port="$(awk -F ':' '{ print $2 }' <<<"$port_map")" proxy_container_port="$(awk -F ':' '{ print $3 }' <<<"$port_map")" @@ -66,7 +65,7 @@ trigger-caddy-vhosts-docker-args-process-deploy() { proxy_container_https_port="$proxy_container_port" fi fi - done + done < <(plugn trigger ports-get "$APP") letsencrypt_email="$(fn-caddy-letsencrypt-email)" if [[ -n "$letsencrypt_email" ]] && [[ -z "$proxy_container_https_port" ]]; then diff --git a/plugins/caddy-vhosts/internal-functions b/plugins/caddy-vhosts/internal-functions index 1df11b0a8..b03a309d7 100755 --- a/plugins/caddy-vhosts/internal-functions +++ b/plugins/caddy-vhosts/internal-functions @@ -46,7 +46,7 @@ fn-caddy-template-compose-file() { } fn-caddy-image() { - fn-plugin-property-get-default "caddy" "--global" "image" "lucaslorentz/caddy-docker-proxy:2.7" + fn-plugin-property-get-default "caddy" "--global" "image" "lucaslorentz/caddy-docker-proxy:2.8" } fn-caddy-letsencrypt-email() { diff --git a/plugins/caddy-vhosts/pre-restore b/plugins/caddy-vhosts/pre-restore index 78f5cae59..4990a7d8e 100755 --- a/plugins/caddy-vhosts/pre-restore +++ b/plugins/caddy-vhosts/pre-restore @@ -8,7 +8,7 @@ trigger-caddy-pre-restore() { declare desc="pre-restore the caddy proxy" declare trigger="install" - if [[ "$(fn-plugin-property-get "caddy" "--global" "proxy-status")" == "stopped" ]]; then + if [[ "$(fn-plugin-property-get "caddy" "--global" "proxy-status")" != "started" ]]; then return fi diff --git a/plugins/checks/subcommands/run b/plugins/checks/subcommands/run index 8f1466957..f753778cd 100755 --- a/plugins/checks/subcommands/run +++ b/plugins/checks/subcommands/run @@ -60,7 +60,17 @@ check_process() { dokku_log_info1 "Running checks for app ($APP.$PROC_TYPE.$CONTAINER_INDEX)" CONTAINER_ID=$(<"$DOKKU_CONTAINER_ID_FILE") IP="$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$CONTAINER_ID")" - PORT="$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$CONTAINER_ID" "$IS_HEROKUISH_CONTAINER")" + PORT=5000 + + while read -r port_map; do + proxy_scheme="$(awk -F ':' '{ print $1 }' <<<"$port_map")" + proxy_host_port="$(awk -F ':' '{ print $2 }' <<<"$port_map")" + proxy_container_port="$(awk -F ':' '{ print $3 }' <<<"$port_map")" + if [[ "$proxy_scheme" != "udp" ]]; then + PORT="${proxy_container_port:="$proxy_container_port"}" + fi + done < <(plugn trigger ports-get "$APP") + plugn trigger check-deploy "$APP" "$CONTAINER_ID" "$PROC_TYPE" "$PORT" "$IP" } diff --git a/plugins/common/common.go b/plugins/common/common.go index 53a7e27fd..99b8f4777 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -596,6 +596,15 @@ func (err *AppDoesNotExist) Error() string { return fmt.Sprintf("App %s does not exist", err.appName) } +// VarArgs skips a number of incoming arguments, returning what is left over +func VarArgs(arguments []string, skip int) []string { + if len(arguments) <= skip { + return []string{} + } + + return arguments[skip:] +} + // VerifyAppName checks if an app conforming to either the old or new // naming conventions exists func VerifyAppName(appName string) error { diff --git a/plugins/common/docker.go b/plugins/common/docker.go index 95d1b2f0d..9393a4a6c 100644 --- a/plugins/common/docker.go +++ b/plugins/common/docker.go @@ -203,7 +203,7 @@ func DockerCleanup(appName string, forceCleanup bool) error { containerIDs := append(exitedContainerIDs, deadContainerIDs...) if len(containerIDs) > 0 { - removeContainers(containerIDs) + DockerRemoveContainers(containerIDs) } // delete dangling images @@ -303,7 +303,7 @@ func ListDanglingImages(appName string) ([]string, error) { command := []string{ DockerBin(), "image", - "list", + "ls", "--quiet", "--filter", "dangling=true", @@ -329,6 +329,10 @@ func ListDanglingImages(appName string) ([]string, error) { // RemoveImages removes images by ID func RemoveImages(imageIDs []string) { + if len(imageIDs) == 0 { + return + } + command := []string{ DockerBin(), "image", @@ -351,21 +355,18 @@ func VerifyImage(image string) bool { return imageCmd.Execute() } -func listContainers(status string, appName string) ([]string, error) { +// DockerFilterContainers returns a slice of container IDs based on the passed in filters +func DockerFilterContainers(filters []string) ([]string, error) { command := []string{ DockerBin(), "container", - "list", + "ls", "--quiet", "--all", - "--filter", - fmt.Sprintf("status=%v", status), - "--filter", - fmt.Sprintf("label=%v", os.Getenv("DOKKU_CONTAINER_LABEL")), } - if appName != "" { - command = append(command, []string{"--filter", fmt.Sprintf("label=com.dokku.app-name=%v", appName)}...) + for _, filter := range filters { + command = append(command, "--filter", filter) } var stderr bytes.Buffer @@ -382,6 +383,18 @@ func listContainers(status string, appName string) ([]string, error) { return output, nil } +func listContainers(status string, appName string) ([]string, error) { + filters := []string{ + fmt.Sprintf("status=%v", status), + fmt.Sprintf("label=%v", os.Getenv("DOKKU_CONTAINER_LABEL")), + } + + if appName != "" { + filters = append(filters, fmt.Sprintf("label=com.dokku.app-name=%v", appName)) + } + return DockerFilterContainers(filters) +} + func pruneUnusedImages(appName string) { command := []string{ DockerBin(), @@ -400,7 +413,8 @@ func pruneUnusedImages(appName string) { pruneCmd.Execute() } -func removeContainers(containerIDs []string) { +// DockerRemoveContainers will call `docker container rm` on the specified containers +func DockerRemoveContainers(containerIDs []string) { command := []string{ DockerBin(), "container", diff --git a/plugins/common/functions b/plugins/common/functions index e3887fc48..f9a07f502 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -480,7 +480,7 @@ copy_from_image() { 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 + trap "rm -rf '$TMP_FILE_COMMAND_OUTPUT' &>/dev/null || 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 @@ -506,6 +506,63 @@ copy_from_image() { fi } +copy_dir_from_image() { + declare desc="copy a directory from named image to destination" + declare IMAGE="$1" SRC_DIR="$2" DST_DIR="$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_DIR"; 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_DIR="${WORKDIR}/${SRC_DIR}" + fi + fi + + TMP_DIR_COMMAND_OUTPUT=$(mktemp -d "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX") + trap "rm -rf '$TMP_DIR_COMMAND_OUTPUT' &>/dev/null || true" RETURN + + local CID=$("$DOCKER_BIN" container create "${DOCKER_CREATE_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS "$IMAGE") + "$DOCKER_BIN" container cp "$CID:$SRC_DIR" "$TMP_DIR_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 [[ ! -d "$TMP_DIR_COMMAND_OUTPUT" ]]; then + return 1 + fi + + pushd "$TMP_DIR_COMMAND_OUTPUT" >/dev/null + for filename in *; do + if [[ ! -f "$TMP_DIR_COMMAND_OUTPUT/$filename" ]]; then + continue + fi + + # workaround for CHECKS file when owner is root. seems to only happen when running inside docker + dos2unix -l <"$TMP_DIR_COMMAND_OUTPUT/$filename" >"$DST_DIR/$filename" + + # add trailing newline for certain places where file parsing depends on it + if [[ "$(tail -c1 "$DST_DIR/$filename")" != "" ]]; then + echo "" >>"$DST_DIR/$filename" + fi + done + popd &>/dev/null || pushd "/tmp" >/dev/null + 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" @@ -632,18 +689,9 @@ release_and_deploy() { 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 IMAGE_SOURCE_TYPE="$(get_image_builder_type "$IMAGE" "$APP")" - if [[ "$IMAGE_SOURCE_TYPE" == "dockerfile" ]]; then - 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)" @@ -699,15 +747,8 @@ docker_cleanup() { 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_log_warn "Deprecated: please use the 'ports-get-available' plugin trigger instead" + plugn trigger ports-get-available } dokku_auth() { @@ -775,12 +816,16 @@ get_ipv6_regex() { get_dockerfile_exposed_ports() { declare desc="return all exposed ports from passed file path" + dokku_log_warn "Deprecated: this function should not be used in app code" + 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" + dokku_log_warn "Deprecated: this function should not be used in app code" + local IMAGE="$1" verify_image "$IMAGE" # shellcheck disable=SC2016 @@ -813,22 +858,6 @@ extract_directive_from_dockerfile() { 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" diff --git a/plugins/common/subprocess.go b/plugins/common/subprocess.go index 642b06c80..b428b9da8 100644 --- a/plugins/common/subprocess.go +++ b/plugins/common/subprocess.go @@ -18,6 +18,7 @@ type ShellCmd struct { CommandString string Args []string ShowOutput bool + ExitError *exec.ExitError } // NewShellCmd returns a new ShellCmd struct @@ -57,6 +58,10 @@ func (sc *ShellCmd) Execute() bool { sc.setup() if err := sc.Command.Run(); err != nil { + exitError, ok := err.(*exec.ExitError) + if ok { + sc.ExitError = exitError + } return false } return true @@ -119,6 +124,12 @@ func PlugnTriggerOutput(triggerName string, args ...string) ([]byte, error) { return readStdout, err } +// PlugnTriggerOutputAsString fires the given plugn trigger with the given args and returns the string contents instead of bytes +func PlugnTriggerOutputAsString(triggerName string, args ...string) (string, error) { + b, err := PlugnTriggerOutput(triggerName, args...) + return strings.TrimSpace(string(b[:])), err +} + // PlugnTriggerSetup sets up a plugn trigger call func PlugnTriggerSetup(triggerName string, args ...string) *sh.Session { shellArgs := make([]interface{}, len(args)+2) diff --git a/plugins/config/src/commands/commands.go b/plugins/config/src/commands/commands.go index df63fabaf..5c9d63c76 100644 --- a/plugins/config/src/commands/commands.go +++ b/plugins/config/src/commands/commands.go @@ -38,16 +38,16 @@ func main() { cmd := flag.Arg(0) switch cmd { case "config": - common.LogWarn("Deprecated: Use the 'config:show' command instead") - args := flag.NewFlagSet("config:show", flag.ExitOnError) + args := flag.NewFlagSet("config", flag.ExitOnError) + args.Usage = usage global := args.Bool("global", false, "--global: use the global environment") shell := args.Bool("shell", false, "--shell: in a single-line for usage in command-line utilities [deprecated]") export := args.Bool("export", false, "--export: print the env as eval-compatible exports [deprecated]") merged := args.Bool("merged", false, "--merged: display the app's environment merged with the global environment") args.Parse(os.Args[2:]) appName := args.Arg(0) - err := config.CommandShow(appName, *global, *merged, *shell, *export) - if err != nil { + + if err := config.CommandShow(appName, *global, *merged, *shell, *export); err != nil { common.LogFailWithError(err) } case "config:help": diff --git a/plugins/cron/Makefile b/plugins/cron/Makefile index e0d6a5320..85c72cfea 100644 --- a/plugins/cron/Makefile +++ b/plugins/cron/Makefile @@ -1,6 +1,5 @@ -SUBCOMMANDS = subcommands/list subcommands/report -TRIGGERS = triggers/cron-write triggers/install triggers/post-delete triggers/post-deploy triggers/report -BUILD = commands subcommands triggers +SUBCOMMANDS = subcommands/list subcommands/report subcommands/run +BUILD = commands subcommands PLUGIN_NAME = cron include ../../common.mk diff --git a/plugins/cron/cron.go b/plugins/cron/cron.go new file mode 100644 index 000000000..7c71f0735 --- /dev/null +++ b/plugins/cron/cron.go @@ -0,0 +1,78 @@ +package cron + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "strings" + + appjson "github.com/dokku/dokku/plugins/app-json" + "github.com/dokku/dokku/plugins/common" + + "github.com/multiformats/go-base36" + cronparser "github.com/robfig/cron/v3" +) + +type TemplateCommand struct { + ID string `json:"id"` + App string `json:"app"` + Command string `json:"command"` + Schedule string `json:"schedule"` + AltCommand string `json:"-"` + LogFile string `json:"-"` +} + +func (t TemplateCommand) CronCommand() string { + if t.AltCommand != "" { + if t.LogFile != "" { + return fmt.Sprintf("%s &>> %s", t.AltCommand, t.LogFile) + } + return t.AltCommand + } + + return fmt.Sprintf("dokku run --cron-id %s %s %s", t.ID, t.App, t.Command) +} + +func FetchCronEntries(appName string) ([]TemplateCommand, error) { + commands := []TemplateCommand{} + appjsonPath := appjson.GetAppjsonPath(appName) + if !common.FileExists(appjsonPath) { + return commands, nil + } + + b, err := ioutil.ReadFile(appjsonPath) + if err != nil { + return commands, fmt.Errorf("Cannot read app.json file for %s: %v", appName, err) + } + + if strings.TrimSpace(string(b)) == "" { + return commands, nil + } + + var appJSON appjson.AppJSON + if err = json.Unmarshal(b, &appJSON); err != nil { + return commands, fmt.Errorf("Cannot parse app.json for %s: %v", appName, err) + } + + for _, c := range appJSON.Cron { + parser := cronparser.NewParser(cronparser.Minute | cronparser.Hour | cronparser.Dom | cronparser.Month | cronparser.Dow | cronparser.Descriptor) + _, err := parser.Parse(c.Schedule) + if err != nil { + return commands, err + } + + commands = append(commands, TemplateCommand{ + App: appName, + Command: c.Command, + Schedule: c.Schedule, + ID: GenerateCommandID(appName, c), + }) + } + + return commands, nil +} + +// GenerateCommandID creates a unique ID for a given app/command/schedule combination +func GenerateCommandID(appName string, c appjson.CronCommand) string { + return base36.EncodeToStringLc([]byte(appName + "===" + c.Command + "===" + c.Schedule)) +} diff --git a/plugins/cron/functions.go b/plugins/cron/functions.go deleted file mode 100644 index 1ce09d794..000000000 --- a/plugins/cron/functions.go +++ /dev/null @@ -1,193 +0,0 @@ -package cron - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - "text/template" - - appjson "github.com/dokku/dokku/plugins/app-json" - "github.com/dokku/dokku/plugins/common" - - base36 "github.com/multiformats/go-base36" - cronparser "github.com/robfig/cron/v3" -) - -type templateCommand struct { - ID string - App string - Command string - Schedule string - AltCommand string - LogFile string -} - -func (t templateCommand) CronCommand() string { - if t.AltCommand != "" { - if t.LogFile != "" { - return fmt.Sprintf("%s &>> %s", t.AltCommand, t.LogFile) - } - return t.AltCommand - } - - return fmt.Sprintf("dokku run --cron-id %s %s %s", t.ID, t.App, t.Command) -} - -func fetchCronEntries(appName string) ([]templateCommand, error) { - commands := []templateCommand{} - scheduler := common.GetAppScheduler(appName) - if scheduler != "docker-local" { - return commands, nil - } - - appjsonPath := appjson.GetAppjsonPath(appName) - if !common.FileExists(appjsonPath) { - return commands, nil - } - - b, err := ioutil.ReadFile(appjsonPath) - if err != nil { - return commands, fmt.Errorf("Cannot read app.json file for %s: %v", appName, err) - } - - if strings.TrimSpace(string(b)) == "" { - return commands, nil - } - - var appJSON appjson.AppJSON - if err = json.Unmarshal(b, &appJSON); err != nil { - return commands, fmt.Errorf("Cannot parse app.json for %s: %v", appName, err) - } - - for _, c := range appJSON.Cron { - parser := cronparser.NewParser(cronparser.Minute | cronparser.Hour | cronparser.Dom | cronparser.Month | cronparser.Dow | cronparser.Descriptor) - _, err := parser.Parse(c.Schedule) - if err != nil { - return commands, err - } - - commands = append(commands, templateCommand{ - App: appName, - Command: c.Command, - Schedule: c.Schedule, - ID: generateCommandID(appName, c), - }) - } - - return commands, nil -} - -func deleteCrontab() error { - command := common.NewShellCmd("sudo /usr/bin/crontab -l -u dokku") - command.ShowOutput = false - if !command.Execute() { - return nil - } - - command = common.NewShellCmd("sudo /usr/bin/crontab -r -u dokku") - command.ShowOutput = false - out, err := command.CombinedOutput() - if err != nil { - return fmt.Errorf("Unable to remove schedule file: %v", string(out)) - } - - common.LogInfo1("Removed") - return nil -} - -func writeCronEntries() error { - apps, _ := common.UnfilteredDokkuApps() - commands := []templateCommand{} - for _, appName := range apps { - scheduler := common.GetAppScheduler(appName) - if scheduler != "docker-local" { - continue - } - - c, err := fetchCronEntries(appName) - if err != nil { - return err - } - - commands = append(commands, c...) - } - - b, _ := common.PlugnTriggerOutput("cron-entries", "docker-local") - for _, line := range strings.Split(strings.TrimSpace(string(b[:])), "\n") { - if strings.TrimSpace(line) == "" { - continue - } - - parts := strings.Split(line, ";") - if len(parts) != 2 && len(parts) != 3 { - return fmt.Errorf("Invalid injected cron task: %v", line) - } - - id := base36.EncodeToStringLc([]byte(strings.Join(parts, ";;;"))) - command := templateCommand{ - ID: id, - Schedule: parts[0], - AltCommand: parts[1], - } - if len(parts) == 3 { - command.LogFile = parts[2] - } - commands = append(commands, command) - } - - if len(commands) == 0 { - return deleteCrontab() - } - - data := map[string]interface{}{ - "Commands": commands, - } - - t, err := getCronTemplate() - if err != nil { - return err - } - - tmpFile, err := ioutil.TempFile(os.TempDir(), fmt.Sprintf("dokku-%s-%s", common.MustGetEnv("DOKKU_PID"), "WriteCronEntries")) - if err != nil { - return fmt.Errorf("Cannot create temporary schedule file: %v", err) - } - - defer tmpFile.Close() - defer os.Remove(tmpFile.Name()) - - if err := t.Execute(tmpFile, data); err != nil { - return fmt.Errorf("Unable to template out schedule file: %v", err) - } - - command := common.NewShellCmd(fmt.Sprintf("sudo /usr/bin/crontab -u dokku %s", tmpFile.Name())) - command.ShowOutput = false - out, err := command.CombinedOutput() - if err != nil { - return fmt.Errorf("Unable to update schedule file: %s", string(out)) - } - - common.LogInfo1("Updated schedule file") - - return nil -} - -func getCronTemplate() (*template.Template, error) { - t := template.New("cron") - - templatePath := filepath.Join(common.MustGetEnv("PLUGIN_ENABLED_PATH"), "cron", "templates", "cron.tmpl") - b, err := ioutil.ReadFile(templatePath) - if err != nil { - return t, fmt.Errorf("Cannot read template file: %v", err) - } - - s := strings.TrimSpace(string(b)) - return t.Parse(s) -} - -func generateCommandID(appName string, c appjson.CronCommand) string { - return base36.EncodeToStringLc([]byte(appName + "===" + c.Command + "===" + c.Schedule)) -} diff --git a/plugins/cron/go.mod b/plugins/cron/go.mod index a11bc619f..66dcf00ba 100644 --- a/plugins/cron/go.mod +++ b/plugins/cron/go.mod @@ -9,6 +9,7 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/ryanuber/columnize v2.1.2+incompatible github.com/spf13/pflag v1.0.5 + mvdan.cc/sh/v3 v3.7.0 ) require ( diff --git a/plugins/cron/go.sum b/plugins/cron/go.sum index f9e6e68bc..02dbf1968 100644 --- a/plugins/cron/go.sum +++ b/plugins/cron/go.sum @@ -2,9 +2,12 @@ github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+Bu github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 h1:HHUr4P/aKh4quafGxDT9LDasjGdlGkzLbfmmrlng3kA= github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27/go.mod h1:VQx0hjo2oUeQkQUET7wRwradO6f+fN5jzXgB/zROxxE= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= @@ -13,6 +16,7 @@ github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4 github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -24,3 +28,5 @@ golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= +mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= diff --git a/plugins/cron/report.go b/plugins/cron/report.go index 7e81b673f..da87de902 100644 --- a/plugins/cron/report.go +++ b/plugins/cron/report.go @@ -28,6 +28,6 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { } func reportTasks(appName string) string { - c, _ := fetchCronEntries(appName) + c, _ := FetchCronEntries(appName) return strconv.Itoa(len(c)) } diff --git a/plugins/cron/src/commands/commands.go b/plugins/cron/src/commands/commands.go index 402ac2799..466d428bb 100644 --- a/plugins/cron/src/commands/commands.go +++ b/plugins/cron/src/commands/commands.go @@ -18,8 +18,9 @@ Manage scheduled cron tasks Additional commands:` helpContent = ` - cron:list , List scheduled cron tasks for an app + cron:list [--format json|stdout], List scheduled cron tasks for an app cron:report [] [], Display report about an app + cron:run [--detach], Run a cron task on the fly ` ) diff --git a/plugins/cron/src/subcommands/subcommands.go b/plugins/cron/src/subcommands/subcommands.go index 12daa862f..94ff0c191 100644 --- a/plugins/cron/src/subcommands/subcommands.go +++ b/plugins/cron/src/subcommands/subcommands.go @@ -20,9 +20,10 @@ func main() { switch subcommand { case "list": args := flag.NewFlagSet("cron:list", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") args.Parse(os.Args[2:]) appName := args.Arg(0) - err = cron.CommandList(appName) + err = cron.CommandList(appName, *format) case "report": args := flag.NewFlagSet("cron:report", flag.ExitOnError) format := args.String("format", "stdout", "format: [ stdout | json ]") @@ -32,6 +33,13 @@ func main() { appName := args.Arg(0) err = cron.CommandReport(appName, *format, infoFlag) } + case "run": + args := flag.NewFlagSet("cron:run", flag.ExitOnError) + detached := args.Bool("detach", false, "--detach: run the container in a detached mode") + args.Parse(os.Args[2:]) + appName := args.Arg(0) + cronID := args.Arg(1) + err = cron.CommandRun(appName, cronID, *detached) default: err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand) } diff --git a/plugins/cron/subcommands.go b/plugins/cron/subcommands.go index 88bca6231..aa6af1c0e 100644 --- a/plugins/cron/subcommands.go +++ b/plugins/cron/subcommands.go @@ -1,29 +1,51 @@ package cron import ( + "encoding/json" "fmt" + "os" "github.com/dokku/dokku/plugins/common" + "github.com/ryanuber/columnize" + "mvdan.cc/sh/v3/shell" ) // CommandList lists all scheduled cron tasks for a given app -func CommandList(appName string) error { +func CommandList(appName string, format string) error { if err := common.VerifyAppName(appName); err != nil { return err } - entries, err := fetchCronEntries(appName) + if format == "" { + format = "stdout" + } + + if format != "stdout" && format != "json" { + return fmt.Errorf("Invalid format specified, supported formats: json, stdout") + } + + entries, err := FetchCronEntries(appName) if err != nil { return err } - output := []string{"ID | Schedule | Command"} - for _, entry := range entries { - output = append(output, fmt.Sprintf("%s | %s | %s", entry.ID, entry.Schedule, entry.Command)) + if format == "stdout" { + output := []string{"ID | Schedule | Command"} + for _, entry := range entries { + output = append(output, fmt.Sprintf("%s | %s | %s", entry.ID, entry.Schedule, entry.Command)) + } + + result := columnize.SimpleFormat(output) + fmt.Println(result) + return nil } - result := columnize.SimpleFormat(output) - fmt.Println(result) + + out, err := json.Marshal(entries) + if err != nil { + return err + } + common.Log(string(out)) return nil } @@ -45,3 +67,48 @@ func CommandReport(appName string, format string, infoFlag string) error { return ReportSingleApp(appName, format, infoFlag) } + +// CommandRun executes a cron command on the fly +func CommandRun(appName string, cronID string, detached bool) error { + if err := common.VerifyAppName(appName); err != nil { + return err + } + + entries, err := FetchCronEntries(appName) + if err != nil { + return err + } + + if cronID == "" { + return fmt.Errorf("Please specify a Cron ID from the output of 'dokku cron:list %s'", appName) + } + + command := "" + for _, entry := range entries { + if entry.ID == cronID { + command = entry.Command + } + } + + if command == "" { + return fmt.Errorf("No matching Cron ID found. Please specify a Cron ID from the output of 'dokku cron:list %s'", appName) + } + + fields, err := shell.Fields(command, func(name string) string { + return "" + }) + if err != nil { + return fmt.Errorf("Could not parse command: %s", err) + } + + if detached { + os.Setenv("DOKKU_DETACH_CONTAINER", "1") + os.Setenv("DOKKU_DISABLE_TTY", "true") + } + + os.Setenv("DOKKU_CRON_ID", cronID) + os.Setenv("DOKKU_RM_CONTAINER", "1") + scheduler := common.GetAppScheduler(appName) + args := append([]string{scheduler, appName, "0", ""}, fields...) + return common.PlugnTrigger("scheduler-run", args...) +} diff --git a/plugins/cron/triggers.go b/plugins/cron/triggers.go deleted file mode 100644 index 07dcbe84b..000000000 --- a/plugins/cron/triggers.go +++ /dev/null @@ -1,26 +0,0 @@ -package cron - -import ( - "github.com/dokku/dokku/plugins/common" -) - -// TriggerInstall installs a sudoers file so we can execute crontab via sudo -func TriggerInstall() error { - lines := []string{"%dokku ALL=(ALL) NOPASSWD:/usr/bin/crontab"} - return common.WriteSliceToFile("/etc/sudoers.d/dokku-cron", lines) -} - -// TriggerPostDelete updates the cron entries for all apps -func TriggerPostDelete() error { - return writeCronEntries() -} - -// TriggerPostDeploy updates the cron entries for all apps -func TriggerPostDeploy() error { - return writeCronEntries() -} - -// TriggerCronWrite force updates the cron file for all apps -func TriggerCronWrite() error { - return writeCronEntries() -} diff --git a/plugins/docker-options/docker-args-deploy b/plugins/docker-options/docker-args-deploy index 9bc2ec673..9c4d8c2ec 100755 --- a/plugins/docker-options/docker-args-deploy +++ b/plugins/docker-options/docker-args-deploy @@ -33,7 +33,6 @@ trigger-docker-options-docker-args() { [[ -z "$line" ]] && continue - # shellcheck disable=SC1001 case "$line" in \#*) continue diff --git a/plugins/docker-options/subcommands/remove b/plugins/docker-options/subcommands/remove index f258a0b34..bddbbcdbd 100755 --- a/plugins/docker-options/subcommands/remove +++ b/plugins/docker-options/subcommands/remove @@ -13,7 +13,6 @@ cmd-docker-options-remove() { verify_app_name "$APP" read -ra passed_phases <<<"$(get_phases "$2")" shift 2 # everything else passed is the docker option - # shellcheck disable=SC2154 [[ -z ${passed_docker_option="$@"} ]] && dokku_log_fail "Please specify docker options to remove from the phase" remove_passed_docker_option passed_phases[@] "${passed_docker_option[@]}" } diff --git a/plugins/domains/functions b/plugins/domains/functions index 17c59c7aa..9b6e481ee 100755 --- a/plugins/domains/functions +++ b/plugins/domains/functions @@ -16,7 +16,6 @@ disable_app_vhost() { fi [[ "$RESTART_APP" == "--no-restart" ]] && local CONFIG_SET_ARGS=$RESTART_APP - # shellcheck disable=SC2086 DOKKU_QUIET_OUTPUT=1 config_set $CONFIG_SET_ARGS $APP NO_VHOST=1 } @@ -196,7 +195,6 @@ enable_app_vhost() { plugn trigger pre-enable-vhost "$APP" [[ "$RESTART_APP" == "--no-restart" ]] && local CONFIG_SET_ARGS=$RESTART_APP - # shellcheck disable=SC2086 DOKKU_QUIET_OUTPUT=1 config_set $CONFIG_SET_ARGS "$APP" NO_VHOST=0 } diff --git a/plugins/domains/internal-functions b/plugins/domains/internal-functions index 921ac9885..c4a870c40 100755 --- a/plugins/domains/internal-functions +++ b/plugins/domains/internal-functions @@ -136,36 +136,23 @@ fn-domains-generate-urls() { fn-domains-generate-urls-from-config() { declare APP="$1" SCHEME="$2" VHOST="$3" DEFAULT_LISTEN_PORT="$4" - local DOKKU_PROXY_PORT_MAP=$(plugn trigger config-get "$APP" DOKKU_PROXY_PORT_MAP || true) - local RAW_TCP_PORTS="$(get_app_raw_tcp_ports "$APP")" + local APP_PORT_MAP="$(plugn trigger ports-get "$APP")" 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 listen_port in $APP_CONTAINER_PORTS; do - fn-domains-generate-url "$SCHEME" "$VHOST" "$listen_port" - 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 - listen_port="$(echo "$DOKKU_APP_WEB_LISTENER" | cut -d ':' -f2)" - fn-domains-generate-url "$SCHEME" "$VHOST" "$listen_port" - done - shopt -u nullglob - fi - elif [[ -n "$DOKKU_PROXY_PORT_MAP" ]]; then + 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 + listen_port="$(echo "$DOKKU_APP_WEB_LISTENER" | cut -d ':' -f2)" + fn-domains-generate-url "$SCHEME" "$VHOST" "$listen_port" + done + shopt -u nullglob + elif [[ -n "$APP_PORT_MAP" ]]; then local port_map - for port_map in $DOKKU_PROXY_PORT_MAP; do + while IFS= read -r port_map; do local scheme="$(awk -F ':' '{ print $1 }' <<<"$port_map")" local listen_port="$(awk -F ':' '{ print $2 }' <<<"$port_map")" fn-domains-generate-url "$SCHEME" "$VHOST" "$listen_port" - done - elif [[ -n "$RAW_TCP_PORTS" ]]; then - for listen_port in $RAW_TCP_PORTS; do - fn-domains-generate-url "$SCHEME" "$VHOST" "$listen_port" - done + done <<<"$APP_PORT_MAP" else fn-domains-generate-url "$SCHEME" "$VHOST" "$DEFAULT_LISTEN_PORT" fi diff --git a/plugins/git/deploy-source-set b/plugins/git/deploy-source-set new file mode 100755 index 000000000..2828f6e2f --- /dev/null +++ b/plugins/git/deploy-source-set @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +trigger-git-deploy-source-set() { + declare desc="clears the source-image if the source type is not docker" + declare trigger="deploy-source-set" + declare APP="$1" SOURCE_TYPE="$2" + + if [[ "$SOURCE_TYPE" == "docker-image" ]]; then + return + fi + + fn-plugin-property-write "git" "$APP" "source-image" +} + +trigger-git-deploy-source-set "$@" diff --git a/plugins/git/functions b/plugins/git/functions index 6773e320f..c19c8397e 100755 --- a/plugins/git/functions +++ b/plugins/git/functions @@ -20,7 +20,7 @@ git_build_app_repo() { chmod 755 "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" unset GIT_DIR GIT_QUARANTINE_PATH GIT_WORK_TREE - ! plugn trigger app-exists "$APP" >/dev/null 2>&1 && plugn trigger app-maybe-create "$APP" + ! plugn trigger app-exists "$APP" &>/dev/null && plugn trigger app-maybe-create "$APP" fn-git-setup-build-dir "$APP" "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" "$REV" pushd "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" >/dev/null @@ -79,7 +79,7 @@ cmd-git-hook() { plugn trigger app-exists "$APP" 2>/dev/null || is_valid_app_name "$APP" DOKKU_DEPLOY_BRANCH="$(fn-git-deploy-branch "$APP")" - if ! git check-ref-format --branch "$DOKKU_DEPLOY_BRANCH" >/dev/null 2>&1; then + if ! git check-ref-format --branch "$DOKKU_DEPLOY_BRANCH" &>/dev/null; then echo $'\e[1G\e[K'"-----> WARNING: Invalid branch name '$DOKKU_DEPLOY_BRANCH' specified via deploy-branch setting." echo $'\e[1G\e[K'"-----> For more details, please see the man page for 'git-check-ref-format.'" return @@ -165,7 +165,7 @@ cmd-git-upload-pack() { APP="$(echo "$APP" | perl -pe 's/(?/dev/null 2>&1 && plugn trigger app-maybe-create "$APP" + ! plugn trigger app-exists "$APP" &>/dev/null && plugn trigger app-maybe-create "$APP" plugn trigger git-pre-pull "$APP" cat | git-upload-pack "$DOKKU_ROOT/$APP" @@ -180,7 +180,7 @@ cmd-git-glob() { plugn trigger app-exists "$APP" 2>/dev/null || is_valid_app_name "$APP" if [[ $1 == "git-receive-pack" && ! -d "$APP_PATH/refs" ]]; then - ! plugn trigger app-exists "$APP" >/dev/null 2>&1 && plugn trigger app-maybe-create "$APP" + ! plugn trigger app-exists "$APP" &>/dev/null && plugn trigger app-maybe-create "$APP" fn-git-create-hook "$APP" fi diff --git a/plugins/git/git-from-directory b/plugins/git/git-from-directory index 9f94568c5..03e7058de 100755 --- a/plugins/git/git-from-directory +++ b/plugins/git/git-from-directory @@ -15,7 +15,6 @@ trigger-git-git-from-directory() { local REV local TMP_WORK_DIR=$(mktemp -d "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX") local TMP_WORK_DIR_2=$(mktemp -d "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX") - # shellcheck disable=SC2086 trap "rm -rf '$TMP_WORK_DIR' '$TMP_WORK_DIR_2' >/dev/null" RETURN INT TERM EXIT local has_code=true diff --git a/plugins/git/internal-functions b/plugins/git/internal-functions index 531eb66dd..b84d070a8 100755 --- a/plugins/git/internal-functions +++ b/plugins/git/internal-functions @@ -408,7 +408,7 @@ fn-git-clone() { fn-git-cmd "$APP_CLONE_ROOT" checkout -qq "$GIT_REF" else GIT_TERMINAL_PROMPT=0 suppress_output git clone -n --branch "$GIT_REF" "$GIT_REMOTE" "$APP_CLONE_ROOT" - if fn-git-cmd "$APP_CLONE_ROOT" show-ref --verify "refs/heads/$GIT_REF" >/dev/null 2>&1; then + if fn-git-cmd "$APP_CLONE_ROOT" show-ref --verify "refs/heads/$GIT_REF" &>/dev/null; then dokku_log_verbose "Detected branch, setting deploy-branch to $GIT_REF" fn-plugin-property-write "git" "$APP" "deploy-branch" "$GIT_REF" fi @@ -450,13 +450,13 @@ fn-git-fetch() { fi DOKKU_DEPLOY_BRANCH="$(fn-git-deploy-branch "$APP")" - if ! fn-git-cmd "$APP_ROOT" check-ref-format --branch "$DOKKU_DEPLOY_BRANCH" >/dev/null 2>&1; then + if ! fn-git-cmd "$APP_ROOT" check-ref-format --branch "$DOKKU_DEPLOY_BRANCH" &>/dev/null; then dokku_log_warn "Invalid branch name '$DOKKU_DEPLOY_BRANCH' specified via deploy-branch setting." dokku_log_warn "For more details, please see the man page for 'git-check-ref-format.'" return fi - fn-git-cmd "$APP_ROOT" remote rm remote >/dev/null 2>&1 || true + fn-git-cmd "$APP_ROOT" remote rm remote &>/dev/null || true fn-git-cmd "$APP_ROOT" remote add --mirror=fetch --no-tags remote "$GIT_REMOTE" if [[ -z "$GIT_REF" ]]; then GIT_TERMINAL_PROMPT=0 fn-git-cmd "$APP_ROOT" fetch --update-head-ok remote "$DOKKU_DEPLOY_BRANCH" diff --git a/plugins/haproxy-vhosts/certs-force b/plugins/haproxy-vhosts/certs-force new file mode 100755 index 000000000..69bab2c83 --- /dev/null +++ b/plugins/haproxy-vhosts/certs-force @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/haproxy-vhosts/internal-functions" + +trigger-haproxy-vhosts-certs-force() { + declare desc="haproxy-vhosts certs-force plugin trigger" + declare trigger="certs-force" + declare APP="$1" + + if [[ "$(plugn trigger proxy-type "$APP")" != "haproxy" ]]; then + return + fi + + if [[ -n "$(fn-haproxy-letsencrypt-email)" ]]; then + echo true + fi +} + +trigger-haproxy-vhosts-certs-force "$@" diff --git a/plugins/haproxy-vhosts/command-functions b/plugins/haproxy-vhosts/command-functions index 648165602..a27a31a13 100755 --- a/plugins/haproxy-vhosts/command-functions +++ b/plugins/haproxy-vhosts/command-functions @@ -129,14 +129,14 @@ cmd-haproxy-start() { local TMP_COMPOSE_FILE=$(mktemp "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX") trap "rm -rf '$TMP_COMPOSE_FILE' >/dev/null" RETURN INT TERM EXIT - fn-plugin-property-write "caddy" "--global" "proxy-status" "started" + fn-plugin-property-write "haproxy" "--global" "proxy-status" "started" fn-haproxy-template-compose-file "$TMP_COMPOSE_FILE" "$DOCKER_BIN" compose -f "$TMP_COMPOSE_FILE" -p haproxy up -d --quiet-pull } cmd-haproxy-stop() { - declare desc="Starts the haproxy server" - declare cmd="haproxy:start" + declare desc="Stops the haproxy server" + declare cmd="haproxy:stop" [[ "$1" == "$cmd" ]] && shift 1 if ! fn-is-compose-installed; then diff --git a/plugins/haproxy-vhosts/docker-args-process-deploy b/plugins/haproxy-vhosts/docker-args-process-deploy index f08713a5e..321b403fe 100755 --- a/plugins/haproxy-vhosts/docker-args-process-deploy +++ b/plugins/haproxy-vhosts/docker-args-process-deploy @@ -8,7 +8,7 @@ trigger-haproxy-vhosts-docker-args-process-deploy() { declare desc="nginx-vhosts core-post-deploy plugin trigger" declare trigger="docker-args-process-deploy" declare APP="$1" IMAGE_SOURCE_TYPE="$2" IMAGE_TAG="$3" PROC_TYPE="$4" CONTAINER_INDEX="$5" - local app_domains haproxy_domains is_app_listening letsencrypt_email output proxy_container_port proxy_host_port port_map proxy_port_map proxy_scheme proxy_schemes scheme tls_internal + local app_domains haproxy_domains is_app_listening letsencrypt_email output proxy_container_port proxy_host_port port_map proxy_scheme proxy_schemes scheme tls_internal local proxy_container_http_port proxy_container_http_port_candidate proxy_host_http_port_candidate local proxy_container_https_port proxy_container_https_port_candidate proxy_host_https_port_candidate proxy_redirect_ssl local STDIN=$(cat) @@ -33,15 +33,14 @@ trigger-haproxy-vhosts-docker-args-process-deploy() { plugn trigger domains-setup "$APP" >/dev/null # ensure we have a port mapping - plugn trigger proxy-configure-ports "$APP" + plugn trigger ports-configure "$APP" # gather port mapping information # we only support proxying a single port for http and https listeners # so this block parses the port mappings and tries to find the correct # mapping to expose is_app_listening="false" - proxy_port_map="$(plugn trigger config-get "$APP" DOKKU_PROXY_PORT_MAP)" - for port_map in $proxy_port_map; do + while read -r port_map; do proxy_scheme="$(awk -F ':' '{ print $1 }' <<<"$port_map")" proxy_host_port="$(awk -F ':' '{ print $2 }' <<<"$port_map")" proxy_container_port="$(awk -F ':' '{ print $3 }' <<<"$port_map")" @@ -69,7 +68,7 @@ trigger-haproxy-vhosts-docker-args-process-deploy() { proxy_container_https_port="$proxy_container_port" fi fi - done + done < <(plugn trigger ports-get "$APP") letsencrypt_email="$(fn-haproxy-letsencrypt-email)" if [[ -n "$letsencrypt_email" ]] && [[ -z "$proxy_container_https_port" ]]; then diff --git a/plugins/haproxy-vhosts/pre-restore b/plugins/haproxy-vhosts/pre-restore index 8e3086210..ed637bd96 100755 --- a/plugins/haproxy-vhosts/pre-restore +++ b/plugins/haproxy-vhosts/pre-restore @@ -8,7 +8,7 @@ trigger-haproxy-pre-restore() { declare desc="pre-restore the caddy proxy" declare trigger="install" - if [[ "$(fn-plugin-property-get "haproxy" "--global" "proxy-status")" == "stopped" ]]; then + if [[ "$(fn-plugin-property-get "haproxy" "--global" "proxy-status")" != "started" ]]; then return fi diff --git a/plugins/logs/functions.go b/plugins/logs/functions.go index 14cb3d5c9..edc41faa5 100644 --- a/plugins/logs/functions.go +++ b/plugins/logs/functions.go @@ -72,7 +72,7 @@ func startVectorContainer(vectorImage string) error { "container", "run", "--detach", "--name", vectorContainerName, common.MustGetEnv("DOKKU_GLOBAL_RUN_ARGS"), "--restart", "unless-stopped", - "--volume", "/var/lib/dokku/data/logs/vector.json:/etc/vector/vector.json", + "--volume", "/var/lib/dokku/data/logs:/etc/vector", "--volume", "/var/run/docker.sock:/var/run/docker.sock", "--volume", common.MustGetEnv("DOKKU_LOGS_HOST_DIR") + ":/var/logs/dokku/apps", "--volume", common.MustGetEnv("DOKKU_LOGS_HOST_DIR") + "/apps:/var/log/dokku/apps", diff --git a/plugins/logs/logs.go b/plugins/logs/logs.go index 752915a2d..52947051a 100644 --- a/plugins/logs/logs.go +++ b/plugins/logs/logs.go @@ -24,7 +24,7 @@ var ( ) // VectorImage contains the default vector image to run -const VectorImage = "timberio/vector:0.23.X-debian" +const VectorImage = "timberio/vector:0.31.X-debian" // VectorDefaultSink contains the default sink in use for vector log shipping const VectorDefaultSink = "blackhole://?print_interval_secs=1" diff --git a/plugins/network/Makefile b/plugins/network/Makefile index 7bf1212d6..094e19513 100644 --- a/plugins/network/Makefile +++ b/plugins/network/Makefile @@ -1,5 +1,5 @@ SUBCOMMANDS = subcommands/create subcommands/destroy subcommands/exists subcommands/info subcommands/list subcommands/rebuild subcommands/rebuildall subcommands/report subcommands/set -TRIGGERS = triggers/core-post-deploy triggers/docker-args-process-build triggers/docker-args-process-deploy triggers/docker-args-process-run triggers/install triggers/network-build-config triggers/network-compute-ports triggers/network-config-exists triggers/network-get-ipaddr triggers/network-get-listeners triggers/network-get-port triggers/network-get-property triggers/network-get-static-listeners triggers/network-write-ipaddr triggers/network-write-port triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-container-create triggers/post-create triggers/post-delete triggers/report +TRIGGERS = triggers/core-post-deploy triggers/docker-args-process-build triggers/docker-args-process-deploy triggers/docker-args-process-run triggers/install triggers/network-build-config triggers/network-config-exists triggers/network-get-ipaddr triggers/network-get-listeners triggers/network-get-property triggers/network-get-static-listeners triggers/network-write-ipaddr triggers/network-write-port triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-container-create triggers/post-create triggers/post-delete triggers/report BUILD = commands subcommands triggers PLUGIN_NAME = network diff --git a/plugins/network/functions.go b/plugins/network/functions.go index d1fd35830..d7bc29c9d 100644 --- a/plugins/network/functions.go +++ b/plugins/network/functions.go @@ -128,7 +128,7 @@ func networkExists(networkName string) (bool, error) { // listNetworks returns a list of docker networks func listNetworks() ([]string, error) { - b, err := sh.Command(common.DockerBin(), "network", "list", "--format", "{{ .Name }}").Output() + b, err := sh.Command(common.DockerBin(), "network", "ls", "--format", "{{ .Name }}").Output() output := strings.TrimSpace(string(b[:])) networks := []string{} diff --git a/plugins/network/network.go b/plugins/network/network.go index 26e636aef..13b1ed54b 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -2,7 +2,6 @@ package network import ( "fmt" - "io/ioutil" "net" "os" "path/filepath" @@ -10,9 +9,6 @@ import ( "strings" "github.com/dokku/dokku/plugins/common" - "github.com/dokku/dokku/plugins/config" - - sh "github.com/codeskyblue/go-sh" ) var ( @@ -65,8 +61,6 @@ func BuildConfig(appName string) error { return nil } - image := common.GetAppImageName(appName, "", "") - isHerokuishContainer := common.IsImageHerokuishBased(image, appName) common.LogInfo1(fmt.Sprintf("Ensuring network configuration is in sync for %s", appName)) for processType, procCount := range scale { @@ -82,8 +76,6 @@ func BuildConfig(appName string) error { } ipAddress := GetContainerIpaddress(appName, processType, containerID) - port := GetContainerPort(appName, processType, containerID, isHerokuishContainer) - if ipAddress != "" { args := []string{appName, processType, containerIndexString, ipAddress} _, err := common.PlugnTriggerOutput("network-write-ipaddr", args...) @@ -91,14 +83,6 @@ func BuildConfig(appName string) error { common.LogWarn(err.Error()) } } - - if port != "" { - args := []string{appName, processType, containerIndexString, port} - _, err := common.PlugnTriggerOutput("network-write-port", args...) - if err != nil { - common.LogWarn(err.Error()) - } - } } } @@ -147,53 +131,6 @@ func GetContainerIpaddress(appName, processType, containerID string) (ipAddr str return } -// GetContainerPort returns the port for a given app container -func GetContainerPort(appName, processType string, containerID string, isHerokuishContainer bool) (port string) { - if processType == "web" { - if staticWebListener := reportStaticWebListener(appName); staticWebListener != "" { - _, port, err := net.SplitHostPort(staticWebListener) - if err == nil { - return port - } - - return "80" - } - } - - dockerfilePorts := make([]string, 0) - if !isHerokuishContainer { - configValue := config.GetWithDefault(appName, "DOKKU_DOCKERFILE_PORTS", "") - if configValue != "" { - dockerfilePorts = strings.Split(configValue, " ") - } - } - - if len(dockerfilePorts) > 0 { - for _, p := range dockerfilePorts { - if strings.HasSuffix(p, "/udp") { - continue - } - port = strings.TrimSuffix(p, "/tcp") - if port != "" { - break - } - } - - if port != "" { - cmd := sh.Command(common.DockerBin(), "container", "port", containerID, port) - cmd.Stderr = ioutil.Discard - b, err := cmd.Output() - if err == nil { - port = strings.Split(string(b[:]), ":")[1] - } - } - } else { - port = "5000" - } - - return -} - // GetListeners returns a string array of app listeners func GetListeners(appName string, processType string) []string { if processType == "web" { @@ -214,6 +151,9 @@ func GetListeners(appName string, processType string) []string { portfile := strings.Replace(ipfile, ipPrefix, portPrefix, 1) ipAddress := common.ReadFirstLine(ipfile) port := common.ReadFirstLine(portfile) + if port == "" { + port = "5000" + } listeners = append(listeners, fmt.Sprintf("%s:%s", ipAddress, port)) } return listeners diff --git a/plugins/network/src/commands/commands.go b/plugins/network/src/commands/commands.go index 7160ceab9..e916485c5 100644 --- a/plugins/network/src/commands/commands.go +++ b/plugins/network/src/commands/commands.go @@ -23,10 +23,10 @@ Additional commands:` network:exists , Checks if a docker network exists network:info , Outputs information about a docker network network:list, Lists all docker networks - network:report [] [], Displays a network report for one or more apps network:rebuild , Rebuilds network settings for an app network:rebuildall, Rebuild network settings for all apps - network:set (), Set or clear a network property for an app + network:report [] [], Displays a network report for one or more apps + network:set (...), Set or clear a network property for an app ` ) diff --git a/plugins/network/src/subcommands/subcommands.go b/plugins/network/src/subcommands/subcommands.go index d2b9cfb2c..e81c49be1 100644 --- a/plugins/network/src/subcommands/subcommands.go +++ b/plugins/network/src/subcommands/subcommands.go @@ -67,12 +67,14 @@ func main() { appName := args.Arg(0) property := args.Arg(1) value := args.Arg(2) + values := common.VarArgs(args.Args(), 2) if *global { appName = "--global" property = args.Arg(0) value = args.Arg(1) + values = common.VarArgs(args.Args(), 1) } - err = network.CommandSet(appName, property, value) + err = network.CommandSet(appName, property, value, values) default: err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand) } diff --git a/plugins/network/src/triggers/triggers.go b/plugins/network/src/triggers/triggers.go index c78856d44..1a54d0f43 100644 --- a/plugins/network/src/triggers/triggers.go +++ b/plugins/network/src/triggers/triggers.go @@ -32,11 +32,6 @@ func main() { case "network-build-config": appName := flag.Arg(0) err = network.BuildConfig(appName) - case "network-compute-ports": - appName := flag.Arg(0) - processType := flag.Arg(1) - isHerokuishContainer := common.ToBool(flag.Arg(2)) - err = network.TriggerNetworkComputePorts(appName, processType, isHerokuishContainer) case "network-config-exists": appName := flag.Arg(0) err = network.TriggerNetworkConfigExists(appName) @@ -49,12 +44,6 @@ func main() { appName := flag.Arg(0) processType := flag.Arg(1) err = network.TriggerNetworkGetListeners(appName, processType) - case "network-get-port": - appName := flag.Arg(0) - processType := flag.Arg(1) - containerID := flag.Arg(2) - isHerokuishContainer := common.ToBool(flag.Arg(3)) - err = network.TriggerNetworkGetPort(appName, processType, containerID, isHerokuishContainer) case "network-get-property": appName := flag.Arg(0) property := flag.Arg(1) diff --git a/plugins/network/subcommands.go b/plugins/network/subcommands.go index bf04f5784..987caf136 100644 --- a/plugins/network/subcommands.go +++ b/plugins/network/subcommands.go @@ -141,7 +141,7 @@ func CommandReport(appName string, format string, infoFlag string) error { } // CommandSet set or clear a network property for an app -func CommandSet(appName string, property string, value string) error { +func CommandSet(appName string, property string, value string, values []string) error { if appName != "--global" { if err := common.VerifyAppName(appName); err != nil { return err @@ -152,7 +152,7 @@ func CommandSet(appName string, property string, value string) error { value = "false" } - attachProperites := map[string]bool{ + attachProperties := map[string]bool{ "attach-post-create": true, "attach-post-deploy": true, } @@ -161,14 +161,17 @@ func CommandSet(appName string, property string, value string) error { "host": true, "bridge": true, } - if attachProperites[property] { - if invalidNetworks[value] { - return errors.New("Invalid network name specified for attach") - } + if attachProperties[property] { + for _, networkName := range values { + if invalidNetworks[networkName] { + return fmt.Errorf("Invalid network name specified for attach: %s", networkName) + } - if isConflictingPropertyValue(appName, property, value) { - return errors.New("Network name already associated with this app") + if isConflictingPropertyValue(appName, property, networkName) { + return fmt.Errorf("Network name already associated with this app: %s", networkName) + } } + value = strings.Join(values, ",") } common.CommandPropertySet("network", appName, property, value, DefaultProperties, GlobalProperties) diff --git a/plugins/network/triggers.go b/plugins/network/triggers.go index fbdc8d510..0669a8581 100644 --- a/plugins/network/triggers.go +++ b/plugins/network/triggers.go @@ -5,10 +5,8 @@ import ( "io/ioutil" "os" "strings" - "unicode/utf8" "github.com/dokku/dokku/plugins/common" - "github.com/dokku/dokku/plugins/config" ) // TriggerDockerArgsProcess outputs the network plugin docker options for an app @@ -58,33 +56,6 @@ func TriggerInstall() error { return nil } -// TriggerNetworkComputePorts computes the ports for a given app container -func TriggerNetworkComputePorts(appName string, processType string, isHerokuishContainer bool) error { - var dockerfilePorts []string - if !isHerokuishContainer { - dokkuDockerfilePorts := strings.Trim(config.GetWithDefault(appName, "DOKKU_DOCKERFILE_PORTS", ""), " ") - if utf8.RuneCountInString(dokkuDockerfilePorts) > 0 { - dockerfilePorts = strings.Split(dokkuDockerfilePorts, " ") - } - } - - var ports []string - if len(dockerfilePorts) == 0 { - ports = append(ports, "5000") - } else { - for _, port := range dockerfilePorts { - port = strings.TrimSuffix(strings.TrimSpace(port), "/tcp") - if port == "" || strings.HasSuffix(port, "/udp") { - continue - } - ports = append(ports, port) - } - } - - fmt.Println(strings.Join(ports, " ")) - return nil -} - // TriggerNetworkConfigExists writes true or false to stdout whether a given app has network config func TriggerNetworkConfigExists(appName string) error { if HasNetworkConfig(appName) { @@ -114,13 +85,6 @@ func TriggerNetworkGetListeners(appName string, processType string) error { return nil } -// TriggerNetworkGetPort writes the port to stdout for a given app container -func TriggerNetworkGetPort(appName string, processType string, containerID string, isHerokuishContainer bool) error { - port := GetContainerPort(appName, processType, containerID, isHerokuishContainer) - fmt.Println(port) - return nil -} - // TriggerNetworkGetProperty writes the network property to stdout for a given app container func TriggerNetworkGetProperty(appName string, property string) error { computedValueMap := map[string]common.ReportFunc{ @@ -225,22 +189,26 @@ func TriggerPostContainerCreate(containerType string, containerID string, appNam } - networkName := reportComputedAttachPostCreate(appName) - if networkName == "" { + networks := reportComputedAttachPostCreate(appName) + if networks == "" { return nil } - exists, err := networkExists(networkName) - if err != nil { - return err + for _, networkName := range strings.Split(networks, ",") { + exists, err := networkExists(networkName) + if err != nil { + return err + } + + if !exists { + return fmt.Errorf("Network %v does not exist", networkName) + } + + return attachAppToNetwork(containerID, networkName, appName, phase, processType) } - if !exists { - return fmt.Errorf("Network %v does not exist", networkName) - } - - return attachAppToNetwork(containerID, networkName, appName, phase, processType) + return nil } // TriggerPostCreate sets bind-all-interfaces to false by default @@ -260,34 +228,37 @@ func TriggerPostDelete(appName string) error { // TriggerCorePostDeploy associates the container with a specified network func TriggerCorePostDeploy(appName string) error { - networkName := reportComputedAttachPostDeploy(appName) - if networkName == "" { + networks := reportComputedAttachPostDeploy(appName) + if networks == "" { return nil } - common.LogInfo1Quiet(fmt.Sprintf("Associating app with network %s", networkName)) - containerIDs, err := common.GetAppRunningContainerIDs(appName, "") - if err != nil { - return err - } - - exists, err := networkExists(networkName) - if err != nil { - return err - } - - if !exists { - return fmt.Errorf("Network %v does not exist", networkName) - } - - for _, containerID := range containerIDs { - processType, err := common.DockerInspect(containerID, "{{ index .Config.Labels \"com.dokku.process-type\"}}") + for _, networkName := range strings.Split(networks, ",") { + common.LogInfo1Quiet(fmt.Sprintf("Associating app with network %s", networkName)) + containerIDs, err := common.GetAppRunningContainerIDs(appName, "") if err != nil { return err } - if err := attachAppToNetwork(containerID, networkName, appName, "deploy", processType); err != nil { + + exists, err := networkExists(networkName) + if err != nil { return err } + + if !exists { + return fmt.Errorf("Network %v does not exist", networkName) + } + + for _, containerID := range containerIDs { + processType, err := common.DockerInspect(containerID, "{{ index .Config.Labels \"com.dokku.process-type\"}}") + if err != nil { + return err + } + if err := attachAppToNetwork(containerID, networkName, appName, "deploy", processType); err != nil { + return err + } + } } + return nil } diff --git a/plugins/nginx-vhosts/command-functions b/plugins/nginx-vhosts/command-functions index 78ab2c6e9..a397e1429 100755 --- a/plugins/nginx-vhosts/command-functions +++ b/plugins/nginx-vhosts/command-functions @@ -39,17 +39,17 @@ cmd-nginx-report-single() { local flag_map=( "--nginx-access-log-format: $(fn-nginx-access-log-format "$APP")" "--nginx-access-log-path: $(fn-nginx-access-log-path "$APP")" - "--nginx-bind-address-ipv4: $(fn-plugin-property-get-default "nginx" "$APP" "bind-address-ipv4" "")" - "--nginx-bind-address-ipv6: $(fn-plugin-property-get-default "nginx" "$APP" "bind-address-ipv6" "::")" - "--nginx-client-max-body-size: $(fn-plugin-property-get-default "nginx" "$APP" "client-max-body-size" "")" + "--nginx-bind-address-ipv4: $(fn-nginx-bind-address-ipv4 "$APP")" + "--nginx-bind-address-ipv6: $(fn-nginx-bind-address-ipv6 "$APP")" + "--nginx-client-max-body-size: $(fn-nginx-client-max-body-size "$APP")" "--nginx-disable-custom-config: $(fn-plugin-property-get-default "nginx" "$APP" "disable-custom-config" "false")" "--nginx-error-log-path: $(fn-nginx-error-log-path "$APP")" "--nginx-global-hsts: $(fn-plugin-property-get-default "nginx" "--global" "hsts" "true")" "--nginx-computed-hsts: $(fn-nginx-hsts-is-enabled "$APP")" "--nginx-hsts: $(fn-plugin-property-get-default "nginx" "$APP" "hsts" "")" - "--nginx-hsts-include-subdomains: $(fn-plugin-property-get-default "nginx" "$APP" "hsts-include-subdomains" "true")" - "--nginx-hsts-max-age: $(fn-plugin-property-get-default "nginx" "$APP" "hsts-max-age" "15724800")" - "--nginx-hsts-preload: $(fn-plugin-property-get-default "nginx" "$APP" "hsts-preload" "false")" + "--nginx-hsts-include-subdomains: $(fn-nginx-hsts-include-subdomains "$APP")" + "--nginx-hsts-max-age: $(fn-nginx-hsts-max-age "$APP")" + "--nginx-hsts-preload: $(fn-nginx-hsts-preload "$APP")" "--nginx-computed-nginx-conf-sigil-path: $(fn-nginx-computed-nginx-conf-sigil-path "$APP")" "--nginx-global-nginx-conf-sigil-path: $(fn-nginx-global-nginx-conf-sigil-path)" "--nginx-nginx-conf-sigil-path: $(fn-nginx-nginx-conf-sigil-path "$APP")" @@ -59,10 +59,10 @@ cmd-nginx-report-single() { "--nginx-proxy-busy-buffers-size: $(fn-nginx-proxy-busy-buffers-size "$APP")" "--nginx-proxy-read-timeout: $(fn-nginx-proxy-read-timeout "$APP")" "--nginx-last-visited-at: $(fn-nginx-vhosts-last-visited-at "$APP")" - "--nginx-x-forwarded-for-value: $(fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-for-value" "\$remote_addr")" - "--nginx-x-forwarded-port-value: $(fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-port-value" "\$server_port")" - "--nginx-x-forwarded-proto-value: $(fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-proto-value" "\$scheme")" - "--nginx-x-forwarded-ssl: $(fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-ssl" "")" + "--nginx-x-forwarded-for-value: $(fn-nginx-x-forwarded-for-value "$APP")" + "--nginx-x-forwarded-port-value: $(fn-nginx-x-forwarded-port-value "$APP")" + "--nginx-x-forwarded-proto-value: $(fn-nginx-x-forwarded-proto-value "$APP")" + "--nginx-x-forwarded-ssl: $(fn-nginx-x-forwarded-ssl "$APP")" ) if [[ -z "$INFO_FLAG" ]]; then diff --git a/plugins/nginx-vhosts/functions b/plugins/nginx-vhosts/functions index 25ccdd4e7..6bfc97622 100755 --- a/plugins/nginx-vhosts/functions +++ b/plugins/nginx-vhosts/functions @@ -28,6 +28,20 @@ fn-nginx-access-log-path() { fn-plugin-property-get-default "nginx" "$APP" "access-log-path" "${NGINX_LOG_ROOT}/${APP}-access.log" } +fn-nginx-bind-address-ipv4() { + declare desc="get the configured ipv4 bind address" + declare APP="$1" + + fn-plugin-property-get-default "nginx" "$APP" "bind-address-ipv4" "" +} + +fn-nginx-bind-address-ipv6() { + declare desc="get the configured ipv6 bind address" + declare APP="$1" + + fn-plugin-property-get-default "nginx" "$APP" "bind-address-ipv6" "::" +} + fn-nginx-proxy-buffer-size() { declare desc="get the configured proxy buffer size" declare APP="$1" @@ -78,6 +92,34 @@ fn-nginx-error-log-path() { fn-plugin-property-get-default "nginx" "$APP" "error-log-path" "${NGINX_LOG_ROOT}/${APP}-error.log" } +fn-nginx-x-forwarded-for-value() { + declare desc="get the configured x-forwarded-for value" + declare APP="$1" + + fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-for-value" "\$remote_addr" +} + +fn-nginx-x-forwarded-port-value() { + declare desc="get the configured x-forwarded-port value" + declare APP="$1" + + fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-port-value" "\$server_port" +} + +fn-nginx-x-forwarded-proto-value() { + declare desc="get the configured x-forwarded-proto value" + declare APP="$1" + + fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-proto-value" "\$scheme" +} + +fn-nginx-x-forwarded-ssl() { + declare desc="get the configured x-forwarded-ssl value" + declare APP="$1" + + fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-ssl" "" +} + get_nginx_location() { declare desc="check that nginx is at the expected location and return it" fn-nginx-vhosts-nginx-location @@ -98,7 +140,7 @@ validate_nginx() { fi set +e - sudo "$NGINX_LOCATION" -t >/dev/null 2>&1 + sudo "$NGINX_LOCATION" -t &>/dev/null EXIT_CODE=$? set -e if [[ "$EXIT_CODE" -eq "0" ]]; then @@ -115,7 +157,7 @@ validate_nginx() { fi set +e - sudo "$NGINX_LOCATION" -t >/dev/null 2>&1 + sudo "$NGINX_LOCATION" -t &>/dev/null EXIT_CODE=$? set -e if [[ "$EXIT_CODE" -eq "0" ]]; then @@ -294,7 +336,6 @@ nginx_build_config() { local SCHEME=http local NGINX_TEMPLATE_SOURCE="built-in" local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" - local RAW_TCP_PORTS="$(get_app_raw_tcp_ports "$APP")" local DOKKU_APP_LISTENERS CUSTOM_NGINX_TEMPLATE="$(plugn trigger nginx-app-template-source "$APP" "app-config")" @@ -318,13 +359,12 @@ nginx_build_config() { fi # setup nginx listen ports - plugn trigger proxy-configure-ports "$APP" + plugn trigger ports-configure "$APP" local PROXY_PORT=$(config_get "$APP" DOKKU_PROXY_PORT) local PROXY_SSL_PORT=$(config_get "$APP" DOKKU_PROXY_SSL_PORT) - local PROXY_PORT_MAP=$(config_get "$APP" DOKKU_PROXY_PORT_MAP) - local PORT_MAP proxy_port_map - for PORT_MAP in $PROXY_PORT_MAP; do + local PORT_MAP PROXY_PORT_MAP proxy_port_map + while read -r PORT_MAP; do local PROXY_UPSTREAM_SCHEME="$(awk -F ':' '{ print $1 }' <<<"$PORT_MAP")" if [[ "$PROXY_UPSTREAM_SCHEME" == "https" ]] && [[ "$IS_SSL_ENABLED" == "false" ]]; then dokku_log_warn "Ignoring detected https port mapping without an accompanying ssl certificate (${PORT_MAP})" @@ -337,8 +377,8 @@ nginx_build_config() { if [[ "$(is_val_in_list "$PROXY_UPSTREAM_PORT" "$PROXY_UPSTREAM_PORTS" " ")" == "false" ]]; then local PROXY_UPSTREAM_PORTS+="$PROXY_UPSTREAM_PORT " fi - done - PROXY_PORT_MAP="$proxy_port_map" + done < <(plugn trigger ports-get "$APP") + PROXY_PORT_MAP="$(echo "$proxy_port_map" | xargs)" # trailing spaces mess up default template local PROXY_UPSTREAM_PORTS="$(echo "$PROXY_UPSTREAM_PORTS" | xargs)" local SSL_INUSE= @@ -379,8 +419,6 @@ nginx_build_config() { HTTP2_PUSH_SUPPORTED="$(is_http2_push_enabled "$NGINX_VERSION")" GRPC_SUPPORTED="$(is_grpc_enabled "$NGINX_VERSION")" - PROXY_PORT_MAP=$(echo "$PROXY_PORT_MAP" | xargs) # trailing spaces mess up default template - local NGINX_LOG_ROOT="$(fn-nginx-log-root)" local NGINX_ACCESS_LOG_FORMAT="$(fn-nginx-access-log-format "$APP")" local NGINX_ACCESS_LOG_PATH="$(fn-nginx-access-log-path "$APP")" @@ -406,7 +444,6 @@ nginx_build_config() { local NGINX_BUILD_CONFIG_TMP_WORK_DIR=$(mktemp -d "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX") local NGINX_CONF=$(mktemp --tmpdir="${NGINX_BUILD_CONFIG_TMP_WORK_DIR}" "nginx.conf.XXXXXX") local CUSTOM_NGINX_TEMPLATE="$NGINX_BUILD_CONFIG_TMP_WORK_DIR/$NGINX_TEMPLATE_NAME" - # shellcheck disable=SC2086 trap "rm -rf '$NGINX_CONF' '$NGINX_BUILD_CONFIG_TMP_WORK_DIR' >/dev/null" RETURN INT TERM EXIT get_custom_nginx_template "$APP" "$CUSTOM_NGINX_TEMPLATE" 2>/dev/null @@ -416,14 +453,14 @@ nginx_build_config() { local NGINX_TEMPLATE_SOURCE="app-supplied" fi - local NGINX_BIND_ADDRESS_IP4="$(fn-plugin-property-get-default "nginx" "$APP" "bind-address-ipv4" "")" - local NGINX_BIND_ADDRESS_IP6="$(fn-plugin-property-get-default "nginx" "$APP" "bind-address-ipv6" "::")" + local NGINX_BIND_ADDRESS_IP4="$(fn-nginx-bind-address-ipv4 "$APP")" + local NGINX_BIND_ADDRESS_IP6="$(fn-nginx-bind-address-ipv6 "$APP")" [[ -z "$NGINX_BIND_ADDRESS_IP6" ]] && NGINX_BIND_ADDRESS_IP6="::" - local PROXY_X_FORWARDED_FOR="$(fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-for-value" "\$remote_addr")" - local PROXY_X_FORWARDED_PORT="$(fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-port-value" "\$server_port")" - local PROXY_X_FORWARDED_PROTO="$(fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-proto-value" "\$scheme")" - local PROXY_X_FORWARDED_SSL="$(fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-ssl" "")" + local PROXY_X_FORWARDED_FOR="$(fn-nginx-x-forwarded-for-value "$APP")" + local PROXY_X_FORWARDED_PORT="$(fn-nginx-x-forwarded-port-value "$APP")" + local PROXY_X_FORWARDED_PROTO="$(fn-nginx-x-forwarded-proto-value "$APP")" + local PROXY_X_FORWARDED_SSL="$(fn-nginx-x-forwarded-ssl "$APP")" eval "$(config_export app "$APP")" local SIGIL_PARAMS=(-f "$NGINX_TEMPLATE" APP="$APP" DOKKU_ROOT="$DOKKU_ROOT" @@ -453,7 +490,7 @@ nginx_build_config() { PROXY_BUSY_BUFFERS_SIZE="$PROXY_BUSY_BUFFERS_SIZE" # Deprecated: Remove this after a few versions NGINX_PORT="$PROXY_PORT" NGINX_SSL_PORT="$PROXY_SSL_PORT" - PROXY_PORT="$PROXY_PORT" PROXY_SSL_PORT="$PROXY_SSL_PORT" RAW_TCP_PORTS="$RAW_TCP_PORTS" + PROXY_PORT="$PROXY_PORT" PROXY_SSL_PORT="$PROXY_SSL_PORT" PROXY_PORT_MAP="$PROXY_PORT_MAP" PROXY_UPSTREAM_PORTS="$PROXY_UPSTREAM_PORTS" PROXY_X_FORWARDED_FOR="$PROXY_X_FORWARDED_FOR" PROXY_X_FORWARDED_PORT="$PROXY_X_FORWARDED_PORT" diff --git a/plugins/nginx-vhosts/internal-functions b/plugins/nginx-vhosts/internal-functions index a653499ef..c2221e009 100755 --- a/plugins/nginx-vhosts/internal-functions +++ b/plugins/nginx-vhosts/internal-functions @@ -13,6 +13,21 @@ fn-nginx-vhosts-last-visited-at() { fi } +fn-nginx-hsts-include-subdomains() { + declare APP="$1" + fn-plugin-property-get-default "nginx" "$APP" "hsts-include-subdomains" "true" +} + +fn-nginx-hsts-max-age() { + declare APP="$1" + fn-plugin-property-get-default "nginx" "$APP" "hsts-max-age" "15724800" +} + +fn-nginx-hsts-preload() { + declare APP="$1" + fn-plugin-property-get-default "nginx" "$APP" "hsts-preload" "false" +} + fn-nginx-hsts-is-enabled() { declare APP="$1" local hsts_is_enabled="$(fn-plugin-property-get-default "nginx" "$APP" "hsts" "")" @@ -49,9 +64,9 @@ fn-nginx-nginx-conf-sigil-path() { fn-nginx-vhosts-manage-hsts() { declare APP="$1" SSL_ENABLED="$2" local HSTS="$(fn-nginx-hsts-is-enabled "$APP")" - local HSTS_INCLUDE_SUBDOMAINS="$(fn-plugin-property-get-default "nginx" "$APP" "hsts-include-subdomains" "true")" - local HSTS_MAX_AGE="$(fn-plugin-property-get-default "nginx" "$APP" "hsts-max-age" "15724800")" - local HSTS_PRELOAD="$(fn-plugin-property-get-default "nginx" "$APP" "hsts-preload" "false")" + local HSTS_INCLUDE_SUBDOMAINS="$(fn-nginx-hsts-include-subdomains "$APP")" + local HSTS_MAX_AGE="$(fn-nginx-hsts-max-age "$APP")" + local HSTS_PRELOAD="$(fn-nginx-hsts-preload "$APP")" local NGINX_HSTS_CONF="$DOKKU_ROOT/$APP/nginx.conf.d/hsts.conf" local HSTS_TEMPLATE="$PLUGIN_AVAILABLE_PATH/nginx-vhosts/templates/hsts.conf.sigil" diff --git a/plugins/nginx-vhosts/pre-disable-vhost b/plugins/nginx-vhosts/pre-disable-vhost index e786e5322..ecfba935c 100755 --- a/plugins/nginx-vhosts/pre-disable-vhost +++ b/plugins/nginx-vhosts/pre-disable-vhost @@ -10,7 +10,8 @@ trigger-nginx-vhosts-pre-disable-vhost() { declare APP="$1" if [[ "$(plugn trigger proxy-type "$APP")" == "nginx" ]]; then - DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$APP" DOKKU_PROXY_PORT DOKKU_PROXY_SSL_PORT DOKKU_PROXY_PORT_MAP + DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$APP" DOKKU_PROXY_PORT DOKKU_PROXY_SSL_PORT + plugn trigger ports-clear "$APP" fi } diff --git a/plugins/nginx-vhosts/pre-enable-vhost b/plugins/nginx-vhosts/pre-enable-vhost index bde11f963..64808eb3d 100755 --- a/plugins/nginx-vhosts/pre-enable-vhost +++ b/plugins/nginx-vhosts/pre-enable-vhost @@ -10,7 +10,8 @@ trigger-nginx-vhosts-pre-enable-vhost() { declare APP="$1" if [[ "$(plugn trigger proxy-type "$APP")" == "nginx" ]]; then - DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$APP" DOKKU_PROXY_PORT DOKKU_PROXY_SSL_PORT DOKKU_PROXY_PORT_MAP + DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$APP" DOKKU_PROXY_PORT DOKKU_PROXY_SSL_PORT + plugn trigger ports-clear "$APP" fi } diff --git a/plugins/nginx-vhosts/pre-restore b/plugins/nginx-vhosts/pre-restore index b7de63b37..40565f897 100755 --- a/plugins/nginx-vhosts/pre-restore +++ b/plugins/nginx-vhosts/pre-restore @@ -8,7 +8,7 @@ trigger-nginx-pre-restore() { declare desc="pre-restore the nginx proxy" declare trigger="install" - if [[ "$(fn-plugin-property-get "nginx" "--global" "proxy-status")" == "stopped" ]]; then + if [[ "$(fn-plugin-property-get "nginx" "--global" "proxy-status")" != "started" ]]; then return fi diff --git a/plugins/nginx-vhosts/templates/nginx.conf.sigil b/plugins/nginx-vhosts/templates/nginx.conf.sigil index 94c7d99c6..59a40a6b7 100644 --- a/plugins/nginx-vhosts/templates/nginx.conf.sigil +++ b/plugins/nginx-vhosts/templates/nginx.conf.sigil @@ -22,7 +22,7 @@ server { gzip on; gzip_min_length 1100; gzip_buffers 4 32k; - gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/json application/xml application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml; + gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/wasm application/json application/xml application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml; gzip_vary on; gzip_comp_level 6; diff --git a/plugins/openresty-vhosts/certs-force b/plugins/openresty-vhosts/certs-force new file mode 100755 index 000000000..fdaaf7285 --- /dev/null +++ b/plugins/openresty-vhosts/certs-force @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/internal-functions" + +trigger-openresty-vhosts-certs-force() { + declare desc="openresty-vhosts certs-force plugin trigger" + declare trigger="certs-force" + declare APP="$1" + + if [[ "$(plugn trigger proxy-type "$APP")" != "openresty" ]]; then + return + fi + + if [[ -n "$(fn-openresty-letsencrypt-email)" ]]; then + echo true + fi +} + +trigger-openresty-vhosts-certs-force "$@" diff --git a/plugins/openresty-vhosts/command-functions b/plugins/openresty-vhosts/command-functions new file mode 100755 index 000000000..d630d87d5 --- /dev/null +++ b/plugins/openresty-vhosts/command-functions @@ -0,0 +1,172 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/internal-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +cmd-openresty-report() { + declare desc="displays a openresty report for one or more apps" + declare cmd="openresty:report" + [[ "$1" == "$cmd" ]] && shift 1 + declare APP="$1" INFO_FLAG="$2" + + if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then + INFO_FLAG="$APP" + APP="" + fi + + if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then + INFO_FLAG="true" + fi + + if [[ -z "$APP" ]]; then + for app in $(dokku_apps); do + cmd-openresty-report-single "$app" "$INFO_FLAG" | tee || true + done + else + cmd-openresty-report-single "$APP" "$INFO_FLAG" + fi +} + +cmd-openresty-report-single() { + declare APP="$1" INFO_FLAG="$2" + if [[ "$INFO_FLAG" == "true" ]]; then + INFO_FLAG="" + fi + verify_app_name "$APP" + local flag_map=( + "--openresty-access-log-format: $(fn-openresty-access-log-format "$APP")" + "--openresty-access-log-path: $(fn-openresty-access-log-path "$APP")" + "--openresty-bind-address-ipv4: $(fn-openresty-bind-address-ipv4 "$APP")" + "--openresty-bind-address-ipv6: $(fn-openresty-bind-address-ipv6 "$APP")" + "--openresty-client-max-body-size: $(fn-openresty-client-max-body-size "$APP")" + "--openresty-error-log-path: $(fn-openresty-error-log-path "$APP")" + "--openresty-global-hsts: $(fn-plugin-property-get-default "openresty" "--global" "hsts" "true")" + "--openresty-computed-hsts: $(fn-openresty-hsts-is-enabled "$APP")" + "--openresty-hsts: $(fn-plugin-property-get-default "openresty" "$APP" "hsts" "")" + "--openresty-hsts-include-subdomains: $(fn-openresty-hsts-include-subdomains "$APP")" + "--openresty-hsts-max-age: $(fn-openresty-hsts-max-age "$APP")" + "--openresty-hsts-preload: $(fn-openresty-hsts-preload "$APP")" + "--openresty-image: $(fn-openresty-image)" + "--openresty-letsencrypt-email: $(fn-openresty-letsencrypt-email)" + "--openresty-letsencrypt-server: $(fn-openresty-letsencrypt-server)" + "--openresty-proxy-buffer-size: $(fn-openresty-proxy-buffer-size "$APP")" + "--openresty-proxy-buffering: $(fn-openresty-proxy-buffering "$APP")" + "--openresty-proxy-buffers: $(fn-openresty-proxy-buffers "$APP")" + "--openresty-proxy-busy-buffers-size: $(fn-openresty-proxy-busy-buffers-size "$APP")" + "--openresty-proxy-read-timeout: $(fn-openresty-proxy-read-timeout "$APP")" + "--openresty-x-forwarded-for-value: $(fn-openresty-x-forwarded-for-value "$APP")" + "--openresty-x-forwarded-port-value: $(fn-openresty-x-forwarded-port-value "$APP")" + "--openresty-x-forwarded-proto-value: $(fn-openresty-x-forwarded-proto-value "$APP")" + "--openresty-x-forwarded-ssl: $(fn-openresty-x-forwarded-ssl "$APP")" + ) + + if [[ -z "$INFO_FLAG" ]]; then + dokku_log_info2_quiet "${APP} openresty information" + for flag in "${flag_map[@]}"; do + key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" + dokku_log_verbose "$(printf "%-30s %-25s" "${key^}" "${flag#*: }")" + done + else + local match=false + local value_exists=false + for flag in "${flag_map[@]}"; do + valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)" + if [[ "$flag" == "${INFO_FLAG}:"* ]]; then + value=${flag#*: } + size="${#value}" + if [[ "$size" -ne 0 ]]; then + echo "$value" && match=true && value_exists=true + else + match=true + fi + fi + done + [[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}" + fi +} + +cmd-openresty-logs() { + declare desc="display openresty logs from command line" + declare cmd="openresty:logs" + [[ "$1" == "$cmd" ]] && shift 1 + local NUM="100" TAIL=false + + local TEMP=$(getopt -o htn: --long help,tail,num: -n 'dokku openresty:logs' -- "$@") + local EXIT_CODE="$?" + if [[ "$EXIT_CODE" != 0 ]]; then + fn-openresty-logs-usage >&2 + exit 1 + fi + eval set -- "$TEMP" + + while true; do + case "$1" in + -t | --tail) + local TAIL=true + shift + ;; + -n | --num) + local NUM="$2" + shift 2 + ;; + --) + shift + break + ;; + *) dokku_log_fail "Internal error" ;; + esac + done + + fn-openresty-logs "$TAIL" "$NUM" +} + +cmd-openresty-show-config() { + declare desc="display openresty config" + declare cmd="openresty:show-config" + [[ "$1" == "$cmd" ]] && shift 1 + + if ! fn-is-compose-installed; then + dokku_log_fail "Required docker compose plugin is not installed" + fi + + local TMP_COMPOSE_FILE=$(mktemp "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX") + trap "rm -rf '$TMP_COMPOSE_FILE' >/dev/null" RETURN INT TERM EXIT + + fn-openresty-template-compose-file "$TMP_COMPOSE_FILE" + cat "$TMP_COMPOSE_FILE" +} + +cmd-openresty-start() { + declare desc="Starts the openresty server" + declare cmd="openresty:start" + [[ "$1" == "$cmd" ]] && shift 1 + + if ! fn-is-compose-installed; then + dokku_log_fail "Required docker compose plugin is not installed" + fi + + local TMP_COMPOSE_FILE=$(mktemp "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX") + trap "rm -rf '$TMP_COMPOSE_FILE' >/dev/null" RETURN INT TERM EXIT + + fn-plugin-property-write "openresty" "--global" "proxy-status" "started" + fn-openresty-template-compose-file "$TMP_COMPOSE_FILE" + "$DOCKER_BIN" compose -f "$TMP_COMPOSE_FILE" -p openresty up -d --quiet-pull +} + +cmd-openresty-stop() { + declare desc="Starts the openresty server" + declare cmd="openresty:stop" + [[ "$1" == "$cmd" ]] && shift 1 + + if ! fn-is-compose-installed; then + dokku_log_fail "Required docker compose plugin is not installed" + fi + + local TMP_COMPOSE_FILE=$(mktemp "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX") + trap "rm -rf '$TMP_COMPOSE_FILE' >/dev/null" RETURN INT TERM EXIT + + fn-plugin-property-write "openresty" "--global" "proxy-status" "stopped" + fn-openresty-template-compose-file "$TMP_COMPOSE_FILE" + "$DOCKER_BIN" compose -f "$TMP_COMPOSE_FILE" -p openresty down --remove-orphans +} diff --git a/plugins/openresty-vhosts/commands b/plugins/openresty-vhosts/commands new file mode 100755 index 000000000..b87c8aa14 --- /dev/null +++ b/plugins/openresty-vhosts/commands @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +[[ " openresty:help help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/help-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +case "$1" in + help | openresty:help) + cmd-openresty-help "$@" + ;; + + *) + exit "$DOKKU_NOT_IMPLEMENTED_EXIT" + ;; +esac diff --git a/plugins/openresty-vhosts/core-post-deploy b/plugins/openresty-vhosts/core-post-deploy new file mode 100755 index 000000000..c8ad3ea0c --- /dev/null +++ b/plugins/openresty-vhosts/core-post-deploy @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/internal-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +trigger-openresty-vhosts-core-post-deploy() { + declare desc="openresty-vhosts core-post-deploy plugin trigger" + declare trigger="core-post-deploy" + declare APP="$1" + local tls_internal + + if [[ "$(plugn trigger proxy-type "$APP")" != "openresty" ]]; then + return + fi + + dokku_log_info1 "Routing app via openresty" + if [[ -f "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID.missing" ]]; then + rm -f "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID.missing" + rm -f "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes" + fi +} + +trigger-openresty-vhosts-core-post-deploy "$@" diff --git a/plugins/openresty-vhosts/core-post-extract b/plugins/openresty-vhosts/core-post-extract new file mode 100755 index 000000000..e30bba6c6 --- /dev/null +++ b/plugins/openresty-vhosts/core-post-extract @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/internal-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +fn-openresty-vhosts-copy-from-image() { + declare APP="$1" IMAGE_NAME="$2" CONF_PATH="$3" + + mkdir -p "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP" + rm -f "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes."* + copy_dir_from_image "$IMAGE_NAME" "$CONF_PATH" "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID" || true + if [[ ! -f "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID" ]]; then + touch "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID.missing" + fi +} + +fn-openresty-vhosts-copy-from-directory() { + declare APP="$1" SOURCECODE_WORK_DIR="$2" CONF_PATH="$3" + + pushd "$SOURCECODE_WORK_DIR" >/dev/null + mkdir -p "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP" + + if [[ -z "$CONF_PATH" ]]; then + touch "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID.missing" + return + fi + + if [[ ! -d "$CONF_PATH" ]]; then + touch "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID.missing" + return + fi + + rm -f "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes."* + mkdir p "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID/" + cp -f "$CONF_PATH"/* "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID/" + popd &>/dev/null || pushd "/tmp" >/dev/null +} + +trigger-openresty-vhosts-core-post-extract() { + declare desc="openresty-vhosts post-extract plugin trigger" + declare trigger="post-extract" + declare APP="$1" SOURCECODE_WORK_DIR="$2" + local CONF_PATH="openresty-http-includes" + local app_source_image + + app_source_image="$(plugn trigger git-get-property "$APP" "source-image")" + if [[ -n "$app_source_image" ]]; then + fn-openresty-vhosts-copy-from-image "$APP" "$app_source_image" "$CONF_PATH" + else + fn-openresty-vhosts-copy-from-directory "$APP" "$SOURCECODE_WORK_DIR" "$CONF_PATH" + fi +} + +trigger-openresty-vhosts-core-post-extract "$@" diff --git a/plugins/openresty-vhosts/docker-args-process-deploy b/plugins/openresty-vhosts/docker-args-process-deploy new file mode 100755 index 000000000..1f78bdb33 --- /dev/null +++ b/plugins/openresty-vhosts/docker-args-process-deploy @@ -0,0 +1,153 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/internal-functions" + +trigger-openresty-vhosts-docker-args-process-deploy() { + declare desc="openresty-vhosts docker-args-process-deploy plugin trigger" + declare trigger="docker-args-process-deploy" + declare APP="$1" IMAGE_SOURCE_TYPE="$2" IMAGE_TAG="$3" PROC_TYPE="$4" CONTAINER_INDEX="$5" + local include_dir output value DATA + local STDIN=$(cat) + + if [[ "$PROC_TYPE" != "web" ]]; then + return + fi + + if [[ "$(plugn trigger proxy-type "$APP")" != "openresty" ]] && [[ "$(plugn trigger proxy-type "$APP")" != "nginx" ]]; then + return + fi + + if [[ "$(plugn trigger proxy-is-enabled "$APP")" != "true" ]]; then + return + fi + + if ! plugn trigger domains-vhost-enabled "$APP" 2>/dev/null; then + return + fi + + # ensure we have a port mapping + plugn trigger ports-configure "$APP" + + include_dir="$(fn-openresty-get-http-includes-dir "$APP")" + if [[ -d "$include_dir" ]]; then + pushd "$include_dir" >/dev/null + for filename in *; do + if [[ ! -f "$include_dir/$filename" ]]; then + continue + fi + + if [[ $filename != *.conf ]]; then + continue + fi + + DATA="$(base64 -w 0 <"$include_dir/$filename")" + output="$output '--label=openresty.include-http-$filename=$DATA'" + done + popd &>/dev/null || pushd "/tmp" >/dev/null + fi + + is_app_listening="false" + local APP_PORT_MAP="$(plugn trigger ports-get "$APP")" + while IFS= read -r port_map; do + proxy_scheme="$(awk -F ':' '{ print $1 }' <<<"$port_map")" + proxy_host_port="$(awk -F ':' '{ print $2 }' <<<"$port_map")" + proxy_container_port="$(awk -F ':' '{ print $3 }' <<<"$port_map")" + + if [[ "$proxy_scheme" == "http" ]]; then + is_app_listening="true" + if [[ -z "$proxy_container_http_port_candidate" ]]; then + proxy_container_http_port_candidate="$proxy_container_port" + proxy_host_http_port_candidate="$proxy_host_port" + fi + + if [[ "$proxy_host_port" == "80" ]] && [[ -z "$proxy_container_http_port" ]]; then + proxy_container_http_port="$proxy_container_port" + fi + fi + + if [[ "$proxy_scheme" == "https" ]]; then + is_app_listening="true" + if [[ -z "$proxy_container_https_port_candidate" ]]; then + proxy_container_https_port_candidate="$proxy_container_port" + proxy_host_https_port_candidate="$proxy_host_port" + fi + + if [[ "$proxy_host_port" == "443" ]] && [[ -z "$proxy_container_https_port" ]]; then + proxy_container_https_port="$proxy_container_port" + fi + fi + done <<<"$APP_PORT_MAP" + + if [[ -n "$letsencrypt_email" ]] && [[ -z "$proxy_container_https_port" ]]; then + proxy_container_https_port_candidate="$proxy_container_http_port_candidate" + proxy_host_https_port_candidate="$proxy_host_http_port_candidate" + if [[ -n "$proxy_container_http_port" ]]; then + proxy_container_https_port_candidate="$proxy_container_http_port" + proxy_host_http_port_candidate=443 + fi + fi + + letsencrypt_value="false" + if [[ -n "$(fn-openresty-letsencrypt-email)" ]]; then + if [[ -n "$proxy_container_https_port" ]] || [[ -n "$proxy_container_https_port_candidate" ]]; then + letsencrypt_value="true" + fi + fi + + output="$output '--label=openresty.letsencrypt=$letsencrypt_value'" + + value="$(fn-openresty-access-log-format "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.access-log-format=$value'" + value="$(fn-openresty-bind-address-ipv4 "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.bind-address-ipv4=$value'" + value="$(fn-openresty-bind-address-ipv6 "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.bind-address-ipv6=$value'" + value="$(fn-openresty-client-max-body-size "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.client-max-body-size=$value'" + value="$(fn-openresty-hsts-include-subdomains "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.hsts-include-subdomains=$value'" + value="$(fn-openresty-hsts-max-age "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.hsts-max-age=$value'" + value="$(fn-openresty-hsts-preload "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.hsts-preload=$value'" + value="$(fn-openresty-hsts-is-enabled "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.hsts=$value'" + value="443" + [[ -n "$value" ]] && output="$output '--label=openresty.https-port=$value'" + value="$(plugn trigger domains-list "$APP" | xargs)" + [[ -n "$value" ]] && output="$output '--label=openresty.domains=$value'" + value="$(plugn trigger network-get-property "$APP" initial-network)" + [[ -n "$value" ]] && output="$output '--label=openresty.initial-network=$value'" + value="$(echo "$APP_PORT_MAP" | xargs)" + [[ -n "$value" ]] && output="$output '--label=openresty.port-mapping=$value'" + value="$(fn-openresty-proxy-buffer-size "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.proxy-buffer-size=$value'" + value="$(fn-openresty-proxy-buffering "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.proxy-buffering=$value'" + value="$(fn-openresty-proxy-buffers "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.proxy-buffers=$value'" + value="$(fn-openresty-proxy-busy-buffers-size "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.proxy-busy-buffer-size=$value'" + value="$(fn-openresty-proxy-connect-timeout "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.proxy-connect-timeout=$value'" + value="$(fn-openresty-proxy-read-timeout "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.proxy-read-timeout=$value'" + value="$(fn-openresty-proxy-send-timeout "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.proxy-send-timeout=$value'" + value="$(fn-openresty-send-timeout "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.send-timeout=$value'" + value="$(fn-openresty-x-forwarded-for-value "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.x-forwarded-for-value=$value'" + value="$(fn-openresty-x-forwarded-port-value "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.x-forwarded-port-value=$value'" + value="$(fn-openresty-x-forwarded-proto-value "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.x-forwarded-proto-value=$value'" + value="$(fn-openresty-x-forwarded-ssl "$APP")" + [[ -n "$value" ]] && output="$output '--label=openresty.x-forwarded-ssl=$value'" + + echo -n "$STDIN$output" +} + +trigger-openresty-vhosts-docker-args-process-deploy "$@" diff --git a/plugins/openresty-vhosts/help-functions b/plugins/openresty-vhosts/help-functions new file mode 100755 index 000000000..d8fc19e7a --- /dev/null +++ b/plugins/openresty-vhosts/help-functions @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +cmd-openresty-help() { + declare desc="help command" + declare CMD="$1" + local plugin_name="openresty" + local plugin_description="Manage the openresty proxy integration" + + if [[ "$CMD" == "${plugin_name}:help" ]]; then + echo -e "Usage: dokku ${plugin_name}[:COMMAND]" + echo '' + echo "$plugin_description" + echo '' + echo 'Additional commands:' + fn-help-content | sort | column -c2 -t -s, + elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then + fn-help-content + else + cat <] [], Displays an openresty report for one or more apps + openresty:set (), Set or clear an openresty property for an app + openresty:show-config , Display openresty compose config + openresty:start, Starts the openresty server + openresty:stop, Stops the openresty server +help_content +} diff --git a/plugins/openresty-vhosts/install b/plugins/openresty-vhosts/install new file mode 100755 index 000000000..643146b17 --- /dev/null +++ b/plugins/openresty-vhosts/install @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +trigger-openresty-install() { + declare desc="installs the openresty plugin" + declare trigger="install" + + mkdir -p "${DOKKU_LIB_ROOT}/data/openresty" "${DOKKU_LIB_ROOT}/data/openresty/.docker-letsencrypt" + chown -R "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "${DOKKU_LIB_ROOT}/data/openresty" + + fn-plugin-property-setup "openresty" +} + +trigger-openresty-install "$@" diff --git a/plugins/openresty-vhosts/internal-functions b/plugins/openresty-vhosts/internal-functions new file mode 100755 index 000000000..c22799263 --- /dev/null +++ b/plugins/openresty-vhosts/internal-functions @@ -0,0 +1,230 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +fn-get-pagesize() { + declare desc="return the underlying system's memory page size" + declare todo="port to common functions" + + "$PLUGIN_CORE_AVAILABLE_PATH/nginx-vhosts/pagesize" +} + +fn-openresty-access-log-format() { + declare desc="get the configured access log format" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "access-log-format" "" +} + +fn-openresty-access-log-path() { + declare desc="get the configured access log path" + declare APP="$1" + local OPENRESTY_LOG_ROOT="$(fn-openresty-log-root)" + + fn-plugin-property-get-default "openresty" "$APP" "access-log-path" "${OPENRESTY_LOG_ROOT}/${APP}-access.log" +} + +fn-openresty-bind-address-ipv4() { + declare desc="get the configured ipv4 bind address" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "bind-address-ipv4" "" +} + +fn-openresty-bind-address-ipv6() { + declare desc="get the configured ipv6 bind address" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "bind-address-ipv6" "::" +} + +fn-openresty-client-max-body-size() { + declare desc="get the configured client max body size" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "client-max-body-size" "" +} + +fn-openresty-get-http-includes-dir() { + declare desc="get any include dir if available" + declare APP="$1" + + if [[ -d "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID.missing" ]]; then + return + fi + + if [[ -d "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID" ]]; then + echo "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID" + elif [[ -d "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes" ]]; then + echo "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes" + fi +} + +fn-openresty-hsts-include-subdomains() { + declare APP="$1" + fn-plugin-property-get-default "openresty" "$APP" "hsts-include-subdomains" "true" +} + +fn-openresty-hsts-max-age() { + declare APP="$1" + fn-plugin-property-get-default "openresty" "$APP" "hsts-max-age" "15724800" +} + +fn-openresty-hsts-preload() { + declare APP="$1" + fn-plugin-property-get-default "openresty" "$APP" "hsts-preload" "false" +} + +fn-openresty-hsts-is-enabled() { + declare APP="$1" + local hsts_is_enabled="$(fn-plugin-property-get-default "openresty" "$APP" "hsts" "")" + + if [[ "$hsts_is_enabled" == "" ]]; then + hsts_is_enabled="$(fn-plugin-property-get-default "openresty" "--global" "hsts" "true")" + fi + + echo "$hsts_is_enabled" +} + +fn-openresty-image() { + fn-plugin-property-get-default "openresty" "--global" "image" "dokku/openresty-docker-proxy:0.5.6" +} + +fn-openresty-letsencrypt-email() { + fn-plugin-property-get-default "openresty" "--global" "letsencrypt-email" "" +} + +fn-openresty-letsencrypt-server() { + fn-plugin-property-get-default "openresty" "--global" "letsencrypt-server" "https://acme-v02.api.letsencrypt.org/directory" +} + +fn-openresty-log-root() { + declare desc="get the openresty log root" + local OPENRESTY_LOG_ROOT="/var/log/openresty" + echo "$OPENRESTY_LOG_ROOT" +} + +fn-openresty-logs() { + declare desc="shows the logs for the openresty container" + declare TAIL="$1" NUM="$2" + local dokku_logs_args=("--tail" "$NUM") + + if [[ "$TAIL" == "true" ]]; then + dokku_logs_args+=("--follow") + fi + + "$DOCKER_BIN" logs openresty-openresty-1 "${dokku_logs_args[@]}" +} + +fn-openresty-logs-usage() { + declare desc="logs specific usage" + echo "Usage: dokku openresty:logs" + echo " display recent openresty log output" + echo "" + echo " -n, --num NUM # the number of lines to display" + echo " -t, --tail # continually stream logs" +} + +fn-openresty-proxy-buffer-size() { + declare desc="get the configured proxy buffer size" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "proxy-buffer-size" "$(fn-get-pagesize)" +} + +fn-openresty-proxy-buffering() { + declare desc="get the configured proxy buffering" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "proxy-buffering" "on" +} + +fn-openresty-proxy-buffers() { + declare desc="get the configured proxy buffers" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "proxy-buffers" "8 $(fn-get-pagesize)" +} + +fn-openresty-proxy-busy-buffers-size() { + declare desc="get the configured proxy busy buffers size" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "proxy-busy-buffers-size" "$(($(fn-get-pagesize) * 2))" +} + +fn-openresty-proxy-connect-timeout() { + declare desc="get the configured proxy connect timeout" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "proxy-connect-timeout" "60s" +} + +fn-openresty-proxy-read-timeout() { + declare desc="get the configured proxy read timeout" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "proxy-read-timeout" "60s" +} + +fn-openresty-proxy-send-timeout() { + declare desc="get the configured proxy send timeout" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "proxy-send-timeout" "60s" +} + +fn-openresty-send-timeout() { + declare desc="get the configured proxy send timeout" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "send-timeout" "60s" +} + +fn-openresty-template-compose-file() { + declare desc="templates out the compose file" + declare OUTPUT_PATH="$1" + local COMPOSE_TEMPLATE="$PLUGIN_AVAILABLE_PATH/openresty-vhosts/templates/compose.yml.sigil" + + CUSTOM_COMPOSE_TEMPLATE="$(plugn trigger openresty-template-source "$APP")" + if [[ -n "$CUSTOM_COMPOSE_TEMPLATE" ]]; then + COMPOSE_TEMPLATE="$CUSTOM_COMPOSE_TEMPLATE" + fi + + local SIGIL_PARAMS=(OPENRESTY_DATA_DIR="${DOKKU_LIB_ROOT}/data/openresty/.docker-letsencrypt" + OPENRESTY_IMAGE="$(fn-openresty-image)" + OPENRESTY_LETSENCRYPT_EMAIL="$(fn-openresty-letsencrypt-email)" + OPENRESTY_LETSENCRYPT_SERVER="$(fn-openresty-letsencrypt-server)") + + sigil -f "$COMPOSE_TEMPLATE" "${SIGIL_PARAMS[@]}" | cat -s >"$OUTPUT_PATH" +} + +fn-openresty-x-forwarded-for-value() { + declare desc="get the configured x-forwarded-for value" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "x-forwarded-for-value" "\$remote_addr" +} + +fn-openresty-x-forwarded-port-value() { + declare desc="get the configured x-forwarded-port value" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "x-forwarded-port-value" "\$server_port" +} + +fn-openresty-x-forwarded-proto-value() { + declare desc="get the configured x-forwarded-proto value" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "x-forwarded-proto-value" "\$scheme" +} + +fn-openresty-x-forwarded-ssl() { + declare desc="get the configured x-forwarded-ssl value" + declare APP="$1" + + fn-plugin-property-get-default "openresty" "$APP" "x-forwarded-ssl" "" +} diff --git a/plugins/openresty-vhosts/plugin.toml b/plugins/openresty-vhosts/plugin.toml new file mode 100644 index 000000000..f5898437d --- /dev/null +++ b/plugins/openresty-vhosts/plugin.toml @@ -0,0 +1,4 @@ +[plugin] +description = "dokku core openresty-vhosts plugin" +version = "0.30.11" +[plugin.config] diff --git a/plugins/openresty-vhosts/pre-restore b/plugins/openresty-vhosts/pre-restore new file mode 100755 index 000000000..8903a23ae --- /dev/null +++ b/plugins/openresty-vhosts/pre-restore @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/command-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +trigger-openresty-pre-restore() { + declare desc="pre-restore the openresty proxy" + declare trigger="install" + + if [[ "$(fn-plugin-property-get "openresty" "--global" "proxy-status")" != "started" ]]; then + return + fi + + if ! cmd-openresty-start; then + dokku_log_warn "Failed to restore openresty proxy, requests may not route as expected" + fi +} + +trigger-openresty-pre-restore "$@" diff --git a/plugins/openresty-vhosts/report b/plugins/openresty-vhosts/report new file mode 100755 index 000000000..05081e23a --- /dev/null +++ b/plugins/openresty-vhosts/report @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/command-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +cmd-openresty-report-single "$@" diff --git a/plugins/openresty-vhosts/subcommands/default b/plugins/openresty-vhosts/subcommands/default new file mode 100755 index 000000000..0d187aa0c --- /dev/null +++ b/plugins/openresty-vhosts/subcommands/default @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/help-functions" + +cmd-openresty-help "openresty:help" diff --git a/plugins/openresty-vhosts/subcommands/logs b/plugins/openresty-vhosts/subcommands/logs new file mode 100755 index 000000000..ff8a9fe66 --- /dev/null +++ b/plugins/openresty-vhosts/subcommands/logs @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/command-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +cmd-openresty-logs "$@" diff --git a/plugins/openresty-vhosts/subcommands/report b/plugins/openresty-vhosts/subcommands/report new file mode 100755 index 000000000..5bf0b5380 --- /dev/null +++ b/plugins/openresty-vhosts/subcommands/report @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/command-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +cmd-openresty-report "$@" diff --git a/plugins/openresty-vhosts/subcommands/set b/plugins/openresty-vhosts/subcommands/set new file mode 100755 index 000000000..079e0019e --- /dev/null +++ b/plugins/openresty-vhosts/subcommands/set @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +cmd-openresty-set() { + declare desc="set or clear an openresty property for an app" + declare cmd="openresty:set" + [[ "$1" == "$cmd" ]] && shift 1 + declare APP="$1" KEY="$2" VALUE="$3" + local VALID_KEYS=("access-log-format" "access-log-path" "bind-address-ipv4" "bind-address-ipv6" "client-max-body-size" "error-log-path" "hsts" "hsts-include-subdomains" "hsts-preload" "hsts-max-age" "image" "log-level" "letsencrypt-email" "letsencrypt-server" "proxy-read-timeout" "proxy-buffer-size" "proxy-buffering" "proxy-buffers" "proxy-busy-buffers-size" "x-forwarded-for-value" "x-forwarded-port-value" "x-forwarded-proto-value" "x-forwarded-ssl") + local GLOBAL_KEYS=("image" "log-level" "letsencrypt-email" "letsencrypt-server") + + [[ -z "$KEY" ]] && dokku_log_fail "No key specified" + + if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then + dokku_log_fail "Invalid key specified, valid keys include: access-log-format, access-log-path, bind-address-ipv4, bind-address-ipv6, client-max-body-size, error-log-path, hsts, hsts-include-subdomains, hsts-preload, hsts-max-age, image, log-level, letsencrypt-email, letsencrypt-server, proxy-read-timeout, proxy-buffer-size, proxy-buffering, proxy-buffers, proxy-busy-buffers-size, x-forwarded-for-value, x-forwarded-port-value, x-forwarded-proto-value, x-forwarded-ssl" + fi + + if ! fn-in-array "$KEY" "${GLOBAL_KEYS[@]}"; then + if [[ "$APP" == "--global" ]]; then + dokku_log_fail "The key '$KEY' cannot be set globally" + fi + verify_app_name "$APP" + fi + + if [[ -n "$VALUE" ]]; then + dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}" + fn-plugin-property-write "openresty" "$APP" "$KEY" "$VALUE" + else + dokku_log_info2_quiet "Unsetting ${KEY}" + fn-plugin-property-delete "openresty" "$APP" "$KEY" + fi +} + +cmd-openresty-set "$@" diff --git a/plugins/openresty-vhosts/subcommands/show-config b/plugins/openresty-vhosts/subcommands/show-config new file mode 100755 index 000000000..98b6e4b83 --- /dev/null +++ b/plugins/openresty-vhosts/subcommands/show-config @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/command-functions" + +cmd-openresty-show-config "$@" diff --git a/plugins/openresty-vhosts/subcommands/start b/plugins/openresty-vhosts/subcommands/start new file mode 100755 index 000000000..7f42b1984 --- /dev/null +++ b/plugins/openresty-vhosts/subcommands/start @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/command-functions" + +cmd-openresty-start "$@" diff --git a/plugins/openresty-vhosts/subcommands/stop b/plugins/openresty-vhosts/subcommands/stop new file mode 100755 index 000000000..308149693 --- /dev/null +++ b/plugins/openresty-vhosts/subcommands/stop @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/command-functions" + +cmd-openresty-stop "$@" diff --git a/plugins/openresty-vhosts/templates/compose.yml.sigil b/plugins/openresty-vhosts/templates/compose.yml.sigil new file mode 100644 index 000000000..36e2fd5e2 --- /dev/null +++ b/plugins/openresty-vhosts/templates/compose.yml.sigil @@ -0,0 +1,27 @@ +--- +version: "3.7" + +services: + openresty: + image: "{{ $.OPENRESTY_IMAGE }}" + + environment: + - OPENRESTY_LABEL_PREFIX=openresty. + {{ if $.OPENRESTY_LETSENCRYPT_EMAIL }} + - OPENRESTY_LETSENCRYPT_EMAIL={{ $.OPENRESTY_LETSENCRYPT_EMAIL }}" + - OPENRESTY_LETSENCRYPT_CA={{ $.OPENRESTY_LETSENCRYPT_SERVER }} + {{ end }} + + network_mode: bridge + + ports: + - "80:80" + {{ if $.OPENRESTY_LETSENCRYPT_EMAIL }} + - "443:443" + {{ end }} + + restart: unless-stopped + + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + - "{{ $.OPENRESTY_DATA_DIR }}:/etc/resty-auto-ssl" diff --git a/plugins/plugin/help-functions b/plugins/plugin/help-functions index 909045612..29087e0e7 100755 --- a/plugins/plugin/help-functions +++ b/plugins/plugin/help-functions @@ -29,11 +29,11 @@ fn-help-content() { cat <, Disable an installed plugin (third-party only) plugin:enable , Enable a previously disabled plugin - plugin:install [--core|git-url [--committish tag|branch|commit|--name custom-plugin-name]], Optionally download git-url (with custom tag/committish) & run install trigger for active plugins (or only core ones) + plugin:install [--core|--git-url] [--committish branch|commit|commit] [--name custom-plugin-name], Optionally download git-url (and pin to the specified branch/commit/tag) & run install trigger for active plugins (or only core ones) plugin:install-dependencies [--core], Run install-dependencies trigger for active plugins (or only core ones) plugin:list, Print active plugins plugin:trigger , Trigger an arbitrary plugin hook plugin:uninstall , Uninstall a plugin (third-party only) - plugin:update [name [committish]], Optionally update named plugin from git (with custom tag/committish) & run update trigger for active plugins + plugin:update [name [branch|commit|tag]], Optionally update named plugin from git (and pin to the specified branch/commit/tag) & run update trigger for active plugins help_content } diff --git a/plugins/ports/.gitignore b/plugins/ports/.gitignore new file mode 100644 index 000000000..3fc2c0d2f --- /dev/null +++ b/plugins/ports/.gitignore @@ -0,0 +1,8 @@ +/commands +/subcommands/* +/triggers/* +/triggers +/install +/ports-* +/post-* +/report diff --git a/plugins/ports/Makefile b/plugins/ports/Makefile new file mode 100644 index 000000000..6affadf3a --- /dev/null +++ b/plugins/ports/Makefile @@ -0,0 +1,6 @@ +SUBCOMMANDS = subcommands/list subcommands/add subcommands/clear subcommands/remove subcommands/set subcommands/report +TRIGGERS = triggers/install triggers/ports-clear triggers/ports-configure triggers/ports-get triggers/ports-get-available triggers/ports-get-property triggers/ports-set-detected triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-certs-remove triggers/post-certs-update triggers/post-delete triggers/report +BUILD = commands subcommands triggers +PLUGIN_NAME = ports + +include ../../common.mk diff --git a/plugins/ports/functions.go b/plugins/ports/functions.go new file mode 100644 index 000000000..7bea56ec3 --- /dev/null +++ b/plugins/ports/functions.go @@ -0,0 +1,418 @@ +package ports + +import ( + "errors" + "fmt" + "net" + "os" + "sort" + "strconv" + "strings" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/config" + "github.com/ryanuber/columnize" +) + +func addPortMaps(appName string, portMaps []PortMap) error { + allPortMaps := getPortMaps(appName) + allPortMaps = append(allPortMaps, portMaps...) + + return setPortMaps(appName, allPortMaps) +} + +func clearPorts(appName string) error { + if err := common.PropertyDelete("ports", appName, "map"); err != nil { + return err + } + + return common.PropertyDelete("ports", appName, "map-detected") +} + +func doesCertExist(appName string) bool { + certsExists, _ := common.PlugnTriggerOutputAsString("certs-exists", []string{appName}...) + if certsExists == "true" { + return true + } + + certsForce, _ := common.PlugnTriggerOutputAsString("certs-force", []string{appName}...) + return certsForce == "true" +} + +func filterAppPortMaps(appName string, scheme string, hostPort int) []PortMap { + var filteredPortMaps []PortMap + for _, portMap := range getPortMaps(appName) { + if portMap.Scheme == scheme && portMap.HostPort == hostPort { + filteredPortMaps = append(filteredPortMaps, portMap) + } + } + + return filteredPortMaps +} + +func getAvailablePort() int { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return 0 + } + + for { + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0 + } + defer l.Close() + + port := l.Addr().(*net.TCPAddr).Port + if port >= 1025 && port <= 65535 { + return port + } + } +} + +func getComputedProxyPort(appName string) int { + port := getProxyPort(appName) + if port == 0 { + port = getGlobalProxyPort() + } + + return port +} + +func getComputedProxySSLPort(appName string) int { + port := getProxySSLPort(appName) + if port == 0 { + port = getGlobalProxySSLPort() + } + + return port +} + +func getDetectedPortMaps(appName string) []PortMap { + basePort := getComputedProxyPort(appName) + if basePort == 0 { + basePort = 80 + } + defaultMapping := []PortMap{ + { + ContainerPort: 5000, + HostPort: basePort, + Scheme: "http", + }, + } + + portMaps := []PortMap{} + value, err := common.PropertyListGet("ports", appName, "map-detected") + if err == nil { + portMaps, _ = parsePortMapString(strings.Join(value, " ")) + } + + if len(portMaps) == 0 { + portMaps = defaultMapping + } + + if doesCertExist(appName) { + setSSLPort := false + baseSSLPort := getComputedProxySSLPort(appName) + if baseSSLPort == 0 { + baseSSLPort = 443 + } + + for _, portMap := range portMaps { + if portMap.Scheme != "http" || portMap.HostPort != 80 { + continue + } + + setSSLPort = true + portMaps = append(portMaps, PortMap{ + ContainerPort: portMap.ContainerPort, + HostPort: baseSSLPort, + Scheme: "https", + }) + } + + if !setSSLPort { + for i, portMap := range portMaps { + if portMap.Scheme != "http" { + continue + } + + portMaps[i].Scheme = "https" + } + } + } + + return portMaps +} + +func getGlobalProxyPort() int { + port := 0 + b, _ := common.PlugnTriggerOutput("config-get-global", []string{"DOKKU_PROXY_PORT"}...) + if intVar, err := strconv.Atoi(strings.TrimSpace(string(b[:]))); err == nil { + port = intVar + } + + return port +} + +func getGlobalProxySSLPort() int { + port := 0 + b, _ := common.PlugnTriggerOutput("config-get-global", []string{"DOKKU_PROXY_SSL_PORT"}...) + if intVar, err := strconv.Atoi(strings.TrimSpace(string(b[:]))); err == nil { + port = intVar + } + + return port +} + +func getPortMaps(appName string) []PortMap { + value, err := common.PropertyListGet("ports", appName, "map") + if err != nil { + return []PortMap{} + } + + portMaps, _ := parsePortMapString(strings.Join(value, " ")) + return portMaps +} + +func getProxyPort(appName string) int { + port := 0 + b, _ := common.PlugnTriggerOutput("config-get", []string{appName, "DOKKU_PROXY_PORT"}...) + if intVar, err := strconv.Atoi(strings.TrimSpace(string(b[:]))); err == nil { + port = intVar + } + + return port +} + +func getProxySSLPort(appName string) int { + port := 0 + b, _ := common.PlugnTriggerOutput("config-get", []string{appName, "DOKKU_PROXY_SSL_PORT"}...) + if intVar, err := strconv.Atoi(strings.TrimSpace(string(b[:]))); err == nil { + port = intVar + } + + return port +} + +func initializeProxyPort(appName string) error { + port := getProxyPort(appName) + if port != 0 { + return nil + } + + if isAppVhostEnabled(appName) { + port = getGlobalProxyPort() + } else { + common.LogInfo1("No port set, setting to random open high port") + port = getAvailablePort() + common.LogInfo1(fmt.Sprintf("Random port %d", port)) + } + + if port == 0 { + port = 80 + } + + if err := setProxyPort(appName, port); err != nil { + return err + } + return nil +} + +func initializeProxySSLPort(appName string) error { + port := getProxySSLPort(appName) + if port != 0 { + return nil + } + + if !doesCertExist(appName) { + return nil + } + + port = getGlobalProxySSLPort() + if port == 0 { + port = 443 + } + + if !isAppVhostEnabled(appName) { + common.LogInfo1("No ssl port set, setting to random open high port") + port = getAvailablePort() + } + + if err := setProxySSLPort(appName, port); err != nil { + return err + } + + return nil +} + +func inRange(value int, min int, max int) bool { + return min < value && value < max +} + +func isAppVhostEnabled(appName string) bool { + if err := common.PlugnTrigger("domains-vhost-enabled", []string{appName}...); err != nil { + return false + } + return true +} + +func listAppPortMaps(appName string) error { + portMaps := getPortMaps(appName) + + if len(portMaps) == 0 { + return errors.New("No port mappings configured for app") + } + + var lines []string + if os.Getenv("DOKKU_QUIET_OUTPUT") == "" { + lines = append(lines, "-----> scheme:host port:container port") + } + + for _, portMap := range portMaps { + lines = append(lines, portMap.String()) + } + + sort.Strings(lines) + common.LogInfo1Quiet(fmt.Sprintf("Port mappings for %s", appName)) + config := columnize.DefaultConfig() + config.Delim = ":" + config.Prefix = " " + config.Empty = "" + fmt.Println(columnize.Format(lines, config)) + return nil +} + +func parsePortMapString(stringPortMap string) ([]PortMap, error) { + var portMaps []PortMap + + for _, v := range strings.Split(strings.TrimSpace(stringPortMap), " ") { + parts := strings.SplitN(v, ":", 3) + if len(parts) == 1 { + hostPort, err := strconv.Atoi(v) + if err != nil { + return portMaps, fmt.Errorf("Invalid port map %s [err=%s]", v, err.Error()) + } + + if !inRange(hostPort, 0, 65536) { + return portMaps, fmt.Errorf("Invalid port map %s [hostPort=%d]", v, hostPort) + } + + portMaps = append(portMaps, PortMap{ + HostPort: hostPort, + Scheme: "__internal__", + }) + continue + } + + if len(parts) != 3 { + return portMaps, fmt.Errorf("Invalid port map %s [len=%d]", v, len(parts)) + } + + hostPort, err := strconv.Atoi(parts[1]) + if err != nil { + return portMaps, fmt.Errorf("Invalid port map %s [err=%s]", v, err.Error()) + } + + containerPort, err := strconv.Atoi(parts[2]) + if err != nil { + return portMaps, fmt.Errorf("Invalid port map %s [err=%s]", v, err.Error()) + } + + if !inRange(hostPort, 0, 65536) { + return portMaps, fmt.Errorf("Invalid port map %s [hostPort=%d]", v, hostPort) + } + + if !inRange(containerPort, 0, 65536) { + return portMaps, fmt.Errorf("Invalid port map %s [containerPort=%d]", v, containerPort) + } + + portMaps = append(portMaps, PortMap{ + ContainerPort: containerPort, + HostPort: hostPort, + Scheme: parts[0], + }) + } + + return uniquePortMaps(portMaps), nil +} + +func removePortMaps(appName string, portMaps []PortMap) error { + toRemove := map[string]bool{} + toRemoveByPort := map[int]bool{} + + for _, portMap := range portMaps { + if portMap.AllowsPersistence() { + toRemoveByPort[portMap.HostPort] = true + continue + } + toRemove[portMap.String()] = true + } + + var toSet []PortMap + for _, portMap := range getPortMaps(appName) { + if toRemove[portMap.String()] { + continue + } + + if toRemoveByPort[portMap.HostPort] { + continue + } + + toSet = append(toSet, portMap) + } + + if len(toSet) == 0 { + return common.PropertyDelete("ports", appName, "map") + } + + return setPortMaps(appName, toSet) +} + +func setPortMaps(appName string, portMaps []PortMap) error { + var value []string + for _, portMap := range uniquePortMaps(portMaps) { + if portMap.AllowsPersistence() { + continue + } + + value = append(value, portMap.String()) + } + + sort.Strings(value) + return common.PropertyListWrite("ports", appName, "map", value) +} + +func setProxyPort(appName string, port int) error { + return common.EnvWrap(func() error { + entries := map[string]string{ + "DOKKU_PROXY_PORT": fmt.Sprint(port), + } + return config.SetMany(appName, entries, false) + }, map[string]string{"DOKKU_QUIET_OUTPUT": "1"}) +} + +func setProxySSLPort(appName string, port int) error { + return common.EnvWrap(func() error { + entries := map[string]string{ + "DOKKU_PROXY_SSL_PORT": fmt.Sprint(port), + } + return config.SetMany(appName, entries, false) + }, map[string]string{"DOKKU_QUIET_OUTPUT": "1"}) +} + +func uniquePortMaps(portMaps []PortMap) []PortMap { + var unique []PortMap + existingPortMaps := map[string]bool{} + + for _, portMap := range portMaps { + if existingPortMaps[portMap.String()] { + continue + } + + existingPortMaps[portMap.String()] = true + unique = append(unique, portMap) + } + + return unique +} diff --git a/plugins/ports/go.mod b/plugins/ports/go.mod new file mode 100644 index 000000000..c1f98fcbe --- /dev/null +++ b/plugins/ports/go.mod @@ -0,0 +1,23 @@ +module github.com/dokku/dokku/plugins/ports + +go 1.19 + +require ( + github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000 + github.com/dokku/dokku/plugins/config v0.0.0-00010101000000-000000000000 + github.com/ryanuber/columnize v2.1.2+incompatible + github.com/spf13/pflag v1.0.5 +) + +require ( + github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect + github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 // indirect + github.com/joho/godotenv v1.2.0 // indirect + github.com/otiai10/copy v1.12.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.10.0 // indirect +) + +replace github.com/dokku/dokku/plugins/common => ../common + +replace github.com/dokku/dokku/plugins/config => ../config diff --git a/plugins/ports/go.sum b/plugins/ports/go.sum new file mode 100644 index 000000000..763fb9de1 --- /dev/null +++ b/plugins/ports/go.sum @@ -0,0 +1,22 @@ +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 h1:HHUr4P/aKh4quafGxDT9LDasjGdlGkzLbfmmrlng3kA= +github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27/go.mod h1:VQx0hjo2oUeQkQUET7wRwradO6f+fN5jzXgB/zROxxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/joho/godotenv v1.2.0 h1:vGTvz69FzUFp+X4/bAkb0j5BoLC+9bpqTWY8mjhA9pc= +github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY= +github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= +github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= +github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= +github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/plugins/ports/plugin.toml b/plugins/ports/plugin.toml new file mode 100644 index 000000000..a596e24c9 --- /dev/null +++ b/plugins/ports/plugin.toml @@ -0,0 +1,4 @@ +[plugin] +description = "dokku core ports plugin" +version = "0.30.11" +[plugin.config] diff --git a/plugins/ports/proxy-configure-ports b/plugins/ports/proxy-configure-ports new file mode 100755 index 000000000..649106588 --- /dev/null +++ b/plugins/ports/proxy-configure-ports @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +trigger-ports-proxy-configure-ports() { + declare desc="ports proxy-configure-ports plugin trigger" + declare trigger="proxy-configure-ports" + declare APP="$1" + + dokku_log_warn "Deprecated: please use the 'ports-configure' plugin trigger instead" + plugn trigger ports-configure "$APP" +} + +trigger-ports-proxy-configure-ports "$@" diff --git a/plugins/ports/proxy.go b/plugins/ports/proxy.go new file mode 100644 index 000000000..1918a8c53 --- /dev/null +++ b/plugins/ports/proxy.go @@ -0,0 +1,21 @@ +package ports + +import ( + "fmt" +) + +// PortMap is a struct that contains a scheme:host-port:container-port mapping +type PortMap struct { + ContainerPort int + HostPort int + Scheme string +} + +func (p PortMap) String() string { + return fmt.Sprintf("%s:%d:%d", p.Scheme, p.HostPort, p.ContainerPort) +} + +// AllowsPersistence returns true if the port map is not to be persisted +func (p PortMap) AllowsPersistence() bool { + return p.Scheme == "__internal__" +} diff --git a/plugins/ports/report.go b/plugins/ports/report.go new file mode 100644 index 000000000..8031478b7 --- /dev/null +++ b/plugins/ports/report.go @@ -0,0 +1,47 @@ +package ports + +import ( + "strings" + + "github.com/dokku/dokku/plugins/common" +) + +// ReportSingleApp is an internal function that displays the ports report for one or more apps +func ReportSingleApp(appName string, format string, infoFlag string) error { + if err := common.VerifyAppName(appName); err != nil { + return err + } + + flags := map[string]common.ReportFunc{ + "--ports-map": reportPortMap, + "--ports-map-detected": reportPortMapDetected, + } + + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + + trimPrefix := false + uppercaseFirstCharacter := true + infoFlags := common.CollectReport(appName, infoFlag, flags) + return common.ReportSingleApp("ports", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) +} + +func reportPortMap(appName string) string { + var portMaps []string + for _, portMap := range getPortMaps(appName) { + portMaps = append(portMaps, portMap.String()) + } + + return strings.Join(portMaps, " ") +} + +func reportPortMapDetected(appName string) string { + var portMaps []string + for _, portMap := range getDetectedPortMaps(appName) { + portMaps = append(portMaps, portMap.String()) + } + + return strings.Join(portMaps, " ") +} diff --git a/plugins/ports/src/commands/commands.go b/plugins/ports/src/commands/commands.go new file mode 100644 index 000000000..9231d27bf --- /dev/null +++ b/plugins/ports/src/commands/commands.go @@ -0,0 +1,60 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strconv" + "strings" + + "github.com/dokku/dokku/plugins/common" +) + +const ( + helpHeader = `Usage: dokku ports[:COMMAND] + +Manage ports for an app + +Additional commands:` + + helpContent = ` + ports:list , List port mappings for app + ports:add [::...], Add port mappings to an app + ports:clear , Clear all port mappings for an app + ports:remove [|::...], Remove specific port mappings from an app + ports:set [::...], Set port mappings for an app + ports:report [] [], Displays a ports report for one or more apps +` +) + +func main() { + flag.Usage = usage + flag.Parse() + + cmd := flag.Arg(0) + switch cmd { + case "ports", "ports:help": + usage() + case "help": + command := common.NewShellCmd(fmt.Sprintf("ps -o command= %d", os.Getppid())) + command.ShowOutput = false + output, err := command.Output() + + if err == nil && strings.Contains(string(output), "--all") { + fmt.Println(helpContent) + } else { + fmt.Print("\n ports, Manage ports for an app\n") + } + default: + dokkuNotImplementExitCode, err := strconv.Atoi(os.Getenv("DOKKU_NOT_IMPLEMENTED_EXIT")) + if err != nil { + fmt.Println("failed to retrieve DOKKU_NOT_IMPLEMENTED_EXIT environment variable") + dokkuNotImplementExitCode = 10 + } + os.Exit(dokkuNotImplementExitCode) + } +} + +func usage() { + common.CommandUsage(helpHeader, helpContent) +} diff --git a/plugins/ports/src/subcommands/subcommands.go b/plugins/ports/src/subcommands/subcommands.go new file mode 100644 index 000000000..14f97ed94 --- /dev/null +++ b/plugins/ports/src/subcommands/subcommands.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/ports" + + flag "github.com/spf13/pflag" +) + +// main entrypoint to all subcommands +func main() { + parts := strings.Split(os.Args[0], "/") + subcommand := parts[len(parts)-1] + + var err error + switch subcommand { + case "list": + args := flag.NewFlagSet("ports:list", flag.ExitOnError) + args.Parse(os.Args[2:]) + appName := args.Arg(0) + err = ports.CommandList(appName) + case "add": + args := flag.NewFlagSet("ports:add", flag.ExitOnError) + args.Parse(os.Args[2:]) + appName := args.Arg(0) + _, portMaps := common.ShiftString(args.Args()) + err = ports.CommandAdd(appName, portMaps) + case "clear": + args := flag.NewFlagSet("ports:clear", flag.ExitOnError) + args.Parse(os.Args[2:]) + appName := args.Arg(0) + err = ports.CommandClear(appName) + case "remove": + args := flag.NewFlagSet("ports:remove", flag.ExitOnError) + args.Parse(os.Args[2:]) + appName := args.Arg(0) + _, portMaps := common.ShiftString(args.Args()) + err = ports.CommandRemove(appName, portMaps) + case "set": + args := flag.NewFlagSet("ports:set", flag.ExitOnError) + args.Parse(os.Args[2:]) + appName := args.Arg(0) + _, portMaps := common.ShiftString(args.Args()) + err = ports.CommandSet(appName, portMaps) + case "report": + args := flag.NewFlagSet("ports:report", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") + osArgs, infoFlag, flagErr := common.ParseReportArgs("ports", os.Args[2:]) + if flagErr == nil { + args.Parse(osArgs) + appName := args.Arg(0) + err = ports.CommandReport(appName, *format, infoFlag) + } + default: + err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/ports/src/triggers/triggers.go b/plugins/ports/src/triggers/triggers.go new file mode 100644 index 000000000..d0e7cfe25 --- /dev/null +++ b/plugins/ports/src/triggers/triggers.go @@ -0,0 +1,69 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/ports" +) + +// main entrypoint to all triggers +func main() { + parts := strings.Split(os.Args[0], "/") + trigger := parts[len(parts)-1] + flag.Parse() + + var err error + switch trigger { + case "install": + err = ports.TriggerInstall() + case "ports-clear": + appName := flag.Arg(0) + err = ports.TriggerPortsClear(appName) + case "ports-configure": + appName := flag.Arg(0) + err = ports.TriggerPortsConfigure(appName) + case "ports-get": + appName := flag.Arg(0) + err = ports.TriggerPortsGet(appName) + case "ports-get-available": + err = ports.TriggerPortsGetAvailable() + case "ports-get-property": + appName := flag.Arg(0) + property := flag.Arg(1) + err = ports.TriggerPortsGetProperty(appName, property) + case "ports-set-detected": + appName := flag.Arg(0) + appName, portMapString := common.ShiftString(flag.Args()) + err = ports.TriggerPortsSetDetected(appName, strings.Join(portMapString, " ")) + case "post-app-clone-setup": + oldAppName := flag.Arg(0) + newAppName := flag.Arg(1) + err = ports.TriggerPostAppCloneSetup(oldAppName, newAppName) + case "post-app-rename-setup": + oldAppName := flag.Arg(0) + newAppName := flag.Arg(1) + err = ports.TriggerPostAppRenameSetup(oldAppName, newAppName) + case "post-certs-remove": + appName := flag.Arg(0) + err = ports.TriggerPostCertsRemove(appName) + case "post-certs-update": + appName := flag.Arg(0) + err = ports.TriggerPostCertsUpdate(appName) + case "post-delete": + appName := flag.Arg(0) + err = ports.TriggerPostDelete(appName) + case "report": + appName := flag.Arg(0) + err = ports.ReportSingleApp(appName, "", "") + default: + err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/ports/subcommands.go b/plugins/ports/subcommands.go new file mode 100644 index 000000000..ce34e37a8 --- /dev/null +++ b/plugins/ports/subcommands.go @@ -0,0 +1,114 @@ +package ports + +import ( + "errors" + "strings" + + "github.com/dokku/dokku/plugins/common" +) + +// CommandList is a cmd wrapper to list port mappings for an app +func CommandList(appName string) error { + if err := common.VerifyAppName(appName); err != nil { + return err + } + + return listAppPortMaps(appName) +} + +// CommandAdd adds port mappings to an app +func CommandAdd(appName string, portMapStrings []string) error { + if err := common.VerifyAppName(appName); err != nil { + return err + } + + if len(portMapStrings) == 0 { + return errors.New("No port mapping specified") + } + + portMaps, err := parsePortMapString(strings.Join(portMapStrings, " ")) + if err != nil { + return err + } + + if err := addPortMaps(appName, portMaps); err != nil { + return err + } + + return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "add"}...) +} + +// CommandClear clears all port mappings for an app +func CommandClear(appName string) error { + if err := common.VerifyAppName(appName); err != nil { + return err + } + + if err := clearPorts(appName); err != nil { + return err + } + + return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "clear"}...) +} + +// CommandRemove removes specific port mappings from an app +func CommandRemove(appName string, portMapStrings []string) error { + if err := common.VerifyAppName(appName); err != nil { + return err + } + + if len(portMapStrings) == 0 { + return errors.New("No port mapping specified") + } + + portMaps, err := parsePortMapString(strings.Join(portMapStrings, " ")) + if err != nil { + return err + } + + if err := removePortMaps(appName, portMaps); err != nil { + return err + } + + return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "remove"}...) +} + +// CommandSet sets port mappings for an app +func CommandSet(appName string, portMapStrings []string) error { + if err := common.VerifyAppName(appName); err != nil { + return err + } + + if len(portMapStrings) == 0 { + return errors.New("No port mapping specified") + } + + portMaps, err := parsePortMapString(strings.Join(portMapStrings, " ")) + if err != nil { + return err + } + + if err := setPortMaps(appName, portMaps); err != nil { + return err + } + + return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "set"}...) +} + +// CommandReport displays a ports report for one or more apps +func CommandReport(appName string, format string, infoFlag string) error { + if len(appName) == 0 { + apps, err := common.DokkuApps() + if err != nil { + return err + } + for _, appName := range apps { + if err := ReportSingleApp(appName, format, infoFlag); err != nil { + return err + } + } + return nil + } + + return ReportSingleApp(appName, format, infoFlag) +} diff --git a/plugins/ports/triggers.go b/plugins/ports/triggers.go new file mode 100644 index 000000000..e872aea2d --- /dev/null +++ b/plugins/ports/triggers.go @@ -0,0 +1,223 @@ +package ports + +import ( + "fmt" + "sort" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/config" +) + +// TriggerInstall migrates the ports config to properties +func TriggerInstall() error { + if err := common.PropertySetup("ports"); err != nil { + return fmt.Errorf("Unable to install the ports plugin: %s", err.Error()) + } + + apps, err := common.UnfilteredDokkuApps() + if err != nil { + return nil + } + + for _, appName := range apps { + if common.PropertyExists("ports", appName, "map") { + continue + } + + common.LogInfo1(fmt.Sprintf("Migrating DOKKU_PROXY_PORT_MAP to ports map property for %s", appName)) + portMapString := config.GetWithDefault(appName, "DOKKU_PROXY_PORT_MAP", "") + portMaps, _ := parsePortMapString(portMapString) + + propertyValue := []string{} + for _, portMap := range portMaps { + propertyValue = append(propertyValue, portMap.String()) + } + + if err := common.PropertyListWrite("ports", appName, "map", propertyValue); err != nil { + return err + } + + if err := config.UnsetMany(appName, []string{"DOKKU_PROXY_PORT_MAP"}, false); err != nil { + return err + } + } + + return nil +} + +// TriggerPortsClear removes all ports for the specified app +func TriggerPortsClear(appName string) error { + return clearPorts(appName) +} + +// TriggerPortsConfigure ensures we have a port mapping +func TriggerPortsConfigure(appName string) error { + if err := initializeProxyPort(appName); err != nil { + return err + } + + if err := initializeProxySSLPort(appName); err != nil { + return err + } + + return nil +} + +// TriggerPortsGet prints out the port mapping for a given app +func TriggerPortsGet(appName string) error { + portMaps := getPortMaps(appName) + if len(portMaps) == 0 { + portMaps = getDetectedPortMaps(appName) + } + + for _, portMap := range portMaps { + if portMap.AllowsPersistence() { + continue + } + + fmt.Println(portMap) + } + + return nil +} + +// TriggerPortsGetAvailable prints out an available port greater than 1024 +func TriggerPortsGetAvailable() error { + port := getAvailablePort() + if port > 0 { + common.Log(fmt.Sprint(port)) + } + + return nil +} + +// TriggerPortsGetProperty writes the ports key to stdout for a given app container +func TriggerPortsGetProperty(appName string, key string) error { + if key == "proxy-port" { + fmt.Println(getComputedProxyPort(appName)) + return nil + } + + if key == "proxy-ssl-port" { + fmt.Println(getComputedProxySSLPort(appName)) + return nil + } + + fmt.Println(common.PropertyGet("ports", appName, key)) + return nil +} + +// TriggerPortsSetDetected writes out detected ports +func TriggerPortsSetDetected(appName string, portMapString string) error { + portMaps, _ := parsePortMapString(portMapString) + + var value []string + for _, portMap := range uniquePortMaps(portMaps) { + if portMap.AllowsPersistence() { + continue + } + + value = append(value, portMap.String()) + } + + sort.Strings(value) + return common.PropertyListWrite("ports", appName, "map-detected", value) +} + +// TriggerPostAppCloneSetup creates new ports files +func TriggerPostAppCloneSetup(oldAppName string, newAppName string) error { + err := common.PropertyClone("ports", oldAppName, newAppName) + if err != nil { + return err + } + + return nil +} + +// TriggerPostAppRenameSetup renames ports files +func TriggerPostAppRenameSetup(oldAppName string, newAppName string) error { + if err := common.PropertyClone("ports", oldAppName, newAppName); err != nil { + return err + } + + if err := common.PropertyDestroy("ports", oldAppName); err != nil { + return err + } + + return nil +} + +// TriggerPostCertsRemove unsets port config vars after SSL cert is added +func TriggerPostCertsRemove(appName string) error { + keys := []string{"DOKKU_PROXY_SSL_PORT"} + if err := config.UnsetMany(appName, keys, false); err != nil { + return err + } + + return removePortMaps(appName, filterAppPortMaps(appName, "https", 443)) +} + +// TriggerPostCertsUpdate sets port config vars after SSL cert is added +func TriggerPostCertsUpdate(appName string) error { + port := config.GetWithDefault(appName, "DOKKU_PROXY_PORT", "") + sslPort := config.GetWithDefault(appName, "DOKKU_PROXY_SSL_PORT", "") + portMaps := getPortMaps(appName) + + toUnset := []string{} + if port == "80" { + toUnset = append(toUnset, "DOKKU_PROXY_PORT") + } + if sslPort == "443" { + toUnset = append(toUnset, "DOKKU_PROXY_SSL_PORT") + } + + if len(toUnset) > 0 { + if err := config.UnsetMany(appName, toUnset, false); err != nil { + return err + } + } + + var http80Ports []PortMap + for _, portMap := range portMaps { + if portMap.Scheme == "http" && portMap.HostPort == 80 { + http80Ports = append(http80Ports, portMap) + } + } + + if len(http80Ports) > 0 { + var https443Ports []PortMap + for _, portMap := range portMaps { + if portMap.Scheme == "https" && portMap.HostPort == 443 { + https443Ports = append(https443Ports, portMap) + } + } + + if err := removePortMaps(appName, https443Ports); err != nil { + return err + } + + var toAdd []PortMap + for _, portMap := range http80Ports { + toAdd = append(toAdd, PortMap{ + Scheme: "https", + HostPort: 443, + ContainerPort: portMap.ContainerPort, + }) + } + + if err := addPortMaps(appName, toAdd); err != nil { + return err + } + } + + return nil +} + +// TriggerPostDelete is the ports post-delete plugin trigger +func TriggerPostDelete(appName string) error { + if err := common.PropertyDestroy("ports", appName); err != nil { + common.LogWarn(err.Error()) + } + + return nil +} diff --git a/plugins/proxy/.gitignore b/plugins/proxy/.gitignore index 3b20f2e1c..7d9f06e65 100644 --- a/plugins/proxy/.gitignore +++ b/plugins/proxy/.gitignore @@ -6,4 +6,4 @@ /install /post-* /report -!/proxy-configure-ports + diff --git a/plugins/proxy/Makefile b/plugins/proxy/Makefile index 21cc25c91..6b127937f 100644 --- a/plugins/proxy/Makefile +++ b/plugins/proxy/Makefile @@ -1,5 +1,5 @@ SUBCOMMANDS = subcommands/build-config subcommands/clear-config subcommands/disable subcommands/enable subcommands/ports subcommands/ports-add subcommands/ports-clear subcommands/ports-remove subcommands/ports-set subcommands/report subcommands/set -TRIGGERS = triggers/proxy-is-enabled triggers/proxy-type triggers/post-certs-remove triggers/post-certs-update triggers/report +TRIGGERS = triggers/proxy-is-enabled triggers/proxy-type triggers/report BUILD = commands subcommands triggers PLUGIN_NAME = proxy diff --git a/plugins/proxy/functions.go b/plugins/proxy/functions.go index bc12f32a9..4b5467f50 100644 --- a/plugins/proxy/functions.go +++ b/plugins/proxy/functions.go @@ -1,192 +1,9 @@ package proxy import ( - "errors" - "fmt" - "os" - "sort" - "strconv" - "strings" - - "github.com/dokku/dokku/plugins/common" "github.com/dokku/dokku/plugins/config" - "github.com/ryanuber/columnize" ) -func addProxyPorts(appName string, proxyPortMap []PortMap) error { - allPortMaps := getProxyPortMap(appName) - allPortMaps = append(allPortMaps, proxyPortMap...) - - return setProxyPorts(appName, allPortMaps) -} - -func filterAppProxyPorts(appName string, scheme string, hostPort int) []PortMap { - var filteredProxyMaps []PortMap - for _, portMap := range getProxyPortMap(appName) { - if portMap.Scheme == scheme && portMap.HostPort == hostPort { - filteredProxyMaps = append(filteredProxyMaps, portMap) - } - } - - return filteredProxyMaps -} - func getAppProxyType(appName string) string { return config.GetWithDefault(appName, "DOKKU_APP_PROXY_TYPE", "nginx") } - -func getProxyPortMap(appName string) []PortMap { - value := config.GetWithDefault(appName, "DOKKU_PROXY_PORT_MAP", "") - portMaps, _ := parseProxyPortMapString(value) - return portMaps -} - -func inRange(value int, min int, max int) bool { - return min < value && value < max -} - -func listAppProxyPorts(appName string) error { - proxyPortMap := getProxyPortMap(appName) - - if len(proxyPortMap) == 0 { - return errors.New("No port mappings configured for app") - } - - var lines []string - if os.Getenv("DOKKU_QUIET_OUTPUT") == "" { - lines = append(lines, "-----> scheme:host port:container port") - } - - for _, portMap := range proxyPortMap { - lines = append(lines, portMap.String()) - } - - sort.Strings(lines) - common.LogInfo1Quiet(fmt.Sprintf("Port mappings for %s", appName)) - config := columnize.DefaultConfig() - config.Delim = ":" - config.Prefix = " " - config.Empty = "" - fmt.Println(columnize.Format(lines, config)) - return nil -} - -func parseProxyPortMapString(stringPortMap string) ([]PortMap, error) { - var proxyPortMap []PortMap - - for _, v := range strings.Split(strings.TrimSpace(stringPortMap), " ") { - parts := strings.SplitN(v, ":", 3) - if len(parts) == 1 { - hostPort, err := strconv.Atoi(v) - if err != nil { - return proxyPortMap, fmt.Errorf("Invalid port map %s [err=%s]", v, err.Error()) - } - - if !inRange(hostPort, 0, 65536) { - return proxyPortMap, fmt.Errorf("Invalid port map %s [hostPort=%d]", v, hostPort) - } - - proxyPortMap = append(proxyPortMap, PortMap{ - HostPort: hostPort, - Scheme: "__internal__", - }) - continue - } - - if len(parts) != 3 { - return proxyPortMap, fmt.Errorf("Invalid port map %s [len=%d]", v, len(parts)) - } - - hostPort, err := strconv.Atoi(parts[1]) - if err != nil { - return proxyPortMap, fmt.Errorf("Invalid port map %s [err=%s]", v, err.Error()) - } - - containerPort, err := strconv.Atoi(parts[2]) - if err != nil { - return proxyPortMap, fmt.Errorf("Invalid port map %s [err=%s]", v, err.Error()) - } - - if !inRange(hostPort, 0, 65536) { - return proxyPortMap, fmt.Errorf("Invalid port map %s [hostPort=%d]", v, hostPort) - } - - if !inRange(containerPort, 0, 65536) { - return proxyPortMap, fmt.Errorf("Invalid port map %s [containerPort=%d]", v, containerPort) - } - - proxyPortMap = append(proxyPortMap, PortMap{ - ContainerPort: containerPort, - HostPort: hostPort, - Scheme: parts[0], - }) - } - - return uniqueProxyPortMap(proxyPortMap), nil -} - -func removeProxyPorts(appName string, proxyPortMap []PortMap) error { - toRemove := map[string]bool{} - toRemoveByPort := map[int]bool{} - - for _, portMap := range proxyPortMap { - if portMap.AllowsPersistence() { - toRemoveByPort[portMap.HostPort] = true - continue - } - toRemove[portMap.String()] = true - } - - var toSet []PortMap - for _, portMap := range getProxyPortMap(appName) { - if toRemove[portMap.String()] { - continue - } - - if toRemoveByPort[portMap.HostPort] { - continue - } - - toSet = append(toSet, portMap) - } - - if len(toSet) == 0 { - keys := []string{"DOKKU_PROXY_PORT_MAP"} - return config.UnsetMany(appName, keys, false) - } - - return setProxyPorts(appName, toSet) -} - -func setProxyPorts(appName string, proxyPortMap []PortMap) error { - var value []string - for _, portMap := range uniqueProxyPortMap(proxyPortMap) { - if portMap.AllowsPersistence() { - continue - } - - value = append(value, portMap.String()) - } - - sort.Strings(value) - entries := map[string]string{ - "DOKKU_PROXY_PORT_MAP": strings.Join(value, " "), - } - return config.SetMany(appName, entries, false) -} - -func uniqueProxyPortMap(proxyPortMap []PortMap) []PortMap { - var unique []PortMap - existingPortMaps := map[string]bool{} - - for _, portMap := range proxyPortMap { - if existingPortMaps[portMap.String()] { - continue - } - - existingPortMaps[portMap.String()] = true - unique = append(unique, portMap) - } - - return unique -} diff --git a/plugins/proxy/go.mod b/plugins/proxy/go.mod index 5da99b906..46c474818 100644 --- a/plugins/proxy/go.mod +++ b/plugins/proxy/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000 github.com/dokku/dokku/plugins/config v0.0.0-00010101000000-000000000000 - github.com/ryanuber/columnize v2.1.2+incompatible + github.com/dokku/dokku/plugins/ports v0.0.0-00010101000000-000000000000 github.com/spf13/pflag v1.0.5 ) @@ -14,6 +14,7 @@ require ( github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 // indirect github.com/joho/godotenv v1.2.0 // indirect github.com/otiai10/copy v1.12.0 // indirect + github.com/ryanuber/columnize v2.1.2+incompatible // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.10.0 // indirect ) @@ -21,3 +22,5 @@ require ( replace github.com/dokku/dokku/plugins/common => ../common replace github.com/dokku/dokku/plugins/config => ../config + +replace github.com/dokku/dokku/plugins/ports => ../ports diff --git a/plugins/proxy/proxy-configure-ports b/plugins/proxy/proxy-configure-ports deleted file mode 100755 index 21c94a69e..000000000 --- a/plugins/proxy/proxy-configure-ports +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/config/functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -trigger-proxy-proxy-configure-ports() { - declare desc="proxy proxy-configure-ports plugin trigger" - declare trigger="proxy-configure-ports" - declare APP="$1" - local RAW_TCP_PORTS="$(get_app_raw_tcp_ports "$APP")" - local DOKKU_PROXY_PORT=$(config_get "$APP" DOKKU_PROXY_PORT) - local DOKKU_PROXY_SSL_PORT=$(config_get "$APP" DOKKU_PROXY_SSL_PORT) - local DOKKU_PROXY_PORT_MAP=$(config_get "$APP" DOKKU_PROXY_PORT_MAP) - local IS_APP_VHOST_ENABLED=true - local UPSTREAM_PORT="5000" - - plugn trigger domains-vhost-enabled "$APP" 2>/dev/null || IS_APP_VHOST_ENABLED=false - - if [[ -z "$DOKKU_PROXY_PORT" ]] && [[ -z "$RAW_TCP_PORTS" ]]; then - if [[ "$IS_APP_VHOST_ENABLED" == "false" ]]; then - dokku_log_info1 "No proxy port set, setting to random open high port" - local PROXY_PORT=$(get_available_port) - else - local PROXY_PORT=$(config_get --global DOKKU_PROXY_PORT) - PROXY_PORT=${PROXY_PORT:=80} - fi - DOKKU_QUIET_OUTPUT=1 config_set --no-restart "$APP" DOKKU_PROXY_PORT="$PROXY_PORT" - fi - if [[ -z "$DOKKU_PROXY_SSL_PORT" ]]; then - if [[ "$(plugn trigger certs-exists "$APP")" == "true" ]]; then - local PROXY_SSL_PORT=$(config_get --global DOKKU_PROXY_SSL_PORT) - PROXY_SSL_PORT=${PROXY_SSL_PORT:=443} - if [[ -z "$RAW_TCP_PORTS" ]] && [[ "$IS_APP_VHOST_ENABLED" == "false" ]]; then - dokku_log_info1 "No proxy ssl port set, setting to random open high port" - PROXY_SSL_PORT=$(get_available_port) - fi - DOKKU_QUIET_OUTPUT=1 config_set --no-restart "$APP" DOKKU_PROXY_SSL_PORT="$PROXY_SSL_PORT" - fi - fi - if [[ -z "$DOKKU_PROXY_PORT_MAP" ]]; then - if [[ -n "$RAW_TCP_PORTS" ]]; then - local RAW_TCP_PORT - for RAW_TCP_PORT in $RAW_TCP_PORTS; do - local PROXY_PORT_MAP+=" http:${RAW_TCP_PORT}:${RAW_TCP_PORT} " - done - else - local PROXY_PORT=${PROXY_PORT:-$DOKKU_PROXY_PORT} - local PROXY_SSL_PORT=${PROXY_SSL_PORT:-$DOKKU_PROXY_SSL_PORT} - [[ -f "$DOKKU_ROOT/$APP/PORT.web.1" ]] && local UPSTREAM_PORT="$(<"$DOKKU_ROOT/$APP/PORT.web.1")" - if [[ -n "$PROXY_PORT" ]] && [[ -n "$PROXY_SSL_PORT" ]]; then - local PROXY_PORT_MAP+=" http:${PROXY_PORT}:$UPSTREAM_PORT https:${PROXY_SSL_PORT}:$UPSTREAM_PORT " - elif [[ -n "$PROXY_PORT" ]]; then - local PROXY_PORT_MAP+=" http:${PROXY_PORT}:$UPSTREAM_PORT " - fi - fi - if [[ -n "$PROXY_PORT_MAP" ]]; then - local PROXY_PORT_MAP="$(echo "$PROXY_PORT_MAP" | xargs)" - local PROXY_PORT_MAP+=" $(merge_dedupe_list "$(remove_val_from_list "$PORT_MAP" "$DOKKU_PROXY_PORT_MAP" " ")" " ") " - DOKKU_QUIET_OUTPUT=1 config_set --no-restart "$APP" DOKKU_PROXY_PORT_MAP="$PROXY_PORT_MAP" - fi - fi -} -trigger-proxy-proxy-configure-ports "$@" diff --git a/plugins/proxy/proxy.go b/plugins/proxy/proxy.go index ec87bf48c..26cfee2ff 100644 --- a/plugins/proxy/proxy.go +++ b/plugins/proxy/proxy.go @@ -1,8 +1,6 @@ package proxy import ( - "fmt" - "github.com/dokku/dokku/plugins/common" "github.com/dokku/dokku/plugins/config" ) @@ -11,22 +9,6 @@ import ( // and defaults to -1 (false) const RunInSerial = 0 -// PortMap is a struct that contains a scheme:host-port:container-port mapping -type PortMap struct { - ContainerPort int - HostPort int - Scheme string -} - -func (p PortMap) String() string { - return fmt.Sprintf("%s:%d:%d", p.Scheme, p.HostPort, p.ContainerPort) -} - -// AllowsPersistence returns true if the port map is not to be persisted -func (p PortMap) AllowsPersistence() bool { - return p.Scheme == "__internal__" -} - // BuildConfig rebuilds the proxy config for the specified app func BuildConfig(appName string) error { return common.PlugnTrigger("proxy-build-config", []string{appName}...) diff --git a/plugins/proxy/report.go b/plugins/proxy/report.go index bf4c5c917..5db7792a2 100644 --- a/plugins/proxy/report.go +++ b/plugins/proxy/report.go @@ -1,8 +1,6 @@ package proxy import ( - "strings" - "github.com/dokku/dokku/plugins/common" ) @@ -13,9 +11,8 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { } flags := map[string]common.ReportFunc{ - "--proxy-enabled": reportEnabled, - "--proxy-type": reportType, - "--proxy-port-map": reportPortMap, + "--proxy-enabled": reportEnabled, + "--proxy-type": reportType, } flagKeys := []string{} @@ -41,12 +38,3 @@ func reportEnabled(appName string) string { func reportType(appName string) string { return getAppProxyType(appName) } - -func reportPortMap(appName string) string { - var proxyPortMap []string - for _, portMap := range getProxyPortMap(appName) { - proxyPortMap = append(proxyPortMap, portMap.String()) - } - - return strings.Join(proxyPortMap, " ") -} diff --git a/plugins/proxy/src/commands/commands.go b/plugins/proxy/src/commands/commands.go index e2f6c016a..129cc2820 100644 --- a/plugins/proxy/src/commands/commands.go +++ b/plugins/proxy/src/commands/commands.go @@ -22,11 +22,6 @@ Additional commands:` proxy:clear-config [--all|], Clears config for a given app proxy:disable , Disable proxy for app proxy:enable , Enable proxy for app - proxy:ports , List proxy port mappings for app - proxy:ports-add [::...], Add proxy port mappings to an app - proxy:ports-clear , Clear all proxy port mappings for an app - proxy:ports-remove [|::...], Remove specific proxy port mappings from an app - proxy:ports-set [::...], Set proxy port mappings for an app proxy:report [] [], Displays a proxy report for one or more apps proxy:set , Set proxy type for app ` diff --git a/plugins/proxy/src/subcommands/subcommands.go b/plugins/proxy/src/subcommands/subcommands.go index c25db395c..0dd972857 100644 --- a/plugins/proxy/src/subcommands/subcommands.go +++ b/plugins/proxy/src/subcommands/subcommands.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/ports" "github.com/dokku/dokku/plugins/proxy" flag "github.com/spf13/pflag" @@ -49,30 +50,35 @@ func main() { args := flag.NewFlagSet("proxy:ports", flag.ExitOnError) args.Parse(os.Args[2:]) appName := args.Arg(0) - err = proxy.CommandPorts(appName) + common.LogWarn("Deprecated: Use 'ports:list' instead") + err = ports.CommandList(appName) case "ports-add": args := flag.NewFlagSet("proxy:ports-add", flag.ExitOnError) args.Parse(os.Args[2:]) appName := args.Arg(0) _, portMaps := common.ShiftString(args.Args()) - err = proxy.CommandPortsAdd(appName, portMaps) + common.LogWarn("Deprecated: Use 'ports:add' instead") + err = ports.CommandAdd(appName, portMaps) case "ports-clear": args := flag.NewFlagSet("proxy:ports-clear", flag.ExitOnError) args.Parse(os.Args[2:]) appName := args.Arg(0) - err = proxy.CommandPortsClear(appName) + common.LogWarn("Deprecated: Use 'ports:clear' instead") + err = ports.CommandClear(appName) case "ports-remove": args := flag.NewFlagSet("proxy:ports-remove", flag.ExitOnError) args.Parse(os.Args[2:]) appName := args.Arg(0) _, portMaps := common.ShiftString(args.Args()) - err = proxy.CommandPortsRemove(appName, portMaps) + common.LogWarn("Deprecated: Use 'ports:remove' instead") + err = ports.CommandRemove(appName, portMaps) case "ports-set": args := flag.NewFlagSet("proxy:ports-set", flag.ExitOnError) args.Parse(os.Args[2:]) appName := args.Arg(0) _, portMaps := common.ShiftString(args.Args()) - err = proxy.CommandPortsSet(appName, portMaps) + common.LogWarn("Deprecated: Use 'ports:set' instead") + err = ports.CommandSet(appName, portMaps) case "report": args := flag.NewFlagSet("proxy:report", flag.ExitOnError) format := args.String("format", "stdout", "format: [ stdout | json ]") diff --git a/plugins/proxy/src/triggers/triggers.go b/plugins/proxy/src/triggers/triggers.go index 11c5bd277..713e1976d 100644 --- a/plugins/proxy/src/triggers/triggers.go +++ b/plugins/proxy/src/triggers/triggers.go @@ -24,12 +24,6 @@ func main() { case "proxy-type": appName := flag.Arg(0) err = proxy.TriggerProxyType(appName) - case "post-certs-remove": - appName := flag.Arg(0) - err = proxy.TriggerPostCertsRemove(appName) - case "post-certs-update": - appName := flag.Arg(0) - err = proxy.TriggerPostCertsUpdate(appName) case "report": appName := flag.Arg(0) err = proxy.ReportSingleApp(appName, "", "") diff --git a/plugins/proxy/subcommands.go b/plugins/proxy/subcommands.go index 1dcb66558..58a57a870 100644 --- a/plugins/proxy/subcommands.go +++ b/plugins/proxy/subcommands.go @@ -2,7 +2,6 @@ package proxy import ( "errors" - "strings" "github.com/dokku/dokku/plugins/common" "github.com/dokku/dokku/plugins/config" @@ -60,95 +59,6 @@ func CommandEnable(appName string, allApps bool, parallelCount int) error { return Enable(appName) } -// CommandPorts is a cmd wrapper to list proxy port mappings for an app -func CommandPorts(appName string) error { - if err := common.VerifyAppName(appName); err != nil { - return err - } - - return listAppProxyPorts(appName) -} - -// CommandPortsAdd adds proxy port mappings to an app -func CommandPortsAdd(appName string, portMaps []string) error { - if err := common.VerifyAppName(appName); err != nil { - return err - } - - if len(portMaps) == 0 { - return errors.New("No port mapping specified") - } - - proxyPortMap, err := parseProxyPortMapString(strings.Join(portMaps, " ")) - if err != nil { - return err - } - - if err := addProxyPorts(appName, proxyPortMap); err != nil { - return err - } - - return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "add"}...) -} - -// CommandPortsClear clears all proxy port mappings for an app -func CommandPortsClear(appName string) error { - if err := common.VerifyAppName(appName); err != nil { - return err - } - - keys := []string{"DOKKU_PROXY_PORT_MAP"} - if err := config.UnsetMany(appName, keys, false); err != nil { - return err - } - - return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "clear"}...) -} - -// CommandPortsRemove removes specific proxy port mappings from an app -func CommandPortsRemove(appName string, portMaps []string) error { - if err := common.VerifyAppName(appName); err != nil { - return err - } - - if len(portMaps) == 0 { - return errors.New("No port mapping specified") - } - - proxyPortMap, err := parseProxyPortMapString(strings.Join(portMaps, " ")) - if err != nil { - return err - } - - if err := removeProxyPorts(appName, proxyPortMap); err != nil { - return err - } - - return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "remove"}...) -} - -// CommandPortsSet sets proxy port mappings for an app -func CommandPortsSet(appName string, portMaps []string) error { - if err := common.VerifyAppName(appName); err != nil { - return err - } - - if len(portMaps) == 0 { - return errors.New("No port mapping specified") - } - - proxyPortMap, err := parseProxyPortMapString(strings.Join(portMaps, " ")) - if err != nil { - return err - } - - if err := setProxyPorts(appName, proxyPortMap); err != nil { - return err - } - - return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "set"}...) -} - // CommandReport displays a proxy report for one or more apps func CommandReport(appName string, format string, infoFlag string) error { if len(appName) == 0 { diff --git a/plugins/proxy/triggers.go b/plugins/proxy/triggers.go index 626f95a30..49c0fef06 100644 --- a/plugins/proxy/triggers.go +++ b/plugins/proxy/triggers.go @@ -2,8 +2,6 @@ package proxy import ( "fmt" - - "github.com/dokku/dokku/plugins/config" ) // TriggerProxyIsEnabled prints true or false depending on whether the proxy is enabled @@ -24,69 +22,3 @@ func TriggerProxyType(appName string) error { return nil } - -// TriggerPostCertsRemove unsets port config vars after SSL cert is added -func TriggerPostCertsRemove(appName string) error { - keys := []string{"DOKKU_PROXY_SSL_PORT"} - if err := config.UnsetMany(appName, keys, false); err != nil { - return err - } - - return removeProxyPorts(appName, filterAppProxyPorts(appName, "https", 443)) -} - -// TriggerPostCertsUpdate sets port config vars after SSL cert is added -func TriggerPostCertsUpdate(appName string) error { - port := config.GetWithDefault(appName, "DOKKU_PROXY_PORT", "") - sslPort := config.GetWithDefault(appName, "DOKKU_PROXY_SSL_PORT", "") - proxyPortMap := getProxyPortMap(appName) - - toUnset := []string{} - if port == "80" { - toUnset = append(toUnset, "DOKKU_PROXY_PORT") - } - if sslPort == "443" { - toUnset = append(toUnset, "DOKKU_PROXY_SSL_PORT") - } - - if len(toUnset) > 0 { - if err := config.UnsetMany(appName, toUnset, false); err != nil { - return err - } - } - - var http80Ports []PortMap - for _, portMap := range proxyPortMap { - if portMap.Scheme == "http" && portMap.HostPort == 80 { - http80Ports = append(http80Ports, portMap) - } - } - - if len(http80Ports) > 0 { - var https443Ports []PortMap - for _, portMap := range proxyPortMap { - if portMap.Scheme == "https" && portMap.HostPort == 443 { - https443Ports = append(https443Ports, portMap) - } - } - - if err := removeProxyPorts(appName, https443Ports); err != nil { - return err - } - - var toAdd []PortMap - for _, portMap := range http80Ports { - toAdd = append(toAdd, PortMap{ - Scheme: "https", - HostPort: 443, - ContainerPort: portMap.ContainerPort, - }) - } - - if err := addProxyPorts(appName, toAdd); err != nil { - return err - } - } - - return nil -} diff --git a/plugins/ps/Makefile b/plugins/ps/Makefile index dbf6e3221..fa9285662 100644 --- a/plugins/ps/Makefile +++ b/plugins/ps/Makefile @@ -1,5 +1,5 @@ SUBCOMMANDS = subcommands/inspect subcommands/rebuild subcommands/report subcommands/restart subcommands/restore subcommands/retire subcommands/scale subcommands/set subcommands/start subcommands/stop -TRIGGERS = triggers/app-restart triggers/core-post-deploy triggers/core-post-extract triggers/install triggers/post-app-clone triggers/post-app-clone-setup triggers/post-app-rename triggers/post-app-rename-setup triggers/post-create triggers/post-delete triggers/post-stop triggers/pre-deploy triggers/procfile-get-command triggers/ps-can-scale triggers/ps-current-scale triggers/ps-set-scale triggers/report +TRIGGERS = triggers/app-restart triggers/core-post-deploy triggers/core-post-extract triggers/install triggers/post-app-clone triggers/post-app-clone-setup triggers/post-app-rename triggers/post-app-rename-setup triggers/post-create triggers/post-delete triggers/post-stop triggers/pre-deploy triggers/procfile-get-command triggers/procfile-exists triggers/ps-can-scale triggers/ps-current-scale triggers/ps-set-scale triggers/report BUILD = commands subcommands triggers PLUGIN_NAME = ps diff --git a/plugins/ps/src/triggers/triggers.go b/plugins/ps/src/triggers/triggers.go index c4170d297..d0acb17b2 100644 --- a/plugins/ps/src/triggers/triggers.go +++ b/plugins/ps/src/triggers/triggers.go @@ -64,6 +64,9 @@ func main() { processType := flag.Arg(1) port := common.ToInt(flag.Arg(2), 5000) err = ps.TriggerProcfileGetCommand(appName, processType, port) + case "procfile-exists": + appName := flag.Arg(0) + err = ps.TriggerProcfileExists(appName) case "ps-can-scale": appName := flag.Arg(0) canScale := common.ToBool(flag.Arg(1)) diff --git a/plugins/ps/triggers.go b/plugins/ps/triggers.go index 160274d22..a36810c0b 100644 --- a/plugins/ps/triggers.go +++ b/plugins/ps/triggers.go @@ -1,6 +1,7 @@ package ps import ( + "errors" "fmt" "os" "path" @@ -259,6 +260,15 @@ func TriggerPreDeploy(appName string, imageTag string) error { return nil } +// TriggerProcfileExists checks if a procfile exists +func TriggerProcfileExists(appName string) error { + if hasProcfile(appName) { + return nil + } + + return errors.New("Procfile does not exist") +} + // TriggerProcfileGetCommand fetches a command from the procfile func TriggerProcfileGetCommand(appName string, processType string, port int) error { if !hasProcfile(appName) { diff --git a/plugins/repo/.gitignore b/plugins/repo/.gitignore index 4e8c51e8a..e1f4bd87f 100644 --- a/plugins/repo/.gitignore +++ b/plugins/repo/.gitignore @@ -2,5 +2,4 @@ /subcommands/* /triggers/* /triggers -/pre-delete -/post-stack-set +/post-* diff --git a/plugins/repo/Makefile b/plugins/repo/Makefile index fd3d74547..0af946983 100644 --- a/plugins/repo/Makefile +++ b/plugins/repo/Makefile @@ -1,5 +1,5 @@ SUBCOMMANDS = subcommands/gc subcommands/purge-cache -TRIGGERS = triggers/post-stack-set triggers/pre-delete +TRIGGERS = triggers/post-stack-set triggers/post-delete BUILD = commands subcommands triggers PLUGIN_NAME = repo diff --git a/plugins/repo/go.mod b/plugins/repo/go.mod index 952593012..bafa0d555 100644 --- a/plugins/repo/go.mod +++ b/plugins/repo/go.mod @@ -4,14 +4,12 @@ go 1.19 require ( github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000 - github.com/dokku/dokku/plugins/config v0.0.0-00010101000000-000000000000 github.com/spf13/pflag v1.0.5 ) require ( github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 // indirect - github.com/joho/godotenv v1.2.0 // indirect github.com/otiai10/copy v1.12.0 // indirect github.com/ryanuber/columnize v2.1.2+incompatible // indirect golang.org/x/sync v0.3.0 // indirect diff --git a/plugins/repo/go.sum b/plugins/repo/go.sum index 763fb9de1..3a1e768e2 100644 --- a/plugins/repo/go.sum +++ b/plugins/repo/go.sum @@ -3,8 +3,6 @@ github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcju github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 h1:HHUr4P/aKh4quafGxDT9LDasjGdlGkzLbfmmrlng3kA= github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27/go.mod h1:VQx0hjo2oUeQkQUET7wRwradO6f+fN5jzXgB/zROxxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/joho/godotenv v1.2.0 h1:vGTvz69FzUFp+X4/bAkb0j5BoLC+9bpqTWY8mjhA9pc= -github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY= github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= diff --git a/plugins/repo/repo.go b/plugins/repo/repo.go index 04e7cdea1..1f459fe1e 100644 --- a/plugins/repo/repo.go +++ b/plugins/repo/repo.go @@ -6,28 +6,45 @@ import ( "strings" "github.com/dokku/dokku/plugins/common" - "github.com/dokku/dokku/plugins/config" ) +// PurgeCacheFailed wraps error to allow returning the correct exit code +type PurgeCacheFailed struct { + exitCode int +} + +// ExitCode returns an exit code to use in case this error bubbles +// up into an os.Exit() call +func (err *PurgeCacheFailed) ExitCode() int { + return err.exitCode +} + +// Error returns a standard non-existent app error +func (err *PurgeCacheFailed) Error() string { + return fmt.Sprintf("failed to purge cache, exit code %d", err.exitCode) +} + // PurgeCache deletes the contents of the build cache stored in the repository func PurgeCache(appName string) error { - cacheDir := strings.Join([]string{common.AppRoot(appName), "cache"}, "/") - cacheHostDir := strings.Join([]string{common.AppHostRoot(appName), "cache"}, "/") - dokkuGlobalRunArgs := common.MustGetEnv("DOKKU_GLOBAL_RUN_ARGS") - image := config.GetWithDefault(appName, "DOKKU_IMAGE", os.Getenv("DOKKU_IMAGE")) - if info, _ := os.Stat(cacheDir); info != nil && info.IsDir() { - dockerLabelArgs := fmt.Sprintf("--label=com.dokku.app-name=%s", appName) - purgeCacheCmd := common.NewShellCmd(strings.Join([]string{ - common.DockerBin(), - "container", - "run", "--rm", dockerLabelArgs, dokkuGlobalRunArgs, - "-v", strings.Join([]string{cacheHostDir, ":/cache"}, ""), image, - `find /cache -depth -mindepth 1 -maxdepth 1 -exec rm -Rf {} ;`}, " ")) - purgeCacheCmd.Execute() - err := os.MkdirAll(cacheDir, 0644) - if err != nil { - return err + containerIDs, _ := common.DockerFilterContainers([]string{ + fmt.Sprintf("label=com.dokku.app-name=%v", appName), + "label=com.dokku.image-stage=build", + }) + if len(containerIDs) > 0 { + common.DockerRemoveContainers(containerIDs) + } + purgeCacheCmd := common.NewShellCmd(strings.Join([]string{ + common.DockerBin(), + "volume", + "rm", "-f", fmt.Sprintf("cache-%s", appName)}, " ")) + purgeCacheCmd.ShowOutput = false + purgeCacheCmd.Command.Stderr = os.Stderr + if !purgeCacheCmd.Execute() { + exitCode := 1 + if purgeCacheCmd.ExitError != nil { + exitCode = purgeCacheCmd.ExitError.ExitCode() } + return &PurgeCacheFailed{exitCode} } return nil diff --git a/plugins/repo/src/triggers/triggers.go b/plugins/repo/src/triggers/triggers.go index 0dd1c21d0..63c4b468b 100644 --- a/plugins/repo/src/triggers/triggers.go +++ b/plugins/repo/src/triggers/triggers.go @@ -21,7 +21,7 @@ func main() { case "post-stack-set": appName := flag.Arg(0) err = repo.PurgeCache(appName) - case "pre-delete": + case "post-delete": appName := flag.Arg(0) err = repo.PurgeCache(appName) default: diff --git a/plugins/scheduler-docker-local/.gitignore b/plugins/scheduler-docker-local/.gitignore new file mode 100644 index 000000000..637119bb8 --- /dev/null +++ b/plugins/scheduler-docker-local/.gitignore @@ -0,0 +1,3 @@ +/triggers/* +/triggers +/cron-* diff --git a/plugins/scheduler-docker-local/Makefile b/plugins/scheduler-docker-local/Makefile new file mode 100644 index 000000000..09f04eabb --- /dev/null +++ b/plugins/scheduler-docker-local/Makefile @@ -0,0 +1,5 @@ +TRIGGERS = triggers/cron-write +BUILD = triggers +PLUGIN_NAME = scheduler-docker-local + +include ../../common.mk diff --git a/plugins/scheduler-docker-local/bin/scheduler-deploy-process b/plugins/scheduler-docker-local/bin/scheduler-deploy-process index 27d59889e..a088ee9df 100755 --- a/plugins/scheduler-docker-local/bin/scheduler-deploy-process +++ b/plugins/scheduler-docker-local/bin/scheduler-deploy-process @@ -24,7 +24,6 @@ main() { # Disable the container restart policy "$DOCKER_BIN" container update --restart=no "$cid" &>/dev/null || true - # shellcheck disable=SC2086 "$DOCKER_BIN" container stop $DOCKER_STOP_TIME_ARG "$cid" &>/dev/null done fi diff --git a/plugins/scheduler-docker-local/bin/scheduler-deploy-process-container b/plugins/scheduler-docker-local/bin/scheduler-deploy-process-container index 772ee88c5..511e52348 100755 --- a/plugins/scheduler-docker-local/bin/scheduler-deploy-process-container +++ b/plugins/scheduler-docker-local/bin/scheduler-deploy-process-container @@ -33,14 +33,16 @@ main() { local DOKKU_PORT="" if [[ "$PROC_TYPE" == "web" ]]; then - ports=($(plugn trigger network-compute-ports "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH" "$CONTAINER_INDEX")) - for p in "${ports[@]}"; do - if [[ ! "$p" =~ .*udp.* ]]; then - DOKKU_PORT=${DOKKU_PORT:="$p"} + ports=($(plugn trigger ports-get "$APP")) + for port_map in "${ports[@]}"; do + local scheme="$(echo "$port_map" | cut -d':' -f1)" + local container_port="$(echo "$port_map" | cut -d':' -f3)" + if [[ "$scheme" != "udp" ]]; then + DOKKU_PORT="${DOKKU_PORT:="$container_port"}" fi if [[ "$DOKKU_NETWORK_BIND_ALL" == "true" ]]; then - DOCKER_ARGS+=" -p $p" + DOCKER_ARGS+=" -p $container_port" fi done fi @@ -59,7 +61,6 @@ main() { "$DOCKER_BIN" container start "$cid" >/dev/null || true ipaddr=$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$cid") - port=$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$cid" "$DOKKU_HEROKUISH") kill_new() { declare desc="wrapper function to kill newly started app container" @@ -81,7 +82,7 @@ main() { trap "kill_new $cid $PROC_TYPE $CONTAINER_INDEX" INT TERM EXIT if [[ "$DOKKU_CHECKS_DISABLED" == "false" ]]; then dokku_log_verbose "Attempting pre-flight checks ($PROC_TYPE.$CONTAINER_INDEX)" - plugn trigger check-deploy "$APP" "$cid" "$PROC_TYPE" "$port" "$ipaddr" "$CONTAINER_INDEX" + plugn trigger check-deploy "$APP" "$cid" "$PROC_TYPE" "$DOKKU_PORT" "$ipaddr" "$CONTAINER_INDEX" fi trap - INT TERM EXIT @@ -94,7 +95,7 @@ main() { # now using the new container [[ -n "$cid" ]] && echo "$cid" >"$DOKKU_CONTAINER_ID_FILE" [[ -n "$ipaddr" ]] && plugn trigger network-write-ipaddr "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$ipaddr" - [[ -n "$port" ]] && plugn trigger network-write-port "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$port" + [[ -n "$DOKKU_PORT" ]] && plugn trigger network-write-port "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$DOKKU_PORT" # cleanup pre-migration files rm -f "$DOKKU_ROOT/$APP/CONTAINER" "$DOKKU_ROOT/$APP/IP" "$DOKKU_ROOT/$APP/PORT" diff --git a/plugins/scheduler-docker-local/check-deploy b/plugins/scheduler-docker-local/check-deploy index 310b5c9a3..cdf2c7610 100755 --- a/plugins/scheduler-docker-local/check-deploy +++ b/plugins/scheduler-docker-local/check-deploy @@ -38,11 +38,13 @@ set -eo pipefail source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/checks/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" +source "$PLUGIN_AVAILABLE_PATH/scheduler-docker-local/internal-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 content local DOKKU_SCHEDULER=$(get_app_scheduler "$APP") if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then @@ -52,9 +54,6 @@ trigger-scheduler-docker-local-check-deploy() { 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]} @@ -76,16 +75,42 @@ trigger-scheduler-docker-local-check-deploy() { # 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 CHECKS_FILENAME="$(fn-scheduler-docker-local-get-process-specific-checks-file-path "$APP")" 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 + + local TMP_APP_JSON_OUTPUT=$(mktemp "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX") + trap "rm -rf '$TMP_APP_JSON_OUTPUT' >/dev/null" RETURN INT TERM EXIT + + plugn trigger app-json-get-content "$APP" >"$TMP_APP_JSON_OUTPUT" + if [[ -s "${CHECKS_FILENAME}" ]]; then + # 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 + + dokku_log_warn "Deprecated: Usage of the CHECKS file is deprecated in favor of healthchecks in app.json" + dokku_log_warn "Please move your healthchecks to app.json." + content="$(docker-container-healthchecker convert "$CHECKS_FILENAME" --app-json "$TMP_APP_JSON_OUTPUT" --pretty)" + echo "$content" >"$TMP_APP_JSON_OUTPUT" + fi checks_check_deploy_cleanup() { - declare desc="cleans up CHECK_DEPLOY_TMP_WORK_DIR and print container output" + declare desc="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" @@ -94,141 +119,40 @@ trigger-scheduler-docker-local-check-deploy() { } 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 + local DOKKU_DEFAULT_CHECKS_WAIT="${DOKKU_DEFAULT_CHECKS_WAIT:-10}" + content="$(docker-container-healthchecker add "$DOKKU_APP_CONTAINER_TYPE" --app-json "$TMP_APP_JSON_OUTPUT" --if-empty --pretty --uptime "$DOKKU_DEFAULT_CHECKS_WAIT")" + echo "$content" >"$TMP_APP_JSON_OUTPUT" - # 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" + local FAILEDCHECKS=0 + local SSL="$DOKKU_ROOT/$APP/tls" + declare -a ARG_ARRAY + ARG_ARRAY+=("--app-json") + ARG_ARRAY+=("$TMP_APP_JSON_OUTPUT") + ARG_ARRAY+=("--process-type") + ARG_ARRAY+=("$DOKKU_APP_CONTAINER_TYPE") - ! (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 + if [[ -e "$SSL/server.crt" && -e "$SSL/server.key" ]]; then + ARG_ARRAY+=("--header") + ARG_ARRAY+=("X-Forwarded-Proto: https") 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" - } + if [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then + ARG_ARRAY+=("--ip-address") + ARG_ARRAY+=("$DOKKU_APP_LISTEN_IP") + fi - template_checks - CHECKS_FILENAME="$NEW_CHECKS_FILENAME" + if [[ -n "$DOKKU_APP_LISTEN_PORT" ]]; then + ARG_ARRAY+=("--port") + ARG_ARRAY+=("$DOKKU_APP_LISTEN_PORT") + fi - # 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 # - # shellcheck disable=SC1001 - [[ -z "$CHECK_URL" || "$CHECK_URL" =~ ^\# ]] && continue - # Ignore if it's not a URL in a supported format - # shellcheck disable=SC1001 - ! [[ "$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 - # shellcheck disable=SC2086 - 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 + docker-container-healthchecker check "$DOKKU_APP_CONTAINER_ID" "${ARG_ARRAY[@]}" || FAILEDCHECKS="$?" if [[ $FAILEDCHECKS -gt 0 ]]; then - dokku_log_fail "Could not start due to $FAILEDCHECKS failed checks ($DOKKU_APP_CONTAINER_TYPE.$CONTAINER_INDEX)" - exit 1 + "$DOCKER_BIN" container update --restart=no "$DOKKU_APP_CONTAINER_ID" &>/dev/null || true + "$DOCKER_BIN" container stop "$DOKKU_APP_CONTAINER_ID" || true + dokku_log_warn "Could not start due to $FAILEDCHECKS failed checks ($DOKKU_APP_CONTAINER_TYPE.$CONTAINER_INDEX)" + return 1 fi trap - EXIT diff --git a/plugins/scheduler-docker-local/core-post-deploy b/plugins/scheduler-docker-local/core-post-deploy index 5100ebf84..592bd0af1 100755 --- a/plugins/scheduler-docker-local/core-post-deploy +++ b/plugins/scheduler-docker-local/core-post-deploy @@ -15,6 +15,18 @@ trigger-scheduler-docker-local-core-post-deploy() { return fi + # move over the checks file appropriately + checks_file="${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/CHECKS" + process_specific_checks_file="${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/CHECKS.$DOKKU_PID" + if [[ -f "$process_specific_checks_file" ]]; then + mv "$process_specific_checks_file" "$checks_file" + elif [[ -f "$process_specific_checks_file.missing" ]]; then + rm -f "$process_specific_checks_file.missing" + if [[ -f "$checks_file" ]]; then + rm -f "$checks_file" + fi + fi + dokku_log_info1 "Renaming containers" local PROCTYPES="$(plugn trigger ps-current-scale "$APP" | awk -F '=' '{ print $1 }' | xargs)" local CONTAINER_FILES="$(find "$DOKKU_ROOT/$APP" -maxdepth 1 -name "CONTAINER.*" -printf "%f\n" 2>/dev/null | sort -t . -k 3 -n | xargs)" @@ -37,7 +49,7 @@ trigger-scheduler-docker-local-core-post-deploy() { local NAME="$APP.$DYNO" local CURRENT_CONTAINER_ID="$(<"$container")" # TODO: Ensure these are from the current service - local PREVIOUS_CIDS=$("$DOCKER_BIN" container list --all --quiet --filter name="^.?$NAME\$" | xargs) || true + local PREVIOUS_CIDS=$("$DOCKER_BIN" container ls --all --quiet --filter name="^.?$NAME\$" | xargs) || true if [[ -n $PREVIOUS_CIDS ]]; then dokku_log_verbose_quiet "Found previous container(s) ($PREVIOUS_CIDS) named $NAME" # in case $PREVIOUS_CIDS has more than one entry @@ -48,7 +60,7 @@ trigger-scheduler-docker-local-core-post-deploy() { if [[ "$PREVIOUS_CONTAINER_STATUS" != "dead" ]]; then local CONTAINER_DATE_NAME="$NAME.$(date +%s)" dokku_log_verbose_quiet "Renaming container ($cid) ${NAME} to $CONTAINER_DATE_NAME" - "$DOCKER_BIN" container rename "$NAME" "$CONTAINER_DATE_NAME" >/dev/null 2>&1 || dokku_log_warn "Unable to rename container" + "$DOCKER_BIN" container rename "$NAME" "$CONTAINER_DATE_NAME" &>/dev/null || dokku_log_warn "Unable to rename container" fi done fi diff --git a/plugins/scheduler-docker-local/core-post-extract b/plugins/scheduler-docker-local/core-post-extract new file mode 100755 index 000000000..f8809136f --- /dev/null +++ b/plugins/scheduler-docker-local/core-post-extract @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_AVAILABLE_PATH/config/functions" +source "$PLUGIN_AVAILABLE_PATH/scheduler-docker-local/internal-functions" + +fn-scheduler-docker-local-copy-from-image() { + declare APP="$1" IMAGE_NAME="$2" CHECKS_PATH="$3" + + mkdir -p "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP" + rm -f "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/CHECKS."* + copy_from_image "$IMAGE_NAME" "$CHECKS_PATH" "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/CHECKS.$DOKKU_PID" 2>/dev/null || true + if [[ ! -f "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/CHECKS.$DOKKU_PID" ]]; then + touch "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/CHECKS.$DOKKU_PID.missing" + fi +} + +fn-scheduler-docker-local-copy-from-directory() { + declare APP="$1" SOURCECODE_WORK_DIR="$2" CHECKS_PATH="$3" + + pushd "$SOURCECODE_WORK_DIR" >/dev/null + mkdir -p "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP" + + if [[ -z "$CHECKS_PATH" ]]; then + touch "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/CHECKS.$DOKKU_PID.missing" + return + fi + + if [[ ! -f "$CHECKS_PATH" ]]; then + touch "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/CHECKS.$DOKKU_PID.missing" + return + fi + + rm -f "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/CHECKS."* + cp -f "$CHECKS_PATH" "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/CHECKS.$DOKKU_PID" + popd &>/dev/null || pushd "/tmp" >/dev/null +} + +trigger-scheduler-docker-local-core-post-extract() { + declare desc="scheduler-docker-local post-extract plugin trigger" + declare trigger="post-extract" + declare APP="$1" SOURCECODE_WORK_DIR="$2" + local CHECKS_PATH="CHECKS" CHECKS_FILENAME="${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/CHECKS.$DOKKU_PID" + local app_source_image + + app_source_image="$(plugn trigger git-get-property "$APP" "source-image")" + if [[ -n "$app_source_image" ]]; then + fn-scheduler-docker-local-copy-from-image "$APP" "$app_source_image" "$CHECKS_PATH" + else + fn-scheduler-docker-local-copy-from-directory "$APP" "$SOURCECODE_WORK_DIR" "$CHECKS_PATH" + fi + + if [[ -f "$CHECKS_FILENAME" ]]; then + local TMP_CHECKS_FILE=$(mktemp "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX") + trap "rm -rf '$TMP_CHECKS_FILE' >/dev/null" RETURN INT TERM EXIT + + template_checks() { + declare desc="templates out the checks file" + declare APP="$1" CHECKS_FILENAME="$2" + eval "$(config_export app "$APP" --format exports --merged)" + sigil -f "$CHECKS_FILENAME" | cat -s >"$TMP_CHECKS_FILE" + mv "$TMP_CHECKS_FILE" "$CHECKS_FILENAME" + } + + template_checks "$APP" "$CHECKS_FILENAME" + fi +} + +trigger-scheduler-docker-local-core-post-extract "$@" diff --git a/plugins/scheduler-docker-local/functions.go b/plugins/scheduler-docker-local/functions.go new file mode 100644 index 000000000..89d1f49e4 --- /dev/null +++ b/plugins/scheduler-docker-local/functions.go @@ -0,0 +1,163 @@ +package schedulerdockerlocal + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "text/template" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/cron" + "golang.org/x/sync/errgroup" + + base36 "github.com/multiformats/go-base36" +) + +func deleteCrontab() error { + command := common.NewShellCmd("sudo /usr/bin/crontab -l -u dokku") + command.ShowOutput = false + if !command.Execute() { + return nil + } + + command = common.NewShellCmd("sudo /usr/bin/crontab -r -u dokku") + command.ShowOutput = false + out, err := command.CombinedOutput() + if err != nil { + return fmt.Errorf("Unable to remove schedule file: %v", string(out)) + } + + common.LogInfo1("Removed") + return nil +} + +func generateCronEntries() ([]cron.TemplateCommand, error) { + apps, _ := common.UnfilteredDokkuApps() + + g := new(errgroup.Group) + results := make(chan []cron.TemplateCommand, len(apps)+1) + for _, appName := range apps { + appName := appName + g.Go(func() error { + scheduler := common.GetAppScheduler(appName) + if scheduler != "docker-local" { + results <- []cron.TemplateCommand{} + return nil + } + + c, err := cron.FetchCronEntries(appName) + if err != nil { + results <- []cron.TemplateCommand{} + return err + } + + results <- c + return nil + }) + } + + g.Go(func() error { + commands := []cron.TemplateCommand{} + b, _ := common.PlugnTriggerOutput("cron-entries", "docker-local") + for _, line := range strings.Split(strings.TrimSpace(string(b[:])), "\n") { + if strings.TrimSpace(line) == "" { + results <- []cron.TemplateCommand{} + return nil + } + + parts := strings.Split(line, ";") + if len(parts) != 2 && len(parts) != 3 { + results <- []cron.TemplateCommand{} + return fmt.Errorf("Invalid injected cron task: %v", line) + } + + id := base36.EncodeToStringLc([]byte(strings.Join(parts, ";;;"))) + command := cron.TemplateCommand{ + ID: id, + Schedule: parts[0], + AltCommand: parts[1], + } + if len(parts) == 3 { + command.LogFile = parts[2] + } + commands = append(commands, command) + } + results <- commands + return nil + }) + + err := g.Wait() + close(results) + + commands := []cron.TemplateCommand{} + if err != nil { + return commands, err + } + + for result := range results { + c := result + if len(c) > 0 { + commands = append(commands, c...) + } + } + + return commands, nil +} + +func writeCronEntries() error { + commands, err := generateCronEntries() + if err != nil { + return err + } + + if len(commands) == 0 { + return deleteCrontab() + } + + data := map[string]interface{}{ + "Commands": commands, + } + + t, err := getCronTemplate() + if err != nil { + return err + } + + tmpFile, err := ioutil.TempFile(os.TempDir(), fmt.Sprintf("dokku-%s-%s", common.MustGetEnv("DOKKU_PID"), "WriteCronEntries")) + if err != nil { + return fmt.Errorf("Cannot create temporary schedule file: %v", err) + } + + defer tmpFile.Close() + defer os.Remove(tmpFile.Name()) + + if err := t.Execute(tmpFile, data); err != nil { + return fmt.Errorf("Unable to template out schedule file: %v", err) + } + + command := common.NewShellCmd(fmt.Sprintf("sudo /usr/bin/crontab -u dokku %s", tmpFile.Name())) + command.ShowOutput = false + out, err := command.CombinedOutput() + if err != nil { + return fmt.Errorf("Unable to update schedule file: %s", string(out)) + } + + common.LogInfo1("Updated schedule file") + + return nil +} + +func getCronTemplate() (*template.Template, error) { + t := template.New("cron") + + templatePath := filepath.Join(common.MustGetEnv("PLUGIN_ENABLED_PATH"), "cron", "templates", "cron.tmpl") + b, err := ioutil.ReadFile(templatePath) + if err != nil { + return t, fmt.Errorf("Cannot read template file: %v", err) + } + + s := strings.TrimSpace(string(b)) + return t.Parse(s) +} diff --git a/plugins/scheduler-docker-local/go.mod b/plugins/scheduler-docker-local/go.mod new file mode 100644 index 000000000..d0890f313 --- /dev/null +++ b/plugins/scheduler-docker-local/go.mod @@ -0,0 +1,28 @@ +module github.com/dokku/dokku/plugins/scheduler-docker-local + +go 1.19 + +require ( + github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000 + github.com/dokku/dokku/plugins/cron v0.0.0-00010101000000-000000000000 + github.com/multiformats/go-base36 v0.2.0 + golang.org/x/sync v0.3.0 +) + +require ( + github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect + github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 // indirect + github.com/dokku/dokku/plugins/app-json v0.0.0-00010101000000-000000000000 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/otiai10/copy v1.12.0 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/ryanuber/columnize v2.1.2+incompatible // indirect + golang.org/x/sys v0.10.0 // indirect + mvdan.cc/sh/v3 v3.7.0 // indirect +) + +replace github.com/dokku/dokku/plugins/app-json => ../app-json + +replace github.com/dokku/dokku/plugins/common => ../common + +replace github.com/dokku/dokku/plugins/cron => ../cron diff --git a/plugins/scheduler-docker-local/go.sum b/plugins/scheduler-docker-local/go.sum new file mode 100644 index 000000000..85df35feb --- /dev/null +++ b/plugins/scheduler-docker-local/go.sum @@ -0,0 +1,30 @@ +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 h1:HHUr4P/aKh4quafGxDT9LDasjGdlGkzLbfmmrlng3kA= +github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27/go.mod h1:VQx0hjo2oUeQkQUET7wRwradO6f+fN5jzXgB/zROxxE= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY= +github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= +github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= +github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= +github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= +mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= diff --git a/plugins/scheduler-docker-local/install b/plugins/scheduler-docker-local/install index 319838e2a..0fd3de0a5 100755 --- a/plugins/scheduler-docker-local/install +++ b/plugins/scheduler-docker-local/install @@ -14,6 +14,9 @@ trigger-scheduler-docker-local-install() { fn-plugin-property-setup "scheduler-docker-local" + echo "%dokku ALL=(ALL) NOPASSWD:/usr/bin/crontab" >"/etc/sudoers.d/dokku-cron" + chmod "0440" "/etc/sudoers.d/dokku-cron" + DOKKU_PATH="$(command -v dokku)" if [[ $(systemctl 2>/dev/null) =~ -\.mount ]]; then diff --git a/plugins/scheduler-docker-local/internal-functions b/plugins/scheduler-docker-local/internal-functions index 1a44deae9..33f16100e 100755 --- a/plugins/scheduler-docker-local/internal-functions +++ b/plugins/scheduler-docker-local/internal-functions @@ -67,6 +67,44 @@ cmd-scheduler-docker-local-report-single() { fi } +fn-scheduler-docker-local-get-checks-file-path() { + declare APP="$1" + + echo "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/CHECKS" +} + +fn-scheduler-docker-local-get-process-specific-checks-file-path() { + declare APP="$1" + + checks_path="$(fn-scheduler-docker-local-get-checks-file-path "$APP")" + process_specific_checks_path="$checks_path.$DOKKU_PID" + if [[ -f "$process_specific_checks_path" ]]; then + echo "$process_specific_checks_path" + return + fi + + echo "$checks_path" +} + +fn-scheduler-docker-local-has-checks-file() { + declare APP="$1" + + checks_path="$(fn-scheduler-docker-local-get-checks-file-path "$APP")" + if [[ -f "$checks_path.$DOKKU_PID.missing" ]]; then + return 1 + fi + + if [[ -f "$checks_path.$DOKKU_PID" ]]; then + return 0 + fi + + if [[ -f "$checks_path" ]]; then + return 0 + fi + + return 1 +} + fn-scheduler-docker-local-retire-container() { declare APP="$1" CID="$2" local STATE @@ -81,14 +119,13 @@ fn-scheduler-docker-local-retire-container() { [[ $DOKKU_DOCKER_STOP_TIMEOUT ]] && DOCKER_STOP_TIME_ARG="--time=${DOKKU_DOCKER_STOP_TIMEOUT}" if [[ "$STATE" == "restarting" ]]; then - "$DOCKER_BIN" container update --restart=no "$CID" >/dev/null 2>&1 + "$DOCKER_BIN" container update --restart=no "$CID" &>/dev/null fi if [[ "$STATE" != "dead" ]] && [[ "$STATE" != "exited" ]]; then # Attempt to stop, if that fails, then force a kill as docker seems # to not send SIGKILL as the docs would indicate. If that fails, move # on to the next. - # shellcheck disable=SC2086 "$DOCKER_BIN" container stop $DOCKER_STOP_TIME_ARG "$CID" \ || "$DOCKER_BIN" container kill "$CID" \ || dokku_log_warn "Unable to kill container ${CID}" @@ -143,8 +180,8 @@ fn-scheduler-docker-local-retire-containers() { continue fi - "$DOCKER_BIN" container rm --force "$CID" >/dev/null 2>&1 || true - if "$DOCKER_BIN" container inspect "${CID}" >/dev/null 2>&1; then + "$DOCKER_BIN" container rm --force "$CID" &>/dev/null || true + if "$DOCKER_BIN" container inspect "${CID}" &>/dev/null; then dokku_log_warn "Container ${CID} still running" continue fi diff --git a/plugins/scheduler-docker-local/post-delete b/plugins/scheduler-docker-local/post-delete index dd1bce3c9..472643544 100755 --- a/plugins/scheduler-docker-local/post-delete +++ b/plugins/scheduler-docker-local/post-delete @@ -12,23 +12,10 @@ trigger-scheduler-docker-local-post-delete() { fn-plugin-property-destroy "scheduler-docker-local" "$APP" rm -rf "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP" - local DOKKU_SCHEDULER=$(get_app_scheduler "$APP") - if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then - return - fi + # remove all application containers + "$DOCKER_BIN" container ls --filter "label=com.dokku.app-name=${APP}" -q | xargs -n1 -I {} "$DOCKER_BIN" container rm --force {} &>/dev/null || true - local IMAGE_REPO=$(get_app_image_repo "$APP") - - # remove all application containers & images - # shellcheck disable=SC2046 - local DOKKU_APP_CIDS=$("$DOCKER_BIN" container list --all --no-trunc | grep "dokku/${APP}:" | awk '{ print $1 }' | xargs) - if [[ -n "$DOKKU_APP_CIDS" ]]; then - # shellcheck disable=SC2086 - "$DOCKER_BIN" container rm --force $DOKKU_APP_CIDS >/dev/null 2>&1 || true - fi - - # shellcheck disable=SC2046 - "$DOCKER_BIN" image remove $("$DOCKER_BIN" image list --quiet "$IMAGE_REPO" | xargs) &>/dev/null || true + plugn trigger cron-write } trigger-scheduler-docker-local-post-delete "$@" diff --git a/plugins/scheduler-docker-local/post-deploy b/plugins/scheduler-docker-local/post-deploy new file mode 100755 index 000000000..3c19037f1 --- /dev/null +++ b/plugins/scheduler-docker-local/post-deploy @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +trigger-scheduler-docker-local-post-deploy() { + declare desc="scheduler-docker-local post-deploy plugin trigger" + declare trigger="post-deploy" + declare APP="$1" + + plugn trigger cron-write +} + +trigger-scheduler-docker-local-post-deploy "$@" diff --git a/plugins/scheduler-docker-local/pre-deploy b/plugins/scheduler-docker-local/pre-deploy index 6dcd94fae..217ce6630 100755 --- a/plugins/scheduler-docker-local/pre-deploy +++ b/plugins/scheduler-docker-local/pre-deploy @@ -4,6 +4,7 @@ set -eo pipefail source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" +source "$PLUGIN_AVAILABLE_PATH/scheduler-docker-local/internal-functions" trigger-scheduler-docker-local-pre-deploy() { declare desc="scheduler-docker-local pre-deploy plugin trigger" @@ -16,7 +17,7 @@ trigger-scheduler-docker-local-pre-deploy() { fi scheduler-docker-local-pre-deploy-chown-app "$APP" "$IMAGE_TAG" - scheduler-docker-local-pre-deploy-precheck "$APP" "$IMAGE_TAG" + scheduler-docker-local-pre-deploy-precheck "$APP" } scheduler-docker-local-pre-deploy-chown-app() { @@ -58,20 +59,15 @@ scheduler-docker-local-pre-deploy-chown-app() { return fi - # shellcheck disable=SC2086 "$DOCKER_BIN" container run --rm "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS "${ARG_ARRAY[@]}" $IMAGE /bin/bash -c "find $CONTAINER_PATHS -not -user $DOKKU_APP_USER -print0 | xargs -0 -r chown -R $DOKKU_APP_USER" || true } scheduler-docker-local-pre-deploy-precheck() { declare desc="Outputs the checks messages if necessary" - declare APP="$1" IMAGE_TAG="$2" - local IMAGE=$(get_app_image_name "$APP" "$IMAGE_TAG") - local CHECKS_FILE=$(mktemp "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX") - trap "rm -rf '$CHECKS_FILE' >/dev/null" RETURN INT TERM EXIT - copy_from_image "$IMAGE" "CHECKS" "$CHECKS_FILE" 2>/dev/null || true + declare APP="$1" dokku_log_info2 "Processing deployment checks" - if [[ ! -s "${CHECKS_FILE}" ]]; then + if ! fn-scheduler-docker-local-has-checks-file "$APP"; then local CHECKS_URL="${DOKKU_CHECKS_URL:-https://dokku.com/docs/deployment/zero-downtime-deploys/}" dokku_log_verbose "No CHECKS file found. Simple container checks will be performed." dokku_log_verbose "For more efficient zero downtime deployments, create a CHECKS file. See ${CHECKS_URL} for examples" diff --git a/plugins/scheduler-docker-local/scheduler-deploy b/plugins/scheduler-docker-local/scheduler-deploy index b9fbdae7b..aa69eedc6 100755 --- a/plugins/scheduler-docker-local/scheduler-deploy +++ b/plugins/scheduler-docker-local/scheduler-deploy @@ -37,6 +37,11 @@ trigger-scheduler-docker-local-scheduler-deploy() { DOKKU_START_CMD="$(config_get "$APP" DOKKU_START_CMD || true)" + local PROCFILE_EXISTS=false + if plugn trigger procfile-exists "$APP" 2>/dev/null; then + PROCFILE_EXISTS=true + fi + local DOKKU_WAIT_TO_RETIRE="$(plugn trigger checks-get-property "$APP" wait-to-retire)" export DOKKU_WAIT_TO_RETIRE @@ -54,6 +59,12 @@ trigger-scheduler-docker-local-scheduler-deploy() { continue fi + local PROC_CMD=$(plugn trigger procfile-get-command "$APP" "$PROC_TYPE" "5000" 2>/dev/null || echo '') + if [[ "$PROCFILE_EXISTS" == true ]] && [[ "$PROC_CMD" == "" ]]; then + dokku_log_warn "Skipping $PROC_TYPE as it is missing from the current Procfile" + continue + fi + if [[ "$PROC_TYPE" != "web" ]]; then echo "$PLUGIN_AVAILABLE_PATH/scheduler-docker-local/bin/scheduler-deploy-process $APP $IMAGE_SOURCE_TYPE $IMAGE $IMAGE_TAG $PROC_TYPE $PROC_COUNT" >>"$TMP_FILE" continue @@ -79,7 +90,7 @@ trigger-scheduler-docker-local-scheduler-deploy() { trap '' INT HUP sleep "$DOKKU_WAIT_TO_RETIRE" for oldid in $oldids; do - if ! docker container inspect "$oldid" >/dev/null 2>&1; then + if ! docker container inspect "$oldid" &>/dev/null; then continue fi # Disable the container restart policy @@ -88,7 +99,6 @@ trigger-scheduler-docker-local-scheduler-deploy() { # Attempt to stop, if that fails, then force a kill as docker seems # to not send SIGKILL as the docs would indicate. If that fails, move # on to the next. - # shellcheck disable=SC2086 "$DOCKER_BIN" container stop $DOCKER_STOP_TIME_ARG "$oldid" \ || "$DOCKER_BIN" container kill "$oldid" \ || plugn trigger retire-container-failed "$APP" "$oldid" # plugin trigger for event logging diff --git a/plugins/scheduler-docker-local/scheduler-enter b/plugins/scheduler-docker-local/scheduler-enter index b6b2a024d..35cc65ff7 100755 --- a/plugins/scheduler-docker-local/scheduler-enter +++ b/plugins/scheduler-docker-local/scheduler-enter @@ -74,7 +74,6 @@ trigger-scheduler-docker-local-scheduler-enter() { local IMAGE="$("$DOCKER_BIN" container inspect --format '{{ .Config.Image }}' "$CONTAINER_ID")" is_image_herokuish_based "$IMAGE" "$APP" && EXEC_CMD="/exec" is_image_cnb_based "$IMAGE" && EXEC_CMD="" && DOKKU_RUN_OPTS+=" -w /workspace" - # shellcheck disable=SC2086 "$DOCKER_BIN" container exec $DOKKU_RUN_OPTS "$CONTAINER_ID" $EXEC_CMD "${@:-$DOKKU_APP_SHELL}" } diff --git a/plugins/scheduler-docker-local/scheduler-logs-failed b/plugins/scheduler-docker-local/scheduler-logs-failed index ed0d45349..d3cc93346 100755 --- a/plugins/scheduler-docker-local/scheduler-logs-failed +++ b/plugins/scheduler-docker-local/scheduler-logs-failed @@ -24,7 +24,7 @@ trigger-scheduler-docker-local-scheduler-logs-failed() { while read -r LINE || [[ -n "$LINE" ]]; do CID="$(echo "$LINE" | cut -d ' ' -f1)" PREFIX="$(echo "$LINE" | cut -d ' ' -f2)" - if "$DOCKER_BIN" container inspect "${CID}" >/dev/null 2>&1; then + if "$DOCKER_BIN" container inspect "${CID}" &>/dev/null; then RUNNING_CONTAINERS+=("$CID") else DEAD_CONTAINERS+=("$CID") diff --git a/plugins/scheduler-docker-local/scheduler-pre-restore b/plugins/scheduler-docker-local/scheduler-pre-restore index 6a88d8ea4..e04755d3a 100755 --- a/plugins/scheduler-docker-local/scheduler-pre-restore +++ b/plugins/scheduler-docker-local/scheduler-pre-restore @@ -14,7 +14,7 @@ trigger-scheduler-docker-local-scheduler-pre-restore() { # delete all "old" containers # shellcheck disable=SC2046 - "$DOCKER_BIN" container rm $("$DOCKER_BIN" container list --all --format "{{.Names}}" --filter "label=$DOKKU_CONTAINER_LABEL" --quiet | grep -E '(.+\..+\.[0-9]+\.[0-9]+$)') &>/dev/null || true + "$DOCKER_BIN" container rm $("$DOCKER_BIN" container ls --all --format "{{.Names}}" --filter "label=$DOKKU_CONTAINER_LABEL" --quiet | grep -E '(.+\..+\.[0-9]+\.[0-9]+$)') &>/dev/null || true } trigger-scheduler-docker-local-scheduler-pre-restore "$@" diff --git a/plugins/scheduler-docker-local/scheduler-run-stop b/plugins/scheduler-docker-local/scheduler-run-stop index 3b9f730d4..d46f9ad72 100755 --- a/plugins/scheduler-docker-local/scheduler-run-stop +++ b/plugins/scheduler-docker-local/scheduler-run-stop @@ -13,7 +13,6 @@ fn-scheduler-docker-local-stop-container() { fi [[ $DOKKU_DOCKER_STOP_TIMEOUT ]] && DOCKER_STOP_TIME_ARG="--time=${DOKKU_DOCKER_STOP_TIMEOUT}" - # shellcheck disable=SC2086 if "$DOCKER_BIN" container stop $DOCKER_STOP_TIME_ARG "$CONTAINER_ID_OR_NAME"; then return fi diff --git a/plugins/scheduler-docker-local/scheduler-stop b/plugins/scheduler-docker-local/scheduler-stop index 32ab1e5ed..88dd43091 100755 --- a/plugins/scheduler-docker-local/scheduler-stop +++ b/plugins/scheduler-docker-local/scheduler-stop @@ -20,10 +20,8 @@ trigger-scheduler-docker-local-scheduler-stop() { if [[ -n "$DOKKU_APP_RUNNING_CONTAINER_IDS" ]]; then # Disable the container restart policy - # shellcheck disable=SC2086 "$DOCKER_BIN" container update --restart=no $DOKKU_APP_RUNNING_CONTAINER_IDS &>/dev/null || true - # shellcheck disable=SC2086 "$DOCKER_BIN" container stop $DOCKER_STOP_TIME_ARG $DOKKU_APP_RUNNING_CONTAINER_IDS >/dev/null || true fi @@ -35,8 +33,7 @@ trigger-scheduler-docker-local-scheduler-stop() { plugn trigger scheduler-register-retired "$APP" "$CID" done - # shellcheck disable=SC2086 - "$DOCKER_BIN" container rm --force $DOKKU_APP_CIDS >/dev/null 2>&1 || true + "$DOCKER_BIN" container rm --force $DOKKU_APP_CIDS &>/dev/null || true fi fi } diff --git a/plugins/cron/src/triggers/triggers.go b/plugins/scheduler-docker-local/src/triggers/triggers.go similarity index 56% rename from plugins/cron/src/triggers/triggers.go rename to plugins/scheduler-docker-local/src/triggers/triggers.go index 43ae583f7..86b030d9d 100644 --- a/plugins/cron/src/triggers/triggers.go +++ b/plugins/scheduler-docker-local/src/triggers/triggers.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/dokku/dokku/plugins/common" - cron "github.com/dokku/dokku/plugins/cron" + schedulerdockerlocal "github.com/dokku/dokku/plugins/scheduler-docker-local" ) // main entrypoint to all triggers @@ -19,16 +19,7 @@ func main() { var err error switch trigger { case "cron-write": - err = cron.TriggerCronWrite() - case "install": - err = cron.TriggerInstall() - case "post-delete": - err = cron.TriggerPostDelete() - case "post-deploy": - err = cron.TriggerPostDeploy() - case "report": - appName := flag.Arg(0) - err = cron.ReportSingleApp(appName, "", "") + err = schedulerdockerlocal.TriggerCronWrite() default: err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) } diff --git a/plugins/scheduler-docker-local/triggers.go b/plugins/scheduler-docker-local/triggers.go new file mode 100644 index 000000000..018ff411e --- /dev/null +++ b/plugins/scheduler-docker-local/triggers.go @@ -0,0 +1,6 @@ +package schedulerdockerlocal + +// TriggerCronWrite force updates the cron file for all apps +func TriggerCronWrite() error { + return writeCronEntries() +} diff --git a/plugins/shell/subcommands/default b/plugins/shell/subcommands/default index 2a8b13abf..00602e160 100755 --- a/plugins/shell/subcommands/default +++ b/plugins/shell/subcommands/default @@ -44,7 +44,6 @@ cmd-shell-default() { # Not a built-in, run as regular dokku command *) - # shellcheck disable=SC2086 dokku $line || true ;; esac diff --git a/plugins/traefik-vhosts/certs-force b/plugins/traefik-vhosts/certs-force new file mode 100755 index 000000000..665717bad --- /dev/null +++ b/plugins/traefik-vhosts/certs-force @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/traefik-vhosts/internal-functions" + +trigger-traefik-vhosts-certs-force() { + declare desc="traefik-vhosts certs-force plugin trigger" + declare trigger="certs-force" + declare APP="$1" + + if [[ "$(plugn trigger proxy-type "$APP")" != "traefik" ]]; then + return + fi + + if [[ -n "$(fn-traefik-letsencrypt-email)" ]]; then + echo true + fi +} + +trigger-traefik-vhosts-certs-force "$@" diff --git a/plugins/traefik-vhosts/command-functions b/plugins/traefik-vhosts/command-functions index 6136fd178..68f194d5b 100755 --- a/plugins/traefik-vhosts/command-functions +++ b/plugins/traefik-vhosts/command-functions @@ -143,8 +143,8 @@ cmd-traefik-start() { } cmd-traefik-stop() { - declare desc="Starts the traefik server" - declare cmd="traefik:start" + declare desc="Stops the traefik server" + declare cmd="traefik:stop" [[ "$1" == "$cmd" ]] && shift 1 if ! fn-is-compose-installed; then diff --git a/plugins/traefik-vhosts/docker-args-process-deploy b/plugins/traefik-vhosts/docker-args-process-deploy index c81248cea..f9d94ffa4 100755 --- a/plugins/traefik-vhosts/docker-args-process-deploy +++ b/plugins/traefik-vhosts/docker-args-process-deploy @@ -8,7 +8,7 @@ trigger-traefik-vhosts-docker-args-process-deploy() { declare desc="nginx-vhosts core-post-deploy plugin trigger" declare trigger="docker-args-process-deploy" declare APP="$1" IMAGE_SOURCE_TYPE="$2" IMAGE_TAG="$3" PROC_TYPE="$4" CONTAINER_INDEX="$5" - local app_domains is_app_listening letsencrypt_email output proxy_container_port proxy_host_port port_map proxy_port_map priority proxy_scheme proxy_schemes traefik_domains + local app_domains is_app_listening letsencrypt_email output proxy_container_port proxy_host_port port_map priority proxy_scheme proxy_schemes traefik_domains local proxy_container_http_port proxy_container_http_port_candidate proxy_host_http_port_candidate local proxy_container_https_port proxy_container_https_port_candidate proxy_host_https_port_candidate local STDIN=$(cat) @@ -30,15 +30,14 @@ trigger-traefik-vhosts-docker-args-process-deploy() { fi # ensure we have a port mapping - plugn trigger proxy-configure-ports "$APP" + plugn trigger ports-configure "$APP" # gather port mapping information # we only support proxying a single port for http and https listeners # so this block parses the port mappings and tries to find the correct # mapping to expose is_app_listening="false" - proxy_port_map="$(plugn trigger config-get "$APP" DOKKU_PROXY_PORT_MAP)" - for port_map in $proxy_port_map; do + while read -r port_map; do proxy_scheme="$(awk -F ':' '{ print $1 }' <<<"$port_map")" proxy_host_port="$(awk -F ':' '{ print $2 }' <<<"$port_map")" proxy_container_port="$(awk -F ':' '{ print $3 }' <<<"$port_map")" @@ -66,7 +65,7 @@ trigger-traefik-vhosts-docker-args-process-deploy() { proxy_container_https_port="$proxy_container_port" fi fi - done + done < <(plugn trigger ports-get "$APP") letsencrypt_email="$(fn-traefik-letsencrypt-email)" if [[ -n "$letsencrypt_email" ]] && [[ -z "$proxy_container_https_port" ]]; then diff --git a/plugins/traefik-vhosts/internal-functions b/plugins/traefik-vhosts/internal-functions index 9bc89ee1a..51af09ef1 100755 --- a/plugins/traefik-vhosts/internal-functions +++ b/plugins/traefik-vhosts/internal-functions @@ -76,7 +76,7 @@ fn-traefik-dashboard-enabled() { } fn-traefik-image() { - fn-plugin-property-get-default "traefik" "--global" "image" "traefik:v2.8" + fn-plugin-property-get-default "traefik" "--global" "image" "traefik:v2.10" } fn-traefik-letsencrypt-email() { diff --git a/plugins/traefik-vhosts/pre-restore b/plugins/traefik-vhosts/pre-restore index 7b522d76f..698d97788 100755 --- a/plugins/traefik-vhosts/pre-restore +++ b/plugins/traefik-vhosts/pre-restore @@ -8,7 +8,7 @@ trigger-traefik-pre-restore() { declare desc="pre-restore the traefik proxy" declare trigger="install" - if [[ "$(fn-plugin-property-get "traefik" "--global" "proxy-status")" == "stopped" ]]; then + if [[ "$(fn-plugin-property-get "traefik" "--global" "proxy-status")" != "started" ]]; then return fi diff --git a/plugins/traefik-vhosts/templates/compose.yml.sigil b/plugins/traefik-vhosts/templates/compose.yml.sigil index 7102f374e..6bddb9938 100644 --- a/plugins/traefik-vhosts/templates/compose.yml.sigil +++ b/plugins/traefik-vhosts/templates/compose.yml.sigil @@ -21,7 +21,7 @@ services: {{ if $.TRAEFIK_LETSENCRYPT_EMAIL }} - --certificatesresolvers.leresolver.acme.caserver={{ $.TRAEFIK_LETSENCRYPT_SERVER }} - --certificatesresolvers.leresolver.acme.email={{ $.TRAEFIK_LETSENCRYPT_EMAIL }} - - --certificatesresolvers.leresolver.acme.storage=/acme.json + - --certificatesresolvers.leresolver.acme.storage=/data/acme.json - --certificatesresolvers.leresolver.acme.tlschallenge=true {{ end }} @@ -59,4 +59,4 @@ services: volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" - - "{{ $.TRAEFIK_DATA_DIR }}/traefik-acme.json:/acme.json" + - "{{ $.TRAEFIK_DATA_DIR }}:/data" diff --git a/tests.mk b/tests.mk index 495d5c80f..a6493efdb 100644 --- a/tests.mk +++ b/tests.mk @@ -1,5 +1,6 @@ SYSTEM := $(shell sh -c 'uname -s 2>/dev/null') DOKKU_SSH_PORT ?= 22 +DOKKU_DOMAIN ?= dokku.me bats: ifeq ($(SYSTEM),Darwin) @@ -13,7 +14,7 @@ else endif shellcheck: -ifneq ($(shell shellcheck --version >/dev/null 2>&1 ; echo $$?),0) +ifneq ($(shell shellcheck --version >/dev/null 2>&1; echo $$?),0) ifeq ($(SYSTEM),Darwin) brew install shellcheck else @@ -22,7 +23,7 @@ endif endif shfmt: -ifneq ($(shell shfmt --version >/dev/null 2>&1 ; echo $$?),0) +ifneq ($(shell shfmt --version >/dev/null 2>&1; echo $$?),0) ifeq ($(shfmt),Darwin) brew install shfmt else @@ -48,8 +49,8 @@ ifdef ENABLE_DOKKU_TRACE echo "-----> Enable dokku trace" dokku trace:on endif - @echo "Setting dokku.me in /etc/hosts" - sudo /bin/bash -c "[[ `ping -c1 dokku.me >/dev/null 2>&1; echo $$?` -eq 0 ]] || echo \"127.0.0.1 dokku.me *.dokku.me www.test.app.dokku.me\" >> /etc/hosts" + @echo "Setting $(DOKKU_DOMAIN) in /etc/hosts" + sudo /bin/bash -c "[[ `ping -c1 $(DOKKU_DOMAIN) >/dev/null 2>&1; echo $$?` -eq 0 ]] || echo \"127.0.0.1 $(DOKKU_DOMAIN) *.$(DOKKU_DOMAIN) www.test.app.$(DOKKU_DOMAIN)\" >> /etc/hosts" @echo "-----> Generating keypair..." mkdir -p /root/.ssh @@ -61,10 +62,10 @@ endif @echo "-----> Setting up ssh config..." ifneq ($(shell ls /root/.ssh/config >/dev/null 2>&1 ; echo $$?),0) - echo "Host dokku.me \\r\\n Port $(DOKKU_SSH_PORT) \\r\\n RequestTTY yes \\r\\n IdentityFile /root/.ssh/dokku_test_rsa" >> /root/.ssh/config + echo "Host $(DOKKU_DOMAIN) \\r\\n Port $(DOKKU_SSH_PORT) \\r\\n RequestTTY yes \\r\\n IdentityFile /root/.ssh/dokku_test_rsa" >> /root/.ssh/config echo "Host 127.0.0.1 \\r\\n Port 22333 \\r\\n RequestTTY yes \\r\\n IdentityFile /root/.ssh/dokku_test_rsa" >> /root/.ssh/config -else ifeq ($(shell grep dokku.me /root/.ssh/config),) - echo "Host dokku.me \\r\\n Port $(DOKKU_SSH_PORT) \\r\\n RequestTTY yes \\r\\n IdentityFile /root/.ssh/dokku_test_rsa" >> /root/.ssh/config +else ifeq ($(shell grep $(DOKKU_DOMAIN) /root/.ssh/config),) + echo "Host $(DOKKU_DOMAIN) \\r\\n Port $(DOKKU_SSH_PORT) \\r\\n RequestTTY yes \\r\\n IdentityFile /root/.ssh/dokku_test_rsa" >> /root/.ssh/config echo "Host 127.0.0.1 \\r\\n Port 22333 \\r\\n RequestTTY yes \\r\\n IdentityFile /root/.ssh/dokku_test_rsa" >> /root/.ssh/config else sed --in-place 's/Port 22 \r/Port $(DOKKU_SSH_PORT) \r/g' /root/.ssh/config @@ -86,9 +87,9 @@ endif chmod 700 /home/dokku/.ssh chmod 600 /home/dokku/.ssh/authorized_keys -ifeq ($(shell grep dokku.me /home/dokku/VHOST 2>/dev/null),) - @echo "-----> Setting default VHOST to dokku.me..." - echo "dokku.me" > /home/dokku/VHOST +ifeq ($(shell grep $(DOKKU_DOMAIN) /home/dokku/VHOST 2>/dev/null),) + @echo "-----> Setting default VHOST to $(DOKKU_DOMAIN)..." + echo "$(DOKKU_DOMAIN)" > /home/dokku/VHOST endif ifeq ($(DOKKU_SSH_PORT), 22) $(MAKE) prime-ssh-known-hosts @@ -105,21 +106,21 @@ endif prime-ssh-known-hosts: @echo "-----> Intitial SSH connection to populate known_hosts..." - @echo "=====> SSH dokku.me" - ssh -o StrictHostKeyChecking=no dokku@dokku.me help >/dev/null + @echo "=====> SSH $(DOKKU_DOMAIN)" + ssh -o StrictHostKeyChecking=no dokku@$(DOKKU_DOMAIN) help >/dev/null @echo "=====> SSH 127.0.0.1" ssh -o StrictHostKeyChecking=no dokku@127.0.0.1 help >/dev/null lint-setup: @mkdir -p test-results/shellcheck tmp/shellcheck @find . -not -path '*/\.*' -not -path './debian/*' -not -path './docs/*' -not -path './tests/*' -not -path './vendor/*' -type f | xargs file | grep text | awk -F ':' '{ print $$1 }' | xargs head -n1 | grep -B1 "bash" | grep "==>" | awk '{ print $$2 }' > tmp/shellcheck/test-files - @cat tests/shellcheck-exclude | sed -n -e '/^# SC/p' | cut -d' ' -f2 | paste -d, -s > tmp/shellcheck/exclude + @cat .shellcheckrc | sed -n -e '/^# SC/p' | cut -d' ' -f2 | paste -d, -s > tmp/shellcheck/exclude lint-ci: lint-setup # these are disabled due to their expansive existence in the codebase. we should clean it up though - @cat tests/shellcheck-exclude | sed -n -e '/^# SC/p' + @cat .shellcheckrc | sed -n -e '/^# SC/p' @echo linting... - @cat tmp/shellcheck/test-files | xargs shellcheck -e $(shell cat tmp/shellcheck/exclude) | tests/shellcheck-to-junit --output test-results/shellcheck/results.xml --files tmp/shellcheck/test-files --exclude $(shell cat tmp/shellcheck/exclude) + @cat tmp/shellcheck/test-files | xargs shellcheck | tests/shellcheck-to-junit --output test-results/shellcheck/results.xml --files tmp/shellcheck/test-files --exclude $(shell cat tmp/shellcheck/exclude) lint-shfmt: shfmt # verifying via shfmt @@ -181,87 +182,87 @@ endif deploy-test-go-fail-predeploy: @echo deploying go-fail-predeploy app... - cd tests && ./test_deploy ./apps/go-fail-predeploy dokku.me '' true + cd tests && ./test_deploy ./apps/go-fail-predeploy $(DOKKU_DOMAIN) '' true deploy-test-go-fail-postdeploy: @echo deploying go-fail-postdeploy app... - cd tests && ./test_deploy ./apps/go-fail-postdeploy dokku.me '' true + cd tests && ./test_deploy ./apps/go-fail-postdeploy $(DOKKU_DOMAIN) '' true deploy-test-checks-root: @echo deploying checks-root app... - cd tests && ./test_deploy ./apps/checks-root dokku.me '' true + cd tests && ./test_deploy ./apps/checks-root $(DOKKU_DOMAIN) '' true deploy-test-main-branch: @echo deploying checks-root app to main branch... - cd tests && ./test_deploy ./apps/checks-root dokku.me '' true main + cd tests && ./test_deploy ./apps/checks-root $(DOKKU_DOMAIN) '' true main deploy-test-clojure: @echo deploying config app... - cd tests && ./test_deploy ./apps/clojure dokku.me + cd tests && ./test_deploy ./apps/clojure $(DOKKU_DOMAIN) deploy-test-config: @echo deploying config app... - cd tests && ./test_deploy ./apps/config dokku.me + cd tests && ./test_deploy ./apps/config $(DOKKU_DOMAIN) deploy-test-dockerfile: @echo deploying dockerfile app... - cd tests && ./test_deploy ./apps/dockerfile dokku.me + cd tests && ./test_deploy ./apps/dockerfile $(DOKKU_DOMAIN) deploy-test-dockerfile-noexpose: @echo deploying dockerfile-noexpose app... - cd tests && ./test_deploy ./apps/dockerfile-noexpose dokku.me + cd tests && ./test_deploy ./apps/dockerfile-noexpose $(DOKKU_DOMAIN) deploy-test-dockerfile-procfile: @echo deploying dockerfile-procfile app... - cd tests && ./test_deploy ./apps/dockerfile-procfile dokku.me + cd tests && ./test_deploy ./apps/dockerfile-procfile $(DOKKU_DOMAIN) deploy-test-gitsubmodules: @echo deploying gitsubmodules app... - cd tests && ./test_deploy ./apps/gitsubmodules dokku.me + cd tests && ./test_deploy ./apps/gitsubmodules $(DOKKU_DOMAIN) deploy-test-go: @echo deploying go app... - cd tests && ./test_deploy ./apps/go dokku.me + cd tests && ./test_deploy ./apps/go $(DOKKU_DOMAIN) deploy-test-java: @echo deploying java app... - cd tests && ./test_deploy ./apps/java dokku.me + cd tests && ./test_deploy ./apps/java $(DOKKU_DOMAIN) deploy-test-multi: @echo deploying multi app... - cd tests && ./test_deploy ./apps/multi dokku.me + cd tests && ./test_deploy ./apps/multi $(DOKKU_DOMAIN) deploy-test-nodejs-express: @echo deploying nodejs-express app... - cd tests && ./test_deploy ./apps/nodejs-express dokku.me + cd tests && ./test_deploy ./apps/nodejs-express $(DOKKU_DOMAIN) deploy-test-nodejs-express-noprocfile: @echo deploying nodejs-express app with no Procfile... - cd tests && ./test_deploy ./apps/nodejs-express-noprocfile dokku.me + cd tests && ./test_deploy ./apps/nodejs-express-noprocfile $(DOKKU_DOMAIN) deploy-test-nodejs-worker: @echo deploying nodejs-worker app... - cd tests && ./test_deploy ./apps/nodejs-worker dokku.me + cd tests && ./test_deploy ./apps/nodejs-worker $(DOKKU_DOMAIN) deploy-test-php: @echo deploying php app... - cd tests && ./test_deploy ./apps/php dokku.me + cd tests && ./test_deploy ./apps/php $(DOKKU_DOMAIN) deploy-test-python-flask: @echo deploying python-flask app... - cd tests && ./test_deploy ./apps/python-flask dokku.me + cd tests && ./test_deploy ./apps/python-flask $(DOKKU_DOMAIN) deploy-test-ruby: @echo deploying ruby app... - cd tests && ./test_deploy ./apps/ruby dokku.me + cd tests && ./test_deploy ./apps/ruby $(DOKKU_DOMAIN) deploy-test-scala: @echo deploying scala app... - cd tests && ./test_deploy ./apps/scala dokku.me + cd tests && ./test_deploy ./apps/scala $(DOKKU_DOMAIN) deploy-test-static: @echo deploying static app... - cd tests && ./test_deploy ./apps/static dokku.me + cd tests && ./test_deploy ./apps/static $(DOKKU_DOMAIN) deploy-tests: @echo running deploy tests... @@ -305,7 +306,7 @@ generate-ssl-tars: generate-ssl-tar generate-ssl-sans-tar generate-ssl-wildcard- generate-ssl-tar: rm -rf /tmp/dokku-server_ssl mkdir -p /tmp/dokku-server_ssl - openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout /tmp/dokku-server_ssl/server.key -out /tmp/dokku-server_ssl/server.crt -subj "/CN=dokku.me" -days 3650 + openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout /tmp/dokku-server_ssl/server.key -out /tmp/dokku-server_ssl/server.crt -subj "/CN=$(DOKKU_DOMAIN)" -days 3650 rm tests/unit/server_ssl.tar cd /tmp/dokku-server_ssl && tar cvf $(PWD)/tests/unit/server_ssl.tar server.key server.crt tar -tvf tests/unit/server_ssl.tar @@ -313,7 +314,7 @@ generate-ssl-tar: generate-ssl-sans-tar: rm -rf /tmp/dokku-server_ssl_sans mkdir -p /tmp/dokku-server_ssl_sans - openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout /tmp/dokku-server_ssl_sans/server.key -out /tmp/dokku-server_ssl_sans/server.crt -subj "/CN=test.dokku.me" -days 3650 -addext "subjectAltName = DNS:www.test.dokku.me, DNS:www.test.app.dokku.me" + openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout /tmp/dokku-server_ssl_sans/server.key -out /tmp/dokku-server_ssl_sans/server.crt -subj "/CN=test.$(DOKKU_DOMAIN)" -days 3650 -addext "subjectAltName = DNS:www.test.$(DOKKU_DOMAIN), DNS:www.test.app.$(DOKKU_DOMAIN)" rm tests/unit/server_ssl_sans.tar cd /tmp/dokku-server_ssl_sans && tar cvf $(PWD)/tests/unit/server_ssl_sans.tar server.key server.crt tar -tvf tests/unit/server_ssl_sans.tar @@ -321,7 +322,7 @@ generate-ssl-sans-tar: generate-ssl-wildcard-tar: rm -rf /tmp/dokku-server_ssl_wildcard mkdir -p /tmp/dokku-server_ssl_wildcard - openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout /tmp/dokku-server_ssl_wildcard/server.key -out /tmp/dokku-server_ssl_wildcard/server.crt -subj "/CN=*.dokku.me" -days 3650 + openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout /tmp/dokku-server_ssl_wildcard/server.key -out /tmp/dokku-server_ssl_wildcard/server.crt -subj "/CN=*.$(DOKKU_DOMAIN)" -days 3650 rm tests/unit/server_ssl_wildcard.tar cd /tmp/dokku-server_ssl_wildcard && tar cvf $(PWD)/tests/unit/server_ssl_wildcard.tar server.key server.crt tar -tvf tests/unit/server_ssl_wildcard.tar diff --git a/tests/apps/dockerfile/app.json b/tests/apps/dockerfile/app.json index ea96d901c..6c39f8703 100644 --- a/tests/apps/dockerfile/app.json +++ b/tests/apps/dockerfile/app.json @@ -25,7 +25,7 @@ "generator": "echo 5" } }, - "image": "gliderlabs/herokuish:latest-20", + "image": "gliderlabs/herokuish:latest-22", "addons": [ "dokku-postgres", "dokku-redis" diff --git a/tests/apps/multi/Gemfile b/tests/apps/multi/Gemfile index df0465167..8c6c3a47c 100644 --- a/tests/apps/multi/Gemfile +++ b/tests/apps/multi/Gemfile @@ -1,6 +1,6 @@ source "https://rubygems.org" -ruby '2.7.5' +ruby '3.1.4' gem 'compass', ">= 1.0.1" gem 'sass-globbing' diff --git a/tests/apps/multi/Gemfile.lock b/tests/apps/multi/Gemfile.lock index 20c4333b1..c4f33b77e 100644 --- a/tests/apps/multi/Gemfile.lock +++ b/tests/apps/multi/Gemfile.lock @@ -16,7 +16,7 @@ GEM sass (>= 3.2, < 3.5) ffi (1.15.5) multi_json (1.15.0) - rb-fsevent (0.11.1) + rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) sass (3.4.25) @@ -31,7 +31,7 @@ DEPENDENCIES sass-globbing RUBY VERSION - ruby 2.7.5p203 + ruby 3.1.4p223 BUNDLED WITH - 2.1.4 + 2.3.26 diff --git a/tests/apps/nodejs-express/app.json b/tests/apps/nodejs-express/app.json index aad3fd590..918760d5f 100644 --- a/tests/apps/nodejs-express/app.json +++ b/tests/apps/nodejs-express/app.json @@ -25,7 +25,7 @@ "generator": "echo 5" } }, - "image": "gliderlabs/herokuish:latest-20", + "image": "gliderlabs/herokuish:latest-22", "addons": [ "dokku-postgres", "dokku-redis" diff --git a/tests/apps/python/cnb/1/buildpack.toml b/tests/apps/python/cnb/1/buildpack.toml index f89645558..9c0af09cf 100644 --- a/tests/apps/python/cnb/1/buildpack.toml +++ b/tests/apps/python/cnb/1/buildpack.toml @@ -6,4 +6,4 @@ version = "0.1.0" name = "1 Packages Buildpack" [[stacks]] -id = "heroku-20" +id = "heroku-22" diff --git a/tests/apps/python/cnb/2/buildpack.toml b/tests/apps/python/cnb/2/buildpack.toml index c15695e03..93b5e2955 100644 --- a/tests/apps/python/cnb/2/buildpack.toml +++ b/tests/apps/python/cnb/2/buildpack.toml @@ -6,4 +6,4 @@ version = "0.1.0" name = "2 Packages Buildpack" [[stacks]] -id = "heroku-20" +id = "heroku-22" diff --git a/tests/apps/python/expose.Dockerfile b/tests/apps/python/expose.Dockerfile new file mode 100644 index 000000000..130cbb2fc --- /dev/null +++ b/tests/apps/python/expose.Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.11.0-buster + +EXPOSE 3001/udp +EXPOSE 3000/tcp +EXPOSE 3003 + +COPY . /app + +WORKDIR /app diff --git a/tests/apps/python/task.py b/tests/apps/python/task.py index 6d5436600..01b998d12 100644 --- a/tests/apps/python/task.py +++ b/tests/apps/python/task.py @@ -1,3 +1,5 @@ +import sys + def main(args): print(args) diff --git a/tests/apps/python/worker.Procfile b/tests/apps/python/worker.Procfile new file mode 100644 index 000000000..9eed80588 --- /dev/null +++ b/tests/apps/python/worker.Procfile @@ -0,0 +1 @@ +worker: python3 -u worker.py diff --git a/tests/ci/setup.sh b/tests/ci/setup.sh index a8ebd8448..7e2699fcc 100755 --- a/tests/ci/setup.sh +++ b/tests/ci/setup.sh @@ -14,6 +14,12 @@ install_dependencies() { curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/focal/docker-image-labeler_${DOCKER_IMAGE_LABELER_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${DOCKER_IMAGE_LABELER_PACKAGE_NAME}" fi + DOCKER_CONTAINER_HEALTHCHECKER_VERSION=$(grep DOCKER_CONTAINER_HEALTHCHECKER_VERSION "${ROOT_DIR}/Makefile" | head -n1 | cut -d' ' -f3) + DOCKER_CONTAINER_HEALTHCHECKER_PACKAGE_NAME="docker-container-healthchecker_${DOCKER_CONTAINER_HEALTHCHECKER_VERSION}_amd64.deb" + if [[ ! -f "$ROOT_DIR/build/${DOCKER_CONTAINER_HEALTHCHECKER_PACKAGE_NAME}" ]]; then + curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/focal/docker-container-healthchecker_${DOCKER_CONTAINER_HEALTHCHECKER_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${DOCKER_CONTAINER_HEALTHCHECKER_PACKAGE_NAME}" + fi + HEROKUISH_VERSION=$(grep HEROKUISH_VERSION "${ROOT_DIR}/Makefile" | head -n1 | cut -d' ' -f3) HEROKUISH_PACKAGE_NAME="herokuish_${HEROKUISH_VERSION}_amd64.deb" if [[ ! -f "$ROOT_DIR/build/${HEROKUISH_PACKAGE_NAME}" ]]; then @@ -62,6 +68,7 @@ install_dependencies() { ls -lah "${ROOT_DIR}/build/" sudo dpkg -i \ + "${ROOT_DIR}/build/$DOCKER_CONTAINER_HEALTHCHECKER_PACKAGE_NAME" \ "${ROOT_DIR}/build/$DOCKER_IMAGE_LABELER_PACKAGE_NAME" \ "${ROOT_DIR}/build/$HEROKUISH_PACKAGE_NAME" \ "${ROOT_DIR}/build/$LAMBDA_BUILDER_PACKAGE_NAME" \ @@ -136,7 +143,7 @@ check_container() { while [ $cnt -lt 100 ]; do echo "$(date) [count: $cnt]: waiting for dokku startup" is_up=$( - docker exec dokku ps -ef | grep "/usr/sbin/sshd -D" >/dev/null 2>&1 + docker exec dokku ps -ef | grep "/usr/sbin/sshd -D" &>/dev/null echo $? ) echo "" && docker logs dokku @@ -159,7 +166,6 @@ create_package() { build_dokku } -# shellcheck disable=SC2120 setup_circle() { install_dependencies install_dokku @@ -188,12 +194,10 @@ case "$1" in run_dokku_container ;; build) - # shellcheck disable=SC2119 create_package exit $? ;; *) - # shellcheck disable=SC2119 setup_circle exit $? ;; diff --git a/tests/ci/unit_test_runner.sh b/tests/ci/unit_test_runner.sh index 53f18d6d2..4d39c10c8 100755 --- a/tests/ci/unit_test_runner.sh +++ b/tests/ci/unit_test_runner.sh @@ -21,7 +21,6 @@ is_number "$BATCH_NUM" || usage TESTS=$(find "$(dirname "$0")/../unit" -maxdepth 1 -name "${BATCH_NUM}0*.bats" | sort -n | xargs) echo "running the following tests $TESTS" -# shellcheck disable=SC2086 for test in $TESTS; do echo $test starttest=$(date +%s) diff --git a/tests/shellcheck-exclude b/tests/shellcheck-exclude deleted file mode 100644 index 2049f4b80..000000000 --- a/tests/shellcheck-exclude +++ /dev/null @@ -1,32 +0,0 @@ -# SC1001 - This \/ will be a regular '/' in this context - https://github.com/koalaman/shellcheck/wiki/SC1001 -# SC1003 - Want to escape a single quote? echo 'This is how it'\''s done' - https://github.com/koalaman/shellcheck/wiki/SC1003 -# SC1090 - Can't follow non-constant source. Use a directive to specify location - https://github.com/koalaman/shellcheck/wiki/SC1090 -# SC1091 - Not following: FILE: does not exist (No such file or directory) - https://github.com/koalaman/shellcheck/wiki/SC1091 -# SC1117 - Backslash is literal in "\n". Prefer explicit escaping: "\\n" - https://github.com/koalaman/shellcheck/wiki/SC1117 -# SC2029 - Note that, unescaped, this expands on the client side - https://github.com/koalaman/shellcheck/wiki/SC2029 -# SC2030 - Modification of var is local (to subshell caused by pipeline). - https://github.com/koalaman/shellcheck/wiki/SC2030 -# SC2031 - var was modified in a subshell. That change might be lost. - https://github.com/koalaman/shellcheck/wiki/SC2031 -# SC2034 - VAR appears unused - https://github.com/koalaman/shellcheck/wiki/SC2034 -# SC2046 - Quote this to prevent word splitting - https://github.com/koalaman/shellcheck/wiki/SC2046 -# SC2064 - Use single quotes, otherwise this expands now rather than when signalled. - https://github.com/koalaman/shellcheck/wiki/SC2064 -# SC2068 - Double quote array expansions to avoid re-splitting elements - https://github.com/koalaman/shellcheck/wiki/SC2068 -# SC2086 - Double quote to prevent globbing and word splitting - https://github.com/koalaman/shellcheck/wiki/SC2086 -# SC2119 - Use 'function "$@"' if function's $1 should mean script's $1 - https://github.com/koalaman/shellcheck/wiki/SC2119 -# SC2120 - Function references arguments, but none are ever passed - https://github.com/koalaman/shellcheck/wiki/SC2120 -# SC2128 - Expanding an array without an index only gives the first element - https://github.com/koalaman/shellcheck/wiki/SC2128 -# SC2148 - Tips depend on target shell and yours is unknown. Add a shebang - https://github.com/koalaman/shellcheck/wiki/SC2148 -# SC2153 - Possible misspelling: DOKKU_ROOT may not be assigned, but DOKKU_PORT is - https://github.com/koalaman/shellcheck/wiki/SC2153 -# SC2154 - passed_docker_option is referenced but not assigned - https://github.com/koalaman/shellcheck/wiki/SC2154 -# SC2155 - Declare and assign separately to avoid masking return values - https://github.com/koalaman/shellcheck/wiki/SC2155 -# SC2162 - read without -r will mangle backslashes - https://github.com/koalaman/shellcheck/wiki/SC2162 -# SC2174 - When used with -p, -m only applies to the deepest directory - https://github.com/koalaman/shellcheck/wiki/SC2174 -# SC2179 - Use array+=("item") to append items to an array - https://github.com/koalaman/shellcheck/wiki/SC2179 -# SC2191 - The = here is literal. To assign by index, use ( [index]=value ) with no spaces. To keep as literal, quote it - https://github.com/koalaman/shellcheck/wiki/SC2191 -# SC2199 - Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @) - https://github.com/koalaman/shellcheck/wiki/SC2199 -# SC2207 - Prefer mapfile or read -a to split command output (or quote to avoid splitting) - https://github.com/koalaman/shellcheck/wiki/SC2207 -# SC2219 - Instead of 'let expr', prefer (( expr )) - https://github.com/koalaman/shellcheck/wiki/SC2219 -# SC2220 - Invalid flags are not handled. Add a *) case - https://github.com/koalaman/shellcheck/wiki/SC2220 -# SC2231 - Quote expansions in this for loop glob to prevent wordsplitting, e.g. "$dir"/*.txt - https://github.com/koalaman/shellcheck/wiki/SC2231 -# SC2235 - Use { ..; } instead of (..) to avoid subshell overhead - https://github.com/koalaman/shellcheck/wiki/SC2235 -# SC2267 - GNU xargs -i is deprecated in favor of -I{} - https://github.com/koalaman/shellcheck/wiki/SC2267 -# SC2295 - Expansions inside ${..} need to be quoted separately, otherwise they match as patterns - https://github.com/koalaman/shellcheck/wiki/SC2295 diff --git a/tests/test_deploy b/tests/test_deploy index fdc67866d..01ecbda18 100755 --- a/tests/test_deploy +++ b/tests/test_deploy @@ -13,7 +13,6 @@ REPO="test-$(basename "$APP")-$RANDOM" ssh "$REMOTE" events:on destroy_app() { - # shellcheck disable=SC2029 ssh "$REMOTE" apps:destroy "$REPO" --force ssh "$REMOTE" events:off } @@ -63,7 +62,6 @@ fi ssh "$REMOTE" events ssh "$REMOTE" apps:list -# shellcheck disable=SC2029 URL=$(ssh "$REMOTE" url "$REPO")$FORWARDED_PORT sleep 2 if (./check_deploy "$URL"); then diff --git a/tests/unit/app-json-2.bats b/tests/unit/app-json-2.bats index d5697fb10..bacf838dc 100644 --- a/tests/unit/app-json-2.bats +++ b/tests/unit/app-json-2.bats @@ -45,7 +45,7 @@ teardown() { } @test "(app-json) app.json dockerfile entrypoint release" { - run deploy_app dockerfile-entrypoint dokku@dokku.me:$TEST_APP add_release_command + run deploy_app dockerfile-entrypoint "dokku@$DOKKU_DOMAIN:$TEST_APP" add_release_command echo "output: $output" echo "status: $status" assert_success diff --git a/tests/unit/app-json-3.bats b/tests/unit/app-json-3.bats index 837699579..efc8de7f8 100644 --- a/tests/unit/app-json-3.bats +++ b/tests/unit/app-json-3.bats @@ -12,10 +12,10 @@ teardown() { } @test "(app-json) persist scale" { - local CUSTOM_TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local CUSTOM_TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM - CUSTOM_TMP="$CUSTOM_TMP" run deploy_app python dokku@dokku.me:$TEST_APP persist_scale_callback_a + CUSTOM_TMP="$CUSTOM_TMP" run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" persist_scale_callback_a echo "output: $output" echo "status: $status" assert_success diff --git a/tests/unit/app-json.bats b/tests/unit/app-json.bats index 693d28e3b..009009033 100644 --- a/tests/unit/app-json.bats +++ b/tests/unit/app-json.bats @@ -47,7 +47,7 @@ teardown() { } @test "(app-json) app.json scripts postdeploy" { - run deploy_app python dokku@dokku.me:$TEST_APP add_postdeploy_command + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_postdeploy_command echo "output: $output" echo "status: $status" assert_success @@ -73,7 +73,7 @@ teardown() { echo "status: $status" assert_success - run deploy_app python dokku@dokku.me:$TEST_APP add_requirements_txt + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt echo "output: $output" echo "status: $status" assert_success @@ -99,7 +99,7 @@ teardown() { echo "status: $status" assert_success - run deploy_app python dokku@dokku.me:$TEST_APP add_requirements_txt + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt echo "output: $output" echo "status: $status" assert_success @@ -181,7 +181,7 @@ teardown() { } @test "(app-json:set)" { - run deploy_app python dokku@dokku.me:$TEST_APP copy_app_json_to_sub_app + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP copy_app_json_to_sub_app echo "output: $output" echo "status: $status" assert_success diff --git a/tests/unit/apps_1.bats b/tests/unit/apps_1.bats index cb353a439..7b415606f 100644 --- a/tests/unit/apps_1.bats +++ b/tests/unit/apps_1.bats @@ -11,7 +11,7 @@ teardown() { } @test "(apps) apps:help" { - run /bin/bash -c "dokku apps" + run /bin/bash -c "dokku apps --help" echo "output: $output" echo "status: $status" assert_output_contains "Manage apps" @@ -55,11 +55,16 @@ teardown() { echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c "dokku apps:list | grep $TEST_APP" echo "output: $output" echo "status: $status" assert_output $TEST_APP - destroy_app + + run destroy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku apps:create 1994testapp" echo "output: $output" @@ -95,12 +100,16 @@ teardown() { echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c "dokku apps:list | grep $TEST_APP" echo "output: $output" echo "status: $status" assert_output $TEST_APP - destroy_app + run destroy_app + echo "output: $output" + echo "status: $status" + assert_success } @test "(apps) app autocreate disabled" { @@ -117,13 +126,21 @@ teardown() { } @test "(apps) apps:destroy" { - create_app + run create_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku --force apps:destroy $TEST_APP" echo "output: $output" echo "status: $status" assert_success - create_app + run create_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku --force --app $TEST_APP apps:destroy" echo "output: $output" echo "status: $status" @@ -131,7 +148,10 @@ teardown() { } @test "(apps) apps:rename" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku apps:rename $TEST_APP great-test-name" echo "output: $output" echo "status: $status" @@ -139,11 +159,11 @@ teardown() { run /bin/bash -c "dokku apps:list | grep $TEST_APP" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists run /bin/bash -c "curl --silent --write-out '%{http_code}\n' $(dokku url great-test-name) | grep 404" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku --force apps:destroy great-test-name" echo "output: $output" echo "status: $status" @@ -248,7 +268,11 @@ teardown() { echo "status: $status" assert_failure - create_app + run create_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku apps:exists $TEST_APP" echo "output: $output" echo "status: $status" @@ -262,7 +286,10 @@ teardown() { } @test "(apps) apps:lock/locked/unlock" { - create_app + run create_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku apps:report $TEST_APP --app-locked" echo "output: $output" diff --git a/tests/unit/apps_2.bats b/tests/unit/apps_2.bats index 2afb61fa8..6b62abdb8 100644 --- a/tests/unit/apps_2.bats +++ b/tests/unit/apps_2.bats @@ -11,7 +11,10 @@ teardown() { } @test "(apps) apps:clone" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku apps:clone $TEST_APP great-test-name" echo "output: $output" echo "status: $status" @@ -23,7 +26,7 @@ teardown() { run /bin/bash -c "curl --silent --write-out '%{http_code}\n' $(dokku url great-test-name) | grep 404" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku --force apps:destroy great-test-name" echo "output: $output" echo "status: $status" @@ -108,20 +111,35 @@ teardown() { } @test "(apps) apps:clone ssl-app" { - run /bin/bash -c "dokku config:set --no-restart $TEST_APP DOKKU_PROXY_PORT_MAP=https:443:5000 DOKKU_PROXY_SSL_PORT=443" - deploy_app + skip "this test always failed and requires changes in dokku to support detected vs specified functionality" + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku ports:set $TEST_APP https:443:5000" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku config:set --no-restart $TEST_APP DOKKU_PROXY_SSL_PORT=443" + echo "output: $output" + echo "status: $status" + assert_success + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku apps:clone $TEST_APP app-without-ssl" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku --quiet proxy:ports app-without-ssl | xargs" + run /bin/bash -c "dokku --quiet ports:list app-without-ssl | xargs" echo "output: $output" echo "status: $status" assert_output "http 80 5000" run /bin/bash -c "dokku config:get app-without-ssl DOKKU_PROXY_SSL_PORT" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku --force apps:destroy app-without-ssl" echo "output: $output" echo "status: $status" @@ -129,7 +147,10 @@ teardown() { } @test "(apps) apps:clone --skip-deploy" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku apps:clone --skip-deploy $TEST_APP great-test-name" echo "output: $output" echo "status: $status" @@ -157,7 +178,10 @@ teardown() { } @test "(apps) apps:clone --ignore-existing" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku apps:create great-test-name" echo "output: $output" echo "status: $status" diff --git a/tests/unit/build-env.bats b/tests/unit/build-env.bats index 2a0df8f61..c6407a4ad 100644 --- a/tests/unit/build-env.bats +++ b/tests/unit/build-env.bats @@ -18,7 +18,10 @@ teardown() { echo "status: $status" assert_success - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku config $TEST_APP" assert_success } @@ -34,7 +37,10 @@ teardown() { echo "status: $status" assert_success - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku config:get --global CURL_CONNECT_TIMEOUT | grep 90" echo "output: $output" echo "status: $status" @@ -59,12 +65,16 @@ teardown() { } @test "(build-env) buildpack deploy with Dockerfile" { - run /bin/bash -c "dokku config:set --no-restart $TEST_APP BUILDPACK_URL='https://github.com/heroku/heroku-buildpack-nodejs'" + run /bin/bash -c "dokku config:set --no-restart $TEST_APP BUILDPACK_URL='https://github.com/dokku/heroku-buildpack-null'" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP move_dockerfile_into_place echo "output: $output" echo "status: $status" assert_success - deploy_app dockerfile run /bin/bash -c "dokku --quiet config:get $TEST_APP DOKKU_APP_TYPE" echo "output: $output" echo "status: $status" @@ -72,7 +82,10 @@ teardown() { } @test "(build-env) DOKKU_ROOT cache bind is used by default" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success BUILD_CID=$(docker ps -a | grep $TEST_APP | grep /bin/bash | awk '{print $1}' | head -n1) run /bin/bash -c "docker inspect --format '{{ .HostConfig.Binds }}' $BUILD_CID | tr -d '[]' | cut -f1 -d:" diff --git a/tests/unit/builder-dockerfile.bats b/tests/unit/builder-dockerfile.bats index b9138e16f..34c80520b 100644 --- a/tests/unit/builder-dockerfile.bats +++ b/tests/unit/builder-dockerfile.bats @@ -4,9 +4,11 @@ load test_helper setup() { create_app + DOCKERFILE="$BATS_TMPDIR/Dockerfile" } teardown() { + rm -f "$DOCKERFILE" destroy_app } @@ -48,3 +50,47 @@ teardown() { assert_success assert_output_contains 'echo hi' 0 } + +@test "(builder-dockerfile) config export" { + run /bin/bash -c "dokku config:set $TEST_APP GITHUB_TOKEN=custom-value" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:add $TEST_APP build '--build-arg GITHUB_TOKEN'" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "TOKEN is: custom-value" 2 +} + +@test "(builder-dockerfile) port exposure (dockerfile raw port)" { + source "$PLUGIN_CORE_AVAILABLE_PATH/builder-dockerfile/internal-functions" + cat <$DOCKERFILE +EXPOSE 3001/udp +EXPOSE 3003 +EXPOSE 3000/tcp +EOF + run fn-builder-dockerfile-get-ports-from-dockerfile $DOCKERFILE + echo "output: $output" + echo "status: $status" + assert_output "3001/udp 3003 3000/tcp" +} + +@test "(builder-dockerfile) port exposure (dockerfile tcp port)" { + source "$PLUGIN_CORE_AVAILABLE_PATH/builder-dockerfile/internal-functions" + cat <$DOCKERFILE +EXPOSE 3001/udp +EXPOSE 3000/tcp +EXPOSE 3003 +EOF + run fn-builder-dockerfile-get-ports-from-dockerfile $DOCKERFILE + echo "output: $output" + echo "status: $status" + assert_output "3001/udp 3000/tcp 3003" +} diff --git a/tests/unit/builder-herokuish.bats b/tests/unit/builder-herokuish.bats index 356788e84..d462398e1 100644 --- a/tests/unit/builder-herokuish.bats +++ b/tests/unit/builder-herokuish.bats @@ -11,7 +11,7 @@ teardown() { } @test "(builder-herouish:build .env)" { - run deploy_app python dokku@dokku.me:$TEST_APP + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP echo "output: $output" echo "status: $status" assert_success diff --git a/tests/unit/builder-lambda.bats b/tests/unit/builder-lambda.bats index fe69ae51a..b6794a1a0 100644 --- a/tests/unit/builder-lambda.bats +++ b/tests/unit/builder-lambda.bats @@ -25,7 +25,7 @@ teardown() { echo "status: $status" assert_success - run deploy_app lambda-python dokku@dokku.me:$TEST_APP inject_lambda_yml + run deploy_app lambda-python dokku@$DOKKU_DOMAIN:$TEST_APP inject_lambda_yml echo "output: $output" echo "status: $status" assert_success diff --git a/tests/unit/builder-pack.bats b/tests/unit/builder-pack.bats index 4fd6d4cff..bbcc7589b 100644 --- a/tests/unit/builder-pack.bats +++ b/tests/unit/builder-pack.bats @@ -25,18 +25,17 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku buildpacks:set-property $TEST_APP stack heroku/buildpacks:20" + run /bin/bash -c "dokku buildpacks:set-property $TEST_APP stack heroku/builder:22" echo "output: $output" echo "status: $status" assert_success - run deploy_app python dokku@dokku.me:$TEST_APP inject_requirements_txt + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP inject_requirements_txt echo "output: $output" echo "status: $status" assert_success assert_output_contains 'Building with buildpack 1' 0 - assert_output_contains 'Installing requirements with pip' - assert_output_contains "Build time env var SECRET_KEY=fjdkslafjdk" + assert_output_contains 'Installing dependencies using Pip' run /bin/bash -c "dokku builder-pack:set $TEST_APP projecttoml-path nonexistent.toml" echo "output: $output" @@ -48,7 +47,7 @@ teardown() { echo "status: $status" assert_success assert_output_contains 'Building with buildpack 1' 0 - assert_output_contains 'Installing requirements with pip' + assert_output_contains 'Installing dependencies using Pip' run /bin/bash -c "dokku builder-pack:set $TEST_APP projecttoml-path project2.toml" echo "output: $output" @@ -60,7 +59,7 @@ teardown() { echo "status: $status" assert_success assert_output_contains 'Building with buildpack 1' - assert_output_contains 'Installing requirements with pip' 0 + assert_output_contains 'Installing dependencies using Pip' 0 run /bin/bash -c "dokku builder-pack:set $TEST_APP projecttoml-path" echo "output: $output" @@ -72,7 +71,7 @@ teardown() { echo "status: $status" assert_success assert_output_contains 'Building with buildpack 1' 0 - assert_output_contains 'Installing requirements with pip' + assert_output_contains 'Installing dependencies using Pip' } @test "(builder-pack) git:from-image without a Procfile" { diff --git a/tests/unit/builder.bats b/tests/unit/builder.bats index 9caf52f3c..8d6907fd6 100644 --- a/tests/unit/builder.bats +++ b/tests/unit/builder.bats @@ -11,7 +11,7 @@ teardown() { } @test "(builder) builder-detect [set]" { - local TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM # test project.toml @@ -79,7 +79,7 @@ teardown() { } @test "(builder) builder-detect [pack]" { - local TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM # test project.toml @@ -115,7 +115,7 @@ teardown() { } @test "(builder) builder-detect [dockerfile]" { - local TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM run touch "$TMP/Dockerfile" @@ -132,7 +132,7 @@ teardown() { } @test "(builder) builder-detect [herokuish]" { - local TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM touch "$TMP/Dockerfile" diff --git a/tests/unit/buildpacks.bats b/tests/unit/buildpacks.bats index cb0064380..acef76307 100644 --- a/tests/unit/buildpacks.bats +++ b/tests/unit/buildpacks.bats @@ -298,7 +298,7 @@ teardown() { } @test "(buildpacks) cleanup existing .buildpacks file" { - run deploy_app python dokku@dokku.me:$TEST_APP template_buildpacks_cleanup + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_buildpacks_cleanup echo "output: $output" echo "status: $status" assert_success diff --git a/tests/unit/caddy.bats b/tests/unit/caddy.bats index 2362ababd..d48d41196 100644 --- a/tests/unit/caddy.bats +++ b/tests/unit/caddy.bats @@ -38,7 +38,7 @@ teardown() { echo "status: $status" assert_success - run deploy_app python dokku@dokku.me:$TEST_APP convert_to_dockerfile + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP convert_to_dockerfile echo "output: $output" echo "status: $status" assert_success @@ -56,28 +56,28 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP-2.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP-2.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run deploy_app python dokku@dokku.me:$TEST_APP convert_to_dockerfile + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP convert_to_dockerfile echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "curl --silent $TEST_APP.dokku.me" + run /bin/bash -c "curl --silent $TEST_APP.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success assert_output "python/http.server" - run /bin/bash -c "curl --silent $TEST_APP-2.dokku.me" + run /bin/bash -c "curl --silent $TEST_APP-2.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -104,7 +104,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "$TEST_APP.dokku.me:80" + assert_output "$TEST_APP.${DOKKU_DOMAIN}:80" run /bin/bash -c "dokku caddy:set --global letsencrypt-email test@example.com" echo "output: $output" @@ -135,10 +135,15 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "$TEST_APP.dokku.me" + assert_output "$TEST_APP.${DOKKU_DOMAIN}" - run /bin/bash -c "dokku proxy:report $TEST_APP --proxy-port-map" + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" echo "output: $output" echo "status: $status" - assert_output "http:80:5000" + assert_output_not_exists + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000 https:443:5000" } diff --git a/tests/unit/checks.bats b/tests/unit/checks.bats index e337dad56..5a3886c1c 100644 --- a/tests/unit/checks.bats +++ b/tests/unit/checks.bats @@ -56,7 +56,7 @@ teardown() { run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_SKIPPED" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku checks:enable $TEST_APP" echo "output: $output" @@ -66,12 +66,12 @@ teardown() { run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_DISABLED" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_SKIPPED" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists } @test "(checks) checks:disable -> checks:skip" { @@ -88,7 +88,7 @@ teardown() { run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_SKIPPED" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku checks:skip $TEST_APP urgentworker,worker" echo "output: $output" @@ -132,7 +132,7 @@ teardown() { run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_DISABLED" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku checks:enable $TEST_APP" echo "output: $output" @@ -142,12 +142,12 @@ teardown() { run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_SKIPPED" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_DISABLED" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists } @test "(checks) checks:skip -> checks:disable" { @@ -164,7 +164,7 @@ teardown() { run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_DISABLED" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku checks:disable $TEST_APP urgentworker,worker" echo "output: $output" @@ -188,7 +188,10 @@ teardown() { echo "status: $status" assert_success - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku checks:run $TEST_APP" echo "output: $output" @@ -227,7 +230,10 @@ teardown() { echo "status: $status" assert_success - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku checks:disable $TEST_APP worker" echo "output: $output" @@ -256,9 +262,9 @@ teardown() { echo "status: $status" assert_success - run deploy_app nodejs-express dokku@dokku.me:$TEST_APP template_checks_file + run deploy_app nodejs-express dokku@$DOKKU_DOMAIN:$TEST_APP template_checks_file echo "output: $output" echo "status: $status" - assert_output_contains "/healthcheck" 2 + assert_output_contains "/healthcheck" assert_success } diff --git a/tests/unit/client.bats b/tests/unit/client.bats index a31b2d77b..bfb51a64d 100644 --- a/tests/unit/client.bats +++ b/tests/unit/client.bats @@ -4,7 +4,7 @@ load test_helper setup() { global_setup - export DOKKU_HOST=dokku.me + export "DOKKU_HOST=${DOKKU_DOMAIN}" create_app } @@ -134,29 +134,29 @@ teardown() { } @test "(client) domains:add" { - run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP www.test.app.dokku.me" + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP test.app.dokku.me" + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success } @test "(client) domains:remove" { - run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP test.app.dokku.me" + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:remove $TEST_APP test.app.dokku.me" + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:remove $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" - refute_line "test.app.dokku.me" + refute_line "test.app.${DOKKU_DOMAIN}" } @test "(client) domains:clear" { - run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP test.app.dokku.me" + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -171,7 +171,10 @@ teardown() { # # looks like docker exec is built to work with docker-under-libcontainer, # # but we're using docker-under-lxc. I don't have an estimated time for the fix, sorry # skip "circleci does not support docker exec at the moment." -# deploy_app +# run deploy_app +# echo "output: $output" +# echo "status: $status" +# assert_success # run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh ps $TEST_APP | grep -q 'node web.js'" # echo "output: $output" # echo "status: $status" @@ -179,7 +182,10 @@ teardown() { # } @test "(client) ps:start" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh ps:stop $TEST_APP" echo "output: $output" echo "status: $status" @@ -197,7 +203,10 @@ teardown() { } @test "(client) ps:stop" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh ps:stop $TEST_APP" echo "output: $output" echo "status: $status" @@ -211,7 +220,10 @@ teardown() { } @test "(client) ps:restart" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh ps:restart $TEST_APP" echo "output: $output" echo "status: $status" @@ -261,7 +273,7 @@ teardown() { assert_success assert_output "dokku" - run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:add dokku2 dokku@dokku.me:dokku2" + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:add dokku2 dokku@${DOKKU_DOMAIN}:dokku2" echo "output: $output" echo "status: $status" assert_success diff --git a/tests/unit/config.bats b/tests/unit/config.bats index 610fd0a63..d8d8d1808 100644 --- a/tests/unit/config.bats +++ b/tests/unit/config.bats @@ -19,21 +19,28 @@ teardown() { } @test "(config) config:help" { + run /bin/bash -c "dokku config --help" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage global and app-specific config vars" + help_output="$output" + run /bin/bash -c "dokku config:help" echo "output: $output" echo "status: $status" assert_output_contains "Manage global and app-specific config vars" + assert_output "$help_output" } @test "(config) config:set --global" { - run ssh dokku@dokku.me config:set --global test_var=true test_var2=\"hello world\" test_var3='double\"quotes' + run ssh "dokku@$DOKKU_DOMAIN" config:set --global test_var=true test_var2=\"hello world\" test_var3='double\"quotes' echo "output: $output" echo "status: $status" assert_success } @test "(config) config:get --global" { - run ssh dokku@dokku.me config:set --global test_var=true test_var2=\"hello world\" test_var3=\"with\\nnewline\" test_var4='double\"quotes' + run ssh "dokku@$DOKKU_DOMAIN" config:set --global test_var=true test_var2=\"hello world\" test_var3=\"with\\nnewline\" test_var4='double\"quotes' echo "output: $output" echo "status: $status" assert_success @@ -52,7 +59,7 @@ teardown() { } @test "(config) config:unset --global" { - run ssh dokku@dokku.me config:set --global test_var=true test_var2=\"hello world\" + run ssh "dokku@$DOKKU_DOMAIN" config:set --global test_var=true test_var2=\"hello world\" echo "output: $output" echo "status: $status" assert_success @@ -67,11 +74,11 @@ teardown() { run /bin/bash -c "dokku config:get --global test_var" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists } @test "(config) config:set/get" { - run ssh dokku@dokku.me config:set $TEST_APP test_var1=true test_var2=\"hello world\" test_var3='double\"quotes' + run ssh "dokku@$DOKKU_DOMAIN" config:set $TEST_APP test_var1=true test_var2=\"hello world\" test_var3='double\"quotes' echo "output: $output" echo "status: $status" assert_success @@ -118,7 +125,7 @@ teardown() { } @test "(config) config:clear" { - run ssh dokku@dokku.me config:set $TEST_APP test_var=true test_var2=\"hello world\" test_var3=\"with\\nnewline\" + run ssh "dokku@$DOKKU_DOMAIN" config:set $TEST_APP test_var=true test_var2=\"hello world\" test_var3=\"with\\nnewline\" echo "output: $output" echo "status: $status" assert_success @@ -129,11 +136,11 @@ teardown() { run /bin/bash -c "dokku config:get $TEST_APP test_var" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists } @test "(config) config:unset" { - run ssh dokku@dokku.me config:set $TEST_APP test_var=true test_var2=\"hello world\" test_var3=\"with\\nnewline\" + run ssh "dokku@$DOKKU_DOMAIN" config:set $TEST_APP test_var=true test_var2=\"hello world\" test_var3=\"with\\nnewline\" echo "output: $output" echo "status: $status" assert_success @@ -148,7 +155,7 @@ teardown() { run /bin/bash -c "dokku config:get $TEST_APP test_var" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku config:get $TEST_APP test_var3" echo "output: $output" echo "status: $status" @@ -160,7 +167,7 @@ teardown() { run /bin/bash -c "dokku config:get $TEST_APP test_var3" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku config:unset $TEST_APP" echo "output: $output" echo "status: $status" diff --git a/tests/unit/core_1.bats b/tests/unit/core_1.bats index 9d2413484..1c9d32606 100644 --- a/tests/unit/core_1.bats +++ b/tests/unit/core_1.bats @@ -16,7 +16,10 @@ teardown() { } @test "(core) remove exited containers" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success # make sure we have many exited containers of the same 'type' run /bin/bash -c "for cnt in 1 2 3; do dokku run $TEST_APP echo $TEST_APP; done" @@ -43,32 +46,6 @@ teardown() { docker rm $RANDOM_RUN_CID } -@test "(core) port exposure (dockerfile raw port)" { - source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" - cat <$DOCKERFILE -EXPOSE 3001/udp -EXPOSE 3003 -EXPOSE 3000/tcp -EOF - run get_dockerfile_exposed_ports $DOCKERFILE - echo "output: $output" - echo "status: $status" - assert_output "3001/udp 3003 3000/tcp" -} - -@test "(core) port exposure (dockerfile tcp port)" { - source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" - cat <$DOCKERFILE -EXPOSE 3001/udp -EXPOSE 3000/tcp -EXPOSE 3003 -EOF - run get_dockerfile_exposed_ports $DOCKERFILE - echo "output: $output" - echo "status: $status" - assert_output "3001/udp 3000/tcp 3003" -} - @test "(core) image type detection (herokuish default user)" { run deploy_app echo "output: $output" @@ -89,7 +66,11 @@ EOF CID=$(<"$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1") docker commit --change "ENV USER postgres" "$CID" "dokku/${TEST_APP}:latest" - dokku config:set --no-restart "$TEST_APP" DOKKU_APP_USER=postgres + run /bin/bash -c "dokku config:set --no-restart $TEST_APP DOKKU_APP_USER=postgres" + echo "output: $output" + echo "status: $status" + assert_success + source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_CORE_AVAILABLE_PATH/config/functions" diff --git a/tests/unit/core_2.bats b/tests/unit/core_2.bats index 43c902616..26cf2708c 100644 --- a/tests/unit/core_2.bats +++ b/tests/unit/core_2.bats @@ -38,13 +38,18 @@ teardown() { } @test "(core) urls (non-ssl)" { - assert_urls "http://${TEST_APP}.dokku.me" - dokku domains:add $TEST_APP "test.dokku.me" - assert_urls "http://${TEST_APP}.dokku.me" "http://test.dokku.me" + assert_urls "http://${TEST_APP}.${DOKKU_DOMAIN}" + + run /bin/bash -c "dokku domains:add $TEST_APP test.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + assert_urls "http://${TEST_APP}.${DOKKU_DOMAIN}" "http://test.${DOKKU_DOMAIN}" } @test "(core) urls (app ssl)" { - assert_urls "http://${TEST_APP}.dokku.me" + assert_urls "http://${TEST_APP}.${DOKKU_DOMAIN}" run setup_test_tls echo "output: $output" @@ -56,14 +61,14 @@ teardown() { echo "status: $status" assert_success - assert_urls "https://${TEST_APP}.dokku.me" + assert_urls "https://${TEST_APP}.${DOKKU_DOMAIN}" - run /bin/bash -c "dokku domains:add $TEST_APP test.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP test.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - assert_urls "http://${TEST_APP}.dokku.me" "https://${TEST_APP}.dokku.me" "https://test.dokku.me" "http://test.dokku.me" + assert_urls "http://${TEST_APP}.${DOKKU_DOMAIN}" "https://${TEST_APP}.${DOKKU_DOMAIN}" "https://test.${DOKKU_DOMAIN}" "http://test.${DOKKU_DOMAIN}" } @test "(core) url (app ssl)" { @@ -77,7 +82,7 @@ teardown() { echo "status: $status" assert_success - assert_url "https://${TEST_APP}.dokku.me" + assert_url "https://${TEST_APP}.${DOKKU_DOMAIN}" } @test "(core) urls (wildcard ssl)" { @@ -91,20 +96,20 @@ teardown() { echo "status: $status" assert_success - assert_urls "https://${TEST_APP}.dokku.me" + assert_urls "https://${TEST_APP}.${DOKKU_DOMAIN}" - run /bin/bash -c "dokku domains:add $TEST_APP test.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP test.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - assert_urls "http://${TEST_APP}.dokku.me" "https://${TEST_APP}.dokku.me" "https://test.dokku.me" "http://test.dokku.me" + assert_urls "http://${TEST_APP}.${DOKKU_DOMAIN}" "https://${TEST_APP}.${DOKKU_DOMAIN}" "https://test.${DOKKU_DOMAIN}" "http://test.${DOKKU_DOMAIN}" run /bin/bash -c "dokku domains:add $TEST_APP dokku.example.com" echo "output: $output" echo "status: $status" assert_success - assert_urls "http://dokku.example.com" "http://${TEST_APP}.dokku.me" "https://dokku.example.com" "https://${TEST_APP}.dokku.me" "https://test.dokku.me" "http://test.dokku.me" + assert_urls "http://dokku.example.com" "http://${TEST_APP}.${DOKKU_DOMAIN}" "https://dokku.example.com" "https://${TEST_APP}.${DOKKU_DOMAIN}" "https://test.${DOKKU_DOMAIN}" "http://test.${DOKKU_DOMAIN}" } @test "(core) git-remote (off-port)" { diff --git a/tests/unit/cron.bats b/tests/unit/cron.bats index 6f7f20afd..8247e0252 100644 --- a/tests/unit/cron.bats +++ b/tests/unit/cron.bats @@ -45,28 +45,28 @@ teardown() { } @test "(cron) invalid [missing-keys]" { - run deploy_app python dokku@dokku.me:$TEST_APP template_cron_file_invalid + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_invalid echo "output: $output" echo "status: $status" assert_failure } @test "(cron) invalid [schedule]" { - run deploy_app python dokku@dokku.me:$TEST_APP template_cron_file_invalid_schedule + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_invalid_schedule echo "output: $output" echo "status: $status" assert_failure } @test "(cron) invalid [seconds]" { - run deploy_app python dokku@dokku.me:$TEST_APP template_cron_file_invalid_schedule_seconds + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_invalid_schedule_seconds echo "output: $output" echo "status: $status" assert_failure } @test "(cron) create [empty]" { - run deploy_app python dokku@dokku.me:$TEST_APP + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP echo "output: $output" echo "status: $status" assert_success @@ -78,7 +78,7 @@ teardown() { } @test "(cron) create [single-verbose]" { - run deploy_app python dokku@dokku.me:$TEST_APP template_cron_file_valid + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_single echo "output: $output" echo "status: $status" assert_success @@ -91,7 +91,7 @@ teardown() { } @test "(cron) create [single-short]" { - run deploy_app python dokku@dokku.me:$TEST_APP template_cron_file_valid_short + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_short echo "output: $output" echo "status: $status" assert_success @@ -104,7 +104,7 @@ teardown() { } @test "(cron) create [multiple]" { - run deploy_app python dokku@dokku.me:$TEST_APP template_cron_file_valid_multiple + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_multiple echo "output: $output" echo "status: $status" assert_success @@ -177,6 +177,59 @@ teardown() { assert_failure } +@test "(cron) cron:list --format json" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku cron:list $TEST_APP --format json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists +} + +@test "(cron) cron:run" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid + echo "output: $output" + echo "status: $status" + assert_success + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'schedule']" + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[1].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'schedule', 'now']" +} + template_cron_file_invalid() { local APP="$1" local APP_REPO_DIR="$2" @@ -238,12 +291,32 @@ template_cron_file_valid() { { "command": "python task.py schedule", "schedule": "5 5 5 5 5" + }, + { + "command": "python task.py schedule now", + "schedule": "6 5 5 5 5" } ] } EOF } +template_cron_file_valid_single() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + echo "injecting valid cron app.json -> $APP_REPO_DIR/app.json" + cat <"$APP_REPO_DIR/app.json" +{ + "cron": [ + { + "command": "python task.py schedule", + "schedule": "5 5 5 5 5" + } + ] +} +EOF +} template_cron_file_valid_short() { local APP="$1" local APP_REPO_DIR="$2" diff --git a/tests/unit/docker-options.bats b/tests/unit/docker-options.bats index ca0d350ff..8ea8cf61f 100644 --- a/tests/unit/docker-options.bats +++ b/tests/unit/docker-options.bats @@ -323,7 +323,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains "One or more build-args \[PAYPAL_CLIENT_ID PAYPAL_CLIENT_MODE\] were not consumed" + # assert_output_contains "One or more build-args \[PAYPAL_CLIENT_ID PAYPAL_CLIENT_MODE\] were not consumed" CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect -f '{{ .Config.Volumes }}' $CID | sed -e 's:map::g' | tr -d '[]' | tr ' ' $'\n' | sort | xargs" @@ -333,7 +333,7 @@ teardown() { } @test "(docker-options) docker-options:add (all phases over SSH)" { - run ssh dokku@dokku.me docker-options:add $TEST_APP build,deploy,run "-v /tmp" + run ssh "dokku@$DOKKU_DOMAIN" docker-options:add $TEST_APP build,deploy,run "-v /tmp" echo "output: $output" echo "status: $status" assert_success @@ -368,5 +368,5 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains "TOKEN is: hello" + assert_output_contains "TOKEN is: hello" 2 } diff --git a/tests/unit/domains.bats b/tests/unit/domains.bats index 377ae71fe..24f0571d5 100644 --- a/tests/unit/domains.bats +++ b/tests/unit/domains.bats @@ -29,19 +29,19 @@ teardown() { } @test "(domains) domains" { - run /bin/bash -c "dokku domains:report $TEST_APP 2>/dev/null | grep ${TEST_APP}.dokku.me" + run /bin/bash -c "dokku domains:report $TEST_APP 2>/dev/null | grep ${TEST_APP}.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" - assert_output_contains "${TEST_APP}.dokku.me" + assert_output_contains "${TEST_APP}.${DOKKU_DOMAIN}" } @test "(domains) domains:add" { - run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:add $TEST_APP 2.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP 2.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -51,7 +51,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku domains:add $TEST_APP .dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP .${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -65,14 +65,14 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains www.test.app.dokku.me - assert_output_contains 2.app.dokku.me + assert_output_contains "www.test.app.${DOKKU_DOMAIN}" + assert_output_contains "2.app.${DOKKU_DOMAIN}" assert_output_contains a--domain.with--hyphens assert_output_contains _ } @test "(domains) domains:add (multiple)" { - run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.dokku.me 2.app.dokku.me a--domain.with--hyphens" + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN} 2.app.${DOKKU_DOMAIN} a--domain.with--hyphens" echo "output: $output" echo "status: $status" assert_success @@ -81,37 +81,37 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains www.test.app.dokku.me - assert_output_contains 2.app.dokku.me + assert_output_contains "www.test.app.${DOKKU_DOMAIN}" + assert_output_contains "2.app.${DOKKU_DOMAIN}" assert_output_contains a--domain.with--hyphens } @test "(domains) domains:add (duplicate)" { - run /bin/bash -c "dokku domains:add $TEST_APP test.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:add $TEST_APP test.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success } @test "(domains) domains:add (invalid)" { - run /bin/bash -c "dokku domains:add $TEST_APP http://test.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP http://test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_failure } @test "(domains) domains:remove" { - run /bin/bash -c "dokku domains:add $TEST_APP test.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:remove $TEST_APP test.app.dokku.me" + run /bin/bash -c "dokku domains:remove $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -120,16 +120,16 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains test.app.dokku.me 0 + assert_output_contains "test.app.${DOKKU_DOMAIN}" 0 } @test "(domains) domains:remove (multiple)" { - run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.dokku.me test.app.dokku.me 2.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN} test.app.${DOKKU_DOMAIN} 2.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:remove $TEST_APP www.test.app.dokku.me test.app.dokku.me" + run /bin/bash -c "dokku domains:remove $TEST_APP www.test.app.${DOKKU_DOMAIN} test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -138,13 +138,13 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains www.test.app.dokku.me 0 - assert_output_contains test.app.dokku.me 0 - assert_output_contains 2.app.dokku.me + assert_output_contains "www.test.app.${DOKKU_DOMAIN}" 0 + assert_output_contains "test.app.${DOKKU_DOMAIN}" 0 + assert_output_contains "2.app.${DOKKU_DOMAIN}" } @test "(domains) domains:remove (wildcard domain)" { - run /bin/bash -c "dokku domains:add $TEST_APP '*.dokku.me'" + run /bin/bash -c "dokku domains:add $TEST_APP '*.${DOKKU_DOMAIN}'" echo "output: $output" echo "status: $status" assert_success @@ -153,9 +153,9 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "$TEST_APP.dokku.me *.dokku.me" + assert_output "$TEST_APP.${DOKKU_DOMAIN} *.${DOKKU_DOMAIN}" - run /bin/bash -c "dokku domains:remove $TEST_APP '*.dokku.me'" + run /bin/bash -c "dokku domains:remove $TEST_APP '*.${DOKKU_DOMAIN}'" echo "output: $output" echo "status: $status" assert_success @@ -164,16 +164,16 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "$TEST_APP.dokku.me" + assert_output "$TEST_APP.${DOKKU_DOMAIN}" } @test "(domains) domains:set" { - run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.dokku.me test.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN} test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:set $TEST_APP 2.app.dokku.me a--domain.with--hyphens" + run /bin/bash -c "dokku domains:set $TEST_APP 2.app.${DOKKU_DOMAIN} a--domain.with--hyphens" echo "output: $output" echo "status: $status" assert_success @@ -182,14 +182,14 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains www.test.app.dokku.me 0 - assert_output_contains test.app.dokku.me 0 - assert_output_contains 2.app.dokku.me + assert_output_contains "www.test.app.${DOKKU_DOMAIN}" 0 + assert_output_contains "test.app.${DOKKU_DOMAIN}" 0 + assert_output_contains "2.app.${DOKKU_DOMAIN}" assert_output_contains a--domain.with--hyphens } @test "(domains) domains:clear" { - run /bin/bash -c "dokku domains:add $TEST_APP test.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -208,11 +208,11 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains test.app.dokku.me 0 + assert_output_contains "test.app.${DOKKU_DOMAIN}" 0 } @test "(domains) domains:add-global" { - run /bin/bash -c "dokku domains:add-global global.dokku.me" + run /bin/bash -c "dokku domains:add-global global.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -222,29 +222,29 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku domains:report 2>/dev/null | grep -qw 'global.dokku.me'" + run /bin/bash -c "dokku domains:report 2>/dev/null | grep -qw 'global.${DOKKU_DOMAIN}'" echo "output: $output" echo "status: $status" assert_success } @test "(domains) domains:add-global (multiple)" { - run /bin/bash -c "dokku domains:add-global global1.dokku.me global2.dokku.me global3.dokku.me" + run /bin/bash -c "dokku domains:add-global global1.${DOKKU_DOMAIN} global2.${DOKKU_DOMAIN} global3.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:report 2>/dev/null | grep -q global1.dokku.me" + run /bin/bash -c "dokku domains:report 2>/dev/null | grep -q global1.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:report 2>/dev/null | grep -q global2.dokku.me" + run /bin/bash -c "dokku domains:report 2>/dev/null | grep -q global2.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:report 2>/dev/null | grep -q global3.dokku.me" + run /bin/bash -c "dokku domains:report 2>/dev/null | grep -q global3.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -256,7 +256,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku domains:add-global global.dokku.me" + run /bin/bash -c "dokku domains:add-global global.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -266,23 +266,23 @@ teardown() { echo "status: $status" assert_success refute_line "global.dokku.invalid" - refute_line "global.dokku.me" + refute_line "global.${DOKKU_DOMAIN}" } @test "(domains) domains:remove-global" { - run /bin/bash -c "dokku domains:add-global global.dokku.me" + run /bin/bash -c "dokku domains:add-global global.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:remove-global global.dokku.me" + run /bin/bash -c "dokku domains:remove-global global.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" - refute_line "global.dokku.me" + refute_line "global.${DOKKU_DOMAIN}" } @test "(domains) domains (multiple global domains)" { - run /bin/bash -c "dokku domains:add-global global1.dokku.me global2.dokku.me" + run /bin/bash -c "dokku domains:add-global global1.${DOKKU_DOMAIN} global2.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -301,17 +301,17 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains ${TEST_APP}.global1.dokku.me - assert_output_contains ${TEST_APP}.global2.dokku.me + assert_output_contains "${TEST_APP}.global1.${DOKKU_DOMAIN}" + assert_output_contains "${TEST_APP}.global2.${DOKKU_DOMAIN}" } @test "(domains) domains:set-global" { - run /bin/bash -c "dokku domains:add-global global1.dokku.me global2.dokku.me" + run /bin/bash -c "dokku domains:add-global global1.${DOKKU_DOMAIN} global2.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:set-global global3.dokku.me global4.dokku.me" + run /bin/bash -c "dokku domains:set-global global3.${DOKKU_DOMAIN} global4.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -330,10 +330,10 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains ${TEST_APP}.global1.dokku.me 0 - assert_output_contains ${TEST_APP}.global2.dokku.me 0 - assert_output_contains ${TEST_APP}.global3.dokku.me - assert_output_contains ${TEST_APP}.global4.dokku.me + assert_output_contains "${TEST_APP}.global1.${DOKKU_DOMAIN}" 0 + assert_output_contains "${TEST_APP}.global2.${DOKKU_DOMAIN}" 0 + assert_output_contains "${TEST_APP}.global3.${DOKKU_DOMAIN}" + assert_output_contains "${TEST_APP}.global4.${DOKKU_DOMAIN}" } @test "(domains) app name overlaps with global domain.tld" { @@ -342,9 +342,16 @@ teardown() { echo "status: $status" assert_success + run /bin/bash -c "dokku --quiet apps:create test.dokku.test" + echo "output: $output" + echo "status: $status" + assert_success + # run domains:clear in order to invoke default vhost creation - dokku --quiet apps:create test.dokku.test - dokku --quiet domains:clear test.dokku.test + run /bin/bash -c "dokku --quiet domains:clear test.dokku.test" + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku domains:report test.dokku.test --domains-app-vhosts" echo "output: $output" @@ -352,16 +359,19 @@ teardown() { assert_success assert_output "test.dokku.test" - dokku --force apps:destroy test.dokku.test + run /bin/bash -c "dokku --force apps:destroy test.dokku.test" + echo "output: $output" + echo "status: $status" + assert_success } @test "(domains) app rename only renames domains associated with global domains" { - run /bin/bash -c "dokku domains:set-global dokku.me" + run /bin/bash -c "dokku domains:set-global ${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me $TEST_APP.dokku.test" + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.${DOKKU_DOMAIN} $TEST_APP.dokku.test" echo "output: $output" echo "status: $status" assert_success @@ -375,7 +385,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "other-name.dokku.me $TEST_APP.dokku.test" + assert_output "other-name.${DOKKU_DOMAIN} $TEST_APP.dokku.test" run /bin/bash -c "dokku apps:rename other-name $TEST_APP" echo "output: $output" diff --git a/tests/unit/events.bats b/tests/unit/events.bats index 65a3cc7e6..ab80d2e94 100644 --- a/tests/unit/events.bats +++ b/tests/unit/events.bats @@ -24,14 +24,17 @@ teardown() { echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c "test -f /etc/rsyslog.d/99-dokku.conf" echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c "stat -c '%U:%G:%a' /var/log/dokku/" echo "output: $output" echo "status: $status" assert_output "syslog:dokku:775" + run /bin/bash -c "stat -c '%U:%G:%a' /var/log/dokku/events.log" echo "output: $output" echo "status: $status" @@ -40,10 +43,22 @@ teardown() { @test "(events) log commands" { run /bin/bash -c "dokku events:on" - deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku events" echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c "dokku events:off" + echo "output: $output" + echo "status: $status" + assert_success } diff --git a/tests/unit/git_1.bats b/tests/unit/git_1.bats index 760b90f6f..2c24971d7 100644 --- a/tests/unit/git_1.bats +++ b/tests/unit/git_1.bats @@ -52,7 +52,7 @@ teardown() { run /bin/bash -c "dokku config:get $TEST_APP GIT_REV" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists } @test "(git) customize the GIT_REV environment variable" { diff --git a/tests/unit/git_2.bats b/tests/unit/git_2.bats index 9b13c1632..4e0d1ef2c 100644 --- a/tests/unit/git_2.bats +++ b/tests/unit/git_2.bats @@ -94,7 +94,10 @@ teardown() { echo "status: $status" assert_success - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "test -d $DOKKU_ROOT/$TEST_APP/refs" echo "output: $output" diff --git a/tests/unit/git_4.bats b/tests/unit/git_4.bats index 601df2b64..2d29aec68 100644 --- a/tests/unit/git_4.bats +++ b/tests/unit/git_4.bats @@ -83,7 +83,7 @@ teardown() { } @test "(git) git:from-image [failing deploy]" { - local CUSTOM_TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local CUSTOM_TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$CUSTOM_TMP"' INT TERM rmdir "$CUSTOM_TMP" && cp -r "${BATS_TEST_DIRNAME}/../../tests/apps/python" "$CUSTOM_TMP" @@ -157,7 +157,7 @@ teardown() { } @test "(git) git:from-image [onbuild]" { - local TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM run /bin/bash -c "dokku storage:mount $TEST_APP /var/run/docker.sock:/var/run/docker.sock" diff --git a/tests/unit/git_5.bats b/tests/unit/git_5.bats index 016eab882..be7ef97bd 100644 --- a/tests/unit/git_5.bats +++ b/tests/unit/git_5.bats @@ -34,7 +34,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success http://${TEST_APP}.dokku.me + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}" } @test "(git) git:from-archive [tar.gz]" { @@ -42,7 +42,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success http://${TEST_APP}.dokku.me + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}" } @test "(git) git:from-archive [zip]" { @@ -50,7 +50,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success http://${TEST_APP}.dokku.me + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}" } @test "(git) git:from-archive [stdin-file]" { @@ -63,7 +63,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success http://${TEST_APP}.dokku.me + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}" } @test "(git) git:from-archive [stdin-curl]" { @@ -71,5 +71,5 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success http://${TEST_APP}.dokku.me + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}" } diff --git a/tests/unit/git_7.bats b/tests/unit/git_7.bats index b26fe0729..03c81f264 100644 --- a/tests/unit/git_7.bats +++ b/tests/unit/git_7.bats @@ -103,7 +103,7 @@ teardown() { } @test "(git) git:load-image [failing deploy]" { - local CUSTOM_TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local CUSTOM_TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$CUSTOM_TMP"' INT TERM rmdir "$CUSTOM_TMP" && cp -r "${BATS_TEST_DIRNAME}/../../tests/apps/python" "$CUSTOM_TMP" @@ -187,7 +187,7 @@ teardown() { } @test "(git) git:load-image [onbuild]" { - local TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM run /bin/bash -c "dokku storage:mount $TEST_APP /var/run/docker.sock:/var/run/docker.sock" diff --git a/tests/unit/haproxy.bats b/tests/unit/haproxy.bats index cf74e6f67..dc4f09218 100644 --- a/tests/unit/haproxy.bats +++ b/tests/unit/haproxy.bats @@ -55,7 +55,7 @@ teardown() { echo "status: $status" assert_success - run deploy_app python dokku@dokku.me:$TEST_APP convert_to_dockerfile + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP convert_to_dockerfile echo "output: $output" echo "status: $status" assert_success @@ -73,28 +73,28 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP-2.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP-2.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run deploy_app python dokku@dokku.me:$TEST_APP convert_to_dockerfile + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP convert_to_dockerfile echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "curl --silent $TEST_APP.dokku.me" + run /bin/bash -c "curl --silent $TEST_APP.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success assert_output "python/http.server" - run /bin/bash -c "curl --silent $TEST_APP-2.dokku.me" + run /bin/bash -c "curl --silent $TEST_APP-2.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -154,8 +154,13 @@ teardown() { assert_success assert_output "true" - run /bin/bash -c "dokku proxy:report $TEST_APP --proxy-port-map" + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" echo "output: $output" echo "status: $status" - assert_output "http:80:5000" + assert_output_not_exists + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000 https:443:5000" } diff --git a/tests/unit/init.bats b/tests/unit/init.bats index 5455e0626..1cd143a32 100644 --- a/tests/unit/init.bats +++ b/tests/unit/init.bats @@ -15,7 +15,11 @@ teardown() { @test "(init) buildpack" { source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" local APP="zombies-buildpack" - deploy_app "$APP" + run deploy_app "$APP" + echo "output: $output" + echo "status: $status" + assert_success + local CIDS=$(get_app_container_ids "$APP") run "$DOCKER_BIN" container top "$CIDS" @@ -26,7 +30,11 @@ teardown() { @test "(init) dockerfile no tini" { source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" local APP="zombies-dockerfile-no-tini" - deploy_app "$APP" + run deploy_app "$APP" + echo "output: $output" + echo "status: $status" + assert_success + local CIDS=$(get_app_container_ids "$APP") run "$DOCKER_BIN" container top "$CIDS" @@ -37,7 +45,11 @@ teardown() { @test "(init) dockerfile with tini" { source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" local APP="zombies-dockerfile-tini" - deploy_app "$APP" + run deploy_app "$APP" + echo "output: $output" + echo "status: $status" + assert_success + local CIDS=$(get_app_container_ids "$APP") run "$DOCKER_BIN" container top "$CIDS" diff --git a/tests/unit/logs.bats b/tests/unit/logs.bats index 7d5f5e950..6d3624114 100644 --- a/tests/unit/logs.bats +++ b/tests/unit/logs.bats @@ -181,7 +181,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku logs:report $TEST_APP --logs-max-size 2>&1" echo "output: $output" @@ -235,7 +235,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "" + assert_output_not_exists } @test "(logs) logs:set escaped uri" { @@ -425,7 +425,7 @@ teardown() { fi driver="$(jq -r '."log-driver"' /etc/docker/daemon.json)" - local TMP_FILE=$(mktemp "/tmp/dokku.me.XXXX") + local TMP_FILE=$(mktemp "/tmp/${DOKKU_DOMAIN}.XXXX") run create_app echo "output: $output" @@ -532,7 +532,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "" + assert_output_not_exists } @test "(logs) logs:vector" { diff --git a/tests/unit/network.bats b/tests/unit/network.bats index 9a5abb6bd..db2831c65 100644 --- a/tests/unit/network.bats +++ b/tests/unit/network.bats @@ -9,7 +9,7 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" docker network rm create-network || true docker network rm deploy-network || true @@ -32,8 +32,11 @@ teardown() { } @test "(network) network:set bind-all-interfaces" { - deploy_app - assert_nonssl_domain "${TEST_APP}.dokku.me" + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + assert_nonssl_domain "${TEST_APP}.${DOKKU_DOMAIN}" run /bin/bash -c "dokku network:set $TEST_APP bind-all-interfaces true" echo "output: $output" @@ -44,7 +47,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success "${TEST_APP}.dokku.me" + assert_http_success "${TEST_APP}.${DOKKU_DOMAIN}" for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do assert_external_port $(<$CID_FILE) @@ -59,7 +62,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success "${TEST_APP}.dokku.me" + assert_http_success "${TEST_APP}.${DOKKU_DOMAIN}" for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do assert_not_external_port $(<$CID_FILE) @@ -156,11 +159,21 @@ teardown() { echo "status: $status" assert_success + run /bin/bash -c "dokku network:create create-network-2" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku network:create deploy-network" echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c "dokku network:create deploy-network-2" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku network:create initial-network" echo "output: $output" echo "status: $status" @@ -180,7 +193,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_failure - assert_http_success "${TEST_APP}.dokku.me" + assert_http_success "${TEST_APP}.${DOKKU_DOMAIN}" run /bin/bash -c "dokku network:set $TEST_APP attach-post-create create-network" echo "output: $output" @@ -206,7 +219,23 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success "${TEST_APP}.dokku.me" + assert_http_success "${TEST_APP}.${DOKKU_DOMAIN}" + + run /bin/bash -c "dokku network:set $TEST_APP attach-post-create create-network create-network-2" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:set $TEST_APP attach-post-deploy deploy-network deploy-network-2" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_http_success "${TEST_APP}.${DOKKU_DOMAIN}" run /bin/bash -c "dokku --force network:destroy create-network" echo "output: $output" @@ -224,7 +253,15 @@ teardown() { assert_success # necessary in order to remove networks in use by "dead" containers - docker container prune --force + run /bin/bash -c "docker container prune --force" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker container ls -a" + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku --force network:destroy create-network" echo "output: $output" diff --git a/tests/unit/nginx-vhosts_1.bats b/tests/unit/nginx-vhosts_1.bats index 56c21ab54..265a84202 100644 --- a/tests/unit/nginx-vhosts_1.bats +++ b/tests/unit/nginx-vhosts_1.bats @@ -29,8 +29,15 @@ teardown() { } @test "(nginx-vhosts) proxy:build-config (domains:disable/enable)" { - deploy_app - dokku domains:disable $TEST_APP + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:disable $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success HOSTNAME=$(<"$DOKKU_ROOT/VHOST") check_urls http://${HOSTNAME}:[0-9]+ @@ -40,29 +47,42 @@ teardown() { assert_http_success $URL done - dokku domains:enable $TEST_APP - check_urls http://${TEST_APP}.dokku.me - assert_http_success http://${TEST_APP}.dokku.me + run /bin/bash -c "dokku domains:enable $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + check_urls "http://${TEST_APP}.${DOKKU_DOMAIN}" + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}" } @test "(nginx-vhosts) proxy:build-config (domains:add pre deploy)" { - create_app - run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.dokku.me" + run create_app echo "output: $output" echo "status: $status" assert_success - deploy_app + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success sleep 5 # wait for nginx to reload - check_urls http://www.test.app.dokku.me - assert_http_success http://www.test.app.dokku.me + check_urls "http://www.test.app.${DOKKU_DOMAIN}" + assert_http_success "http://www.test.app.${DOKKU_DOMAIN}" } @test "(nginx-vhosts) proxy:build-config (with global VHOST)" { - echo "dokku.me" >"$DOKKU_ROOT/VHOST" - deploy_app + echo "${DOKKU_DOMAIN}" >"$DOKKU_ROOT/VHOST" + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success - check_urls http://${TEST_APP}.dokku.me - assert_http_success http://${TEST_APP}.dokku.me + check_urls "http://${TEST_APP}.${DOKKU_DOMAIN}" + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}" } diff --git a/tests/unit/nginx-vhosts_10.bats b/tests/unit/nginx-vhosts_10.bats index b18f07311..75878a3bc 100644 --- a/tests/unit/nginx-vhosts_10.bats +++ b/tests/unit/nginx-vhosts_10.bats @@ -9,7 +9,7 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } diff --git a/tests/unit/nginx-vhosts_11.bats b/tests/unit/nginx-vhosts_11.bats index cc0db0184..0bc9d11e7 100644 --- a/tests/unit/nginx-vhosts_11.bats +++ b/tests/unit/nginx-vhosts_11.bats @@ -9,13 +9,16 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @test "(nginx-vhosts) nginx:set proxy-buffer-size" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP proxy-buffer-size 2k" echo "output: $output" @@ -49,7 +52,10 @@ teardown() { } @test "(nginx-vhosts) nginx:set proxy-buffering" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP proxy-buffering off" echo "output: $output" @@ -83,7 +89,10 @@ teardown() { } @test "(nginx-vhosts) nginx:set proxy-buffers" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP proxy-buffers \"64 4k\"" echo "output: $output" @@ -117,7 +126,10 @@ teardown() { } @test "(nginx-vhosts) nginx:set proxy-busy-buffers-size" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP proxy-busy-buffers-size 10k" echo "output: $output" @@ -151,7 +163,7 @@ teardown() { } @test "(nginx-vhosts) nginx:set nginx.conf.sigil" { - local TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM export CUSTOM_TMP="$TMP" diff --git a/tests/unit/nginx-vhosts_12.bats b/tests/unit/nginx-vhosts_12.bats index 159526f6b..95b6af069 100644 --- a/tests/unit/nginx-vhosts_12.bats +++ b/tests/unit/nginx-vhosts_12.bats @@ -9,13 +9,13 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @test "(nginx-vhosts) git:from-image nginx.conf.sigil" { - local CUSTOM_TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local CUSTOM_TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$CUSTOM_TMP"' INT TERM rmdir "$CUSTOM_TMP" && cp -r "${BATS_TEST_DIRNAME}/../../tests/apps/python" "$CUSTOM_TMP" @@ -127,8 +127,5 @@ teardown() { assert_success assert_output_contains "Overriding default nginx.conf with detected nginx.conf.sigil" 2 - run /bin/bash -c "docker image rm dokku-test/$TEST_APP:latest dokku-test/$TEST_APP:v2" - echo "output: $output" - echo "status: $status" - assert_success + docker image rm dokku-test/$TEST_APP:latest dokku-test/$TEST_APP:v2 || true } diff --git a/tests/unit/nginx-vhosts_2.bats b/tests/unit/nginx-vhosts_2.bats index 178756522..441ef1ed2 100644 --- a/tests/unit/nginx-vhosts_2.bats +++ b/tests/unit/nginx-vhosts_2.bats @@ -9,33 +9,69 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @test "(nginx-vhosts) proxy:build-config (with SSL and unrelated domain)" { setup_test_tls - dokku domains:add $TEST_APP "node-js-app.dokku.me" - dokku domains:add $TEST_APP "test.dokku.me" - deploy_app - dokku nginx:show-config $TEST_APP - assert_ssl_domain "node-js-app.dokku.me" - assert_http_redirect "http://test.dokku.me" "https://test.dokku.me:443/" + run /bin/bash -c "dokku domains:add $TEST_APP node-js-app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP test.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + assert_ssl_domain "node-js-app.${DOKKU_DOMAIN}" + assert_http_redirect "http://test.${DOKKU_DOMAIN}" "https://test.${DOKKU_DOMAIN}:443/" } @test "(nginx-vhosts) proxy:build-config (wildcard SSL)" { setup_test_tls wildcard - dokku domains:add $TEST_APP "wildcard1.dokku.me" - dokku domains:add $TEST_APP "wildcard2.dokku.me" - deploy_app - dokku nginx:show-config $TEST_APP - assert_ssl_domain "wildcard1.dokku.me" - assert_ssl_domain "wildcard2.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP wildcard1.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP wildcard2.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + assert_ssl_domain "wildcard1.${DOKKU_DOMAIN}" + assert_ssl_domain "wildcard2.${DOKKU_DOMAIN}" } @test "(nginx-vhosts) proxy:build-config (wildcard SSL and unrelated domain) 1" { - destroy_app + run destroy_app + echo "output: $output" + echo "status: $status" + assert_success + TEST_APP="${TEST_APP}.example.com" run /bin/bash -c "dokku apps:create $TEST_APP" echo "output: $output" @@ -43,19 +79,46 @@ teardown() { assert_success setup_test_tls wildcard - deploy_app nodejs-express dokku@dokku.me:$TEST_APP - run /bin/bash -c "dokku nginx:show-config $TEST_APP | grep -e '*.dokku.me' | wc -l" - dokku nginx:show-config $TEST_APP + + run deploy_app nodejs-express dokku@$DOKKU_DOMAIN:$TEST_APP + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP | grep -e '*.${DOKKU_DOMAIN}' | wc -l" + echo "output: $output" + echo "status: $status" + assert_success assert_output "0" } @test "(nginx-vhosts) proxy:build-config (with SSL and Multiple SANs)" { setup_test_tls sans - dokku domains:add $TEST_APP "test.dokku.me" - dokku domains:add $TEST_APP "www.test.dokku.me" - dokku domains:add $TEST_APP "www.test.app.dokku.me" - deploy_app - assert_ssl_domain "test.dokku.me" - assert_ssl_domain "www.test.dokku.me" - assert_ssl_domain "www.test.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP test.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP www.test.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + assert_ssl_domain "test.${DOKKU_DOMAIN}" + assert_ssl_domain "www.test.${DOKKU_DOMAIN}" + assert_ssl_domain "www.test.app.${DOKKU_DOMAIN}" } diff --git a/tests/unit/nginx-vhosts_3.bats b/tests/unit/nginx-vhosts_3.bats index babc690c5..46669928d 100644 --- a/tests/unit/nginx-vhosts_3.bats +++ b/tests/unit/nginx-vhosts_3.bats @@ -11,29 +11,67 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @test "(nginx-vhosts) grpc endpoint" { - deploy_app gogrpc - dokku proxy:ports-add "$TEST_APP" "grpc:80:50051" - run /bin/bash -c "docker run --rm ${TEST_APP}-docker-image /go/bin/greeter_client -address ${TEST_APP}.dokku.me:80 -name grpc" + run deploy_app gogrpc + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:add $TEST_APP grpc:80:50051" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker run --rm ${TEST_APP}-docker-image /go/bin/greeter_client -address ${TEST_APP}.${DOKKU_DOMAIN}:80 -name grpc" + echo "output: $output" + echo "status: $status" + assert_success assert_output "Greeting: Hello grpc" } @test "(nginx-vhosts) grpc endpoint on a port other than 80" { - deploy_app gogrpc - dokku proxy:ports-add "$TEST_APP" "grpc:8080:50051" - run /bin/bash -c "docker run --rm ${TEST_APP}-docker-image /go/bin/greeter_client -address ${TEST_APP}.dokku.me:8080 -name grpc8080" + run deploy_app gogrpc + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:add $TEST_APP grpc:8080:50051" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker run --rm ${TEST_APP}-docker-image /go/bin/greeter_client -address ${TEST_APP}.${DOKKU_DOMAIN}:8080 -name grpc8080" + echo "output: $output" + echo "status: $status" + assert_success assert_output "Greeting: Hello grpc8080" } @test "(nginx-vhosts) grpcs endpoint" { setup_test_tls - deploy_app gogrpc - dokku proxy:ports-add "$TEST_APP" "grpcs:443:50051" - run /bin/bash -c "docker run --rm ${TEST_APP}-docker-image /go/bin/greeter_client -address ${TEST_APP}.dokku.me:443 -name grpcs -tls" + run deploy_app gogrpc + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:add $TEST_APP grpcs:443:50051" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker run --rm ${TEST_APP}-docker-image /go/bin/greeter_client -address ${TEST_APP}.${DOKKU_DOMAIN}:443 -name grpcs -tls" + echo "output: $output" + echo "status: $status" + assert_success assert_output "Greeting: Hello grpcs" } diff --git a/tests/unit/nginx-vhosts_4.bats b/tests/unit/nginx-vhosts_4.bats index 9cfd30a56..d9a281179 100644 --- a/tests/unit/nginx-vhosts_4.bats +++ b/tests/unit/nginx-vhosts_4.bats @@ -16,13 +16,21 @@ teardown() { @test "(nginx-vhosts) proxy:build-config (without global VHOST)" { rm "$DOKKU_ROOT/VHOST" - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku --quiet urls $TEST_APP" echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c "dokku ports:report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + HOSTNAME="$(hostname -f)" check_urls http://${HOSTNAME}:[0-9]+ @@ -35,7 +43,10 @@ teardown() { @test "(nginx-vhosts) proxy:build-config (without global VHOST and IPv4 address set as HOSTNAME)" { rm "$DOKKU_ROOT/VHOST" echo "127.0.0.1" >"$DOKKU_ROOT/VHOST" - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success HOSTNAME=$(<"$DOKKU_ROOT/VHOST") check_urls http://${HOSTNAME}:[0-9]+ @@ -49,7 +60,10 @@ teardown() { @test "(nginx-vhosts) proxy:build-config (without global VHOST and IPv6 address set as HOSTNAME)" { rm "$DOKKU_ROOT/VHOST" echo "fda5:c7db:a520:bb6d::aabb:ccdd:eeff" >"$DOKKU_ROOT/VHOST" - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success HOSTNAME=$(<"$DOKKU_ROOT/VHOST") check_urls http://${HOSTNAME}:[0-9]+ @@ -57,16 +71,35 @@ teardown() { @test "(nginx-vhosts) proxy:build-config (without global VHOST and domains:add pre deploy)" { rm "$DOKKU_ROOT/VHOST" - create_app - dokku domains:add $TEST_APP "www.test.app.dokku.me" - deploy_app - assert_nonssl_domain "www.test.app.dokku.me" + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + assert_nonssl_domain "www.test.app.${DOKKU_DOMAIN}" } @test "(nginx-vhosts) proxy:build-config (without global VHOST and domains:add post deploy)" { rm "$DOKKU_ROOT/VHOST" - deploy_app - dokku domains:add $TEST_APP "www.test.app.dokku.me" - check_urls http://www.test.app.dokku.me - assert_http_success http://www.test.app.dokku.me + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + check_urls "http://www.test.app.${DOKKU_DOMAIN}" + assert_http_success "http://www.test.app.${DOKKU_DOMAIN}" } diff --git a/tests/unit/nginx-vhosts_5.bats b/tests/unit/nginx-vhosts_5.bats index c48ea3855..bd0fdf763 100644 --- a/tests/unit/nginx-vhosts_5.bats +++ b/tests/unit/nginx-vhosts_5.bats @@ -16,30 +16,42 @@ teardown() { } @test "(nginx-vhosts) proxy:build-config (sslip.io style hostnames)" { - echo "127.0.0.1.sslip.io.dokku.me" >"$DOKKU_ROOT/VHOST" - deploy_app + echo "127.0.0.1.sslip.io.${DOKKU_DOMAIN}" >"$DOKKU_ROOT/VHOST" + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success - check_urls http://${TEST_APP}.127.0.0.1.sslip.io.dokku.me - assert_http_success http://${TEST_APP}.127.0.0.1.sslip.io.dokku.me + check_urls "http://${TEST_APP}.127.0.0.1.sslip.io.${DOKKU_DOMAIN}" + assert_http_success "http://${TEST_APP}.127.0.0.1.sslip.io.${DOKKU_DOMAIN}" } @test "(nginx-vhosts) proxy:build-config (dockerfile expose)" { - deploy_app dockerfile + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success - dokku domains:add $TEST_APP "www.test.app.dokku.me" - check_urls http://${TEST_APP}.dokku.me:3000 - check_urls http://${TEST_APP}.dokku.me:3003 - check_urls http://www.test.app.dokku.me:3000 - check_urls http://www.test.app.dokku.me:3003 - assert_http_success http://${TEST_APP}.dokku.me:3000 - assert_http_success http://${TEST_APP}.dokku.me:3003 - assert_http_success http://www.test.app.dokku.me:3000 - assert_http_success http://www.test.app.dokku.me:3003 + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + check_urls "http://${TEST_APP}.${DOKKU_DOMAIN}:3000" + check_urls "http://${TEST_APP}.${DOKKU_DOMAIN}:3003" + check_urls "http://www.test.app.${DOKKU_DOMAIN}:3000" + check_urls "http://www.test.app.${DOKKU_DOMAIN}:3003" + assert_http_localhost_success "http" "${TEST_APP}.${DOKKU_DOMAIN}" "3000" + assert_http_localhost_success "http" "${TEST_APP}.${DOKKU_DOMAIN}" "3003" + assert_http_localhost_success "http" "www.test.app.${DOKKU_DOMAIN}" "3000" + assert_http_localhost_success "http" "www.test.app.${DOKKU_DOMAIN}" "3003" } @test "(nginx-vhosts) proxy:build-config (multiple networks)" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success create_attach_network run /bin/bash -c "dokku proxy:build-config $TEST_APP" @@ -51,10 +63,19 @@ teardown() { @test "(nginx-vhosts) proxy:build-config (global DOKKU_PROXY_PORT)" { local GLOBAL_PORT=30999 run /bin/bash -c "dokku config:set --global DOKKU_PROXY_PORT=${GLOBAL_PORT}" + echo "output: $output" + echo "status: $status" + assert_success - deploy_app - check_urls http://${TEST_APP}.dokku.me:${GLOBAL_PORT} - assert_http_success http://${TEST_APP}.dokku.me:${GLOBAL_PORT} + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + check_urls "http://${TEST_APP}.${DOKKU_DOMAIN}:${GLOBAL_PORT}" + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}:${GLOBAL_PORT}" run /bin/bash -c "dokku config:unset --global DOKKU_PROXY_PORT" + echo "output: $output" + echo "status: $status" + assert_success } diff --git a/tests/unit/nginx-vhosts_6.bats b/tests/unit/nginx-vhosts_6.bats index 707231d59..15ec92725 100644 --- a/tests/unit/nginx-vhosts_6.bats +++ b/tests/unit/nginx-vhosts_6.bats @@ -9,13 +9,16 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @test "(nginx-vhosts) nginx (no server tokens)" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "curl -s -D - $(dokku url $TEST_APP) -o /dev/null | grep -E '^Server' | grep -E '[0-9]+'" echo "output: $output" echo "status: $status" @@ -205,7 +208,10 @@ teardown() { } @test "(nginx-vhosts) nginx:validate-config" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:validate-config" echo "output: $output" echo "status: $status" diff --git a/tests/unit/nginx-vhosts_7.bats b/tests/unit/nginx-vhosts_7.bats index 1cd3eca3b..2b35c1f6c 100644 --- a/tests/unit/nginx-vhosts_7.bats +++ b/tests/unit/nginx-vhosts_7.bats @@ -9,26 +9,41 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @test "(nginx-vhosts) proxy:build-config (wildcard SSL and custom nginx template)" { setup_test_tls wildcard - dokku domains:add $TEST_APP "wildcard1.dokku.me" - dokku domains:add $TEST_APP "wildcard2.dokku.me" - deploy_app nodejs-express dokku@dokku.me:$TEST_APP custom_ssl_nginx_template + run /bin/bash -c "dokku domains:add $TEST_APP wildcard1.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success - assert_ssl_domain "wildcard1.dokku.me" - assert_ssl_domain "wildcard2.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP wildcard2.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app nodejs-express dokku@$DOKKU_DOMAIN:$TEST_APP custom_ssl_nginx_template + echo "output: $output" + echo "status: $status" + assert_success + + assert_ssl_domain "wildcard1.${DOKKU_DOMAIN}" + assert_ssl_domain "wildcard2.${DOKKU_DOMAIN}" assert_http_redirect "http://${CUSTOM_TEMPLATE_SSL_DOMAIN}" "https://${CUSTOM_TEMPLATE_SSL_DOMAIN}:443/" assert_http_success "https://${CUSTOM_TEMPLATE_SSL_DOMAIN}" } @test "(nginx-vhosts) proxy:build-config (custom nginx template - no ssl)" { - dokku domains:add $TEST_APP "www.test.app.dokku.me" - run deploy_app nodejs-express dokku@dokku.me:$TEST_APP custom_nginx_template + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP custom_nginx_template echo "output: $output" echo "status: $status" assert_success @@ -38,8 +53,8 @@ teardown() { echo "status: $status" assert_success - assert_nonssl_domain "www.test.app.dokku.me" - assert_http_success "customtemplate.dokku.me" + assert_nonssl_domain "www.test.app.${DOKKU_DOMAIN}" + assert_http_localhost_success "http" "customtemplate.${DOKKU_DOMAIN}" run /bin/bash -c "dokku nginx:show-config $TEST_APP" echo "output: $output" @@ -53,8 +68,12 @@ teardown() { echo "status: $status" assert_success - dokku domains:add $TEST_APP "www.test.app.dokku.me" - run deploy_app nodejs-express dokku@dokku.me:$TEST_APP custom_nginx_template + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP custom_nginx_template echo "output: $output" echo "status: $status" assert_success @@ -64,7 +83,7 @@ teardown() { echo "status: $status" assert_success - assert_nonssl_domain "www.test.app.dokku.me" + assert_nonssl_domain "www.test.app.${DOKKU_DOMAIN}" run /bin/bash -c "dokku nginx:show-config $TEST_APP" echo "output: $output" @@ -73,7 +92,7 @@ teardown() { } @test "(nginx-vhosts) proxy:build-config (failed validate_nginx)" { - run deploy_app nodejs-express dokku@dokku.me:$TEST_APP bad_custom_nginx_template + run deploy_app nodejs-express dokku@$DOKKU_DOMAIN:$TEST_APP bad_custom_nginx_template echo "output: $output" echo "status: $status" assert_failure diff --git a/tests/unit/nginx-vhosts_8.bats b/tests/unit/nginx-vhosts_8.bats index 248296737..c56a3cca0 100644 --- a/tests/unit/nginx-vhosts_8.bats +++ b/tests/unit/nginx-vhosts_8.bats @@ -9,13 +9,16 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @test "(nginx-vhosts) nginx:set client-max-body-size" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP client-max-body-size" echo "output: $output" @@ -49,7 +52,10 @@ teardown() { } @test "(nginx-vhosts) nginx:set proxy-read-timeout" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP proxy-read-timeout 45s" echo "output: $output" @@ -84,7 +90,10 @@ teardown() { @test "(nginx-vhosts) nginx:set proxy-read-timeout (with SSL)" { setup_test_tls - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP proxy-read-timeout 45s" echo "output: $output" @@ -124,19 +133,25 @@ teardown() { echo "status: $status" assert_output_contains "Ignoring detected https port mapping without an accompanying ssl certificate" 0 - teardown_test_tls - run /bin/bash -c "dokku proxy:report $TEST_APP --proxy-port-map" + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" echo "output: $output" echo "status: $status" assert_output "http:80:5000 https:443:5000" + teardown_test_tls + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000" run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" - assert_output_contains "Ignoring detected https port mapping without an accompanying ssl certificate" 1 + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + assert_output_contains "Ignoring detected https port mapping without an accompanying ssl certificate" 0 - run /bin/bash -c "dokku proxy:report $TEST_APP --proxy-port-map" + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" echo "output: $output" echo "status: $status" - assert_output "http:80:5000 https:443:5000" + assert_output "http:80:5000" } diff --git a/tests/unit/nginx-vhosts_9.bats b/tests/unit/nginx-vhosts_9.bats index 702b961c9..143238aee 100644 --- a/tests/unit/nginx-vhosts_9.bats +++ b/tests/unit/nginx-vhosts_9.bats @@ -9,13 +9,16 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @test "(nginx-vhosts) logging" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run [ -a "/var/log/nginx/$TEST_APP-access.log" ] echo "output: $output" @@ -39,7 +42,10 @@ teardown() { } @test "(nginx-vhosts) log-path" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP access-log-path off" echo "output: $output" @@ -103,7 +109,10 @@ teardown() { } @test "(nginx-vhosts) access-log-format" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP access-log-format combined" echo "output: $output" @@ -154,20 +163,52 @@ teardown() { @test "(nginx-vhosts) proxy:build-config (with SSL and unrelated domain)" { setup_test_tls - dokku domains:add $TEST_APP "node-js-app.dokku.me" - dokku domains:add $TEST_APP "test.dokku.me" - deploy_app - dokku nginx:show-config $TEST_APP - assert_ssl_domain "node-js-app.dokku.me" - assert_http_redirect "http://test.dokku.me" "https://test.dokku.me:443/" + run /bin/bash -c "dokku domains:add $TEST_APP node-js-app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP test.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + assert_ssl_domain "node-js-app.${DOKKU_DOMAIN}" + assert_http_redirect "http://test.${DOKKU_DOMAIN}" "https://test.${DOKKU_DOMAIN}:443/" } @test "(nginx-vhosts) proxy:build-config (wildcard SSL)" { setup_test_tls wildcard - dokku domains:add $TEST_APP "wildcard1.dokku.me" - dokku domains:add $TEST_APP "wildcard2.dokku.me" - deploy_app - dokku nginx:show-config $TEST_APP - assert_ssl_domain "wildcard1.dokku.me" - assert_ssl_domain "wildcard2.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP wildcard1.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP wildcard2.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + assert_ssl_domain "wildcard1.${DOKKU_DOMAIN}" + assert_ssl_domain "wildcard2.${DOKKU_DOMAIN}" } diff --git a/tests/unit/openresty.bats b/tests/unit/openresty.bats new file mode 100644 index 000000000..f188bbedc --- /dev/null +++ b/tests/unit/openresty.bats @@ -0,0 +1,189 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + dokku nginx:stop + dokku openresty:set --global letsencrypt-server https://acme-staging-v02.api.letsencrypt.org/directory + dokku openresty:set --global letsencrypt-email + dokku openresty:start + create_app +} + +teardown() { + global_teardown + destroy_app + dokku openresty:stop + dokku nginx:start +} + +@test "(openresty) openresty:help" { + run /bin/bash -c "dokku openresty" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage the openresty proxy integration" + help_output="$output" + + run /bin/bash -c "dokku openresty:help" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage the openresty proxy integration" + assert_output "$help_output" +} + +@test "(openresty) single domain" { + run /bin/bash -c "dokku proxy:set $TEST_APP openresty" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@dokku.me:$TEST_APP convert_to_dockerfile + echo "output: $output" + echo "status: $status" + assert_success + + assert_http_localhost_success "http" "$TEST_APP.dokku.me" "80" "" "python/http.server" +} + +@test "(openresty) multiple domains" { + run /bin/bash -c "dokku proxy:set $TEST_APP openresty" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP-2.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@dokku.me:$TEST_APP convert_to_dockerfile + echo "output: $output" + echo "status: $status" + assert_success + + assert_http_localhost_success "http" "$TEST_APP.dokku.me" "80" "" "python/http.server" + assert_http_localhost_success "http" "$TEST_APP-2.dokku.me" "80" "" "python/http.server" +} + +@test "(openresty) ssl" { + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku proxy:set $TEST_APP openresty" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + assert_http_localhost_success "http" "$TEST_APP.dokku.me" "80" "" "python/http.server" + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "http:80:5000" + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"openresty.letsencrypt\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku openresty:set --global letsencrypt-email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:stop" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:start" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:inspect $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"openresty.letsencrypt\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "http:80:5000 https:443:5000" +} + +@test "(openresty) includes" { + run /bin/bash -c "dokku proxy:set $TEST_APP openresty" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@dokku.me:$TEST_APP add_openresty_include + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:inspect $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"openresty.include-http-example.conf\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "IyBmb3JjZSB0aGUgY2hhcmFjdGVyIHNldCB0byB1dGYtOApjaGFyc2V0IFVURi04Owo=" + + run /bin/bash -c "docker logs openresty-openresty-1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker exec openresty-openresty-1 /usr/local/openresty/nginx/sbin/nginx -t" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker exec openresty-openresty-1 cat /etc/nginx/sites-enabled/sites.conf" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "force the character set to utf-8" + assert_output_contains "charset UTF-8;" +} + +add_openresty_include() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + mkdir -p "$APP_REPO_DIR/openresty-http-includes" + touch "$APP_REPO_DIR/openresty-http-includes/example.conf" + echo "# force the character set to utf-8" >>"$APP_REPO_DIR/openresty-http-includes/example.conf" + echo "charset UTF-8;" >>"$APP_REPO_DIR/openresty-http-includes/example.conf" +} diff --git a/tests/unit/ports.bats b/tests/unit/ports.bats new file mode 100644 index 000000000..62674ea07 --- /dev/null +++ b/tests/unit/ports.bats @@ -0,0 +1,184 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -fp "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" + create_app +} + +teardown() { + destroy_app + [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" + global_teardown +} + +@test "(ports) ports:help" { + run /bin/bash -c "dokku ports" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage ports for an app" + help_output="$output" + + run /bin/bash -c "dokku ports:help" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage ports for an app" + assert_output "$help_output" +} + +@test "(ports) ports subcommands (list/add/set/remove/clear)" { + run /bin/bash -c "dokku ports:set $TEST_APP http:1234:5001" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output "http:1234:5001" + + run /bin/bash -c "dokku ports:add $TEST_APP http:8080:5002 https:8443:5003" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output "http:1234:5001 http:8080:5002 https:8443:5003" + + run /bin/bash -c "dokku ports:set $TEST_APP http:8080:5000 https:8443:5000 http:1234:5001" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output "http:1234:5001 http:8080:5000 https:8443:5000" + + run /bin/bash -c "dokku ports:remove $TEST_APP 8080" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output "http:1234:5001 https:8443:5000" + + run /bin/bash -c "dokku ports:remove $TEST_APP http:1234:5001" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output "https:8443:5000" + + run /bin/bash -c "dokku ports:clear $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output_not_exists + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000" +} + +@test "(ports:add) post-deploy add" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:add $TEST_APP http:8080:5000 http:8081:5000" + echo "output: $output" + echo "status: $status" + assert_success + + URLS="$(dokku --quiet urls "$TEST_APP")" + for URL in $URLS; do + assert_http_success $URL + done + assert_http_success "http://$TEST_APP.${DOKKU_DOMAIN}:8080" + assert_http_success "http://$TEST_APP.${DOKKU_DOMAIN}:8081" +} + +@test "(ports:report) herokuish tls" { + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output_not_exists + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000" + + run setup_test_tls + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output_not_exists + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000 https:443:5000" +} + +@test "(ports:report) dockerfile tls" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP move_expose_dockerfile_into_place + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output_not_exists + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:3000:3000 http:3003:3003 udp:3001:3001" + + run setup_test_tls + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output_not_exists + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "https:3000:3000 https:3003:3003 udp:3001:3001" +} diff --git a/tests/unit/proxied-app.bats b/tests/unit/proxied-app.bats index e16928634..92181b2b1 100644 --- a/tests/unit/proxied-app.bats +++ b/tests/unit/proxied-app.bats @@ -29,12 +29,12 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku proxy:ports-set $TEST_APP http:80:8080" + run /bin/bash -c "dokku ports:set $TEST_APP http:80:8080" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success diff --git a/tests/unit/proxy.bats b/tests/unit/proxy.bats index be69693fd..45ae78c48 100644 --- a/tests/unit/proxy.bats +++ b/tests/unit/proxy.bats @@ -9,7 +9,7 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @@ -91,8 +91,12 @@ teardown() { } @test "(proxy) proxy:enable/disable" { - deploy_app - assert_nonssl_domain "${TEST_APP}.dokku.me" + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + assert_nonssl_domain "${TEST_APP}.${DOKKU_DOMAIN}" run /bin/bash -c "dokku proxy:disable $TEST_APP" echo "output: $output" @@ -107,86 +111,9 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success "${TEST_APP}.dokku.me" + assert_http_success "${TEST_APP}.${DOKKU_DOMAIN}" for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do assert_not_external_port $(<$CID_FILE) done } - -@test "(proxy) proxy:ports (list/add/set/remove/clear)" { - run /bin/bash -c "dokku proxy:ports-set $TEST_APP http:1234:5001" - echo "output: $output" - echo "status: $status" - assert_success - - run /bin/bash -c "dokku --quiet proxy:ports $TEST_APP | xargs" - echo "output: $output" - echo "status: $status" - assert_output "http 1234 5001" - - run /bin/bash -c "dokku proxy:ports-add $TEST_APP http:8080:5002 https:8443:5003" - echo "output: $output" - echo "status: $status" - assert_success - - run /bin/bash -c "dokku --quiet proxy:ports $TEST_APP | xargs" - echo "output: $output" - echo "status: $status" - assert_output "http 1234 5001 http 8080 5002 https 8443 5003" - - run /bin/bash -c "dokku proxy:ports-set $TEST_APP http:8080:5000 https:8443:5000 http:1234:5001" - echo "output: $output" - echo "status: $status" - assert_success - - run /bin/bash -c "dokku --quiet proxy:ports $TEST_APP | xargs" - echo "output: $output" - echo "status: $status" - assert_output "http 1234 5001 http 8080 5000 https 8443 5000" - - run /bin/bash -c "dokku proxy:ports-remove $TEST_APP 8080" - echo "output: $output" - echo "status: $status" - assert_success - - run /bin/bash -c "dokku --quiet proxy:ports $TEST_APP | xargs" - echo "output: $output" - echo "status: $status" - assert_output "http 1234 5001 https 8443 5000" - - run /bin/bash -c "dokku proxy:ports-remove $TEST_APP http:1234:5001" - echo "output: $output" - echo "status: $status" - assert_success - - run /bin/bash -c "dokku --quiet proxy:ports $TEST_APP | xargs" - echo "output: $output" - echo "status: $status" - assert_output "https 8443 5000" - - run /bin/bash -c "dokku proxy:ports-clear $TEST_APP" - echo "output: $output" - echo "status: $status" - assert_success - - run /bin/bash -c "dokku --quiet proxy:ports $TEST_APP | xargs" - echo "output: $output" - echo "status: $status" - assert_output "http 80 5000" -} - -@test "(proxy) proxy:ports (post-deploy add)" { - deploy_app - run /bin/bash -c "dokku proxy:ports-add $TEST_APP http:8080:5000 http:8081:5000" - echo "output: $output" - echo "status: $status" - assert_success - - URLS="$(dokku --quiet urls "$TEST_APP")" - for URL in $URLS; do - assert_http_success $URL - done - assert_http_success "http://$TEST_APP.dokku.me:8080" - assert_http_success "http://$TEST_APP.dokku.me:8081" -} diff --git a/tests/unit/ps-cnb.bats b/tests/unit/ps-cnb.bats index 5abbbb1bd..61dc10ba8 100644 --- a/tests/unit/ps-cnb.bats +++ b/tests/unit/ps-cnb.bats @@ -22,7 +22,7 @@ teardown() { echo "status: $status" assert_success - run deploy_app python dokku@dokku.me:$TEST_APP add_requirements_txt + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt echo "output: $output" echo "status: $status" assert_success diff --git a/tests/unit/ps-dockerfile-1.bats b/tests/unit/ps-dockerfile-1.bats index f74aaf60f..a7bd6645d 100644 --- a/tests/unit/ps-dockerfile-1.bats +++ b/tests/unit/ps-dockerfile-1.bats @@ -13,11 +13,16 @@ teardown() { } @test "(ps) dockerfile" { - deploy_app dockerfile + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku ps:stop $TEST_APP" echo "output: $output" echo "status: $status" assert_success + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" @@ -65,9 +70,21 @@ teardown() { echo "status: $status" assert_success - destroy_app - create_app - deploy_app dockerfile-procfile + run destroy_app + echo "output: $output" + echo "status: $status" + assert_success + + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app dockerfile-procfile + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku ps:scale $TEST_APP non-existent=2" echo "output: $output" echo "status: $status" @@ -80,7 +97,11 @@ teardown() { echo "status: $status" assert_success - deploy_app dockerfile + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success + CIDS="" for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do CIDS+=$(<$CID_FILE) diff --git a/tests/unit/ps-dockerfile-2.bats b/tests/unit/ps-dockerfile-2.bats index 3946efe06..dd4cf314d 100644 --- a/tests/unit/ps-dockerfile-2.bats +++ b/tests/unit/ps-dockerfile-2.bats @@ -18,7 +18,11 @@ teardown() { echo "status: $status" assert_success - deploy_app dockerfile-app-json-formations + run deploy_app dockerfile-app-json-formations + echo "output: $output" + echo "status: $status" + assert_success + CIDS="" for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do CIDS+=$(<$CID_FILE) @@ -42,7 +46,11 @@ teardown() { } @test "(ps) dockerfile with procfile" { - deploy_app dockerfile-procfile + run deploy_app dockerfile-procfile + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku ps:stop $TEST_APP" echo "output: $output" echo "status: $status" @@ -106,7 +114,11 @@ teardown() { echo "status: $status" assert_success - deploy_app dockerfile-procfile + run deploy_app dockerfile-procfile + echo "output: $output" + echo "status: $status" + assert_success + for PROC_TYPE in web worker; do run /bin/bash -c "docker ps --format '{{.ID}} {{.Command}}' --no-trunc" echo "output: $output" diff --git a/tests/unit/ps-general-1.bats b/tests/unit/ps-general-1.bats index 4171f8fbb..94de64bc6 100644 --- a/tests/unit/ps-general-1.bats +++ b/tests/unit/ps-general-1.bats @@ -27,8 +27,15 @@ teardown() { } @test "(ps) ps:inspect" { - dokku config:set "$TEST_APP" key=value key=value=value - deploy_app dockerfile + run /bin/bash -c "dokku config:set $TEST_APP key=value key=value=value" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "dokku ps:inspect $TEST_APP" @@ -56,7 +63,7 @@ EOF } @test "(ps:scale) update formations from Procfile" { - local TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM CUSTOM_TMP="$TMP" deploy_app nodejs-express @@ -113,7 +120,10 @@ EOF echo "status: $status" assert_output "$test_restart_policy" - deploy_app dockerfile + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect -f '{{ .HostConfig.RestartPolicy.Name }}:{{ .HostConfig.RestartPolicy.MaximumRetryCount }}' $CID" diff --git a/tests/unit/ps-herokuish-1.bats b/tests/unit/ps-herokuish-1.bats index ce2c133c7..31ecd2c65 100644 --- a/tests/unit/ps-herokuish-1.bats +++ b/tests/unit/ps-herokuish-1.bats @@ -13,7 +13,11 @@ teardown() { } @test "(ps) herokuish" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku ps:stop $TEST_APP" echo "output: $output" echo "status: $status" diff --git a/tests/unit/ps-herokuish-2.bats b/tests/unit/ps-herokuish-2.bats index ef6ba5a1e..cab55128f 100644 --- a/tests/unit/ps-herokuish-2.bats +++ b/tests/unit/ps-herokuish-2.bats @@ -19,7 +19,10 @@ teardown() { echo "status: $status" assert_success - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success for PROC_TYPE in web worker; do CIDS="" for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.$PROC_TYPE.*; do @@ -71,8 +74,15 @@ teardown() { @test "(ps:restore) herokuish" { MYAPP="manual-randomtestapp-1" - create_app "$MYAPP" - deploy_app + run create_app "$MYAPP" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku ps:stop $TEST_APP" echo "output: $output" diff --git a/tests/unit/repo.bats b/tests/unit/repo.bats index abdc68213..6a74c315d 100644 --- a/tests/unit/repo.bats +++ b/tests/unit/repo.bats @@ -4,9 +4,11 @@ load test_helper setup() { global_setup + create_app } teardown() { + destroy_app global_teardown } @@ -24,7 +26,7 @@ teardown() { assert_output "$help_output" } -@test "(repo) repo:gc, repo:purge-cache" { +@test "(repo) repo:gc" { run deploy_app echo "output: $output" echo "status: $status" @@ -34,29 +36,36 @@ teardown() { echo "output: $output" echo "status: $status" assert_success +} - run /bin/bash -c "touch $DOKKU_ROOT/$TEST_APP/cache/derp" +@test "(repo) repo:purge-cache" { + run deploy_app echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "find $DOKKU_ROOT/$TEST_APP/cache -type f | wc -l" + run /bin/bash -c "docker volume ls -q --filter label=com.dokku.app-name=$TEST_APP | wc -l" echo "count: '$output'" echo "status: $status" - assert_not_output 0 - - run /bin/bash -c "dokku repo:purge-cache $TEST_APP" - echo "output: $output" - echo "status: $status" - assert_success + assert_output 1 run /bin/bash -c "find $DOKKU_ROOT/$TEST_APP/cache -type f | wc -l" echo "count: '$output'" echo "status: $status" assert_output 0 - run destroy_app + run /bin/bash -c "dokku repo:purge-cache $TEST_APP" echo "output: $output" echo "status: $status" assert_success + + run /bin/bash -c "docker volume ls -q --filter label=com.dokku.app-name=$TEST_APP | wc -l" + echo "count: '$output'" + echo "status: $status" + assert_output 0 + + run /bin/bash -c "find $DOKKU_ROOT/$TEST_APP/cache -type f | wc -l" + echo "count: '$output'" + echo "status: $status" + assert_output 0 } diff --git a/tests/unit/report.bats b/tests/unit/report.bats index 2c3d27f6b..fc280fae8 100644 --- a/tests/unit/report.bats +++ b/tests/unit/report.bats @@ -39,14 +39,21 @@ teardown() { assert_output_contains "App fake-app-name does not exist" assert_failure - dokku apps:create "${TEST_APP}-2" + run /bin/bash -c "dokku apps:create ${TEST_APP}-2" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku report ${TEST_APP}-2 2>&1" echo "output: $output" echo "status: $status" assert_output_contains "Deployed: false" assert_success - dokku --force apps:destroy "${TEST_APP}-2" + run /bin/bash -c "dokku --force apps:destroy ${TEST_APP}-2" + echo "output: $output" + echo "status: $status" + assert_success } @test "(report) custom docker bin" { diff --git a/tests/unit/resource_1.bats b/tests/unit/resource_1.bats index 88836a461..c36031e89 100644 --- a/tests/unit/resource_1.bats +++ b/tests/unit/resource_1.bats @@ -32,7 +32,11 @@ teardown() { echo "status: $status" assert_output_contains "resource limits $TEST_APP information" - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku resource:limit --memory 512MB $TEST_APP" echo "output: $output" echo "status: $status" @@ -44,7 +48,11 @@ teardown() { echo "status: $status" assert_output "0" - dokku ps:rebuild "$TEST_APP" + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" echo "output: $output" @@ -56,7 +64,11 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" echo "output: $output" @@ -68,7 +80,11 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" echo "output: $output" @@ -80,7 +96,11 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" echo "output: $output" @@ -92,7 +112,11 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" echo "output: $output" @@ -104,7 +128,11 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" echo "output: $output" diff --git a/tests/unit/resource_2.bats b/tests/unit/resource_2.bats index 44bc4198e..5ef9bfde1 100644 --- a/tests/unit/resource_2.bats +++ b/tests/unit/resource_2.bats @@ -18,7 +18,11 @@ teardown() { echo "status: $status" assert_output_contains "resource reservation $TEST_APP information" - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku resource:reserve --memory 512MB $TEST_APP" echo "output: $output" echo "status: $status" @@ -30,7 +34,11 @@ teardown() { echo "status: $status" assert_output "0" - dokku ps:rebuild "$TEST_APP" + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" echo "output: $output" @@ -47,7 +55,11 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" echo "output: $output" @@ -59,7 +71,11 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" echo "output: $output" @@ -71,7 +87,11 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" echo "output: $output" @@ -83,7 +103,11 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" echo "output: $output" diff --git a/tests/unit/run_1.bats b/tests/unit/run_1.bats index d157ef4f0..1ab4ee1dd 100644 --- a/tests/unit/run_1.bats +++ b/tests/unit/run_1.bats @@ -24,7 +24,11 @@ teardown() { } @test "(run) run (with --options)" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku --force --quiet run $TEST_APP python -V" echo "output: $output" echo "status: $status" @@ -42,7 +46,7 @@ teardown() { echo "status: $status" assert_success - run deploy_app python dokku@dokku.me:$TEST_APP add_requirements_txt + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt echo "output: $output" echo "status: $status" assert_success @@ -69,7 +73,7 @@ teardown() { echo "status: $status" assert_success - run deploy_app python dokku@dokku.me:$TEST_APP add_requirements_txt + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt echo "output: $output" echo "status: $status" assert_success diff --git a/tests/unit/run_2.bats b/tests/unit/run_2.bats index 5e7b4af3e..d6f8e860a 100644 --- a/tests/unit/run_2.bats +++ b/tests/unit/run_2.bats @@ -13,7 +13,10 @@ teardown() { } @test "(run) run" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku run $TEST_APP echo $TEST_APP" echo "output: $output" @@ -27,7 +30,10 @@ teardown() { } @test "(run) run:detached" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success RANDOM_RUN_CID="$(dokku --label=com.dokku.test-label=value run:detached $TEST_APP sleep 300)" run /bin/bash -c "docker inspect -f '{{ .State.Status }}' $RANDOM_RUN_CID" @@ -53,7 +59,11 @@ teardown() { } @test "(run) run (with tty)" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku run $TEST_APP ls /app/null" echo "output: $output" echo "status: $status" @@ -61,7 +71,11 @@ teardown() { } @test "(run) run (without tty)" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c ": |dokku run $TEST_APP ls /app/null" echo "output: $output" echo "status: $status" @@ -69,7 +83,11 @@ teardown() { } @test "(run) run command from Procfile" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku run $TEST_APP custom 'hi dokku' | tail -n 1" echo "output: $output" echo "status: $status" @@ -79,7 +97,10 @@ teardown() { } @test "(run) list" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku --quiet run:list $TEST_APP" echo "output: $output" diff --git a/tests/unit/scheduler-docker-local.bats b/tests/unit/scheduler-docker-local.bats index 42c906634..ba3199a3b 100644 --- a/tests/unit/scheduler-docker-local.bats +++ b/tests/unit/scheduler-docker-local.bats @@ -38,8 +38,8 @@ teardown() { assert_success # the run command is equivalent to the following line, except with backslashes due to the enclosing doublequotes - # dokku docker-options:add test deploy '--label "some.key=Host(\`$TEST_APP.dokku.me\`)"' - run /bin/bash -c "dokku docker-options:add $TEST_APP deploy '--label \"some.key=Host(\\\`$TEST_APP.dokku.me\\\`)\"'" + # dokku docker-options:add test deploy '--label "some.key=Host(\`$TEST_APP.$DOKKU_DOMAIN\`)"' + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy '--label \"some.key=Host(\\\`$TEST_APP.$DOKKU_DOMAIN\\\`)\"'" echo "output: $output" echo "status: $status" assert_success @@ -52,10 +52,28 @@ teardown() { run /bin/bash -c "docker inspect --format '{{ index .Config.Labels \"some.key\"}}' $TEST_APP.web.1" echo "output: $output" echo "status: $status" - assert_output "Host(\`$TEST_APP.dokku.me\`)" + assert_output "Host(\`$TEST_APP.$DOKKU_DOMAIN\`)" assert_success } +@test "(scheduler-docker-local) no-web" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:set $TEST_APP procfile-path worker.Procfile" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Skipping web as it is missing from the current Procfile" +} + @test "(scheduler-docker-local) init-process" { run create_app echo "output: $output" diff --git a/tests/unit/storage.bats b/tests/unit/storage.bats index 13bd0c155..37205ecf4 100644 --- a/tests/unit/storage.bats +++ b/tests/unit/storage.bats @@ -141,7 +141,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku storage:mount $TEST_APP /tmp/mount:/mount" echo "output: $output" @@ -158,7 +158,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'" echo "output: $output" diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index 7ca8aad7f..6cdca2e2a 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -4,12 +4,13 @@ DOKKU_ROOT=${DOKKU_ROOT:=~dokku} DOCKER_BIN=${DOCKER_BIN:="docker"} DOKKU_LIB_ROOT=${DOKKU_LIB_PATH:="/var/lib/dokku"} +DOKKU_DOMAIN=${DOKKU_DOMAIN:="dokku.me"} PLUGIN_PATH=${PLUGIN_PATH:="$DOKKU_LIB_ROOT/plugins"} PLUGIN_AVAILABLE_PATH=${PLUGIN_AVAILABLE_PATH:="$PLUGIN_PATH/available"} PLUGIN_ENABLED_PATH=${PLUGIN_ENABLED_PATH:="$PLUGIN_PATH/enabled"} PLUGIN_CORE_PATH=${PLUGIN_CORE_PATH:="$DOKKU_LIB_ROOT/core-plugins"} PLUGIN_CORE_AVAILABLE_PATH=${PLUGIN_CORE_AVAILABLE_PATH:="$PLUGIN_CORE_PATH/available"} -CUSTOM_TEMPLATE_SSL_DOMAIN=customssltemplate.dokku.me +CUSTOM_TEMPLATE_SSL_DOMAIN="customssltemplate.${DOKKU_DOMAIN}" UUID=$(uuidgen) TEST_APP="rdmtestapp-${UUID}" TEST_NETWORK="test-network-${UUID}" @@ -57,8 +58,6 @@ flunk() { } # ShellCheck doesn't know about $status from Bats -# shellcheck disable=SC2154 -# shellcheck disable=SC2120 assert_success() { if [[ "$status" -ne 0 ]]; then flunk "command failed with exit status $status" @@ -68,8 +67,6 @@ assert_success() { } # ShellCheck doesn't know about $status from Bats -# shellcheck disable=SC2154 -# shellcheck disable=SC2120 assert_failure() { if [[ "$status" -eq 0 ]]; then flunk "expected failed exit status" @@ -97,7 +94,6 @@ assert_not_equal() { } # ShellCheck doesn't know about $output from Bats -# shellcheck disable=SC2154 assert_output() { local expected if [[ $# -eq 0 ]]; then @@ -109,7 +105,6 @@ assert_output() { } # ShellCheck doesn't know about $output from Bats -# shellcheck disable=SC2154 assert_not_output() { local expected if [[ $# -eq 0 ]]; then @@ -121,19 +116,16 @@ assert_not_output() { } # ShellCheck doesn't know about $output from Bats -# shellcheck disable=SC2154 assert_output_exists() { [[ -n "$output" ]] || flunk "expected output, found none" } # ShellCheck doesn't know about $output from Bats -# shellcheck disable=SC2154 assert_output_not_exists() { [[ -z "$output" ]] || flunk "expected no output, found some" } # ShellCheck doesn't know about $output from Bats -# shellcheck disable=SC2154 assert_output_contains() { local input="$output" local expected="$1" @@ -147,7 +139,6 @@ assert_output_contains() { } # ShellCheck doesn't know about $lines from Bats -# shellcheck disable=SC2154 assert_line() { if [[ "$1" -ge 0 ]] 2>/dev/null; then assert_equal "$2" "${lines[$1]}" @@ -161,7 +152,6 @@ assert_line() { } # ShellCheck doesn't know about $lines from Bats -# shellcheck disable=SC2154 assert_line_count() { declare EXPECTED="$1" local num_lines="${#lines[@]}" @@ -206,9 +196,7 @@ create_key() { } destroy_app() { - local RC="$1" - local RC=${RC:=0} - local APP="$2" + declare RC="${1:-0}" APP="$2" local TEST_APP=${APP:=$TEST_APP} dokku --force apps:destroy "$TEST_APP" return "$RC" @@ -218,7 +206,6 @@ destroy_key() { rm -f /tmp/testkey* &>/dev/null || true } -# shellcheck disable=SC2119 check_urls() { local PATTERN="$1" run /bin/bash -c "dokku urls $TEST_APP | grep -E \"${PATTERN}\"" @@ -235,6 +222,22 @@ assert_http_success() { assert_output "200" } +assert_http_localhost_success() { + local scheme="$1" domain="$2" port="${3:-80}" path="${4:-}" content="${5:-}" + run curl --connect-to "$domain:$port:localhost:$port" -kSso /dev/null -w "%{http_code}" "$scheme://$domain:$port$path" + echo "curl: curl --connect-to $domain:$port:localhost:$port -kSso /dev/null -w %{http_code} $scheme://$domain:$port$path" + echo "output: $output" + echo "status: $status" + assert_output "200" + + if [[ -n "$content" ]]; then + run curl --connect-to "$domain:$port:localhost:$port" -kSs "$scheme://$domain:$port$path" + echo "output: $output" + echo "status: $status" + assert_output "$content" + fi +} + assert_ssl_domain() { local domain=$1 assert_app_domain "${domain}" @@ -288,7 +291,7 @@ assert_url() { echo "VHOST: $(cat $DOKKU_ROOT/$TEST_APP/VHOST | xargs)" echo "tls: $(ls $DOKKU_ROOT/$TEST_APP/tls || true)" echo "proxy-is-enabled: $(dokku plugin:trigger proxy-is-enabled "$TEST_APP")" - echo "port-map: $(dokku config:get "$TEST_APP" DOKKU_PROXY_PORT_MAP)" + echo "port-map: $(dokku ports:report "$TEST_APP" --ports-map)" echo "url: $(dokku urls $TEST_APP)" echo "output: $output" echo "status: $status" @@ -302,7 +305,7 @@ assert_urls() { echo "VHOST: $(cat $DOKKU_ROOT/$TEST_APP/VHOST | xargs)" echo "tls: $(ls $DOKKU_ROOT/$TEST_APP/tls || true)" echo "proxy-is-enabled: $(dokku plugin:trigger proxy-is-enabled "$TEST_APP")" - echo "port-map: $(dokku config:get "$TEST_APP" DOKKU_PROXY_PORT_MAP)" + echo "port-map: $(dokku ports:report "$TEST_APP" --ports-map)" echo "urls: $(dokku urls $TEST_APP)" echo "output: $output" echo "status: $status" @@ -314,13 +317,12 @@ assert_urls() { deploy_app() { declare APP_TYPE="$1" GIT_REMOTE="$2" CUSTOM_TEMPLATE="$3" CUSTOM_PATH="$4" local APP_TYPE=${APP_TYPE:="python"} - local GIT_REMOTE=${GIT_REMOTE:="dokku@dokku.me:$TEST_APP"} + local GIT_REMOTE=${GIT_REMOTE:="dokku@${DOKKU_DOMAIN}:$TEST_APP"} local GIT_REMOTE_BRANCH=${GIT_REMOTE_BRANCH:="master"} - local TMP=${CUSTOM_TMP:=$(mktemp -d "/tmp/dokku.me.XXXXX")} + local TMP=${CUSTOM_TMP:=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX")} rmdir "$TMP" && cp -r "${BATS_TEST_DIRNAME}/../../tests/apps/$APP_TYPE" "$TMP" - # shellcheck disable=SC2086 [[ -n "$CUSTOM_TEMPLATE" ]] && $CUSTOM_TEMPLATE $TEST_APP $TMP/$CUSTOM_PATH pushd "$TMP" &>/dev/null || exit 1 @@ -341,7 +343,7 @@ deploy_app() { setup_client_repo() { local TMP - TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") rmdir "$TMP" && cp -r "${BATS_TEST_DIRNAME}/../../tests/apps/nodejs-express" "$TMP" cd "$TMP" || exit 1 git init @@ -357,7 +359,7 @@ setup_test_tls() { local TLS_TYPE="$1" local TLS="/home/dokku/$TEST_APP/tls" - if ! dokku apps:exists "$TEST_APP" >/dev/null 2>&1; then + if ! dokku apps:exists "$TEST_APP" &>/dev/null; then create_app "$TEST_APP" fi mkdir -p "$TLS" @@ -457,7 +459,7 @@ custom_nginx_template() { server { listen [::]:{{ \$listen_port }}; listen {{ \$listen_port }}; - server_name {{ $.NOSSL_SERVER_NAME }} customtemplate.dokku.me; + server_name {{ $.NOSSL_SERVER_NAME }} customtemplate.${DOKKU_DOMAIN}; location / { proxy_pass http://{{ $.APP }}-{{ \$upstream_port }}; @@ -535,6 +537,21 @@ add_postdeploy_command() { echo "${contents}" >"$APP_REPO_DIR/app.json" } +move_dockerfile_into_place() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + mv "$APP_REPO_DIR/alt.Dockerfile" "$APP_REPO_DIR/Dockerfile" +} + +move_expose_dockerfile_into_place() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + cat "$APP_REPO_DIR/expose.Dockerfile" + mv "$APP_REPO_DIR/expose.Dockerfile" "$APP_REPO_DIR/Dockerfile" +} + add_requirements_txt() { local APP="$1" local APP_REPO_DIR="$2" @@ -543,42 +560,42 @@ add_requirements_txt() { } create_network() { - local NETWORK_NAME="$1:=$TEST_NETWORK" + local NETWORK_NAME="${1:-$TEST_NETWORK}" NETWORK=$(docker network ls -q -f name="$NETWORK_NAME") [[ -z "$NETWORK" ]] && docker network create "$NETWORK_NAME" } attach_network() { - local NETWORK_NAME="$1:=$TEST_NETWORK" + local NETWORK_NAME="${1:-$TEST_NETWORK}" NETWORK=$(docker network ls -q -f name="$NETWORK_NAME") [[ -n "$NETWORK" ]] && docker network connect "$NETWORK_NAME" "${TEST_APP}.web.1" } create_attach_network() { - local NETWORK_NAME="$1:=$TEST_NETWORK" + local NETWORK_NAME="${1:-$TEST_NETWORK}" create_network "$NETWORK_NAME" attach_network "$NETWORK_NAME" } delete_network() { - local NETWORK_NAME="$1:=$TEST_NETWORK" + local NETWORK_NAME="${1:-$TEST_NETWORK}" NETWORK=$(docker network ls -q -f name="$NETWORK_NAME") [[ -n "$NETWORK" ]] && docker network rm "$NETWORK_NAME" } detach_network() { - local NETWORK_NAME="$1:=$TEST_NETWORK" + local NETWORK_NAME="${1:-$TEST_NETWORK}" NETWORK=$(docker network ls -q -f name="$NETWORK_NAME") [[ -z "$NETWORK" ]] && docker network disconnect "$NETWORK_NAME" "${TEST_APP}.web.1" } detach_delete_network() { - local NETWORK_NAME="$1:=$TEST_NETWORK" + local NETWORK_NAME="${1:-$TEST_NETWORK}" detach_network "$NETWORK_NAME" delete_network "$NETWORK_NAME" diff --git a/tests/unit/traefik.bats b/tests/unit/traefik.bats index 4885a2aa4..835b974cd 100644 --- a/tests/unit/traefik.bats +++ b/tests/unit/traefik.bats @@ -39,7 +39,7 @@ teardown() { echo "status: $status" assert_success - run deploy_app python dokku@dokku.me:$TEST_APP convert_to_dockerfile + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP convert_to_dockerfile echo "output: $output" echo "status: $status" assert_success @@ -57,28 +57,28 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP-2.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP-2.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run deploy_app python dokku@dokku.me:$TEST_APP convert_to_dockerfile + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP convert_to_dockerfile echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "curl --silent $TEST_APP.dokku.me" + run /bin/bash -c "curl --silent $TEST_APP.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success assert_output "python/http.server" - run /bin/bash -c "curl --silent $TEST_APP-2.dokku.me" + run /bin/bash -c "curl --silent $TEST_APP-2.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -136,7 +136,7 @@ teardown() { run /bin/bash -c "docker inspect traefik-traefik-1 --format '{{ index .Config.Labels \"traefik.http.routers.api.rule\" }}'" echo "output: $output" echo "status: $status" - assert_output "Host(\`traefik.dokku.me\`)" + assert_output "Host(\`traefik.$DOKKU_DOMAIN\`)" assert_success } @@ -194,7 +194,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku traefik:set --global letsencrypt-email test@example.com" echo "output: $output" @@ -233,10 +233,15 @@ teardown() { assert_success assert_output "5000" - run /bin/bash -c "dokku proxy:report $TEST_APP --proxy-port-map" + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" echo "output: $output" echo "status: $status" - assert_output "http:80:5000" + assert_output_not_exists + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000 https:443:5000" } @test "(traefik) show-config without auth set" {