这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions docs/deployment/deployment-tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Deployment Tasks

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 initialised
* Running database migrations
* Any commands required to set up the server (e.g. something like a Django `collectstatic`)

## `app.json` and `scripts.dokku`

Dokku accomplishes this by using an `app.json` file. We (mostly) use the same format as 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`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we support scripts.postdeploy as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. We said in #1824 that we didn't want to overload the heroku bits of app.json. I took that to mean we would support only the things that correlate directly or implement our own. Meaning, Heroku says they only run scripts.postdeploy at app creation and we wanted something different. With that understanding we can reuse things like buildpacks and env since we'll use those in the same way as Heroku.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, what I'm saying is should we also implement their scripts.postdeploy hook at app creation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. It feels a little confusing from a user perspective though. As that wasn't part of the original issue (#1824), I propose we punt on this one and see if we get any user feedback asking for it.

Simply place an `app.json` file in the root of your repository.
NOTE: `app.json` is only supported in buildpack-deployed apps and postdeploy changes are not committed to the app image.

### Example app.json

```
{
"name": "barebones nodejs",
"description": "A barebones Node.js app using Express 4.",
"keywords": [
"nodejs",
"express"
],
"repository": "https://github.com/michaelshobbs/node-js-sample",
"scripts": {
"dokku": {
"predeploy": "touch /app/predeploy.test",
"postdeploy": "touch /app/postdeploy.test"
}
},
"env": {
"SECRET_TOKEN": {
"description": "A secret key for verifying the integrity of signed cookies.",
"value": "secret"
},
"WEB_CONCURRENCY": {
"description": "The number of processes to run.",
"generator": "echo 5"
}
},
"image": "gliderlabs/herokuish",
"addons": [
"dokku-postgres",
"dokku-redis"
],
"buildpacks": [
{
"url": "https://github.com/heroku/heroku-buildpack-nodejs"
}
]
}
```
2 changes: 1 addition & 1 deletion docs/development/plugin-triggers.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ dokku postgres:link $POSTGRES $APP

- Description: Allows running of commands after a deploy has completed. Dokku core currently uses this to switch traffic on nginx.
- Invoked by: `dokku deploy`
- Arguments: `$APP $INTERNAL_PORT $INTERNAL_IP_ADDRESS`
- Arguments: `$APP $INTERNAL_PORT $INTERNAL_IP_ADDRESS $IMAGE_TAG`
- Example:

```shell
Expand Down
2 changes: 1 addition & 1 deletion dokku
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ case "$1" in
done < "$DOKKU_SCALE_FILE"

dokku_log_info1 "Running post-deploy"
plugn trigger post-deploy $APP $port $ipaddr
plugn trigger post-deploy $APP $port $ipaddr $IMAGE_TAG
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should really be cleaned up imo. port and then ip? ew.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wut?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pre-deploy hook is:

plugn trigger pre-deploy $APP $IMAGE_TAG

Maybe we should follow that and instead do:

plugn trigger post-deploy $APP $IMAGE_TAG $ipaddr $port

Or similar? What uses the port/ipaddr from here that cannot retrieve it from some other helper?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see. This would break any post-deploy plugins that used those arguments. While I agree with you that this should probably happen at some point, I don't think it should go in this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant post-deploy in my second example.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I meant that too :)


# kill the old container
if [[ -n "$oldids" ]]; then
Expand Down
59 changes: 59 additions & 0 deletions plugins/00_dokku-standard/exec-app-json-scripts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
APP="$1"

case "$0" in
*pre-deploy)
IMAGE_TAG="$2"
IMAGE=$(get_app_image_name $APP $IMAGE_TAG)
PHASE_SCRIPT_KEY="predeploy"
;;
*post-deploy)
IMAGE_TAG="$4"
IMAGE=$(get_app_image_name $APP $IMAGE_TAG)
PHASE_SCRIPT_KEY="postdeploy"
;;
esac

get_phase_script() {
local PHASE_SCRIPT_KEY="$1"
local TMP_WORK_DIR=$(mktemp -d)
local APP_JSON_FILE="$TMP_WORK_DIR/app.json"
trap 'rm -rf "$TMP_WORK_DIR" > /dev/null' RETURN

copy_from_image "$IMAGE" "/app/app.json" "$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
return 0
fi

echo "$VALUE"
}

execute_script() {
local SCRIPT_CMD=$(get_phase_script "$PHASE_SCRIPT_KEY")
if [[ -n "$SCRIPT_CMD" ]];then
dokku_log_info1 "Running '$SCRIPT_CMD' in app container"
local COMMAND="cd /app ; "
local COMMAND+=" for file in /app/.profile.d/*; do source \$file; done ; "
local COMMAND+=" echo restoring installation cache... ; "
local COMMAND+=" cp -rfp /cache /tmp/ || true ; "
local COMMAND+=" $SCRIPT_CMD ; "
local COMMAND+=" echo removing installation cache... ; "
local COMMAND+=" rm -rf /tmp/cache || true "
local DOCKER_ARGS=$(: | plugn trigger docker-args-deploy $APP)
local CACHE_DIR="$DOKKU_ROOT/$APP/cache"
id=$(docker run $DOKKU_GLOBAL_RUN_ARGS --label=dokku_phase_script=${PHASE_SCRIPT_KEY} -d -v $CACHE_DIR:/cache $DOCKER_ARGS $IMAGE /bin/bash -c "$COMMAND")
test "$(docker wait $id)" -eq 0
dokku_container_log_verbose_quiet $id
if [[ "$PHASE_SCRIPT_KEY" != "postdeploy" ]];then
docker commit "$id" "$IMAGE" > /dev/null
fi
fi
}

dokku_log_info1 "Attempting to run scripts.dokku.$PHASE_SCRIPT_KEY from app.json (if defined)"
execute_script
1 change: 1 addition & 0 deletions plugins/00_dokku-standard/post-deploy
1 change: 1 addition & 0 deletions plugins/00_dokku-standard/pre-deploy
23 changes: 23 additions & 0 deletions plugins/common/functions
Original file line number Diff line number Diff line change
Expand Up @@ -599,3 +599,26 @@ get_app_urls() {
fi
fi
}

get_json_value() {
# return value of provided json key from a json stream on stdin
# JSON_NODE should be expresses as either a top-level object that has no children
# or in the format of json.node.path
local JSON_NODE="$1"
local JSON_NODE=${JSON_NODE//\./\"][\"}
local JSON_NODE="[\"${JSON_NODE}\"]"
cat | python -c 'import json,sys;obj=json.load(sys.stdin);print json.dumps(obj'"${JSON_NODE}"').strip("\"")';
}

get_json_keys() {
# return space-separated list of json keys from json provided on stdin
# JSON_NODE should be expressed as json.node.path and is expected to have children
local JSON_NODE="$1"
local JSON_NODE=${JSON_NODE//\./\"][\"}
local JSON_NODE="[\"${JSON_NODE}\"]"
if [[ "$JSON_NODE" == "[\"\"]" ]]; then
cat | python -c 'import json,sys;obj=json.load(sys.stdin);print " ".join(obj.keys())';
else
cat | python -c 'import json,sys;obj=json.load(sys.stdin);print " ".join(obj'"${JSON_NODE}"'.keys())';
fi
}
38 changes: 38 additions & 0 deletions tests/apps/nodejs-express/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "Sample node.js express app",
"description": "Used in dokku's test suite",
"keywords": [
"nodejs",
"express",
"testing"
],
"website": "http://dokku.viewdocs.io/dokku/",
"repository": "https://github.com/dokku/dokku",
"logo": "https://raw.githubusercontent.com/dokku/dokku/master/docs/assets/dokku.png",
"scripts": {
"dokku": {
"predeploy": "touch /app/predeploy.test",
"postdeploy": "touch /app/postdeploy.test"
}
},
"env": {
"SECRET_TOKEN": {
"description": "A secret key for verifying the integrity of signed cookies.",
"value": "secret"
},
"WEB_CONCURRENCY": {
"description": "The number of processes to run.",
"generator": "echo 5"
}
},
"image": "gliderlabs/herokuish",
"addons": [
"dokku-postgres",
"dokku-redis"
],
"buildpacks": [
{
"url": "https://github.com/heroku/heroku-buildpack-nodejs"
}
]
}
21 changes: 21 additions & 0 deletions tests/unit/10_core_1.bats
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,24 @@ EOF
echo "status: "$status
assert_output "3001/udp 3000/tcp 3003"
}

@test "(core) app.json scripts" {
deploy_app

run /bin/bash -c "dokku run $TEST_APP ls /app/prebuild.test"
echo "output: "$output
echo "status: "$status
assert_failure

run /bin/bash -c "dokku run $TEST_APP ls /app/predeploy.test"
echo "output: "$output
echo "status: "$status
assert_success

CID=$(docker ps -a -q -f "ancestor=dokku/${TEST_APP}" -f "label=dokku_phase_script=postdeploy")
IMAGE_ID=$(docker commit $CID dokku-test/${TEST_APP})
run /bin/bash -c "docker run -ti $IMAGE_ID ls /app/postdeploy.test"
echo "output: "$output
echo "status: "$status
assert_success
}