diff --git a/Makefile b/Makefile index 55cbed0cd..39327fef1 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ DOKKU_VERSION ?= master SSHCOMMAND_URL ?= https://raw.githubusercontent.com/dokku/sshcommand/v0.7.0/sshcommand +PROCFILE_UTIL_URL ?= https://github.com/josegonzalez/go-procfile-util/releases/download/v0.4.0/procfile-util_0.4.0_linux_x86_64.tgz PLUGN_URL ?= https://github.com/dokku/plugn/releases/download/v0.3.0/plugn_0.3.0_linux_x86_64.tgz SIGIL_URL ?= https://github.com/gliderlabs/sigil/releases/download/v0.4.0/sigil_0.4.0_Linux_x86_64.tgz STACK_URL ?= https://github.com/gliderlabs/herokuish.git @@ -26,7 +27,7 @@ endif include common.mk -.PHONY: all apt-update install version copyfiles copyplugin man-db plugins dependencies sshcommand plugn docker aufs stack count dokku-installer vagrant-acl-add vagrant-dokku go-build +.PHONY: all apt-update install version copyfiles copyplugin man-db plugins dependencies sshcommand procfile-util plugn docker aufs stack count dokku-installer vagrant-acl-add vagrant-dokku go-build include tests.mk include deb.mk @@ -109,13 +110,13 @@ else echo $(DOKKU_VERSION) > ~dokku/VERSION endif -plugin-dependencies: plugn +plugin-dependencies: plugn procfile-util sudo -E dokku plugin:install-dependencies --core -plugins: plugn docker +plugins: plugn procfile-util docker sudo -E dokku plugin:install --core -dependencies: apt-update sshcommand plugn docker help2man man-db sigil +dependencies: apt-update sshcommand plugn procfile-util docker help2man man-db sigil $(MAKE) -e stack apt-update: @@ -132,6 +133,10 @@ sshcommand: chmod +x /usr/local/bin/sshcommand sshcommand create dokku /usr/local/bin/dokku +procfile-util: + wget -qO /tmp/procfile-util_latest.tgz ${PROCFILE_UTIL_URL} + tar xzf /tmp/procfile-util_latest.tgz -C /usr/local/bin + plugn: wget -qO /tmp/plugn_latest.tgz ${PLUGN_URL} tar xzf /tmp/plugn_latest.tgz -C /usr/local/bin diff --git a/debian/control b/debian/control index 0459eaa80..e2e60dbdd 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Version: 0.12.13 Section: web Priority: optional Architecture: amd64 -Depends: locales, git, make, curl, gcc, man-db, netcat, sshcommand (>= 0.6.0), gliderlabs-sigil, docker-engine-cs (>= 1.7.1) | docker-engine (>= 1.7.1) | docker-io (>= 1.7.1) | docker-ce | docker-ee, net-tools, software-properties-common, python-software-properties | python3-software-properties, rsyslog +Depends: locales, git, make, curl, gcc, man-db, netcat, sshcommand (>= 0.6.0), gliderlabs-sigil, docker-engine-cs (>= 1.7.1) | docker-engine (>= 1.7.1) | docker-io (>= 1.7.1) | docker-ce | docker-ee, net-tools, software-properties-common, procfile-util, python-software-properties | python3-software-properties, rsyslog Recommends: herokuish (>= 0.3.4), parallel, dokku-update Pre-Depends: nginx (>= 1.8.0) | openresty, dnsutils, cgroupfs-mount | cgroup-lite, plugn (>= 0.3.0), sudo, python2.7, debconf Maintainer: Jose Diaz-Gonzalez diff --git a/plugins/common/functions b/plugins/common/functions index cb0a7a2af..fa0d63835 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -392,25 +392,6 @@ get_app_running_container_types() { echo "$CONTAINER_TYPES" } -get_cmd_from_procfile() { - declare desc="parse cmd from app Procfile" - local APP=$1; local PROC_TYPE=$2; local DOKKU_PROCFILE="$DOKKU_ROOT/$APP/DOKKU_PROCFILE" - verify_app_name "$APP" - - if [[ -f $DOKKU_PROCFILE ]]; then - local line; local name; local command - while read -r line || [[ -n "$line" ]]; do - if [[ -z "$line" ]] || [[ "$line" == "#"* ]]; then - continue - fi - line=$(strip_inline_comments "$line") - name="${line%%:*}" - command="${line#*:[[:space:]]}" - [[ "$name" == "$PROC_TYPE" ]] && echo "$command" && break - done < "$DOKKU_PROCFILE" - fi -} - is_deployed() { declare desc="return 0 if given app has a running container" local APP="$1" diff --git a/plugins/ps/functions b/plugins/ps/functions index cbb1793c6..2682aedb1 100755 --- a/plugins/ps/functions +++ b/plugins/ps/functions @@ -3,6 +3,17 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" +get_cmd_from_procfile() { + declare desc="parse cmd from app Procfile" + declare APP="$1" PROC_TYPE="$2" PORT="$3" + local DOKKU_PROCFILE="$DOKKU_ROOT/$APP/DOKKU_PROCFILE" + local COMMAND + verify_app_name "$APP" + + [[ ! -f $DOKKU_PROCFILE ]] && return + procfile-util show --procfile "$DOKKU_PROCFILE" --process-type "$PROC_TYPE" --default-port "$PORT" +} + print_dokku_scale_file() { declare desc="prints contents of DOKKU_SCALE file" local APP="$1"; local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" @@ -23,6 +34,11 @@ extract_procfile() { copy_from_image "$IMAGE" "Procfile" "$DOKKU_PROCFILE" 2>/dev/null || true if [[ -f "$DOKKU_PROCFILE" ]]; then dokku_log_info1_quiet "App Procfile file found ($DOKKU_PROCFILE)" + # shellcheck disable=SC2069 + PROCFILE_ERRORS="$(procfile-util check --procfile "$DOKKU_PROCFILE" 2>&1 >/dev/null || true)" + if [[ -n "$PROCFILE_ERRORS" ]]; then + dokku_log_fail "${PROCFILE_ERRORS}" + fi else dokku_log_info1_quiet "No Procfile found in app image" fi @@ -71,12 +87,21 @@ generate_scale_file() { set_scale() { declare desc="sets app proc type scaling" - local APP="$1"; local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" + declare APP="$1" + local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" + local DOKKU_PROCFILE="$DOKKU_ROOT/$APP/DOKKU_PROCFILE" shift 1 + + extract_procfile "$APP" "$IMAGE_TAG" > /dev/null + trap 'remove_procfile $APP' RETURN INT TERM EXIT + local SCALE_SETTINGS=("$@") for procscale in "${SCALE_SETTINGS[@]}"; do local PROC_NAME=${procscale%%=*} local PROC_COUNT=${procscale#*=} + if [[ -f "$DOKKU_PROCFILE" ]] && ! procfile-util exists --procfile "$DOKKU_PROCFILE" --process-type "$PROC_NAME" > /dev/null 2>&1; then + dokku_log_fail "${PROC_NAME} is not a valid process name" + fi is_number "$PROC_COUNT" || dokku_log_fail "ps:scale $PROC_COUNT is not a number" dokku_log_info1_quiet "Scaling $APP:$PROC_NAME to $PROC_COUNT" if (egrep -q "^${PROC_NAME}=" "$DOKKU_SCALE_FILE" > /dev/null 2>&1); then @@ -148,6 +173,7 @@ ps_scale() { local APP="$1"; verify_app_name "$APP" local IMAGE_TAG=$(get_running_image_tag "$APP") local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" + local DOKKU_PROCFILE="$DOKKU_ROOT/$APP/DOKKU_PROCFILE" shift 1 [[ ! -e $DOKKU_SCALE_FILE ]] && generate_scale_file "$APP" "$IMAGE_TAG" diff --git a/plugins/scheduler-docker-local/scheduler-deploy b/plugins/scheduler-docker-local/scheduler-deploy index c4a463ae6..3faac4875 100755 --- a/plugins/scheduler-docker-local/scheduler-deploy +++ b/plugins/scheduler-docker-local/scheduler-deploy @@ -3,6 +3,7 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/checks/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" +source "$PLUGIN_AVAILABLE_PATH/ps/functions" scheduler-docker-local-scheduler-deploy() { declare desc="deploys an image tag for a given application" @@ -62,13 +63,8 @@ scheduler-docker-local-scheduler-deploy() { declare -a ARG_ARRAY eval "ARG_ARRAY=($DOCKER_ARGS)" - [[ "$DOKKU_HEROKUISH" == "true" ]] && local START_CMD="/start $PROC_TYPE" - - if [[ "$DOKKU_HEROKUISH" == "false" ]]; then - local DOKKU_DOCKERFILE_START_CMD=$(config_get "$APP" DOKKU_DOCKERFILE_START_CMD || true) - local DOKKU_PROCFILE_START_CMD=$(get_cmd_from_procfile "$APP" "$PROC_TYPE") - local START_CMD=${DOKKU_DOCKERFILE_START_CMD:-$DOKKU_PROCFILE_START_CMD} - fi + local START_CMD + [[ "$DOKKU_HEROKUISH" == "true" ]] && START_CMD="/start $PROC_TYPE" if [[ "$PROC_TYPE" == "web" ]]; then ports=($(plugn trigger network-compute-ports "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH")) @@ -81,6 +77,7 @@ scheduler-docker-local-scheduler-deploy() { DOKKU_DOCKER_PORT_ARGS+=" -p $p " done + START_CMD=$(fn-scheduler-docker-local-extract-start-cmd "$APP" "$PROC_TYPE" "$START_CMD" "$DOKKU_HEROKUISH" "$DOKKU_PORT") if [[ "$DOKKU_NETWORK_BIND_ALL" == "false" ]]; then # shellcheck disable=SC2086 cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -e PORT=$DOKKU_PORT "${ARG_ARRAY[@]}" $IMAGE $START_CMD) @@ -89,6 +86,8 @@ scheduler-docker-local-scheduler-deploy() { cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOKKU_DOCKER_PORT_ARGS -e PORT=$DOKKU_PORT "${ARG_ARRAY[@]}" $IMAGE $START_CMD) fi else + START_CMD=$(fn-scheduler-docker-local-extract-start-cmd "$APP" "$PROC_TYPE" "$START_CMD" "$DOKKU_HEROKUISH") + # shellcheck disable=SC2086 cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d "${ARG_ARRAY[@]}" $IMAGE $START_CMD) fi @@ -177,4 +176,18 @@ scheduler-docker-local-scheduler-deploy() { fi } +fn-scheduler-docker-local-extract-start-cmd() { + declare APP="$1" PROC_TYPE="$2" START_CMD="$3" DOKKU_HEROKUISH="$4" PORT="$5" + local DOKKU_DOCKERFILE_START_CMD DOKKU_PROCFILE_START_CMD START_CMD + if [[ "$DOKKU_HEROKUISH" != "false" ]]; then + echo "$START_CMD" + return + fi + + DOKKU_DOCKERFILE_START_CMD=$(config_get "$APP" DOKKU_DOCKERFILE_START_CMD || true) + DOKKU_PROCFILE_START_CMD=$(get_cmd_from_procfile "$APP" "$PROC_TYPE" "$PORT") + START_CMD=${DOKKU_DOCKERFILE_START_CMD:-$DOKKU_PROCFILE_START_CMD} + echo "$START_CMD" +} + scheduler-docker-local-scheduler-deploy "$@" diff --git a/plugins/scheduler-docker-local/scheduler-run b/plugins/scheduler-docker-local/scheduler-run index de53d95db..33929d80e 100755 --- a/plugins/scheduler-docker-local/scheduler-run +++ b/plugins/scheduler-docker-local/scheduler-run @@ -1,8 +1,8 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/ps/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" +source "$PLUGIN_AVAILABLE_PATH/ps/functions" scheduler-docker-local-scheduler-run() { declare desc="runs command in container based on app image" diff --git a/rpm.mk b/rpm.mk index 9fe76e747..61340546d 100644 --- a/rpm.mk +++ b/rpm.mk @@ -95,20 +95,21 @@ endif -v "$$VERSION" \ -a $(RPM_ARCHITECTURE) \ -p "dokku-$$VERSION-1.x86_64.rpm" \ - --depends 'git' \ - --depends 'make' \ - --depends 'curl' \ - --depends 'gcc' \ - --depends 'man-db' \ - --depends 'sshcommand' \ - --depends 'gliderlabs-sigil' \ --depends '/usr/bin/docker' \ --depends 'bind-utils' \ + --depends 'curl' \ + --depends 'gcc' \ + --depends 'git' \ + --depends 'gliderlabs-sigil' \ + --depends 'make' \ + --depends 'man-db' \ + --depends 'nc' \ --depends 'nginx >= 1.8.0' \ --depends 'plugn' \ - --depends 'sudo' \ + --depends 'procfile-util' \ --depends 'python' \ - --depends 'nc' \ + --depends 'sshcommand' \ + --depends 'sudo' \ --after-install rpm/dokku.postinst \ --url "https://github.com/$(DOKKU_REPO_NAME)" \ --description $(DOKKU_DESCRIPTION) \ diff --git a/tests/apps/dockerfile-procfile-bad/CHECKS b/tests/apps/dockerfile-procfile-bad/CHECKS new file mode 100644 index 000000000..068926825 --- /dev/null +++ b/tests/apps/dockerfile-procfile-bad/CHECKS @@ -0,0 +1,5 @@ +WAIT=2 # wait 2 seconds +TIMEOUT=5 # set timeout to 5 seconds +ATTEMPTS=2 # try 2 times + +/ nodejs/express diff --git a/tests/apps/dockerfile-procfile-bad/Dockerfile b/tests/apps/dockerfile-procfile-bad/Dockerfile new file mode 100644 index 000000000..fa82185d5 --- /dev/null +++ b/tests/apps/dockerfile-procfile-bad/Dockerfile @@ -0,0 +1,11 @@ +FROM node:4 + +EXPOSE 3001/udp +EXPOSE 3000/tcp +EXPOSE 3003 + +COPY . /app +WORKDIR /app +RUN npm install + +CMD npm start diff --git a/tests/apps/dockerfile-procfile-bad/Procfile b/tests/apps/dockerfile-procfile-bad/Procfile new file mode 100644 index 000000000..ad7dc37c9 --- /dev/null +++ b/tests/apps/dockerfile-procfile-bad/Procfile @@ -0,0 +1,12 @@ +############################### +# DEVELOPMENT # +############################### + +# Procfile for development using the new threaded worker (scheduler, twitter stream and delayed job) +cron: node worker.js +web: node web.js +<<<<<<< HEAD=0 +worker: node worker.js +========0 +worker: node worker-2.js +>>>>>>> 5bb7ec3e2a8f4f6565432a4fc82c92d4a3603d28=0 diff --git a/tests/apps/dockerfile-procfile-bad/check_deploy b/tests/apps/dockerfile-procfile-bad/check_deploy new file mode 100644 index 000000000..1334888e2 --- /dev/null +++ b/tests/apps/dockerfile-procfile-bad/check_deploy @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +set -e; output="$(curl -s -S "$1")"; echo "$output"; test "$output" == "nodejs/express" diff --git a/tests/apps/dockerfile-procfile-bad/package.json b/tests/apps/dockerfile-procfile-bad/package.json new file mode 100644 index 000000000..397b0ce84 --- /dev/null +++ b/tests/apps/dockerfile-procfile-bad/package.json @@ -0,0 +1,13 @@ +{ + "name": "node-example", + "version": "0.0.1", + "dependencies": { + "express": "2.5.x" + }, + "engines": { + "node": "4.2.x" + }, + "scripts": { + "start": "false" + } +} diff --git a/tests/apps/dockerfile-procfile-bad/web.js b/tests/apps/dockerfile-procfile-bad/web.js new file mode 100644 index 000000000..6b8f4d819 --- /dev/null +++ b/tests/apps/dockerfile-procfile-bad/web.js @@ -0,0 +1,12 @@ +var express = require('express'); + +var app = express.createServer(express.logger()); + +app.get('/', function(request, response) { + response.send('nodejs/express'); +}); + +var port = process.env.PORT || 5000; +app.listen(port, function() { + console.log("Listening on " + port); +}); diff --git a/tests/apps/dockerfile-procfile-bad/worker.js b/tests/apps/dockerfile-procfile-bad/worker.js new file mode 100644 index 000000000..12133530d --- /dev/null +++ b/tests/apps/dockerfile-procfile-bad/worker.js @@ -0,0 +1,6 @@ +function worker() { + console.log('sleeping for 60 seconds'); + setTimeout(worker, 60 * 1000); +} + +worker(); diff --git a/tests/unit/10_git.bats b/tests/unit/10_git.bats index 45e970a18..517b25f2a 100644 --- a/tests/unit/10_git.bats +++ b/tests/unit/10_git.bats @@ -117,4 +117,4 @@ teardown() { echo "output: "$output echo "status: "$status assert_success -} \ No newline at end of file +} diff --git a/tests/unit/10_ps-dockerfile.bats b/tests/unit/10_ps-dockerfile.bats index 15f1eea16..205014fcf 100644 --- a/tests/unit/10_ps-dockerfile.bats +++ b/tests/unit/10_ps-dockerfile.bats @@ -70,6 +70,21 @@ teardown() { done } +@test "(ps:scale) dockerfile non-existent process" { + run bash -c "dokku ps:scale $TEST_APP non-existent=2" + echo "output: $output" + echo "status: $status" + assert_success + + destroy_app + create_app + deploy_app dockerfile-procfile + run bash -c "dokku ps:scale $TEST_APP non-existent=2" + echo "output: $output" + echo "status: $status" + assert_failure +} + @test "(ps:scale) dockerfile" { run bash -c "dokku ps:scale $TEST_APP web=2" echo "output: "$output @@ -166,6 +181,16 @@ teardown() { done } +@test "(ps) dockerfile with bad procfile" { + run deploy_app dockerfile-procfile-bad + echo "output: "$output + echo "status: "$status + assert_failure + + run create_app + assert_success +} + @test "(ps:scale) dockerfile with procfile" { run bash -c "dokku ps:scale $TEST_APP web=2 worker=2" echo "output: "$output @@ -225,4 +250,3 @@ teardown() { assert_success done } - diff --git a/tests/unit/10_ps-general.bats b/tests/unit/10_ps-general.bats index c18c6a746..356cc50f4 100644 --- a/tests/unit/10_ps-general.bats +++ b/tests/unit/10_ps-general.bats @@ -13,16 +13,15 @@ teardown() { } @test "(ps:scale) procfile commands extraction" { - source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_CORE_AVAILABLE_PATH/ps/functions" cat < "$DOKKU_ROOT/$TEST_APP/DOKKU_PROCFILE" -web: node web.js +web: node web.js --port \$PORT worker: node worker.js EOF - run get_cmd_from_procfile "$TEST_APP" web + run get_cmd_from_procfile "$TEST_APP" web 5001 echo "output: "$output echo "status: "$status - assert_output "node web.js" + assert_output "node web.js --port 5001" run get_cmd_from_procfile "$TEST_APP" worker echo "output: "$output