From a5a66dd9169dd3858a72512b40d181f1a475af30 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Mon, 7 Jan 2019 07:42:20 -0500 Subject: [PATCH] feat: add support for the Procfile release command Closes #3136 --- docs/advanced-usage/deployment-tasks.md | 63 +++++++++++++++++++++---- plugins/app-json/internal-functions | 33 ++++++++----- plugins/app-json/pre-deploy | 1 + tests/apps/nodejs-express/Procfile | 1 + 4 files changed, 77 insertions(+), 21 deletions(-) diff --git a/docs/advanced-usage/deployment-tasks.md b/docs/advanced-usage/deployment-tasks.md index 93e9a4d8f..bfe38490e 100644 --- a/docs/advanced-usage/deployment-tasks.md +++ b/docs/advanced-usage/deployment-tasks.md @@ -2,25 +2,52 @@ > New as of 0.5.0 -Sometimes you need to run a command on at deployment time, but before an app is completely deployed. -Common use cases include: +Sometimes you need to run a command on at deployment time, but before an app is completely deployed. Common use cases include: * Checking a database is initialized * Running database migrations * Any commands required to set up the server (e.g. something like a Django `collectstatic`) -## `app.json` and `scripts.dokku` +To support this, Dokku provides support for a special `release` command within your app's `Procfile`, as well as a special `scripts.dokku` key inside of your app's `app.json` file. Be aware that all commands are run within the context of the built docker image - no commands affect the host unless there are volume mounts attached to your app. -Dokku accomplishes this by using an `app.json` file. The format in use is similar to format of Heroku's [app.json](https://devcenter.heroku.com/articles/app-json-schema). -However, Dokku currently only supports the nodes `scripts.dokku.predeploy` and `scripts.dokku.postdeploy`. -For buildpack apps, simply place an `app.json` file in the root of your repository. -For dockerfile apps, place `app.json` in the configured `WORKDIR` directory; otherwise Dokku defaults to the buildpack app behavior of looking in `/app`. +Each "phase" has different expectations and limitations: -> NOTE: postdeploy changes are *NOT* committed to the app image. +- `app.json`: `scripts.dokku.predeploy` + - When to use: This should be used if your app does not support arbitrary build commands and you need to make changes to the built image. + - Are changes committed to the image at this phase: Yes + - Example use-cases + - Bundling assets in a slightly different way + - Installing a custom package from source or copying a binary into place +- `app.json`: `scripts.dokku.postdeploy` + - When to use: This should be used in conjunction with external systems to signal the completion of your deploy. + - Are changes committed to the image at this phase: No + - Example use-cases + - Notifying slack that your app is deployed + - Coordinating traffic routing with a central load balancer +- `Procfile`: `release` + - When to use: This should be used in conjunction with external systems to signal the completion of your app image build. + - Are changes committed to the image at this phase: No + - Example use-cases + - Sending CSS, JS, and other assets from your app’s slug to a CDN or S3 bucket + - Priming or invalidating cache stores + - Running database migrations -### Example app.json +Please keep the above in mind when utilizing deployment tasks. -> NOTE: Only the `scripts.dokku.predeploy` and `scripts.dokku.postdeploy` tasks are supported by Dokku at this time. All other fields will be ignored and can be omitted. +> To execute commands on the host during a release phase, see the [plugin creation documentation](/docs/development/plugin-creation) docs for more information on building your own custom plugin. + +## `app.json` deployment tasks + +Dokku provides limited support for the `app.json` manifest from Heroku (documentation available [here](https://devcenter.heroku.com/articles/app-json-schema)). The keys available for use with Deployment Tasks are: + +- `scripts.dokku.predeploy`: This is run _after_ an app's docker image is built, but _before_ any containers are scheduled. Changes made to your image are committed at this phase. +- `scripts.dokku.postdeploy`: This is run _after_ an app's containers are scheduled. Changes made to your image are *not* committed at this phase. + +For buildpack-based deployments, the location of the `app.json` file should be at the root of your repository. Dockefile-based app deploys should have the `app.json` in the configured `WORKDIR` directory; otherwise Dokku defaults to the buildpack app behavior of looking in `/app`. + +> Warning: Any failed `app.json` deployment task will fail the deploy. In the case of either phase, a failure will not affect any running containers. + +The following is an example `app.json` file. Please note that only the `scripts.dokku.predeploy` and `scripts.dokku.postdeploy` tasks are supported by Dokku at this time. All other fields will be ignored and can be omitted. ```json { @@ -32,3 +59,19 @@ For dockerfile apps, place `app.json` in the configured `WORKDIR` directory; oth } } ``` + +## Procfile Release command + +> New as of 0.14.0 + +The `Procfile` also supports a special `release` command which acts in a similar way to the [Heroku Release Phase](https://devcenter.heroku.com/articles/release-phase). This command is executed _after_ an app's docker image is built, but _before_ any containers are scheduled. This is also run _after_ any command executed by `scripts.dokku.predeploy`. + +To use the `release` command, simply add a `release` stanza to your Procfile. + +```Procfile +release: curl https://some.external.api.service.com/deployment?state=built +``` + +Unlike the `scripts.dokku.predeploy` command, changes made during by the `release` command are *not* persisted to disk. + +> Warning: scaling the release command up will likely result in unspecified issues within your deployment, and is highly discouraged. diff --git a/plugins/app-json/internal-functions b/plugins/app-json/internal-functions index 1a16d4496..4a2d5debc 100755 --- a/plugins/app-json/internal-functions +++ b/plugins/app-json/internal-functions @@ -3,35 +3,46 @@ set -eo pipefail [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" +source "$PLUGIN_AVAILABLE_PATH/ps/functions" get_phase_script() { declare desc="extracts app.json from app image and returns the appropriate json key/value" - local IMAGE="$1" - local PHASE_SCRIPT_KEY="$2" + declare IMAGE_TAG="$1" PHASE_SCRIPT_KEY="$2" local GET_PHASE_SCRIPT_TMP_WORK_DIR=$(mktemp -d "/tmp/dokku_get_phase_script.XXXX") local APP_JSON_FILE="$GET_PHASE_SCRIPT_TMP_WORK_DIR/app.json" trap 'rm -rf "$GET_PHASE_SCRIPT_TMP_WORK_DIR" > /dev/null' RETURN INT TERM copy_from_image "$IMAGE" "app.json" "$GET_PHASE_SCRIPT_TMP_WORK_DIR" 2>/dev/null || true - if [[ -f "$APP_JSON_FILE" ]]; then - local VALUE=$(get_json_value "scripts.dokku.${PHASE_SCRIPT_KEY}" <"$APP_JSON_FILE") - else + if [[ ! -f "$APP_JSON_FILE" ]]; then return 0 fi - echo "$VALUE" + get_json_value "scripts.dokku.${PHASE_SCRIPT_KEY}" <"$APP_JSON_FILE" +} + +get_release_cmd() { + declare desc="extracts the release command from a given app's procfile" + declare APP="$1" IMAGE_TAG="$2" + + extract_procfile "$APP" "$IMAGE_TAG" >/dev/null + trap 'remove_procfile $APP' RETURN INT TERM EXIT + + get_cmd_from_procfile "$APP" "release" "5000" || true } execute_script() { declare desc="executes appropriate phase script key from app.json" - local APP="$1" - local IMAGE_TAG="$2" - local PHASE_SCRIPT_KEY="$3" - local IMAGE id + declare APP="$1" IMAGE_TAG="$2" PHASE_SCRIPT_KEY="$3" + local IMAGE id SCRIPT_CMD IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG") - local SCRIPT_CMD=$(get_phase_script "$IMAGE" "$PHASE_SCRIPT_KEY" 2>/dev/null) + if [[ "$PHASE_SCRIPT_KEY" == "release" ]]; then + SCRIPT_CMD=$(get_release_cmd "$APP" "$IMAGE" 2>/dev/null) + else + SCRIPT_CMD=$(get_phase_script "$IMAGE" "$PHASE_SCRIPT_KEY" 2>/dev/null) + fi + if [[ -z "$SCRIPT_CMD" ]]; then return fi diff --git a/plugins/app-json/pre-deploy b/plugins/app-json/pre-deploy index eb8b3e620..f1598a7ab 100755 --- a/plugins/app-json/pre-deploy +++ b/plugins/app-json/pre-deploy @@ -12,6 +12,7 @@ app_json_pre_deploy() { local PHASE_SCRIPT_KEY="predeploy" execute_script "$APP" "$IMAGE_TAG" "$PHASE_SCRIPT_KEY" + execute_script "$APP" "$IMAGE_TAG" "release" } app_json_pre_deploy "$@" diff --git a/tests/apps/nodejs-express/Procfile b/tests/apps/nodejs-express/Procfile index e9bf88240..f38a198e3 100644 --- a/tests/apps/nodejs-express/Procfile +++ b/tests/apps/nodejs-express/Procfile @@ -7,6 +7,7 @@ cron: node worker.js web: node web.js # testing inline comment worker: node worker.js custom: echo -n +release: touch /app/release.test # Old version with separate processes (use this if you have issues with the threaded version)