diff --git a/.gitignore b/.gitignore index c1b47c1e0fb..a81ffdbd759 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ -.vagrant +.vagrant/* .DS_Store stack.tgz -build -tmp +tmp/* *.deb diff --git a/docs/development/plugin-creation.md b/docs/development/plugin-creation.md index c1822e1d938..82e3b036333 100644 --- a/docs/development/plugin-creation.md +++ b/docs/development/plugin-creation.md @@ -7,14 +7,53 @@ If you create your own plugin: 3. Edit [this page](http://progrium.viewdocs.io/dokku/plugins) and add a link to it. 4. Subscribe to the [dokku development blog](http://progrium.com) to be notified about API changes and releases -### Sample plugin +### Sample plugin - new structure +The below plugin is a dummy `dokku hello` plugin. + +hello/command + +```shell +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +APP="$2"; + +echo "Hello $APP" +``` + +hello/subcommands/world + +```shell +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +APP="$2"; + +echo "Hello world" +``` + +hello/help.txt + +```shell +: Says "Hello " +hello:world Says "Hello world" +``` + + +### Sample plugin - old structure (still supported but not advised) The below plugin is a dummy `dokku hello` plugin. If your plugin exposes commands, this is a good template for your `commands` file: ```shell #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$(dirname $0)/../common/functions" +source "$PLUGIN_PATH/common/functions" case "$1" in hello) diff --git a/dokku b/dokku index 3b4a7c26af1..7f48603f541 100755 --- a/dokku +++ b/dokku @@ -138,22 +138,6 @@ case "$1" in docker rmi $(docker images -f 'dangling=true' -q) &> /dev/null & ;; - plugins) - ls -1 -d $PLUGIN_PATH/*/ - ;; - - plugins-install) - pluginhook install - ;; - - plugins-install-dependencies) - pluginhook dependencies - ;; - - plugins-update) - pluginhook update - ;; - # DEPRECATED as of v0.3.14 deploy:all) echo "*DEPRECATED* in v0.3.14: deploy:all will be removed in future versions" @@ -165,29 +149,24 @@ case "$1" in echo "" echo "Options:" - cat< /dev/null - - case "$IMAGE_SOURCE_TYPE" in - buildstep) - id=$(tar -c . | docker run -i -a stdin $DOKKU_IMAGE /bin/bash -c "mkdir -p /app && tar -xC /app") - test "$(docker wait $id)" -eq 0 - docker commit $id $IMAGE > /dev/null - [[ -d $CACHE_DIR ]] || mkdir $CACHE_DIR - # *DEPRECATED* in v0.4.0: `pluginhook pre-build` will be removed in future releases - pluginhook pre-build "$APP" - pluginhook pre-build-buildstep "$APP" - - # *DEPRECATED* in v0.3.14: `pluginhook docker-args` will be removed in future releases - # https://github.com/progrium/dokku/issues/896 & https://github.com/progrium/dokku/issues/906 - DOCKER_ARGS=$(: | pluginhook docker-args $APP build) - DOCKER_ARGS+=$(: | pluginhook docker-args-build $APP) - id=$(docker run -d -v $CACHE_DIR:/cache -e CACHE_PATH=/cache $DOCKER_ARGS $IMAGE /build/builder) - docker attach $id - test "$(docker wait $id)" -eq 0 - docker commit $id $IMAGE > /dev/null - - # *DEPRECATED* in v0.4.0: `pluginhook post-build` will be removed in future releases - pluginhook post-build "$APP" - pluginhook post-build-buildstep "$APP" - ;; - - dockerfile) - # extract first port from Dockerfile - DOCKERFILE_PORT=$(grep EXPOSE Dockerfile | head -1 | awk '{ print $2 }') - [[ -n "$DOCKERFILE_PORT" ]] && dokku config:set-norestart $APP DOKKU_DOCKERFILE_PORT=$DOCKERFILE_PORT - - # sticking with same pattern of building app image before pre-build for now. - docker build -t "$IMAGE" . - # buildstep pluginhooks don't necessarily make sense for dockerfiles. call the new breed!!! - pluginhook pre-build-dockerfile "$APP" - pluginhook post-build-dockerfile "$APP" - ;; - - *) - dokku_log_fail "Building image source type $IMAGE_SOURCE_TYPE not supported!" - ;; - esac - ;; - - release) - APP="$2"; IMAGE="dokku/$APP"; IMAGE_SOURCE_TYPE="$3" - case "$IMAGE_SOURCE_TYPE" in - buildstep) - # *DEPRECATED* in v0.4.0: `pluginhook pre-release` will be removed in future releases - pluginhook pre-release "$APP" - pluginhook pre-release-buildstep "$APP" - if [[ -f "$DOKKU_ROOT/ENV" ]]; then - id=$(docker run -i -a stdin $IMAGE /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/00-global-env.sh" < "$DOKKU_ROOT/ENV") - test "$(docker wait $id)" -eq 0 - docker commit $id $IMAGE > /dev/null - fi - if [[ -f "$DOKKU_ROOT/$APP/ENV" ]]; then - id=$(docker run -i -a stdin $IMAGE /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/01-app-env.sh" < "$DOKKU_ROOT/$APP/ENV") - test "$(docker wait $id)" -eq 0 - docker commit $id $IMAGE > /dev/null - fi - # *DEPRECATED* in v0.4.0: `pluginhook post-release` will be removed in future releases - pluginhook post-release "$APP" - pluginhook post-release-buildstep "$APP" - ;; - - dockerfile) - # buildstep pluginhooks don't necessarily make sense for dockerfiles. call the new breed!!! - pluginhook pre-release-dockerfile "$APP" - pluginhook post-release-dockerfile "$APP" - ;; - - *) - dokku_log_fail "Releasing image source type $IMAGE_SOURCE_TYPE not supported!" - ;; - esac - ;; - - trace) - [[ -d $DOKKU_ROOT/.dokkurc ]] || mkdir -p $DOKKU_ROOT/.dokkurc - [[ "$2" == "on" ]] || [[ "$2" == "off" ]] || { - echo "Valid trace options are [on/off]" - exit 1 - } - - if [[ "$2" == "on" ]]; then - echo "Enabling dokku trace" - echo "export DOKKU_TRACE=1" > $DOKKU_ROOT/.dokkurc/DOKKU_TRACE - fi - - if [[ "$2" == "off" ]]; then - echo "Disabling dokku trace" - rm -f $DOKKU_ROOT/.dokkurc/DOKKU_TRACE - fi - ;; - - delete) - dokku apps:destroy $2 - ;; - - ls) - apps=$(ls -d $DOKKU_ROOT/*/ 2>/dev/null) || (echo "You haven't depoyed any applications yet" && exit 1) - - dokku_col_log_info2_quiet "App Name" "Container id" - - for app in $apps; do - if [[ -f $app/CONTAINER ]]; then - dokku_col_log_msg "$(basename $app)" "$(< $app/CONTAINER)" - else - dokku_col_log_msg "$(basename $app)" - fi - done - ;; - - logs) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2"; - - if [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]]; then - CONTAINER=$(<$DOKKU_ROOT/$APP/CONTAINER) - if [[ $3 == "-t" ]]; then - docker logs --follow $CONTAINER - else - docker logs $CONTAINER | tail -n 100 - fi - else - echo "Application's container not found" - fi - ;; - - run) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2"; IMAGE="dokku/$APP" - - shift 2 - - DOCKER_ARGS=$(: | pluginhook docker-args $APP run) - DOCKER_ARGS+=$(: | pluginhook docker-args-run $APP) - [[ $DOKKU_RM_CONTAINER ]] && DOKKU_RUN_OPTS="--rm" - has_tty && DOKKU_RUN_OPTS+=" -i -t" - is_image_buildstep_based "$IMAGE" && EXEC_CMD="/exec" - - docker run $DOKKU_RUN_OPTS $DOCKER_ARGS $IMAGE $EXEC_CMD "$@" - ;; - - url | urls) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2"; - - if [[ -s "$DOKKU_ROOT/$APP/URLS" ]]; then - case "$1" in - url) - grep "^http" "$DOKKU_ROOT/$APP/URLS" | head -1 - ;; - urls) - grep "^http" "$DOKKU_ROOT/$APP/URLS" - ;; - esac - - exit 0 - fi - - SCHEME="http"; SSL="$DOKKU_ROOT/$APP/tls"; WILDCARD_SSL="$DOKKU_ROOT/tls" - if [[ -e "$SSL/server.crt" && -e "$SSL/server.key" ]] || [[ -e "$WILDCARD_SSL/server.crt" && -e "$WILDCARD_SSL/server.key" ]]; then - SCHEME="https" - fi - - if [[ -f "$DOKKU_ROOT/VHOST" ]]; then - echo "$SCHEME://$(< "$DOKKU_ROOT/VHOST")" - else - echo "$SCHEME://$(< "$DOKKU_ROOT/HOSTNAME"):$(< "$DOKKU_ROOT/$APP/PORT")" - fi - ;; - - version) - cat "$DOKKU_ROOT/VERSION" || { - echo "Unable to determine dokku's version" 2>&1 - exit 1 - } - ;; - - help) - cat && cat< [-t] Show the last logs for an application (-t follows) - run Run a command in the environment of an application - url Show the first URL for an application (compatibility) - urls Show all URLs for an application - version Print dokku's version -EOF - ;; - - *) - exit $DOKKU_NOT_IMPLEMENTED_EXIT - ;; - -esac diff --git a/plugins/WARNING b/plugins/WARNING deleted file mode 100644 index d3eee9aacc8..00000000000 --- a/plugins/WARNING +++ /dev/null @@ -1,2 +0,0 @@ - -Please note that the hooks in Dokku are experimental and are likely to change quite a bit. \ No newline at end of file diff --git a/plugins/apps/README.md b/plugins/apps/README.md new file mode 100644 index 00000000000..ca54c841085 --- /dev/null +++ b/plugins/apps/README.md @@ -0,0 +1,4 @@ +# Dokku core apps plugin + +This plugin handles creation and destruction of applications. + diff --git a/plugins/apps/command b/plugins/apps/command new file mode 100755 index 00000000000..2a8128d095c --- /dev/null +++ b/plugins/apps/command @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" + +dokku_log_info2_quiet "My Apps" +find $DOKKU_ROOT -follow -maxdepth 1 -type d \( ! -iname ".*" \) -not -path $DOKKU_ROOT/tls | sed 's|^\./||g' | sed 's|'$DOKKU_ROOT'\/||' | tail -n +2 | sort diff --git a/plugins/apps/commands b/plugins/apps/commands deleted file mode 100755 index a82aa1e4fbf..00000000000 --- a/plugins/apps/commands +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$(dirname $0)/../common/functions" - -case "$1" in - apps) - dokku_log_info2_quiet "My Apps" - find $DOKKU_ROOT -follow -maxdepth 1 -type d \( ! -iname ".*" \) -not -path $DOKKU_ROOT/tls | sed 's|^\./||g' | sed 's|'$DOKKU_ROOT'\/||' | tail -n +2 | sort - ;; - - apps:create) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - [[ -d "$DOKKU_ROOT/$2" ]] && dokku_log_warn "Name is already taken" && exit 1 - APP="$2" - - mkdir -p "$DOKKU_ROOT/$APP" - echo "Creating $APP... done" - ;; - - apps:destroy) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - [[ "$2" == "tls" ]] && echo "Unable to destroy tls directory" && exit 1 - APP="$2"; IMAGE="dokku/$APP" - - [[ "$3" == "force" ]] && DOKKU_APPS_FORCE_DELETE=1 - if [[ -z "$DOKKU_APPS_FORCE_DELETE" ]]; then - dokku_log_warn "WARNING: Potentially Destructive Action" - dokku_log_warn "This command will destroy $APP (including all add-ons)." - dokku_log_warn "To proceed, type \"$APP\"" - echo "" - - read -p "> " app_name - if [[ "$app_name" != "$APP" ]]; then - dokku_log_warn "Confirmation did not match $APP. Aborted." - exit 1 - fi - fi - - echo "Destroying $APP (including all add-ons)" - - pluginhook pre-delete $APP - if [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]]; then - ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") - - docker stop $ID > /dev/null || true - docker rm $ID > /dev/null || true - fi - - docker images | grep $IMAGE | awk '{print $3}' | xargs docker rmi &> /dev/null & - - pluginhook post-delete $APP - ;; - - help | apps:help) - cat && cat< Create a new app - apps:destroy Permanently destroy an app -EOF - ;; - - *) - exit $DOKKU_NOT_IMPLEMENTED_EXIT - ;; - -esac diff --git a/plugins/apps/help.txt b/plugins/apps/help.txt new file mode 100644 index 00000000000..944c547e6ff --- /dev/null +++ b/plugins/apps/help.txt @@ -0,0 +1,3 @@ +: List your apps +apps:create Create a new app +apps:destroy Permanently destroy an app diff --git a/plugins/apps/subcommands/create b/plugins/apps/subcommands/create new file mode 100755 index 00000000000..587562cfd28 --- /dev/null +++ b/plugins/apps/subcommands/create @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +[[ -d "$DOKKU_ROOT/$2" ]] && dokku_log_warn "Name is already taken" && exit 1 +APP="$2" + +mkdir -p "$DOKKU_ROOT/$APP" +echo "Creating $APP... done" diff --git a/plugins/apps/subcommands/destroy b/plugins/apps/subcommands/destroy new file mode 100755 index 00000000000..4157d3983a7 --- /dev/null +++ b/plugins/apps/subcommands/destroy @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +[[ "$2" == "tls" ]] && echo "Unable to destroy tls directory" && exit 1 +APP="$2"; IMAGE="dokku/$APP" + +[[ "$3" == "force" ]] && DOKKU_APPS_FORCE_DELETE=1 +if [[ -z "$DOKKU_APPS_FORCE_DELETE" ]]; then + dokku_log_warn "WARNING: Potentially Destructive Action" + dokku_log_warn "This command will destroy $APP (including all add-ons)." + dokku_log_warn "To proceed, type \"$APP\"" + echo "" + + read -p "> " app_name + if [[ "$app_name" != "$APP" ]]; then + dokku_log_warn "Confirmation did not match $APP. Aborted." + exit 1 + fi +fi + +echo "Destroying $APP (including all add-ons)" + +pluginhook pre-delete $APP +if [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]]; then + ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") + + docker stop $ID > /dev/null || true + docker rm $ID > /dev/null || true +fi + +docker images | grep $IMAGE | awk '{print $3}' | xargs docker rmi &> /dev/null & + +pluginhook post-delete $APP diff --git a/plugins/backup/README.md b/plugins/backup/README.md new file mode 100644 index 00000000000..72d2aebe162 --- /dev/null +++ b/plugins/backup/README.md @@ -0,0 +1,12 @@ +# Dokku core backup system + +This plugin provides the commands for exporting and importing backups of a +Dokku configuration (including the list of apps and any plugin configurations +for those apps), as well as hooks for the backup of core Dokku configuration +files. + +Note that this does *not* backup the content of the apps themselves: to back up +your apps, you should be using another solution, such as pushing your apps to a +Git backup server. Similarly, you should have a relevant backup solution for +any data you may be keeping through a datastore plugin. + diff --git a/plugins/backup/commands b/plugins/backup/commands deleted file mode 100755 index 3eacdde2a04..00000000000 --- a/plugins/backup/commands +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x - -CURRENT_BACKUP_VERSION=1 - -case "$1" in - backup:export) - OUTPUT_FILE="$2" - BACKUP_DIR="$DOKKU_ROOT" - - BACKUP_TMP_DIR=$(mktemp -d) - BACKUP_TMP_FILE="$BACKUP_TMP_DIR/backup.tar" - - : | pluginhook backup-export 1 $BACKUP_DIR | tar -cf $BACKUP_TMP_FILE --files-from - - - pushd $BACKUP_DIR > /dev/null - find . -maxdepth 1 -type d -not -name . | sed 's:./::g' > "$BACKUP_TMP_DIR/.dokku_backup_apps" - popd > /dev/null - - # we want to insert the files in the root of the tar - pushd $BACKUP_TMP_DIR > /dev/null - echo $CURRENT_BACKUP_VERSION > .dokku_backup_version - tar --append -f $BACKUP_TMP_FILE .dokku_backup_version - tar --append -f $BACKUP_TMP_FILE .dokku_backup_apps - popd > /dev/null - - # if no file specified, output to stdout - if [[ -z $OUTPUT_FILE ]]; then - cat $BACKUP_TMP_FILE - else - mv $BACKUP_TMP_FILE $OUTPUT_FILE - fi - - rm -rf $BACKUP_TMP_DIR - ;; - - backup:import) - if [[ $2 == "-f" ]]; then - force=true - shift - else - force=false - fi - - INPUT_FILE="$2" - [[ -z $INPUT_FILE ]] && INPUT_FILE="-" - - BACKUP_TMP_DIR=$(mktemp -d) - - tar xf $INPUT_FILE --directory=$BACKUP_TMP_DIR - - if [[ ! -f $BACKUP_TMP_DIR/.dokku_backup_version ]]; then - echo "Unable to determine backup version" - exit 1 - fi - - VERSION=$(< $BACKUP_TMP_DIR/.dokku_backup_version) - if [[ $VERSION -ne 1 ]]; then - echo "Unknown format version $VERSION" - exit 1 - fi - - echo "Importing a version $VERSION backup..." - - BACKUP_ROOT="$BACKUP_TMP_DIR""$DOKKU_ROOT" - TARGET_DIR="$DOKKU_ROOT" - - if ! pluginhook backup-check $VERSION "$BACKUP_ROOT" "$TARGET_DIR" "$BACKUP_TMP_DIR/.dokku_backup_apps"; then - if $force; then - echo "-f used. Ignoring warnings." - else - echo "Archive did not pass sanity checks. Use -f to import anyway" >&2 - exit 1 - fi - fi - - # create all the app directories - while read app; do mkdir "$TARGET_DIR/$app"; echo "Imported $app"; done < $BACKUP_TMP_DIR/.dokku_backup_apps - - # have the plugins import their stuff - pluginhook backup-import $VERSION "$BACKUP_ROOT" $TARGET_DIR "$BACKUP_TMP_DIR/.dokku_backup_apps" - - rm -rf $BACKUP_TMP_DIR - - echo "Import complete." - ;; - - help | backup:help) - cat && cat< /dev/null +find . -maxdepth 1 -type d -not -name . | sed 's:./::g' > "$BACKUP_TMP_DIR/.dokku_backup_apps" +popd > /dev/null + +# we want to insert the files in the root of the tar +pushd $BACKUP_TMP_DIR > /dev/null +echo $CURRENT_BACKUP_VERSION > .dokku_backup_version +tar --append -f $BACKUP_TMP_FILE .dokku_backup_version +tar --append -f $BACKUP_TMP_FILE .dokku_backup_apps +popd > /dev/null + +# if no file specified, output to stdout +if [[ -z $OUTPUT_FILE ]]; then + cat $BACKUP_TMP_FILE +else + mv $BACKUP_TMP_FILE $OUTPUT_FILE +fi + +rm -rf $BACKUP_TMP_DIR diff --git a/plugins/backup/subcommands/import b/plugins/backup/subcommands/import new file mode 100755 index 00000000000..00c293bc95d --- /dev/null +++ b/plugins/backup/subcommands/import @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +CURRENT_BACKUP_VERSION=1 + +if [[ $2 == "-f" ]]; then + force=true + shift +else + force=false +fi + +INPUT_FILE="$2" +[[ -z $INPUT_FILE ]] && INPUT_FILE="-" + +BACKUP_TMP_DIR=$(mktemp -d) + +tar xf $INPUT_FILE --directory=$BACKUP_TMP_DIR + +if [[ ! -f $BACKUP_TMP_DIR/.dokku_backup_version ]]; then + echo "Unable to determine backup version" + exit 1 +fi + +VERSION=$(< $BACKUP_TMP_DIR/.dokku_backup_version) +if [[ $VERSION -ne 1 ]]; then + echo "Unknown format version $VERSION" + exit 1 +fi + +echo "Importing a version $VERSION backup..." + +BACKUP_ROOT="$BACKUP_TMP_DIR""$DOKKU_ROOT" +TARGET_DIR="$DOKKU_ROOT" + +if ! pluginhook backup-check $VERSION "$BACKUP_ROOT" "$TARGET_DIR" "$BACKUP_TMP_DIR/.dokku_backup_apps"; then + if $force; then + echo "-f used. Ignoring warnings." + else + echo "Archive did not pass sanity checks. Use -f to import anyway" >&2 + exit 1 + fi +fi + +# create all the app directories +while read app; do mkdir "$TARGET_DIR/$app"; echo "Imported $app"; done < $BACKUP_TMP_DIR/.dokku_backup_apps + +# have the plugins import their stuff +pluginhook backup-import $VERSION "$BACKUP_ROOT" $TARGET_DIR "$BACKUP_TMP_DIR/.dokku_backup_apps" + +rm -rf $BACKUP_TMP_DIR + +echo "Import complete." diff --git a/plugins/build-env/README.md b/plugins/build-env/README.md new file mode 100644 index 00000000000..ff28a6ef1af --- /dev/null +++ b/plugins/build-env/README.md @@ -0,0 +1,4 @@ +# Dokku core build-env plugin + +This plugin is responsible for generating an application's build environment. + diff --git a/plugins/build-env/pre-build b/plugins/build-env/pre-build index fa14ac60b82..6f557d8909a 100755 --- a/plugins/build-env/pre-build +++ b/plugins/build-env/pre-build @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$(dirname $0)/../common/functions" +source "$PLUGIN_PATH/common/functions" APP="$1"; IMAGE="dokku/$APP"; BUILD_ENV="" diff --git a/plugins/build/README.md b/plugins/build/README.md new file mode 100644 index 00000000000..dd1d7f31ff8 --- /dev/null +++ b/plugins/build/README.md @@ -0,0 +1,12 @@ +# Dokku core build command + +This plugin defines the `build` command used internally to build an app +(usually after receiving an app with the internally-used `receive` command). + +It runs any pre-build hooks, builds a container for the app using +[buildstep][], then runs any post-build hooks. Further release and deployment, +if any, is handled by the external context (again, usually the `receive` +command). + +[buildstep]: https://github.com/progrium/buildstep + diff --git a/plugins/build/command b/plugins/build/command new file mode 100755 index 00000000000..f9703a3babe --- /dev/null +++ b/plugins/build/command @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" + +APP="$2"; IMAGE="dokku/$APP"; IMAGE_SOURCE_TYPE="$3"; TMP_WORK_DIR="$4" +CACHE_DIR="$DOKKU_ROOT/$APP/cache" + +pushd "$TMP_WORK_DIR" &> /dev/null + +case "$IMAGE_SOURCE_TYPE" in + buildstep) + id=$(tar -c . | docker run -i -a stdin $DOKKU_IMAGE /bin/bash -c "mkdir -p /app && tar -xC /app") + test "$(docker wait $id)" -eq 0 + docker commit $id $IMAGE > /dev/null + [[ -d $CACHE_DIR ]] || mkdir $CACHE_DIR + # *DEPRECATED* in v0.4.0: `pluginhook pre-build` will be removed in future releases + pluginhook pre-build "$APP" + pluginhook pre-build-buildstep "$APP" + + # *DEPRECATED* in v0.3.14: `pluginhook docker-args` will be removed in future releases + # https://github.com/progrium/dokku/issues/896 & https://github.com/progrium/dokku/issues/906 + DOCKER_ARGS=$(: | pluginhook docker-args $APP build) + DOCKER_ARGS+=$(: | pluginhook docker-args-build $APP) + id=$(docker run -d -v $CACHE_DIR:/cache -e CACHE_PATH=/cache $DOCKER_ARGS $IMAGE /build/builder) + docker attach $id + test "$(docker wait $id)" -eq 0 + docker commit $id $IMAGE > /dev/null + + # *DEPRECATED* in v0.4.0: `pluginhook post-build` will be removed in future releases + pluginhook post-build "$APP" + pluginhook post-build-buildstep "$APP" + ;; + + dockerfile) + # extract first port from dockerfile + DOCKERFILE_PORT=$(grep EXPOSE Dockerfile | head -1 | awk '{ print $2 }') + [[ -n "$DOCKERFILE_PORT" ]] && dokku config:set-norestart $APP DOKKU_DOCKERFILE_PORT=$DOCKERFILE_PORT + + # sticking with same pattern of building app image before pre-build for now. + docker build -t "$IMAGE" . + # buildstep pluginhooks don't necessarily make sense for dockerfiles. call the new breed!!! + pluginhook pre-build-dockerfile "$APP" + pluginhook post-build-dockerfile "$APP" + ;; + + *) + dokku_log_fail "Building image source type $IMAGE_SOURCE_TYPE not supported!" + ;; +esac diff --git a/plugins/checks/README.md b/plugins/checks/README.md new file mode 100644 index 00000000000..6b027d990ca --- /dev/null +++ b/plugins/checks/README.md @@ -0,0 +1,35 @@ +# Dokku core checks plugin + +This plugin handles zero-downtime functionality by checking the application +container against a list of checks specified in CHECKS file. + +The CHECKS file may contain empty lines, comments (lines starting with #), +settings (NAME=VALUE) and check instructions. + +The format of a check instruction is a path, optionally followed by the +expected content. For example: + ```shell + / My Amazing App + /stylesheets/index.css .body + /scripts/index.js $(function() + /images/logo.png + ``` + +To check an application that supports multiple hostnames, use relative URLs +that include the hostname, for example: + ```shell + //admin.example.com Admin Dashboard + //static.example.com/logo.png + ``` + +You can also specify the protocol to explicitly check HTTPS requests. + +The default behavior is to wait for 5 seconds before running the first check, +and timeout each check to 30 minutes. + +You can change these by setting DOKKU_CHECKS_WAIT and DOKKU_CHECKS_TIMEOUT to different values, for +example: + ```shell + DOKKU_CHECKS_WAIT=30 # Wait 1/2 minute + DOKKU_CHECKS_TIMEOUT=60 # Timeout after a minute + ``` diff --git a/plugins/checks/check-deploy b/plugins/checks/check-deploy index 861d90de93c..65b125a476d 100755 --- a/plugins/checks/check-deploy +++ b/plugins/checks/check-deploy @@ -1,38 +1,6 @@ #!/usr/bin/env bash - -# Hook to check server against list of checks specified in CHECKS file. -# -# The CHECKS file may contain empty lines, comments (lines starting with #), -# settings (NAME=VALUE) and check instructions. -# -# The format of a check instruction is a path, optionally followed by the -# expected content. For example: -# -# / My Amazing App -# /stylesheets/index.css .body -# /scripts/index.js $(function() -# /images/logo.png -# -# To check an application that supports multiple hostnames, use relative URLs -# that include the hostname, for example: -# -# //admin.example.com Admin Dashboard -# //static.example.com/logo.png -# -# You can also specify the protocol to explicitly check HTTPS requests. -# -# The default behavior is to wait for 5 seconds before running the first check, -# and timeout each check to 30 minutes. -# -# You can change these by setting WAIT and TIMEOUT to different values, for -# example: -# -# WAIT=30 # Wait 1/2 minute -# TIMEOUT=60 # Timeout after a minute -# - set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$(dirname $0)/../common/functions" +source "$PLUGIN_PATH/common/functions" APP="$1"; DOKKU_APP_LISTEN_PORT="$2"; DOKKU_APP_LISTEN_IP="$3"; DOKKU_APP_CONTAINER_ID="$4" if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -f "$DOKKU_ROOT/$APP/PORT" ]]; then diff --git a/plugins/common/README.md b/plugins/common/README.md new file mode 100644 index 00000000000..ebc1ef3d306 --- /dev/null +++ b/plugins/common/README.md @@ -0,0 +1,4 @@ +# Dokku core common functions + +This plugin provides the common functions for dokku and all plugins. + diff --git a/plugins/config/README.md b/plugins/config/README.md new file mode 100644 index 00000000000..43a06ac0192 --- /dev/null +++ b/plugins/config/README.md @@ -0,0 +1,6 @@ +# Dokku core config commands + +This plugin provides the `config` commands for easily dealing with an app's +environment definition, as well as hooks for backing up apps' configured +environments. + diff --git a/plugins/config/command b/plugins/config/command new file mode 100755 index 00000000000..912de81f979 --- /dev/null +++ b/plugins/config/command @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +APP="$2" + +config_create +[[ ! -s $ENV_FILE ]] && echo "$APP has no config vars" && exit 1 + +VARS=$(grep -Eo "export ([a-zA-Z_][a-zA-Z0-9_]*=.*)" $ENV_FILE | cut -d" " -f2-) + +for var in "$@"; do + if [[ "$var" == "--shell" ]]; then + echo $VARS + exit 0 + fi +done + +dokku_log_info2_quiet "$APP config vars" +config_styled_hash "$VARS" diff --git a/plugins/config/commands b/plugins/config/commands deleted file mode 100755 index 348ca4bba0e..00000000000 --- a/plugins/config/commands +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$(dirname $0)/../common/functions" - -ENV_FILE="$DOKKU_ROOT/$2/ENV" -ENV_FILE_TEMP="$DOKKU_ROOT/$2/ENV.tmp" -config_create () { - [ -f $ENV_FILE ] || { - touch $ENV_FILE - } -} - -config_styled_hash () { - vars="$1" - prefix="$2" - - longest="" - while read -r word; do - KEY=$(echo $word | cut -d"=" -f1) - if [ ${#KEY} -gt ${#longest} ]; then - longest=$KEY - fi - done <<< "$vars" - - while read -r word; do - KEY=$(echo $word | cut -d"=" -f1) - VALUE=$(echo $word | cut -d"=" -f2- | sed -e "s/^'//" -e "s/'$//") - - num_zeros=$((${#longest} - ${#KEY})) - zeros=" " - while [ $num_zeros -gt 0 ]; do - zeros="$zeros " - num_zeros=$((num_zeros - 1)) - done - echo "$prefix$KEY:$zeros$VALUE" - done <<< "$vars" -} - -config_write() { - ENV_TEMP="$1" - echo -e "$ENV_TEMP" | sed '/^$/d' | sort > $ENV_FILE_TEMP - if ! cmp -s $ENV_FILE $ENV_FILE_TEMP; then - cp -f $ENV_FILE_TEMP $ENV_FILE - chmod 600 $ENV_FILE - fi - rm -f $ENV_FILE_TEMP -} - -case "$1" in - config) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2" - - config_create - [[ ! -s $ENV_FILE ]] && echo "$APP has no config vars" && exit 1 - - VARS=$(grep -Eo "export ([a-zA-Z_][a-zA-Z0-9_]*=.*)" $ENV_FILE | cut -d" " -f2-) - - for var in "$@"; do - if [[ "$var" == "--shell" ]]; then - echo $VARS - exit 0 - fi - done - - dokku_log_info2_quiet "$APP config vars" - config_styled_hash "$VARS" - ;; - - config:get) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2" - - if [[ -z $3 ]]; then - echo "Usage: dokku config:get APP KEY" - echo "Must specify KEY." - exit 1 - fi - - config_create - if [[ ! -s $ENV_FILE ]] ; then - exit 0 - fi - - KEY="$3" - - grep -Eo "export ([a-zA-Z_][a-zA-Z0-9_]*=.*)" $ENV_FILE | grep "^export $KEY=" | cut -d"=" -f2- | sed -e "s/^'//" -e "s/'$//" - ;; - - config:set) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2" - - if [[ -z "${*:3}" ]]; then - echo "Usage: dokku config:set APP KEY1=VALUE1 [KEY2=VALUE2 ...]" - echo "Must specify KEY and VALUE to set." - exit 1 - fi - - shift 2 - dokku config:set-norestart $APP "$@" - dokku_log_info1 "Restarting app $APP" - dokku ps:restart $APP - ;; - - config:set-norestart) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2" - - if [[ -z "${*:3}" ]]; then - echo "Usage: dokku config:set APP KEY1=VALUE1 [KEY2=VALUE2 ...]" - echo "Must specify KEY and VALUE to set." - exit 1 - fi - - config_create - ENV_ADD="" - ENV_TEMP=$(cat "${ENV_FILE}") - RESTART_APP=false - shift 2 - - for var; do - if [[ $var != *"="* ]]; then - echo "Usage: dokku config:set APP KEY1=VALUE1 [KEY2=VALUE2 ...]" - echo "Must specify KEY and VALUE to set." - exit 1 - fi - done - - for var; do - KEY=$(echo ${var} | cut -d"=" -f1) - VALUE=$(echo ${var} | cut -d"=" -f2-) - - if [[ $KEY =~ [a-zA-Z_][a-zA-Z0-9_]* ]]; then - RESTART_APP=true - ENV_TEMP=$(echo -e "${ENV_TEMP}" | sed "/^export $KEY=/ d") - ENV_TEMP="${ENV_TEMP}\nexport $KEY='$VALUE'" - ENV_ADD=$(echo -e "${ENV_ADD}" | sed "/^$KEY=/ d") - ENV_ADD="${ENV_ADD}$ -${var}" - fi - done - ENV_ADD=$(echo "$ENV_ADD" | tail -n +2) #remove first empty line - - if [ $RESTART_APP ]; then - dokku_log_info1 "Setting config vars" - config_styled_hash "$ENV_ADD" " " - - config_write "$ENV_TEMP" - fi - ;; - - config:unset) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2" - - if [[ -z $3 ]]; then - echo "Usage: dokku config:unset APP KEY1 [KEY2 ...]" - echo "Must specify KEY to unset." - exit 1 - fi - - shift 2 - dokku config:unset-norestart $APP "$@" - dokku_log_info1 "Restarting app $APP" - dokku ps:restart $APP - ;; - - config:unset-norestart) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2" - - if [[ -z $3 ]]; then - echo "Usage: dokku config:unset APP KEY1 [KEY2 ...]" - echo "Must specify KEY to unset." - exit 1 - fi - - config_create - ENV_TEMP=$(cat "${ENV_FILE}") - VARS="${*:3}" - - for var in $VARS; do - dokku_log_info1 "Unsetting $var" - ENV_TEMP=$(echo -e "${ENV_TEMP}" | sed "/^export $var=/ d") - - config_write "$ENV_TEMP" - done - ;; - - help | config:help) - cat && cat< Display the config vars for an app - config:get KEY Display a config value for an app - config:set KEY1=VALUE1 [KEY2=VALUE2 ...] Set one or more config vars - config:unset KEY1 [KEY2 ...] Unset one or more config vars -EOF - ;; - - *) - exit $DOKKU_NOT_IMPLEMENTED_EXIT - ;; - -esac diff --git a/plugins/config/docker-args-deploy b/plugins/config/docker-args-deploy index 53bda23835c..c33a9930c15 100755 --- a/plugins/config/docker-args-deploy +++ b/plugins/config/docker-args-deploy @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$(dirname $0)/../common/functions" +source "$PLUGIN_PATH/common/functions" STDIN=$(cat); APP="$1"; IMAGE="dokku/$APP" DOCKERFILE_ENV_FILE="$DOKKU_ROOT/$APP/DOCKERFILE_ENV_FILE" diff --git a/plugins/config/functions b/plugins/config/functions new file mode 100755 index 00000000000..c9eeeacae4d --- /dev/null +++ b/plugins/config/functions @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" + +ENV_FILE="$DOKKU_ROOT/$2/ENV" +ENV_FILE_TEMP="$DOKKU_ROOT/$2/ENV.tmp" + +config_create () { + [ -f $ENV_FILE ] || { + touch $ENV_FILE + } +} + +config_styled_hash () { + vars="$1" + prefix="$2" + + longest="" + while read -r word; do + KEY=$(echo $word | cut -d"=" -f1) + if [ ${#KEY} -gt ${#longest} ]; then + longest=$KEY + fi + done <<< "$vars" + + while read -r word; do + KEY=$(echo $word | cut -d"=" -f1) + VALUE=$(echo $word | cut -d"=" -f2- | sed -e "s/^'//" -e "s/'$//") + + num_zeros=$((${#longest} - ${#KEY})) + zeros=" " + while [ $num_zeros -gt 0 ]; do + zeros="$zeros " + num_zeros=$((num_zeros - 1)) + done + echo "$prefix$KEY:$zeros$VALUE" + done <<< "$vars" +} + +config_write() { + ENV_TEMP="$1" + echo -e "$ENV_TEMP" | sed '/^$/d' | sort > $ENV_FILE_TEMP + if ! cmp -s $ENV_FILE $ENV_FILE_TEMP; then + cp -f $ENV_FILE_TEMP $ENV_FILE + chmod 600 $ENV_FILE + dokku ps:restart $APP + fi + rm -f $ENV_FILE_TEMP +} diff --git a/plugins/config/help.txt b/plugins/config/help.txt new file mode 100644 index 00000000000..d66c5b0c09f --- /dev/null +++ b/plugins/config/help.txt @@ -0,0 +1,4 @@ +: Display the config vars for an app +config:get KEY Display a config value for an app +config:set KEY1=VALUE1 [KEY2=VALUE2 ...] Set one or more config vars +config:unset KEY1 [KEY2 ...] Unset one or more config vars diff --git a/plugins/config/subcommands/get b/plugins/config/subcommands/get new file mode 100755 index 00000000000..cfa60e4430b --- /dev/null +++ b/plugins/config/subcommands/get @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +ENV_FILE="$DOKKU_ROOT/$2/ENV" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +APP="$2" + +if [[ -z $3 ]]; then + echo "Usage: dokku config:get APP KEY" + echo "Must specify KEY." + exit 1 +fi + +config_create +if [[ ! -s $ENV_FILE ]] ; then + exit 0 +fi + +KEY="$3" + +grep -Eo "export ([a-zA-Z_][a-zA-Z0-9_]*=.*)" $ENV_FILE | grep "^export $KEY=" | cut -d"=" -f2- | sed -e "s/^'//" -e "s/'$//" diff --git a/plugins/config/subcommands/set b/plugins/config/subcommands/set new file mode 100755 index 00000000000..724be3659fb --- /dev/null +++ b/plugins/config/subcommands/set @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +ENV_FILE="$DOKKU_ROOT/$2/ENV" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +APP="$2" + +if [[ -z "${*:3}" ]]; then + echo "Usage: dokku config:set APP KEY1=VALUE1 [KEY2=VALUE2 ...]" + echo "Must specify KEY and VALUE to set." + exit 1 +fi + +shift 2 +dokku config:set-norestart $APP "$@" +dokku_log_info1 "Restarting app $APP" +dokku ps:restart $APP diff --git a/plugins/config/subcommands/set-norestart b/plugins/config/subcommands/set-norestart new file mode 100755 index 00000000000..59422f7e18a --- /dev/null +++ b/plugins/config/subcommands/set-norestart @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +ENV_FILE="$DOKKU_ROOT/$2/ENV" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +APP="$2" + +if [[ -z "${*:3}" ]]; then + echo "Usage: dokku config:set APP KEY1=VALUE1 [KEY2=VALUE2 ...]" + echo "Must specify KEY and VALUE to set." + exit 1 +fi + +config_create +ENV_ADD="" +ENV_TEMP=$(cat "${ENV_FILE}") +RESTART_APP=false +shift 2 + +for var; do + if [[ $var != *"="* ]]; then + echo "Usage: dokku config:set APP KEY1=VALUE1 [KEY2=VALUE2 ...]" + echo "Must specify KEY and VALUE to set." + exit 1 + fi +done + +for var; do + KEY=$(echo ${var} | cut -d"=" -f1) + VALUE=$(echo ${var} | cut -d"=" -f2-) + + if [[ $KEY =~ [a-zA-Z_][a-zA-Z0-9_]* ]]; then + RESTART_APP=true + ENV_TEMP=$(echo -e "${ENV_TEMP}" | sed "/^export $KEY=/ d") + ENV_TEMP="${ENV_TEMP}\nexport $KEY='$VALUE'" + ENV_ADD=$(echo -e "${ENV_ADD}" | sed "/^$KEY=/ d") + ENV_ADD="${ENV_ADD}$ +${var}" + fi +done +ENV_ADD=$(echo "$ENV_ADD" | tail -n +2) #remove first empty line + +if [ $RESTART_APP ]; then + dokku_log_info1 "Setting config vars" + config_styled_hash "$ENV_ADD" " " + + config_write "$ENV_TEMP" +fi diff --git a/plugins/config/subcommands/unset b/plugins/config/subcommands/unset new file mode 100755 index 00000000000..313e5b4dc56 --- /dev/null +++ b/plugins/config/subcommands/unset @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +ENV_FILE="$DOKKU_ROOT/$2/ENV" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +APP="$2" + +if [[ -z $3 ]]; then + echo "Usage: dokku config:unset APP KEY1 [KEY2 ...]" + echo "Must specify KEY to unset." + exit 1 +fi + +shift 2 +dokku config:unset-norestart $APP "$@" +dokku_log_info1 "Restarting app $APP" +dokku ps:restart $APP diff --git a/plugins/config/subcommands/unset-norestart b/plugins/config/subcommands/unset-norestart new file mode 100755 index 00000000000..be630ab8332 --- /dev/null +++ b/plugins/config/subcommands/unset-norestart @@ -0,0 +1,26 @@ +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +ENV_FILE="$DOKKU_ROOT/$2/ENV" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +APP="$2" + +if [[ -z $3 ]]; then + echo "Usage: dokku config:unset APP KEY1 [KEY2 ...]" + echo "Must specify KEY to unset." + exit 1 +fi + +config_create +ENV_TEMP=$(cat "${ENV_FILE}") +VARS="${*:3}" + +for var in $VARS; do + dokku_log_info1 "Unsetting $var" + ENV_TEMP=$(echo -e "${ENV_TEMP}" | sed "/^export $var=/ d") + + config_write "$ENV_TEMP" +done diff --git a/plugins/core/README.md b/plugins/core/README.md new file mode 100644 index 00000000000..7e9c6343491 --- /dev/null +++ b/plugins/core/README.md @@ -0,0 +1,10 @@ +# Dokku core + +This plugin provides the base Dokku installation steps. + +## Dokku core installation + +- Sets the dokku HOSTNAME file from the server's hostname, if missing. +- Creates an Upstart rule to restart all apps after a reboot (redeploying them + to refresh their port mappings for virtual hosting). + diff --git a/plugins/00_dokku-standard/backup-check b/plugins/core/backup-check similarity index 100% rename from plugins/00_dokku-standard/backup-check rename to plugins/core/backup-check diff --git a/plugins/00_dokku-standard/backup-export b/plugins/core/backup-export similarity index 100% rename from plugins/00_dokku-standard/backup-export rename to plugins/core/backup-export diff --git a/plugins/00_dokku-standard/backup-import b/plugins/core/backup-import similarity index 100% rename from plugins/00_dokku-standard/backup-import rename to plugins/core/backup-import diff --git a/plugins/core/commands b/plugins/core/commands new file mode 100755 index 00000000000..3ff09eeab11 --- /dev/null +++ b/plugins/core/commands @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" + +case "$1" in + delete) + dokku apps:destroy $2 + ;; + + ls) + apps=$(ls -d $DOKKU_ROOT/*/ 2>/dev/null) || (echo "You haven't depoyed any applications yet" && exit 1) + + dokku_col_log_info2_quiet "App Name" "Container id" + + for app in $apps; do + if [[ -f $app/CONTAINER ]]; then + dokku_col_log_msg "$(basename $app)" "$(< $app/CONTAINER)" + else + dokku_col_log_msg "$(basename $app)" + fi + done + ;; + + logs) + [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 + verify_app_name "$2" + APP="$2"; + + if [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]]; then + CONTAINER=$(<$DOKKU_ROOT/$APP/CONTAINER) + if [[ $3 == "-t" ]]; then + docker logs --follow $CONTAINER + else + docker logs $CONTAINER | tail -n 100 + fi + else + echo "Application's container not found" + fi + ;; + + url | urls) + [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 + verify_app_name "$2" + APP="$2"; + + if [[ -s "$DOKKU_ROOT/$APP/URLS" ]]; then + case "$1" in + url) + grep "^http" "$DOKKU_ROOT/$APP/URLS" | head -1 + ;; + urls) + grep "^http" "$DOKKU_ROOT/$APP/URLS" + ;; + esac + + exit 0 + fi + + SCHEME="http"; SSL="$DOKKU_ROOT/$APP/tls"; WILDCARD_SSL="$DOKKU_ROOT/tls" + if [[ -e "$SSL/server.crt" && -e "$SSL/server.key" ]] || [[ -e "$WILDCARD_SSL/server.crt" && -e "$WILDCARD_SSL/server.key" ]]; then + SCHEME="https" + fi + + if [[ -f "$DOKKU_ROOT/VHOST" ]]; then + echo "$SCHEME://$(< "$DOKKU_ROOT/VHOST")" + else + echo "$SCHEME://$(< "$DOKKU_ROOT/HOSTNAME"):$(< "$DOKKU_ROOT/$APP/PORT")" + fi + ;; + + help) + ;; + + *) + exit $DOKKU_NOT_IMPLEMENTED_EXIT + ;; + +esac diff --git a/plugins/core/help.txt b/plugins/core/help.txt new file mode 100644 index 00000000000..dd793a07bce --- /dev/null +++ b/plugins/core/help.txt @@ -0,0 +1,4 @@ +ls Pretty listing of deployed applications and containers +logs [-t] Show the last logs for an application (-t follows) +url Show the first URL for an application (compatibility) +urls Show all URLs for an application diff --git a/plugins/00_dokku-standard/install b/plugins/core/install similarity index 100% rename from plugins/00_dokku-standard/install rename to plugins/core/install diff --git a/plugins/domains/README.md b/plugins/domains/README.md new file mode 100644 index 00000000000..964ddfd02ce --- /dev/null +++ b/plugins/domains/README.md @@ -0,0 +1,4 @@ +# Dokku core domains plugin + +This plugin handles management of custom domains. + diff --git a/plugins/domains/command b/plugins/domains/command new file mode 100755 index 00000000000..8e5a5a68424 --- /dev/null +++ b/plugins/domains/command @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +APP="$2" + +dokku domains:setup $APP +dokku_log_info2_quiet "$APP Domain Names" +cat "$DOKKU_ROOT/$APP/VHOST" diff --git a/plugins/domains/commands b/plugins/domains/commands deleted file mode 100755 index c479a63f3a0..00000000000 --- a/plugins/domains/commands +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$(dirname $0)/../common/functions" - -RE_IPV4="([0-9]{1,3}[\.]){3}[0-9]{1,3}" - -RE_IPV6="([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" # TEST: 1:2:3:4:5:6:7:8 -RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,7}:|" # TEST: 1:: 1:2:3:4:5:6:7:: -RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" # TEST: 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8 -RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" # TEST: 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8 -RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" # TEST: 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8 -RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" # TEST: 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8 -RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" # TEST: 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8 -RE_IPV6="${RE_IPV6}[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" # TEST: 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8 -RE_IPV6="${RE_IPV6}:((:[0-9a-fA-F]{1,4}){1,7}|:)|" # TEST: ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 :: -RE_IPV6="${RE_IPV6}fe08:(:[0-9a-fA-F]{1,4}){2,2}%[0-9a-zA-Z]{1,}|" # TEST: fe08::7:8%eth0 fe08::7:8%1 (link-local IPv6 addresses with zone index) -RE_IPV6="${RE_IPV6}::(ffff(:0{1,4}){0,1}:){0,1}${RE_IPV4}|" # TEST: ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses) -RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}:${RE_IPV4}" # TEST: 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 - -# Ensure the ip address continues to the end of the line -# Fixes using a wildcard dns service such as xip.io which allows for *..xip.io -RE_IPV4="${RE_IPV4}\$" -RE_IPV6="${RE_IPV6}\$" - -case "$1" in - domains) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2" - - dokku domains:setup $APP - dokku_log_info2_quiet "$APP Domain Names" - cat "$DOKKU_ROOT/$APP/VHOST" - ;; - - domains:setup) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2"; VHOST_PATH="$DOKKU_ROOT/$APP/VHOST" - - if [[ ! -f $VHOST_PATH ]]; then - if [[ -f "$DOKKU_ROOT/VHOST" ]];then - VHOST=$(< "$DOKKU_ROOT/VHOST") - else - VHOST=$(< "$DOKKU_ROOT/HOSTNAME") - fi - if [[ "$VHOST" =~ $RE_IPV4 ]] || [[ "$VHOST" =~ $RE_IPV6 ]];then - echo "unsupported vhost config found. disabling vhost support" - [[ ! $(grep -q NO_VHOST "$DOKKU_ROOT/$APP/ENV") ]] && echo "export NO_VHOST='1'" >> "$DOKKU_ROOT/$APP/ENV" - else - if [[ -f "$DOKKU_ROOT/VHOST" ]]; then - dokku_log_info1 "Creating new $VHOST_PATH..." - SUBDOMAIN=${APP/%\.${VHOST}/} - hostname=$(: | pluginhook nginx-hostname $APP $SUBDOMAIN $VHOST) - if [[ ! -n $hostname ]]; then - if [[ "$APP" == *.* ]] && [[ "$SUBDOMAIN" == "$APP" ]]; then - hostname="${APP/\//-}" - else - hostname="${APP/\//-}.$VHOST" - fi - fi - - echo "$hostname" > $VHOST_PATH - fi - fi - fi - ;; - - domains:add) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2" - - if [[ -z $3 ]]; then - echo "Usage: dokku $1 $APP DOMAIN" - echo "Must specify DOMAIN." - exit 1 - fi - - if [[ $(egrep ^"$3"$ "$DOKKU_ROOT/$APP/VHOST" > /dev/null 2>&1; echo $?) -eq 0 ]]; then - echo "$3 is already defined for $APP" - exit 1 - fi - - dokku domains:setup $APP - echo "$3" >> "$DOKKU_ROOT/$APP/VHOST" - # we need to restart the app to make sure we're binding to the appropriate network interface - dokku ps:restart $APP - pluginhook post-domains-update $APP - dokku_log_info1 "Added $3 to $APP" - - ;; - - domains:clear) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2" - - rm -f "$DOKKU_ROOT/$APP/VHOST" - dokku domains:setup $APP - pluginhook post-domains-update $APP - dokku_log_info1 "Cleared domains in $APP" - - ;; - - domains:remove) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2" - - if [[ -z $3 ]]; then - echo "Usage: dokku $1 $2 DOMAIN" - echo "Must specify DOMAIN." - exit 1 - fi - - dokku domains:setup $APP - sed -i "/^$3$/d" "$DOKKU_ROOT/$APP/VHOST" - pluginhook post-domains-update $APP - dokku_log_info1 "Removed $3 from $APP" - - ;; - - help | domains:help) - cat && cat< List custom domains for app - domains:add DOMAIN Add a custom domain to app - domains:clear Clear all custom domains for app - domains:remove DOMAIN Remove a custom domain from app -EOF - ;; - - *) - exit $DOKKU_NOT_IMPLEMENTED_EXIT - ;; - -esac diff --git a/plugins/domains/functions b/plugins/domains/functions new file mode 100755 index 00000000000..d166bd92ba2 --- /dev/null +++ b/plugins/domains/functions @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" + +RE_IPV4="([0-9]{1,3}[\.]){3}[0-9]{1,3}" + +RE_IPV6="([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" # TEST: 1:2:3:4:5:6:7:8 +RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,7}:|" # TEST: 1:: 1:2:3:4:5:6:7:: +RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" # TEST: 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8 +RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" # TEST: 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8 +RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" # TEST: 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8 +RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" # TEST: 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8 +RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" # TEST: 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8 +RE_IPV6="${RE_IPV6}[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" # TEST: 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8 +RE_IPV6="${RE_IPV6}:((:[0-9a-fA-F]{1,4}){1,7}|:)|" # TEST: ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 :: +RE_IPV6="${RE_IPV6}fe08:(:[0-9a-fA-F]{1,4}){2,2}%[0-9a-zA-Z]{1,}|" # TEST: fe08::7:8%eth0 fe08::7:8%1 (link-local IPv6 addresses with zone index) +RE_IPV6="${RE_IPV6}::(ffff(:0{1,4}){0,1}:){0,1}${RE_IPV4}|" # TEST: ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses) +RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}:${RE_IPV4}" # TEST: 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 + +# Ensure the ip address continues to the end of the line +# Fixes using a wildcard dns service such as xip.io which allows for *..xip.io +RE_IPV4="${RE_IPV4}\$" +RE_IPV6="${RE_IPV6}\$" diff --git a/plugins/domains/help.txt b/plugins/domains/help.txt new file mode 100644 index 00000000000..165c112f99f --- /dev/null +++ b/plugins/domains/help.txt @@ -0,0 +1,4 @@ +: List custom domains for app +domains:add DOMAIN Add a custom domain to app +domains:clear Clear all custom domains for app +domains:remove DOMAIN Remove a custom domain from app diff --git a/plugins/domains/subcommands/add b/plugins/domains/subcommands/add new file mode 100755 index 00000000000..6847c6737b5 --- /dev/null +++ b/plugins/domains/subcommands/add @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +APP="$2" + +if [[ -z $3 ]]; then + echo "Usage: dokku $1 $APP DOMAIN" + echo "Must specify DOMAIN." + exit 1 +fi + +if [[ $(egrep ^"$3"$ "$DOKKU_ROOT/$APP/VHOST" > /dev/null 2>&1; echo $?) -eq 0 ]]; then + echo "$3 is already defined for $APP" + exit 1 +fi + +dokku domains:setup $APP +echo "$3" >> "$DOKKU_ROOT/$APP/VHOST" +# we need to restart the app to make sure we're binding to the appropriate network interface +dokku ps:restart $APP +pluginhook post-domains-update $APP +dokku_log_info1 "Added $3 to $APP" diff --git a/plugins/domains/subcommands/clear b/plugins/domains/subcommands/clear new file mode 100755 index 00000000000..9e41112dedf --- /dev/null +++ b/plugins/domains/subcommands/clear @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +APP="$2" + +rm -f "$DOKKU_ROOT/$APP/VHOST" +dokku domains:setup $APP +pluginhook post-domains-update $APP +dokku_log_info1 "Cleared domains in $APP" diff --git a/plugins/domains/subcommands/remove b/plugins/domains/subcommands/remove new file mode 100755 index 00000000000..b91ce56537f --- /dev/null +++ b/plugins/domains/subcommands/remove @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +APP="$2" + +if [[ -z $3 ]]; then + echo "Usage: dokku $1 $2 DOMAIN" + echo "Must specify DOMAIN." + exit 1 +fi + +dokku domains:setup $APP +sed -i "/^$3$/d" "$DOKKU_ROOT/$APP/VHOST" +pluginhook post-domains-update $APP +dokku_log_info1 "Removed $3 from $APP" diff --git a/plugins/domains/subcommands/setup b/plugins/domains/subcommands/setup new file mode 100755 index 00000000000..dcb1a8ecbdb --- /dev/null +++ b/plugins/domains/subcommands/setup @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +APP="$2"; VHOST_PATH="$DOKKU_ROOT/$APP/VHOST" + +if [[ ! -f $VHOST_PATH ]]; then + if [[ -f "$DOKKU_ROOT/VHOST" ]];then + VHOST=$(< "$DOKKU_ROOT/VHOST") + else + VHOST=$(< "$DOKKU_ROOT/HOSTNAME") + fi + if [[ "$VHOST" =~ $RE_IPV4 ]] || [[ "$VHOST" =~ $RE_IPV6 ]];then + echo "unsupported vhost config found. disabling vhost support" + [[ ! $(grep -q NO_VHOST "$DOKKU_ROOT/$APP/ENV") ]] && echo "export NO_VHOST='1'" >> "$DOKKU_ROOT/$APP/ENV" + else + if [[ -f "$DOKKU_ROOT/VHOST" ]]; then + dokku_log_info1 "Creating new $VHOST_PATH..." + SUBDOMAIN=${APP/%\.${VHOST}/} + hostname=$(: | pluginhook nginx-hostname $APP $SUBDOMAIN $VHOST) + if [[ ! -n $hostname ]]; then + if [[ "$APP" == *.* ]] && [[ "$SUBDOMAIN" == "$APP" ]]; then + hostname="${APP/\//-}" + else + hostname="${APP/\//-}.$VHOST" + fi + fi + + echo "$hostname" > $VHOST_PATH + fi + fi +fi diff --git a/plugins/git/README.md b/plugins/git/README.md new file mode 100644 index 00000000000..b5a40ad0631 --- /dev/null +++ b/plugins/git/README.md @@ -0,0 +1,7 @@ +# Dokku core git handling + +This plugin provides the `git` interface for creating and deploying apps in +Dokku by adding commands to Dokku to wrap Git's SSH interface, as well as +defining a hook to create the relevant git pre-receive hook for any apps being +imported from backup. + diff --git a/plugins/git/commands b/plugins/git/commands index 47934f493b3..eff68d0f1ea 100755 --- a/plugins/git/commands +++ b/plugins/git/commands @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$(dirname $0)/../common/functions" +source "$PLUGIN_PATH/common/functions" git_build_app_repo() { verify_app_name "$1" @@ -91,8 +91,8 @@ EOF git-shell -c "$args" ;; - help | git:help) - cat + help) + : ;; *) diff --git a/plugins/git/receive-app b/plugins/git/receive-app index 847df876c63..c8e2c1866e6 100755 --- a/plugins/git/receive-app +++ b/plugins/git/receive-app @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$(dirname $0)/../common/functions" +source "$PLUGIN_PATH/common/functions" APP="$1"; REV="$2" diff --git a/plugins/help/README.md b/plugins/help/README.md new file mode 100644 index 00000000000..e80cc4c58ba --- /dev/null +++ b/plugins/help/README.md @@ -0,0 +1,33 @@ +# Dokku core help command + +This plugin provides the `help` command, which outputs the usage of every +command provided by a Dokku plugin, as specified by the plugin's `help.txt` +and the output of the `help` command as by other plugins. + +## help.txt format + +Each line is interpreted to describe the usage, and human explanation, for a +command. Usage and explanation are separated by the first instance of 2 or more +spaces. + +Lines starting with ":" will have the ":" replaced with the name of the plugin, +followed by a colon if there is a subcommand name immediately following the +colon. + +For example, this `help.txt` for the `config` plugin: + +``` +: display the config vars for an app +:get KEY display a config value for an app +:set KEY1=VALUE1 [KEY2=VALUE2 ...] set one or more config vars +:unset KEY1 [KEY2 ...] unset one or more config vars +``` + +would be formatted as this when output by the `help` command: +``` + config display the config vars for an app + config:get KEY display a config value for an app + config:set KEY1=VALUE1 [KEY2=VALUE2 ...] set one or more config vars + config:unset KEY1 [KEY2 ...] unset one or more config vars +``` + diff --git a/plugins/help/command b/plugins/help/command new file mode 100755 index 00000000000..39057f7e220 --- /dev/null +++ b/plugins/help/command @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +shopt -s nullglob +for helpfile in $PLUGIN_PATH/*/help.txt; do + PLUGIN=$(basename ${helpfile%/help.txt}) + sed -e "s/^\\s*: /$PLUGIN /" -e "s/^\\s*:/$PLUGIN:/" -e "s/^/ /" $helpfile +done + +# Print any legacy plugin help output +DOKKU_NO_COMMAND=1 pluginhook commands help | sort diff --git a/plugins/help/help.txt b/plugins/help/help.txt new file mode 100644 index 00000000000..17ceb7f319c --- /dev/null +++ b/plugins/help/help.txt @@ -0,0 +1 @@ +: Print the list of commands diff --git a/plugins/nginx-vhosts/README.md b/plugins/nginx-vhosts/README.md new file mode 100644 index 00000000000..8545c81bd52 --- /dev/null +++ b/plugins/nginx-vhosts/README.md @@ -0,0 +1,4 @@ +# Dokku core nginx virtual hosting + +This plugin handles virtual hosting for Dokku apps using a running nginx instance. + diff --git a/plugins/nginx-vhosts/commands b/plugins/nginx-vhosts/commands deleted file mode 100755 index ab8d6e24527..00000000000 --- a/plugins/nginx-vhosts/commands +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$(dirname $0)/../common/functions" - -restart_nginx () { - case "$DOKKU_DISTRO" in - ubuntu) - sudo /etc/init.d/nginx reload > /dev/null - ;; - - opensuse) - sudo /sbin/service nginx reload > /dev/null - ;; - esac -} - -case "$1" in - nginx:build-config) - APP="$2"; DOKKU_APP_LISTEN_PORT="$3"; DOKKU_APP_LISTEN_IP="${4}" - VHOST_PATH="$DOKKU_ROOT/$APP/VHOST" - URLS_PATH="$DOKKU_ROOT/$APP/URLS" - WILDCARD_SSL="$DOKKU_ROOT/tls" - SSL="$DOKKU_ROOT/$APP/tls" - - if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -f "$DOKKU_ROOT/$APP/PORT" ]]; then - DOKKU_APP_LISTEN_PORT=$(< "$DOKKU_ROOT/$APP/PORT") - fi - if [[ -z "$DOKKU_APP_LISTEN_IP" ]] && [[ -f "$DOKKU_ROOT/$APP/IP" ]]; then - DOKKU_APP_LISTEN_IP=$(< "$DOKKU_ROOT/$APP/IP") - fi - - [[ -f "$DOKKU_ROOT/ENV" ]] && source $DOKKU_ROOT/ENV - [[ -f "$DOKKU_ROOT/$APP/ENV" ]] && source $DOKKU_ROOT/$APP/ENV - - if [[ ! -n "$NO_VHOST" ]] && [[ -f "$DOKKU_ROOT/$APP/VHOST" ]]; then - NONSSL_VHOSTS=$(cat $VHOST_PATH) - if [[ -e "$SSL/server.crt" ]] && [[ -e "$SSL/server.key" ]]; then - SSL_INUSE="$SSL" - SSL_DIRECTIVES=$(cat <> $VHOST_PATH - fi - - SSL_HOSTNAME_ALT=$(openssl x509 -in $SSL_INUSE/server.crt -noout -text | grep --after-context=1 '509v3 Subject Alternative Name:' | tail -n 1 | sed -e "s/[[:space:]]*DNS://g" | tr ',' '\n' || true) - if [[ -n "$SSL_HOSTNAME_ALT" ]]; then - SSL_HOSTNAME_ALT_REGEX=$(echo "$SSL_HOSTNAME_ALT" | sed 's|\.|\\.|g' | sed 's/\*/\[^\.\]\*/g') - [[ -z "$(egrep "^${SSL_HOSTNAME_ALT_REGEX}$" $VHOST_PATH)" ]] && echo "$SSL_HOSTNAME_ALT" >> $VHOST_PATH - fi - - SSL_VHOSTS=$(egrep "^${SSL_HOSTNAME_REGEX}$|^${SSL_HOSTNAME_ALT_REGEX}$" $VHOST_PATH || exit 0) - NONSSL_VHOSTS=$(egrep -v "^${SSL_HOSTNAME}$|^${SSL_HOSTNAME_ALT}$" $VHOST_PATH || exit 0) - - NGINX_TEMPLATE="$PLUGIN_PATH/nginx-vhosts/templates/nginx.ssl.conf" - while read line; do - dokku_log_info1 "Configuring SSL for $line..." - SSL_SERVER_NAME=$line - NOSSL_SERVER_NAME=$line - eval "cat <<< \"$(< $NGINX_TEMPLATE)\" >> $NGINX_CONF" - done <<< "$SSL_VHOSTS" - fi - - NOSSL_SERVER_NAME=$(echo $NONSSL_VHOSTS | tr '\n' ' ') - APP_NGINX_TEMPLATE="$DOKKU_ROOT/$APP/nginx.conf.template" - if [[ -f $APP_NGINX_TEMPLATE ]]; then - dokku_log_info1 "Overriding default nginx.conf with detected nginx.conf.template" - eval "cat <<< \"$(< $APP_NGINX_TEMPLATE)\" > $NGINX_CONF" - elif [[ -n "$NONSSL_VHOSTS" ]]; then - xargs -i echo "-----> Configuring {}..." <<< "$NONSSL_VHOSTS" - NGINX_TEMPLATE="$PLUGIN_PATH/nginx-vhosts/templates/nginx.conf" - eval "cat <<< \"$(< $NGINX_TEMPLATE)\" >> $NGINX_CONF" - fi - - if [[ -n "$DOKKU_APP_LISTEN_PORT" ]] && [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then - echo "upstream $APP { server $DOKKU_APP_LISTEN_IP:$DOKKU_APP_LISTEN_PORT; }" >> $NGINX_CONF - fi - - dokku_log_info1 "Creating $SCHEME nginx.conf" - mv $NGINX_CONF "$DOKKU_ROOT/$APP/nginx.conf" - - if [[ -n "$DOKKU_APP_LISTEN_PORT" ]] && [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then - dokku_log_info1 "Running nginx-pre-reload" - pluginhook nginx-pre-reload $APP $DOKKU_APP_LISTEN_PORT $DOKKU_APP_LISTEN_IP - - echo " Reloading nginx" - restart_nginx - fi - - echo "# THIS FILE IS GENERATED BY DOKKU - DO NOT EDIT, YOUR CHANGES WILL BE OVERWRITTEN" > $URLS_PATH - xargs -i echo "https://{}" <<< "${SSL_VHOSTS}" >> $URLS_PATH - xargs -i echo "http://{}" <<< "${NONSSL_VHOSTS}" >> $URLS_PATH - else - if [[ -f "$DOKKU_ROOT/$APP/VHOST" ]]; then - dokku_log_info1 "VHOST support disabled, deleting $APP/VHOST" - rm "$DOKKU_ROOT/$APP/VHOST" - fi - if [[ -f "$DOKKU_ROOT/$APP/nginx.conf" ]]; then - dokku_log_info1 "VHOST support disabled, deleting nginx.conf" - rm "$DOKKU_ROOT/$APP/nginx.conf" - - dokku_log_info1 "VHOST support disabled, reloading nginx after nginx.conf deletion" - restart_nginx - fi - fi - ;; - - nginx:import-ssl) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - [[ -t 0 ]] && echo "Tar archive containing server.crt and server.key expected on stdin" && exit 1 - APP="$2" - - TEMP_DIR=$(mktemp -d) - cd $TEMP_DIR - tar xvf - <&0 - [[ ! -f "$TEMP_DIR/server.crt" ]] && echo "Tar archive missing server.crt" && exit 1 - [[ ! -f "$TEMP_DIR/server.key" ]] && echo "Tar archive missing server.key" && exit 1 - - mkdir -p "$DOKKU_ROOT/$APP/tls" - mv "$TEMP_DIR/server.crt" "$DOKKU_ROOT/$APP/tls/server.crt" - mv "$TEMP_DIR/server.key" "$DOKKU_ROOT/$APP/tls/server.key" - cd $DOKKU_ROOT - rm -rf $TEMP_DIR - dokku nginx:build-config $APP - ;; - - help | nginx:help) - cat && cat< Imports a tarball from stdin; should contain server.crt and server.key - nginx:build-config (Re)builds nginx config for given app -EOF - ;; - - *) - exit $DOKKU_NOT_IMPLEMENTED_EXIT - ;; - -esac diff --git a/plugins/nginx-vhosts/functions b/plugins/nginx-vhosts/functions new file mode 100755 index 00000000000..c581b01610c --- /dev/null +++ b/plugins/nginx-vhosts/functions @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" + +restart_nginx () { + case "$DOKKU_DISTRO" in + ubuntu) + sudo /etc/init.d/nginx reload > /dev/null + ;; + + opensuse) + sudo /sbin/service nginx reload > /dev/null + ;; + esac +} diff --git a/plugins/nginx-vhosts/help.txt b/plugins/nginx-vhosts/help.txt new file mode 100644 index 00000000000..f51c92c6df0 --- /dev/null +++ b/plugins/nginx-vhosts/help.txt @@ -0,0 +1,2 @@ +nginx:import-ssl Imports a tarball from stdin; should contain server.crt and server.key +nginx:build-config (Re)builds nginx config for given app diff --git a/plugins/nginx-vhosts/post-deploy b/plugins/nginx-vhosts/post-deploy index c803c980b63..df7c160899c 100755 --- a/plugins/nginx-vhosts/post-deploy +++ b/plugins/nginx-vhosts/post-deploy @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$(dirname $0)/../common/functions" +source "$PLUGIN_PATH/common/functions" APP="$1"; PORT="$2"; IP="$3" NO_VHOST=$(dokku config:get $APP NO_VHOST || true) diff --git a/plugins/nginx-vhosts/post-domains-update b/plugins/nginx-vhosts/post-domains-update index 30d8006f46a..945057998a7 100755 --- a/plugins/nginx-vhosts/post-domains-update +++ b/plugins/nginx-vhosts/post-domains-update @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$(dirname $0)/../common/functions" +source "$PLUGIN_PATH/common/functions" APP="$1" diff --git a/plugins/nginx-vhosts/subcommands/build-config b/plugins/nginx-vhosts/subcommands/build-config new file mode 100755 index 00000000000..af4d6f49e1c --- /dev/null +++ b/plugins/nginx-vhosts/subcommands/build-config @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +APP="$2"; DOKKU_APP_LISTEN_PORT="$3"; DOKKU_APP_LISTEN_IP="${4}" +VHOST_PATH="$DOKKU_ROOT/$APP/VHOST" +URLS_PATH="$DOKKU_ROOT/$APP/URLS" +WILDCARD_SSL="$DOKKU_ROOT/tls" +SSL="$DOKKU_ROOT/$APP/tls" + +if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -f "$DOKKU_ROOT/$APP/PORT" ]]; then + DOKKU_APP_LISTEN_PORT=$(< "$DOKKU_ROOT/$APP/PORT") +fi +if [[ -z "$DOKKU_APP_LISTEN_IP" ]] && [[ -f "$DOKKU_ROOT/$APP/IP" ]]; then + DOKKU_APP_LISTEN_IP=$(< "$DOKKU_ROOT/$APP/IP") +fi + +[[ -f "$DOKKU_ROOT/ENV" ]] && source $DOKKU_ROOT/ENV +[[ -f "$DOKKU_ROOT/$APP/ENV" ]] && source $DOKKU_ROOT/$APP/ENV + +if [[ ! -n "$NO_VHOST" ]] && [[ -f "$DOKKU_ROOT/$APP/VHOST" ]]; then + NONSSL_VHOSTS=$(cat $VHOST_PATH) + if [[ -e "$SSL/server.crt" ]] && [[ -e "$SSL/server.key" ]]; then + SSL_INUSE="$SSL" + SSL_DIRECTIVES=$(cat <> $VHOST_PATH + fi + + SSL_HOSTNAME_ALT=$(openssl x509 -in $SSL_INUSE/server.crt -noout -text | grep --after-context=1 '509v3 Subject Alternative Name:' | tail -n 1 | sed -e "s/[[:space:]]*DNS://g" | tr ',' '\n' || true) + if [[ -n "$SSL_HOSTNAME_ALT" ]]; then + SSL_HOSTNAME_ALT_REGEX=$(echo "$SSL_HOSTNAME_ALT" | sed 's|\.|\\.|g' | sed 's/\*/\[^\.\]\*/g') + [[ -z "$(egrep "^${SSL_HOSTNAME_ALT_REGEX}$" $VHOST_PATH)" ]] && echo "$SSL_HOSTNAME_ALT" >> $VHOST_PATH + fi + + SSL_VHOSTS=$(egrep "^${SSL_HOSTNAME_REGEX}$|^${SSL_HOSTNAME_ALT_REGEX}$" $VHOST_PATH || exit 0) + NONSSL_VHOSTS=$(egrep -v "^${SSL_HOSTNAME_REGEX}$|^${SSL_HOSTNAME_ALT_REGEX}$" $VHOST_PATH || exit 0) + + NGINX_TEMPLATE="$PLUGIN_PATH/nginx-vhosts/templates/nginx.ssl.conf" + while read line; do + dokku_log_info1 "Configuring SSL for $line..." + SSL_SERVER_NAME=$line + NOSSL_SERVER_NAME=$line + eval "cat <<< \"$(< $NGINX_TEMPLATE)\" >> $NGINX_CONF" + done <<< "$SSL_VHOSTS" + fi + + NOSSL_SERVER_NAME=$(echo $NONSSL_VHOSTS | tr '\n' ' ') + APP_NGINX_TEMPLATE="$DOKKU_ROOT/$APP/nginx.conf.template" + if [[ -f $APP_NGINX_TEMPLATE ]]; then + dokku_log_info1 "Overriding default nginx.conf with detected nginx.conf.template" + eval "cat <<< \"$(< $APP_NGINX_TEMPLATE)\" > $NGINX_CONF" + elif [[ -n "$NONSSL_VHOSTS" ]]; then + xargs -i echo "-----> Configuring {}..." <<< "$NONSSL_VHOSTS" + NGINX_TEMPLATE="$PLUGIN_PATH/nginx-vhosts/templates/nginx.conf" + eval "cat <<< \"$(< $NGINX_TEMPLATE)\" >> $NGINX_CONF" + fi + + if [[ -n "$DOKKU_APP_LISTEN_PORT" ]] && [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then + echo "upstream $APP { server $DOKKU_APP_LISTEN_IP:$DOKKU_APP_LISTEN_PORT; }" >> $NGINX_CONF + fi + + dokku_log_info1 "Creating $SCHEME nginx.conf" + mv $NGINX_CONF "$DOKKU_ROOT/$APP/nginx.conf" + + if [[ -n "$DOKKU_APP_LISTEN_PORT" ]] && [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then + dokku_log_info1 "Running nginx-pre-reload" + pluginhook nginx-pre-reload $APP $DOKKU_APP_LISTEN_PORT $DOKKU_APP_LISTEN_IP + + echo " Reloading nginx" + restart_nginx + fi + + echo "# THIS FILE IS GENERATED BY DOKKU - DO NOT EDIT, YOUR CHANGES WILL BE OVERWRITTEN" > $URLS_PATH + xargs -i echo "https://{}" <<< "${SSL_VHOSTS}" >> $URLS_PATH + xargs -i echo "http://{}" <<< "${NONSSL_VHOSTS}" >> $URLS_PATH +else + if [[ -f "$DOKKU_ROOT/$APP/VHOST" ]]; then + dokku_log_info1 "VHOST support disabled, deleting $APP/VHOST" + rm "$DOKKU_ROOT/$APP/VHOST" + fi + if [[ -f "$DOKKU_ROOT/$APP/nginx.conf" ]]; then + dokku_log_info1 "VHOST support disabled, deleting nginx.conf" + rm "$DOKKU_ROOT/$APP/nginx.conf" + + dokku_log_info1 "VHOST support disabled, reloading nginx after nginx.conf deletion" + restart_nginx + fi +fi diff --git a/plugins/nginx-vhosts/subcommands/import-ssl b/plugins/nginx-vhosts/subcommands/import-ssl new file mode 100755 index 00000000000..6ad7408661f --- /dev/null +++ b/plugins/nginx-vhosts/subcommands/import-ssl @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +[[ -t 0 ]] && echo "Tar archive containing server.crt and server.key expected on stdin" && exit 1 +APP="$2" + +TEMP_DIR=$(mktemp -d) +cd $TEMP_DIR +tar xvf - <&0 +[[ ! -f "$TEMP_DIR/server.crt" ]] && echo "Tar archive missing server.crt" && exit 1 +[[ ! -f "$TEMP_DIR/server.key" ]] && echo "Tar archive missing server.key" && exit 1 + +mkdir -p "$DOKKU_ROOT/$APP/tls" +mv "$TEMP_DIR/server.crt" "$DOKKU_ROOT/$APP/tls/server.crt" +mv "$TEMP_DIR/server.key" "$DOKKU_ROOT/$APP/tls/server.key" +cd $DOKKU_ROOT +rm -rf $TEMP_DIR +dokku nginx:build-config $APP diff --git a/plugins/plugins/README.md b/plugins/plugins/README.md new file mode 100644 index 00000000000..b1a9961f039 --- /dev/null +++ b/plugins/plugins/README.md @@ -0,0 +1,9 @@ +# Dokku core plugin management system + +This plugin provides the `plugins` commands for managing plugins in Dokku, +including the `plugins-install` command that triggers the `install` script for +each plugin. + +It also includes the functionality that executes command and subcommand +scripts for each plugin. + diff --git a/plugins/plugins/command b/plugins/plugins/command new file mode 100755 index 00000000000..256d37787a3 --- /dev/null +++ b/plugins/plugins/command @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +ls -1 -d $PLUGIN_PATH/*/ diff --git a/plugins/plugins/commands b/plugins/plugins/commands new file mode 100755 index 00000000000..2a3df4ae52c --- /dev/null +++ b/plugins/plugins/commands @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +case "$1" in + plugins-install) + pluginhook install + exit $? + ;; + + plugins-install-dependencies) + pluginhook dependencies + exit $? + ;; + + plugins-update) + pluginhook update + exit $? + ;; + + help) + : + ;; +esac + +if [ -z $DOKKU_NO_COMMAND ]; then + case "$1" in + nginx:*) + PLUGIN_NAME="nginx-vhosts" + ;; + *) + PLUGIN_NAME="$1" + ;; + esac + if [ -x $PLUGIN_PATH/$PLUGIN_NAME/command ]; then + "$PLUGIN_PATH/$PLUGIN_NAME/command" "$@" + elif [ -x $PLUGIN_PATH/${PLUGIN_NAME%%:*}/subcommands/${1#*:} ]; then + "$PLUGIN_PATH/${PLUGIN_NAME%%:*}/subcommands/${1#*:}" "$@" + else + exit $DOKKU_NOT_IMPLEMENTED_EXIT + fi +fi diff --git a/plugins/plugins/help.txt b/plugins/plugins/help.txt new file mode 100644 index 00000000000..19b8f64fa4f --- /dev/null +++ b/plugins/plugins/help.txt @@ -0,0 +1,3 @@ +: Print active plugins +plugins-install Install active plugins +plugins-update Update active plugins diff --git a/plugins/ps/README.md b/plugins/ps/README.md new file mode 100644 index 00000000000..8f34c12a5d2 --- /dev/null +++ b/plugins/ps/README.md @@ -0,0 +1,5 @@ +# Dokku core ps plugin + +This plugin handles standard process level functions such as stopping, starting, restarting +and rebuilding of dokku apps. + diff --git a/plugins/ps/command b/plugins/ps/command new file mode 100755 index 00000000000..9cd501a8474 --- /dev/null +++ b/plugins/ps/command @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" + +APP="$2"; [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]] && CONTAINER_ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") +[[ -z "$CONTAINER_ID" ]] && echo "App $APP has not been deployed" && exit 0 + +docker exec -ti "$CONTAINER_ID" /bin/bash -c "ps auxwww" diff --git a/plugins/ps/commands b/plugins/ps/commands deleted file mode 100755 index 4fd227b7656..00000000000 --- a/plugins/ps/commands +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$(dirname $0)/../common/functions" - -release_and_deploy() { - source "$(dirname $0)/../common/functions" - local APP="$1"; local IMAGE="dokku/$APP" - - if [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]]; then - if is_image_buildstep_based "$IMAGE"; then - IMAGE_SOURCE_TYPE="buildstep" - else - IMAGE_SOURCE_TYPE="dockerfile" - fi - - dokku_log_info1 "Releasing $APP ..." - dokku release "$APP" "$IMAGE_SOURCE_TYPE" - dokku_log_info1 "Deploying $APP ..." - dokku deploy "$APP" - dokku_log_info2 "Application deployed:" - dokku urls $APP | sed "s/^/ /" - echo - fi -} - -case "$1" in - ps) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - - APP="$2"; [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]] && CONTAINER_ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") - [[ -z "$CONTAINER_ID" ]] && echo "App $APP has not been deployed" && exit 0 - - docker exec -ti "$CONTAINER_ID" /bin/bash -c "ps auxwww" - ;; - - ps:start) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - - APP="$2"; [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]] && CONTAINER_ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") - [[ -z "$CONTAINER_ID" ]] && echo "App $APP has not been deployed" && exit 0 - - if [[ "$(docker ps -q --no-trunc| grep -q $CONTAINER_ID; echo $?)" != "0" ]]; then - release_and_deploy $APP - else - echo "App $APP already running" - fi - ;; - - ps:stop) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - - APP="$2"; [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]] && CONTAINER_ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") - [[ -z "$CONTAINER_ID" ]] && echo "App $APP has not been deployed" && exit 0 - - if [[ "$(docker ps -q --no-trunc| grep -q $CONTAINER_ID; echo $?)" = "0" ]]; then - echo "Stopping $APP ..." - docker stop $CONTAINER_ID > /dev/null - else - echo "App $APP already stopped" - fi - ;; - - ps:rebuild) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2" - - pluginhook receive-app $APP - ;; - - ps:rebuildall) - shopt -s nullglob - for app in $DOKKU_ROOT/*/CONTAINER; do - APP=$(basename "$(dirname $app)"); - dokku ps:rebuild $APP - done - ;; - - ps:restart) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - - APP="$2"; [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]] && CONTAINER_ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") - [[ -z "$CONTAINER_ID" ]] && echo "App $APP has not been deployed" && exit 0 - - release_and_deploy $APP - ;; - - ps:restartall) - shopt -s nullglob - for app in $DOKKU_ROOT/*/CONTAINER; do - APP=$(basename "$(dirname $app)"); - dokku ps:restart $APP - done - ;; - - help | ps:help) - cat && cat< List processes running in app container(s) - ps:start Start app container(s) - ps:stop Stop app container(s) - ps:rebuild Rebuild an app - ps:rebuildall Rebuild all apps - ps:restart Restart app container(s) - ps:restartall Restart all deployed app containers -EOF - ;; - - *) - exit $DOKKU_NOT_IMPLEMENTED_EXIT - ;; - -esac diff --git a/plugins/ps/functions b/plugins/ps/functions new file mode 100755 index 00000000000..319a2c812ed --- /dev/null +++ b/plugins/ps/functions @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" + +release_and_deploy() { + local APP="$1"; local IMAGE="dokku/$APP" + + if [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]]; then + if is_image_buildstep_based "$IMAGE"; then + IMAGE_SOURCE_TYPE="buildstep" + else + IMAGE_SOURCE_TYPE="dockerfile" + fi + + dokku_log_info1 "Releasing $APP ..." + dokku release "$APP" "$IMAGE_SOURCE_TYPE" + dokku_log_info1 "Deploying $APP ..." + dokku deploy "$APP" + dokku_log_info2 "Application deployed:" + dokku urls $APP | sed "s/^/ /" + echo + fi +} diff --git a/plugins/ps/help.txt b/plugins/ps/help.txt new file mode 100644 index 00000000000..0c6b187cf1d --- /dev/null +++ b/plugins/ps/help.txt @@ -0,0 +1,7 @@ +: List processes running in app container(s) +ps:start Start app container(s) +ps:stop Stop app container(s) +ps:rebuild Rebuild an app +ps:rebuildall Rebuild all apps +ps:restart Restart app container(s) +ps:restartall Restart all deployed app containers diff --git a/plugins/ps/subcommands/rebuild b/plugins/ps/subcommands/rebuild new file mode 100755 index 00000000000..0bf0d41fa15 --- /dev/null +++ b/plugins/ps/subcommands/rebuild @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +APP="$2" + +pluginhook receive-app $APP diff --git a/plugins/ps/subcommands/rebuildall b/plugins/ps/subcommands/rebuildall new file mode 100755 index 00000000000..e01b3c626f4 --- /dev/null +++ b/plugins/ps/subcommands/rebuildall @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +shopt -s nullglob +for app in $DOKKU_ROOT/*/CONTAINER; do + APP=$(basename "$(dirname $app)"); + dokku ps:rebuild $APP +done diff --git a/plugins/ps/subcommands/restart b/plugins/ps/subcommands/restart new file mode 100755 index 00000000000..9bbc7c5a7b0 --- /dev/null +++ b/plugins/ps/subcommands/restart @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" + +APP="$2"; [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]] && CONTAINER_ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") +[[ -z "$CONTAINER_ID" ]] && echo "App $APP has not been deployed" && exit 0 + +release_and_deploy $APP diff --git a/plugins/ps/subcommands/restartall b/plugins/ps/subcommands/restartall new file mode 100755 index 00000000000..e2e9f1b75aa --- /dev/null +++ b/plugins/ps/subcommands/restartall @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +shopt -s nullglob +for app in $DOKKU_ROOT/*/CONTAINER; do + APP=$(basename "$(dirname $app)"); + dokku ps:restart $APP +done diff --git a/plugins/ps/subcommands/start b/plugins/ps/subcommands/start new file mode 100755 index 00000000000..07d2c78afa5 --- /dev/null +++ b/plugins/ps/subcommands/start @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" + +APP="$2"; [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]] && CONTAINER_ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") +[[ -z "$CONTAINER_ID" ]] && echo "App $APP has not been deployed" && exit 0 + +if [[ "$(docker ps -q --no-trunc| grep -q $CONTAINER_ID; echo $?)" != "0" ]]; then + release_and_deploy $APP +else + echo "App $APP already running" +fi diff --git a/plugins/ps/subcommands/stop b/plugins/ps/subcommands/stop new file mode 100755 index 00000000000..ee74902c684 --- /dev/null +++ b/plugins/ps/subcommands/stop @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" +source "$(dirname $0)/../functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" + +APP="$2"; [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]] && CONTAINER_ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") +[[ -z "$CONTAINER_ID" ]] && echo "App $APP has not been deployed" && exit 0 + +if [[ "$(docker ps -q --no-trunc| grep -q $CONTAINER_ID; echo $?)" = "0" ]]; then + echo "Stopping $APP ..." + docker stop $CONTAINER_ID > /dev/null +else + echo "App $APP already stopped" +fi diff --git a/plugins/release/README.md b/plugins/release/README.md new file mode 100644 index 00000000000..240cad8706a --- /dev/null +++ b/plugins/release/README.md @@ -0,0 +1,11 @@ +# Dokku core release command + +This plugin defines the `release` command used internally to cut a new release +of an app with a new configuration environment (usually after the `receive` +command has built an app, or when setting a new configuration via the `config` +command). + +It runs any pre-release hooks, creates a new container with the new environment +included as its profile, then runs any post-release hooks. Further deployment, +if any, is handled by the external context. + diff --git a/plugins/release/command b/plugins/release/command new file mode 100755 index 00000000000..1e4dda251d0 --- /dev/null +++ b/plugins/release/command @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" + +APP="$2"; IMAGE="dokku/$APP"; IMAGE_SOURCE_TYPE="$3" +case "$IMAGE_SOURCE_TYPE" in + buildstep) + # *DEPRECATED* in v0.4.0: `pluginhook pre-release` will be removed in future releases + pluginhook pre-release "$APP" + pluginhook pre-release-buildstep "$APP" + if [[ -f "$DOKKU_ROOT/ENV" ]]; then + id=$(docker run -i -a stdin $IMAGE /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/00-global-env.sh" < "$DOKKU_ROOT/ENV") + test "$(docker wait $id)" -eq 0 + docker commit $id $IMAGE > /dev/null + fi + if [[ -f "$DOKKU_ROOT/$APP/ENV" ]]; then + id=$(docker run -i -a stdin $IMAGE /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/01-app-env.sh" < "$DOKKU_ROOT/$APP/ENV") + test "$(docker wait $id)" -eq 0 + docker commit $id $IMAGE > /dev/null + fi + # *DEPRECATED* in v0.4.0: `pluginhook post-release` will be removed in future releases + pluginhook post-release "$APP" + pluginhook post-release-buildstep "$APP" + ;; + + dockerfile) + # buildstep pluginhooks don't necessarily make sense for dockerfiles. call the new breed!!! + pluginhook pre-release-dockerfile "$APP" + pluginhook post-release-dockerfile "$APP" + ;; + + *) + dokku_log_fail "Releasing image source type $IMAGE_SOURCE_TYPE not supported!" + ;; +esac diff --git a/plugins/run/README.md b/plugins/run/README.md new file mode 100644 index 00000000000..b4510ee6850 --- /dev/null +++ b/plugins/run/README.md @@ -0,0 +1,5 @@ +# Dokku core "run" command + +This plugin provides the Dokku `run` command, to run a command in the +environment of an app's container. + diff --git a/plugins/run/command b/plugins/run/command new file mode 100755 index 00000000000..511e8f437f5 --- /dev/null +++ b/plugins/run/command @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" + +[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 +verify_app_name "$2" +APP="$2"; IMAGE="dokku/$APP" + +shift 2 + +DOCKER_ARGS=$(: | pluginhook docker-args $APP run) +DOCKER_ARGS+=$(: | pluginhook docker-args-run $APP) +[[ $DOKKU_RM_CONTAINER ]] && DOKKU_RUN_OPTS="--rm" +has_tty && DOKKU_RUN_OPTS+=" -i -t" +is_image_buildstep_based "$IMAGE" && EXEC_CMD="/exec" + +docker run $DOKKU_RUN_OPTS $DOCKER_ARGS $IMAGE $EXEC_CMD "$@" diff --git a/plugins/run/help.txt b/plugins/run/help.txt new file mode 100644 index 00000000000..057edb1e047 --- /dev/null +++ b/plugins/run/help.txt @@ -0,0 +1 @@ +: Run a command in the environment of an application diff --git a/plugins/shell/README.md b/plugins/shell/README.md new file mode 100644 index 00000000000..64829f30e75 --- /dev/null +++ b/plugins/shell/README.md @@ -0,0 +1,4 @@ +# Dokku core shell plugin` + +This plugin provides a shell in which to run dokku commands. + diff --git a/plugins/shell/command b/plugins/shell/command new file mode 100755 index 00000000000..ccb64f5a73a --- /dev/null +++ b/plugins/shell/command @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +INPUTRC="$PLUGIN_ROOT/inputrc" +HISTFILE=~/.dokku_history + +history -r || true + +trim() +{ + sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g' +} + +trap 'history -w' EXIT + +while true; do + trap '' SIGINT + read -ep "dokku> " line || { + echo; true; break + } + trap - SIGINT + + line=$(echo $line | trim) + CMD=$(echo $line | awk '{ print $1 }') + + [ -z $CMD ] && continue + + [[ "$line" != "$(fc -ln -1 | trim)" ]] && history -s "$line" + + case $CMD in +# shell builtins + clear) + clear + ;; + quit|exit) + break + ;; + +# Not a built-in, run as regular dokku command + *) + dokku $line || true + esac + +done diff --git a/plugins/shell/commands b/plugins/shell/commands deleted file mode 100755 index c6dca6ffe5d..00000000000 --- a/plugins/shell/commands +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x - -case "$1" in - shell) - INPUTRC="$PLUGIN_ROOT/inputrc" - HISTFILE=~/.dokku_history - - history -r || true - - trim() - { - sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g' - } - - trap 'history -w' EXIT - - while true; do - trap '' SIGINT - read -ep "dokku> " line || { - echo; true; break - } - trap - SIGINT - - line=$(echo $line | trim) - CMD=$(echo $line | awk '{ print $1 }') - - [ -z $CMD ] && continue - - [[ "$line" != "$(fc -ln -1 | trim)" ]] && history -s "$line" - - case $CMD in - # shell builtins - clear) - clear - ;; - quit|exit) - break - ;; - - # Not a built-in, run as regular dokku command - *) - dokku $line || true - esac - - done - ;; - - help | shell:help) - cat && cat< $DOKKU_ROOT/.dokkurc/DOKKU_TRACE +fi + +if [[ "$2" == "off" ]]; then + echo "Disabling dokku trace" + rm -f $DOKKU_ROOT/.dokkurc/DOKKU_TRACE +fi diff --git a/plugins/trace/help.txt b/plugins/trace/help.txt new file mode 100644 index 00000000000..1d1f6c7a531 --- /dev/null +++ b/plugins/trace/help.txt @@ -0,0 +1 @@ +: [on|off] Set DOKKU_TRACE on/off diff --git a/plugins/version/README.md b/plugins/version/README.md new file mode 100644 index 00000000000..05b6363024a --- /dev/null +++ b/plugins/version/README.md @@ -0,0 +1,6 @@ +# Dokku core version command + +This plugin provides the `version` command, which outputs the version of Dokku +that is installed, as stored as the content of the VERSION file in the Dokku +root directory. + diff --git a/plugins/version/command b/plugins/version/command new file mode 100755 index 00000000000..525857e20bd --- /dev/null +++ b/plugins/version/command @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_PATH/common/functions" + +cat "$DOKKU_ROOT/VERSION" || { + echo "Unable to determine dokku's version" 2>&1 + exit 1 +} diff --git a/plugins/version/help.txt b/plugins/version/help.txt new file mode 100644 index 00000000000..18b4ccca81b --- /dev/null +++ b/plugins/version/help.txt @@ -0,0 +1 @@ +: Print dokku's version diff --git a/tests.mk b/tests.mk index a1d7e62b3b7..012b2bd8355 100644 --- a/tests.mk +++ b/tests.mk @@ -57,7 +57,7 @@ lint: # SC2001: See if you can use ${variable//search/replace} instead. - https://github.com/koalaman/shellcheck/wiki/SC2001 @echo linting... @$(QUIET) shellcheck -e SC2029 ./contrib/dokku_client.sh - @$(QUIET) find . -not -path '*/\.*' | xargs file | egrep "shell|bash" | grep -v directory | awk '{ print $$1 }' | sed 's/://g' | grep -v dokku_client.sh | xargs shellcheck -e SC2034,SC2086,SC2143,SC2001 + @$(QUIET) find . -not -path '*/\.*' | xargs file | egrep "shell script|bash script" | grep -v directory | awk '{ print $$1 }' | sed 's/://g' | grep -v dokku_client.sh | xargs shellcheck -e SC2034,SC2086,SC2143,SC2001 unit-tests: @echo running unit tests... diff --git a/tests/apps/config/package.json b/tests/apps/config/package.json index 7bdf8db0c41..eb17caf0c4a 100644 --- a/tests/apps/config/package.json +++ b/tests/apps/config/package.json @@ -5,7 +5,7 @@ "express": "2.5.x" }, "engines": { - "node": "0.8.x", - "npm": "1.1.x" + "node": "0.10.x", + "npm": "2.7.x" } -} \ No newline at end of file +} diff --git a/tests/apps/gitsubmodules/package.json b/tests/apps/gitsubmodules/package.json index 742e0610536..ca800a5dfe1 100644 --- a/tests/apps/gitsubmodules/package.json +++ b/tests/apps/gitsubmodules/package.json @@ -6,6 +6,6 @@ }, "engines": { "node": "0.10.x", - "npm": "1.3.x" + "npm": "2.7.x" } -} \ No newline at end of file +} diff --git a/tests/apps/nodejs-express-noprocfile/package.json b/tests/apps/nodejs-express-noprocfile/package.json index 7f0462b584d..6f8ca43e18c 100644 --- a/tests/apps/nodejs-express-noprocfile/package.json +++ b/tests/apps/nodejs-express-noprocfile/package.json @@ -5,8 +5,8 @@ "express": "2.5.x" }, "engines": { - "node": "0.8.x", - "npm": "1.1.x" + "node": "0.10.x", + "npm": "2.7.x" }, "scripts": { "start": "node web.js" diff --git a/tests/apps/nodejs-express/package.json b/tests/apps/nodejs-express/package.json index 7bdf8db0c41..eb17caf0c4a 100644 --- a/tests/apps/nodejs-express/package.json +++ b/tests/apps/nodejs-express/package.json @@ -5,7 +5,7 @@ "express": "2.5.x" }, "engines": { - "node": "0.8.x", - "npm": "1.1.x" + "node": "0.10.x", + "npm": "2.7.x" } -} \ No newline at end of file +} diff --git a/tests/unit/apps.bats b/tests/unit/apps.bats old mode 100755 new mode 100644 diff --git a/tests/unit/build-env.bats b/tests/unit/build-env.bats index cd062742aaa..f18b58bfd7e 100644 --- a/tests/unit/build-env.bats +++ b/tests/unit/build-env.bats @@ -10,7 +10,7 @@ teardown() { destroy_app } -@test "build-env with special chars" { +@test "build-env: with special chars" { run dokku config:set $TEST_APP NEWRELIC_APP_NAME="$TEST_APP (Staging)" echo "output: "$output echo "status: "$status diff --git a/tests/unit/cleanup.bats b/tests/unit/cleanup.bats index 22e0c4f5bde..31c3c82dbf0 100644 --- a/tests/unit/cleanup.bats +++ b/tests/unit/cleanup.bats @@ -10,7 +10,7 @@ teardown() { destroy_app } -@test "remove exited containers" { +@test "core: remove exited containers" { # make sure we have many exited containers of the same 'type' run bash -c "for cnt in 1 2 3; do dokku run $TEST_APP hostname; done" echo "output: "$output diff --git a/tests/unit/00_dokku-standard.bats b/tests/unit/core.bats similarity index 78% rename from tests/unit/00_dokku-standard.bats rename to tests/unit/core.bats index d7a5cde9548..a77286159ca 100644 --- a/tests/unit/00_dokku-standard.bats +++ b/tests/unit/core.bats @@ -26,7 +26,18 @@ build_nginx_config() { dokku nginx:build-config $TEST_APP } -@test "run (with tty)" { +@test "core: unknown command" { + run /bin/bash -c "dokku fakecommand" + echo "output: "$output + echo "status: "$status + assert_failure + run /bin/bash -c "dokku fakecommand | grep -q 'is not a dokku command'" + echo "output: "$output + echo "status: "$status + assert_success +} + +@test "core: run (with tty)" { deploy_app run /bin/bash -c "dokku run $TEST_APP ls /app/package.json" echo "output: "$output @@ -34,7 +45,7 @@ build_nginx_config() { assert_success } -@test "run (without tty)" { +@test "core: run (without tty)" { deploy_app run /bin/bash -c ": |dokku run $TEST_APP ls /app/package.json" echo "output: "$output @@ -42,7 +53,7 @@ build_nginx_config() { assert_success } -@test "run (with --options)" { +@test "core: run (with --options)" { deploy_app run /bin/bash -c "dokku --force --quiet run $TEST_APP node --version" echo "output: "$output @@ -50,7 +61,7 @@ build_nginx_config() { assert_success } -@test "urls (non-ssl)" { +@test "core: urls (non-ssl)" { assert_urls "http://dokku.me" build_nginx_config assert_urls "http://${TEST_APP}.dokku.me" @@ -58,7 +69,7 @@ build_nginx_config() { assert_urls "http://${TEST_APP}.dokku.me" "http://test.dokku.me" } -@test "urls (app ssl)" { +@test "core: urls (app ssl)" { setup_test_tls assert_urls "https://dokku.me" build_nginx_config @@ -67,7 +78,7 @@ build_nginx_config() { assert_urls "https://node-js-app.dokku.me" "http://${TEST_APP}.dokku.me" "http://test.dokku.me" } -@test "urls (wildcard ssl)" { +@test "core: urls (wildcard ssl)" { setup_test_tls_wildcard assert_urls "https://dokku.me" build_nginx_config diff --git a/tests/unit/ps-buildstep.bats b/tests/unit/ps-buildstep.bats index 9973731efeb..4cbbdd0ed19 100644 --- a/tests/unit/ps-buildstep.bats +++ b/tests/unit/ps-buildstep.bats @@ -21,33 +21,25 @@ teardown() { # assert_success # } -@test "ps:start (buildstep)" { +@test "ps (buildstep)" { run bash -c "dokku ps:stop $TEST_APP" echo "output: "$output echo "status: "$status assert_success - run bash -c "dokku ps:start $TEST_APP" - echo "output: "$output - echo "status: "$status - assert_success run bash -c "docker ps -q --no-trunc | grep -q $(< $DOKKU_ROOT/$TEST_APP/CONTAINER)" echo "output: "$output echo "status: "$status - assert_success -} + assert_failure -@test "ps:stop (buildstep)" { - run bash -c "dokku ps:stop $TEST_APP" + run bash -c "dokku ps:start $TEST_APP" echo "output: "$output echo "status: "$status assert_success run bash -c "docker ps -q --no-trunc | grep -q $(< $DOKKU_ROOT/$TEST_APP/CONTAINER)" echo "output: "$output echo "status: "$status - assert_failure -} + assert_success -@test "ps:restart (buildstep)" { run bash -c "dokku ps:restart $TEST_APP" echo "output: "$output echo "status: "$status @@ -56,9 +48,7 @@ teardown() { echo "output: "$output echo "status: "$status assert_success -} -@test "ps:rebuild (buildstep)" { run bash -c "dokku ps:rebuild $TEST_APP" echo "output: "$output echo "status: "$status diff --git a/tests/unit/ps-dockerfile.bats b/tests/unit/ps-dockerfile.bats index ddc1d370688..1cce3a70460 100644 --- a/tests/unit/ps-dockerfile.bats +++ b/tests/unit/ps-dockerfile.bats @@ -10,44 +10,36 @@ teardown() { destroy_app } -@test "ps (dockerfile)" { - # CI support: 'Ah. I just spoke with our Docker expert -- - # 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." - run bash -c "dokku ps $TEST_APP | grep -q \"node web.js\"" - echo "output: "$output - echo "status: "$status - assert_success -} +# @test "ps (dockerfile)" { +# # CI support: 'Ah. I just spoke with our Docker expert -- +# # 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." +# run bash -c "dokku ps $TEST_APP | grep -q \"node web.js\"" +# echo "output: "$output +# echo "status: "$status +# assert_success +# } -@test "ps:start (dockerfile)" { +@test "ps (dockerfile)" { run bash -c "dokku ps:stop $TEST_APP" echo "output: "$output echo "status: "$status assert_success - run bash -c "dokku ps:start $TEST_APP" - echo "output: "$output - echo "status: "$status - assert_success run bash -c "docker ps -q --no-trunc | grep -q $(< $DOKKU_ROOT/$TEST_APP/CONTAINER)" echo "output: "$output echo "status: "$status - assert_success -} + assert_failure -@test "ps:stop (dockerfile)" { - run bash -c "dokku ps:stop $TEST_APP" + run bash -c "dokku ps:start $TEST_APP" echo "output: "$output echo "status: "$status assert_success run bash -c "docker ps -q --no-trunc | grep -q $(< $DOKKU_ROOT/$TEST_APP/CONTAINER)" echo "output: "$output echo "status: "$status - assert_failure -} + assert_success -@test "ps:restart (dockerfile)" { run bash -c "dokku ps:restart $TEST_APP" echo "output: "$output echo "status: "$status @@ -56,9 +48,7 @@ teardown() { echo "output: "$output echo "status: "$status assert_success -} -@test "ps:rebuild (dockerfile)" { run bash -c "dokku ps:rebuild $TEST_APP" echo "output: "$output echo "status: "$status