diff --git a/docs/deployment/application-management.md b/docs/deployment/application-management.md index 9b44c3ebc..1ddf5715a 100644 --- a/docs/deployment/application-management.md +++ b/docs/deployment/application-management.md @@ -4,6 +4,7 @@ ``` apps # List your apps +apps:clone # Clones an app apps:create # Create a new app apps:destroy # Permanently destroy an app apps:rename # Rename an app @@ -112,3 +113,27 @@ Renaming node-js-app to io-js-app... done ``` This will copy all of your app's contents into a new app directory with the name of your choice, delete your old app, then rebuild the new version of the app and deploy it. All of your config variables, including database urls, will be preserved. + +### Cloning an existing app + +> New as of 0.8.1 + +You can clone an existing app using the `apps:clone` command. Note that the application *must* have been deployed at least once, or the rename will not complete successfully: + +```shell +dokku apps:clone node-js-app io-js-app +``` + +``` +Cloning node-js-app to io-js-app... done +``` + +This will copy all of your app's contents into a new app directory with the name of your choice and then rebuild the new version of the app and deploy it. All of your config variables, including database urls, will be preserved. + +> Warning: If you have exposed specific ports via docker-options, added generic domains, or performed anything that cannot be done against multiple applications, `apps:clone` may result in errors. + +By default, Dokku will deploy this new application, though you can skip the deploy by using the `--skip-deploy` flag: + +```shell +dokku apps:clone --skip-deploy node-js-app io-js-app +``` diff --git a/plugins/apps/commands b/plugins/apps/commands index c46d255b1..234108cdf 100755 --- a/plugins/apps/commands +++ b/plugins/apps/commands @@ -8,6 +8,7 @@ case "$1" in declare desc="return apps plugin help content" cat< , Clones an app apps:create , Create a new app apps:destroy , Permanently destroy an app apps:rename , Rename an app diff --git a/plugins/apps/subcommands/clone b/plugins/apps/subcommands/clone new file mode 100755 index 000000000..829882c2c --- /dev/null +++ b/plugins/apps/subcommands/clone @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/apps/functions" +source "$PLUGIN_AVAILABLE_PATH/ps/functions" + +apps_clone_cmd() { + declare desc="clones an app" + declare OLD_APP="$2" NEW_APP="$3" + local cmd="apps:clone" + local SKIP_REBUILD=false + + if [[ "$2" == "--skip-deploy" ]]; then + SKIP_REBUILD=true + OLD_APP="$3" + NEW_APP="$4" + fi + + [[ -z "$OLD_APP" ]] && dokku_log_fail "Please specify an app to run the command on" + [[ -d "$DOKKU_ROOT/$NEW_APP" ]] && dokku_log_fail "Name is already taken" + local NEW_CACHE_DIR="$DOKKU_ROOT/$NEW_APP/cache" + + apps_create "$NEW_APP" + cp -a "$DOKKU_ROOT/$OLD_APP/." "$DOKKU_ROOT/$NEW_APP" + + if [[ -d "$NEW_CACHE_DIR" ]] && ! rmdir "$NEW_CACHE_DIR"; then + docker run "$DOKKU_GLOBAL_RUN_ARGS" --rm -v "$NEW_CACHE_DIR:/cache" "dokku/$OLD_APP" chmod 777 -R /cache + fi + rm -rf "$NEW_CACHE_DIR" + + [[ -f "$DOKKU_ROOT/$NEW_APP/URLS" ]] && sed -i -e "s/$OLD_APP/$NEW_APP/g" "$DOKKU_ROOT/$NEW_APP/URLS" + [[ -f "$DOKKU_ROOT/$NEW_APP/VHOST" ]] && sed -i -e "s/$OLD_APP/$NEW_APP/g" "$DOKKU_ROOT/$NEW_APP/VHOST" + [[ -f "$DOKKU_ROOT/$NEW_APP/hooks/pre-receive" ]] && sed -i -e "s/git-hook $OLD_APP/git-hook $NEW_APP/g" "$DOKKU_ROOT/$NEW_APP/hooks/pre-receive" + [[ "$SKIP_REBUILD" == "true" ]] || ps_rebuild "$NEW_APP" + plugn trigger post-app-clone "$OLD_APP" "$NEW_APP" + echo "Cloning $OLD_APP to $NEW_APP... done" +} + +apps_clone_cmd "$@" diff --git a/tests/unit/10_apps.bats b/tests/unit/10_apps.bats index 9558b3e9b..17ed29be3 100644 --- a/tests/unit/10_apps.bats +++ b/tests/unit/10_apps.bats @@ -90,3 +90,36 @@ teardown () { echo "status: "$status assert_success } + +@test "(apps) apps:clone" { + deploy_app + run bash -c "dokku apps:clone $TEST_APP great-test-name" + echo "output: "$output + echo "status: "$status + assert_success + run bash -c "dokku apps | grep $TEST_APP" + echo "output: "$output + echo "status: "$status + assert_success + run bash -c "curl --silent --write-out '%{http_code}\n' `dokku url great-test-name` | grep 404" + echo "output: "$output + echo "status: "$status + assert_output "" + run bash -c "dokku --force apps:destroy great-test-name" + echo "output: "$output + echo "status: "$status + assert_success + + run bash -c "dokku apps:clone --skip-deploy $TEST_APP great-test-name" + echo "output: "$output + echo "status: "$status + assert_success + run bash -c "curl --silent --write-out '%{http_code}\n' `dokku url great-test-name` | grep 404" + echo "output: "$output + echo "status: "$status + assert_failure + run bash -c "dokku --force apps:destroy great-test-name" + echo "output: "$output + echo "status: "$status + assert_success +}