From 809a271859d0ebb2323bf33487fdd5630267aa85 Mon Sep 17 00:00:00 2001 From: apple Date: Mon, 27 Oct 2025 13:17:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20mysql=20redis=20=20postgre?= =?UTF-8?q?s=20=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/mysql/.devcontainer/20_init_plugin | 24 + plugins/mysql/.devcontainer/Dockerfile | 26 + plugins/mysql/.devcontainer/bin/copy-file | 8 + plugins/mysql/.devcontainer/devcontainer.json | 16 + plugins/mysql/.editorconfig | 16 + plugins/mysql/.github/dependabot.yml | 12 + plugins/mysql/.github/labels.yml | 57 + plugins/mysql/.github/workflows/ci.yml | 100 ++ .../.github/workflows/tagged-release.yml | 20 + plugins/mysql/.gitignore | 3 + plugins/mysql/Dockerfile | 1 + plugins/mysql/LICENSE.txt | 7 + plugins/mysql/Makefile | 98 ++ plugins/mysql/README.md | 774 ++++++++++++ plugins/mysql/Vagrantfile | 27 + plugins/mysql/bin/generate | 677 +++++++++++ plugins/mysql/commands | 19 + plugins/mysql/common-functions | 1056 +++++++++++++++++ plugins/mysql/config | 36 + plugins/mysql/docs/README.md | 4 + plugins/mysql/functions | 237 ++++ plugins/mysql/help-functions | 320 +++++ plugins/mysql/install | 69 ++ plugins/mysql/plugin.toml | 4 + plugins/mysql/post-app-clone-setup | 18 + plugins/mysql/post-app-rename-setup | 18 + plugins/mysql/pre-delete | 13 + plugins/mysql/pre-restore | 36 + plugins/mysql/pre-start | 32 + plugins/mysql/service-list | 20 + plugins/mysql/subcommands/app-links | 23 + plugins/mysql/subcommands/backup | 28 + plugins/mysql/subcommands/backup-auth | 36 + plugins/mysql/subcommands/backup-deauth | 23 + plugins/mysql/subcommands/backup-schedule | 31 + plugins/mysql/subcommands/backup-schedule-cat | 22 + .../mysql/subcommands/backup-set-encryption | 25 + .../backup-set-public-key-encryption | 25 + plugins/mysql/subcommands/backup-unschedule | 23 + .../mysql/subcommands/backup-unset-encryption | 23 + .../backup-unset-public-key-encryption | 23 + plugins/mysql/subcommands/clone | 53 + plugins/mysql/subcommands/connect | 24 + plugins/mysql/subcommands/create | 40 + plugins/mysql/subcommands/destroy | 58 + plugins/mysql/subcommands/enter | 27 + plugins/mysql/subcommands/exists | 22 + plugins/mysql/subcommands/export | 25 + plugins/mysql/subcommands/expose | 25 + plugins/mysql/subcommands/import | 23 + plugins/mysql/subcommands/info | 49 + plugins/mysql/subcommands/link | 61 + plugins/mysql/subcommands/linked | 26 + plugins/mysql/subcommands/links | 24 + plugins/mysql/subcommands/list | 18 + plugins/mysql/subcommands/logs | 27 + plugins/mysql/subcommands/pause | 22 + plugins/mysql/subcommands/promote | 39 + plugins/mysql/subcommands/restart | 24 + plugins/mysql/subcommands/set | 45 + plugins/mysql/subcommands/start | 22 + plugins/mysql/subcommands/stop | 22 + plugins/mysql/subcommands/unexpose | 22 + plugins/mysql/subcommands/unlink | 28 + plugins/mysql/subcommands/upgrade | 76 ++ plugins/mysql/tests/hook_pre_delete.bats | 20 + plugins/mysql/tests/link_networks.bats | 289 +++++ plugins/mysql/tests/service_clone.bats | 47 + plugins/mysql/tests/service_connect.bats | 26 + plugins/mysql/tests/service_create.bats | 27 + plugins/mysql/tests/service_destroy.bats | 30 + plugins/mysql/tests/service_export.bats | 39 + plugins/mysql/tests/service_expose.bats | 66 ++ plugins/mysql/tests/service_import.bats | 40 + plugins/mysql/tests/service_info.bats | 75 ++ plugins/mysql/tests/service_link.bats | 142 +++ plugins/mysql/tests/service_list.bats | 22 + plugins/mysql/tests/service_logs.bats | 42 + plugins/mysql/tests/service_pause.bats | 25 + plugins/mysql/tests/service_promote.bats | 63 + plugins/mysql/tests/service_restart.bats | 25 + plugins/mysql/tests/service_start.bats | 25 + plugins/mysql/tests/service_stop.bats | 25 + plugins/mysql/tests/service_unexpose.bats | 27 + plugins/mysql/tests/service_unlink.bats | 77 ++ plugins/mysql/tests/setup.sh | 33 + plugins/mysql/tests/shellcheck-exclude | 8 + plugins/mysql/tests/shellcheck-to-junit | 205 ++++ plugins/mysql/tests/test_helper.bash | 86 ++ plugins/mysql/update | 1 + plugins/postgres/.devcontainer/20_init_plugin | 24 + plugins/postgres/.devcontainer/Dockerfile | 26 + plugins/postgres/.devcontainer/bin/copy-file | 8 + .../postgres/.devcontainer/devcontainer.json | 16 + plugins/postgres/.editorconfig | 16 + plugins/postgres/.github/dependabot.yml | 12 + plugins/postgres/.github/labels.yml | 57 + plugins/postgres/.github/workflows/ci.yml | 100 ++ .../.github/workflows/tagged-release.yml | 20 + plugins/postgres/.gitignore | 3 + plugins/postgres/Dockerfile | 1 + plugins/postgres/LICENSE.txt | 7 + plugins/postgres/Makefile | 98 ++ plugins/postgres/README.md | 825 +++++++++++++ plugins/postgres/Vagrantfile | 27 + plugins/postgres/bin/generate | 677 +++++++++++ plugins/postgres/commands | 19 + plugins/postgres/common-functions | 1056 +++++++++++++++++ plugins/postgres/config | 36 + plugins/postgres/docs/README.md | 4 + plugins/postgres/docs/create.md | 13 + plugins/postgres/docs/export.md | 5 + plugins/postgres/docs/upgrade.md | 30 + plugins/postgres/functions | 233 ++++ plugins/postgres/help-functions | 320 +++++ plugins/postgres/install | 69 ++ plugins/postgres/plugin.toml | 4 + plugins/postgres/post-app-clone-setup | 18 + plugins/postgres/post-app-rename-setup | 18 + plugins/postgres/pre-delete | 13 + plugins/postgres/pre-restore | 36 + plugins/postgres/pre-start | 32 + plugins/postgres/scripts/create_ssl_certs.sh | 9 + plugins/postgres/scripts/enable_ssl.sh | 12 + plugins/postgres/service-list | 20 + plugins/postgres/subcommands/app-links | 23 + plugins/postgres/subcommands/backup | 28 + plugins/postgres/subcommands/backup-auth | 36 + plugins/postgres/subcommands/backup-deauth | 23 + plugins/postgres/subcommands/backup-schedule | 31 + .../postgres/subcommands/backup-schedule-cat | 22 + .../subcommands/backup-set-encryption | 25 + .../backup-set-public-key-encryption | 25 + .../postgres/subcommands/backup-unschedule | 23 + .../subcommands/backup-unset-encryption | 23 + .../backup-unset-public-key-encryption | 23 + plugins/postgres/subcommands/clone | 53 + plugins/postgres/subcommands/connect | 24 + plugins/postgres/subcommands/create | 40 + plugins/postgres/subcommands/destroy | 58 + plugins/postgres/subcommands/enter | 27 + plugins/postgres/subcommands/exists | 22 + plugins/postgres/subcommands/export | 25 + plugins/postgres/subcommands/expose | 25 + plugins/postgres/subcommands/import | 23 + plugins/postgres/subcommands/info | 49 + plugins/postgres/subcommands/link | 61 + plugins/postgres/subcommands/linked | 26 + plugins/postgres/subcommands/links | 24 + plugins/postgres/subcommands/list | 18 + plugins/postgres/subcommands/logs | 27 + plugins/postgres/subcommands/pause | 22 + plugins/postgres/subcommands/promote | 39 + plugins/postgres/subcommands/restart | 24 + plugins/postgres/subcommands/set | 45 + plugins/postgres/subcommands/start | 22 + plugins/postgres/subcommands/stop | 22 + plugins/postgres/subcommands/unexpose | 22 + plugins/postgres/subcommands/unlink | 28 + plugins/postgres/subcommands/upgrade | 76 ++ plugins/postgres/tests/hook_pre_delete.bats | 20 + plugins/postgres/tests/link_networks.bats | 289 +++++ plugins/postgres/tests/service_clone.bats | 47 + plugins/postgres/tests/service_connect.bats | 26 + plugins/postgres/tests/service_create.bats | 27 + plugins/postgres/tests/service_destroy.bats | 30 + plugins/postgres/tests/service_export.bats | 39 + plugins/postgres/tests/service_expose.bats | 66 ++ plugins/postgres/tests/service_import.bats | 41 + plugins/postgres/tests/service_info.bats | 75 ++ plugins/postgres/tests/service_link.bats | 142 +++ plugins/postgres/tests/service_list.bats | 22 + plugins/postgres/tests/service_logs.bats | 42 + plugins/postgres/tests/service_pause.bats | 25 + plugins/postgres/tests/service_promote.bats | 62 + plugins/postgres/tests/service_restart.bats | 25 + plugins/postgres/tests/service_start.bats | 25 + plugins/postgres/tests/service_stop.bats | 25 + plugins/postgres/tests/service_unexpose.bats | 27 + plugins/postgres/tests/service_unlink.bats | 77 ++ plugins/postgres/tests/setup.sh | 33 + plugins/postgres/tests/shellcheck-exclude | 8 + plugins/postgres/tests/shellcheck-to-junit | 205 ++++ plugins/postgres/tests/test_helper.bash | 86 ++ plugins/postgres/update | 1 + plugins/redis/.devcontainer/20_init_plugin | 24 + plugins/redis/.devcontainer/Dockerfile | 26 + plugins/redis/.devcontainer/bin/copy-file | 8 + plugins/redis/.devcontainer/devcontainer.json | 16 + plugins/redis/.editorconfig | 16 + plugins/redis/.github/dependabot.yml | 12 + plugins/redis/.github/labels.yml | 57 + plugins/redis/.github/workflows/ci.yml | 100 ++ .../.github/workflows/tagged-release.yml | 20 + plugins/redis/.gitignore | 3 + plugins/redis/Dockerfile | 1 + plugins/redis/LICENSE.txt | 7 + plugins/redis/Makefile | 98 ++ plugins/redis/README.md | 774 ++++++++++++ plugins/redis/Vagrantfile | 27 + plugins/redis/bin/generate | 677 +++++++++++ plugins/redis/commands | 19 + plugins/redis/common-functions | 1056 +++++++++++++++++ plugins/redis/config | 36 + plugins/redis/docs/README.md | 4 + plugins/redis/functions | 234 ++++ plugins/redis/help-functions | 320 +++++ plugins/redis/install | 69 ++ plugins/redis/plugin.toml | 4 + plugins/redis/post-app-clone-setup | 18 + plugins/redis/post-app-rename-setup | 18 + plugins/redis/pre-delete | 13 + plugins/redis/pre-restore | 36 + plugins/redis/pre-start | 32 + plugins/redis/service-list | 20 + plugins/redis/subcommands/app-links | 23 + plugins/redis/subcommands/backup | 28 + plugins/redis/subcommands/backup-auth | 36 + plugins/redis/subcommands/backup-deauth | 23 + plugins/redis/subcommands/backup-schedule | 31 + plugins/redis/subcommands/backup-schedule-cat | 22 + .../redis/subcommands/backup-set-encryption | 25 + .../backup-set-public-key-encryption | 25 + plugins/redis/subcommands/backup-unschedule | 23 + .../redis/subcommands/backup-unset-encryption | 23 + .../backup-unset-public-key-encryption | 23 + plugins/redis/subcommands/clone | 53 + plugins/redis/subcommands/connect | 24 + plugins/redis/subcommands/create | 40 + plugins/redis/subcommands/destroy | 58 + plugins/redis/subcommands/enter | 27 + plugins/redis/subcommands/exists | 22 + plugins/redis/subcommands/export | 25 + plugins/redis/subcommands/expose | 25 + plugins/redis/subcommands/import | 23 + plugins/redis/subcommands/info | 49 + plugins/redis/subcommands/link | 61 + plugins/redis/subcommands/linked | 26 + plugins/redis/subcommands/links | 24 + plugins/redis/subcommands/list | 18 + plugins/redis/subcommands/logs | 27 + plugins/redis/subcommands/pause | 22 + plugins/redis/subcommands/promote | 39 + plugins/redis/subcommands/restart | 24 + plugins/redis/subcommands/set | 45 + plugins/redis/subcommands/start | 22 + plugins/redis/subcommands/stop | 22 + plugins/redis/subcommands/unexpose | 22 + plugins/redis/subcommands/unlink | 28 + plugins/redis/subcommands/upgrade | 76 ++ plugins/redis/tests/hook_pre_delete.bats | 20 + plugins/redis/tests/link_networks.bats | 289 +++++ plugins/redis/tests/service_clone.bats | 47 + plugins/redis/tests/service_connect.bats | 26 + plugins/redis/tests/service_create.bats | 26 + plugins/redis/tests/service_destroy.bats | 30 + plugins/redis/tests/service_export.bats | 39 + plugins/redis/tests/service_expose.bats | 66 ++ plugins/redis/tests/service_import.bats | 40 + plugins/redis/tests/service_info.bats | 75 ++ plugins/redis/tests/service_link.bats | 142 +++ plugins/redis/tests/service_list.bats | 22 + plugins/redis/tests/service_logs.bats | 42 + plugins/redis/tests/service_pause.bats | 25 + plugins/redis/tests/service_promote.bats | 63 + plugins/redis/tests/service_restart.bats | 25 + plugins/redis/tests/service_start.bats | 25 + plugins/redis/tests/service_stop.bats | 25 + plugins/redis/tests/service_unexpose.bats | 27 + plugins/redis/tests/service_unlink.bats | 77 ++ plugins/redis/tests/setup.sh | 33 + plugins/redis/tests/shellcheck-exclude | 8 + plugins/redis/tests/shellcheck-to-junit | 205 ++++ plugins/redis/tests/test_helper.bash | 86 ++ plugins/redis/update | 1 + 275 files changed, 19003 insertions(+) create mode 100755 plugins/mysql/.devcontainer/20_init_plugin create mode 100644 plugins/mysql/.devcontainer/Dockerfile create mode 100755 plugins/mysql/.devcontainer/bin/copy-file create mode 100644 plugins/mysql/.devcontainer/devcontainer.json create mode 100644 plugins/mysql/.editorconfig create mode 100644 plugins/mysql/.github/dependabot.yml create mode 100644 plugins/mysql/.github/labels.yml create mode 100644 plugins/mysql/.github/workflows/ci.yml create mode 100644 plugins/mysql/.github/workflows/tagged-release.yml create mode 100644 plugins/mysql/.gitignore create mode 100644 plugins/mysql/Dockerfile create mode 100644 plugins/mysql/LICENSE.txt create mode 100644 plugins/mysql/Makefile create mode 100644 plugins/mysql/README.md create mode 100644 plugins/mysql/Vagrantfile create mode 100755 plugins/mysql/bin/generate create mode 100755 plugins/mysql/commands create mode 100755 plugins/mysql/common-functions create mode 100644 plugins/mysql/config create mode 100644 plugins/mysql/docs/README.md create mode 100755 plugins/mysql/functions create mode 100755 plugins/mysql/help-functions create mode 100755 plugins/mysql/install create mode 100644 plugins/mysql/plugin.toml create mode 100755 plugins/mysql/post-app-clone-setup create mode 100755 plugins/mysql/post-app-rename-setup create mode 100755 plugins/mysql/pre-delete create mode 100755 plugins/mysql/pre-restore create mode 100755 plugins/mysql/pre-start create mode 100755 plugins/mysql/service-list create mode 100755 plugins/mysql/subcommands/app-links create mode 100755 plugins/mysql/subcommands/backup create mode 100755 plugins/mysql/subcommands/backup-auth create mode 100755 plugins/mysql/subcommands/backup-deauth create mode 100755 plugins/mysql/subcommands/backup-schedule create mode 100755 plugins/mysql/subcommands/backup-schedule-cat create mode 100755 plugins/mysql/subcommands/backup-set-encryption create mode 100755 plugins/mysql/subcommands/backup-set-public-key-encryption create mode 100755 plugins/mysql/subcommands/backup-unschedule create mode 100755 plugins/mysql/subcommands/backup-unset-encryption create mode 100755 plugins/mysql/subcommands/backup-unset-public-key-encryption create mode 100755 plugins/mysql/subcommands/clone create mode 100755 plugins/mysql/subcommands/connect create mode 100755 plugins/mysql/subcommands/create create mode 100755 plugins/mysql/subcommands/destroy create mode 100755 plugins/mysql/subcommands/enter create mode 100755 plugins/mysql/subcommands/exists create mode 100755 plugins/mysql/subcommands/export create mode 100755 plugins/mysql/subcommands/expose create mode 100755 plugins/mysql/subcommands/import create mode 100755 plugins/mysql/subcommands/info create mode 100755 plugins/mysql/subcommands/link create mode 100755 plugins/mysql/subcommands/linked create mode 100755 plugins/mysql/subcommands/links create mode 100755 plugins/mysql/subcommands/list create mode 100755 plugins/mysql/subcommands/logs create mode 100755 plugins/mysql/subcommands/pause create mode 100755 plugins/mysql/subcommands/promote create mode 100755 plugins/mysql/subcommands/restart create mode 100755 plugins/mysql/subcommands/set create mode 100755 plugins/mysql/subcommands/start create mode 100755 plugins/mysql/subcommands/stop create mode 100755 plugins/mysql/subcommands/unexpose create mode 100755 plugins/mysql/subcommands/unlink create mode 100755 plugins/mysql/subcommands/upgrade create mode 100755 plugins/mysql/tests/hook_pre_delete.bats create mode 100755 plugins/mysql/tests/link_networks.bats create mode 100755 plugins/mysql/tests/service_clone.bats create mode 100755 plugins/mysql/tests/service_connect.bats create mode 100755 plugins/mysql/tests/service_create.bats create mode 100755 plugins/mysql/tests/service_destroy.bats create mode 100755 plugins/mysql/tests/service_export.bats create mode 100755 plugins/mysql/tests/service_expose.bats create mode 100755 plugins/mysql/tests/service_import.bats create mode 100755 plugins/mysql/tests/service_info.bats create mode 100755 plugins/mysql/tests/service_link.bats create mode 100755 plugins/mysql/tests/service_list.bats create mode 100755 plugins/mysql/tests/service_logs.bats create mode 100755 plugins/mysql/tests/service_pause.bats create mode 100755 plugins/mysql/tests/service_promote.bats create mode 100755 plugins/mysql/tests/service_restart.bats create mode 100755 plugins/mysql/tests/service_start.bats create mode 100755 plugins/mysql/tests/service_stop.bats create mode 100755 plugins/mysql/tests/service_unexpose.bats create mode 100755 plugins/mysql/tests/service_unlink.bats create mode 100644 plugins/mysql/tests/setup.sh create mode 100644 plugins/mysql/tests/shellcheck-exclude create mode 100755 plugins/mysql/tests/shellcheck-to-junit create mode 100755 plugins/mysql/tests/test_helper.bash create mode 120000 plugins/mysql/update create mode 100755 plugins/postgres/.devcontainer/20_init_plugin create mode 100644 plugins/postgres/.devcontainer/Dockerfile create mode 100755 plugins/postgres/.devcontainer/bin/copy-file create mode 100644 plugins/postgres/.devcontainer/devcontainer.json create mode 100644 plugins/postgres/.editorconfig create mode 100644 plugins/postgres/.github/dependabot.yml create mode 100644 plugins/postgres/.github/labels.yml create mode 100644 plugins/postgres/.github/workflows/ci.yml create mode 100644 plugins/postgres/.github/workflows/tagged-release.yml create mode 100644 plugins/postgres/.gitignore create mode 100644 plugins/postgres/Dockerfile create mode 100644 plugins/postgres/LICENSE.txt create mode 100644 plugins/postgres/Makefile create mode 100644 plugins/postgres/README.md create mode 100644 plugins/postgres/Vagrantfile create mode 100755 plugins/postgres/bin/generate create mode 100755 plugins/postgres/commands create mode 100755 plugins/postgres/common-functions create mode 100644 plugins/postgres/config create mode 100644 plugins/postgres/docs/README.md create mode 100644 plugins/postgres/docs/create.md create mode 100644 plugins/postgres/docs/export.md create mode 100644 plugins/postgres/docs/upgrade.md create mode 100755 plugins/postgres/functions create mode 100755 plugins/postgres/help-functions create mode 100755 plugins/postgres/install create mode 100644 plugins/postgres/plugin.toml create mode 100755 plugins/postgres/post-app-clone-setup create mode 100755 plugins/postgres/post-app-rename-setup create mode 100755 plugins/postgres/pre-delete create mode 100755 plugins/postgres/pre-restore create mode 100755 plugins/postgres/pre-start create mode 100755 plugins/postgres/scripts/create_ssl_certs.sh create mode 100755 plugins/postgres/scripts/enable_ssl.sh create mode 100755 plugins/postgres/service-list create mode 100755 plugins/postgres/subcommands/app-links create mode 100755 plugins/postgres/subcommands/backup create mode 100755 plugins/postgres/subcommands/backup-auth create mode 100755 plugins/postgres/subcommands/backup-deauth create mode 100755 plugins/postgres/subcommands/backup-schedule create mode 100755 plugins/postgres/subcommands/backup-schedule-cat create mode 100755 plugins/postgres/subcommands/backup-set-encryption create mode 100755 plugins/postgres/subcommands/backup-set-public-key-encryption create mode 100755 plugins/postgres/subcommands/backup-unschedule create mode 100755 plugins/postgres/subcommands/backup-unset-encryption create mode 100755 plugins/postgres/subcommands/backup-unset-public-key-encryption create mode 100755 plugins/postgres/subcommands/clone create mode 100755 plugins/postgres/subcommands/connect create mode 100755 plugins/postgres/subcommands/create create mode 100755 plugins/postgres/subcommands/destroy create mode 100755 plugins/postgres/subcommands/enter create mode 100755 plugins/postgres/subcommands/exists create mode 100755 plugins/postgres/subcommands/export create mode 100755 plugins/postgres/subcommands/expose create mode 100755 plugins/postgres/subcommands/import create mode 100755 plugins/postgres/subcommands/info create mode 100755 plugins/postgres/subcommands/link create mode 100755 plugins/postgres/subcommands/linked create mode 100755 plugins/postgres/subcommands/links create mode 100755 plugins/postgres/subcommands/list create mode 100755 plugins/postgres/subcommands/logs create mode 100755 plugins/postgres/subcommands/pause create mode 100755 plugins/postgres/subcommands/promote create mode 100755 plugins/postgres/subcommands/restart create mode 100755 plugins/postgres/subcommands/set create mode 100755 plugins/postgres/subcommands/start create mode 100755 plugins/postgres/subcommands/stop create mode 100755 plugins/postgres/subcommands/unexpose create mode 100755 plugins/postgres/subcommands/unlink create mode 100755 plugins/postgres/subcommands/upgrade create mode 100755 plugins/postgres/tests/hook_pre_delete.bats create mode 100755 plugins/postgres/tests/link_networks.bats create mode 100755 plugins/postgres/tests/service_clone.bats create mode 100755 plugins/postgres/tests/service_connect.bats create mode 100755 plugins/postgres/tests/service_create.bats create mode 100755 plugins/postgres/tests/service_destroy.bats create mode 100755 plugins/postgres/tests/service_export.bats create mode 100755 plugins/postgres/tests/service_expose.bats create mode 100755 plugins/postgres/tests/service_import.bats create mode 100755 plugins/postgres/tests/service_info.bats create mode 100755 plugins/postgres/tests/service_link.bats create mode 100755 plugins/postgres/tests/service_list.bats create mode 100755 plugins/postgres/tests/service_logs.bats create mode 100755 plugins/postgres/tests/service_pause.bats create mode 100755 plugins/postgres/tests/service_promote.bats create mode 100755 plugins/postgres/tests/service_restart.bats create mode 100755 plugins/postgres/tests/service_start.bats create mode 100755 plugins/postgres/tests/service_stop.bats create mode 100755 plugins/postgres/tests/service_unexpose.bats create mode 100755 plugins/postgres/tests/service_unlink.bats create mode 100644 plugins/postgres/tests/setup.sh create mode 100644 plugins/postgres/tests/shellcheck-exclude create mode 100755 plugins/postgres/tests/shellcheck-to-junit create mode 100755 plugins/postgres/tests/test_helper.bash create mode 120000 plugins/postgres/update create mode 100755 plugins/redis/.devcontainer/20_init_plugin create mode 100644 plugins/redis/.devcontainer/Dockerfile create mode 100755 plugins/redis/.devcontainer/bin/copy-file create mode 100644 plugins/redis/.devcontainer/devcontainer.json create mode 100644 plugins/redis/.editorconfig create mode 100644 plugins/redis/.github/dependabot.yml create mode 100644 plugins/redis/.github/labels.yml create mode 100644 plugins/redis/.github/workflows/ci.yml create mode 100644 plugins/redis/.github/workflows/tagged-release.yml create mode 100644 plugins/redis/.gitignore create mode 100644 plugins/redis/Dockerfile create mode 100644 plugins/redis/LICENSE.txt create mode 100644 plugins/redis/Makefile create mode 100644 plugins/redis/README.md create mode 100644 plugins/redis/Vagrantfile create mode 100755 plugins/redis/bin/generate create mode 100755 plugins/redis/commands create mode 100755 plugins/redis/common-functions create mode 100755 plugins/redis/config create mode 100644 plugins/redis/docs/README.md create mode 100755 plugins/redis/functions create mode 100755 plugins/redis/help-functions create mode 100755 plugins/redis/install create mode 100644 plugins/redis/plugin.toml create mode 100755 plugins/redis/post-app-clone-setup create mode 100755 plugins/redis/post-app-rename-setup create mode 100755 plugins/redis/pre-delete create mode 100755 plugins/redis/pre-restore create mode 100755 plugins/redis/pre-start create mode 100755 plugins/redis/service-list create mode 100755 plugins/redis/subcommands/app-links create mode 100755 plugins/redis/subcommands/backup create mode 100755 plugins/redis/subcommands/backup-auth create mode 100755 plugins/redis/subcommands/backup-deauth create mode 100755 plugins/redis/subcommands/backup-schedule create mode 100755 plugins/redis/subcommands/backup-schedule-cat create mode 100755 plugins/redis/subcommands/backup-set-encryption create mode 100755 plugins/redis/subcommands/backup-set-public-key-encryption create mode 100755 plugins/redis/subcommands/backup-unschedule create mode 100755 plugins/redis/subcommands/backup-unset-encryption create mode 100755 plugins/redis/subcommands/backup-unset-public-key-encryption create mode 100755 plugins/redis/subcommands/clone create mode 100755 plugins/redis/subcommands/connect create mode 100755 plugins/redis/subcommands/create create mode 100755 plugins/redis/subcommands/destroy create mode 100755 plugins/redis/subcommands/enter create mode 100755 plugins/redis/subcommands/exists create mode 100755 plugins/redis/subcommands/export create mode 100755 plugins/redis/subcommands/expose create mode 100755 plugins/redis/subcommands/import create mode 100755 plugins/redis/subcommands/info create mode 100755 plugins/redis/subcommands/link create mode 100755 plugins/redis/subcommands/linked create mode 100755 plugins/redis/subcommands/links create mode 100755 plugins/redis/subcommands/list create mode 100755 plugins/redis/subcommands/logs create mode 100755 plugins/redis/subcommands/pause create mode 100755 plugins/redis/subcommands/promote create mode 100755 plugins/redis/subcommands/restart create mode 100755 plugins/redis/subcommands/set create mode 100755 plugins/redis/subcommands/start create mode 100755 plugins/redis/subcommands/stop create mode 100755 plugins/redis/subcommands/unexpose create mode 100755 plugins/redis/subcommands/unlink create mode 100755 plugins/redis/subcommands/upgrade create mode 100755 plugins/redis/tests/hook_pre_delete.bats create mode 100755 plugins/redis/tests/link_networks.bats create mode 100755 plugins/redis/tests/service_clone.bats create mode 100755 plugins/redis/tests/service_connect.bats create mode 100755 plugins/redis/tests/service_create.bats create mode 100755 plugins/redis/tests/service_destroy.bats create mode 100755 plugins/redis/tests/service_export.bats create mode 100755 plugins/redis/tests/service_expose.bats create mode 100755 plugins/redis/tests/service_import.bats create mode 100755 plugins/redis/tests/service_info.bats create mode 100755 plugins/redis/tests/service_link.bats create mode 100755 plugins/redis/tests/service_list.bats create mode 100755 plugins/redis/tests/service_logs.bats create mode 100755 plugins/redis/tests/service_pause.bats create mode 100755 plugins/redis/tests/service_promote.bats create mode 100755 plugins/redis/tests/service_restart.bats create mode 100755 plugins/redis/tests/service_start.bats create mode 100755 plugins/redis/tests/service_stop.bats create mode 100755 plugins/redis/tests/service_unexpose.bats create mode 100755 plugins/redis/tests/service_unlink.bats create mode 100644 plugins/redis/tests/setup.sh create mode 100644 plugins/redis/tests/shellcheck-exclude create mode 100755 plugins/redis/tests/shellcheck-to-junit create mode 100755 plugins/redis/tests/test_helper.bash create mode 120000 plugins/redis/update diff --git a/plugins/mysql/.devcontainer/20_init_plugin b/plugins/mysql/.devcontainer/20_init_plugin new file mode 100755 index 000000000..55205064a --- /dev/null +++ b/plugins/mysql/.devcontainer/20_init_plugin @@ -0,0 +1,24 @@ +#!/bin/bash +set -eo pipefail + +log-info() { + declare desc="Log info formatter" + echo " $*" 1>&2 +} + +log-fail() { + declare desc="Log fail formatter" + echo "! $*" 1>&2 + exit 1 +} + +main() { + dokku plugin:install + + # built in the Dockerfile + PLUGIN_NAME="$(source /tmp/.env && echo "$PLUGIN_NAME")" + PLUGIN_VARIABLE="$(source /tmp/.env && echo "$PLUGIN_VARIABLE")" + echo "export ${PLUGIN_VARIABLE}_HOST_ROOT=${SERVICE_HOST_ROOT}/$PLUGIN_NAME" >/etc/default/dokku +} + +main "$@" diff --git a/plugins/mysql/.devcontainer/Dockerfile b/plugins/mysql/.devcontainer/Dockerfile new file mode 100644 index 000000000..8a6e852f5 --- /dev/null +++ b/plugins/mysql/.devcontainer/Dockerfile @@ -0,0 +1,26 @@ +FROM dokku/dokku:latest + +RUN apt-get update +RUN apt-get install --no-install-recommends -y build-essential file nano && \ + apt-get clean autoclean && \ + apt-get autoremove --yes && \ + rm -rf /var/lib/apt/lists/* && \ + mkdir -p /mnt/dokku/home/dokku /mnt/dokku/var/lib/dokku/config /mnt/dokku/var/lib/dokku/data /mnt/dokku/var/lib/dokku/services && \ + chown -R dokku:dokku /mnt/dokku/home/dokku /mnt/dokku/var/lib/dokku/config /mnt/dokku/var/lib/dokku/data /mnt/dokku/var/lib/dokku/services && \ + echo "dokku.me" > /home/dokku/VHOST + +ADD https://raw.githubusercontent.com/dokku/dokku/master/tests/dhparam.pem /mnt/dokku/etc/nginx/dhparam.pem + +COPY .devcontainer/20_init_plugin /etc/my_init.d/20_init_plugin +COPY .devcontainer/bin/ /usr/local/bin/ + +COPY . . + +RUN source /tmp/config && \ + echo "export ${PLUGIN_DISABLE_PULL_VARIABLE}=true" > /tmp/.env && \ + echo "export PLUGIN_NAME=${PLUGIN_COMMAND_PREFIX}" >> /tmp/.env && \ + echo "export PLUGIN_VARIABLE=${PLUGIN_VARIABLE}" >> /tmp/.env + +RUN source /tmp/.env && \ + dokku plugin:install file:///tmp --name $PLUGIN_NAME && \ + make ci-dependencies diff --git a/plugins/mysql/.devcontainer/bin/copy-file b/plugins/mysql/.devcontainer/bin/copy-file new file mode 100755 index 000000000..a97030753 --- /dev/null +++ b/plugins/mysql/.devcontainer/bin/copy-file @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +main() { + PLUGIN_NAME="$(source /tmp/.env && echo "$PLUGIN_NAME")" + cp "$1" "/var/lib/dokku/plugins/enabled/$PLUGIN_NAME/$1" +} + +main "$@" diff --git a/plugins/mysql/.devcontainer/devcontainer.json b/plugins/mysql/.devcontainer/devcontainer.json new file mode 100644 index 000000000..430d2f138 --- /dev/null +++ b/plugins/mysql/.devcontainer/devcontainer.json @@ -0,0 +1,16 @@ +{ + "build": { + "dockerfile": "Dockerfile", + "context": ".." + }, + "containerEnv": { + "SERVICE_HOST_ROOT": "${localWorkspaceFolder}/tmp/data" + }, + "initializeCommand": ["mkdir", "-p", "tmp/data"], + "mounts": [ + "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind", + "source=${localWorkspaceFolder}/tmp/data/,target=/var/lib/dokku/services/,type=bind" + ], + "overrideCommand": false, + "runArgs": ["--init"] +} diff --git a/plugins/mysql/.editorconfig b/plugins/mysql/.editorconfig new file mode 100644 index 000000000..514928225 --- /dev/null +++ b/plugins/mysql/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +insert_final_newline = true +indent_style = space +indent_size = 2 + +[Makefile] +insert_final_newline = true +indent_style = tab +indent_size = 4 + +[*.mk] +insert_final_newline = true +indent_style = tab +indent_size = 4 diff --git a/plugins/mysql/.github/dependabot.yml b/plugins/mysql/.github/dependabot.yml new file mode 100644 index 000000000..9d4798567 --- /dev/null +++ b/plugins/mysql/.github/dependabot.yml @@ -0,0 +1,12 @@ +--- +version: 2 +updates: + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/plugins/mysql/.github/labels.yml b/plugins/mysql/.github/labels.yml new file mode 100644 index 000000000..0ccbb96e5 --- /dev/null +++ b/plugins/mysql/.github/labels.yml @@ -0,0 +1,57 @@ +labels: +- name: bc-break + color: eb6420 +- name: blocks release + color: "000000" +- name: 'difficulty: easy' + color: c5def5 +- name: 'difficulty: hard' + color: e99695 +- name: 'difficulty: medium' + color: fef2c0 +- name: hacktoberfest + color: b0581d +- name: 'needs: documentation' + color: c2e0c6 +- name: 'needs: more info' + color: c2e0c6 +- name: 'needs: rebase' + color: c2e0c6 +- name: 'needs: tests' + color: c2e0c6 +- name: 'status: duplicate' + color: cccccc +- name: 'status: fix-provided' + color: c5def5 +- name: 'status: future' + color: c5def5 +- name: 'status: has plan' + color: c5def5 +- name: 'status: invalid' + color: cccccc +- name: 'status: merge for next minor' + color: c5def5 +- name: 'status: merge for next patch' + color: c5def5 +- name: 'status: wontfix' + color: cccccc +- name: 'type: bug' + color: e01b1b +- name: 'type: documentation' + color: 0052cc +- name: 'type: enhancement' + color: 09ab3c +- name: 'type: question' + color: cc317c +- name: 'type: refactor' + color: 0052cc +- name: 'type: rfc' + color: 0052cc +- name: 'type: roadmap' + color: 0052cc +- name: 'type: service' + color: "5319e7" +- name: 'type: support' + color: cc317c +- name: 'type: tests' + color: 0052cc diff --git a/plugins/mysql/.github/workflows/ci.yml b/plugins/mysql/.github/workflows/ci.yml new file mode 100644 index 000000000..2cccc934b --- /dev/null +++ b/plugins/mysql/.github/workflows/ci.yml @@ -0,0 +1,100 @@ +--- +name: CI + +# yamllint disable-line rule:truthy +on: + pull_request: + branches: + - "*" + push: + branches: + - master + +concurrency: + group: build-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + unit-tests-master: + name: unit-tests + runs-on: ubuntu-24.04 + env: + DOKKU_VERSION: master + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - run: make setup + + - run: sudo sysctl -w vm.max_map_count=262144 + + - run: | + git fetch -q origin master + changed=$(git --no-pager diff --name-only $GITHUB_SHA..origin/master) + if [ $changed = "Dockerfile" ]; then + echo "Please run 'make generate' to update the image version in the README.md" + else + make generate + if ! git diff --quiet README.md; then + echo "Please run 'make generate'" + git status --short + git --no-pager diff README.md + exit 1 + fi + fi + + - run: make test + + - uses: actions/upload-artifact@v5 + if: failure() + with: + name: tmp/test-results + path: test-results + + unit-tests-0_19_0: + name: unit-tests-0.19.0 + runs-on: ubuntu-24.04 + env: + DOKKU_TAG: v0.19.0 + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - run: make setup + + - run: sudo sysctl -w vm.max_map_count=262144 + + - run: | + git fetch -q origin master + changed=$(git --no-pager diff --name-only $GITHUB_SHA..origin/master) + if [ $changed = "Dockerfile" ]; then + echo "Please run 'make generate' to update the image version in the README.md" + else + make generate + if ! git diff --quiet README.md; then + echo "Please run 'make generate'" + git status --short + git --no-pager diff README.md + exit 1 + fi + fi + + - run: make test + + - uses: actions/upload-artifact@v5 + if: failure() + with: + name: tmp/test-results + path: test-results diff --git a/plugins/mysql/.github/workflows/tagged-release.yml b/plugins/mysql/.github/workflows/tagged-release.yml new file mode 100644 index 000000000..de1658121 --- /dev/null +++ b/plugins/mysql/.github/workflows/tagged-release.yml @@ -0,0 +1,20 @@ +--- +name: "tagged-release" + +# yamllint disable-line rule:truthy +on: + push: + tags: + - "*" + +jobs: + tagged-release: + name: tagged-release + runs-on: ubuntu-24.04 + + steps: + - name: Release + uses: softprops/action-gh-release@v2.4.1 + with: + generate_release_notes: true + make_latest: "true" diff --git a/plugins/mysql/.gitignore b/plugins/mysql/.gitignore new file mode 100644 index 000000000..dd7b12a23 --- /dev/null +++ b/plugins/mysql/.gitignore @@ -0,0 +1,3 @@ +/tmp +.vagrant +bootstrap.sh diff --git a/plugins/mysql/Dockerfile b/plugins/mysql/Dockerfile new file mode 100644 index 000000000..483babb3a --- /dev/null +++ b/plugins/mysql/Dockerfile @@ -0,0 +1 @@ +FROM mysql:9.5.0 diff --git a/plugins/mysql/LICENSE.txt b/plugins/mysql/LICENSE.txt new file mode 100644 index 000000000..d1e7e803c --- /dev/null +++ b/plugins/mysql/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright (C) 2020 Jose Diaz-Gonzalez + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/mysql/Makefile b/plugins/mysql/Makefile new file mode 100644 index 000000000..d14111ab9 --- /dev/null +++ b/plugins/mysql/Makefile @@ -0,0 +1,98 @@ +HARDWARE = $(shell uname -m) +SYSTEM_NAME = $(shell uname -s | tr '[:upper:]' '[:lower:]') +ARCH = $(shell dpkg --print-architecture) +SHFMT_VERSION = 3.0.2 +XUNIT_TO_GITHUB_VERSION = 0.3.0 +XUNIT_READER_VERSION = 0.1.0 + + +bats: +ifeq ($(SYSTEM_NAME),darwin) +ifneq ($(shell bats --version >/dev/null 2>&1 ; echo $$?),0) + brew install bats-core +endif +else + git clone https://github.com/bats-core/bats-core.git /tmp/bats + cd /tmp/bats && sudo ./install.sh /usr/local + rm -rf /tmp/bats +endif + +shellcheck: +ifneq ($(shell shellcheck --version >/dev/null 2>&1 ; echo $$?),0) +ifeq ($(SYSTEM_NAME),darwin) + brew install shellcheck +else +ifeq ($(ARCH),arm64) + sudo add-apt-repository 'deb http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse' +else + sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu jammy-backports main restricted universe multiverse' +endif + sudo rm -rf /var/lib/apt/lists/* && sudo apt-get clean + sudo apt-get update -qq && sudo apt-get install -qq -y shellcheck +endif +endif + +shfmt: +ifneq ($(shell shfmt --version >/dev/null 2>&1 ; echo $$?),0) +ifeq ($(shfmt),Darwin) + brew install shfmt +else + wget -qO /tmp/shfmt https://github.com/mvdan/sh/releases/download/v$(SHFMT_VERSION)/shfmt_v$(SHFMT_VERSION)_linux_amd64 + chmod +x /tmp/shfmt + sudo mv /tmp/shfmt /usr/local/bin/shfmt +endif +endif + +readlink: +ifeq ($(shell uname),Darwin) +ifeq ($(shell greadlink > /dev/null 2>&1 ; echo $$?),127) + brew install coreutils +endif + ln -nfs `which greadlink` tests/bin/readlink +endif + +ci-dependencies: shellcheck bats readlink + +lint-setup: + @mkdir -p tmp/test-results/shellcheck tmp/shellcheck + @find . -not -path '*/\.*' -type f | xargs file | grep text | awk -F ':' '{ print $$1 }' | xargs head -n1 | egrep -B1 "bash" | grep "==>" | awk '{ print $$2 }' > tmp/shellcheck/test-files + @cat tests/shellcheck-exclude | sed -n -e '/^# SC/p' | cut -d' ' -f2 | paste -d, -s - > tmp/shellcheck/exclude + +lint: lint-setup + # these are disabled due to their expansive existence in the codebase. we should clean it up though + @cat tests/shellcheck-exclude | sed -n -e '/^# SC/p' + @echo linting... + @cat tmp/shellcheck/test-files | xargs shellcheck -e $(shell cat tmp/shellcheck/exclude) | tests/shellcheck-to-junit --output tmp/test-results/shellcheck/results.xml --files tmp/shellcheck/test-files --exclude $(shell cat tmp/shellcheck/exclude) + +unit-tests: + @echo running unit tests... + @mkdir -p tmp/test-results/bats + @cd tests && echo "executing tests: $(shell cd tests ; ls *.bats | xargs)" + cd tests && bats --report-formatter junit --timing -o ../tmp/test-results/bats *.bats + +tmp/xunit-reader: + mkdir -p tmp + curl -o tmp/xunit-reader.tgz -sL https://github.com/josegonzalez/go-xunit-reader/releases/download/v$(XUNIT_READER_VERSION)/xunit-reader_$(XUNIT_READER_VERSION)_$(SYSTEM_NAME)_$(HARDWARE).tgz + tar xf tmp/xunit-reader.tgz -C tmp + chmod +x tmp/xunit-reader + +setup: + bash tests/setup.sh + $(MAKE) ci-dependencies + +test: lint unit-tests + +report: tmp/xunit-reader + tmp/xunit-reader -p 'tmp/test-results/bats/*.xml' + tmp/xunit-reader -p 'tmp/test-results/shellcheck/*.xml' + +.PHONY: clean +clean: + rm -f README.md + +.PHONY: generate +generate: clean README.md + +.PHONY: README.md +README.md: + bin/generate diff --git a/plugins/mysql/README.md b/plugins/mysql/README.md new file mode 100644 index 000000000..e4e982d5b --- /dev/null +++ b/plugins/mysql/README.md @@ -0,0 +1,774 @@ +# dokku mysql [![Build Status](https://img.shields.io/github/actions/workflow/status/dokku/dokku-mysql/ci.yml?branch=master&style=flat-square "Build Status")](https://github.com/dokku/dokku-mysql/actions/workflows/ci.yml?query=branch%3Amaster) [![IRC Network](https://img.shields.io/badge/irc-libera-blue.svg?style=flat-square "IRC Libera")](https://webchat.libera.chat/?channels=dokku) + +Official mysql plugin for dokku. Currently defaults to installing [mysql 9.5.0](https://hub.docker.com/_/mysql/). + +## Requirements + +- dokku 0.19.x+ +- docker 1.8.x + +## Installation + +```shell +# on 0.19.x+ +sudo dokku plugin:install https://github.com/dokku/dokku-mysql.git --name mysql +``` + +## Commands + +``` +mysql:app-links # list all mysql service links for a given app +mysql:backup [--use-iam] # create a backup of the mysql service to an existing s3 bucket +mysql:backup-auth # set up authentication for backups on the mysql service +mysql:backup-deauth # remove backup authentication for the mysql service +mysql:backup-schedule [--use-iam] # schedule a backup of the mysql service +mysql:backup-schedule-cat # cat the contents of the configured backup cronfile for the service +mysql:backup-set-encryption # set encryption for all future backups of mysql service +mysql:backup-set-public-key-encryption # set GPG Public Key encryption for all future backups of mysql service +mysql:backup-unschedule # unschedule the backup of the mysql service +mysql:backup-unset-encryption # unset encryption for future backups of the mysql service +mysql:backup-unset-public-key-encryption # unset GPG Public Key encryption for future backups of the mysql service +mysql:clone [--clone-flags...] # create container then copy data from into +mysql:connect # connect to the service via the mysql connection tool +mysql:create [--create-flags...] # create a mysql service +mysql:destroy [-f|--force] # delete the mysql service/data/container if there are no links left +mysql:enter # enter or run a command in a running mysql service container +mysql:exists # check if the mysql service exists +mysql:export # export a dump of the mysql service database +mysql:expose # expose a mysql service on custom host:port if provided (random port on the 0.0.0.0 interface if otherwise unspecified) +mysql:import # import a dump into the mysql service database +mysql:info [--single-info-flag] # print the service information +mysql:link [--link-flags...] # link the mysql service to the app +mysql:linked # check if the mysql service is linked to an app +mysql:links # list all apps linked to the mysql service +mysql:list # list all mysql services +mysql:logs [-t|--tail] # print the most recent log(s) for this service +mysql:pause # pause a running mysql service +mysql:promote # promote service as DATABASE_URL in +mysql:restart # graceful shutdown and restart of the mysql service container +mysql:set # set or clear a property for a service +mysql:start # start a previously stopped mysql service +mysql:stop # stop a running mysql service +mysql:unexpose # unexpose a previously exposed mysql service +mysql:unlink # unlink the mysql service from the app +mysql:upgrade [--upgrade-flags...] # upgrade service to the specified versions +``` + +## Usage + +Help for any commands can be displayed by specifying the command as an argument to mysql:help. Plugin help output in conjunction with any files in the `docs/` folder is used to generate the plugin documentation. Please consult the `mysql:help` command for any undocumented commands. + +### Basic Usage + +### create a mysql service + +```shell +# usage +dokku mysql:create [--create-flags...] +``` + +flags: + +- `-c|--config-options "--args --go=here"`: extra arguments to pass to the container create command (default: `None`) +- `-C|--custom-env "USER=alpha;HOST=beta"`: semi-colon delimited environment variables to start the service with +- `-i|--image IMAGE`: the image name to start the service with +- `-I|--image-version IMAGE_VERSION`: the image version to start the service with +- `-m|--memory MEMORY`: container memory limit in megabytes (default: unlimited) +- `-N|--initial-network INITIAL_NETWORK`: the initial network to attach the service to +- `-p|--password PASSWORD`: override the user-level service password +- `-P|--post-create-network NETWORKS`: a comma-separated list of networks to attach the service container to after service creation +- `-r|--root-password PASSWORD`: override the root-level service password +- `-S|--post-start-network NETWORKS`: a comma-separated list of networks to attach the service container to after service start +- `-s|--shm-size SHM_SIZE`: override shared memory size for mysql docker container + +Create a mysql service named lollipop: + +```shell +dokku mysql:create lollipop +``` + +You can also specify the image and image version to use for the service. It *must* be compatible with the mysql image. + +```shell +export MYSQL_IMAGE="mysql" +export MYSQL_IMAGE_VERSION="${PLUGIN_IMAGE_VERSION}" +dokku mysql:create lollipop +``` + +You can also specify custom environment variables to start the mysql service in semicolon-separated form. + +```shell +export MYSQL_CUSTOM_ENV="USER=alpha;HOST=beta" +dokku mysql:create lollipop +``` + +### print the service information + +```shell +# usage +dokku mysql:info [--single-info-flag] +``` + +flags: + +- `--config-dir`: show the service configuration directory +- `--data-dir`: show the service data directory +- `--dsn`: show the service DSN +- `--exposed-ports`: show service exposed ports +- `--id`: show the service container id +- `--internal-ip`: show the service internal ip +- `--initial-network`: show the initial network being connected to +- `--links`: show the service app links +- `--post-create-network`: show the networks to attach to after service container creation +- `--post-start-network`: show the networks to attach to after service container start +- `--service-root`: show the service root directory +- `--status`: show the service running status +- `--version`: show the service image version + +Get connection information as follows: + +```shell +dokku mysql:info lollipop +``` + +You can also retrieve a specific piece of service info via flags: + +```shell +dokku mysql:info lollipop --config-dir +dokku mysql:info lollipop --data-dir +dokku mysql:info lollipop --dsn +dokku mysql:info lollipop --exposed-ports +dokku mysql:info lollipop --id +dokku mysql:info lollipop --internal-ip +dokku mysql:info lollipop --initial-network +dokku mysql:info lollipop --links +dokku mysql:info lollipop --post-create-network +dokku mysql:info lollipop --post-start-network +dokku mysql:info lollipop --service-root +dokku mysql:info lollipop --status +dokku mysql:info lollipop --version +``` + +### list all mysql services + +```shell +# usage +dokku mysql:list +``` + +List all services: + +```shell +dokku mysql:list +``` + +### print the most recent log(s) for this service + +```shell +# usage +dokku mysql:logs [-t|--tail] +``` + +flags: + +- `-t|--tail []`: do not stop when end of the logs are reached and wait for additional output + +You can tail logs for a particular service: + +```shell +dokku mysql:logs lollipop +``` + +By default, logs will not be tailed, but you can do this with the --tail flag: + +```shell +dokku mysql:logs lollipop --tail +``` + +The default tail setting is to show all logs, but an initial count can also be specified: + +```shell +dokku mysql:logs lollipop --tail 5 +``` + +### link the mysql service to the app + +```shell +# usage +dokku mysql:link [--link-flags...] +``` + +flags: + +- `-a|--alias "BLUE_DATABASE"`: an alternative alias to use for linking to an app via environment variable +- `-q|--querystring "pool=5"`: ampersand delimited querystring arguments to append to the service link +- `-n|--no-restart "false"`: whether or not to restart the app on link (default: true) + +A mysql service can be linked to a container. This will use native docker links via the docker-options plugin. Here we link it to our `playground` app. + +> NOTE: this will restart your app + +```shell +dokku mysql:link lollipop playground +``` + +The following environment variables will be set automatically by docker (not on the app itself, so they won’t be listed when calling dokku config): + +``` +DOKKU_MYSQL_LOLLIPOP_NAME=/lollipop/DATABASE +DOKKU_MYSQL_LOLLIPOP_PORT=tcp://172.17.0.1:3306 +DOKKU_MYSQL_LOLLIPOP_PORT_3306_TCP=tcp://172.17.0.1:3306 +DOKKU_MYSQL_LOLLIPOP_PORT_3306_TCP_PROTO=tcp +DOKKU_MYSQL_LOLLIPOP_PORT_3306_TCP_PORT=3306 +DOKKU_MYSQL_LOLLIPOP_PORT_3306_TCP_ADDR=172.17.0.1 +``` + +The following will be set on the linked application by default: + +``` +DATABASE_URL=mysql://mysql:SOME_PASSWORD@dokku-mysql-lollipop:3306/lollipop +``` + +The host exposed here only works internally in docker containers. If you want your container to be reachable from outside, you should use the `expose` subcommand. Another service can be linked to your app: + +```shell +dokku mysql:link other_service playground +``` + +It is possible to change the protocol for `DATABASE_URL` by setting the environment variable `MYSQL_DATABASE_SCHEME` on the app. Doing so will after linking will cause the plugin to think the service is not linked, and we advise you to unlink before proceeding. + +```shell +dokku config:set playground MYSQL_DATABASE_SCHEME=mysql2 +dokku mysql:link lollipop playground +``` + +This will cause `DATABASE_URL` to be set as: + +``` +mysql2://mysql:SOME_PASSWORD@dokku-mysql-lollipop:3306/lollipop +``` + +### unlink the mysql service from the app + +```shell +# usage +dokku mysql:unlink +``` + +flags: + +- `-n|--no-restart "false"`: whether or not to restart the app on unlink (default: true) + +You can unlink a mysql service: + +> NOTE: this will restart your app and unset related environment variables + +```shell +dokku mysql:unlink lollipop playground +``` + +### set or clear a property for a service + +```shell +# usage +dokku mysql:set +``` + +Set the network to attach after the service container is started: + +```shell +dokku mysql:set lollipop post-create-network custom-network +``` + +Set multiple networks: + +```shell +dokku mysql:set lollipop post-create-network custom-network,other-network +``` + +Unset the post-create-network value: + +```shell +dokku mysql:set lollipop post-create-network +``` + +### Service Lifecycle + +The lifecycle of each service can be managed through the following commands: + +### connect to the service via the mysql connection tool + +```shell +# usage +dokku mysql:connect +``` + +Connect to the service via the mysql connection tool: + +> NOTE: disconnecting from ssh while running this command may leave zombie processes due to moby/moby#9098 + +```shell +dokku mysql:connect lollipop +``` + +### enter or run a command in a running mysql service container + +```shell +# usage +dokku mysql:enter +``` + +A bash prompt can be opened against a running service. Filesystem changes will not be saved to disk. + +> NOTE: disconnecting from ssh while running this command may leave zombie processes due to moby/moby#9098 + +```shell +dokku mysql:enter lollipop +``` + +You may also run a command directly against the service. Filesystem changes will not be saved to disk. + +```shell +dokku mysql:enter lollipop touch /tmp/test +``` + +### expose a mysql service on custom host:port if provided (random port on the 0.0.0.0 interface if otherwise unspecified) + +```shell +# usage +dokku mysql:expose +``` + +Expose the service on the service's normal ports, allowing access to it from the public interface (`0.0.0.0`): + +```shell +dokku mysql:expose lollipop 3306 +``` + +Expose the service on the service's normal ports, with the first on a specified ip address (127.0.0.1): + +```shell +dokku mysql:expose lollipop 127.0.0.1:3306 +``` + +### unexpose a previously exposed mysql service + +```shell +# usage +dokku mysql:unexpose +``` + +Unexpose the service, removing access to it from the public interface (`0.0.0.0`): + +```shell +dokku mysql:unexpose lollipop +``` + +### promote service as DATABASE_URL in + +```shell +# usage +dokku mysql:promote +``` + +If you have a mysql service linked to an app and try to link another mysql service another link environment variable will be generated automatically: + +``` +DOKKU_DATABASE_BLUE_URL=mysql://other_service:ANOTHER_PASSWORD@dokku-mysql-other-service:3306/other_service +``` + +You can promote the new service to be the primary one: + +> NOTE: this will restart your app + +```shell +dokku mysql:promote other_service playground +``` + +This will replace `DATABASE_URL` with the url from other_service and generate another environment variable to hold the previous value if necessary. You could end up with the following for example: + +``` +DATABASE_URL=mysql://other_service:ANOTHER_PASSWORD@dokku-mysql-other-service:3306/other_service +DOKKU_DATABASE_BLUE_URL=mysql://other_service:ANOTHER_PASSWORD@dokku-mysql-other-service:3306/other_service +DOKKU_DATABASE_SILVER_URL=mysql://lollipop:SOME_PASSWORD@dokku-mysql-lollipop:3306/lollipop +``` + +### start a previously stopped mysql service + +```shell +# usage +dokku mysql:start +``` + +Start the service: + +```shell +dokku mysql:start lollipop +``` + +### stop a running mysql service + +```shell +# usage +dokku mysql:stop +``` + +Stop the service and removes the running container: + +```shell +dokku mysql:stop lollipop +``` + +### pause a running mysql service + +```shell +# usage +dokku mysql:pause +``` + +Pause the running container for the service: + +```shell +dokku mysql:pause lollipop +``` + +### graceful shutdown and restart of the mysql service container + +```shell +# usage +dokku mysql:restart +``` + +Restart the service: + +```shell +dokku mysql:restart lollipop +``` + +### upgrade service to the specified versions + +```shell +# usage +dokku mysql:upgrade [--upgrade-flags...] +``` + +flags: + +- `-c|--config-options "--args --go=here"`: extra arguments to pass to the container create command (default: `None`) +- `-C|--custom-env "USER=alpha;HOST=beta"`: semi-colon delimited environment variables to start the service with +- `-i|--image IMAGE`: the image name to start the service with +- `-I|--image-version IMAGE_VERSION`: the image version to start the service with +- `-N|--initial-network INITIAL_NETWORK`: the initial network to attach the service to +- `-P|--post-create-network NETWORKS`: a comma-separated list of networks to attach the service container to after service creation +- `-R|--restart-apps "true"`: whether or not to force an app restart (default: false) +- `-S|--post-start-network NETWORKS`: a comma-separated list of networks to attach the service container to after service start +- `-s|--shm-size SHM_SIZE`: override shared memory size for mysql docker container + +You can upgrade an existing service to a new image or image-version: + +```shell +dokku mysql:upgrade lollipop +``` + +### Service Automation + +Service scripting can be executed using the following commands: + +### list all mysql service links for a given app + +```shell +# usage +dokku mysql:app-links +``` + +List all mysql services that are linked to the `playground` app. + +```shell +dokku mysql:app-links playground +``` + +### create container then copy data from into + +```shell +# usage +dokku mysql:clone [--clone-flags...] +``` + +flags: + +- `-c|--config-options "--args --go=here"`: extra arguments to pass to the container create command (default: `None`) +- `-C|--custom-env "USER=alpha;HOST=beta"`: semi-colon delimited environment variables to start the service with +- `-i|--image IMAGE`: the image name to start the service with +- `-I|--image-version IMAGE_VERSION`: the image version to start the service with +- `-m|--memory MEMORY`: container memory limit in megabytes (default: unlimited) +- `-N|--initial-network INITIAL_NETWORK`: the initial network to attach the service to +- `-p|--password PASSWORD`: override the user-level service password +- `-P|--post-create-network NETWORKS`: a comma-separated list of networks to attach the service container to after service creation +- `-r|--root-password PASSWORD`: override the root-level service password +- `-S|--post-start-network NETWORKS`: a comma-separated list of networks to attach the service container to after service start +- `-s|--shm-size SHM_SIZE`: override shared memory size for mysql docker container + +You can clone an existing service to a new one: + +```shell +dokku mysql:clone lollipop lollipop-2 +``` + +### check if the mysql service exists + +```shell +# usage +dokku mysql:exists +``` + +Here we check if the lollipop mysql service exists. + +```shell +dokku mysql:exists lollipop +``` + +### check if the mysql service is linked to an app + +```shell +# usage +dokku mysql:linked +``` + +Here we check if the lollipop mysql service is linked to the `playground` app. + +```shell +dokku mysql:linked lollipop playground +``` + +### list all apps linked to the mysql service + +```shell +# usage +dokku mysql:links +``` + +List all apps linked to the `lollipop` mysql service. + +```shell +dokku mysql:links lollipop +``` + +### Data Management + +The underlying service data can be imported and exported with the following commands: + +### import a dump into the mysql service database + +```shell +# usage +dokku mysql:import +``` + +Import a datastore dump: + +```shell +dokku mysql:import lollipop < data.dump +``` + +### export a dump of the mysql service database + +```shell +# usage +dokku mysql:export +``` + +By default, datastore output is exported to stdout: + +```shell +dokku mysql:export lollipop +``` + +You can redirect this output to a file: + +```shell +dokku mysql:export lollipop > data.dump +``` + +### Backups + +Datastore backups are supported via AWS S3 and S3 compatible services like [minio](https://github.com/minio/minio). + +You may skip the `backup-auth` step if your dokku install is running within EC2 and has access to the bucket via an IAM profile. In that case, use the `--use-iam` option with the `backup` command. + +Backups can be performed using the backup commands: + +### set up authentication for backups on the mysql service + +```shell +# usage +dokku mysql:backup-auth +``` + +Setup s3 backup authentication: + +```shell +dokku mysql:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY +``` + +Setup s3 backup authentication with different region: + +```shell +dokku mysql:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION +``` + +Setup s3 backup authentication with different signature version and endpoint: + +```shell +dokku mysql:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION AWS_SIGNATURE_VERSION ENDPOINT_URL +``` + +More specific example for minio auth: + +```shell +dokku mysql:backup-auth lollipop MINIO_ACCESS_KEY_ID MINIO_SECRET_ACCESS_KEY us-east-1 s3v4 https://YOURMINIOSERVICE +``` + +### remove backup authentication for the mysql service + +```shell +# usage +dokku mysql:backup-deauth +``` + +Remove s3 authentication: + +```shell +dokku mysql:backup-deauth lollipop +``` + +### create a backup of the mysql service to an existing s3 bucket + +```shell +# usage +dokku mysql:backup [--use-iam] +``` + +flags: + +- `-u|--use-iam`: use the IAM profile associated with the current server + +Backup the `lollipop` service to the `my-s3-bucket` bucket on `AWS`:` + +```shell +dokku mysql:backup lollipop my-s3-bucket --use-iam +``` + +Restore a backup file (assuming it was extracted via `tar -xf backup.tgz`): + +```shell +dokku mysql:import lollipop < backup-folder/export +``` + +### set encryption for all future backups of mysql service + +```shell +# usage +dokku mysql:backup-set-encryption +``` + +Set the GPG-compatible passphrase for encrypting backups for backups: + +```shell +dokku mysql:backup-set-encryption lollipop +``` + +### set GPG Public Key encryption for all future backups of mysql service + +```shell +# usage +dokku mysql:backup-set-public-key-encryption +``` + +Set the `GPG` Public Key for encrypting backups: + +```shell +dokku mysql:backup-set-public-key-encryption lollipop +``` + +### unset encryption for future backups of the mysql service + +```shell +# usage +dokku mysql:backup-unset-encryption +``` + +Unset the `GPG` encryption passphrase for backups: + +```shell +dokku mysql:backup-unset-encryption lollipop +``` + +### unset GPG Public Key encryption for future backups of the mysql service + +```shell +# usage +dokku mysql:backup-unset-public-key-encryption +``` + +Unset the `GPG` Public Key encryption for backups: + +```shell +dokku mysql:backup-unset-public-key-encryption lollipop +``` + +### schedule a backup of the mysql service + +```shell +# usage +dokku mysql:backup-schedule [--use-iam] +``` + +flags: + +- `-u|--use-iam`: use the IAM profile associated with the current server + +Schedule a backup: + +> 'schedule' is a crontab expression, eg. "0 3 * * *" for each day at 3am + +```shell +dokku mysql:backup-schedule lollipop "0 3 * * *" my-s3-bucket +``` + +Schedule a backup and authenticate via iam: + +```shell +dokku mysql:backup-schedule lollipop "0 3 * * *" my-s3-bucket --use-iam +``` + +### cat the contents of the configured backup cronfile for the service + +```shell +# usage +dokku mysql:backup-schedule-cat +``` + +Cat the contents of the configured backup cronfile for the service: + +```shell +dokku mysql:backup-schedule-cat lollipop +``` + +### unschedule the backup of the mysql service + +```shell +# usage +dokku mysql:backup-unschedule +``` + +Remove the scheduled backup from cron: + +```shell +dokku mysql:backup-unschedule lollipop +``` + +### Disabling `docker image pull` calls + +If you wish to disable the `docker image pull` calls that the plugin triggers, you may set the `MYSQL_DISABLE_PULL` environment variable to `true`. Once disabled, you will need to pull the service image you wish to deploy as shown in the `stderr` output. + +Please ensure the proper images are in place when `docker image pull` is disabled. diff --git a/plugins/mysql/Vagrantfile b/plugins/mysql/Vagrantfile new file mode 100644 index 000000000..4e6545b64 --- /dev/null +++ b/plugins/mysql/Vagrantfile @@ -0,0 +1,27 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +BOX_NAME = ENV["BOX_NAME"] || "bento/ubuntu-24.04" +BOX_MEMORY = ENV["BOX_MEMORY"] || "2048" +DOKKU_VERSION = "master" + +Vagrant.configure(2) do |config| + config.vm.box = BOX_NAME + config.ssh.forward_agent = true + + config.vm.provider :virtualbox do |vb| + vb.customize ["modifyvm", :id, "--memory", BOX_MEMORY] + end + + config.vm.provider :vmware_fusion do |v, override| + v.vmx["memsize"] = BOX_MEMORY + end + + config.vm.define "default", primary: true do |vm| + vm.vm.synced_folder File.dirname(__FILE__), "/vagrant" + + vm.vm.provision :shell, :inline => "apt -q update && apt -y -qq install git software-properties-common" + vm.vm.provision :shell, :inline => "cd /vagrant && DOKKU_VERSION=#{DOKKU_VERSION} make setup" + vm.vm.provision :shell, :inline => "cd /vagrant && DOKKU_TRACE=1 DOKKU_VERSION=#{DOKKU_VERSION} make test" + end +end diff --git a/plugins/mysql/bin/generate b/plugins/mysql/bin/generate new file mode 100755 index 000000000..1a8e5c538 --- /dev/null +++ b/plugins/mysql/bin/generate @@ -0,0 +1,677 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from __future__ import print_function + +import os +import re + + +def compile( + service, + version, + variable, + alias, + image, + scheme, + ports, + sponsors, + options, + unimplemented, + dokku_version, +): + prefix = "\n\n".join( + [ + header(service), + description(service, image, version), + ] + ) + + if len(sponsors) > 0: + prefix += "\n\n" + prefix += sponsors_section(service, sponsors) + + return ( + "\n\n".join( + [ + prefix, + requirements_section(dokku_version), + installation_section(service, dokku_version), + commands_section( + service, variable, alias, image, scheme, ports, unimplemented + ), + usage_section( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + ), + ] + ) + .replace("\n\n\n\n\n", "\n") + .replace("\n\n\n\n", "\n") + .replace("\n\n\n", "\n\n") + ) + + +def header(service): + return " ".join( + [ + f"# dokku {service}", + f'[![Build Status](https://img.shields.io/github/actions/workflow/status/dokku/dokku-{service}/ci.yml?branch=master&style=flat-square "Build Status")](https://github.com/dokku/dokku-{service}/actions/workflows/ci.yml?query=branch%3Amaster)', + f'[![IRC Network](https://img.shields.io/badge/irc-libera-blue.svg?style=flat-square "IRC Libera")](https://webchat.libera.chat/?channels=dokku)', + ] + ) + + +def description(service, full_image, version): + base = "_" + image = full_image + if "/" in full_image: + base = "r/" + full_image.split("/")[0] + image = full_image.split("/")[1] + + return f"Official {service} plugin for dokku. Currently defaults to installing [{full_image} {version}](https://hub.docker.com/{base}/{image}/)." + + +def sponsors_section(service, sponsors): + if len(sponsors) == 0: + return "" + + sponsor_data = [ + "## Sponsors", + "", + f"The {service} plugin was generously sponsored by the following:", + "", + ] + sponsor_data.extend([f"- [{s}](https://github.com/{s})" for s in sponsors]) + + return "\n".join(sponsor_data) + + +def requirements_section(dokku_version): + return "\n".join( + [ + "## Requirements", + "", + f"- dokku {dokku_version}", + "- docker 1.8.x", + ] + ) + + +def installation_section(service, dokku_version): + return "\n".join( + [ + "## Installation", + "", + "```shell", + f"# on {dokku_version}", + f"sudo dokku plugin:install https://github.com/dokku/dokku-{service}.git --name {service}", + "```", + ] + ) + + +def commands_section(service, variable, alias, image, scheme, ports, unimplemented): + content = [ + "## Commands", + "", + "```", + ] + + subcommands = os.listdir("subcommands") + subcommands.sort() + + command_list = [] + descriptions = [] + for filename in subcommands: + if filename in unimplemented: + continue + data = command_data(filename, service, variable, alias, image, scheme, ports) + description = data["description"] + arguments = data["arguments_string"] + + command_list.append(f"{service}:{filename} {arguments}") + descriptions.append(description) + + maxlen = max(map(len, command_list)) + if maxlen > 50: + maxlen = 50 + for command, description in zip(command_list, descriptions): + space_count = maxlen - len(command) + content.append("{0}{1} # {2}".format(command, " " * space_count, description)) + + content.append("```") + return "\n".join(content) + + +def usage_section( + service, variable, alias, image, scheme, ports, options, unimplemented +): + return "\n\n".join( + [ + "## Usage", + f"Help for any commands can be displayed by specifying the command as an argument to {service}:help. Plugin help output in conjunction with any files in the `docs/` folder is used to generate the plugin documentation. Please consult the `{service}:help` command for any undocumented commands.", + usage_intro( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + usage_lifecycle( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + usage_automation( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + usage_data_management( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + usage_backup( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + usage_docker_pull( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + ] + ) + + +def usage_intro(service, variable, alias, image, scheme, ports, options, unimplemented): + commands = ["create", "info", "list", "logs", "link", "unlink", "set"] + content = ["### Basic Usage"] + + return fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, + ) + + +def usage_lifecycle( + service, variable, alias, image, scheme, ports, options, unimplemented +): + commands = [ + "connect", + "enter", + "expose", + "unexpose", + "promote", + "start", + "stop", + "pause", + "restart", + "upgrade", + ] + content = [ + "### Service Lifecycle", + "", + "The lifecycle of each service can be managed through the following commands:", + "", + ] + + return fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, + ) + + +def usage_automation( + service, variable, alias, image, scheme, ports, options, unimplemented +): + commands = ["app-links", "clone", "exists", "linked", "links"] + content = [ + "### Service Automation", + "", + "Service scripting can be executed using the following commands:", + "", + ] + + return fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, + ) + + +def usage_data_management( + service, variable, alias, image, scheme, ports, options, unimplemented +): + commands = ["import", "export"] + content = [ + "### Data Management", + "", + "The underlying service data can be imported and exported with the following commands:", + "", + ] + + return fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, + ) + + +def usage_backup( + service, variable, alias, image, scheme, ports, options, unimplemented +): + commands = [ + "backup-auth", + "backup-deauth", + "backup", + "backup-set-encryption", + "backup-set-public-key-encryption", + "backup-unset-encryption", + "backup-unset-public-key-encryption", + "backup-schedule", + "backup-schedule-cat", + "backup-unschedule", + ] + content = [ + "### Backups", + "", + "Datastore backups are supported via AWS S3 and S3 compatible services like [minio](https://github.com/minio/minio).", + "", + "You may skip the `backup-auth` step if your dokku install is running within EC2 and has access to the bucket via an IAM profile. In that case, use the `--use-iam` option with the `backup` command.", + "", + "Backups can be performed using the backup commands:", + "", + ] + + return fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, + ) + + +def usage_docker_pull( + service, variable, alias, image, scheme, ports, options, unimplemented +): + service_prefix = service.upper() + return "\n".join( + [ + "### Disabling `docker image pull` calls", + "", + f"If you wish to disable the `docker image pull` calls that the plugin triggers, you may set the `{service_prefix}_DISABLE_PULL` environment variable to `true`. Once disabled, you will need to pull the service image you wish to deploy as shown in the `stderr` output.", + "", + "Please ensure the proper images are in place when `docker image pull` is disabled.", + ] + ) + + +def fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, +): + i = 0 + for command in commands: + output = command_help( + command, + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + ) + if output == "": + continue + content.append(output) + i += 1 + + if i == 0: + return "" + + return "\n".join(content) + + +def parse_args(line): + line = line.strip() + arguments = [] + for arg in re.findall("([A-Z_]+)", line): + arg = arg.replace("_", "-").lower() + if arg.endswith("optional-flag"): + arg = arg.replace("-optional-flag", "") + arguments.append(f"[--{arg}]") + elif arg.endswith("-flag"): + if arg == "info-flag": + arguments.append(f"[--single-info-flag]") + else: + arg = arg.replace("-flag", "") + first_letter = arg[0] + arguments.append(f"[-{first_letter}|--{arg}]") + elif arg.endswith("-flags-list"): + arg = arg.replace("-list", "") + arguments.append(f"[--{arg}...]") + elif arg.endswith("list"): + arg = arg.replace("-list", "") + arguments.append(f"<{arg}...>") + else: + arguments.append(f"<{arg}>") + return " ".join(arguments) + + +def command_help( + command, service, variable, alias, image, scheme, ports, options, unimplemented +): + if command in unimplemented: + return "" + + data = command_data(command, service, variable, alias, image, scheme, ports) + content = [ + f"### {data['description']}", + "", + "```shell", + "# usage", + f"dokku {service}:{command} {data['arguments_string']}".strip(), + "```", + ] + + # if len(data["arguments"]) > 0: + # content.append("") + # content.append("arguments:") + # content.append("") + # for argument in data["arguments"]: + # content.append(f"- {argument}") + + if len(data["flags"]) > 0: + content.append("") + content.append("flags:") + content.append("") + for flag in data["flags"]: + if "--config-options" in flag and options != "": + flag = f"{flag} (default: `{options}`)" + content.append(f"- {flag}") + + if len(data["examples"]) > 0: + content.append("") + content.append(data["examples"]) + + doc_file = os.path.join("docs", f"{command}.md") + if os.path.isfile(doc_file): + content.append("") + with open(doc_file) as f: + content.append(f.read()) + + return "\n" + "\n".join(content) + + +def command_data(command, service, variable, alias, image, scheme, ports): + description = None + arguments = [] + arguments_string = "" + example_lines = [] + flags = [] + with open(os.path.join("subcommands", command)) as f: + for line in f.readlines(): + line = line.strip() + line = line.replace("$PLUGIN_SERVICE", service) + line = line.replace("$PLUGIN_COMMAND_PREFIX", service) + line = line.replace("${PLUGIN_COMMAND_PREFIX}", service) + line = line.replace("${PLUGIN_VARIABLE}", variable) + line = line.replace("${PLUGIN_DEFAULT_ALIAS}", alias) + line = line.replace("${PLUGIN_IMAGE}", image) + line = line.replace("${PLUGIN_SCHEME}", scheme) + line = line.replace("${PLUGIN_DATASTORE_PORTS[0]}", ports[0]) + line = line.replace("${PLUGIN_DATASTORE_PORTS[@]}", " ".join(ports)) + + if "declare desc" in line: + description = re.search('"(.+)"', line).group(1) + elif "$1" in line: + arguments_string = parse_args(line) + elif line.startswith("#A "): + argument = line.replace("#A ", "") + parts = [a.strip() for a in argument.split(",", 1)] + arguments.append(f"`{parts[0]}`: {parts[1]}") + elif line.startswith("#F "): + flag = line.replace("#F ", "") + parts = [a.strip() for a in flag.split(",", 1)] + flags.append(f"`{parts[0]}`: {parts[1]}") + elif line.startswith("#E "): + example_lines.append(line.replace("#E ", "")) + + examples = [] + sentence_lines = [] + command_lines = [] + codeblock_lines = [] + blockquote_lines = [] + for line in example_lines: + if line.startswith("export") or line.startswith("dokku"): + if len(blockquote_lines) > 0: + examples.append("\n" + process_blockquote(blockquote_lines)) + blockquote_lines = [] + if len(codeblock_lines) > 0: + examples.append("\n" + process_codeblock(codeblock_lines)) + codeblock_lines = [] + if len(sentence_lines) > 0: + examples.append("\n" + process_sentence(sentence_lines)) + sentence_lines = [] + + command_lines.append(line) + elif line.startswith(" "): + if len(blockquote_lines) > 0: + examples.append("\n" + process_blockquote(blockquote_lines)) + blockquote_lines = [] + if len(command_lines) > 0: + examples.append("\n" + process_command(command_lines)) + command_lines = [] + if len(sentence_lines) > 0: + examples.append("\n" + process_sentence(sentence_lines)) + sentence_lines = [] + + codeblock_lines.append(line.strip()) + elif line.startswith(">"): + if len(codeblock_lines) > 0: + examples.append("\n" + process_codeblock(codeblock_lines)) + codeblock_lines = [] + if len(command_lines) > 0: + examples.append("\n" + process_command(command_lines)) + command_lines = [] + if len(sentence_lines) > 0: + examples.append("\n" + process_sentence(sentence_lines)) + sentence_lines = [] + + blockquote_lines.append(line) + else: + if len(blockquote_lines) > 0: + examples.append("\n" + process_blockquote(blockquote_lines)) + blockquote_lines = [] + if len(codeblock_lines) > 0: + examples.append("\n" + process_codeblock(codeblock_lines)) + codeblock_lines = [] + if len(command_lines) > 0: + examples.append("\n" + process_command(command_lines)) + command_lines = [] + + sentence_lines.append(line) + + if len(blockquote_lines) > 0: + examples.append("\n" + process_blockquote(blockquote_lines)) + blockquote_lines = [] + if len(codeblock_lines) > 0: + examples.append("\n" + process_codeblock(codeblock_lines)) + codeblock_lines = [] + if len(command_lines) > 0: + examples.append("\n" + process_command(command_lines)) + command_lines = [] + if len(sentence_lines) > 0: + examples.append("\n" + process_sentence(sentence_lines)) + sentence_lines = [] + + return { + "description": description, + "arguments_string": arguments_string, + "arguments": arguments, + "flags": flags, + "examples": "\n".join(examples).strip(), + } + + +def process_sentence(sentence_lines): + sentence_lines = " ".join(sentence_lines) + sentences = ". ".join( + upperfirst(i.strip()) for i in sentence_lines.split(". ") + ).strip() + if not sentences.endswith(".") and not sentences.endswith(":"): + sentences += ":" + + text = [] + for sentence in sentences.split(". "): + parts = [] + for word in sentence.strip().split(" "): + if word.isupper() and len(word) > 1: + for ending in [":", "."]: + if word.endswith(ending): + word = "`{0}`{1}".format(word[:-1], ending) + else: + word = "`{0}`".format(word) + parts.append(word) + text.append(" ".join(parts)) + + text = ". ".join(text) + + # some cleanup + text = text.replace("(0.0.0.0)", "(`0.0.0.0`)") + text = text.replace("'", "`") + text = text.replace("`s", "'s") + text = text.replace("``", "`") + text = text.strip(" ") + + return text + + +def upperfirst(x): + return x[:1].upper() + x[1:] + + +def process_blockquote(blockquote_lines): + return "\n".join(blockquote_lines) + + +def process_command(command_lines): + command_lines = "\n".join(command_lines) + return f"```shell\n{command_lines}\n```" + + +def process_codeblock(codeblock_lines): + codeblock_lines = "\n".join(codeblock_lines) + return f"```\n{codeblock_lines}\n```" + + +def main(): + service = None + version = None + variable = None + image = None + alias = None + options = None + unimplemented = [] + + with open("Dockerfile") as f: + for line in f.readlines(): + if "FROM " in line: + image, version = line.split(" ")[1].split(":") + image = image.strip() + version = version.strip() + + with open("config") as f: + for line in f.readlines(): + if "PLUGIN_COMMAND_PREFIX=" in line: + service = re.search('"(.+)"', line).group(1) + if "PLUGIN_DEFAULT_ALIAS=" in line: + alias = re.search('"(.+)"', line).group(1) + if "PLUGIN_VARIABLE=" in line: + variable = re.search('"(.+)"', line).group(1) + if "PLUGIN_SCHEME=" in line: + scheme = re.search('"(.+)"', line).group(1) + if "PLUGIN_DATASTORE_PORTS=" in line: + ports = re.search("\((.+)\)", line).group(1).split(" ") + if "PLUGIN_UNIMPLEMENTED_SUBCOMMANDS=" in line: + match = re.search("\((.+)\)", line) + if match is not None: + unimplemented = [s.strip('"') for s in match.group(1).split(" ")] + + with open("config") as f: + for line in f.readlines(): + if f"{variable}_CONFIG_OPTIONS" in line: + match = re.search('"(.+)"', line) + if match is not None: + options = match.group(1) + + sponsors = [] + with open("plugin.toml") as f: + for line in f.readlines(): + if line.startswith("sponsors"): + sponsors = re.search('\[(["\w\s,_-]+)\]', line).group(1) + sponsors = [s.strip('"') for s in sponsors.split(",")] + + text = compile( + service, + version, + variable, + alias, + image, + scheme, + ports, + sponsors, + options, + unimplemented, + "0.19.x+", + ) + + base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + readme_file = os.path.join(base_path, "README.md") + with open(readme_file, "w") as f: + f.write(text + "\n") + + +if __name__ == "__main__": + main() diff --git a/plugins/mysql/commands b/plugins/mysql/commands new file mode 100755 index 000000000..0b22b42f5 --- /dev/null +++ b/plugins/mysql/commands @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +[[ " help $PLUGIN_COMMAND_PREFIX:help $PLUGIN_COMMAND_PREFIX $PLUGIN_COMMAND_PREFIX:default " == *" $1 "* ]] || [[ "$1" == "$PLUGIN_COMMAND_PREFIX:"* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/help-functions" + +if [[ ! -d $PLUGIN_CONFIG_ROOT ]]; then + dokku_log_fail "$PLUGIN_SERVICE: Please run: sudo dokku plugin:install" +fi + +if [[ ! -d $PLUGIN_DATA_ROOT ]]; then + dokku_log_fail "$PLUGIN_SERVICE: Please run: sudo dokku plugin:install" +fi + +fn-help "$@" diff --git a/plugins/mysql/common-functions b/plugins/mysql/common-functions new file mode 100755 index 000000000..5c4108906 --- /dev/null +++ b/plugins/mysql/common-functions @@ -0,0 +1,1056 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" + +add_to_links_file() { + declare desc="add an app to the service link file" + declare SERVICE="$1" APP="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + touch "$LINKS_FILE" + sed -i.bak "/^$APP\$/d" "$LINKS_FILE" && rm "$LINKS_FILE.bak" + echo "$APP" >>"$LINKS_FILE" + sort "$LINKS_FILE" -u -o "$LINKS_FILE" +} + +auth_service_filter() { + declare desc="calls user-service plugin trigger" + declare SERVICES=("$@") + local user_auth_count + + if [[ "${#SERVICES[@]}" -eq 0 ]]; then + return + fi + + user_auth_count="$(find "$PLUGIN_PATH"/enabled/*/user-auth-service 2>/dev/null | wc -l)" + + # no plugin trigger exists + if [[ $user_auth_count == 0 ]]; then + # echo out all the services since there is no plugin trigger + for SERVICE in "${SERVICES[@]}"; do + [[ -n "$SERVICE" ]] && echo "$SERVICE" + done + return 0 + fi + + # this plugin trigger exists in the core `20_events` plugin + if [[ "$user_auth_count" == 1 ]] && [[ -f "$PLUGIN_PATH"/enabled/20_events/user-auth-service ]]; then + # echo out all the services since there is no valid plugin trigger + for SERVICE in "${SERVICES[@]}"; do + [[ -n "$SERVICE" ]] && echo "$SERVICE" + done + return 0 + fi + + export SSH_USER=${SSH_USER:=$USER} + export SSH_NAME=${NAME:="default"} + # the output of this trigger should be all the services a user has access to + plugn trigger user-auth-service "$SSH_USER" "$SSH_NAME" "$PLUGIN_COMMAND_PREFIX" "${SERVICES[@]}" +} + +fn-services-list() { + declare desc="prints a filtered list of all local apps" + declare FILTER="$1" + local services=() + + pushd "$PLUGIN_DATA_ROOT" >/dev/null + for f in *; do + [[ -d $f ]] || continue + services+=("$f") + done + popd >/dev/null 2>&1 || pushd "/tmp" >/dev/null + + if [[ "${#services[@]}" -eq 0 ]]; then + return + fi + + if [[ "$FILTER" == "false" ]]; then + for service in "${services[@]}"; do + if [[ -n "$service" ]]; then + echo "$service" + fi + done + return + fi + + for service in $(auth_service_filter "${services[@]}" 2>/dev/null); do + if [[ -n "$service" ]]; then + echo "$service" + fi + done +} + +docker_ports_options() { + declare desc="export a list of exposed ports" + declare PORTS=("$@") + for ((i = 0; i < ${#PLUGIN_DATASTORE_PORTS[@]}; i++)); do + echo -n "-p ${PORTS[i]}:${PLUGIN_DATASTORE_PORTS[i]} " + done +} + +get_container_ip() { + declare desc="retrieve the ip address of a container" + declare CONTAINER_ID="$1" + "$DOCKER_BIN" container inspect --format '{{ .NetworkSettings.IPAddress }}' "$CONTAINER_ID" 2>/dev/null +} + +get_database_name() { + declare desc="retrieve a sanitized database name" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + + if [[ ! -f "$SERVICE_ROOT/DATABASE_NAME" ]]; then + echo "$SERVICE" >"$SERVICE_ROOT/DATABASE_NAME" + fi + + cat "$SERVICE_ROOT/DATABASE_NAME" +} + +get_random_ports() { + declare desc="retrieve N random ports" + declare iterations="${1:-1}" + for ((i = 0; i < iterations; i++)); do + local port=$RANDOM + local quit=0 + while [ "$quit" -ne 1 ]; do + netstat -an | grep $port >/dev/null + # shellcheck disable=SC2181 + if [ $? -gt 0 ]; then + quit=1 + else + port=$((port + 1)) + fi + done + echo $port + done +} + +get_service_name() { + declare desc="retrieve a docker service label" + declare SERVICE="$1" + echo "dokku.${PLUGIN_COMMAND_PREFIX}.$SERVICE" +} + +get_url_from_config() { + declare desc="retrieve a given _URL from a list of configuration variables" + declare EXISTING_CONFIG="$1" CONFIG_VAR="$2" + echo "$EXISTING_CONFIG" | grep "$CONFIG_VAR" | sed "s/$CONFIG_VAR:\s*//" | xargs +} + +in_links_file() { + declare desc="check if a service LINKS file contains an app" + declare SERVICE="$1" APP="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + grep -qE "^$APP\$" "$LINKS_FILE" +} + +is_container_status() { + declare desc="return 0 or 1 depending upon whether a given container has a certain status" + declare CID="$1" STATUS="$2" + local TEMPLATE="{{.State.$STATUS}}" + local CONTAINER_STATUS=$("$DOCKER_BIN" container inspect -f "$TEMPLATE" "$CID" 2>/dev/null || true) + + if [[ "$CONTAINER_STATUS" == "true" ]]; then + return 0 + fi + return 1 +} + +is_implemented_command() { + declare desc="return true if value ($1) is in list (all other arguments)" + declare CMD="$1" + CMD="$(echo "$CMD" | cut -d ':' -f2)" + + if [[ ${#PLUGIN_UNIMPLEMENTED_SUBCOMMANDS[@]} -eq 0 ]]; then + return 0 + fi + + local e + for e in "${PLUGIN_UNIMPLEMENTED_SUBCOMMANDS[@]}"; do + [[ "$e" == "$CMD" ]] && return 1 + done + return 0 +} + +is_valid_service_name() { + declare desc="validate a service name" + declare SERVICE="$1" + [[ -z "$SERVICE" ]] && return 1 + + if [[ "$SERVICE" =~ ^[A-Za-z0-9_-]+$ ]]; then + return 0 + fi + + return 1 +} + +remove_from_links_file() { + declare desc="remove an app from the service link file" + declare SERVICE="$1" APP="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + if [[ ! -f "$LINKS_FILE" ]]; then + return + fi + + sed -i.bak "/^$APP\$/d" "$LINKS_FILE" && rm "$LINKS_FILE.bak" + sort "$LINKS_FILE" -u -o "$LINKS_FILE" +} + +retry-docker-command() { + local ID="$1" COMMAND="$2" + local i=0 success=false + until [ $i -ge 100 ]; do + set +e + suppress_output "$DOCKER_BIN" container exec "$ID" sh -c "$COMMAND" + exit_code=$? + set -e + if [[ "$exit_code" == 0 ]]; then + success=true + break + fi + i=$((i + 1)) + sleep 1 + done + if [[ $i -gt 0 ]]; then + dokku_log_verbose "Container command retried ${i} time(s): ${COMMAND}" + fi + [[ "$success" == "true" ]] || dokku_log_fail "Failed to run command: ${COMMAND}" +} + +service_alternative_alias() { + declare desc="retrieve an alternative alias for a service" + declare EXISTING_CONFIG="$1" + local COLORS=(AQUA BLACK BLUE FUCHSIA GRAY GREEN LIME MAROON NAVY OLIVE PURPLE RED SILVER TEAL WHITE YELLOW) + local ALIAS + + for COLOR in "${COLORS[@]}"; do + ALIAS="${PLUGIN_ALT_ALIAS}_${COLOR}" + local IN_USE=$(echo "$EXISTING_CONFIG" | grep "${ALIAS}_URL") + if [[ -z "$IN_USE" ]]; then + break + fi + unset ALIAS + done + echo "$ALIAS" +} + +service_app_links() { + declare desc="output all service links for a given app" + declare APP="$1" + local LINKED_APP SERVICE SERVICE_ROOT + + for SERVICE in $(fn-services-list true); do + [[ -n "$SERVICE" ]] || continue + + SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + [[ -f "$SERVICE_ROOT/LINKS" ]] || continue + for LINKED_APP in $(<"$SERVICE_ROOT/LINKS"); do + if [[ "$LINKED_APP" == "$APP" ]]; then + echo "$SERVICE" + fi + done + done +} + +service_backup() { + declare desc="create a backup of a service to an existing s3 bucket" + declare SERVICE="$1" BUCKET_NAME="$2" USE_IAM_OPTIONAL_FLAG="$3" + local SERVICE_BACKUP_ROOT="$PLUGIN_DATA_ROOT/$SERVICE/backup" + local BACKUP_ENCRYPTION_CONFIG_ROOT="$PLUGIN_DATA_ROOT/$SERVICE/backup-encryption" + local AWS_ACCESS_KEY_ID_FILE="$SERVICE_BACKUP_ROOT/AWS_ACCESS_KEY_ID" + local AWS_SECRET_ACCESS_KEY_FILE="$SERVICE_BACKUP_ROOT/AWS_SECRET_ACCESS_KEY" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ID="$(cat "$SERVICE_ROOT/ID")" + local BACKUP_PARAMETERS="" + + if [[ -z "$USE_IAM_OPTIONAL_FLAG" ]]; then + [[ ! -f "$AWS_ACCESS_KEY_ID_FILE" ]] && dokku_log_fail "Missing AWS_ACCESS_KEY_ID file" + [[ ! -f "$AWS_SECRET_ACCESS_KEY_FILE" ]] && dokku_log_fail "Missing AWS_SECRET_ACCESS_KEY file" + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e AWS_ACCESS_KEY_ID=$(cat "$AWS_ACCESS_KEY_ID_FILE") -e AWS_SECRET_ACCESS_KEY=$(cat "$AWS_SECRET_ACCESS_KEY_FILE")" + elif [[ "$USE_IAM_OPTIONAL_FLAG" != "--use-iam" ]] && [[ "$USE_IAM_OPTIONAL_FLAG" != "-u" ]]; then + dokku_log_fail "Provide AWS credentials or use the --use-iam flag" + fi + + BACKUP_TMPDIR=$(mktemp -d --tmpdir) + trap 'rm -rf "$BACKUP_TMPDIR" > /dev/null' RETURN INT TERM EXIT + + "$DOCKER_BIN" container inspect "$ID" >/dev/null 2>&1 || dokku_log_fail "Service container does not exist" + is_container_status "$ID" "Running" || dokku_log_fail "Service container is not running" + + (service_export "$SERVICE" >"${BACKUP_TMPDIR}/export") + + # Build parameter list for s3backup tool + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e BUCKET_NAME=$BUCKET_NAME" + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e BACKUP_NAME=${PLUGIN_COMMAND_PREFIX}-${SERVICE}" + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -v ${BACKUP_TMPDIR}:/backup" + + if [[ -f "$SERVICE_BACKUP_ROOT/AWS_DEFAULT_REGION" ]]; then + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e AWS_DEFAULT_REGION=$(cat "$SERVICE_BACKUP_ROOT/AWS_DEFAULT_REGION")" + fi + + if [[ -f "$SERVICE_BACKUP_ROOT/AWS_SIGNATURE_VERSION" ]]; then + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e AWS_SIGNATURE_VERSION=$(cat "$SERVICE_BACKUP_ROOT/AWS_SIGNATURE_VERSION")" + fi + + if [[ -f "$SERVICE_BACKUP_ROOT/ENDPOINT_URL" ]]; then + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e ENDPOINT_URL=$(cat "$SERVICE_BACKUP_ROOT/ENDPOINT_URL")" + fi + + if [[ -f "$BACKUP_ENCRYPTION_CONFIG_ROOT/ENCRYPTION_KEY" ]]; then + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e ENCRYPTION_KEY=$(cat "$BACKUP_ENCRYPTION_CONFIG_ROOT/ENCRYPTION_KEY")" + fi + + if [[ -f "$BACKUP_ENCRYPTION_CONFIG_ROOT/ENCRYPT_WITH_PUBLIC_KEY_ID" ]]; then + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e ENCRYPT_WITH_PUBLIC_KEY_ID=$(cat "$BACKUP_ENCRYPTION_CONFIG_ROOT/ENCRYPT_WITH_PUBLIC_KEY_ID")" + fi + + # shellcheck disable=SC2086 + "$DOCKER_BIN" container run --rm $BACKUP_PARAMETERS "$PLUGIN_S3BACKUP_IMAGE" +} + +service_commit_config() { + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local CONFIG_VARIABLE="${PLUGIN_VARIABLE}_CONFIG_OPTIONS" + local ENV_VARIABLE="${PLUGIN_VARIABLE}_CUSTOM_ENV" + + custom_env="${!ENV_VARIABLE}" + [[ -n "$SERVICE_CUSTOM_ENV" ]] && custom_env="$SERVICE_CUSTOM_ENV" + if [[ -n $custom_env ]]; then + echo "$custom_env" | tr ';' "\n" >"$SERVICE_ROOT/ENV" + else + echo "" >"$SERVICE_ROOT/ENV" + fi + + config_options="${!CONFIG_VARIABLE}" + [[ -n "$PLUGIN_CONFIG_OPTIONS" ]] && config_options="$PLUGIN_CONFIG_OPTIONS" + if [[ -n "$config_options" ]]; then + echo "$config_options" >"$SERVICE_ROOT/CONFIG_OPTIONS" + else + echo "" >"$SERVICE_ROOT/CONFIG_OPTIONS" + fi + + if [[ -n "$SERVICE_MEMORY" ]]; then + echo "$SERVICE_MEMORY" >"$SERVICE_ROOT/SERVICE_MEMORY" + fi + + if [[ -n "$SERVICE_SHM_SIZE" ]]; then + echo "$SERVICE_SHM_SIZE" >"$SERVICE_ROOT/SHM_SIZE" + fi + + if [[ -n "$PLUGIN_IMAGE" ]]; then + echo "$PLUGIN_IMAGE" >"$SERVICE_ROOT/IMAGE" + fi + + if [[ -n "$PLUGIN_IMAGE_VERSION" ]]; then + echo "$PLUGIN_IMAGE_VERSION" >"$SERVICE_ROOT/IMAGE_VERSION" + fi + + if [[ -n "$SERVICE_INITIAL_NETWORK" ]]; then + fn-plugin-property-write "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "initial-network" "$SERVICE_INITIAL_NETWORK" + fi + + if [[ -n "$SERVICE_POST_CREATE_NETWORK" ]]; then + fn-plugin-property-write "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-create-network" "$SERVICE_POST_CREATE_NETWORK" + fi + + if [[ -n "$SERVICE_POST_START_NETWORK" ]]; then + fn-plugin-property-write "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-start-network" "$SERVICE_POST_START_NETWORK" + fi +} + +service_backup_auth() { + declare desc="set up authentication" + declare SERVICE="$1" AWS_ACCESS_KEY_ID="$2" AWS_SECRET_ACCESS_KEY="$3" AWS_DEFAULT_REGION="$4" AWS_SIGNATURE_VERSION="$5" ENDPOINT_URL="$6" + local SERVICE_BACKUP_ROOT="$PLUGIN_DATA_ROOT/$SERVICE/backup" + + mkdir "$SERVICE_BACKUP_ROOT" + echo "$AWS_ACCESS_KEY_ID" >"$SERVICE_BACKUP_ROOT/AWS_ACCESS_KEY_ID" + echo "$AWS_SECRET_ACCESS_KEY" >"$SERVICE_BACKUP_ROOT/AWS_SECRET_ACCESS_KEY" + + if [[ -n "$AWS_DEFAULT_REGION" ]]; then + echo "$AWS_DEFAULT_REGION" >"$SERVICE_BACKUP_ROOT/AWS_DEFAULT_REGION" + fi + + if [[ -n "$AWS_SIGNATURE_VERSION" ]]; then + echo "$AWS_SIGNATURE_VERSION" >"$SERVICE_BACKUP_ROOT/AWS_SIGNATURE_VERSION" + fi + + if [[ -n "$ENDPOINT_URL" ]]; then + echo "$ENDPOINT_URL" >"$SERVICE_BACKUP_ROOT/ENDPOINT_URL" + fi +} + +service_backup_deauth() { + declare desc="remove authentication" + declare SERVICE="$1" + local SERVICE_ROOT="${PLUGIN_DATA_ROOT}/${SERVICE}" + local SERVICE_BACKUP_ROOT="${SERVICE_ROOT}/backup/" + + rm -rf "$SERVICE_BACKUP_ROOT" +} + +service_backup_schedule() { + declare desc="schedules a backup of the service" + declare SERVICE="$1" SCHEDULE="$2" BUCKET_NAME="$3" USE_IAM_OPTIONAL_FLAG="$4" + local DOKKU_BIN="$(which dokku)" + local CRON_FILE="/etc/cron.d/dokku-${PLUGIN_COMMAND_PREFIX}-${SERVICE}" + local TMP_CRON_FILE="${PLUGIN_DATA_ROOT}/.TMP_CRON_FILE" + + if [[ -n "$USE_IAM_OPTIONAL_FLAG" ]] && [[ "$USE_IAM_OPTIONAL_FLAG" != "--use-iam" ]] && [[ "$USE_IAM_OPTIONAL_FLAG" != "-u" ]]; then + dokku_log_fail "Invalid flag provided, only '--use-iam' allowed" + fi + + echo "${SCHEDULE} dokku ${DOKKU_BIN} ${PLUGIN_COMMAND_PREFIX}:backup ${SERVICE} ${BUCKET_NAME} ${USE_IAM_OPTIONAL_FLAG}" >"$TMP_CRON_FILE" + sudo /bin/mv "$TMP_CRON_FILE" "$CRON_FILE" + sudo /bin/chown root:root "$CRON_FILE" + sudo /bin/chmod 644 "$CRON_FILE" +} + +service_backup_schedule_cat() { + declare desc="cat the contents of the configured backup cronfile for the service" + declare SERVICE="$1" + local CRON_FILE="/etc/cron.d/dokku-${PLUGIN_COMMAND_PREFIX}-${SERVICE}" + + if [[ ! -f "$CRON_FILE" ]]; then + dokku_log_fail "There is no scheduled backup for ${SERVICE}." + fi + + cat "$CRON_FILE" +} + +service_backup_set_encryption() { + declare desc="set up backup encryption" + declare SERVICE="$1" ENCRYPTION_KEY="$2" + local SERVICE_ROOT="${PLUGIN_DATA_ROOT}/${SERVICE}" + local SERVICE_BACKUP_ENCRYPTION_ROOT="${SERVICE_ROOT}/backup-encryption/" + + mkdir "$SERVICE_BACKUP_ENCRYPTION_ROOT" + echo "$ENCRYPTION_KEY" >"${SERVICE_BACKUP_ENCRYPTION_ROOT}/ENCRYPTION_KEY" +} + +service_backup_set_public_key_encryption() { + declare desc="set up backup GPG Public Key encryption" + declare SERVICE="$1" ENCRYPT_WITH_PUBLIC_KEY_ID="$2" + local SERVICE_ROOT="${PLUGIN_DATA_ROOT}/${SERVICE}" + local SERVICE_BACKUP_ENCRYPTION_ROOT="${SERVICE_ROOT}/backup-encryption/" + + mkdir "$SERVICE_BACKUP_ENCRYPTION_ROOT" + echo "$ENCRYPT_WITH_PUBLIC_KEY_ID" >"${SERVICE_BACKUP_ENCRYPTION_ROOT}/ENCRYPT_WITH_PUBLIC_KEY_ID" +} + +service_backup_unschedule() { + declare desc="unschedule the backup of the service" + declare SERVICE="$1" + local CRON_FILE="/etc/cron.d/dokku-${PLUGIN_COMMAND_PREFIX}-${SERVICE}" + + sudo /bin/rm -f "$CRON_FILE" +} + +service_backup_unset_encryption() { + declare desc="remove backup encryption" + declare SERVICE="$1" + local SERVICE_ROOT="${PLUGIN_DATA_ROOT}/${SERVICE}" + local SERVICE_BACKUP_ENCRYPTION_ROOT="${SERVICE_ROOT}/backup-encryption/" + + rm -rf "$SERVICE_BACKUP_ENCRYPTION_ROOT" +} + +service_backup_unset_encryption() { + declare desc="remove backup encryption" + declare SERVICE="$1" + local SERVICE_ROOT="${PLUGIN_DATA_ROOT}/${SERVICE}" + local SERVICE_BACKUP_ENCRYPTION_ROOT="${SERVICE_ROOT}/backup-encryption/" + + rm -rf "$SERVICE_BACKUP_ENCRYPTION_ROOT" +} + +service_container_rm() { + declare desc="stop a service and remove the running container" + declare SERVICE="$1" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local ID + + service_pause "$SERVICE" + ID=$("$DOCKER_BIN" container ps -aq --no-trunc --filter "name=^/$SERVICE_NAME$") || true + # this may be 'true' in tests... + if [[ -z "$ID" ]] || [[ "$ID" == "true" ]]; then + return 0 + fi + + dokku_log_verbose_quiet "Removing container" + "$DOCKER_BIN" container update --restart=no "$SERVICE_NAME" >/dev/null 2>&1 + if ! "$DOCKER_BIN" container rm "$SERVICE_NAME" >/dev/null 2>&1; then + dokku_log_fail "Unable to remove container for service $SERVICE" + fi +} + +service_dns_hostname() { + declare desc="retrieve the alias of a service" + declare SERVICE="$1" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + echo "$SERVICE_NAME" | tr ._ - +} + +service_enter() { + declare desc="enter running app container of specified proc type" + declare SERVICE="$1" && shift 1 + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ID="$(cat "$SERVICE_ROOT/ID")" + + "$DOCKER_BIN" container inspect "$ID" >/dev/null 2>&1 || dokku_log_fail "Service container does not exist" + is_container_status "$ID" "Running" || dokku_log_fail "Service container is not running" + + local EXEC_CMD="" + has_tty && local DOKKU_RUN_OPTS+=" -i -t" + # shellcheck disable=SC2086 + "$DOCKER_BIN" container exec $DOKKU_RUN_OPTS $ID $EXEC_CMD "${@:-/bin/bash}" +} + +service_exists() { + declare desc="returns 0 or 1 depending on whether service exists or not" + declare SERVICE="$1" + [[ -z "$SERVICE" ]] && return 1 + [[ -d "$PLUGIN_DATA_ROOT/$SERVICE" ]] && return 0 + return 1 +} + +service_exposed_ports() { + declare desc="list exposed ports for a service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PORT_FILE="$SERVICE_ROOT/PORT" + [[ ! -f $PORT_FILE ]] && echo '-' && return 0 + local PORTS=($(cat "$PORT_FILE")) + for ((i = 0; i < ${#PLUGIN_DATASTORE_PORTS[@]}; i++)); do + echo -n "${PLUGIN_DATASTORE_PORTS[i]}->${PORTS[i]} " + done +} + +service_image_exists() { + declare desc="check if the current image exists" + declare SERVICE="$1" PLUGIN_IMAGE="${2:-$PLUGIN_IMAGE}" PLUGIN_IMAGE_VERSION="${3:-$PLUGIN_IMAGE_VERSION}" + local plugin_image="$PLUGIN_IMAGE" + local plugin_image_version="$PLUGIN_IMAGE_VERSION" + + if [[ -z "$PLUGIN_IMAGE" ]] && [[ -f "$SERVICE_ROOT/IMAGE" ]]; then + plugin_image="$(cat "$SERVICE_ROOT/IMAGE")" + fi + + if [[ -z "$PLUGIN_IMAGE_VERSION" ]] && [[ -f "$SERVICE_ROOT/IMAGE_VERSION" ]]; then + plugin_image_version="$(cat "$SERVICE_ROOT/IMAGE_VERSION")" + fi + + local IMAGE="$plugin_image:$plugin_image_version" + + if [[ "$("$DOCKER_BIN" image ls -q "$IMAGE" 2>/dev/null)" == "" ]]; then + return 1 + fi + + return 0 +} + +service_info() { + declare desc="retrieve information about a given service" + declare SERVICE="$1" INFO_FLAG="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_URL=$(service_url "$SERVICE") + local PORT_FILE="$SERVICE_ROOT/PORT" + local SERVICE_CONTAINER_ID="$(cat "$SERVICE_ROOT/ID")" + local flag key valid_flags + + local flag_map=( + "--config-dir: ${SERVICE_ROOT}/${PLUGIN_CONFIG_SUFFIX}" + "--config-options: $(cat "$SERVICE_ROOT/CONFIG_OPTIONS" 2>/dev/null || true)" + "--data-dir: ${SERVICE_ROOT}/data" + "--dsn: ${SERVICE_URL}" + "--exposed-ports: $(service_exposed_ports "$SERVICE")" + "--id: ${SERVICE_CONTAINER_ID}" + "--internal-ip: $(get_container_ip "${SERVICE_CONTAINER_ID}")" + "--initial-network: $(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "initial-network")" + "--links: $(service_linked_apps "$SERVICE")" + "--post-create-network: $(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-create-network")" + "--post-start-network: $(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-start-network")" + "--service-root: ${SERVICE_ROOT}" + "--status: $(service_status "$SERVICE")" + "--version: $(service_version "$SERVICE")" + ) + if [[ -z "$INFO_FLAG" ]]; then + dokku_log_info2_quiet "$SERVICE $PLUGIN_COMMAND_PREFIX service information" + for flag in "${flag_map[@]}"; do + key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" + dokku_log_verbose "$(printf "%-20s %-25s" "${key^}" "${flag#*: }")" + done + else + local match=false + for flag in "${flag_map[@]}"; do + valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)" + if [[ "$flag" == "${INFO_FLAG}:"* ]]; then + echo "${flag#*: }" && match=true + fi + done + [[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}" + fi +} + +service_is_linked() { + declare desc="link a service to an application" + declare SERVICE="$1" APP="$2" + update_plugin_scheme_for_app "$APP" + local SERVICE_URL=$(service_url "$SERVICE") + local EXISTING_CONFIG=$(config_all "$APP") + local LINK=$(echo "$EXISTING_CONFIG" | grep "$SERVICE_URL" | cut -d: -f1) || true + if [[ -z $LINK ]]; then + dokku_log_warn "Service $SERVICE is not linked to $APP" + exit 1 + fi + dokku_log_info1 "Service $SERVICE is linked to $APP" +} + +service_link() { + declare desc="link a service to an application" + declare SERVICE="$1" APP="$2" + update_plugin_scheme_for_app "$APP" + local SERVICE_URL=$(service_url "$SERVICE") + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local EXISTING_CONFIG=$(config_all "$APP") + local LINK=$(echo "$EXISTING_CONFIG" | grep "$SERVICE_URL" | cut -d: -f1) || true + local SERVICE_DNS_HOSTNAME=$(service_dns_hostname "$SERVICE") + local LINKS_FILE="$SERVICE_ROOT/LINKS" + local ALIAS="$PLUGIN_DEFAULT_ALIAS" + local DEFAULT_ALIAS + + if [[ -n "$SERVICE_ALIAS" ]]; then + ALIAS="$SERVICE_ALIAS" + ALIAS_IN_USE=$(echo "$EXISTING_CONFIG" | grep "${ALIAS}_URL") || true + [[ -n "$ALIAS_IN_USE" ]] && dokku_log_fail "Specified alias $ALIAS already in use" + else + DEFAULT_ALIAS=$(echo "$EXISTING_CONFIG" | grep "${PLUGIN_DEFAULT_ALIAS}_URL") || true + if [[ -n "$DEFAULT_ALIAS" ]]; then + ALIAS=$(service_alternative_alias "$EXISTING_CONFIG") + fi + [[ -z "$ALIAS" ]] && dokku_log_fail "Unable to use default or generated URL alias" + fi + + [[ -n $LINK ]] && dokku_log_fail "Already linked as $LINK" + plugn trigger service-action pre-link "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" + add_to_links_file "$SERVICE" "$APP" + + if declare -f -F add_passed_docker_option >/dev/null; then + # shellcheck disable=SC2034 + local passed_phases=(build deploy run) + add_passed_docker_option passed_phases[@] "--link $SERVICE_NAME:$SERVICE_DNS_HOSTNAME" + else + dokku docker-options:add "$APP" build,deploy,run "--link $SERVICE_NAME:$SERVICE_DNS_HOSTNAME" + fi + [[ -n "$SERVICE_QUERYSTRING" ]] && SERVICE_URL="${SERVICE_URL}?${SERVICE_QUERYSTRING}" + plugn trigger service-action post-link "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" + if [[ "$DOKKU_GLOBAL_FLAGS" == *"--no-restart"* ]] || [[ "$SERVICE_RESTART_APPS" == "false" ]]; then + config_set --no-restart "$APP" "${ALIAS}_URL=$SERVICE_URL" + dokku_log_verbose "Skipping restart of linked app" + else + config_set "$APP" "${ALIAS}_URL=$SERVICE_URL" + fi + plugn trigger service-action post-link-complete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" +} + +service_linked_apps() { + declare desc="list all apps linked to a service for info output" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + touch "$LINKS_FILE" + [[ -z $(<"$LINKS_FILE") ]] && echo '-' && return 0 + + tr '\n' ' ' <"$LINKS_FILE" +} + +service_links() { + declare desc="list all apps linked to a service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + touch "$LINKS_FILE" + [[ -z $(<"$LINKS_FILE") ]] && return 0 + + cat "$LINKS_FILE" +} + +service_list() { + declare desc="list all services and their status" + + mapfile -t services < <(fn-services-list true) + if [[ "${#services[@]}" -eq 0 ]] || [[ -z "$services" ]]; then + dokku_log_warn "There are no $PLUGIN_SERVICE services" + return + fi + + dokku_log_info2_quiet "$PLUGIN_SERVICE services" + for service in "${services[@]}"; do + echo "${service}" + done +} + +service_logs() { + declare desc="display logs for a service" + declare SERVICE="$1" TAIL_FLAG="$2" TAIL_COUNT="$3" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ID=$(cat "$SERVICE_ROOT/ID") + local RE_INTEGER='^[0-9]+$' + + DOKKU_LOGS_ARGS="--tail $TAIL_COUNT" + if [[ "$TAIL_FLAG" == "-t" ]] || [[ "$TAIL_FLAG" == "--tail" ]]; then + DOKKU_LOGS_ARGS+=" --follow" + fi + + "$DOCKER_BIN" container inspect "$ID" >/dev/null 2>&1 || dokku_log_fail "Service container does not exist" + is_container_status "$ID" "Running" || dokku_log_warn "Service logs may not be output as service is not running" + + # shellcheck disable=SC2086 + "$DOCKER_BIN" container logs $DOKKU_LOGS_ARGS "$ID" 2>&1 +} + +service_parse_args() { + declare desc="cli arg parser" + local next_index=1 + local skip=false + local args=("$@") + + for arg in "$@"; do + shift + case "$arg" in + "--alias") set -- "$@" "-a" ;; + "--config-options") set -- "$@" "-c" ;; + "--custom-env") set -- "$@" "-C" ;; + "--database") set -- "$@" "-d" ;; + "--image-version") set -- "$@" "-I" ;; + "--initial-network") set -- "$@" "-N" ;; + "--image") set -- "$@" "-i" ;; + "--memory") set -- "$@" "-m" ;; + "--no-restart") set -- "$@" "-n" ;; + "--password") set -- "$@" "-p" ;; + "--post-create-network") set -- "$@" "-P" ;; + "--post-start-network") set -- "$@" "-S" ;; + "--querystring") set -- "$@" "-q" ;; + "--restart-apps") set -- "$@" "-R" ;; + "--root-password") set -- "$@" "-r" ;; + "--shm-size") set -- "$@" "-s" ;; + "--user") set -- "$@" "-u" ;; + *) set -- "$@" "$arg" ;; + esac + done + + OPTIND=1 + while getopts "na:c:C:d:i:I:m:n:N:p:P:q:R:r:s:S:u:" opt; do + case "$opt" in + a) + SERVICE_ALIAS="${OPTARG^^}" + export SERVICE_ALIAS="${SERVICE_ALIAS%_URL}" + ;; + c) + export PLUGIN_CONFIG_OPTIONS=$OPTARG + ;; + C) + export SERVICE_CUSTOM_ENV=$OPTARG + ;; + d) + export SERVICE_DATABASE=$OPTARG + ;; + i) + export PLUGIN_IMAGE=$OPTARG + ;; + I) + export PLUGIN_IMAGE_VERSION=$OPTARG + ;; + m) + export SERVICE_MEMORY=$OPTARG + ;; + n) + export SERVICE_RESTART_APPS=false + ;; + N) + export SERVICE_INITIAL_NETWORK=$OPTARG + ;; + p) + export SERVICE_PASSWORD=$OPTARG + ;; + P) + export SERVICE_POST_CREATE_NETWORK=$OPTARG + ;; + q) + export SERVICE_QUERYSTRING=${OPTARG#"?"} + ;; + R) + export SERVICE_RESTART_APPS=$OPTARG + ;; + r) + export SERVICE_ROOT_PASSWORD=$OPTARG + ;; + s) + export SERVICE_SHM_SIZE=$OPTARG + ;; + S) + export SERVICE_POST_START_NETWORK=$OPTARG + ;; + u) + export SERVICE_USER=$OPTARG + ;; + esac + done + shift "$((OPTIND - 1))" # remove options from positional parameters +} + +service_password() { + declare desc="fetch the password for a service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PASSWORD_FILE="$SERVICE_ROOT/PASSWORD" + if [[ -f "$PASSWORD_FILE" ]]; then + cat "$PASSWORD_FILE" + fi +} + +service_root_password() { + declare desc="fetch the root password for a service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PASSWORD_FILE="$SERVICE_ROOT/ROOTPASSWORD" + if [[ -f "$PASSWORD_FILE" ]]; then + cat "$PASSWORD_FILE" + fi +} + +service_port_expose() { + declare desc="wrapper for exposing service ports" + declare SERVICE="$1" PORTS=(${@:2}) + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PORT_FILE="$SERVICE_ROOT/PORT" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local EXPOSED_NAME="$SERVICE_NAME.ambassador" + + if [[ ${#PORTS[@]} -eq 0 ]]; then + # shellcheck disable=SC2206 + PORTS=(${PORTS[@]:-$(get_random_ports ${#PLUGIN_DATASTORE_PORTS[@]})}) + fi + + [[ "${#PORTS[@]}" != "${#PLUGIN_DATASTORE_PORTS[@]}" ]] && dokku_log_fail "${#PLUGIN_DATASTORE_PORTS[@]} ports to be exposed need to be provided in the following order: ${PLUGIN_DATASTORE_PORTS[*]}" + + if [[ -s "$PORT_FILE" ]]; then + # shellcheck disable=SC2207 + PORTS=($(cat "$PORT_FILE")) + dokku_log_fail "Service $SERVICE already exposed on port(s) ${PORTS[*]}" + fi + + if "$DOCKER_BIN" container inspect "$EXPOSED_NAME" >/dev/null 2>&1; then + dokku_log_warn "Service $SERVICE has an untracked expose container, removing" + "$DOCKER_BIN" container stop "$EXPOSED_NAME" >/dev/null 2>&1 || true + suppress_output "$DOCKER_BIN" container rm "$EXPOSED_NAME" + fi + + echo "${PORTS[@]}" >"$PORT_FILE" + + service_start "$SERVICE" "true" + service_port_reconcile_status "$SERVICE" + dokku_log_info1 "Service $SERVICE exposed on port(s) [container->host]: $(service_exposed_ports "$SERVICE")" +} + +service_port_unexpose() { + declare desc="wrapper for pausing exposed service ports" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PORT_FILE="$SERVICE_ROOT/PORT" + + rm -rf "$PORT_FILE" + service_port_reconcile_status "$SERVICE" + dokku_log_info1 "Service $SERVICE unexposed" +} + +service_port_reconcile_status() { + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PORT_FILE="$SERVICE_ROOT/PORT" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local EXPOSED_NAME="$SERVICE_NAME.ambassador" + + if [[ ! -s "$PORT_FILE" ]]; then + if "$DOCKER_BIN" container inspect "$EXPOSED_NAME" >/dev/null 2>&1; then + "$DOCKER_BIN" container stop "$EXPOSED_NAME" >/dev/null 2>&1 || true + suppress_output "$DOCKER_BIN" container rm "$EXPOSED_NAME" + return $? + fi + return + fi + + if is_container_status "$EXPOSED_NAME" "Running"; then + return + fi + + if "$DOCKER_BIN" container inspect "$EXPOSED_NAME" >/dev/null 2>&1; then + suppress_output "$DOCKER_BIN" container start "$EXPOSED_NAME" + return $? + fi + + # shellcheck disable=SC2207 + PORTS=($(cat "$PORT_FILE")) + # shellcheck disable=SC2046 + "$DOCKER_BIN" container run -d --link "$SERVICE_NAME:$PLUGIN_COMMAND_PREFIX" --name "$EXPOSED_NAME" $(docker_ports_options "${PORTS[@]}") --restart always --label dokku=ambassador --label "dokku.ambassador=$PLUGIN_COMMAND_PREFIX" "$PLUGIN_AMBASSADOR_IMAGE" >/dev/null +} + +service_promote() { + declare desc="promote a secondary service to the primary env var" + declare SERVICE="$1" APP="$2" + local PLUGIN_DEFAULT_CONFIG_VAR="${PLUGIN_DEFAULT_ALIAS}_URL" + local EXISTING_CONFIG=$(config_all "$APP") + update_plugin_scheme_for_app "$APP" + local SERVICE_URL=$(service_url "$SERVICE") + local CONFIG_VARS=($(echo "$EXISTING_CONFIG" | grep "$SERVICE_URL" | cut -d: -f1)) || true + local PREVIOUS_DEFAULT_URL=$(get_url_from_config "$EXISTING_CONFIG" "$PLUGIN_DEFAULT_CONFIG_VAR") + + [[ -z ${CONFIG_VARS[*]} ]] && dokku_log_fail "Not linked to app $APP" + [[ ${CONFIG_VARS[*]} =~ $PLUGIN_DEFAULT_CONFIG_VAR ]] && dokku_log_fail "Service $1 already promoted as $PLUGIN_DEFAULT_CONFIG_VAR" + + local NEW_CONFIG_VARS="" + if [[ -n $PREVIOUS_DEFAULT_URL ]]; then + local PREVIOUS_ALIAS=$(echo "$EXISTING_CONFIG" | grep "$PREVIOUS_DEFAULT_URL" | grep -v "$PLUGIN_DEFAULT_CONFIG_VAR") || true + if [[ -z $PREVIOUS_ALIAS ]]; then + local ALIAS=$(service_alternative_alias "$EXISTING_CONFIG") + NEW_CONFIG_VARS+="${ALIAS}_URL=$PREVIOUS_DEFAULT_URL " + fi + fi + local PROMOTE_URL=$(get_url_from_config "$EXISTING_CONFIG" "${CONFIG_VARS[0]}") + NEW_CONFIG_VARS+="$PLUGIN_DEFAULT_CONFIG_VAR=$PROMOTE_URL" + + # shellcheck disable=SC2086 + config_set "$APP" $NEW_CONFIG_VARS +} + +service_set_alias() { + declare desc="sets the alias in use for a service" + declare SERVICE="$1" ALIAS="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ALIAS_FILE="$SERVICE_ROOT/ALIAS" + + touch "$ALIAS_FILE" + echo "$ALIAS" >"$ALIAS_FILE" +} + +service_status() { + declare desc="display the status of a service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ID="$(cat "$SERVICE_ROOT/ID")" + local CONTAINER_STATUS + + CONTAINER_STATUS=$("$DOCKER_BIN" container inspect -f "{{.State.Status}}" "$ID" 2>/dev/null || true) + [[ -n "$CONTAINER_STATUS" ]] && echo "$CONTAINER_STATUS" && return 0 + echo "missing" && return 0 +} + +service_pause() { + declare desc="pause a running service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local ID=$("$DOCKER_BIN" container ps -aq --no-trunc --filter "name=^/$SERVICE_NAME$") || true + [[ -z $ID ]] && dokku_log_warn "Service is already paused" && return 0 + + if [[ -n $ID ]]; then + dokku_log_info2_quiet "Pausing container" + "$DOCKER_BIN" container stop "$SERVICE_NAME" >/dev/null + if "$DOCKER_BIN" container inspect "$ID" >/dev/null 2>&1; then + "$DOCKER_BIN" container stop "$SERVICE_NAME.ambassador" >/dev/null 2>&1 || true + fi + dokku_log_verbose_quiet "Container paused" + else + dokku_log_verbose_quiet "No container exists for $SERVICE" + fi +} + +service_unlink() { + declare desc="unlink an application from a service" + declare SERVICE="$1" APP="$2" + update_plugin_scheme_for_app "$APP" + local SERVICE_URL=$(service_url "$SERVICE") + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local EXISTING_CONFIG=$(config_all "$APP") + local SERVICE_DNS_HOSTNAME=$(service_dns_hostname "$SERVICE") + local LINK=($(echo "$EXISTING_CONFIG" | grep "$SERVICE_URL" | cut -d: -f1)) || true + + plugn trigger service-action pre-unlink "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" + remove_from_links_file "$SERVICE" "$APP" + + if declare -f -F add_passed_docker_option >/dev/null; then + # shellcheck disable=SC2034 + local passed_phases=(build deploy run) + remove_passed_docker_option passed_phases[@] "--link $SERVICE_NAME:$SERVICE_DNS_HOSTNAME" + else + dokku docker-options:remove "$APP" build,deploy,run "--link $SERVICE_NAME:$SERVICE_DNS_HOSTNAME" + fi + + [[ -z ${LINK[*]} ]] && dokku_log_fail "Not linked to app $APP" + plugn trigger service-action post-unlink "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" + if [[ "$DOKKU_GLOBAL_FLAGS" == *"--no-restart"* ]] || [[ "$SERVICE_RESTART_APPS" == "false" ]]; then + config_unset --no-restart "$APP" "${LINK[@]}" + dokku_log_verbose "Skipping restart of linked app" + else + config_unset "$APP" "${LINK[@]}" + fi + plugn trigger service-action post-unlink-complete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" +} + +service_version() { + declare desc="display the running version for an image" + declare SERVICE="$1" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + "$DOCKER_BIN" container inspect -f '{{.Config.Image}}' "$SERVICE_NAME" 2>/dev/null || true +} + +update_plugin_scheme_for_app() { + declare desc="retrieve the updated plugin scheme" + declare APP="$1" + local DATABASE_SCHEME + + DATABASE_SCHEME=$(config_get "$APP" "${PLUGIN_VARIABLE}_DATABASE_SCHEME" || true) + PLUGIN_SCHEME=${DATABASE_SCHEME:-$PLUGIN_SCHEME} +} + +verify_service_name() { + declare desc="verify that a service exists" + declare SERVICE="$@" + + if [[ -z "$SERVICE" ]]; then + dokku_log_fail "SERVICE must not be empty" + fi + + if [[ ! -d "$PLUGIN_DATA_ROOT/$SERVICE" ]]; then + dokku_log_fail "$PLUGIN_SERVICE service $SERVICE does not exist" + fi + + SERVICE="$(auth_service_filter "$SERVICE")" + if [[ -z "$SERVICE" ]]; then + dokku_log_fail "$PLUGIN_SERVICE service $SERVICE does not exist" + fi + + return 0 +} + +write_database_name() { + declare desc="write a sanitized database name" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + + # some datastores do not like special characters in database names + # so we need to normalize them out + echo "$SERVICE" | tr .- _ >"$SERVICE_ROOT/DATABASE_NAME" +} diff --git a/plugins/mysql/config b/plugins/mysql/config new file mode 100644 index 000000000..892c86ef8 --- /dev/null +++ b/plugins/mysql/config @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +export MYSQL_IMAGE=${MYSQL_IMAGE:="$(awk -F '[ :]' '{print $2}' "${_DIR}/Dockerfile")"} +export MYSQL_IMAGE_VERSION=${MYSQL_IMAGE_VERSION:="$(awk -F '[ :]' '{print $3}' "${_DIR}/Dockerfile")"} +export MYSQL_ROOT=${MYSQL_ROOT:="$DOKKU_LIB_ROOT/services/mysql"} +export DOKKU_LIB_HOST_ROOT=${DOKKU_LIB_HOST_ROOT:=$DOKKU_LIB_ROOT} +export MYSQL_HOST_ROOT=${MYSQL_HOST_ROOT:="$DOKKU_LIB_HOST_ROOT/services/mysql"} + +export PLUGIN_UNIMPLEMENTED_SUBCOMMANDS=() +export PLUGIN_COMMAND_PREFIX="mysql" +export PLUGIN_CONFIG_ROOT=${PLUGIN_CONFIG_ROOT:="$DOKKU_LIB_ROOT/config/$PLUGIN_COMMAND_PREFIX"} +export PLUGIN_DATA_ROOT=$MYSQL_ROOT +export PLUGIN_DATA_HOST_ROOT=$MYSQL_HOST_ROOT +export PLUGIN_DATASTORE_PORTS=(3306) +export PLUGIN_DATASTORE_WAIT_PORT=3306 +export PLUGIN_DEFAULT_ALIAS="DATABASE" +export PLUGIN_DISABLE_PULL=${MYSQL_DISABLE_PULL:=} +export PLUGIN_DISABLE_PULL_VARIABLE="MYSQL_DISABLE_PULL" +export PLUGIN_ALT_ALIAS="DOKKU_MYSQL" +export PLUGIN_IMAGE=$MYSQL_IMAGE +export PLUGIN_IMAGE_VERSION=$MYSQL_IMAGE_VERSION +export PLUGIN_SCHEME="mysql" +export PLUGIN_SERVICE="MySQL" +export PLUGIN_VARIABLE="MYSQL" +export PLUGIN_BASE_PATH="$PLUGIN_PATH" +export PLUGIN_CONFIG_SUFFIX="config" +if [[ -n $DOKKU_API_VERSION ]]; then + export PLUGIN_BASE_PATH="$PLUGIN_ENABLED_PATH" +fi + +export PLUGIN_BUSYBOX_IMAGE=${PLUGIN_BUSYBOX_IMAGE:=busybox:1.37.0-uclibc} +export PLUGIN_AMBASSADOR_IMAGE=${PLUGIN_AMBASSADOR_IMAGE:=dokku/ambassador:0.8.2} +export PLUGIN_S3BACKUP_IMAGE=${PLUGIN_S3BACKUP_IMAGE:=dokku/s3backup:0.18.0} +export PLUGIN_WAIT_IMAGE=${PLUGIN_WAIT_IMAGE:=dokku/wait:0.9.3} + +export MYSQL_CONFIG_OPTIONS=${MYSQL_CONFIG_OPTIONS:=""} diff --git a/plugins/mysql/docs/README.md b/plugins/mysql/docs/README.md new file mode 100644 index 000000000..468382b40 --- /dev/null +++ b/plugins/mysql/docs/README.md @@ -0,0 +1,4 @@ +# Extra Documentation + +The documentation in this folder is supplemental to using this plugin. It is injected automatically into the plugin's readme during documentation generation. + diff --git a/plugins/mysql/functions b/plugins/mysql/functions new file mode 100755 index 000000000..4bc1c84ba --- /dev/null +++ b/plugins/mysql/functions @@ -0,0 +1,237 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" +if [[ -f "$PLUGIN_AVAILABLE_PATH/docker-options/functions" ]]; then + source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" +fi + +service_connect() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local DATABASE_NAME="$(get_database_name "$SERVICE")" + local PASSWORD="$(service_password "$SERVICE")" + local SERVICE_TTY_OPTS + has_tty && SERVICE_TTY_OPTS="-t" + + "$DOCKER_BIN" container exec --env=LANG=C.UTF-8 --env=LC_ALL=C.UTF-8 -i $SERVICE_TTY_OPTS "$SERVICE_NAME" mysql --user=mysql --password="$PASSWORD" --database="$DATABASE_NAME" +} + +service_create() { + local SERVICE="$1" + is_valid_service_name "$SERVICE" || dokku_log_fail "Please specify a valid name for the service. Valid characters are: [A-Za-z0-9_]+" + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ ! -d "$PLUGIN_DATA_ROOT/$SERVICE" ]] || dokku_log_fail "$PLUGIN_SERVICE service $SERVICE already exists" + SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + LINKS_FILE="$SERVICE_ROOT/LINKS" + + service_parse_args "${@:2}" + + if ! service_image_exists "$SERVICE"; then + if [[ "$PLUGIN_DISABLE_PULL" == "true" ]]; then + dokku_log_warn "${PLUGIN_DISABLE_PULL_VARIABLE} environment variable detected. Not running pull command." 1>&2 + dokku_log_warn " docker image pull ${IMAGE}" 1>&2 + dokku_log_warn "$PLUGIN_SERVICE service creation failed" + exit 1 + fi + "$DOCKER_BIN" image pull "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" || dokku_log_fail "$PLUGIN_SERVICE image $PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION pull failed" + fi + + plugn trigger service-action pre-create "$PLUGIN_COMMAND_PREFIX" "$SERVICE" + mkdir -p "$SERVICE_ROOT" || dokku_log_fail "Unable to create service directory" + mkdir -p "$SERVICE_ROOT/data" || dokku_log_fail "Unable to create service data directory" + mkdir -p "$SERVICE_ROOT/$PLUGIN_CONFIG_SUFFIX" || dokku_log_fail "Unable to create service config directory" + touch "$LINKS_FILE" + + echo -e "[mysqld]\nperformance_schema = 0" >"$SERVICE_ROOT/$PLUGIN_CONFIG_SUFFIX/disable_performance_schema.cnf" + echo -e "[mysqld]\ncharacter-set-server = utf8mb4\ncollation-server = utf8mb4_unicode_ci" >"$SERVICE_ROOT/$PLUGIN_CONFIG_SUFFIX/charset_utf8.cnf" + ROOTPASSWORD=$(openssl rand -hex 8) + PASSWORD=$(openssl rand -hex 8) + if [[ -n "$SERVICE_PASSWORD" ]]; then + PASSWORD="$SERVICE_PASSWORD" + dokku_log_warn "Specified password may not be as secure as the auto-generated password" + fi + if [[ -n "$SERVICE_ROOT_PASSWORD" ]]; then + ROOTPASSWORD="$SERVICE_ROOT_PASSWORD" + dokku_log_warn "Specified root password may not be as secure as the auto-generated root password" + fi + echo "$ROOTPASSWORD" >"$SERVICE_ROOT/ROOTPASSWORD" + echo "$PASSWORD" >"$SERVICE_ROOT/PASSWORD" + chmod 640 "$SERVICE_ROOT/ROOTPASSWORD" "$SERVICE_ROOT/PASSWORD" + + service_commit_config "$SERVICE" + write_database_name "$SERVICE" + plugn trigger service-action post-create "$PLUGIN_COMMAND_PREFIX" "$SERVICE" + service_create_container "$SERVICE" + plugn trigger service-action post-create-complete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" +} + +service_create_container() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_HOST_ROOT="$PLUGIN_DATA_HOST_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local ROOTPASSWORD="$(service_root_password "$SERVICE")" + local PASSWORD="$(service_password "$SERVICE")" + local DATABASE_NAME="$(get_database_name "$SERVICE")" + local MEMORY_LIMIT="" + + if [[ -f "$SERVICE_ROOT/CONFIG_OPTIONS" ]]; then + export CONFIG_OPTIONS="$(cat "$SERVICE_ROOT/CONFIG_OPTIONS")" + fi + + local network_alias="$(service_dns_hostname "$SERVICE")" + + rm -f "$SERVICE_ROOT/ID" + declare -a DOCKER_ARGS + DOCKER_ARGS=() + DOCKER_ARGS+=("--cidfile=$SERVICE_ROOT/ID") + DOCKER_ARGS+=("--env-file=$SERVICE_ROOT/ENV") + DOCKER_ARGS+=("--env=MYSQL_DATABASE=$DATABASE_NAME") + DOCKER_ARGS+=("--env=MYSQL_PASSWORD=$PASSWORD") + DOCKER_ARGS+=("--env=MYSQL_ROOT_PASSWORD=$ROOTPASSWORD") + DOCKER_ARGS+=("--env=MYSQL_USER=mysql") + DOCKER_ARGS+=("--hostname=$SERVICE_NAME") + DOCKER_ARGS+=("--label=dokku.service=$PLUGIN_COMMAND_PREFIX") + DOCKER_ARGS+=("--label=dokku=service") + DOCKER_ARGS+=("--name=$SERVICE_NAME") + DOCKER_ARGS+=("--restart=always") + DOCKER_ARGS+=("--volume=$SERVICE_HOST_ROOT/$PLUGIN_CONFIG_SUFFIX:/etc/mysql/conf.d") + DOCKER_ARGS+=("--volume=$SERVICE_HOST_ROOT/data:/var/lib/mysql") + + declare -a LINK_CONTAINER_DOCKER_ARGS + LINK_CONTAINER_DOCKER_ARGS=() + LINK_CONTAINER_DOCKER_ARGS+=("--rm") + LINK_CONTAINER_DOCKER_ARGS+=("--link") + LINK_CONTAINER_DOCKER_ARGS+=("$SERVICE_NAME:$network_alias") + + [[ -f "$SERVICE_ROOT/SERVICE_MEMORY" ]] && SERVICE_MEMORY="$(cat "$SERVICE_ROOT/SERVICE_MEMORY")" + if [[ -n "$SERVICE_MEMORY" ]]; then + DOCKER_ARGS+=("--memory=${SERVICE_MEMORY}m") + fi + + [[ -f "$SERVICE_ROOT/SHM_SIZE" ]] && SERVICE_SHM_SIZE="$(cat "$SERVICE_ROOT/SHM_SIZE")" + if [[ -n "$SERVICE_SHM_SIZE" ]]; then + DOCKER_ARGS+=("--shm-size=${SERVICE_SHM_SIZE}") + fi + + [[ -f "$SERVICE_ROOT/IMAGE" ]] && PLUGIN_IMAGE="$(cat "$SERVICE_ROOT/IMAGE")" + [[ -f "$SERVICE_ROOT/IMAGE_VERSION" ]] && PLUGIN_IMAGE_VERSION="$(cat "$SERVICE_ROOT/IMAGE_VERSION")" + + local network="$(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "initial-network")" + if [[ -n "$network" ]]; then + DOCKER_ARGS+=("--network=${network}") + DOCKER_ARGS+=("--network-alias=${network_alias}") + LINK_CONTAINER_DOCKER_ARGS+=("--network=${network}") + fi + + # shellcheck disable=SC2086 + suppress_output "$DOCKER_BIN" container create "${DOCKER_ARGS[@]}" "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" $CONFIG_OPTIONS + + if [[ -n "$(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-create-network")" ]]; then + dokku_log_verbose_quiet "Connecting to networks after container create" + while read -r line || [[ -n "$line" ]]; do + dokku_log_verbose_quiet "- $line" + "$DOCKER_BIN" network connect --alias "$network_alias" "$line" "$SERVICE_NAME" + done < <(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-create-network" | tr "," "\n") + fi + suppress_output "$DOCKER_BIN" container start "$(cat "$SERVICE_ROOT/ID")" + service_port_reconcile_status "$SERVICE" + + if [[ -n "$(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-start-network")" ]]; then + dokku_log_verbose_quiet "Connecting to networks after container start" + while read -r line || [[ -n "$line" ]]; do + dokku_log_verbose_quiet "- $line" + "$DOCKER_BIN" network connect --alias "$network_alias" "$line" "$SERVICE_NAME" + done < <(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-start-network" | tr "," "\n") + fi + + dokku_log_verbose_quiet "Waiting for container to be ready" + if ! suppress_output "$DOCKER_BIN" container run "${LINK_CONTAINER_DOCKER_ARGS[@]}" "$PLUGIN_WAIT_IMAGE" -c "$network_alias:$PLUGIN_DATASTORE_WAIT_PORT"; then + dokku_log_info2_quiet "Start of $SERVICE container output" + dokku_container_log_verbose_quiet "$SERVICE_NAME" + dokku_log_info2_quiet "End of $SERVICE container output" + return 1 + fi + + dokku_log_info2 "$PLUGIN_SERVICE container created: $SERVICE" + service_info "$SERVICE" +} + +service_export() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local DATABASE_NAME="$(get_database_name "$SERVICE")" + local ROOTPASSWORD="$(service_root_password "$SERVICE")" + + [[ -n $SSH_TTY ]] && stty -opost + "$DOCKER_BIN" container exec "$SERVICE_NAME" mysqldump --default-character-set=utf8mb4 --user=root --password="$ROOTPASSWORD" --single-transaction --no-tablespaces --quick "$DATABASE_NAME" + status=$? + [[ -n $SSH_TTY ]] && stty opost + exit $status +} + +service_import() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local SERVICE_HOST_ROOT="$PLUGIN_DATA_HOST_ROOT/$SERVICE" + local DATABASE_NAME="$(get_database_name "$SERVICE")" + local ROOTPASSWORD="$(service_root_password "$SERVICE")" + + if [[ -t 0 ]]; then + dokku_log_fail "No data provided on stdin." + fi + "$DOCKER_BIN" container exec -i "$SERVICE_NAME" mysql --user=root --password="$ROOTPASSWORD" "$DATABASE_NAME" +} + +service_start() { + local SERVICE="$1" + local QUIET="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local ID=$("$DOCKER_BIN" container ps -aq --no-trunc --filter "status=running" --filter "name=^/$SERVICE_NAME$") || true + if [[ -n $ID ]]; then + [[ -z $QUIET ]] && dokku_log_warn "Service is already started" + if [[ ! -f "$SERVICE_ROOT/ID" ]] || [[ "$(cat "$SERVICE_ROOT/ID")" != "$ID" ]]; then + [[ -z $QUIET ]] && dokku_log_warn "Updating local container ID" + echo "$ID" >"$SERVICE_ROOT/ID" + fi + return 0 + fi + + dokku_log_info2_quiet "Starting container" + local PREVIOUS_ID=$("$DOCKER_BIN" container ps -aq --no-trunc --filter "status=exited" --filter "name=^/$SERVICE_NAME$") || true + local ROOTPASSWORD="$(service_root_password "$SERVICE")" + local PASSWORD="$(service_password "$SERVICE")" + + if [[ -n $PREVIOUS_ID ]]; then + "$DOCKER_BIN" container start "$PREVIOUS_ID" >/dev/null + service_port_reconcile_status "$SERVICE" + dokku_log_info2 "Container started" + elif service_image_exists "$SERVICE" && [[ -n "$ROOTPASSWORD" ]] && [[ -n "$PASSWORD" ]]; then + service_create_container "$SERVICE" + else + if ! service_image_exists "$SERVICE"; then + [[ -f "$SERVICE_ROOT/IMAGE" ]] && PLUGIN_IMAGE="$(cat "$SERVICE_ROOT/IMAGE")" + [[ -f "$SERVICE_ROOT/IMAGE_VERSION" ]] && PLUGIN_IMAGE_VERSION="$(cat "$SERVICE_ROOT/IMAGE_VERSION")" + dokku_log_verbose_quiet "Missing image $PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION for $SERVICE" + else + dokku_log_verbose_quiet "Neither container nor valid configuration exists for $SERVICE" + fi + fi +} + +service_url() { + local SERVICE="$1" + local SERVICE_DNS_HOSTNAME="$(service_dns_hostname "$SERVICE")" + local DATABASE_NAME="$(get_database_name "$SERVICE")" + local PASSWORD="$(service_password "$SERVICE")" + echo "$PLUGIN_SCHEME://mysql:$PASSWORD@$SERVICE_DNS_HOSTNAME:${PLUGIN_DATASTORE_PORTS[0]}/$DATABASE_NAME" +} diff --git a/plugins/mysql/help-functions b/plugins/mysql/help-functions new file mode 100755 index 000000000..3ff6dc9c4 --- /dev/null +++ b/plugins/mysql/help-functions @@ -0,0 +1,320 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +export SUBCOMMAND_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/subcommands" + +fn-help() { + declare CMD="$1" + local cmd EXIT_CODE + + if [[ "$CMD" == "help" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:help" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:default" ]]; then + fn-help-all "$@" + exit 0 + fi + + pushd "$SUBCOMMAND_ROOT" >/dev/null 2>&1 + for cmd in *; do + if [[ "$CMD" == "${PLUGIN_COMMAND_PREFIX}:$cmd" ]]; then + "$SUBCOMMAND_ROOT/$cmd" "$@" + EXIT_CODE="$?" + exit "$EXIT_CODE" + fi + done + popd >/dev/null 2>&1 + + exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +} + +fn-help-all() { + declare CMD="$1" SUBCOMMAND="$2" + local CMD_OUTPUT BLUE BOLD FULL_OUTPUT NORMAL + FULL_OUTPUT=true + + if [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:help" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:default" ]]; then + BOLD="$(fn-help-fancy-tput bold)" + NORMAL="$(fn-help-fancy-color "\033[m")" + BLUE="$(fn-help-fancy-color "\033[0;34m")" + CYAN="$(fn-help-fancy-color "\033[1;36m")" + if [[ -n "$SUBCOMMAND" ]] && [[ "$SUBCOMMAND" != "--all" ]]; then + fn-help-contents-subcommand "$SUBCOMMAND" "$FULL_OUTPUT" + return "$?" + fi + + echo -e "${BOLD}usage${NORMAL}: dokku ${PLUGIN_COMMAND_PREFIX}[:COMMAND]" + echo '' + echo -e "${BOLD}List your $PLUGIN_COMMAND_PREFIX services.${NORMAL}" + echo '' + echo -e "${BLUE}Example:${NORMAL}" + echo '' + echo " \$ dokku $PLUGIN_COMMAND_PREFIX:list" + echo '' + fn-help-list-example | column -c5 -t -s, + echo '' + echo -e "dokku ${BOLD}${PLUGIN_COMMAND_PREFIX}${NORMAL} commands: (get help with ${CYAN}dokku ${PLUGIN_COMMAND_PREFIX}:help SUBCOMMAND${NORMAL})" + echo '' + fn-help-contents | sort | column -c2 -t -s, + echo '' + elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then + fn-help-contents + else + cat </dev/null 2>&1 + for cmd in *; do + fn-help-contents-subcommand "$cmd" || true + done +} + +fn-help-contents-subcommand() { + declare SUBCOMMAND="$1" FULL_OUTPUT="$2" + local HELP_TMPDIR=$(mktemp -d --tmpdir) + local UNCLEAN_FILE="${HELP_TMPDIR}/cmd-unclean" CLEAN_FILE="${HELP_TMPDIR}/cmd-clean" + local BOLD CMD_OUTPUT CYAN EXAMPLE LIGHT_GRAY NORMAL + trap 'rm -rf "$HELP_TMPDIR" > /dev/null' RETURN INT TERM EXIT + + rm -rf "$UNCLEAN_FILE" "$CLEAN_FILE" + cat "$SUBCOMMAND_ROOT/$SUBCOMMAND" >"$UNCLEAN_FILE" + + fn-help-subcommand-sanitize "$UNCLEAN_FILE" "$CLEAN_FILE" + if ! is_implemented_command "$SUBCOMMAND"; then + return 1 + fi + + args="$(fn-help-subcommand-args "$CLEAN_FILE" "$FULL_OUTPUT")" + SUBCOMMAND=":$SUBCOMMAND" + [[ "$SUBCOMMAND" == ":default" ]] && SUBCOMMAND="" + cmd_line="$(echo -e "${SUBCOMMAND} ${args}" | sed -e 's/[[:space:]]*$//')" + desc="$(grep desc "$CLEAN_FILE" | head -1)" + eval "$desc" + + BLUE="$(fn-help-fancy-color "\033[0;34m")" + BOLD="$(fn-help-fancy-tput bold)" + CYAN="$(fn-help-fancy-color "\033[1;36m")" + NORMAL="$(fn-help-fancy-color "\033[m")" + LIGHT_GRAY="$(fn-help-fancy-color "\033[2;37m")" + LIGHT_RED="$(fn-help-fancy-color "\033[1;31m")" + CMD_OUTPUT="$(echo -e " ${PLUGIN_COMMAND_PREFIX}${cmd_line}, ${LIGHT_GRAY}${desc}${NORMAL}")" + if [[ "$FULL_OUTPUT" != "true" ]]; then + echo "$CMD_OUTPUT" + return 0 + fi + + echo -e "${BOLD}usage:${NORMAL} dokku ${PLUGIN_COMMAND_PREFIX}${cmd_line}" + echo '' + echo -e "${BOLD}${desc}${NORMAL}" + echo '' + + ARGS="$(fn-help-subcommand-list-args "$CLEAN_FILE")" + if [[ -n "$ARGS" ]]; then + echo -e "${CYAN}arguments:${NORMAL}" + echo '' + echo "$ARGS" | column -c2 -t -s, + echo '' + fi + + FLAGS="$(fn-help-subcommand-list-flags "$CLEAN_FILE")" + if [[ -n "$FLAGS" ]]; then + echo -e "${BLUE}flags:${NORMAL}" + echo '' + echo "$FLAGS" | column -c2 -t -s, + echo '' + fi + + EXAMPLE="$(fn-help-subcommand-example "$CLEAN_FILE")" + if [[ -n "$EXAMPLE" ]]; then + echo -e "${LIGHT_RED}examples:${NORMAL}" + echo '' + echo "$EXAMPLE" + echo '' + fi + + return 0 +} + +fn-help-fancy-tput() { + declare desc="a wrapper around tput" + + if [[ -n "$DOKKU_NO_COLOR" ]] || [[ "$TERM" == "unknown" ]] || [[ "$TERM" == "dumb" ]]; then + return + fi + + tput "$@" +} + +fn-help-fancy-color() { + declare desc="a wrapper around colors" + + if [[ -n "$DOKKU_NO_COLOR" ]] || [[ "$TERM" == "unknown" ]] || [[ "$TERM" == "dumb" ]]; then + return + fi + + echo "$@" +} + +fn-help-list-example() { + # shellcheck disable=SC2034 + declare desc="return $PLUGIN_COMMAND_PREFIX plugin help content" + cat <* ]] && line="\n ${BOLD}${line}${NORMAL}" + # shellcheck disable=SC2001 + [[ "$line" == " "* ]] && line=" ${OTHER_GRAY}$(echo "$line" | sed -e 's/^[[:space:]]*//')${NORMAL}" + echo -e "${NEWLINE}${line}" + LAST_LINE="sentence" + NEWLINE="\n" + fi + done +} + +fn-help-subcommand-list-args() { + declare FUNC_FILE="$1" + local EXAMPLE LIGHT_GRAY NORMAL + + FLAGS=$(grep "#A" "$FUNC_FILE" | cut -d'A' -f2- | sed -e 's/^[[:space:]]*//' || true) + if [[ -z "$FLAGS" ]]; then + return 0 + fi + + NORMAL="$(fn-help-fancy-color "\033[m")" + LIGHT_GRAY="$(fn-help-fancy-color "\033[2;37m")" + + _fn-help-apply-shell-expansion "$FLAGS" | while read -r line; do + echo -e "$(echo "$line" | cut -d',' -f1),${LIGHT_GRAY}$(echo "$line" | cut -d',' -f2-)${NORMAL}" + done +} + +fn-help-subcommand-list-flags() { + declare FUNC_FILE="$1" + local EXAMPLE LIGHT_GRAY NORMAL + + FLAGS=$(grep "#F" "$FUNC_FILE" | cut -d'F' -f2- | sed -e 's/^[[:space:]]*//' || true) + if [[ -z "$FLAGS" ]]; then + return 0 + fi + + NORMAL="$(fn-help-fancy-color "\033[m")" + LIGHT_GRAY="$(fn-help-fancy-color "\033[2;37m")" + + _fn-help-apply-shell-expansion "$FLAGS" | while read -r line; do + echo -e "$(echo "$line" | cut -d',' -f1),${LIGHT_GRAY}$(echo "$line" | cut -d',' -f2-)${NORMAL}" + done +} + +fn-help-subcommand-sanitize() { + declare FUNC_FILE="$1" OUTGOING_FUNC_FILE="$2" + local FUNCTION_FOUND=false + local IFS OIFS + + touch "$OUTGOING_FUNC_FILE" + + OIFS="$IFS" + IFS=, + while read -r p; do + IFS="$OIFS" + if [[ "$p" == *"-cmd \"\$@\""* ]] || [[ "$p" == "" ]]; then + continue + fi + + if [[ "$FUNCTION_FOUND" == true ]]; then + echo "$p" >>"$OUTGOING_FUNC_FILE" + continue + fi + + if [[ "$p" == *"()"* ]]; then + FUNCTION_FOUND=true + echo "$p" >>"$OUTGOING_FUNC_FILE" + continue + fi + done <"$FUNC_FILE" +} + +_fn-help-apply-shell-expansion() { + declare desc="expand environment variables for a shell command" + declare data="$1" + declare delimiter="__apply_shell_expansion_delimiter__" + declare command="cat <<$delimiter"$'\n'"$data"$'\n'"$delimiter" + eval "$command" +} diff --git a/plugins/mysql/install b/plugins/mysql/install new file mode 100755 index 000000000..f8fc40418 --- /dev/null +++ b/plugins/mysql/install @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-install() { + pull-docker-image() { + declare IMAGE="$1" + if [[ "$PLUGIN_DISABLE_PULL" == "true" ]]; then + echo " ! ${PLUGIN_DISABLE_PULL_VARIABLE} environment variable detected. Not running pull command." 1>&2 + echo " ! docker image pull ${IMAGE}" 1>&2 + return + fi + if [[ "$("$DOCKER_BIN" image ls -q "${IMAGE}" 2>/dev/null)" == "" ]]; then + "$DOCKER_BIN" image pull "${IMAGE}" + fi + } + + fn-plugin-property-setup "$PLUGIN_COMMAND_PREFIX" + pull-docker-image "${PLUGIN_IMAGE}:${PLUGIN_IMAGE_VERSION}" + pull-docker-image "$PLUGIN_BUSYBOX_IMAGE" + pull-docker-image "$PLUGIN_AMBASSADOR_IMAGE" + pull-docker-image "$PLUGIN_S3BACKUP_IMAGE" + pull-docker-image "$PLUGIN_WAIT_IMAGE" + + mkdir -p "$PLUGIN_DATA_ROOT" || echo "Failed to create $PLUGIN_SERVICE data directory" + chown "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "$PLUGIN_DATA_ROOT" + + mkdir -p "$PLUGIN_CONFIG_ROOT" || echo "Failed to create $PLUGIN_SERVICE config directory" + chown "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "$PLUGIN_CONFIG_ROOT" + + rm -f "/etc/sudoers.d/dokku-${PLUGIN_COMMAND_PREFIX}*" + _SUDOERS_FILE="/etc/sudoers.d/dokku-${PLUGIN_COMMAND_PREFIX}" + + touch "$_SUDOERS_FILE" + cat >"$_SUDOERS_FILE" <"$SERVICE_ROOT/IMAGE" + echo "${image##*:}" >"$SERVICE_ROOT/IMAGE_VERSION" + fi + fi + + chown "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "$SERVICE_ROOT/IMAGE" "$SERVICE_ROOT/IMAGE_VERSION" + + if [[ -f "$SERVICE_ROOT/${PLUGIN_VARIABLE}_CONFIG_OPTIONS" ]]; then + mv "$SERVICE_ROOT/${PLUGIN_VARIABLE}_CONFIG_OPTIONS" "$SERVICE_ROOT/CONFIG_OPTIONS" + chown "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "$SERVICE_ROOT/CONFIG_OPTIONS" + fi + done +} + +plugin-install "$@" diff --git a/plugins/mysql/plugin.toml b/plugins/mysql/plugin.toml new file mode 100644 index 000000000..e55891693 --- /dev/null +++ b/plugins/mysql/plugin.toml @@ -0,0 +1,4 @@ +[plugin] +description = "dokku mysql service plugin" +version = "1.43.0" +[plugin.config] diff --git a/plugins/mysql/post-app-clone-setup b/plugins/mysql/post-app-clone-setup new file mode 100755 index 000000000..c5d762d2c --- /dev/null +++ b/plugins/mysql/post-app-clone-setup @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-post-app-clone-setup() { + declare OLD_APP_NAME="$1" NEW_APP_NAME="$2" + + for SERVICE in $(fn-services-list false); do + if in_links_file "$SERVICE" "$OLD_APP_NAME"; then + add_to_links_file "$SERVICE" "$NEW_APP_NAME" + fi + done +} + +plugin-post-app-clone-setup "$@" diff --git a/plugins/mysql/post-app-rename-setup b/plugins/mysql/post-app-rename-setup new file mode 100755 index 000000000..6c1e16b16 --- /dev/null +++ b/plugins/mysql/post-app-rename-setup @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-post-app-rename-setup() { + declare OLD_APP_NAME="$1" NEW_APP_NAME="$2" + + for SERVICE in $(fn-services-list false); do + if in_links_file "$SERVICE" "$OLD_APP_NAME"; then + add_to_links_file "$SERVICE" "$NEW_APP_NAME" + fi + done +} + +plugin-post-app-rename-setup "$@" diff --git a/plugins/mysql/pre-delete b/plugins/mysql/pre-delete new file mode 100755 index 000000000..d31546478 --- /dev/null +++ b/plugins/mysql/pre-delete @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +APP="$1" +for SERVICE in $(fn-services-list false); do + [[ -n "$SERVICE" ]] || continue + dokku_log_verbose_quiet "Unlinking from $SERVICE" + remove_from_links_file "$(basename "$SERVICE")" "$APP" +done diff --git a/plugins/mysql/pre-restore b/plugins/mysql/pre-restore new file mode 100755 index 000000000..c3322028f --- /dev/null +++ b/plugins/mysql/pre-restore @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-pre-restore() { + declare SCHEDULER="$1" APP="$2" + local status + + if [[ "$SCHEDULER" != "docker-local" ]]; then + return + fi + + for SERVICE in $(fn-services-list false); do + if ! in_links_file "$SERVICE" "$APP"; then + continue + fi + + status="$(service_status "$SERVICE")" + if [[ "$status" == "running" ]]; then + continue + fi + + if [[ "$status" == "restarting" ]]; then + dokku_log_warn "$PLUGIN_SERVICE service $SERVICE is restarting and may cause issues with linked app $APP" + continue + fi + + dokku_log_warn "$PLUGIN_SERVICE service $SERVICE is not running, issuing service start" + service_start "$SERVICE" + done +} + +plugin-pre-restore "$@" diff --git a/plugins/mysql/pre-start b/plugins/mysql/pre-start new file mode 100755 index 000000000..b3d83a8e4 --- /dev/null +++ b/plugins/mysql/pre-start @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-pre-start() { + declare APP="$1" + local status + + for SERVICE in $(fn-services-list false); do + if ! in_links_file "$SERVICE" "$APP"; then + continue + fi + + status="$(service_status "$SERVICE")" + if [[ "$status" == "running" ]]; then + continue + fi + + if [[ "$status" == "restarting" ]]; then + dokku_log_warn "$PLUGIN_SERVICE service $SERVICE is restarting and may cause issues with linked app $APP" + continue + fi + + dokku_log_warn "$PLUGIN_SERVICE service $SERVICE is not running, issuing service start" + service_start "$SERVICE" + done +} + +plugin-pre-start "$@" diff --git a/plugins/mysql/service-list b/plugins/mysql/service-list new file mode 100755 index 000000000..6fc54e90d --- /dev/null +++ b/plugins/mysql/service-list @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-service-list() { + declare desc="allows listing all services for use by other dokku plugins" + declare SERVICE_TYPE="$1" + + if [[ -n "$SERVICE_TYPE" ]] && [[ "$SERVICE_TYPE" != "$PLUGIN_COMMAND_PREFIX" ]]; then + return + fi + + for service in $(fn-services-list false); do + echo "$PLUGIN_COMMAND_PREFIX:$service" + done +} + +plugin-service-list "$@" diff --git a/plugins/mysql/subcommands/app-links b/plugins/mysql/subcommands/app-links new file mode 100755 index 000000000..6b0a0eb04 --- /dev/null +++ b/plugins/mysql/subcommands/app-links @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-app-links-cmd() { + #E list all $PLUGIN_COMMAND_PREFIX services that are linked to the 'playground' app. + #E dokku $PLUGIN_COMMAND_PREFIX:app-links playground + #A app, app to run command against + declare desc="list all $PLUGIN_SERVICE service links for a given app" + local cmd="$PLUGIN_COMMAND_PREFIX:app-links" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare APP="$1" + APP=${APP:="$DOKKU_APP_NAME"} + + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + verify_app_name "$APP" + service_app_links "$APP" +} + +service-app-links-cmd "$@" diff --git a/plugins/mysql/subcommands/backup b/plugins/mysql/subcommands/backup new file mode 100755 index 000000000..354ed9b67 --- /dev/null +++ b/plugins/mysql/subcommands/backup @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-cmd() { + #E backup the 'lollipop' service to the 'my-s3-bucket' bucket on AWS + #E dokku $PLUGIN_COMMAND_PREFIX:backup lollipop my-s3-bucket --use-iam + #E restore a backup file (assuming it was extracted via 'tar -xf backup.tgz') + #E dokku $PLUGIN_COMMAND_PREFIX:import lollipop < backup-folder/export + #F -u|--use-iam, use the IAM profile associated with the current server + #A service, service to run command against + #A bucket-name, name of the s3 bucket to upload backups to + declare desc="create a backup of the $PLUGIN_SERVICE service to an existing s3 bucket" + local cmd="$PLUGIN_COMMAND_PREFIX:backup" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" BUCKET_NAME="$2" USE_IAM_OPTIONAL_FLAG="$3" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$BUCKET_NAME" ]] && dokku_log_fail "Please specify an aws bucket for the backup" + verify_service_name "$SERVICE" + service_backup "$SERVICE" "$BUCKET_NAME" "$USE_IAM_OPTIONAL_FLAG" +} + +service-backup-cmd "$@" diff --git a/plugins/mysql/subcommands/backup-auth b/plugins/mysql/subcommands/backup-auth new file mode 100755 index 000000000..050397bcc --- /dev/null +++ b/plugins/mysql/subcommands/backup-auth @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-auth-cmd() { + #E setup s3 backup authentication + #E dokku $PLUGIN_COMMAND_PREFIX:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY + #E setup s3 backup authentication with different region + #E dokku $PLUGIN_COMMAND_PREFIX:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION + #E setup s3 backup authentication with different signature version and endpoint + #E dokku $PLUGIN_COMMAND_PREFIX:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION AWS_SIGNATURE_VERSION ENDPOINT_URL + #E more specific example for minio auth + #E dokku $PLUGIN_COMMAND_PREFIX:backup-auth lollipop MINIO_ACCESS_KEY_ID MINIO_SECRET_ACCESS_KEY us-east-1 s3v4 https://YOURMINIOSERVICE + #A service, service to run command against + #A access-key-id, an amazon AWS_ACCESS_KEY_ID + #A aws-secret-access-key, an amazon AWS_SECRET_ACCESS_KEY + #A aws-default-region, (optional) a valid amazon S3 region + #A aws-signature-version, (optional) the AWS signature version to use when signing S3 requests + #A endpoint-url, (optional) an aws endpoint to upload to + declare desc="set up authentication for backups on the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-auth" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" AWS_ACCESS_KEY_ID="$2" AWS_SECRET_ACCESS_KEY="$3" AWS_DEFAULT_REGION="$4" AWS_SIGNATURE_VERSION="$5" ENDPOINT_URL="$6" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$AWS_ACCESS_KEY_ID" ]] && dokku_log_fail "Please specify an aws access key id" + [[ -z "$AWS_SECRET_ACCESS_KEY" ]] && dokku_log_fail "Please specify an aws secret access key" + verify_service_name "$SERVICE" + service_backup_auth "$SERVICE" "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY" "$AWS_DEFAULT_REGION" "$AWS_SIGNATURE_VERSION" "$ENDPOINT_URL" +} + +service-backup-auth-cmd "$@" diff --git a/plugins/mysql/subcommands/backup-deauth b/plugins/mysql/subcommands/backup-deauth new file mode 100755 index 000000000..19cbe8aff --- /dev/null +++ b/plugins/mysql/subcommands/backup-deauth @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-deauth-cmd() { + #E remove s3 authentication + #E dokku $PLUGIN_COMMAND_PREFIX:backup-deauth lollipop + #A service, service to run command against + declare desc="remove backup authentication for the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-deauth" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_backup_deauth "$SERVICE" +} + +service-backup-deauth-cmd "$@" diff --git a/plugins/mysql/subcommands/backup-schedule b/plugins/mysql/subcommands/backup-schedule new file mode 100755 index 000000000..594c74846 --- /dev/null +++ b/plugins/mysql/subcommands/backup-schedule @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-schedule-cmd() { + #E schedule a backup + #E > 'schedule' is a crontab expression, eg. "0 3 * * *" for each day at 3am + #E dokku $PLUGIN_COMMAND_PREFIX:backup-schedule lollipop "0 3 * * *" my-s3-bucket + #E schedule a backup and authenticate via iam + #E dokku $PLUGIN_COMMAND_PREFIX:backup-schedule lollipop "0 3 * * *" my-s3-bucket --use-iam + #F -u|--use-iam, use the IAM profile associated with the current server + #A service, service to run command against + #A schedule, a cron schedule to run backups on + #A bucket-name, name of the s3 bucket to upload backups to + declare desc="schedule a backup of the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-schedule" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" SCHEDULE="$2" BUCKET_NAME="$3" USE_IAM_OPTIONAL_FLAG="$4" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$SCHEDULE" ]] && dokku_log_fail "Please specify a schedule for the backup" + [[ -z "$BUCKET_NAME" ]] && dokku_log_fail "Please specify an aws bucket for the backup" + verify_service_name "$SERVICE" + service_backup_schedule "$SERVICE" "$SCHEDULE" "$BUCKET_NAME" "$USE_IAM_OPTIONAL_FLAG" +} + +service-backup-schedule-cmd "$@" diff --git a/plugins/mysql/subcommands/backup-schedule-cat b/plugins/mysql/subcommands/backup-schedule-cat new file mode 100755 index 000000000..56a388ac5 --- /dev/null +++ b/plugins/mysql/subcommands/backup-schedule-cat @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-schedule-cat-cmd() { + #E cat the contents of the configured backup cronfile for the service + #E dokku $PLUGIN_COMMAND_PREFIX:backup-schedule-cat lollipop + #A service, service to run command against + declare desc="cat the contents of the configured backup cronfile for the service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-schedule-cat" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_backup_schedule_cat "$SERVICE" +} + +service-backup-schedule-cat-cmd "$@" diff --git a/plugins/mysql/subcommands/backup-set-encryption b/plugins/mysql/subcommands/backup-set-encryption new file mode 100755 index 000000000..27cb86e80 --- /dev/null +++ b/plugins/mysql/subcommands/backup-set-encryption @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-set-encryption-cmd() { + #E set the GPG-compatible passphrase for encrypting backups for backups + #E dokku $PLUGIN_COMMAND_PREFIX:backup-set-encryption lollipop + #A service, service to run command against + #A passphrase, a GPG-compatible passphrase + declare desc="set encryption for all future backups of $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-set-encryption" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" PASSPHRASE="$2" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$PASSPHRASE" ]] && dokku_log_fail "Please specify a GPG backup passphrase" + verify_service_name "$SERVICE" + service_backup_set_encryption "$SERVICE" "$PASSPHRASE" +} + +service-backup-set-encryption-cmd "$@" diff --git a/plugins/mysql/subcommands/backup-set-public-key-encryption b/plugins/mysql/subcommands/backup-set-public-key-encryption new file mode 100755 index 000000000..d058bb203 --- /dev/null +++ b/plugins/mysql/subcommands/backup-set-public-key-encryption @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-set-public-key-encryption-cmd() { + #E set the GPG Public Key for encrypting backups + #E dokku $PLUGIN_COMMAND_PREFIX:backup-set-public-key-encryption lollipop + #A service, service to run command against + #A public-key-id, a GPG Public Key ID (or fingerprint) to use for encryption. Must be uploaded to the GPG keyserver beforehand. + declare desc="set GPG Public Key encryption for all future backups of $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-set-public-key-encryption" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" PUBLIC_KEY_ID="$2" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$PUBLIC_KEY_ID" ]] && dokku_log_fail "Please specify a valid GPG Public Key ID (or fingerprint)" + verify_service_name "$SERVICE" + service_backup_set_public_key_encryption "$SERVICE" "$PUBLIC_KEY_ID" +} + +service-backup-set-public-key-encryption-cmd "$@" diff --git a/plugins/mysql/subcommands/backup-unschedule b/plugins/mysql/subcommands/backup-unschedule new file mode 100755 index 000000000..874a22a07 --- /dev/null +++ b/plugins/mysql/subcommands/backup-unschedule @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-unschedule-cmd() { + #E remove the scheduled backup from cron + #E dokku $PLUGIN_COMMAND_PREFIX:backup-unschedule lollipop + #A service, service to run command against + declare desc="unschedule the backup of the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-unschedule" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_backup_unschedule "$SERVICE" +} + +service-backup-unschedule-cmd "$@" diff --git a/plugins/mysql/subcommands/backup-unset-encryption b/plugins/mysql/subcommands/backup-unset-encryption new file mode 100755 index 000000000..c046a5511 --- /dev/null +++ b/plugins/mysql/subcommands/backup-unset-encryption @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-unset-encryption-cmd() { + #E unset the GPG encryption passphrase for backups + #E dokku $PLUGIN_COMMAND_PREFIX:backup-unset-encryption lollipop + #A service, service to run command against + declare desc="unset encryption for future backups of the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-unset-encryption" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_backup_unset_encryption "$SERVICE" +} + +service-backup-unset-encryption-cmd "$@" diff --git a/plugins/mysql/subcommands/backup-unset-public-key-encryption b/plugins/mysql/subcommands/backup-unset-public-key-encryption new file mode 100755 index 000000000..8e0352f8d --- /dev/null +++ b/plugins/mysql/subcommands/backup-unset-public-key-encryption @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-unset-public-key-encryption-cmd() { + #E unset the GPG Public Key encryption for backups + #E dokku $PLUGIN_COMMAND_PREFIX:backup-unset-public-key-encryption lollipop + #A service, service to run command against + declare desc="unset GPG Public Key encryption for future backups of the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-unset-public-key-encryption" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" # TODO: [22.03.2024 by Mykola] + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_backup_unset_public_key_encryption "$SERVICE" # TODO: [22.03.2024 by Mykola] +} + +service-backup-unset-encryption-cmd "$@" diff --git a/plugins/mysql/subcommands/clone b/plugins/mysql/subcommands/clone new file mode 100755 index 000000000..b9d99a330 --- /dev/null +++ b/plugins/mysql/subcommands/clone @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-clone-cmd() { + #E you can clone an existing service to a new one + #E dokku $PLUGIN_COMMAND_PREFIX:clone lollipop lollipop-2 + #A service, service to run command against + #A new-service, name of new service + #F -c|--config-options "--args --go=here", extra arguments to pass to the container create command + #F -C|--custom-env "USER=alpha;HOST=beta", semi-colon delimited environment variables to start the service with + #F -i|--image IMAGE, the image name to start the service with + #F -I|--image-version IMAGE_VERSION, the image version to start the service with + #F -m|--memory MEMORY, container memory limit in megabytes (default: unlimited) + #F -N|--initial-network INITIAL_NETWORK, the initial network to attach the service to + #F -p|--password PASSWORD, override the user-level service password + #F -P|--post-create-network NETWORKS, a comma-separated list of networks to attach the service container to after service creation + #F -r|--root-password PASSWORD, override the root-level service password + #F -S|--post-start-network NETWORKS, a comma-separated list of networks to attach the service container to after service start + #F -s|--shm-size SHM_SIZE, override shared memory size for $PLUGIN_COMMAND_PREFIX docker container + declare desc="create container then copy data from into " + local cmd="$PLUGIN_COMMAND_PREFIX:clone" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" NEW_SERVICE="$2" CLONE_FLAGS_LIST=("${@:3}") + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$NEW_SERVICE" ]] && dokku_log_fail "Please specify a name for the new service" + verify_service_name "$SERVICE" + if service_exists "$NEW_SERVICE"; then + dokku_log_fail "Invalid service name $NEW_SERVICE. Verify the service name is not already in use." + fi + + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ID="$(cat "$SERVICE_ROOT/ID")" + is_container_status "$ID" "Running" || dokku_log_fail "Service ${SERVICE} container is not running" + + PLUGIN_IMAGE=$(service_version "$SERVICE" | grep -o "^.*:" | sed -r "s/://g") + PLUGIN_IMAGE_VERSION=$(service_version "$SERVICE" | grep -o ":.*$" | sed -r "s/://g") + + service_parse_args "${@:3}" + + dokku_log_info2 "Cloning $SERVICE to $NEW_SERVICE @ $PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" + service_create "$NEW_SERVICE" "${@:3}" + dokku_log_info1 "Copying data from $SERVICE to $NEW_SERVICE" + service_export "$SERVICE" | service_import "$NEW_SERVICE" >/dev/null 2>&1 || true + dokku_log_info2 "Done" +} + +service-clone-cmd "$@" diff --git a/plugins/mysql/subcommands/connect b/plugins/mysql/subcommands/connect new file mode 100755 index 000000000..ad6abcaed --- /dev/null +++ b/plugins/mysql/subcommands/connect @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-connect-cmd() { + #E connect to the service via the $PLUGIN_COMMAND_PREFIX connection tool + #E > NOTE: disconnecting from ssh while running this command may leave zombie processes due to moby/moby#9098 + #E dokku $PLUGIN_COMMAND_PREFIX:connect lollipop + #A service, service to run command against + declare desc="connect to the service via the $PLUGIN_COMMAND_PREFIX connection tool" + local cmd="$PLUGIN_COMMAND_PREFIX:connect" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_connect "$SERVICE" +} + +service-connect-cmd "$@" diff --git a/plugins/mysql/subcommands/create b/plugins/mysql/subcommands/create new file mode 100755 index 000000000..7112f18f1 --- /dev/null +++ b/plugins/mysql/subcommands/create @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-create-cmd() { + #E create a $PLUGIN_COMMAND_PREFIX service named lollipop + #E dokku $PLUGIN_COMMAND_PREFIX:create lollipop + #E you can also specify the image and image version to use for the service. + #E it *must* be compatible with the ${PLUGIN_IMAGE} image. + #E export ${PLUGIN_VARIABLE}_IMAGE="${PLUGIN_IMAGE}" + #E export ${PLUGIN_VARIABLE}_IMAGE_VERSION="${PLUGIN_IMAGE_VERSION}" + #E dokku $PLUGIN_COMMAND_PREFIX:create lollipop + #E you can also specify custom environment variables to start + #E the ${PLUGIN_COMMAND_PREFIX} service in semicolon-separated form. + #E export ${PLUGIN_VARIABLE}_CUSTOM_ENV="USER=alpha;HOST=beta" + #E dokku $PLUGIN_COMMAND_PREFIX:create lollipop + #A service, service to run command against + #F -c|--config-options "--args --go=here", extra arguments to pass to the container create command + #F -C|--custom-env "USER=alpha;HOST=beta", semi-colon delimited environment variables to start the service with + #F -i|--image IMAGE, the image name to start the service with + #F -I|--image-version IMAGE_VERSION, the image version to start the service with + #F -m|--memory MEMORY, container memory limit in megabytes (default: unlimited) + #F -N|--initial-network INITIAL_NETWORK, the initial network to attach the service to + #F -p|--password PASSWORD, override the user-level service password + #F -P|--post-create-network NETWORKS, a comma-separated list of networks to attach the service container to after service creation + #F -r|--root-password PASSWORD, override the root-level service password + #F -S|--post-start-network NETWORKS, a comma-separated list of networks to attach the service container to after service start + #F -s|--shm-size SHM_SIZE, override shared memory size for $PLUGIN_COMMAND_PREFIX docker container + declare desc="create a $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:create" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" CREATE_FLAGS_LIST=("${@:2}") + + service_create "$SERVICE" "${@:2}" +} + +service-create-cmd "$@" diff --git a/plugins/mysql/subcommands/destroy b/plugins/mysql/subcommands/destroy new file mode 100755 index 000000000..cffe32d0e --- /dev/null +++ b/plugins/mysql/subcommands/destroy @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-destroy-cmd() { + #E destroy the service, it's data, and the running container + #E dokku $PLUGIN_COMMAND_PREFIX:destroy lollipop + #A service, service to run command against + #F -f|--force, force destroy without asking for confirmation + declare desc="delete the $PLUGIN_SERVICE service/data/container if there are no links left" + local cmd="$PLUGIN_COMMAND_PREFIX:destroy" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" FORCE_FLAG="$2" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + LINKS_FILE="$SERVICE_ROOT/LINKS" + SERVICE_HOST_ROOT="$PLUGIN_DATA_HOST_ROOT/$SERVICE" + SERVICE_NAME="$(get_service_name "$SERVICE")" + + [[ -s "$LINKS_FILE" ]] && dokku_log_fail "Cannot delete linked service" + + if [[ "$FORCE_FLAG" == "force" ]] || [[ "$FORCE_FLAG" == "-f" ]] || [[ "$FORCE_FLAG" == "--force" ]]; then + DOKKU_APPS_FORCE_DELETE=1 + fi + if [[ -z "$DOKKU_APPS_FORCE_DELETE" ]]; then + dokku_log_warn "WARNING: Potentially Destructive Action" + dokku_log_warn "This command will destroy $SERVICE $PLUGIN_SERVICE service." + dokku_log_warn "To proceed, type \"$SERVICE\"" + echo "" + + read -rp "> " service_name + if [[ "$service_name" != "$SERVICE" ]]; then + dokku_log_warn "Confirmation did not match $SERVICE. Aborted." + exit 1 + fi + fi + + dokku_log_info2_quiet "Deleting $SERVICE" + plugn trigger service-action pre-delete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" + service_backup_unschedule "$SERVICE" + service_container_rm "$SERVICE" + + dokku_log_verbose_quiet "Removing data" + "$DOCKER_BIN" container run --rm -v "$SERVICE_HOST_ROOT/data:/data" -v "$SERVICE_HOST_ROOT/$PLUGIN_CONFIG_SUFFIX:/config" "$PLUGIN_BUSYBOX_IMAGE" chmod 777 -R /config /data + rm -rf "$SERVICE_ROOT" + + fn-plugin-property-destroy "$PLUGIN_COMMAND_PREFIX" "$SERVICE" + + plugn trigger service-action post-delete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" + dokku_log_info2 "$PLUGIN_SERVICE container deleted: $SERVICE" +} + +service-destroy-cmd "$@" diff --git a/plugins/mysql/subcommands/enter b/plugins/mysql/subcommands/enter new file mode 100755 index 000000000..9062bebe7 --- /dev/null +++ b/plugins/mysql/subcommands/enter @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-enter-cmd() { + #E a bash prompt can be opened against a running service. + #E filesystem changes will not be saved to disk. + #E > NOTE: disconnecting from ssh while running this command may leave zombie processes due to moby/moby#9098 + #E dokku $PLUGIN_COMMAND_PREFIX:enter lollipop + #E you may also run a command directly against the service. + #E filesystem changes will not be saved to disk. + #E dokku $PLUGIN_COMMAND_PREFIX:enter lollipop touch /tmp/test + #A service, service to run command against + declare desc="enter or run a command in a running $PLUGIN_SERVICE service container" + local cmd="$PLUGIN_COMMAND_PREFIX:enter" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + verify_service_name "$SERVICE" + dokku_log_info1_quiet "Filesystem changes may not persist after container restarts" + service_enter "$SERVICE" "${@:2}" +} + +service-enter-cmd "$@" diff --git a/plugins/mysql/subcommands/exists b/plugins/mysql/subcommands/exists new file mode 100755 index 000000000..6fd1bfc9a --- /dev/null +++ b/plugins/mysql/subcommands/exists @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-exists-cmd() { + #E here we check if the lollipop $PLUGIN_COMMAND_PREFIX service exists. + #E dokku $PLUGIN_COMMAND_PREFIX:exists lollipop + #A service, service to run command against + declare desc="check if the $PLUGIN_SERVICE service exists" + local cmd="$PLUGIN_COMMAND_PREFIX:exists" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + dokku_log_info1 "Service $SERVICE exists" +} + +service-exists-cmd "$@" diff --git a/plugins/mysql/subcommands/export b/plugins/mysql/subcommands/export new file mode 100755 index 000000000..d77dd479d --- /dev/null +++ b/plugins/mysql/subcommands/export @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-export-cmd() { + #E by default, datastore output is exported to stdout + #E dokku $PLUGIN_COMMAND_PREFIX:export lollipop + #E you can redirect this output to a file + #E dokku $PLUGIN_COMMAND_PREFIX:export lollipop > data.dump + #A service, service to run command against + declare desc="export a dump of the $PLUGIN_SERVICE service database" + local cmd="$PLUGIN_COMMAND_PREFIX:export" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_export "$SERVICE" +} + +service-export-cmd "$@" diff --git a/plugins/mysql/subcommands/expose b/plugins/mysql/subcommands/expose new file mode 100755 index 000000000..ab541d697 --- /dev/null +++ b/plugins/mysql/subcommands/expose @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-expose-cmd() { + #E expose the service on the service's normal ports, allowing access to it from the public interface (0.0.0.0) + #E dokku $PLUGIN_COMMAND_PREFIX:expose lollipop ${PLUGIN_DATASTORE_PORTS[@]} + #E expose the service on the service's normal ports, with the first on a specified ip address (127.0.0.1) + #E dokku $PLUGIN_COMMAND_PREFIX:expose lollipop 127.0.0.1:${PLUGIN_DATASTORE_PORTS[@]} + #A service, service to run command against + #A ports, a list of ports to run against + declare desc="expose a $PLUGIN_SERVICE service on custom host:port if provided (random port on the 0.0.0.0 interface if otherwise unspecified)" + local cmd="$PLUGIN_COMMAND_PREFIX:expose" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" PORTS_LIST=("${@:2}") + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_port_expose "$SERVICE" "${@:2}" +} + +service-expose-cmd "$@" diff --git a/plugins/mysql/subcommands/import b/plugins/mysql/subcommands/import new file mode 100755 index 000000000..441f415f6 --- /dev/null +++ b/plugins/mysql/subcommands/import @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-import-cmd() { + #E import a datastore dump + #E dokku $PLUGIN_COMMAND_PREFIX:import lollipop < data.dump + #A service, service to run command against + declare desc="import a dump into the $PLUGIN_SERVICE service database" + local cmd="$PLUGIN_COMMAND_PREFIX:import" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_import "$SERVICE" +} + +service-import-cmd "$@" diff --git a/plugins/mysql/subcommands/info b/plugins/mysql/subcommands/info new file mode 100755 index 000000000..f9970d3a4 --- /dev/null +++ b/plugins/mysql/subcommands/info @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-info-cmd() { + #E get connection information as follows: + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop + #E you can also retrieve a specific piece of service info via flags: + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --config-dir + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --data-dir + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --dsn + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --exposed-ports + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --id + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --internal-ip + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --initial-network + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --links + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --post-create-network + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --post-start-network + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --service-root + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --status + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --version + #A service, service to run command against + #F --config-dir, show the service configuration directory + #F --data-dir, show the service data directory + #F --dsn, show the service DSN + #F --exposed-ports, show service exposed ports + #F --id, show the service container id + #F --internal-ip, show the service internal ip + #F --initial-network, show the initial network being connected to + #F --links, show the service app links + #F --post-create-network, show the networks to attach to after service container creation + #F --post-start-network, show the networks to attach to after service container start + #F --service-root, show the service root directory + #F --status, show the service running status + #F --version, show the service image version + declare desc="print the service information" + local cmd="$PLUGIN_COMMAND_PREFIX:info" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" INFO_FLAG="$2" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_info "$SERVICE" "$INFO_FLAG" +} + +service-info-cmd "$@" diff --git a/plugins/mysql/subcommands/link b/plugins/mysql/subcommands/link new file mode 100755 index 000000000..fafd1741f --- /dev/null +++ b/plugins/mysql/subcommands/link @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-link-cmd() { + #E a $PLUGIN_COMMAND_PREFIX service can be linked to a container. + #E this will use native docker links via the docker-options plugin. + #E here we link it to our 'playground' app. + #E > NOTE: this will restart your app + #E dokku $PLUGIN_COMMAND_PREFIX:link lollipop playground + #E the following environment variables will be set automatically by docker + #E (not on the app itself, so they won’t be listed when calling dokku config): + #E + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_NAME=/lollipop/DATABASE + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_PORT=tcp://172.17.0.1:${PLUGIN_DATASTORE_PORTS[0]} + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_PORT_${PLUGIN_DATASTORE_PORTS[0]}_TCP=tcp://172.17.0.1:${PLUGIN_DATASTORE_PORTS[0]} + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_PORT_${PLUGIN_DATASTORE_PORTS[0]}_TCP_PROTO=tcp + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_PORT_${PLUGIN_DATASTORE_PORTS[0]}_TCP_PORT=${PLUGIN_DATASTORE_PORTS[0]} + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_PORT_${PLUGIN_DATASTORE_PORTS[0]}_TCP_ADDR=172.17.0.1 + #E + #E the following will be set on the linked application by default: + #E + #E ${PLUGIN_DEFAULT_ALIAS}_URL=${PLUGIN_SCHEME}://mysql:SOME_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-lollipop:${PLUGIN_DATASTORE_PORTS[0]}/lollipop + #E + #E the host exposed here only works internally in docker containers. + #E if you want your container to be reachable from outside, you should + #E use the 'expose' subcommand. another service can be linked to your app: + #E dokku $PLUGIN_COMMAND_PREFIX:link other_service playground + #E it is possible to change the protocol for ${PLUGIN_DEFAULT_ALIAS}_URL by setting the + #E environment variable ${PLUGIN_VARIABLE}_DATABASE_SCHEME on the app. doing so will + #E after linking will cause the plugin to think the service is not + #E linked, and we advise you to unlink before proceeding. + #E dokku config:set playground ${PLUGIN_VARIABLE}_DATABASE_SCHEME=${PLUGIN_SCHEME}2 + #E dokku $PLUGIN_COMMAND_PREFIX:link lollipop playground + #E this will cause ${PLUGIN_DEFAULT_ALIAS}_URL to be set as: + #E + #E ${PLUGIN_SCHEME}2://mysql:SOME_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-lollipop:${PLUGIN_DATASTORE_PORTS[0]}/lollipop + #A service, service to run command against + #A app, app to run command against + #F -a|--alias "BLUE_DATABASE", an alternative alias to use for linking to an app via environment variable + #F -q|--querystring "pool=5", ampersand delimited querystring arguments to append to the service link + #F -n|--no-restart "false", whether or not to restart the app on link (default: true) + declare desc="link the $PLUGIN_SERVICE service to the app" + local cmd="$PLUGIN_COMMAND_PREFIX:link" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" APP="$2" LINK_FLAGS_LIST=("${@:3}") + APP=${APP:="$DOKKU_APP_NAME"} + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + verify_app_name "$APP" + verify_service_name "$SERVICE" + + service_parse_args "${@:3}" + service_link "$SERVICE" "$APP" +} + +service-link-cmd "$@" diff --git a/plugins/mysql/subcommands/linked b/plugins/mysql/subcommands/linked new file mode 100755 index 000000000..5d61d79ed --- /dev/null +++ b/plugins/mysql/subcommands/linked @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-linked-cmd() { + #E here we check if the lollipop $PLUGIN_COMMAND_PREFIX service is linked to the 'playground' app. + #E dokku $PLUGIN_COMMAND_PREFIX:linked lollipop playground + #A service, service to run command against + #A app, app to run command against + declare desc="check if the $PLUGIN_SERVICE service is linked to an app" + local cmd="$PLUGIN_COMMAND_PREFIX:linked" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" APP="$2" + APP=${APP:="$DOKKU_APP_NAME"} + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + verify_app_name "$APP" + verify_service_name "$SERVICE" + service_is_linked "$SERVICE" "$APP" +} + +service-linked-cmd "$@" diff --git a/plugins/mysql/subcommands/links b/plugins/mysql/subcommands/links new file mode 100755 index 000000000..35586aad5 --- /dev/null +++ b/plugins/mysql/subcommands/links @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-links-cmd() { + #E list all apps linked to the 'lollipop' $PLUGIN_COMMAND_PREFIX service. + #E dokku $PLUGIN_COMMAND_PREFIX:links lollipop + #A service, service to run command against + declare desc="list all apps linked to the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:links" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_links "$SERVICE" +} + +service-links-cmd "$@" diff --git a/plugins/mysql/subcommands/list b/plugins/mysql/subcommands/list new file mode 100755 index 000000000..cebe31c13 --- /dev/null +++ b/plugins/mysql/subcommands/list @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-list-cmd() { + #E list all services + #E dokku $PLUGIN_COMMAND_PREFIX:list + declare desc="list all $PLUGIN_SERVICE services" + local cmd="$PLUGIN_COMMAND_PREFIX:list" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + + service_list +} + +service-list-cmd "$@" diff --git a/plugins/mysql/subcommands/logs b/plugins/mysql/subcommands/logs new file mode 100755 index 000000000..7f2e90eac --- /dev/null +++ b/plugins/mysql/subcommands/logs @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-logs-cmd() { + #E you can tail logs for a particular service: + #E dokku $PLUGIN_COMMAND_PREFIX:logs lollipop + #E by default, logs will not be tailed, but you can do this with the --tail flag: + #E dokku $PLUGIN_COMMAND_PREFIX:logs lollipop --tail + #E the default tail setting is to show all logs, but an initial count can also be specified + #E dokku $PLUGIN_COMMAND_PREFIX:logs lollipop --tail 5 + #A service, service to run command against + #F -t|--tail [], do not stop when end of the logs are reached and wait for additional output + declare desc="print the most recent log(s) for this service" + local cmd="$PLUGIN_COMMAND_PREFIX:logs" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" TAIL_FLAG="$2" TAIL_NUM_OPTIONAL="${3:-all}" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_logs "$SERVICE" "$TAIL_FLAG" "$TAIL_NUM_OPTIONAL" +} + +service-logs-cmd "$@" diff --git a/plugins/mysql/subcommands/pause b/plugins/mysql/subcommands/pause new file mode 100755 index 000000000..91d51fe69 --- /dev/null +++ b/plugins/mysql/subcommands/pause @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-pause-cmd() { + #E pause the running container for the service + #E dokku $PLUGIN_COMMAND_PREFIX:pause lollipop + #A service, service to run command against + declare desc="pause a running $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:pause" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_pause "$SERVICE" +} + +service-pause-cmd "$@" diff --git a/plugins/mysql/subcommands/promote b/plugins/mysql/subcommands/promote new file mode 100755 index 000000000..ddf35ada0 --- /dev/null +++ b/plugins/mysql/subcommands/promote @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-promote-cmd() { + #E if you have a $PLUGIN_COMMAND_PREFIX service linked to an app and try to link another $PLUGIN_COMMAND_PREFIX service + #E another link environment variable will be generated automatically: + #E + #E DOKKU_${PLUGIN_DEFAULT_ALIAS}_BLUE_URL=${PLUGIN_SCHEME}://other_service:ANOTHER_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-other-service:${PLUGIN_DATASTORE_PORTS[0]}/other_service + #E + #E you can promote the new service to be the primary one + #E > NOTE: this will restart your app + #E dokku $PLUGIN_COMMAND_PREFIX:promote other_service playground + #E this will replace ${PLUGIN_DEFAULT_ALIAS}_URL with the url from other_service and generate + #E another environment variable to hold the previous value if necessary. + #E you could end up with the following for example: + #E + #E ${PLUGIN_DEFAULT_ALIAS}_URL=${PLUGIN_SCHEME}://other_service:ANOTHER_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-other-service:${PLUGIN_DATASTORE_PORTS[0]}/other_service + #E DOKKU_${PLUGIN_DEFAULT_ALIAS}_BLUE_URL=${PLUGIN_SCHEME}://other_service:ANOTHER_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-other-service:${PLUGIN_DATASTORE_PORTS[0]}/other_service + #E DOKKU_${PLUGIN_DEFAULT_ALIAS}_SILVER_URL=${PLUGIN_SCHEME}://lollipop:SOME_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-lollipop:${PLUGIN_DATASTORE_PORTS[0]}/lollipop + #A service, service to run command against + #A app, app to run command against + declare desc="promote service as ${PLUGIN_DEFAULT_ALIAS}_URL in " + local cmd="$PLUGIN_COMMAND_PREFIX:promote" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" APP="$2" + APP=${APP:="$DOKKU_APP_NAME"} + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + verify_service_name "$SERVICE" + verify_app_name "$APP" + service_promote "$SERVICE" "$APP" +} + +service-promote-cmd "$@" diff --git a/plugins/mysql/subcommands/restart b/plugins/mysql/subcommands/restart new file mode 100755 index 000000000..9ff6b030d --- /dev/null +++ b/plugins/mysql/subcommands/restart @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-restart-cmd() { + #E restart the service + #E dokku $PLUGIN_COMMAND_PREFIX:restart lollipop + #A service, service to run command against + declare desc="graceful shutdown and restart of the $PLUGIN_SERVICE service container" + local cmd="$PLUGIN_COMMAND_PREFIX:restart" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_pause "$SERVICE" + service_start "$SERVICE" + dokku_log_info1 "Please call dokku ps:restart on all linked apps" +} + +service-restart-cmd "$@" diff --git a/plugins/mysql/subcommands/set b/plugins/mysql/subcommands/set new file mode 100755 index 000000000..0bcf474b5 --- /dev/null +++ b/plugins/mysql/subcommands/set @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +source "$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)/common-functions" + +service-set-cmd() { + #E set the network to attach after the service container is started + #E dokku $PLUGIN_COMMAND_PREFIX:set lollipop post-create-network custom-network + #E set multiple networks + #E dokku $PLUGIN_COMMAND_PREFIX:set lollipop post-create-network custom-network,other-network + #E unset the post-create-network value + #E dokku $PLUGIN_COMMAND_PREFIX:set lollipop post-create-network + #A service, service to run command against + #A key, property name to set + #A value, optional property value to set or empty to unset key + declare desc="set or clear a property for a service" + local cmd="$PLUGIN_COMMAND_PREFIX:set" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" KEY="$2" VALUE="$3" + local VALID_KEYS=("initial-network" "post-create-network" "post-start-network") + verify_service_name "$SERVICE" + + [[ -z "$KEY" ]] && dokku_log_fail "No key specified" + + if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then + dokku_log_fail "Invalid key specified, valid keys include: initial-network, post-create-network, post-start-network" + fi + + if [[ -n "$VALUE" ]]; then + dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}" + fn-plugin-property-write "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$KEY" "$VALUE" + else + dokku_log_info2_quiet "Unsetting ${KEY}" + if [[ "$KEY" == "rev-env-var" ]]; then + fn-plugin-property-write "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$KEY" "$VALUE" + else + fn-plugin-property-delete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$KEY" + fi + fi +} + +service-set-cmd "$@" diff --git a/plugins/mysql/subcommands/start b/plugins/mysql/subcommands/start new file mode 100755 index 000000000..c928f3a6e --- /dev/null +++ b/plugins/mysql/subcommands/start @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-start-cmd() { + #E start the service + #E dokku $PLUGIN_COMMAND_PREFIX:start lollipop + #A service, service to run command against + declare desc="start a previously stopped $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:start" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_start "$SERVICE" +} + +service-start-cmd "$@" diff --git a/plugins/mysql/subcommands/stop b/plugins/mysql/subcommands/stop new file mode 100755 index 000000000..9680ea503 --- /dev/null +++ b/plugins/mysql/subcommands/stop @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-stop-cmd() { + #E stop the service and removes the running container + #E dokku $PLUGIN_COMMAND_PREFIX:stop lollipop + #A service, service to run command against + declare desc="stop a running $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:stop" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_container_rm "$SERVICE" +} + +service-stop-cmd "$@" diff --git a/plugins/mysql/subcommands/unexpose b/plugins/mysql/subcommands/unexpose new file mode 100755 index 000000000..a3e990a24 --- /dev/null +++ b/plugins/mysql/subcommands/unexpose @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-unexpose-cmd() { + #E unexpose the service, removing access to it from the public interface (0.0.0.0) + #E dokku $PLUGIN_COMMAND_PREFIX:unexpose lollipop + #A service, service to run command against + declare desc="unexpose a previously exposed $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:unexpose" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_port_unexpose "$SERVICE" +} + +service-unexpose-cmd "$@" diff --git a/plugins/mysql/subcommands/unlink b/plugins/mysql/subcommands/unlink new file mode 100755 index 000000000..63c4c97f4 --- /dev/null +++ b/plugins/mysql/subcommands/unlink @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-unlink-cmd() { + #E you can unlink a $PLUGIN_COMMAND_PREFIX service + #E > NOTE: this will restart your app and unset related environment variables + #E dokku $PLUGIN_COMMAND_PREFIX:unlink lollipop playground + #A service, service to run command against + #A app, app to run command against + #F -n|--no-restart "false", whether or not to restart the app on unlink (default: true) + declare desc="unlink the $PLUGIN_SERVICE service from the app" + local cmd="$PLUGIN_COMMAND_PREFIX:unlink" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" APP="$2" + APP=${APP:="$DOKKU_APP_NAME"} + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + verify_service_name "$SERVICE" + verify_app_name "$APP" + service_unlink "$SERVICE" "$APP" +} + +service-unlink-cmd "$@" diff --git a/plugins/mysql/subcommands/upgrade b/plugins/mysql/subcommands/upgrade new file mode 100755 index 000000000..3eeccde88 --- /dev/null +++ b/plugins/mysql/subcommands/upgrade @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" +source "$PLUGIN_AVAILABLE_PATH/ps/functions" + +service-upgrade-cmd() { + #E you can upgrade an existing service to a new image or image-version + #E dokku $PLUGIN_COMMAND_PREFIX:upgrade lollipop + #A service, service to run command against + #F -c|--config-options "--args --go=here", extra arguments to pass to the container create command + #F -C|--custom-env "USER=alpha;HOST=beta", semi-colon delimited environment variables to start the service with + #F -i|--image IMAGE, the image name to start the service with + #F -I|--image-version IMAGE_VERSION, the image version to start the service with + #F -N|--initial-network INITIAL_NETWORK, the initial network to attach the service to + #F -P|--post-create-network NETWORKS, a comma-separated list of networks to attach the service container to after service creation + #F -R|--restart-apps "true", whether or not to force an app restart (default: false) + #F -S|--post-start-network NETWORKS, a comma-separated list of networks to attach the service container to after service start + #F -s|--shm-size SHM_SIZE, override shared memory size for $PLUGIN_COMMAND_PREFIX docker container + declare desc="upgrade service to the specified versions" + local cmd="$PLUGIN_COMMAND_PREFIX:upgrade" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" UPGRADE_FLAGS_LIST=("${@:2}") + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + + service_parse_args "${@:2}" + + if ! service_image_exists "$SERVICE" "$PLUGIN_IMAGE" "$PLUGIN_IMAGE_VERSION"; then + if [[ "$PLUGIN_DISABLE_PULL" == "true" ]]; then + dokku_log_warn "${PLUGIN_DISABLE_PULL_VARIABLE} environment variable detected. Not running pull command." 1>&2 + dokku_log_warn " docker image pull ${IMAGE}" 1>&2 + dokku_log_warn "$PLUGIN_SERVICE service $SERVICE upgrade failed" + exit 1 + fi + "$DOCKER_BIN" image pull "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" || dokku_log_fail "$PLUGIN_SERVICE image $PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION pull failed" + fi + + local NEW_PLUGIN_IMAGE_TAG="$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" + if [[ "$(service_version "$SERVICE")" == "$NEW_PLUGIN_IMAGE_TAG" ]]; then + dokku_log_info1 "Service $SERVICE already running $NEW_PLUGIN_IMAGE_TAG" + return + fi + + service_commit_config "$SERVICE" + + dokku_log_info2 "Upgrading $SERVICE to $NEW_PLUGIN_IMAGE_TAG" + if [[ "$SERVICE_RESTART_APPS" == "true" ]]; then + dokku_log_info2 "Stopping all linked services" + for app in $(service_linked_apps "$SERVICE"); do + [[ "$app" == "-" ]] && continue + ps_stop "$app" + done + fi + + dokku_log_info2 "Stopping $SERVICE" + service_container_rm "$SERVICE" + service_start "$SERVICE" "${@:2}" + + if [[ "$SERVICE_RESTART_APPS" == "true" ]]; then + dokku_log_info2 "Starting all linked services" + for app in $(service_linked_apps "$SERVICE"); do + [[ "$app" == "-" ]] && continue + ps_start "$app" + done + fi + + dokku_log_info2 "Done" +} + +service-upgrade-cmd "$@" diff --git a/plugins/mysql/tests/hook_pre_delete.bats b/plugins/mysql/tests/hook_pre_delete.bats new file mode 100755 index 000000000..600b8b047 --- /dev/null +++ b/plugins/mysql/tests/hook_pre_delete.bats @@ -0,0 +1,20 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku apps:create my-app + dokku "$PLUGIN_COMMAND_PREFIX:create" ls + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app >&2 +} + +teardown() { + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app >&2 + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ls + dokku --force apps:destroy my-app || true +} + +@test "($PLUGIN_COMMAND_PREFIX:hook:pre-delete) removes app from links file when destroying app" { + [[ -n $(<"$PLUGIN_DATA_ROOT/ls/LINKS") ]] + dokku --force apps:destroy my-app + [[ -z $(<"$PLUGIN_DATA_ROOT/ls/LINKS") ]] +} diff --git a/plugins/mysql/tests/link_networks.bats b/plugins/mysql/tests/link_networks.bats new file mode 100755 index 000000000..3d3763817 --- /dev/null +++ b/plugins/mysql/tests/link_networks.bats @@ -0,0 +1,289 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" ls + dokku network:create custom-network +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ls || true + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" lsa || true + dokku network:destroy --force custom-network +} + +@test "($PLUGIN_COMMAND_PREFIX:set) set initial-network" { + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls initial-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --initial-network + echo "output: $output" + echo "status: $status" + assert_output "custom-network" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge 0 + assert_output_contains custom-network + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{range $k,$alias := $v.DNSNames}}{{printf "alias:%s\n" $alias}}{{end}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "alias:dokku.$PLUGIN_COMMAND_PREFIX.ls" + assert_output_contains "alias:dokku-$PLUGIN_COMMAND_PREFIX-ls" + + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls initial-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --initial-network + echo "output: $output" + echo "status: $status" + assert_output "" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 +} + +@test "($PLUGIN_COMMAND_PREFIX:set) set post-create-network" { + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls post-create-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --post-create-network + echo "output: $output" + echo "status: $status" + assert_output "custom-network" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains custom-network + assert_output_contains bridge + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{range $k,$alias := $v.DNSNames}}{{printf "alias:%s\n" $alias}}{{end}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "alias:dokku.$PLUGIN_COMMAND_PREFIX.ls" + assert_output_contains "alias:dokku-$PLUGIN_COMMAND_PREFIX-ls" + + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls post-create-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --post-create-network + echo "output: $output" + echo "status: $status" + assert_output "" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 +} + +@test "($PLUGIN_COMMAND_PREFIX:set) set an post-start-network" { + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls post-start-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --post-start-network + echo "output: $output" + echo "status: $status" + assert_output "custom-network" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{range $k,$alias := $v.DNSNames}}{{printf "alias:%s\n" $alias}}{{end}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "alias:dokku.$PLUGIN_COMMAND_PREFIX.ls" + assert_output_contains "alias:dokku-$PLUGIN_COMMAND_PREFIX-ls" + + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls post-start-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --post-start-network + echo "output: $output" + echo "status: $status" + assert_output "" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 +} + +@test "($PLUGIN_COMMAND_PREFIX:create) flags" { + run dokku "$PLUGIN_COMMAND_PREFIX:create" lsa --initial-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect "dokku.$PLUGIN_COMMAND_PREFIX.lsa" -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge 0 + assert_output_contains custom-network + + run dokku "$PLUGIN_COMMAND_PREFIX:destroy" lsa --force + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:create" lsa --post-create-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect "dokku.$PLUGIN_COMMAND_PREFIX.lsa" -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.lsa -f '{{range $net,$v := .NetworkSettings.Networks}}{{range $k,$alias := $v.DNSNames}}{{printf "alias:%s\n" $alias}}{{end}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "alias:dokku.$PLUGIN_COMMAND_PREFIX.lsa" + assert_output_contains "alias:dokku-$PLUGIN_COMMAND_PREFIX-lsa" + + run dokku "$PLUGIN_COMMAND_PREFIX:destroy" lsa --force + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:create" lsa --post-start-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect "dokku.$PLUGIN_COMMAND_PREFIX.lsa" -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network + + run dokku "$PLUGIN_COMMAND_PREFIX:destroy" lsa --force + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/plugins/mysql/tests/service_clone.bats b/plugins/mysql/tests/service_clone.bats new file mode 100755 index 000000000..e14809bc0 --- /dev/null +++ b/plugins/mysql/tests/service_clone.bats @@ -0,0 +1,47 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:clone) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:clone" + assert_contains "${lines[*]}" "Please specify a valid name for the service" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:clone) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:clone" not_existing_service new_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:clone) error when new service isn't provided" { + run dokku "$PLUGIN_COMMAND_PREFIX:clone" l + assert_contains "${lines[*]}" "Please specify a name for the new service" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:clone) error when new service already exists" { + dokku "$PLUGIN_COMMAND_PREFIX:create" new_service + run dokku "$PLUGIN_COMMAND_PREFIX:clone" l new_service + assert_contains "${lines[*]}" "Invalid service name new_service" + assert_failure + + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" new_service +} + +@test "($PLUGIN_COMMAND_PREFIX:clone) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:clone" l new_service + [[ -f $PLUGIN_DATA_ROOT/new_service/ID ]] + assert_contains "${lines[*]}" "Copying data from l to new_service" + assert_contains "${lines[*]}" "Done" + assert_success + + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" new_service +} diff --git a/plugins/mysql/tests/service_connect.bats b/plugins/mysql/tests/service_connect.bats new file mode 100755 index 000000000..4af0a2b19 --- /dev/null +++ b/plugins/mysql/tests/service_connect.bats @@ -0,0 +1,26 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:connect) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:connect" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:connect) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:connect" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:connect) success" { + skip "Connect hangs indefinitely without input" + run dokku "$PLUGIN_COMMAND_PREFIX:connect" l + assert_success +} diff --git a/plugins/mysql/tests/service_create.bats b/plugins/mysql/tests/service_create.bats new file mode 100755 index 000000000..e9ac62dd8 --- /dev/null +++ b/plugins/mysql/tests/service_create.bats @@ -0,0 +1,27 @@ +#!/usr/bin/env bats +load test_helper + +@test "($PLUGIN_COMMAND_PREFIX:create) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:create" l + assert_contains "${lines[*]}" "container created: l" + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:create) service with dashes" { + run dokku "$PLUGIN_COMMAND_PREFIX:create" service-with-dashes + assert_contains "${lines[*]}" "container created: service-with-dashes" + assert_contains "${lines[*]}" "dokku-$PLUGIN_COMMAND_PREFIX-service-with-dashes" + assert_contains "${lines[*]}" "service_with_dashes" + + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" service-with-dashes +} + +@test "($PLUGIN_COMMAND_PREFIX:create) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:create" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:create) error when there is an invalid name specified" { + run dokku "$PLUGIN_COMMAND_PREFIX:create" d.erp + assert_failure +} diff --git a/plugins/mysql/tests/service_destroy.bats b/plugins/mysql/tests/service_destroy.bats new file mode 100755 index 000000000..98695ead2 --- /dev/null +++ b/plugins/mysql/tests/service_destroy.bats @@ -0,0 +1,30 @@ +#!/usr/bin/env bats +load test_helper + +@test "($PLUGIN_COMMAND_PREFIX:destroy) success with --force" { + dokku "$PLUGIN_COMMAND_PREFIX:create" l + run dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + assert_contains "${lines[*]}" "container deleted: l" +} + +@test "($PLUGIN_COMMAND_PREFIX:destroy) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:destroy" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:destroy) error when container does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:destroy" non_existing_container + assert_contains "${lines[*]}" "service non_existing_container does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:destroy) error when container is linked to an app" { + dokku "$PLUGIN_COMMAND_PREFIX:create" l + dokku apps:create app + dokku "$PLUGIN_COMMAND_PREFIX:link" l app + run dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + assert_contains "${lines[*]}" "Cannot delete linked service" + + dokku "$PLUGIN_COMMAND_PREFIX:unlink" l app + run dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + assert_contains "${lines[*]}" "container deleted: l" +} diff --git a/plugins/mysql/tests/service_export.bats b/plugins/mysql/tests/service_export.bats new file mode 100755 index 000000000..845702c52 --- /dev/null +++ b/plugins/mysql/tests/service_export.bats @@ -0,0 +1,39 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:export) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:export" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:export) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:export" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:export) success with SSH_TTY" { + if [[ -n "$GITHUB_WORKFLOW" ]]; then + skip "No tty is available on Github Actions" + fi + export SSH_TTY=$(tty) + run dokku "$PLUGIN_COMMAND_PREFIX:export" l + echo "output: $output" + echo "status: $status" + assert_exit_status 0 +} + +@test "($PLUGIN_COMMAND_PREFIX:export) success without SSH_TTY" { + unset SSH_TTY + run dokku "$PLUGIN_COMMAND_PREFIX:export" l + echo "output: $output" + echo "status: $status" + assert_exit_status 0 +} diff --git a/plugins/mysql/tests/service_expose.bats b/plugins/mysql/tests/service_expose.bats new file mode 100755 index 000000000..f3c4df4c9 --- /dev/null +++ b/plugins/mysql/tests/service_expose.bats @@ -0,0 +1,66 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" ls +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ls +} + +@test "($PLUGIN_COMMAND_PREFIX:expose) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:expose" + echo "output: $output" + echo "status: $status" + assert_failure + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:expose) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:expose" not_existing_service + echo "output: $output" + echo "status: $status" + assert_failure + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:expose) error when already exposed" { + run dokku "$PLUGIN_COMMAND_PREFIX:expose" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:expose" ls + echo "output: $output" + echo "status: $status" + assert_failure + assert_contains "${lines[*]}" "Service ls already exposed on port(s)" + + run sudo rm "$PLUGIN_DATA_ROOT/ls/PORT" + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:expose" ls + echo "output: $output" + echo "status: $status" + assert_success + assert_contains "${lines[*]}" "Service ls has an untracked expose container, removing" +} + +@test "($PLUGIN_COMMAND_PREFIX:expose) success when not providing custom ports" { + run dokku "$PLUGIN_COMMAND_PREFIX:expose" ls + echo "output: $output" + echo "status: $status" + assert_success + [[ "${lines[*]}" =~ exposed\ on\ port\(s\)\ \[container\-\>host\]\:\ [[:digit:]]+ ]] +} + +@test "($PLUGIN_COMMAND_PREFIX:expose) success when providing custom ports" { + run dokku "$PLUGIN_COMMAND_PREFIX:expose" ls 4242 + echo "output: $output" + echo "status: $status" + assert_success + assert_contains "${lines[*]}" "exposed on port(s) [container->host]: 3306->4242" +} diff --git a/plugins/mysql/tests/service_import.bats b/plugins/mysql/tests/service_import.bats new file mode 100755 index 000000000..f27ac3481 --- /dev/null +++ b/plugins/mysql/tests/service_import.bats @@ -0,0 +1,40 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l + echo "CREATE DATABASE IF NOT EXISTS l;" | tee "/tmp/fake.sql" +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + rm -f "/tmp/fake.sql" +} + +@test "($PLUGIN_COMMAND_PREFIX:import) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:import" + assert_contains "${lines[*]}" "Please specify a valid name for the service" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:import) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:import" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:import) error when data is not provided" { + if [[ -n "$GITHUB_WORKFLOW" ]]; then + skip "No tty is available on Github Actions" + fi + run dokku "$PLUGIN_COMMAND_PREFIX:import" l + assert_contains "${lines[*]}" "No data provided on stdin" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:import) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:import" l <"/tmp/fake.sql" + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/plugins/mysql/tests/service_info.bats b/plugins/mysql/tests/service_info.bats new file mode 100755 index 000000000..e20f79df4 --- /dev/null +++ b/plugins/mysql/tests/service_info.bats @@ -0,0 +1,75 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:info) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:info" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:info) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:info" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:info) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:info" l + local password="$(sudo cat "$PLUGIN_DATA_ROOT/l/PASSWORD")" + assert_contains "${lines[*]}" "mysql://mysql:$password@dokku-mysql-l:3306/l" +} + +@test "($PLUGIN_COMMAND_PREFIX:info) replaces underscores by dash in hostname" { + dokku "$PLUGIN_COMMAND_PREFIX:create" test_with_underscores + run dokku "$PLUGIN_COMMAND_PREFIX:info" test_with_underscores + local password="$(sudo cat "$PLUGIN_DATA_ROOT/test_with_underscores/PASSWORD")" + assert_contains "${lines[*]}" "mysql://mysql:$password@dokku-mysql-test-with-underscores:3306/test_with_underscores" + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" test_with_underscores +} + +@test "($PLUGIN_COMMAND_PREFIX:info) success with flag" { + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --dsn + local password="$(sudo cat "$PLUGIN_DATA_ROOT/l/PASSWORD")" + assert_output "mysql://mysql:$password@dokku-mysql-l:3306/l" + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --config-dir + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --data-dir + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --dsn + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --exposed-ports + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --id + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --internal-ip + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --links + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --service-root + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --status + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --version + assert_success +} + +@test "($PLUGIN_COMMAND_PREFIX:info) error when invalid flag" { + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --invalid-flag + assert_failure +} diff --git a/plugins/mysql/tests/service_link.bats b/plugins/mysql/tests/service_link.bats new file mode 100755 index 000000000..c20793124 --- /dev/null +++ b/plugins/mysql/tests/service_link.bats @@ -0,0 +1,142 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" ls + dokku "$PLUGIN_COMMAND_PREFIX:create" ms + dokku apps:create my-app +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ms + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ls + dokku --force apps:destroy my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "Please specify a valid name for the service" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:link) error when the app argument is missing" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "Please specify an app to run the command on" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:link) error when the app does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls not_existing_app + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "App not_existing_app does not exist" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:link) error when the service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" not_existing_service my-app + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "service not_existing_service does not exist" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:link) error when the service is already linked to app" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "Already linked as DATABASE_URL" + assert_failure + + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) exports DATABASE_URL to app" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + echo "output: $output" + echo "status: $status" + url=$(dokku config:get my-app DATABASE_URL) + password="$(sudo cat "$PLUGIN_DATA_ROOT/ls/PASSWORD")" + assert_contains "$url" "mysql://mysql:$password@dokku-mysql-ls:3306/ls" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) generates an alternate config url when DATABASE_URL already in use" { + dokku config:set my-app DATABASE_URL=mysql://user:pass@host:3306/db + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + run dokku config my-app + assert_contains "${lines[*]}" "DOKKU_MYSQL_AQUA_URL" + assert_success + + dokku "$PLUGIN_COMMAND_PREFIX:link" ms my-app + run dokku config my-app + assert_contains "${lines[*]}" "DOKKU_MYSQL_BLACK_URL" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ms my-app + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) links to app with docker-options" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + run dokku docker-options:report my-app + assert_contains "${lines[*]}" "--link dokku.mysql.ls:dokku-mysql-ls" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) uses apps MYSQL_DATABASE_SCHEME variable" { + dokku config:set my-app MYSQL_DATABASE_SCHEME=mysql2 + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + url=$(dokku config:get my-app DATABASE_URL) + password="$(sudo cat "$PLUGIN_DATA_ROOT/ls/PASSWORD")" + assert_contains "$url" "mysql2://mysql:$password@dokku-mysql-ls:3306/ls" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) adds a querystring" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app --querystring "pool=5" + url=$(dokku config:get my-app DATABASE_URL) + assert_contains "$url" "?pool=5" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) uses a specified config url when alias is specified" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app --alias "ALIAS" + url=$(dokku config:get my-app ALIAS_URL) + password="$(sudo cat "$PLUGIN_DATA_ROOT/ls/PASSWORD")" + assert_contains "$url" "mysql://mysql:$password@dokku-mysql-ls:3306/ls" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) respects --no-restart" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + echo "output: $output" + echo "status: $status" + assert_output_contains "Skipping restart of linked app" 0 + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app --no-restart + echo "output: $output" + echo "status: $status" + assert_output_contains "Skipping restart of linked app" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/plugins/mysql/tests/service_list.bats b/plugins/mysql/tests/service_list.bats new file mode 100755 index 000000000..8ace1312a --- /dev/null +++ b/plugins/mysql/tests/service_list.bats @@ -0,0 +1,22 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:list) with no exposed ports, no linked apps" { + run dokku --quiet "$PLUGIN_COMMAND_PREFIX:list" + assert_output "l" +} + +@test "($PLUGIN_COMMAND_PREFIX:list) when there are no services" { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + run dokku "$PLUGIN_COMMAND_PREFIX:list" + assert_output "${lines[*]}" "There are no $PLUGIN_SERVICE services" + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} diff --git a/plugins/mysql/tests/service_logs.bats b/plugins/mysql/tests/service_logs.bats new file mode 100755 index 000000000..d12585cdd --- /dev/null +++ b/plugins/mysql/tests/service_logs.bats @@ -0,0 +1,42 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:logs) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:logs" + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "Please specify a valid name for the service" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:logs) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:logs" not_existing_service + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "service not_existing_service does not exist" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:logs) success when not tailing" { + skip "This may fail if there is no log output" + run dokku "$PLUGIN_COMMAND_PREFIX:logs" l + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "($PLUGIN_COMMAND_PREFIX:logs) success when tailing" { + skip "This will hang as it waits for log output" + run dokku "$PLUGIN_COMMAND_PREFIX:logs" l -t + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/plugins/mysql/tests/service_pause.bats b/plugins/mysql/tests/service_pause.bats new file mode 100755 index 000000000..a2a2a696c --- /dev/null +++ b/plugins/mysql/tests/service_pause.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:pause) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:pause" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:pause) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:pause" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:pause) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:pause" l + assert_success +} diff --git a/plugins/mysql/tests/service_promote.bats b/plugins/mysql/tests/service_promote.bats new file mode 100755 index 000000000..d78798312 --- /dev/null +++ b/plugins/mysql/tests/service_promote.bats @@ -0,0 +1,63 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l + dokku apps:create my-app + dokku "$PLUGIN_COMMAND_PREFIX:link" l my-app +} + +teardown() { + dokku "$PLUGIN_COMMAND_PREFIX:unlink" l my-app + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + dokku --force apps:destroy my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:promote" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) error when the app argument is missing" { + run dokku "$PLUGIN_COMMAND_PREFIX:promote" l + assert_contains "${lines[*]}" "Please specify an app to run the command on" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) error when the app does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:promote" l not_existing_app + assert_contains "${lines[*]}" "App not_existing_app does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) error when the service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:promote" not_existing_service my-app + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) error when the service is already promoted" { + run dokku "$PLUGIN_COMMAND_PREFIX:promote" l my-app + assert_contains "${lines[*]}" "already promoted as DATABASE_URL" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) changes DATABASE_URL" { + password="$(sudo cat "$PLUGIN_DATA_ROOT/l/PASSWORD")" + dokku config:set my-app "DATABASE_URL=mysql://u:p@host:3306/db" "DOKKU_MYSQL_BLUE_URL=mysql://mysql:$password@dokku-mysql-l:3306/l" + dokku "$PLUGIN_COMMAND_PREFIX:promote" l my-app + url=$(dokku config:get my-app DATABASE_URL) + assert_equal "$url" "mysql://mysql:$password@dokku-mysql-l:3306/l" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) creates new config url when needed" { + password="$(sudo cat "$PLUGIN_DATA_ROOT/l/PASSWORD")" + dokku config:set my-app "DATABASE_URL=mysql://u:p@host:3306/db" "DOKKU_MYSQL_BLUE_URL=mysql://mysql:$password@dokku-mysql-l:3306/l" + dokku "$PLUGIN_COMMAND_PREFIX:promote" l my-app + run dokku config my-app + assert_contains "${lines[*]}" "DOKKU_MYSQL_" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) uses MYSQL_DATABASE_SCHEME variable" { + password="$(sudo cat "$PLUGIN_DATA_ROOT/l/PASSWORD")" + dokku config:set my-app "MYSQL_DATABASE_SCHEME=mysql2" "DATABASE_URL=mysql://u:p@host:3306/db" "DOKKU_MYSQL_BLUE_URL=mysql2://mysql:$password@dokku-mysql-l:3306/l" + dokku "$PLUGIN_COMMAND_PREFIX:promote" l my-app + url=$(dokku config:get my-app DATABASE_URL) + assert_contains "$url" "mysql2://mysql:$password@dokku-mysql-l:3306/l" +} diff --git a/plugins/mysql/tests/service_restart.bats b/plugins/mysql/tests/service_restart.bats new file mode 100755 index 000000000..e74cc4752 --- /dev/null +++ b/plugins/mysql/tests/service_restart.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:restart) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:restart" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:restart) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:restart" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:restart) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:restart" l + assert_success +} diff --git a/plugins/mysql/tests/service_start.bats b/plugins/mysql/tests/service_start.bats new file mode 100755 index 000000000..adff1d20e --- /dev/null +++ b/plugins/mysql/tests/service_start.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:start) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:start" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:start) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:start" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:start) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:start" l + assert_success +} diff --git a/plugins/mysql/tests/service_stop.bats b/plugins/mysql/tests/service_stop.bats new file mode 100755 index 000000000..826bc917c --- /dev/null +++ b/plugins/mysql/tests/service_stop.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:stop) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:stop" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:stop) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:stop" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:stop) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:stop" l + assert_success +} diff --git a/plugins/mysql/tests/service_unexpose.bats b/plugins/mysql/tests/service_unexpose.bats new file mode 100755 index 000000000..1442728f5 --- /dev/null +++ b/plugins/mysql/tests/service_unexpose.bats @@ -0,0 +1,27 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:unexpose) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:unexpose" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:unexpose) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:unexpose" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:unexpose) success" { + dokku "$PLUGIN_COMMAND_PREFIX:expose" l + run dokku "$PLUGIN_COMMAND_PREFIX:unexpose" l + [[ ! -f $PLUGIN_DATA_ROOT/PORT ]] + assert_contains "${lines[*]}" "Service l unexposed" +} diff --git a/plugins/mysql/tests/service_unlink.bats b/plugins/mysql/tests/service_unlink.bats new file mode 100755 index 000000000..164baf2a7 --- /dev/null +++ b/plugins/mysql/tests/service_unlink.bats @@ -0,0 +1,77 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku apps:create my-app + dokku "$PLUGIN_COMMAND_PREFIX:create" ls +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ls + dokku --force apps:destroy my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) error when the app argument is missing" { + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls + assert_contains "${lines[*]}" "Please specify an app to run the command on" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) error when the app does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls not_existing_app + assert_contains "${lines[*]}" "App not_existing_app does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) error when the service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" not_existing_service my-app + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) error when service not linked to app" { + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + assert_contains "${lines[*]}" "Not linked to app my-app" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) removes link from docker-options" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app >&2 + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + + check_value="Docker options build: Docker options deploy: --restart=on-failure:10 Docker options run:" + options=$(dokku --quiet docker-options:report my-app | xargs) + assert_equal "$options" "$check_value" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) unsets config url from app" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app >&2 + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + config=$(dokku config:get my-app DATABASE_URL || true) + assert_equal "$config" "" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) respects --no-restart" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + echo "output: $output" + echo "status: $status" + assert_output_contains "Skipping restart of linked app" 0 + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app --no-restart + echo "output: $output" + echo "status: $status" + assert_output_contains "Skipping restart of linked app" + assert_success +} diff --git a/plugins/mysql/tests/setup.sh b/plugins/mysql/tests/setup.sh new file mode 100644 index 000000000..c48b0c139 --- /dev/null +++ b/plugins/mysql/tests/setup.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $TRACE ]] && set -x +sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 762E3157 +echo "deb http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" | sudo tee /etc/apt/sources.list.d/nginx.list +curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add - + +sudo mkdir -p /etc/nginx +sudo curl https://raw.githubusercontent.com/dokku/dokku/master/tests/dhparam.pem -o /etc/nginx/dhparam.pem + +echo "dokku dokku/skip_key_file boolean true" | sudo debconf-set-selections +wget https://raw.githubusercontent.com/dokku/dokku/master/bootstrap.sh +if [[ "$DOKKU_VERSION" == "master" ]]; then + sudo bash bootstrap.sh +else + sudo DOKKU_TAG="$DOKKU_VERSION" bash bootstrap.sh +fi +echo "Dokku version $DOKKU_VERSION" + +export DOKKU_LIB_ROOT="/var/lib/dokku" +export DOKKU_PLUGINS_ROOT="$DOKKU_LIB_ROOT/plugins/available" +pushd "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")" >/dev/null +source "config" +popd >/dev/null +sudo rm -rf "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX" +sudo mkdir -p "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX" "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/subcommands" "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/scripts" "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/templates" +sudo find ./ -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX" \; +[[ -d "./scripts" ]] && sudo find ./scripts -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/scripts" \; +[[ -d "./subcommands" ]] && sudo find ./subcommands -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/subcommands" \; +[[ -d "./templates" ]] && sudo find ./templates -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/templates" \; +sudo mkdir -p "$PLUGIN_CONFIG_ROOT" "$PLUGIN_DATA_ROOT" +sudo dokku plugin:enable "$PLUGIN_COMMAND_PREFIX" +sudo dokku plugin:install diff --git a/plugins/mysql/tests/shellcheck-exclude b/plugins/mysql/tests/shellcheck-exclude new file mode 100644 index 000000000..9fbac741c --- /dev/null +++ b/plugins/mysql/tests/shellcheck-exclude @@ -0,0 +1,8 @@ +# SC1090 - Can't follow non-constant source. Use a directive to specify location - https://github.com/koalaman/shellcheck/wiki/SC1090 +# SC1091 - Not following - +# SC2034 - Variable appears unused. Verify it or export it - https://github.com/koalaman/shellcheck/wiki/SC2034 +# SC2155 - Declare and assign separately to avoid masking return values - https://github.com/koalaman/shellcheck/wiki/SC2155 +# SC2206 - Quote to prevent word splitting/globbing, or split robustly with mapfile or read -a - https://github.com/koalaman/shellcheck/wiki/SC2206 +# SC2207 - Prefer mapfile or read -a to split command output (or quote to avoid splitting) - https://github.com/koalaman/shellcheck/wiki/SC2207 +# SC2220 - Invalid flags are not handled. Add a *) case - https://github.com/koalaman/shellcheck/wiki/SC2220 +# SC2230 - which is non-standard. Use builtin 'command -v' instead - https://github.com/koalaman/shellcheck/wiki/SC2230 diff --git a/plugins/mysql/tests/shellcheck-to-junit b/plugins/mysql/tests/shellcheck-to-junit new file mode 100755 index 000000000..f2ebf795f --- /dev/null +++ b/plugins/mysql/tests/shellcheck-to-junit @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from __future__ import print_function +import argparse +import collections +import datetime +import re +import socket +import sys + +from xml.etree import ElementTree + + +def CDATA(text=None): + element = ElementTree.Element('![CDATA[') + element.text = text + return element + + +def _serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs): + + if elem.tag == '![CDATA[': + write("\n<{}{}]]>\n".format(elem.tag, elem.text)) + if elem.tail: + write(ElementTree._escape_cdata(elem.tail)) + else: + return ElementTree._original_serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs) + + +ElementTree._original_serialize_xml = ElementTree._serialize_xml +ElementTree._serialize_xml = ElementTree._serialize['xml'] = _serialize_xml + + +def read_in(): + lines = sys.stdin.readlines() + for i in range(len(lines)): + lines[i] = lines[i].rstrip() + return lines + + +def process_lines(lines): + files = {} + current_file = None + previous_line = None + line_no = None + new_issues = [] + code = None + + RE_VIOLATION = re.compile(r"\^-- (SC[\w]+): (.*)") + RE_VIOLATION_NEW = re.compile(r"\^[-]+\^ (SC[\w]+): (.*)") + + for line in lines: + # start a new block + if line == '': + if current_file is not None: + file_data = files.get(current_file, {}) + files[current_file] = file_data + + issue_data = file_data.get(line_no, {}) + issue_data['code'] = code + files[current_file][line_no] = issue_data + + issues = issue_data.get('issues', []) + issues.extend(new_issues) + issue_data['issues'] = issues + + files[current_file][line_no] = issue_data + + code = None + current_file = None + line_no = None + elif line.startswith('In ./') and not previous_line: + current_file = line.split(' ')[1].replace('./', '') + line_no = line.split(' ')[3] + new_issues = [] + code = None + elif code is None and len(new_issues) == 0: + code = line + else: + match = RE_VIOLATION.match(line.strip()) + if not match: + match = RE_VIOLATION_NEW.match(line.strip()) + + if not match: + if 'https://www.shellcheck.net/wiki/SC' in line: + continue + if 'For more information:' == line: + continue + print('Error: Issue parsing line "{0}"'.format(line.strip())) + else: + new_issues.append({ + 'shellcheck_id': match.group(1), + 'message': match.group(2), + 'original_message': line + }) + + previous_line = line + + return files + + +def output_junit(files, args): + timestamp = datetime.datetime.now().replace(microsecond=0).isoformat() + failures = 0 + for file, data in files.items(): + for line, issue_data in data.items(): + code = issue_data.get('code') + for issue in issue_data.get('issues', []): + failures += 1 + + tests = 0 + if args.files: + with open(args.files, 'r') as f: + tests = len(f.readlines()) + + root = ElementTree.Element("testsuite", + name="shellcheck", + tests="{0}".format(tests), + failures="{0}".format(failures), + errors="0", + skipped="0", + timestamp=timestamp, + time="0", + hostname=socket.gethostname()) + + properties = ElementTree.SubElement(root, "properties") + if args.exclude: + ElementTree.SubElement(properties, + "property", + name="exclude", + value=args.exclude) + + if args.files: + with open(args.files, 'r') as f: + lines = f.readlines() + for i in range(len(lines)): + file = lines[i].rstrip().replace('./', '') + data = files.get(file, None) + if data: + for line, issue_data in data.items(): + code = issue_data.get('code') + for issue in issue_data.get('issues', []): + testcase = ElementTree.SubElement(root, + "testcase", + classname=file, + name=file, + time="0") + shellcheck_id = issue.get('shellcheck_id') + message = 'line {0}: {1}'.format( + line, issue.get('message')) + original_message = issue.get('original_message') + e = ElementTree.Element("failure", + type=shellcheck_id, + message=message) + cdata = CDATA("\n".join([code, original_message])) + e.append(cdata) + testcase.append(e) + ElementTree.SubElement(root, + "testcase", + classname=file, + name=file, + time="0") + + ElementTree.SubElement(root, "system-out") + ElementTree.SubElement(root, "system-err") + + content = ElementTree.tostring(root, encoding='UTF-8', method='xml') + if args.output: + with open(args.output, 'w') as f: + try: + f.write(content) + except TypeError: + f.write(content.decode("utf-8")) + + +def main(): + parser = argparse.ArgumentParser( + description='Process shellcheck output to junit.') + parser.add_argument('--output', + dest='output', + action='store', + default=None, + help='file to write shellcheck output') + parser.add_argument('--files', + dest='files', + action='store', + default=None, + help='a file containing a list of all files processed by shellcheck') + parser.add_argument('--exclude', + dest='exclude', + action='store', + default=None, + help='a comma-separated list of rules being excluded by shellcheck') + args = parser.parse_args() + + lines = read_in() + files = process_lines(lines) + files = collections.OrderedDict(sorted(files.items())) + output_junit(files, args) + for line in lines: + print(line) + + +if __name__ == '__main__': + main() diff --git a/plugins/mysql/tests/test_helper.bash b/plugins/mysql/tests/test_helper.bash new file mode 100755 index 000000000..fb397ab7d --- /dev/null +++ b/plugins/mysql/tests/test_helper.bash @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +export DOKKU_LIB_ROOT="/var/lib/dokku" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" + +flunk() { + { + if [ "$#" -eq 0 ]; then + cat - + else + echo "$*" + fi + } + return 1 +} + +assert_equal() { + if [ "$1" != "$2" ]; then + { + echo "expected: $1" + echo "actual: $2" + } | flunk + fi +} + +# ShellCheck doesn't know about $status from Bats +# shellcheck disable=SC2154 +assert_exit_status() { + assert_equal "$1" "$status" +} + +# ShellCheck doesn't know about $status from Bats +# shellcheck disable=SC2154 +# shellcheck disable=SC2120 +assert_success() { + if [ "$status" -ne 0 ]; then + flunk "command failed with exit status $status" + elif [ "$#" -gt 0 ]; then + assert_output "$1" + fi +} + +assert_failure() { + if [[ "$status" -eq 0 ]]; then + flunk "expected failed exit status" + elif [[ "$#" -gt 0 ]]; then + assert_output "$1" + fi +} + +assert_exists() { + if [ ! -f "$1" ]; then + flunk "expected file to exist: $1" + fi +} + +assert_contains() { + if [[ "$1" != *"$2"* ]]; then + flunk "expected $2 to be in: $1" + fi +} + +# ShellCheck doesn't know about $output from Bats +# shellcheck disable=SC2154 +assert_output() { + local expected + if [ $# -eq 0 ]; then + expected="$(cat -)" + else + expected="$1" + fi + assert_equal "$expected" "$output" +} + +# ShellCheck doesn't know about $output from Bats +# shellcheck disable=SC2154 +assert_output_contains() { + local input="$output" + local expected="$1" + local count="${2:-1}" + local found=0 + until [ "${input/$expected/}" = "$input" ]; do + input="${input/$expected/}" + found=$((found + 1)) + done + assert_equal "$count" "$found" +} diff --git a/plugins/mysql/update b/plugins/mysql/update new file mode 120000 index 000000000..f7ffc47af --- /dev/null +++ b/plugins/mysql/update @@ -0,0 +1 @@ +install \ No newline at end of file diff --git a/plugins/postgres/.devcontainer/20_init_plugin b/plugins/postgres/.devcontainer/20_init_plugin new file mode 100755 index 000000000..55205064a --- /dev/null +++ b/plugins/postgres/.devcontainer/20_init_plugin @@ -0,0 +1,24 @@ +#!/bin/bash +set -eo pipefail + +log-info() { + declare desc="Log info formatter" + echo " $*" 1>&2 +} + +log-fail() { + declare desc="Log fail formatter" + echo "! $*" 1>&2 + exit 1 +} + +main() { + dokku plugin:install + + # built in the Dockerfile + PLUGIN_NAME="$(source /tmp/.env && echo "$PLUGIN_NAME")" + PLUGIN_VARIABLE="$(source /tmp/.env && echo "$PLUGIN_VARIABLE")" + echo "export ${PLUGIN_VARIABLE}_HOST_ROOT=${SERVICE_HOST_ROOT}/$PLUGIN_NAME" >/etc/default/dokku +} + +main "$@" diff --git a/plugins/postgres/.devcontainer/Dockerfile b/plugins/postgres/.devcontainer/Dockerfile new file mode 100644 index 000000000..8a6e852f5 --- /dev/null +++ b/plugins/postgres/.devcontainer/Dockerfile @@ -0,0 +1,26 @@ +FROM dokku/dokku:latest + +RUN apt-get update +RUN apt-get install --no-install-recommends -y build-essential file nano && \ + apt-get clean autoclean && \ + apt-get autoremove --yes && \ + rm -rf /var/lib/apt/lists/* && \ + mkdir -p /mnt/dokku/home/dokku /mnt/dokku/var/lib/dokku/config /mnt/dokku/var/lib/dokku/data /mnt/dokku/var/lib/dokku/services && \ + chown -R dokku:dokku /mnt/dokku/home/dokku /mnt/dokku/var/lib/dokku/config /mnt/dokku/var/lib/dokku/data /mnt/dokku/var/lib/dokku/services && \ + echo "dokku.me" > /home/dokku/VHOST + +ADD https://raw.githubusercontent.com/dokku/dokku/master/tests/dhparam.pem /mnt/dokku/etc/nginx/dhparam.pem + +COPY .devcontainer/20_init_plugin /etc/my_init.d/20_init_plugin +COPY .devcontainer/bin/ /usr/local/bin/ + +COPY . . + +RUN source /tmp/config && \ + echo "export ${PLUGIN_DISABLE_PULL_VARIABLE}=true" > /tmp/.env && \ + echo "export PLUGIN_NAME=${PLUGIN_COMMAND_PREFIX}" >> /tmp/.env && \ + echo "export PLUGIN_VARIABLE=${PLUGIN_VARIABLE}" >> /tmp/.env + +RUN source /tmp/.env && \ + dokku plugin:install file:///tmp --name $PLUGIN_NAME && \ + make ci-dependencies diff --git a/plugins/postgres/.devcontainer/bin/copy-file b/plugins/postgres/.devcontainer/bin/copy-file new file mode 100755 index 000000000..a97030753 --- /dev/null +++ b/plugins/postgres/.devcontainer/bin/copy-file @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +main() { + PLUGIN_NAME="$(source /tmp/.env && echo "$PLUGIN_NAME")" + cp "$1" "/var/lib/dokku/plugins/enabled/$PLUGIN_NAME/$1" +} + +main "$@" diff --git a/plugins/postgres/.devcontainer/devcontainer.json b/plugins/postgres/.devcontainer/devcontainer.json new file mode 100644 index 000000000..430d2f138 --- /dev/null +++ b/plugins/postgres/.devcontainer/devcontainer.json @@ -0,0 +1,16 @@ +{ + "build": { + "dockerfile": "Dockerfile", + "context": ".." + }, + "containerEnv": { + "SERVICE_HOST_ROOT": "${localWorkspaceFolder}/tmp/data" + }, + "initializeCommand": ["mkdir", "-p", "tmp/data"], + "mounts": [ + "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind", + "source=${localWorkspaceFolder}/tmp/data/,target=/var/lib/dokku/services/,type=bind" + ], + "overrideCommand": false, + "runArgs": ["--init"] +} diff --git a/plugins/postgres/.editorconfig b/plugins/postgres/.editorconfig new file mode 100644 index 000000000..514928225 --- /dev/null +++ b/plugins/postgres/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +insert_final_newline = true +indent_style = space +indent_size = 2 + +[Makefile] +insert_final_newline = true +indent_style = tab +indent_size = 4 + +[*.mk] +insert_final_newline = true +indent_style = tab +indent_size = 4 diff --git a/plugins/postgres/.github/dependabot.yml b/plugins/postgres/.github/dependabot.yml new file mode 100644 index 000000000..9d4798567 --- /dev/null +++ b/plugins/postgres/.github/dependabot.yml @@ -0,0 +1,12 @@ +--- +version: 2 +updates: + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/plugins/postgres/.github/labels.yml b/plugins/postgres/.github/labels.yml new file mode 100644 index 000000000..0ccbb96e5 --- /dev/null +++ b/plugins/postgres/.github/labels.yml @@ -0,0 +1,57 @@ +labels: +- name: bc-break + color: eb6420 +- name: blocks release + color: "000000" +- name: 'difficulty: easy' + color: c5def5 +- name: 'difficulty: hard' + color: e99695 +- name: 'difficulty: medium' + color: fef2c0 +- name: hacktoberfest + color: b0581d +- name: 'needs: documentation' + color: c2e0c6 +- name: 'needs: more info' + color: c2e0c6 +- name: 'needs: rebase' + color: c2e0c6 +- name: 'needs: tests' + color: c2e0c6 +- name: 'status: duplicate' + color: cccccc +- name: 'status: fix-provided' + color: c5def5 +- name: 'status: future' + color: c5def5 +- name: 'status: has plan' + color: c5def5 +- name: 'status: invalid' + color: cccccc +- name: 'status: merge for next minor' + color: c5def5 +- name: 'status: merge for next patch' + color: c5def5 +- name: 'status: wontfix' + color: cccccc +- name: 'type: bug' + color: e01b1b +- name: 'type: documentation' + color: 0052cc +- name: 'type: enhancement' + color: 09ab3c +- name: 'type: question' + color: cc317c +- name: 'type: refactor' + color: 0052cc +- name: 'type: rfc' + color: 0052cc +- name: 'type: roadmap' + color: 0052cc +- name: 'type: service' + color: "5319e7" +- name: 'type: support' + color: cc317c +- name: 'type: tests' + color: 0052cc diff --git a/plugins/postgres/.github/workflows/ci.yml b/plugins/postgres/.github/workflows/ci.yml new file mode 100644 index 000000000..2cccc934b --- /dev/null +++ b/plugins/postgres/.github/workflows/ci.yml @@ -0,0 +1,100 @@ +--- +name: CI + +# yamllint disable-line rule:truthy +on: + pull_request: + branches: + - "*" + push: + branches: + - master + +concurrency: + group: build-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + unit-tests-master: + name: unit-tests + runs-on: ubuntu-24.04 + env: + DOKKU_VERSION: master + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - run: make setup + + - run: sudo sysctl -w vm.max_map_count=262144 + + - run: | + git fetch -q origin master + changed=$(git --no-pager diff --name-only $GITHUB_SHA..origin/master) + if [ $changed = "Dockerfile" ]; then + echo "Please run 'make generate' to update the image version in the README.md" + else + make generate + if ! git diff --quiet README.md; then + echo "Please run 'make generate'" + git status --short + git --no-pager diff README.md + exit 1 + fi + fi + + - run: make test + + - uses: actions/upload-artifact@v5 + if: failure() + with: + name: tmp/test-results + path: test-results + + unit-tests-0_19_0: + name: unit-tests-0.19.0 + runs-on: ubuntu-24.04 + env: + DOKKU_TAG: v0.19.0 + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - run: make setup + + - run: sudo sysctl -w vm.max_map_count=262144 + + - run: | + git fetch -q origin master + changed=$(git --no-pager diff --name-only $GITHUB_SHA..origin/master) + if [ $changed = "Dockerfile" ]; then + echo "Please run 'make generate' to update the image version in the README.md" + else + make generate + if ! git diff --quiet README.md; then + echo "Please run 'make generate'" + git status --short + git --no-pager diff README.md + exit 1 + fi + fi + + - run: make test + + - uses: actions/upload-artifact@v5 + if: failure() + with: + name: tmp/test-results + path: test-results diff --git a/plugins/postgres/.github/workflows/tagged-release.yml b/plugins/postgres/.github/workflows/tagged-release.yml new file mode 100644 index 000000000..de1658121 --- /dev/null +++ b/plugins/postgres/.github/workflows/tagged-release.yml @@ -0,0 +1,20 @@ +--- +name: "tagged-release" + +# yamllint disable-line rule:truthy +on: + push: + tags: + - "*" + +jobs: + tagged-release: + name: tagged-release + runs-on: ubuntu-24.04 + + steps: + - name: Release + uses: softprops/action-gh-release@v2.4.1 + with: + generate_release_notes: true + make_latest: "true" diff --git a/plugins/postgres/.gitignore b/plugins/postgres/.gitignore new file mode 100644 index 000000000..dd7b12a23 --- /dev/null +++ b/plugins/postgres/.gitignore @@ -0,0 +1,3 @@ +/tmp +.vagrant +bootstrap.sh diff --git a/plugins/postgres/Dockerfile b/plugins/postgres/Dockerfile new file mode 100644 index 000000000..49fdaa0cf --- /dev/null +++ b/plugins/postgres/Dockerfile @@ -0,0 +1 @@ +FROM postgres:17.5 diff --git a/plugins/postgres/LICENSE.txt b/plugins/postgres/LICENSE.txt new file mode 100644 index 000000000..d1e7e803c --- /dev/null +++ b/plugins/postgres/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright (C) 2020 Jose Diaz-Gonzalez + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/postgres/Makefile b/plugins/postgres/Makefile new file mode 100644 index 000000000..d14111ab9 --- /dev/null +++ b/plugins/postgres/Makefile @@ -0,0 +1,98 @@ +HARDWARE = $(shell uname -m) +SYSTEM_NAME = $(shell uname -s | tr '[:upper:]' '[:lower:]') +ARCH = $(shell dpkg --print-architecture) +SHFMT_VERSION = 3.0.2 +XUNIT_TO_GITHUB_VERSION = 0.3.0 +XUNIT_READER_VERSION = 0.1.0 + + +bats: +ifeq ($(SYSTEM_NAME),darwin) +ifneq ($(shell bats --version >/dev/null 2>&1 ; echo $$?),0) + brew install bats-core +endif +else + git clone https://github.com/bats-core/bats-core.git /tmp/bats + cd /tmp/bats && sudo ./install.sh /usr/local + rm -rf /tmp/bats +endif + +shellcheck: +ifneq ($(shell shellcheck --version >/dev/null 2>&1 ; echo $$?),0) +ifeq ($(SYSTEM_NAME),darwin) + brew install shellcheck +else +ifeq ($(ARCH),arm64) + sudo add-apt-repository 'deb http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse' +else + sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu jammy-backports main restricted universe multiverse' +endif + sudo rm -rf /var/lib/apt/lists/* && sudo apt-get clean + sudo apt-get update -qq && sudo apt-get install -qq -y shellcheck +endif +endif + +shfmt: +ifneq ($(shell shfmt --version >/dev/null 2>&1 ; echo $$?),0) +ifeq ($(shfmt),Darwin) + brew install shfmt +else + wget -qO /tmp/shfmt https://github.com/mvdan/sh/releases/download/v$(SHFMT_VERSION)/shfmt_v$(SHFMT_VERSION)_linux_amd64 + chmod +x /tmp/shfmt + sudo mv /tmp/shfmt /usr/local/bin/shfmt +endif +endif + +readlink: +ifeq ($(shell uname),Darwin) +ifeq ($(shell greadlink > /dev/null 2>&1 ; echo $$?),127) + brew install coreutils +endif + ln -nfs `which greadlink` tests/bin/readlink +endif + +ci-dependencies: shellcheck bats readlink + +lint-setup: + @mkdir -p tmp/test-results/shellcheck tmp/shellcheck + @find . -not -path '*/\.*' -type f | xargs file | grep text | awk -F ':' '{ print $$1 }' | xargs head -n1 | egrep -B1 "bash" | grep "==>" | awk '{ print $$2 }' > tmp/shellcheck/test-files + @cat tests/shellcheck-exclude | sed -n -e '/^# SC/p' | cut -d' ' -f2 | paste -d, -s - > tmp/shellcheck/exclude + +lint: lint-setup + # these are disabled due to their expansive existence in the codebase. we should clean it up though + @cat tests/shellcheck-exclude | sed -n -e '/^# SC/p' + @echo linting... + @cat tmp/shellcheck/test-files | xargs shellcheck -e $(shell cat tmp/shellcheck/exclude) | tests/shellcheck-to-junit --output tmp/test-results/shellcheck/results.xml --files tmp/shellcheck/test-files --exclude $(shell cat tmp/shellcheck/exclude) + +unit-tests: + @echo running unit tests... + @mkdir -p tmp/test-results/bats + @cd tests && echo "executing tests: $(shell cd tests ; ls *.bats | xargs)" + cd tests && bats --report-formatter junit --timing -o ../tmp/test-results/bats *.bats + +tmp/xunit-reader: + mkdir -p tmp + curl -o tmp/xunit-reader.tgz -sL https://github.com/josegonzalez/go-xunit-reader/releases/download/v$(XUNIT_READER_VERSION)/xunit-reader_$(XUNIT_READER_VERSION)_$(SYSTEM_NAME)_$(HARDWARE).tgz + tar xf tmp/xunit-reader.tgz -C tmp + chmod +x tmp/xunit-reader + +setup: + bash tests/setup.sh + $(MAKE) ci-dependencies + +test: lint unit-tests + +report: tmp/xunit-reader + tmp/xunit-reader -p 'tmp/test-results/bats/*.xml' + tmp/xunit-reader -p 'tmp/test-results/shellcheck/*.xml' + +.PHONY: clean +clean: + rm -f README.md + +.PHONY: generate +generate: clean README.md + +.PHONY: README.md +README.md: + bin/generate diff --git a/plugins/postgres/README.md b/plugins/postgres/README.md new file mode 100644 index 000000000..1cf3f85a3 --- /dev/null +++ b/plugins/postgres/README.md @@ -0,0 +1,825 @@ +# dokku postgres [![Build Status](https://img.shields.io/github/actions/workflow/status/dokku/dokku-postgres/ci.yml?branch=master&style=flat-square "Build Status")](https://github.com/dokku/dokku-postgres/actions/workflows/ci.yml?query=branch%3Amaster) [![IRC Network](https://img.shields.io/badge/irc-libera-blue.svg?style=flat-square "IRC Libera")](https://webchat.libera.chat/?channels=dokku) + +Official postgres plugin for dokku. Currently defaults to installing [postgres 17.5](https://hub.docker.com/_/postgres/). + +## Requirements + +- dokku 0.19.x+ +- docker 1.8.x + +## Installation + +```shell +# on 0.19.x+ +sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git --name postgres +``` + +## Commands + +``` +postgres:app-links # list all postgres service links for a given app +postgres:backup [--use-iam] # create a backup of the postgres service to an existing s3 bucket +postgres:backup-auth # set up authentication for backups on the postgres service +postgres:backup-deauth # remove backup authentication for the postgres service +postgres:backup-schedule [--use-iam] # schedule a backup of the postgres service +postgres:backup-schedule-cat # cat the contents of the configured backup cronfile for the service +postgres:backup-set-encryption # set encryption for all future backups of postgres service +postgres:backup-set-public-key-encryption # set GPG Public Key encryption for all future backups of postgres service +postgres:backup-unschedule # unschedule the backup of the postgres service +postgres:backup-unset-encryption # unset encryption for future backups of the postgres service +postgres:backup-unset-public-key-encryption # unset GPG Public Key encryption for future backups of the postgres service +postgres:clone [--clone-flags...] # create container then copy data from into +postgres:connect # connect to the service via the postgres connection tool +postgres:create [--create-flags...] # create a postgres service +postgres:destroy [-f|--force] # delete the postgres service/data/container if there are no links left +postgres:enter # enter or run a command in a running postgres service container +postgres:exists # check if the postgres service exists +postgres:export # export a dump of the postgres service database +postgres:expose # expose a postgres service on custom host:port if provided (random port on the 0.0.0.0 interface if otherwise unspecified) +postgres:import # import a dump into the postgres service database +postgres:info [--single-info-flag] # print the service information +postgres:link [--link-flags...] # link the postgres service to the app +postgres:linked # check if the postgres service is linked to an app +postgres:links # list all apps linked to the postgres service +postgres:list # list all postgres services +postgres:logs [-t|--tail] # print the most recent log(s) for this service +postgres:pause # pause a running postgres service +postgres:promote # promote service as DATABASE_URL in +postgres:restart # graceful shutdown and restart of the postgres service container +postgres:set # set or clear a property for a service +postgres:start # start a previously stopped postgres service +postgres:stop # stop a running postgres service +postgres:unexpose # unexpose a previously exposed postgres service +postgres:unlink # unlink the postgres service from the app +postgres:upgrade [--upgrade-flags...] # upgrade service to the specified versions +``` + +## Usage + +Help for any commands can be displayed by specifying the command as an argument to postgres:help. Plugin help output in conjunction with any files in the `docs/` folder is used to generate the plugin documentation. Please consult the `postgres:help` command for any undocumented commands. + +### Basic Usage + +### create a postgres service + +```shell +# usage +dokku postgres:create [--create-flags...] +``` + +flags: + +- `-c|--config-options "--args --go=here"`: extra arguments to pass to the container create command (default: `None`) +- `-C|--custom-env "USER=alpha;HOST=beta"`: semi-colon delimited environment variables to start the service with +- `-i|--image IMAGE`: the image name to start the service with +- `-I|--image-version IMAGE_VERSION`: the image version to start the service with +- `-m|--memory MEMORY`: container memory limit in megabytes (default: unlimited) +- `-N|--initial-network INITIAL_NETWORK`: the initial network to attach the service to +- `-p|--password PASSWORD`: override the user-level service password +- `-P|--post-create-network NETWORKS`: a comma-separated list of networks to attach the service container to after service creation +- `-r|--root-password PASSWORD`: override the root-level service password +- `-S|--post-start-network NETWORKS`: a comma-separated list of networks to attach the service container to after service start +- `-s|--shm-size SHM_SIZE`: override shared memory size for postgres docker container + +Create a postgres service named lollipop: + +```shell +dokku postgres:create lollipop +``` + +You can also specify the image and image version to use for the service. It *must* be compatible with the postgres image. + +```shell +export POSTGRES_IMAGE="postgres" +export POSTGRES_IMAGE_VERSION="${PLUGIN_IMAGE_VERSION}" +dokku postgres:create lollipop +``` + +You can also specify custom environment variables to start the postgres service in semicolon-separated form. + +```shell +export POSTGRES_CUSTOM_ENV="USER=alpha;HOST=beta" +dokku postgres:create lollipop +``` + +Official Postgres "$DOCKER_BIN" image ls does not include postgis extension (amongst others). The following example creates a new postgres service using `postgis/postgis:13-3.1` image, which includes the `postgis` extension. + +```shell +# use the appropriate image-version for your use-case +dokku postgres:create postgis-database --image "postgis/postgis" --image-version "13-3.1" +``` + +To use pgvector instead, run the following: + +```shell +# use the appropriate image-version for your use-case +dokku postgres:create pgvector-database --image "pgvector/pgvector" --image-version "pg17" +``` + +### print the service information + +```shell +# usage +dokku postgres:info [--single-info-flag] +``` + +flags: + +- `--config-dir`: show the service configuration directory +- `--data-dir`: show the service data directory +- `--dsn`: show the service DSN +- `--exposed-ports`: show service exposed ports +- `--id`: show the service container id +- `--internal-ip`: show the service internal ip +- `--initial-network`: show the initial network being connected to +- `--links`: show the service app links +- `--post-create-network`: show the networks to attach to after service container creation +- `--post-start-network`: show the networks to attach to after service container start +- `--service-root`: show the service root directory +- `--status`: show the service running status +- `--version`: show the service image version + +Get connection information as follows: + +```shell +dokku postgres:info lollipop +``` + +You can also retrieve a specific piece of service info via flags: + +```shell +dokku postgres:info lollipop --config-dir +dokku postgres:info lollipop --data-dir +dokku postgres:info lollipop --dsn +dokku postgres:info lollipop --exposed-ports +dokku postgres:info lollipop --id +dokku postgres:info lollipop --internal-ip +dokku postgres:info lollipop --initial-network +dokku postgres:info lollipop --links +dokku postgres:info lollipop --post-create-network +dokku postgres:info lollipop --post-start-network +dokku postgres:info lollipop --service-root +dokku postgres:info lollipop --status +dokku postgres:info lollipop --version +``` + +### list all postgres services + +```shell +# usage +dokku postgres:list +``` + +List all services: + +```shell +dokku postgres:list +``` + +### print the most recent log(s) for this service + +```shell +# usage +dokku postgres:logs [-t|--tail] +``` + +flags: + +- `-t|--tail []`: do not stop when end of the logs are reached and wait for additional output + +You can tail logs for a particular service: + +```shell +dokku postgres:logs lollipop +``` + +By default, logs will not be tailed, but you can do this with the --tail flag: + +```shell +dokku postgres:logs lollipop --tail +``` + +The default tail setting is to show all logs, but an initial count can also be specified: + +```shell +dokku postgres:logs lollipop --tail 5 +``` + +### link the postgres service to the app + +```shell +# usage +dokku postgres:link [--link-flags...] +``` + +flags: + +- `-a|--alias "BLUE_DATABASE"`: an alternative alias to use for linking to an app via environment variable +- `-q|--querystring "pool=5"`: ampersand delimited querystring arguments to append to the service link +- `-n|--no-restart "false"`: whether or not to restart the app on link (default: true) + +A postgres service can be linked to a container. This will use native docker links via the docker-options plugin. Here we link it to our `playground` app. + +> NOTE: this will restart your app + +```shell +dokku postgres:link lollipop playground +``` + +The following environment variables will be set automatically by docker (not on the app itself, so they won’t be listed when calling dokku config): + +``` +DOKKU_POSTGRES_LOLLIPOP_NAME=/lollipop/DATABASE +DOKKU_POSTGRES_LOLLIPOP_PORT=tcp://172.17.0.1:5432 +DOKKU_POSTGRES_LOLLIPOP_PORT_5432_TCP=tcp://172.17.0.1:5432 +DOKKU_POSTGRES_LOLLIPOP_PORT_5432_TCP_PROTO=tcp +DOKKU_POSTGRES_LOLLIPOP_PORT_5432_TCP_PORT=5432 +DOKKU_POSTGRES_LOLLIPOP_PORT_5432_TCP_ADDR=172.17.0.1 +``` + +The following will be set on the linked application by default: + +``` +DATABASE_URL=postgres://lollipop:SOME_PASSWORD@dokku-postgres-lollipop:5432/lollipop +``` + +The host exposed here only works internally in docker containers. If you want your container to be reachable from outside, you should use the `expose` subcommand. Another service can be linked to your app: + +```shell +dokku postgres:link other_service playground +``` + +It is possible to change the protocol for `DATABASE_URL` by setting the environment variable `POSTGRES_DATABASE_SCHEME` on the app. Doing so will after linking will cause the plugin to think the service is not linked, and we advise you to unlink before proceeding. + +```shell +dokku config:set playground POSTGRES_DATABASE_SCHEME=postgres2 +dokku postgres:link lollipop playground +``` + +This will cause `DATABASE_URL` to be set as: + +``` +postgres2://lollipop:SOME_PASSWORD@dokku-postgres-lollipop:5432/lollipop +``` + +### unlink the postgres service from the app + +```shell +# usage +dokku postgres:unlink +``` + +flags: + +- `-n|--no-restart "false"`: whether or not to restart the app on unlink (default: true) + +You can unlink a postgres service: + +> NOTE: this will restart your app and unset related environment variables + +```shell +dokku postgres:unlink lollipop playground +``` + +### set or clear a property for a service + +```shell +# usage +dokku postgres:set +``` + +Set the network to attach after the service container is started: + +```shell +dokku postgres:set lollipop post-create-network custom-network +``` + +Set multiple networks: + +```shell +dokku postgres:set lollipop post-create-network custom-network,other-network +``` + +Unset the post-create-network value: + +```shell +dokku postgres:set lollipop post-create-network +``` + +### Service Lifecycle + +The lifecycle of each service can be managed through the following commands: + +### connect to the service via the postgres connection tool + +```shell +# usage +dokku postgres:connect +``` + +Connect to the service via the postgres connection tool: + +> NOTE: disconnecting from ssh while running this command may leave zombie processes due to moby/moby#9098 + +```shell +dokku postgres:connect lollipop +``` + +### enter or run a command in a running postgres service container + +```shell +# usage +dokku postgres:enter +``` + +A bash prompt can be opened against a running service. Filesystem changes will not be saved to disk. + +> NOTE: disconnecting from ssh while running this command may leave zombie processes due to moby/moby#9098 + +```shell +dokku postgres:enter lollipop +``` + +You may also run a command directly against the service. Filesystem changes will not be saved to disk. + +```shell +dokku postgres:enter lollipop touch /tmp/test +``` + +### expose a postgres service on custom host:port if provided (random port on the 0.0.0.0 interface if otherwise unspecified) + +```shell +# usage +dokku postgres:expose +``` + +Expose the service on the service's normal ports, allowing access to it from the public interface (`0.0.0.0`): + +```shell +dokku postgres:expose lollipop 5432 +``` + +Expose the service on the service's normal ports, with the first on a specified ip address (127.0.0.1): + +```shell +dokku postgres:expose lollipop 127.0.0.1:5432 +``` + +### unexpose a previously exposed postgres service + +```shell +# usage +dokku postgres:unexpose +``` + +Unexpose the service, removing access to it from the public interface (`0.0.0.0`): + +```shell +dokku postgres:unexpose lollipop +``` + +### promote service as DATABASE_URL in + +```shell +# usage +dokku postgres:promote +``` + +If you have a postgres service linked to an app and try to link another postgres service another link environment variable will be generated automatically: + +``` +DOKKU_DATABASE_BLUE_URL=postgres://other_service:ANOTHER_PASSWORD@dokku-postgres-other-service:5432/other_service +``` + +You can promote the new service to be the primary one: + +> NOTE: this will restart your app + +```shell +dokku postgres:promote other_service playground +``` + +This will replace `DATABASE_URL` with the url from other_service and generate another environment variable to hold the previous value if necessary. You could end up with the following for example: + +``` +DATABASE_URL=postgres://other_service:ANOTHER_PASSWORD@dokku-postgres-other-service:5432/other_service +DOKKU_DATABASE_BLUE_URL=postgres://other_service:ANOTHER_PASSWORD@dokku-postgres-other-service:5432/other_service +DOKKU_DATABASE_SILVER_URL=postgres://lollipop:SOME_PASSWORD@dokku-postgres-lollipop:5432/lollipop +``` + +### start a previously stopped postgres service + +```shell +# usage +dokku postgres:start +``` + +Start the service: + +```shell +dokku postgres:start lollipop +``` + +### stop a running postgres service + +```shell +# usage +dokku postgres:stop +``` + +Stop the service and removes the running container: + +```shell +dokku postgres:stop lollipop +``` + +### pause a running postgres service + +```shell +# usage +dokku postgres:pause +``` + +Pause the running container for the service: + +```shell +dokku postgres:pause lollipop +``` + +### graceful shutdown and restart of the postgres service container + +```shell +# usage +dokku postgres:restart +``` + +Restart the service: + +```shell +dokku postgres:restart lollipop +``` + +### upgrade service to the specified versions + +```shell +# usage +dokku postgres:upgrade [--upgrade-flags...] +``` + +flags: + +- `-c|--config-options "--args --go=here"`: extra arguments to pass to the container create command (default: `None`) +- `-C|--custom-env "USER=alpha;HOST=beta"`: semi-colon delimited environment variables to start the service with +- `-i|--image IMAGE`: the image name to start the service with +- `-I|--image-version IMAGE_VERSION`: the image version to start the service with +- `-N|--initial-network INITIAL_NETWORK`: the initial network to attach the service to +- `-P|--post-create-network NETWORKS`: a comma-separated list of networks to attach the service container to after service creation +- `-R|--restart-apps "true"`: whether or not to force an app restart (default: false) +- `-S|--post-start-network NETWORKS`: a comma-separated list of networks to attach the service container to after service start +- `-s|--shm-size SHM_SIZE`: override shared memory size for postgres docker container + +You can upgrade an existing service to a new image or image-version: + +```shell +dokku postgres:upgrade lollipop +``` + +Postgres does not handle upgrading data for major versions automatically (eg. 11 => 12). Upgrades should be done manually. Users are encouraged to upgrade to the latest minor release for their postgres version before performing a major upgrade. + +While there are many ways to upgrade a postgres database, for safety purposes, it is recommended that an upgrade is performed by exporting the data from an existing database and importing it into a new database. This also allows testing to ensure that applications interact with the database correctly after the upgrade, and can be used in a staging environment. + +The following is an example of how to upgrade a postgres database named `lollipop-11` from 11.13 to 12.8. + +```shell +# stop any linked apps +dokku ps:stop linked-app + +# export the database contents +dokku postgres:export lollipop-11 > /tmp/lollipop-11.export + +# create a new database at the desired version +dokku postgres:create lollipop-12 --image-version 12.8 + +# import the export file +dokku postgres:import lollipop-12 < /tmp/lollipop-11.export + +# run any sql tests against the new database to verify the import went smoothly + +# unlink the old database from your apps +dokku postgres:unlink lollipop-11 linked-app + +# link the new database to your apps +dokku postgres:link lollipop-12 linked-app + +# start the linked apps again +dokku ps:start linked-app +``` + +### Service Automation + +Service scripting can be executed using the following commands: + +### list all postgres service links for a given app + +```shell +# usage +dokku postgres:app-links +``` + +List all postgres services that are linked to the `playground` app. + +```shell +dokku postgres:app-links playground +``` + +### create container then copy data from into + +```shell +# usage +dokku postgres:clone [--clone-flags...] +``` + +flags: + +- `-c|--config-options "--args --go=here"`: extra arguments to pass to the container create command (default: `None`) +- `-C|--custom-env "USER=alpha;HOST=beta"`: semi-colon delimited environment variables to start the service with +- `-i|--image IMAGE`: the image name to start the service with +- `-I|--image-version IMAGE_VERSION`: the image version to start the service with +- `-m|--memory MEMORY`: container memory limit in megabytes (default: unlimited) +- `-N|--initial-network INITIAL_NETWORK`: the initial network to attach the service to +- `-p|--password PASSWORD`: override the user-level service password +- `-P|--post-create-network NETWORKS`: a comma-separated list of networks to attach the service container to after service creation +- `-r|--root-password PASSWORD`: override the root-level service password +- `-S|--post-start-network NETWORKS`: a comma-separated list of networks to attach the service container to after service start +- `-s|--shm-size SHM_SIZE`: override shared memory size for postgres docker container + +You can clone an existing service to a new one: + +```shell +dokku postgres:clone lollipop lollipop-2 +``` + +### check if the postgres service exists + +```shell +# usage +dokku postgres:exists +``` + +Here we check if the lollipop postgres service exists. + +```shell +dokku postgres:exists lollipop +``` + +### check if the postgres service is linked to an app + +```shell +# usage +dokku postgres:linked +``` + +Here we check if the lollipop postgres service is linked to the `playground` app. + +```shell +dokku postgres:linked lollipop playground +``` + +### list all apps linked to the postgres service + +```shell +# usage +dokku postgres:links +``` + +List all apps linked to the `lollipop` postgres service. + +```shell +dokku postgres:links lollipop +``` + +### Data Management + +The underlying service data can be imported and exported with the following commands: + +### import a dump into the postgres service database + +```shell +# usage +dokku postgres:import +``` + +Import a datastore dump: + +```shell +dokku postgres:import lollipop < data.dump +``` + +### export a dump of the postgres service database + +```shell +# usage +dokku postgres:export +``` + +By default, datastore output is exported to stdout: + +```shell +dokku postgres:export lollipop +``` + +You can redirect this output to a file: + +```shell +dokku postgres:export lollipop > data.dump +``` + +Note that the export will result in a file containing the binary postgres export data. It can be converted to plain text using `pg_restore` as follows + +```shell +pg_restore data.dump -f plain.sql +``` + +### Backups + +Datastore backups are supported via AWS S3 and S3 compatible services like [minio](https://github.com/minio/minio). + +You may skip the `backup-auth` step if your dokku install is running within EC2 and has access to the bucket via an IAM profile. In that case, use the `--use-iam` option with the `backup` command. + +Backups can be performed using the backup commands: + +### set up authentication for backups on the postgres service + +```shell +# usage +dokku postgres:backup-auth +``` + +Setup s3 backup authentication: + +```shell +dokku postgres:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY +``` + +Setup s3 backup authentication with different region: + +```shell +dokku postgres:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION +``` + +Setup s3 backup authentication with different signature version and endpoint: + +```shell +dokku postgres:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION AWS_SIGNATURE_VERSION ENDPOINT_URL +``` + +More specific example for minio auth: + +```shell +dokku postgres:backup-auth lollipop MINIO_ACCESS_KEY_ID MINIO_SECRET_ACCESS_KEY us-east-1 s3v4 https://YOURMINIOSERVICE +``` + +### remove backup authentication for the postgres service + +```shell +# usage +dokku postgres:backup-deauth +``` + +Remove s3 authentication: + +```shell +dokku postgres:backup-deauth lollipop +``` + +### create a backup of the postgres service to an existing s3 bucket + +```shell +# usage +dokku postgres:backup [--use-iam] +``` + +flags: + +- `-u|--use-iam`: use the IAM profile associated with the current server + +Backup the `lollipop` service to the `my-s3-bucket` bucket on `AWS`:` + +```shell +dokku postgres:backup lollipop my-s3-bucket --use-iam +``` + +Restore a backup file (assuming it was extracted via `tar -xf backup.tgz`): + +```shell +dokku postgres:import lollipop < backup-folder/export +``` + +### set encryption for all future backups of postgres service + +```shell +# usage +dokku postgres:backup-set-encryption +``` + +Set the GPG-compatible passphrase for encrypting backups for backups: + +```shell +dokku postgres:backup-set-encryption lollipop +``` + +### set GPG Public Key encryption for all future backups of postgres service + +```shell +# usage +dokku postgres:backup-set-public-key-encryption +``` + +Set the `GPG` Public Key for encrypting backups: + +```shell +dokku postgres:backup-set-public-key-encryption lollipop +``` + +### unset encryption for future backups of the postgres service + +```shell +# usage +dokku postgres:backup-unset-encryption +``` + +Unset the `GPG` encryption passphrase for backups: + +```shell +dokku postgres:backup-unset-encryption lollipop +``` + +### unset GPG Public Key encryption for future backups of the postgres service + +```shell +# usage +dokku postgres:backup-unset-public-key-encryption +``` + +Unset the `GPG` Public Key encryption for backups: + +```shell +dokku postgres:backup-unset-public-key-encryption lollipop +``` + +### schedule a backup of the postgres service + +```shell +# usage +dokku postgres:backup-schedule [--use-iam] +``` + +flags: + +- `-u|--use-iam`: use the IAM profile associated with the current server + +Schedule a backup: + +> 'schedule' is a crontab expression, eg. "0 3 * * *" for each day at 3am + +```shell +dokku postgres:backup-schedule lollipop "0 3 * * *" my-s3-bucket +``` + +Schedule a backup and authenticate via iam: + +```shell +dokku postgres:backup-schedule lollipop "0 3 * * *" my-s3-bucket --use-iam +``` + +### cat the contents of the configured backup cronfile for the service + +```shell +# usage +dokku postgres:backup-schedule-cat +``` + +Cat the contents of the configured backup cronfile for the service: + +```shell +dokku postgres:backup-schedule-cat lollipop +``` + +### unschedule the backup of the postgres service + +```shell +# usage +dokku postgres:backup-unschedule +``` + +Remove the scheduled backup from cron: + +```shell +dokku postgres:backup-unschedule lollipop +``` + +### Disabling `docker image pull` calls + +If you wish to disable the `docker image pull` calls that the plugin triggers, you may set the `POSTGRES_DISABLE_PULL` environment variable to `true`. Once disabled, you will need to pull the service image you wish to deploy as shown in the `stderr` output. + +Please ensure the proper images are in place when `docker image pull` is disabled. diff --git a/plugins/postgres/Vagrantfile b/plugins/postgres/Vagrantfile new file mode 100644 index 000000000..4e6545b64 --- /dev/null +++ b/plugins/postgres/Vagrantfile @@ -0,0 +1,27 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +BOX_NAME = ENV["BOX_NAME"] || "bento/ubuntu-24.04" +BOX_MEMORY = ENV["BOX_MEMORY"] || "2048" +DOKKU_VERSION = "master" + +Vagrant.configure(2) do |config| + config.vm.box = BOX_NAME + config.ssh.forward_agent = true + + config.vm.provider :virtualbox do |vb| + vb.customize ["modifyvm", :id, "--memory", BOX_MEMORY] + end + + config.vm.provider :vmware_fusion do |v, override| + v.vmx["memsize"] = BOX_MEMORY + end + + config.vm.define "default", primary: true do |vm| + vm.vm.synced_folder File.dirname(__FILE__), "/vagrant" + + vm.vm.provision :shell, :inline => "apt -q update && apt -y -qq install git software-properties-common" + vm.vm.provision :shell, :inline => "cd /vagrant && DOKKU_VERSION=#{DOKKU_VERSION} make setup" + vm.vm.provision :shell, :inline => "cd /vagrant && DOKKU_TRACE=1 DOKKU_VERSION=#{DOKKU_VERSION} make test" + end +end diff --git a/plugins/postgres/bin/generate b/plugins/postgres/bin/generate new file mode 100755 index 000000000..1a8e5c538 --- /dev/null +++ b/plugins/postgres/bin/generate @@ -0,0 +1,677 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from __future__ import print_function + +import os +import re + + +def compile( + service, + version, + variable, + alias, + image, + scheme, + ports, + sponsors, + options, + unimplemented, + dokku_version, +): + prefix = "\n\n".join( + [ + header(service), + description(service, image, version), + ] + ) + + if len(sponsors) > 0: + prefix += "\n\n" + prefix += sponsors_section(service, sponsors) + + return ( + "\n\n".join( + [ + prefix, + requirements_section(dokku_version), + installation_section(service, dokku_version), + commands_section( + service, variable, alias, image, scheme, ports, unimplemented + ), + usage_section( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + ), + ] + ) + .replace("\n\n\n\n\n", "\n") + .replace("\n\n\n\n", "\n") + .replace("\n\n\n", "\n\n") + ) + + +def header(service): + return " ".join( + [ + f"# dokku {service}", + f'[![Build Status](https://img.shields.io/github/actions/workflow/status/dokku/dokku-{service}/ci.yml?branch=master&style=flat-square "Build Status")](https://github.com/dokku/dokku-{service}/actions/workflows/ci.yml?query=branch%3Amaster)', + f'[![IRC Network](https://img.shields.io/badge/irc-libera-blue.svg?style=flat-square "IRC Libera")](https://webchat.libera.chat/?channels=dokku)', + ] + ) + + +def description(service, full_image, version): + base = "_" + image = full_image + if "/" in full_image: + base = "r/" + full_image.split("/")[0] + image = full_image.split("/")[1] + + return f"Official {service} plugin for dokku. Currently defaults to installing [{full_image} {version}](https://hub.docker.com/{base}/{image}/)." + + +def sponsors_section(service, sponsors): + if len(sponsors) == 0: + return "" + + sponsor_data = [ + "## Sponsors", + "", + f"The {service} plugin was generously sponsored by the following:", + "", + ] + sponsor_data.extend([f"- [{s}](https://github.com/{s})" for s in sponsors]) + + return "\n".join(sponsor_data) + + +def requirements_section(dokku_version): + return "\n".join( + [ + "## Requirements", + "", + f"- dokku {dokku_version}", + "- docker 1.8.x", + ] + ) + + +def installation_section(service, dokku_version): + return "\n".join( + [ + "## Installation", + "", + "```shell", + f"# on {dokku_version}", + f"sudo dokku plugin:install https://github.com/dokku/dokku-{service}.git --name {service}", + "```", + ] + ) + + +def commands_section(service, variable, alias, image, scheme, ports, unimplemented): + content = [ + "## Commands", + "", + "```", + ] + + subcommands = os.listdir("subcommands") + subcommands.sort() + + command_list = [] + descriptions = [] + for filename in subcommands: + if filename in unimplemented: + continue + data = command_data(filename, service, variable, alias, image, scheme, ports) + description = data["description"] + arguments = data["arguments_string"] + + command_list.append(f"{service}:{filename} {arguments}") + descriptions.append(description) + + maxlen = max(map(len, command_list)) + if maxlen > 50: + maxlen = 50 + for command, description in zip(command_list, descriptions): + space_count = maxlen - len(command) + content.append("{0}{1} # {2}".format(command, " " * space_count, description)) + + content.append("```") + return "\n".join(content) + + +def usage_section( + service, variable, alias, image, scheme, ports, options, unimplemented +): + return "\n\n".join( + [ + "## Usage", + f"Help for any commands can be displayed by specifying the command as an argument to {service}:help. Plugin help output in conjunction with any files in the `docs/` folder is used to generate the plugin documentation. Please consult the `{service}:help` command for any undocumented commands.", + usage_intro( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + usage_lifecycle( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + usage_automation( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + usage_data_management( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + usage_backup( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + usage_docker_pull( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + ] + ) + + +def usage_intro(service, variable, alias, image, scheme, ports, options, unimplemented): + commands = ["create", "info", "list", "logs", "link", "unlink", "set"] + content = ["### Basic Usage"] + + return fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, + ) + + +def usage_lifecycle( + service, variable, alias, image, scheme, ports, options, unimplemented +): + commands = [ + "connect", + "enter", + "expose", + "unexpose", + "promote", + "start", + "stop", + "pause", + "restart", + "upgrade", + ] + content = [ + "### Service Lifecycle", + "", + "The lifecycle of each service can be managed through the following commands:", + "", + ] + + return fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, + ) + + +def usage_automation( + service, variable, alias, image, scheme, ports, options, unimplemented +): + commands = ["app-links", "clone", "exists", "linked", "links"] + content = [ + "### Service Automation", + "", + "Service scripting can be executed using the following commands:", + "", + ] + + return fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, + ) + + +def usage_data_management( + service, variable, alias, image, scheme, ports, options, unimplemented +): + commands = ["import", "export"] + content = [ + "### Data Management", + "", + "The underlying service data can be imported and exported with the following commands:", + "", + ] + + return fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, + ) + + +def usage_backup( + service, variable, alias, image, scheme, ports, options, unimplemented +): + commands = [ + "backup-auth", + "backup-deauth", + "backup", + "backup-set-encryption", + "backup-set-public-key-encryption", + "backup-unset-encryption", + "backup-unset-public-key-encryption", + "backup-schedule", + "backup-schedule-cat", + "backup-unschedule", + ] + content = [ + "### Backups", + "", + "Datastore backups are supported via AWS S3 and S3 compatible services like [minio](https://github.com/minio/minio).", + "", + "You may skip the `backup-auth` step if your dokku install is running within EC2 and has access to the bucket via an IAM profile. In that case, use the `--use-iam` option with the `backup` command.", + "", + "Backups can be performed using the backup commands:", + "", + ] + + return fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, + ) + + +def usage_docker_pull( + service, variable, alias, image, scheme, ports, options, unimplemented +): + service_prefix = service.upper() + return "\n".join( + [ + "### Disabling `docker image pull` calls", + "", + f"If you wish to disable the `docker image pull` calls that the plugin triggers, you may set the `{service_prefix}_DISABLE_PULL` environment variable to `true`. Once disabled, you will need to pull the service image you wish to deploy as shown in the `stderr` output.", + "", + "Please ensure the proper images are in place when `docker image pull` is disabled.", + ] + ) + + +def fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, +): + i = 0 + for command in commands: + output = command_help( + command, + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + ) + if output == "": + continue + content.append(output) + i += 1 + + if i == 0: + return "" + + return "\n".join(content) + + +def parse_args(line): + line = line.strip() + arguments = [] + for arg in re.findall("([A-Z_]+)", line): + arg = arg.replace("_", "-").lower() + if arg.endswith("optional-flag"): + arg = arg.replace("-optional-flag", "") + arguments.append(f"[--{arg}]") + elif arg.endswith("-flag"): + if arg == "info-flag": + arguments.append(f"[--single-info-flag]") + else: + arg = arg.replace("-flag", "") + first_letter = arg[0] + arguments.append(f"[-{first_letter}|--{arg}]") + elif arg.endswith("-flags-list"): + arg = arg.replace("-list", "") + arguments.append(f"[--{arg}...]") + elif arg.endswith("list"): + arg = arg.replace("-list", "") + arguments.append(f"<{arg}...>") + else: + arguments.append(f"<{arg}>") + return " ".join(arguments) + + +def command_help( + command, service, variable, alias, image, scheme, ports, options, unimplemented +): + if command in unimplemented: + return "" + + data = command_data(command, service, variable, alias, image, scheme, ports) + content = [ + f"### {data['description']}", + "", + "```shell", + "# usage", + f"dokku {service}:{command} {data['arguments_string']}".strip(), + "```", + ] + + # if len(data["arguments"]) > 0: + # content.append("") + # content.append("arguments:") + # content.append("") + # for argument in data["arguments"]: + # content.append(f"- {argument}") + + if len(data["flags"]) > 0: + content.append("") + content.append("flags:") + content.append("") + for flag in data["flags"]: + if "--config-options" in flag and options != "": + flag = f"{flag} (default: `{options}`)" + content.append(f"- {flag}") + + if len(data["examples"]) > 0: + content.append("") + content.append(data["examples"]) + + doc_file = os.path.join("docs", f"{command}.md") + if os.path.isfile(doc_file): + content.append("") + with open(doc_file) as f: + content.append(f.read()) + + return "\n" + "\n".join(content) + + +def command_data(command, service, variable, alias, image, scheme, ports): + description = None + arguments = [] + arguments_string = "" + example_lines = [] + flags = [] + with open(os.path.join("subcommands", command)) as f: + for line in f.readlines(): + line = line.strip() + line = line.replace("$PLUGIN_SERVICE", service) + line = line.replace("$PLUGIN_COMMAND_PREFIX", service) + line = line.replace("${PLUGIN_COMMAND_PREFIX}", service) + line = line.replace("${PLUGIN_VARIABLE}", variable) + line = line.replace("${PLUGIN_DEFAULT_ALIAS}", alias) + line = line.replace("${PLUGIN_IMAGE}", image) + line = line.replace("${PLUGIN_SCHEME}", scheme) + line = line.replace("${PLUGIN_DATASTORE_PORTS[0]}", ports[0]) + line = line.replace("${PLUGIN_DATASTORE_PORTS[@]}", " ".join(ports)) + + if "declare desc" in line: + description = re.search('"(.+)"', line).group(1) + elif "$1" in line: + arguments_string = parse_args(line) + elif line.startswith("#A "): + argument = line.replace("#A ", "") + parts = [a.strip() for a in argument.split(",", 1)] + arguments.append(f"`{parts[0]}`: {parts[1]}") + elif line.startswith("#F "): + flag = line.replace("#F ", "") + parts = [a.strip() for a in flag.split(",", 1)] + flags.append(f"`{parts[0]}`: {parts[1]}") + elif line.startswith("#E "): + example_lines.append(line.replace("#E ", "")) + + examples = [] + sentence_lines = [] + command_lines = [] + codeblock_lines = [] + blockquote_lines = [] + for line in example_lines: + if line.startswith("export") or line.startswith("dokku"): + if len(blockquote_lines) > 0: + examples.append("\n" + process_blockquote(blockquote_lines)) + blockquote_lines = [] + if len(codeblock_lines) > 0: + examples.append("\n" + process_codeblock(codeblock_lines)) + codeblock_lines = [] + if len(sentence_lines) > 0: + examples.append("\n" + process_sentence(sentence_lines)) + sentence_lines = [] + + command_lines.append(line) + elif line.startswith(" "): + if len(blockquote_lines) > 0: + examples.append("\n" + process_blockquote(blockquote_lines)) + blockquote_lines = [] + if len(command_lines) > 0: + examples.append("\n" + process_command(command_lines)) + command_lines = [] + if len(sentence_lines) > 0: + examples.append("\n" + process_sentence(sentence_lines)) + sentence_lines = [] + + codeblock_lines.append(line.strip()) + elif line.startswith(">"): + if len(codeblock_lines) > 0: + examples.append("\n" + process_codeblock(codeblock_lines)) + codeblock_lines = [] + if len(command_lines) > 0: + examples.append("\n" + process_command(command_lines)) + command_lines = [] + if len(sentence_lines) > 0: + examples.append("\n" + process_sentence(sentence_lines)) + sentence_lines = [] + + blockquote_lines.append(line) + else: + if len(blockquote_lines) > 0: + examples.append("\n" + process_blockquote(blockquote_lines)) + blockquote_lines = [] + if len(codeblock_lines) > 0: + examples.append("\n" + process_codeblock(codeblock_lines)) + codeblock_lines = [] + if len(command_lines) > 0: + examples.append("\n" + process_command(command_lines)) + command_lines = [] + + sentence_lines.append(line) + + if len(blockquote_lines) > 0: + examples.append("\n" + process_blockquote(blockquote_lines)) + blockquote_lines = [] + if len(codeblock_lines) > 0: + examples.append("\n" + process_codeblock(codeblock_lines)) + codeblock_lines = [] + if len(command_lines) > 0: + examples.append("\n" + process_command(command_lines)) + command_lines = [] + if len(sentence_lines) > 0: + examples.append("\n" + process_sentence(sentence_lines)) + sentence_lines = [] + + return { + "description": description, + "arguments_string": arguments_string, + "arguments": arguments, + "flags": flags, + "examples": "\n".join(examples).strip(), + } + + +def process_sentence(sentence_lines): + sentence_lines = " ".join(sentence_lines) + sentences = ". ".join( + upperfirst(i.strip()) for i in sentence_lines.split(". ") + ).strip() + if not sentences.endswith(".") and not sentences.endswith(":"): + sentences += ":" + + text = [] + for sentence in sentences.split(". "): + parts = [] + for word in sentence.strip().split(" "): + if word.isupper() and len(word) > 1: + for ending in [":", "."]: + if word.endswith(ending): + word = "`{0}`{1}".format(word[:-1], ending) + else: + word = "`{0}`".format(word) + parts.append(word) + text.append(" ".join(parts)) + + text = ". ".join(text) + + # some cleanup + text = text.replace("(0.0.0.0)", "(`0.0.0.0`)") + text = text.replace("'", "`") + text = text.replace("`s", "'s") + text = text.replace("``", "`") + text = text.strip(" ") + + return text + + +def upperfirst(x): + return x[:1].upper() + x[1:] + + +def process_blockquote(blockquote_lines): + return "\n".join(blockquote_lines) + + +def process_command(command_lines): + command_lines = "\n".join(command_lines) + return f"```shell\n{command_lines}\n```" + + +def process_codeblock(codeblock_lines): + codeblock_lines = "\n".join(codeblock_lines) + return f"```\n{codeblock_lines}\n```" + + +def main(): + service = None + version = None + variable = None + image = None + alias = None + options = None + unimplemented = [] + + with open("Dockerfile") as f: + for line in f.readlines(): + if "FROM " in line: + image, version = line.split(" ")[1].split(":") + image = image.strip() + version = version.strip() + + with open("config") as f: + for line in f.readlines(): + if "PLUGIN_COMMAND_PREFIX=" in line: + service = re.search('"(.+)"', line).group(1) + if "PLUGIN_DEFAULT_ALIAS=" in line: + alias = re.search('"(.+)"', line).group(1) + if "PLUGIN_VARIABLE=" in line: + variable = re.search('"(.+)"', line).group(1) + if "PLUGIN_SCHEME=" in line: + scheme = re.search('"(.+)"', line).group(1) + if "PLUGIN_DATASTORE_PORTS=" in line: + ports = re.search("\((.+)\)", line).group(1).split(" ") + if "PLUGIN_UNIMPLEMENTED_SUBCOMMANDS=" in line: + match = re.search("\((.+)\)", line) + if match is not None: + unimplemented = [s.strip('"') for s in match.group(1).split(" ")] + + with open("config") as f: + for line in f.readlines(): + if f"{variable}_CONFIG_OPTIONS" in line: + match = re.search('"(.+)"', line) + if match is not None: + options = match.group(1) + + sponsors = [] + with open("plugin.toml") as f: + for line in f.readlines(): + if line.startswith("sponsors"): + sponsors = re.search('\[(["\w\s,_-]+)\]', line).group(1) + sponsors = [s.strip('"') for s in sponsors.split(",")] + + text = compile( + service, + version, + variable, + alias, + image, + scheme, + ports, + sponsors, + options, + unimplemented, + "0.19.x+", + ) + + base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + readme_file = os.path.join(base_path, "README.md") + with open(readme_file, "w") as f: + f.write(text + "\n") + + +if __name__ == "__main__": + main() diff --git a/plugins/postgres/commands b/plugins/postgres/commands new file mode 100755 index 000000000..0b22b42f5 --- /dev/null +++ b/plugins/postgres/commands @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +[[ " help $PLUGIN_COMMAND_PREFIX:help $PLUGIN_COMMAND_PREFIX $PLUGIN_COMMAND_PREFIX:default " == *" $1 "* ]] || [[ "$1" == "$PLUGIN_COMMAND_PREFIX:"* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/help-functions" + +if [[ ! -d $PLUGIN_CONFIG_ROOT ]]; then + dokku_log_fail "$PLUGIN_SERVICE: Please run: sudo dokku plugin:install" +fi + +if [[ ! -d $PLUGIN_DATA_ROOT ]]; then + dokku_log_fail "$PLUGIN_SERVICE: Please run: sudo dokku plugin:install" +fi + +fn-help "$@" diff --git a/plugins/postgres/common-functions b/plugins/postgres/common-functions new file mode 100755 index 000000000..5c4108906 --- /dev/null +++ b/plugins/postgres/common-functions @@ -0,0 +1,1056 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" + +add_to_links_file() { + declare desc="add an app to the service link file" + declare SERVICE="$1" APP="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + touch "$LINKS_FILE" + sed -i.bak "/^$APP\$/d" "$LINKS_FILE" && rm "$LINKS_FILE.bak" + echo "$APP" >>"$LINKS_FILE" + sort "$LINKS_FILE" -u -o "$LINKS_FILE" +} + +auth_service_filter() { + declare desc="calls user-service plugin trigger" + declare SERVICES=("$@") + local user_auth_count + + if [[ "${#SERVICES[@]}" -eq 0 ]]; then + return + fi + + user_auth_count="$(find "$PLUGIN_PATH"/enabled/*/user-auth-service 2>/dev/null | wc -l)" + + # no plugin trigger exists + if [[ $user_auth_count == 0 ]]; then + # echo out all the services since there is no plugin trigger + for SERVICE in "${SERVICES[@]}"; do + [[ -n "$SERVICE" ]] && echo "$SERVICE" + done + return 0 + fi + + # this plugin trigger exists in the core `20_events` plugin + if [[ "$user_auth_count" == 1 ]] && [[ -f "$PLUGIN_PATH"/enabled/20_events/user-auth-service ]]; then + # echo out all the services since there is no valid plugin trigger + for SERVICE in "${SERVICES[@]}"; do + [[ -n "$SERVICE" ]] && echo "$SERVICE" + done + return 0 + fi + + export SSH_USER=${SSH_USER:=$USER} + export SSH_NAME=${NAME:="default"} + # the output of this trigger should be all the services a user has access to + plugn trigger user-auth-service "$SSH_USER" "$SSH_NAME" "$PLUGIN_COMMAND_PREFIX" "${SERVICES[@]}" +} + +fn-services-list() { + declare desc="prints a filtered list of all local apps" + declare FILTER="$1" + local services=() + + pushd "$PLUGIN_DATA_ROOT" >/dev/null + for f in *; do + [[ -d $f ]] || continue + services+=("$f") + done + popd >/dev/null 2>&1 || pushd "/tmp" >/dev/null + + if [[ "${#services[@]}" -eq 0 ]]; then + return + fi + + if [[ "$FILTER" == "false" ]]; then + for service in "${services[@]}"; do + if [[ -n "$service" ]]; then + echo "$service" + fi + done + return + fi + + for service in $(auth_service_filter "${services[@]}" 2>/dev/null); do + if [[ -n "$service" ]]; then + echo "$service" + fi + done +} + +docker_ports_options() { + declare desc="export a list of exposed ports" + declare PORTS=("$@") + for ((i = 0; i < ${#PLUGIN_DATASTORE_PORTS[@]}; i++)); do + echo -n "-p ${PORTS[i]}:${PLUGIN_DATASTORE_PORTS[i]} " + done +} + +get_container_ip() { + declare desc="retrieve the ip address of a container" + declare CONTAINER_ID="$1" + "$DOCKER_BIN" container inspect --format '{{ .NetworkSettings.IPAddress }}' "$CONTAINER_ID" 2>/dev/null +} + +get_database_name() { + declare desc="retrieve a sanitized database name" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + + if [[ ! -f "$SERVICE_ROOT/DATABASE_NAME" ]]; then + echo "$SERVICE" >"$SERVICE_ROOT/DATABASE_NAME" + fi + + cat "$SERVICE_ROOT/DATABASE_NAME" +} + +get_random_ports() { + declare desc="retrieve N random ports" + declare iterations="${1:-1}" + for ((i = 0; i < iterations; i++)); do + local port=$RANDOM + local quit=0 + while [ "$quit" -ne 1 ]; do + netstat -an | grep $port >/dev/null + # shellcheck disable=SC2181 + if [ $? -gt 0 ]; then + quit=1 + else + port=$((port + 1)) + fi + done + echo $port + done +} + +get_service_name() { + declare desc="retrieve a docker service label" + declare SERVICE="$1" + echo "dokku.${PLUGIN_COMMAND_PREFIX}.$SERVICE" +} + +get_url_from_config() { + declare desc="retrieve a given _URL from a list of configuration variables" + declare EXISTING_CONFIG="$1" CONFIG_VAR="$2" + echo "$EXISTING_CONFIG" | grep "$CONFIG_VAR" | sed "s/$CONFIG_VAR:\s*//" | xargs +} + +in_links_file() { + declare desc="check if a service LINKS file contains an app" + declare SERVICE="$1" APP="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + grep -qE "^$APP\$" "$LINKS_FILE" +} + +is_container_status() { + declare desc="return 0 or 1 depending upon whether a given container has a certain status" + declare CID="$1" STATUS="$2" + local TEMPLATE="{{.State.$STATUS}}" + local CONTAINER_STATUS=$("$DOCKER_BIN" container inspect -f "$TEMPLATE" "$CID" 2>/dev/null || true) + + if [[ "$CONTAINER_STATUS" == "true" ]]; then + return 0 + fi + return 1 +} + +is_implemented_command() { + declare desc="return true if value ($1) is in list (all other arguments)" + declare CMD="$1" + CMD="$(echo "$CMD" | cut -d ':' -f2)" + + if [[ ${#PLUGIN_UNIMPLEMENTED_SUBCOMMANDS[@]} -eq 0 ]]; then + return 0 + fi + + local e + for e in "${PLUGIN_UNIMPLEMENTED_SUBCOMMANDS[@]}"; do + [[ "$e" == "$CMD" ]] && return 1 + done + return 0 +} + +is_valid_service_name() { + declare desc="validate a service name" + declare SERVICE="$1" + [[ -z "$SERVICE" ]] && return 1 + + if [[ "$SERVICE" =~ ^[A-Za-z0-9_-]+$ ]]; then + return 0 + fi + + return 1 +} + +remove_from_links_file() { + declare desc="remove an app from the service link file" + declare SERVICE="$1" APP="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + if [[ ! -f "$LINKS_FILE" ]]; then + return + fi + + sed -i.bak "/^$APP\$/d" "$LINKS_FILE" && rm "$LINKS_FILE.bak" + sort "$LINKS_FILE" -u -o "$LINKS_FILE" +} + +retry-docker-command() { + local ID="$1" COMMAND="$2" + local i=0 success=false + until [ $i -ge 100 ]; do + set +e + suppress_output "$DOCKER_BIN" container exec "$ID" sh -c "$COMMAND" + exit_code=$? + set -e + if [[ "$exit_code" == 0 ]]; then + success=true + break + fi + i=$((i + 1)) + sleep 1 + done + if [[ $i -gt 0 ]]; then + dokku_log_verbose "Container command retried ${i} time(s): ${COMMAND}" + fi + [[ "$success" == "true" ]] || dokku_log_fail "Failed to run command: ${COMMAND}" +} + +service_alternative_alias() { + declare desc="retrieve an alternative alias for a service" + declare EXISTING_CONFIG="$1" + local COLORS=(AQUA BLACK BLUE FUCHSIA GRAY GREEN LIME MAROON NAVY OLIVE PURPLE RED SILVER TEAL WHITE YELLOW) + local ALIAS + + for COLOR in "${COLORS[@]}"; do + ALIAS="${PLUGIN_ALT_ALIAS}_${COLOR}" + local IN_USE=$(echo "$EXISTING_CONFIG" | grep "${ALIAS}_URL") + if [[ -z "$IN_USE" ]]; then + break + fi + unset ALIAS + done + echo "$ALIAS" +} + +service_app_links() { + declare desc="output all service links for a given app" + declare APP="$1" + local LINKED_APP SERVICE SERVICE_ROOT + + for SERVICE in $(fn-services-list true); do + [[ -n "$SERVICE" ]] || continue + + SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + [[ -f "$SERVICE_ROOT/LINKS" ]] || continue + for LINKED_APP in $(<"$SERVICE_ROOT/LINKS"); do + if [[ "$LINKED_APP" == "$APP" ]]; then + echo "$SERVICE" + fi + done + done +} + +service_backup() { + declare desc="create a backup of a service to an existing s3 bucket" + declare SERVICE="$1" BUCKET_NAME="$2" USE_IAM_OPTIONAL_FLAG="$3" + local SERVICE_BACKUP_ROOT="$PLUGIN_DATA_ROOT/$SERVICE/backup" + local BACKUP_ENCRYPTION_CONFIG_ROOT="$PLUGIN_DATA_ROOT/$SERVICE/backup-encryption" + local AWS_ACCESS_KEY_ID_FILE="$SERVICE_BACKUP_ROOT/AWS_ACCESS_KEY_ID" + local AWS_SECRET_ACCESS_KEY_FILE="$SERVICE_BACKUP_ROOT/AWS_SECRET_ACCESS_KEY" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ID="$(cat "$SERVICE_ROOT/ID")" + local BACKUP_PARAMETERS="" + + if [[ -z "$USE_IAM_OPTIONAL_FLAG" ]]; then + [[ ! -f "$AWS_ACCESS_KEY_ID_FILE" ]] && dokku_log_fail "Missing AWS_ACCESS_KEY_ID file" + [[ ! -f "$AWS_SECRET_ACCESS_KEY_FILE" ]] && dokku_log_fail "Missing AWS_SECRET_ACCESS_KEY file" + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e AWS_ACCESS_KEY_ID=$(cat "$AWS_ACCESS_KEY_ID_FILE") -e AWS_SECRET_ACCESS_KEY=$(cat "$AWS_SECRET_ACCESS_KEY_FILE")" + elif [[ "$USE_IAM_OPTIONAL_FLAG" != "--use-iam" ]] && [[ "$USE_IAM_OPTIONAL_FLAG" != "-u" ]]; then + dokku_log_fail "Provide AWS credentials or use the --use-iam flag" + fi + + BACKUP_TMPDIR=$(mktemp -d --tmpdir) + trap 'rm -rf "$BACKUP_TMPDIR" > /dev/null' RETURN INT TERM EXIT + + "$DOCKER_BIN" container inspect "$ID" >/dev/null 2>&1 || dokku_log_fail "Service container does not exist" + is_container_status "$ID" "Running" || dokku_log_fail "Service container is not running" + + (service_export "$SERVICE" >"${BACKUP_TMPDIR}/export") + + # Build parameter list for s3backup tool + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e BUCKET_NAME=$BUCKET_NAME" + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e BACKUP_NAME=${PLUGIN_COMMAND_PREFIX}-${SERVICE}" + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -v ${BACKUP_TMPDIR}:/backup" + + if [[ -f "$SERVICE_BACKUP_ROOT/AWS_DEFAULT_REGION" ]]; then + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e AWS_DEFAULT_REGION=$(cat "$SERVICE_BACKUP_ROOT/AWS_DEFAULT_REGION")" + fi + + if [[ -f "$SERVICE_BACKUP_ROOT/AWS_SIGNATURE_VERSION" ]]; then + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e AWS_SIGNATURE_VERSION=$(cat "$SERVICE_BACKUP_ROOT/AWS_SIGNATURE_VERSION")" + fi + + if [[ -f "$SERVICE_BACKUP_ROOT/ENDPOINT_URL" ]]; then + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e ENDPOINT_URL=$(cat "$SERVICE_BACKUP_ROOT/ENDPOINT_URL")" + fi + + if [[ -f "$BACKUP_ENCRYPTION_CONFIG_ROOT/ENCRYPTION_KEY" ]]; then + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e ENCRYPTION_KEY=$(cat "$BACKUP_ENCRYPTION_CONFIG_ROOT/ENCRYPTION_KEY")" + fi + + if [[ -f "$BACKUP_ENCRYPTION_CONFIG_ROOT/ENCRYPT_WITH_PUBLIC_KEY_ID" ]]; then + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e ENCRYPT_WITH_PUBLIC_KEY_ID=$(cat "$BACKUP_ENCRYPTION_CONFIG_ROOT/ENCRYPT_WITH_PUBLIC_KEY_ID")" + fi + + # shellcheck disable=SC2086 + "$DOCKER_BIN" container run --rm $BACKUP_PARAMETERS "$PLUGIN_S3BACKUP_IMAGE" +} + +service_commit_config() { + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local CONFIG_VARIABLE="${PLUGIN_VARIABLE}_CONFIG_OPTIONS" + local ENV_VARIABLE="${PLUGIN_VARIABLE}_CUSTOM_ENV" + + custom_env="${!ENV_VARIABLE}" + [[ -n "$SERVICE_CUSTOM_ENV" ]] && custom_env="$SERVICE_CUSTOM_ENV" + if [[ -n $custom_env ]]; then + echo "$custom_env" | tr ';' "\n" >"$SERVICE_ROOT/ENV" + else + echo "" >"$SERVICE_ROOT/ENV" + fi + + config_options="${!CONFIG_VARIABLE}" + [[ -n "$PLUGIN_CONFIG_OPTIONS" ]] && config_options="$PLUGIN_CONFIG_OPTIONS" + if [[ -n "$config_options" ]]; then + echo "$config_options" >"$SERVICE_ROOT/CONFIG_OPTIONS" + else + echo "" >"$SERVICE_ROOT/CONFIG_OPTIONS" + fi + + if [[ -n "$SERVICE_MEMORY" ]]; then + echo "$SERVICE_MEMORY" >"$SERVICE_ROOT/SERVICE_MEMORY" + fi + + if [[ -n "$SERVICE_SHM_SIZE" ]]; then + echo "$SERVICE_SHM_SIZE" >"$SERVICE_ROOT/SHM_SIZE" + fi + + if [[ -n "$PLUGIN_IMAGE" ]]; then + echo "$PLUGIN_IMAGE" >"$SERVICE_ROOT/IMAGE" + fi + + if [[ -n "$PLUGIN_IMAGE_VERSION" ]]; then + echo "$PLUGIN_IMAGE_VERSION" >"$SERVICE_ROOT/IMAGE_VERSION" + fi + + if [[ -n "$SERVICE_INITIAL_NETWORK" ]]; then + fn-plugin-property-write "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "initial-network" "$SERVICE_INITIAL_NETWORK" + fi + + if [[ -n "$SERVICE_POST_CREATE_NETWORK" ]]; then + fn-plugin-property-write "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-create-network" "$SERVICE_POST_CREATE_NETWORK" + fi + + if [[ -n "$SERVICE_POST_START_NETWORK" ]]; then + fn-plugin-property-write "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-start-network" "$SERVICE_POST_START_NETWORK" + fi +} + +service_backup_auth() { + declare desc="set up authentication" + declare SERVICE="$1" AWS_ACCESS_KEY_ID="$2" AWS_SECRET_ACCESS_KEY="$3" AWS_DEFAULT_REGION="$4" AWS_SIGNATURE_VERSION="$5" ENDPOINT_URL="$6" + local SERVICE_BACKUP_ROOT="$PLUGIN_DATA_ROOT/$SERVICE/backup" + + mkdir "$SERVICE_BACKUP_ROOT" + echo "$AWS_ACCESS_KEY_ID" >"$SERVICE_BACKUP_ROOT/AWS_ACCESS_KEY_ID" + echo "$AWS_SECRET_ACCESS_KEY" >"$SERVICE_BACKUP_ROOT/AWS_SECRET_ACCESS_KEY" + + if [[ -n "$AWS_DEFAULT_REGION" ]]; then + echo "$AWS_DEFAULT_REGION" >"$SERVICE_BACKUP_ROOT/AWS_DEFAULT_REGION" + fi + + if [[ -n "$AWS_SIGNATURE_VERSION" ]]; then + echo "$AWS_SIGNATURE_VERSION" >"$SERVICE_BACKUP_ROOT/AWS_SIGNATURE_VERSION" + fi + + if [[ -n "$ENDPOINT_URL" ]]; then + echo "$ENDPOINT_URL" >"$SERVICE_BACKUP_ROOT/ENDPOINT_URL" + fi +} + +service_backup_deauth() { + declare desc="remove authentication" + declare SERVICE="$1" + local SERVICE_ROOT="${PLUGIN_DATA_ROOT}/${SERVICE}" + local SERVICE_BACKUP_ROOT="${SERVICE_ROOT}/backup/" + + rm -rf "$SERVICE_BACKUP_ROOT" +} + +service_backup_schedule() { + declare desc="schedules a backup of the service" + declare SERVICE="$1" SCHEDULE="$2" BUCKET_NAME="$3" USE_IAM_OPTIONAL_FLAG="$4" + local DOKKU_BIN="$(which dokku)" + local CRON_FILE="/etc/cron.d/dokku-${PLUGIN_COMMAND_PREFIX}-${SERVICE}" + local TMP_CRON_FILE="${PLUGIN_DATA_ROOT}/.TMP_CRON_FILE" + + if [[ -n "$USE_IAM_OPTIONAL_FLAG" ]] && [[ "$USE_IAM_OPTIONAL_FLAG" != "--use-iam" ]] && [[ "$USE_IAM_OPTIONAL_FLAG" != "-u" ]]; then + dokku_log_fail "Invalid flag provided, only '--use-iam' allowed" + fi + + echo "${SCHEDULE} dokku ${DOKKU_BIN} ${PLUGIN_COMMAND_PREFIX}:backup ${SERVICE} ${BUCKET_NAME} ${USE_IAM_OPTIONAL_FLAG}" >"$TMP_CRON_FILE" + sudo /bin/mv "$TMP_CRON_FILE" "$CRON_FILE" + sudo /bin/chown root:root "$CRON_FILE" + sudo /bin/chmod 644 "$CRON_FILE" +} + +service_backup_schedule_cat() { + declare desc="cat the contents of the configured backup cronfile for the service" + declare SERVICE="$1" + local CRON_FILE="/etc/cron.d/dokku-${PLUGIN_COMMAND_PREFIX}-${SERVICE}" + + if [[ ! -f "$CRON_FILE" ]]; then + dokku_log_fail "There is no scheduled backup for ${SERVICE}." + fi + + cat "$CRON_FILE" +} + +service_backup_set_encryption() { + declare desc="set up backup encryption" + declare SERVICE="$1" ENCRYPTION_KEY="$2" + local SERVICE_ROOT="${PLUGIN_DATA_ROOT}/${SERVICE}" + local SERVICE_BACKUP_ENCRYPTION_ROOT="${SERVICE_ROOT}/backup-encryption/" + + mkdir "$SERVICE_BACKUP_ENCRYPTION_ROOT" + echo "$ENCRYPTION_KEY" >"${SERVICE_BACKUP_ENCRYPTION_ROOT}/ENCRYPTION_KEY" +} + +service_backup_set_public_key_encryption() { + declare desc="set up backup GPG Public Key encryption" + declare SERVICE="$1" ENCRYPT_WITH_PUBLIC_KEY_ID="$2" + local SERVICE_ROOT="${PLUGIN_DATA_ROOT}/${SERVICE}" + local SERVICE_BACKUP_ENCRYPTION_ROOT="${SERVICE_ROOT}/backup-encryption/" + + mkdir "$SERVICE_BACKUP_ENCRYPTION_ROOT" + echo "$ENCRYPT_WITH_PUBLIC_KEY_ID" >"${SERVICE_BACKUP_ENCRYPTION_ROOT}/ENCRYPT_WITH_PUBLIC_KEY_ID" +} + +service_backup_unschedule() { + declare desc="unschedule the backup of the service" + declare SERVICE="$1" + local CRON_FILE="/etc/cron.d/dokku-${PLUGIN_COMMAND_PREFIX}-${SERVICE}" + + sudo /bin/rm -f "$CRON_FILE" +} + +service_backup_unset_encryption() { + declare desc="remove backup encryption" + declare SERVICE="$1" + local SERVICE_ROOT="${PLUGIN_DATA_ROOT}/${SERVICE}" + local SERVICE_BACKUP_ENCRYPTION_ROOT="${SERVICE_ROOT}/backup-encryption/" + + rm -rf "$SERVICE_BACKUP_ENCRYPTION_ROOT" +} + +service_backup_unset_encryption() { + declare desc="remove backup encryption" + declare SERVICE="$1" + local SERVICE_ROOT="${PLUGIN_DATA_ROOT}/${SERVICE}" + local SERVICE_BACKUP_ENCRYPTION_ROOT="${SERVICE_ROOT}/backup-encryption/" + + rm -rf "$SERVICE_BACKUP_ENCRYPTION_ROOT" +} + +service_container_rm() { + declare desc="stop a service and remove the running container" + declare SERVICE="$1" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local ID + + service_pause "$SERVICE" + ID=$("$DOCKER_BIN" container ps -aq --no-trunc --filter "name=^/$SERVICE_NAME$") || true + # this may be 'true' in tests... + if [[ -z "$ID" ]] || [[ "$ID" == "true" ]]; then + return 0 + fi + + dokku_log_verbose_quiet "Removing container" + "$DOCKER_BIN" container update --restart=no "$SERVICE_NAME" >/dev/null 2>&1 + if ! "$DOCKER_BIN" container rm "$SERVICE_NAME" >/dev/null 2>&1; then + dokku_log_fail "Unable to remove container for service $SERVICE" + fi +} + +service_dns_hostname() { + declare desc="retrieve the alias of a service" + declare SERVICE="$1" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + echo "$SERVICE_NAME" | tr ._ - +} + +service_enter() { + declare desc="enter running app container of specified proc type" + declare SERVICE="$1" && shift 1 + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ID="$(cat "$SERVICE_ROOT/ID")" + + "$DOCKER_BIN" container inspect "$ID" >/dev/null 2>&1 || dokku_log_fail "Service container does not exist" + is_container_status "$ID" "Running" || dokku_log_fail "Service container is not running" + + local EXEC_CMD="" + has_tty && local DOKKU_RUN_OPTS+=" -i -t" + # shellcheck disable=SC2086 + "$DOCKER_BIN" container exec $DOKKU_RUN_OPTS $ID $EXEC_CMD "${@:-/bin/bash}" +} + +service_exists() { + declare desc="returns 0 or 1 depending on whether service exists or not" + declare SERVICE="$1" + [[ -z "$SERVICE" ]] && return 1 + [[ -d "$PLUGIN_DATA_ROOT/$SERVICE" ]] && return 0 + return 1 +} + +service_exposed_ports() { + declare desc="list exposed ports for a service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PORT_FILE="$SERVICE_ROOT/PORT" + [[ ! -f $PORT_FILE ]] && echo '-' && return 0 + local PORTS=($(cat "$PORT_FILE")) + for ((i = 0; i < ${#PLUGIN_DATASTORE_PORTS[@]}; i++)); do + echo -n "${PLUGIN_DATASTORE_PORTS[i]}->${PORTS[i]} " + done +} + +service_image_exists() { + declare desc="check if the current image exists" + declare SERVICE="$1" PLUGIN_IMAGE="${2:-$PLUGIN_IMAGE}" PLUGIN_IMAGE_VERSION="${3:-$PLUGIN_IMAGE_VERSION}" + local plugin_image="$PLUGIN_IMAGE" + local plugin_image_version="$PLUGIN_IMAGE_VERSION" + + if [[ -z "$PLUGIN_IMAGE" ]] && [[ -f "$SERVICE_ROOT/IMAGE" ]]; then + plugin_image="$(cat "$SERVICE_ROOT/IMAGE")" + fi + + if [[ -z "$PLUGIN_IMAGE_VERSION" ]] && [[ -f "$SERVICE_ROOT/IMAGE_VERSION" ]]; then + plugin_image_version="$(cat "$SERVICE_ROOT/IMAGE_VERSION")" + fi + + local IMAGE="$plugin_image:$plugin_image_version" + + if [[ "$("$DOCKER_BIN" image ls -q "$IMAGE" 2>/dev/null)" == "" ]]; then + return 1 + fi + + return 0 +} + +service_info() { + declare desc="retrieve information about a given service" + declare SERVICE="$1" INFO_FLAG="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_URL=$(service_url "$SERVICE") + local PORT_FILE="$SERVICE_ROOT/PORT" + local SERVICE_CONTAINER_ID="$(cat "$SERVICE_ROOT/ID")" + local flag key valid_flags + + local flag_map=( + "--config-dir: ${SERVICE_ROOT}/${PLUGIN_CONFIG_SUFFIX}" + "--config-options: $(cat "$SERVICE_ROOT/CONFIG_OPTIONS" 2>/dev/null || true)" + "--data-dir: ${SERVICE_ROOT}/data" + "--dsn: ${SERVICE_URL}" + "--exposed-ports: $(service_exposed_ports "$SERVICE")" + "--id: ${SERVICE_CONTAINER_ID}" + "--internal-ip: $(get_container_ip "${SERVICE_CONTAINER_ID}")" + "--initial-network: $(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "initial-network")" + "--links: $(service_linked_apps "$SERVICE")" + "--post-create-network: $(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-create-network")" + "--post-start-network: $(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-start-network")" + "--service-root: ${SERVICE_ROOT}" + "--status: $(service_status "$SERVICE")" + "--version: $(service_version "$SERVICE")" + ) + if [[ -z "$INFO_FLAG" ]]; then + dokku_log_info2_quiet "$SERVICE $PLUGIN_COMMAND_PREFIX service information" + for flag in "${flag_map[@]}"; do + key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" + dokku_log_verbose "$(printf "%-20s %-25s" "${key^}" "${flag#*: }")" + done + else + local match=false + for flag in "${flag_map[@]}"; do + valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)" + if [[ "$flag" == "${INFO_FLAG}:"* ]]; then + echo "${flag#*: }" && match=true + fi + done + [[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}" + fi +} + +service_is_linked() { + declare desc="link a service to an application" + declare SERVICE="$1" APP="$2" + update_plugin_scheme_for_app "$APP" + local SERVICE_URL=$(service_url "$SERVICE") + local EXISTING_CONFIG=$(config_all "$APP") + local LINK=$(echo "$EXISTING_CONFIG" | grep "$SERVICE_URL" | cut -d: -f1) || true + if [[ -z $LINK ]]; then + dokku_log_warn "Service $SERVICE is not linked to $APP" + exit 1 + fi + dokku_log_info1 "Service $SERVICE is linked to $APP" +} + +service_link() { + declare desc="link a service to an application" + declare SERVICE="$1" APP="$2" + update_plugin_scheme_for_app "$APP" + local SERVICE_URL=$(service_url "$SERVICE") + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local EXISTING_CONFIG=$(config_all "$APP") + local LINK=$(echo "$EXISTING_CONFIG" | grep "$SERVICE_URL" | cut -d: -f1) || true + local SERVICE_DNS_HOSTNAME=$(service_dns_hostname "$SERVICE") + local LINKS_FILE="$SERVICE_ROOT/LINKS" + local ALIAS="$PLUGIN_DEFAULT_ALIAS" + local DEFAULT_ALIAS + + if [[ -n "$SERVICE_ALIAS" ]]; then + ALIAS="$SERVICE_ALIAS" + ALIAS_IN_USE=$(echo "$EXISTING_CONFIG" | grep "${ALIAS}_URL") || true + [[ -n "$ALIAS_IN_USE" ]] && dokku_log_fail "Specified alias $ALIAS already in use" + else + DEFAULT_ALIAS=$(echo "$EXISTING_CONFIG" | grep "${PLUGIN_DEFAULT_ALIAS}_URL") || true + if [[ -n "$DEFAULT_ALIAS" ]]; then + ALIAS=$(service_alternative_alias "$EXISTING_CONFIG") + fi + [[ -z "$ALIAS" ]] && dokku_log_fail "Unable to use default or generated URL alias" + fi + + [[ -n $LINK ]] && dokku_log_fail "Already linked as $LINK" + plugn trigger service-action pre-link "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" + add_to_links_file "$SERVICE" "$APP" + + if declare -f -F add_passed_docker_option >/dev/null; then + # shellcheck disable=SC2034 + local passed_phases=(build deploy run) + add_passed_docker_option passed_phases[@] "--link $SERVICE_NAME:$SERVICE_DNS_HOSTNAME" + else + dokku docker-options:add "$APP" build,deploy,run "--link $SERVICE_NAME:$SERVICE_DNS_HOSTNAME" + fi + [[ -n "$SERVICE_QUERYSTRING" ]] && SERVICE_URL="${SERVICE_URL}?${SERVICE_QUERYSTRING}" + plugn trigger service-action post-link "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" + if [[ "$DOKKU_GLOBAL_FLAGS" == *"--no-restart"* ]] || [[ "$SERVICE_RESTART_APPS" == "false" ]]; then + config_set --no-restart "$APP" "${ALIAS}_URL=$SERVICE_URL" + dokku_log_verbose "Skipping restart of linked app" + else + config_set "$APP" "${ALIAS}_URL=$SERVICE_URL" + fi + plugn trigger service-action post-link-complete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" +} + +service_linked_apps() { + declare desc="list all apps linked to a service for info output" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + touch "$LINKS_FILE" + [[ -z $(<"$LINKS_FILE") ]] && echo '-' && return 0 + + tr '\n' ' ' <"$LINKS_FILE" +} + +service_links() { + declare desc="list all apps linked to a service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + touch "$LINKS_FILE" + [[ -z $(<"$LINKS_FILE") ]] && return 0 + + cat "$LINKS_FILE" +} + +service_list() { + declare desc="list all services and their status" + + mapfile -t services < <(fn-services-list true) + if [[ "${#services[@]}" -eq 0 ]] || [[ -z "$services" ]]; then + dokku_log_warn "There are no $PLUGIN_SERVICE services" + return + fi + + dokku_log_info2_quiet "$PLUGIN_SERVICE services" + for service in "${services[@]}"; do + echo "${service}" + done +} + +service_logs() { + declare desc="display logs for a service" + declare SERVICE="$1" TAIL_FLAG="$2" TAIL_COUNT="$3" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ID=$(cat "$SERVICE_ROOT/ID") + local RE_INTEGER='^[0-9]+$' + + DOKKU_LOGS_ARGS="--tail $TAIL_COUNT" + if [[ "$TAIL_FLAG" == "-t" ]] || [[ "$TAIL_FLAG" == "--tail" ]]; then + DOKKU_LOGS_ARGS+=" --follow" + fi + + "$DOCKER_BIN" container inspect "$ID" >/dev/null 2>&1 || dokku_log_fail "Service container does not exist" + is_container_status "$ID" "Running" || dokku_log_warn "Service logs may not be output as service is not running" + + # shellcheck disable=SC2086 + "$DOCKER_BIN" container logs $DOKKU_LOGS_ARGS "$ID" 2>&1 +} + +service_parse_args() { + declare desc="cli arg parser" + local next_index=1 + local skip=false + local args=("$@") + + for arg in "$@"; do + shift + case "$arg" in + "--alias") set -- "$@" "-a" ;; + "--config-options") set -- "$@" "-c" ;; + "--custom-env") set -- "$@" "-C" ;; + "--database") set -- "$@" "-d" ;; + "--image-version") set -- "$@" "-I" ;; + "--initial-network") set -- "$@" "-N" ;; + "--image") set -- "$@" "-i" ;; + "--memory") set -- "$@" "-m" ;; + "--no-restart") set -- "$@" "-n" ;; + "--password") set -- "$@" "-p" ;; + "--post-create-network") set -- "$@" "-P" ;; + "--post-start-network") set -- "$@" "-S" ;; + "--querystring") set -- "$@" "-q" ;; + "--restart-apps") set -- "$@" "-R" ;; + "--root-password") set -- "$@" "-r" ;; + "--shm-size") set -- "$@" "-s" ;; + "--user") set -- "$@" "-u" ;; + *) set -- "$@" "$arg" ;; + esac + done + + OPTIND=1 + while getopts "na:c:C:d:i:I:m:n:N:p:P:q:R:r:s:S:u:" opt; do + case "$opt" in + a) + SERVICE_ALIAS="${OPTARG^^}" + export SERVICE_ALIAS="${SERVICE_ALIAS%_URL}" + ;; + c) + export PLUGIN_CONFIG_OPTIONS=$OPTARG + ;; + C) + export SERVICE_CUSTOM_ENV=$OPTARG + ;; + d) + export SERVICE_DATABASE=$OPTARG + ;; + i) + export PLUGIN_IMAGE=$OPTARG + ;; + I) + export PLUGIN_IMAGE_VERSION=$OPTARG + ;; + m) + export SERVICE_MEMORY=$OPTARG + ;; + n) + export SERVICE_RESTART_APPS=false + ;; + N) + export SERVICE_INITIAL_NETWORK=$OPTARG + ;; + p) + export SERVICE_PASSWORD=$OPTARG + ;; + P) + export SERVICE_POST_CREATE_NETWORK=$OPTARG + ;; + q) + export SERVICE_QUERYSTRING=${OPTARG#"?"} + ;; + R) + export SERVICE_RESTART_APPS=$OPTARG + ;; + r) + export SERVICE_ROOT_PASSWORD=$OPTARG + ;; + s) + export SERVICE_SHM_SIZE=$OPTARG + ;; + S) + export SERVICE_POST_START_NETWORK=$OPTARG + ;; + u) + export SERVICE_USER=$OPTARG + ;; + esac + done + shift "$((OPTIND - 1))" # remove options from positional parameters +} + +service_password() { + declare desc="fetch the password for a service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PASSWORD_FILE="$SERVICE_ROOT/PASSWORD" + if [[ -f "$PASSWORD_FILE" ]]; then + cat "$PASSWORD_FILE" + fi +} + +service_root_password() { + declare desc="fetch the root password for a service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PASSWORD_FILE="$SERVICE_ROOT/ROOTPASSWORD" + if [[ -f "$PASSWORD_FILE" ]]; then + cat "$PASSWORD_FILE" + fi +} + +service_port_expose() { + declare desc="wrapper for exposing service ports" + declare SERVICE="$1" PORTS=(${@:2}) + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PORT_FILE="$SERVICE_ROOT/PORT" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local EXPOSED_NAME="$SERVICE_NAME.ambassador" + + if [[ ${#PORTS[@]} -eq 0 ]]; then + # shellcheck disable=SC2206 + PORTS=(${PORTS[@]:-$(get_random_ports ${#PLUGIN_DATASTORE_PORTS[@]})}) + fi + + [[ "${#PORTS[@]}" != "${#PLUGIN_DATASTORE_PORTS[@]}" ]] && dokku_log_fail "${#PLUGIN_DATASTORE_PORTS[@]} ports to be exposed need to be provided in the following order: ${PLUGIN_DATASTORE_PORTS[*]}" + + if [[ -s "$PORT_FILE" ]]; then + # shellcheck disable=SC2207 + PORTS=($(cat "$PORT_FILE")) + dokku_log_fail "Service $SERVICE already exposed on port(s) ${PORTS[*]}" + fi + + if "$DOCKER_BIN" container inspect "$EXPOSED_NAME" >/dev/null 2>&1; then + dokku_log_warn "Service $SERVICE has an untracked expose container, removing" + "$DOCKER_BIN" container stop "$EXPOSED_NAME" >/dev/null 2>&1 || true + suppress_output "$DOCKER_BIN" container rm "$EXPOSED_NAME" + fi + + echo "${PORTS[@]}" >"$PORT_FILE" + + service_start "$SERVICE" "true" + service_port_reconcile_status "$SERVICE" + dokku_log_info1 "Service $SERVICE exposed on port(s) [container->host]: $(service_exposed_ports "$SERVICE")" +} + +service_port_unexpose() { + declare desc="wrapper for pausing exposed service ports" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PORT_FILE="$SERVICE_ROOT/PORT" + + rm -rf "$PORT_FILE" + service_port_reconcile_status "$SERVICE" + dokku_log_info1 "Service $SERVICE unexposed" +} + +service_port_reconcile_status() { + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PORT_FILE="$SERVICE_ROOT/PORT" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local EXPOSED_NAME="$SERVICE_NAME.ambassador" + + if [[ ! -s "$PORT_FILE" ]]; then + if "$DOCKER_BIN" container inspect "$EXPOSED_NAME" >/dev/null 2>&1; then + "$DOCKER_BIN" container stop "$EXPOSED_NAME" >/dev/null 2>&1 || true + suppress_output "$DOCKER_BIN" container rm "$EXPOSED_NAME" + return $? + fi + return + fi + + if is_container_status "$EXPOSED_NAME" "Running"; then + return + fi + + if "$DOCKER_BIN" container inspect "$EXPOSED_NAME" >/dev/null 2>&1; then + suppress_output "$DOCKER_BIN" container start "$EXPOSED_NAME" + return $? + fi + + # shellcheck disable=SC2207 + PORTS=($(cat "$PORT_FILE")) + # shellcheck disable=SC2046 + "$DOCKER_BIN" container run -d --link "$SERVICE_NAME:$PLUGIN_COMMAND_PREFIX" --name "$EXPOSED_NAME" $(docker_ports_options "${PORTS[@]}") --restart always --label dokku=ambassador --label "dokku.ambassador=$PLUGIN_COMMAND_PREFIX" "$PLUGIN_AMBASSADOR_IMAGE" >/dev/null +} + +service_promote() { + declare desc="promote a secondary service to the primary env var" + declare SERVICE="$1" APP="$2" + local PLUGIN_DEFAULT_CONFIG_VAR="${PLUGIN_DEFAULT_ALIAS}_URL" + local EXISTING_CONFIG=$(config_all "$APP") + update_plugin_scheme_for_app "$APP" + local SERVICE_URL=$(service_url "$SERVICE") + local CONFIG_VARS=($(echo "$EXISTING_CONFIG" | grep "$SERVICE_URL" | cut -d: -f1)) || true + local PREVIOUS_DEFAULT_URL=$(get_url_from_config "$EXISTING_CONFIG" "$PLUGIN_DEFAULT_CONFIG_VAR") + + [[ -z ${CONFIG_VARS[*]} ]] && dokku_log_fail "Not linked to app $APP" + [[ ${CONFIG_VARS[*]} =~ $PLUGIN_DEFAULT_CONFIG_VAR ]] && dokku_log_fail "Service $1 already promoted as $PLUGIN_DEFAULT_CONFIG_VAR" + + local NEW_CONFIG_VARS="" + if [[ -n $PREVIOUS_DEFAULT_URL ]]; then + local PREVIOUS_ALIAS=$(echo "$EXISTING_CONFIG" | grep "$PREVIOUS_DEFAULT_URL" | grep -v "$PLUGIN_DEFAULT_CONFIG_VAR") || true + if [[ -z $PREVIOUS_ALIAS ]]; then + local ALIAS=$(service_alternative_alias "$EXISTING_CONFIG") + NEW_CONFIG_VARS+="${ALIAS}_URL=$PREVIOUS_DEFAULT_URL " + fi + fi + local PROMOTE_URL=$(get_url_from_config "$EXISTING_CONFIG" "${CONFIG_VARS[0]}") + NEW_CONFIG_VARS+="$PLUGIN_DEFAULT_CONFIG_VAR=$PROMOTE_URL" + + # shellcheck disable=SC2086 + config_set "$APP" $NEW_CONFIG_VARS +} + +service_set_alias() { + declare desc="sets the alias in use for a service" + declare SERVICE="$1" ALIAS="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ALIAS_FILE="$SERVICE_ROOT/ALIAS" + + touch "$ALIAS_FILE" + echo "$ALIAS" >"$ALIAS_FILE" +} + +service_status() { + declare desc="display the status of a service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ID="$(cat "$SERVICE_ROOT/ID")" + local CONTAINER_STATUS + + CONTAINER_STATUS=$("$DOCKER_BIN" container inspect -f "{{.State.Status}}" "$ID" 2>/dev/null || true) + [[ -n "$CONTAINER_STATUS" ]] && echo "$CONTAINER_STATUS" && return 0 + echo "missing" && return 0 +} + +service_pause() { + declare desc="pause a running service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local ID=$("$DOCKER_BIN" container ps -aq --no-trunc --filter "name=^/$SERVICE_NAME$") || true + [[ -z $ID ]] && dokku_log_warn "Service is already paused" && return 0 + + if [[ -n $ID ]]; then + dokku_log_info2_quiet "Pausing container" + "$DOCKER_BIN" container stop "$SERVICE_NAME" >/dev/null + if "$DOCKER_BIN" container inspect "$ID" >/dev/null 2>&1; then + "$DOCKER_BIN" container stop "$SERVICE_NAME.ambassador" >/dev/null 2>&1 || true + fi + dokku_log_verbose_quiet "Container paused" + else + dokku_log_verbose_quiet "No container exists for $SERVICE" + fi +} + +service_unlink() { + declare desc="unlink an application from a service" + declare SERVICE="$1" APP="$2" + update_plugin_scheme_for_app "$APP" + local SERVICE_URL=$(service_url "$SERVICE") + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local EXISTING_CONFIG=$(config_all "$APP") + local SERVICE_DNS_HOSTNAME=$(service_dns_hostname "$SERVICE") + local LINK=($(echo "$EXISTING_CONFIG" | grep "$SERVICE_URL" | cut -d: -f1)) || true + + plugn trigger service-action pre-unlink "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" + remove_from_links_file "$SERVICE" "$APP" + + if declare -f -F add_passed_docker_option >/dev/null; then + # shellcheck disable=SC2034 + local passed_phases=(build deploy run) + remove_passed_docker_option passed_phases[@] "--link $SERVICE_NAME:$SERVICE_DNS_HOSTNAME" + else + dokku docker-options:remove "$APP" build,deploy,run "--link $SERVICE_NAME:$SERVICE_DNS_HOSTNAME" + fi + + [[ -z ${LINK[*]} ]] && dokku_log_fail "Not linked to app $APP" + plugn trigger service-action post-unlink "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" + if [[ "$DOKKU_GLOBAL_FLAGS" == *"--no-restart"* ]] || [[ "$SERVICE_RESTART_APPS" == "false" ]]; then + config_unset --no-restart "$APP" "${LINK[@]}" + dokku_log_verbose "Skipping restart of linked app" + else + config_unset "$APP" "${LINK[@]}" + fi + plugn trigger service-action post-unlink-complete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" +} + +service_version() { + declare desc="display the running version for an image" + declare SERVICE="$1" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + "$DOCKER_BIN" container inspect -f '{{.Config.Image}}' "$SERVICE_NAME" 2>/dev/null || true +} + +update_plugin_scheme_for_app() { + declare desc="retrieve the updated plugin scheme" + declare APP="$1" + local DATABASE_SCHEME + + DATABASE_SCHEME=$(config_get "$APP" "${PLUGIN_VARIABLE}_DATABASE_SCHEME" || true) + PLUGIN_SCHEME=${DATABASE_SCHEME:-$PLUGIN_SCHEME} +} + +verify_service_name() { + declare desc="verify that a service exists" + declare SERVICE="$@" + + if [[ -z "$SERVICE" ]]; then + dokku_log_fail "SERVICE must not be empty" + fi + + if [[ ! -d "$PLUGIN_DATA_ROOT/$SERVICE" ]]; then + dokku_log_fail "$PLUGIN_SERVICE service $SERVICE does not exist" + fi + + SERVICE="$(auth_service_filter "$SERVICE")" + if [[ -z "$SERVICE" ]]; then + dokku_log_fail "$PLUGIN_SERVICE service $SERVICE does not exist" + fi + + return 0 +} + +write_database_name() { + declare desc="write a sanitized database name" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + + # some datastores do not like special characters in database names + # so we need to normalize them out + echo "$SERVICE" | tr .- _ >"$SERVICE_ROOT/DATABASE_NAME" +} diff --git a/plugins/postgres/config b/plugins/postgres/config new file mode 100644 index 000000000..301ec3d69 --- /dev/null +++ b/plugins/postgres/config @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +export POSTGRES_IMAGE=${POSTGRES_IMAGE:="$(awk -F '[ :]' '{print $2}' "${_DIR}/Dockerfile")"} +export POSTGRES_IMAGE_VERSION=${POSTGRES_IMAGE_VERSION:="$(awk -F '[ :]' '{print $3}' "${_DIR}/Dockerfile")"} +export POSTGRES_ROOT=${POSTGRES_ROOT:="$DOKKU_LIB_ROOT/services/postgres"} +export DOKKU_LIB_HOST_ROOT=${DOKKU_LIB_HOST_ROOT:=$DOKKU_LIB_ROOT} +export POSTGRES_HOST_ROOT=${POSTGRES_HOST_ROOT:="$DOKKU_LIB_HOST_ROOT/services/postgres"} + +export PLUGIN_UNIMPLEMENTED_SUBCOMMANDS=() +export PLUGIN_COMMAND_PREFIX="postgres" +export PLUGIN_CONFIG_ROOT=${PLUGIN_CONFIG_ROOT:="$DOKKU_LIB_ROOT/config/$PLUGIN_COMMAND_PREFIX"} +export PLUGIN_DATA_ROOT=$POSTGRES_ROOT +export PLUGIN_DATA_HOST_ROOT=$POSTGRES_HOST_ROOT +export PLUGIN_DATASTORE_PORTS=(5432) +export PLUGIN_DATASTORE_WAIT_PORT=5432 +export PLUGIN_DEFAULT_ALIAS="DATABASE" +export PLUGIN_DISABLE_PULL=${POSTGRES_DISABLE_PULL:=} +export PLUGIN_DISABLE_PULL_VARIABLE="POSTGRES_DISABLE_PULL" +export PLUGIN_ALT_ALIAS="DOKKU_POSTGRES" +export PLUGIN_IMAGE=$POSTGRES_IMAGE +export PLUGIN_IMAGE_VERSION=$POSTGRES_IMAGE_VERSION +export PLUGIN_SCHEME="postgres" +export PLUGIN_SERVICE="Postgres" +export PLUGIN_VARIABLE="POSTGRES" +export PLUGIN_BASE_PATH="$PLUGIN_PATH" +export PLUGIN_CONFIG_SUFFIX="data" +if [[ -n $DOKKU_API_VERSION ]]; then + export PLUGIN_BASE_PATH="$PLUGIN_ENABLED_PATH" +fi + +export PLUGIN_BUSYBOX_IMAGE=${PLUGIN_BUSYBOX_IMAGE:=busybox:1.37.0-uclibc} +export PLUGIN_AMBASSADOR_IMAGE=${PLUGIN_AMBASSADOR_IMAGE:=dokku/ambassador:0.8.2} +export PLUGIN_S3BACKUP_IMAGE=${PLUGIN_S3BACKUP_IMAGE:=dokku/s3backup:0.18.0} +export PLUGIN_WAIT_IMAGE=${PLUGIN_WAIT_IMAGE:=dokku/wait:0.9.3} + +export POSTGRES_CONFIG_OPTIONS=${POSTGRES_CONFIG_OPTIONS:=""} diff --git a/plugins/postgres/docs/README.md b/plugins/postgres/docs/README.md new file mode 100644 index 000000000..468382b40 --- /dev/null +++ b/plugins/postgres/docs/README.md @@ -0,0 +1,4 @@ +# Extra Documentation + +The documentation in this folder is supplemental to using this plugin. It is injected automatically into the plugin's readme during documentation generation. + diff --git a/plugins/postgres/docs/create.md b/plugins/postgres/docs/create.md new file mode 100644 index 000000000..a81f148c2 --- /dev/null +++ b/plugins/postgres/docs/create.md @@ -0,0 +1,13 @@ +Official Postgres "$DOCKER_BIN" image ls does not include postgis extension (amongst others). The following example creates a new postgres service using `postgis/postgis:13-3.1` image, which includes the `postgis` extension. + +```shell +# use the appropriate image-version for your use-case +dokku postgres:create postgis-database --image "postgis/postgis" --image-version "13-3.1" +``` + +To use pgvector instead, run the following: + +```shell +# use the appropriate image-version for your use-case +dokku postgres:create pgvector-database --image "pgvector/pgvector" --image-version "pg17" +``` diff --git a/plugins/postgres/docs/export.md b/plugins/postgres/docs/export.md new file mode 100644 index 000000000..940558fef --- /dev/null +++ b/plugins/postgres/docs/export.md @@ -0,0 +1,5 @@ +Note that the export will result in a file containing the binary postgres export data. It can be converted to plain text using `pg_restore` as follows + +```shell +pg_restore data.dump -f plain.sql +``` diff --git a/plugins/postgres/docs/upgrade.md b/plugins/postgres/docs/upgrade.md new file mode 100644 index 000000000..fa0a6381e --- /dev/null +++ b/plugins/postgres/docs/upgrade.md @@ -0,0 +1,30 @@ +Postgres does not handle upgrading data for major versions automatically (eg. 11 => 12). Upgrades should be done manually. Users are encouraged to upgrade to the latest minor release for their postgres version before performing a major upgrade. + +While there are many ways to upgrade a postgres database, for safety purposes, it is recommended that an upgrade is performed by exporting the data from an existing database and importing it into a new database. This also allows testing to ensure that applications interact with the database correctly after the upgrade, and can be used in a staging environment. + +The following is an example of how to upgrade a postgres database named `lollipop-11` from 11.13 to 12.8. + +```shell +# stop any linked apps +dokku ps:stop linked-app + +# export the database contents +dokku postgres:export lollipop-11 > /tmp/lollipop-11.export + +# create a new database at the desired version +dokku postgres:create lollipop-12 --image-version 12.8 + +# import the export file +dokku postgres:import lollipop-12 < /tmp/lollipop-11.export + +# run any sql tests against the new database to verify the import went smoothly + +# unlink the old database from your apps +dokku postgres:unlink lollipop-11 linked-app + +# link the new database to your apps +dokku postgres:link lollipop-12 linked-app + +# start the linked apps again +dokku ps:start linked-app +``` diff --git a/plugins/postgres/functions b/plugins/postgres/functions new file mode 100755 index 000000000..9442d722a --- /dev/null +++ b/plugins/postgres/functions @@ -0,0 +1,233 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" +if [[ -f "$PLUGIN_AVAILABLE_PATH/docker-options/functions" ]]; then + source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" +fi + +service_connect() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local DATABASE_NAME="$(get_database_name "$SERVICE")" + local SERVICE_TTY_OPTS + has_tty && SERVICE_TTY_OPTS="-t" + + "$DOCKER_BIN" container exec --env=LANG=C.UTF-8 --env=LC_ALL=C.UTF-8 -i $SERVICE_TTY_OPTS "$SERVICE_NAME" psql -v ON_ERROR_STOP=1 -h localhost -U postgres "$DATABASE_NAME" +} + +service_create() { + local SERVICE="$1" + is_valid_service_name "$SERVICE" || dokku_log_fail "Please specify a valid name for the service. Valid characters are: [A-Za-z0-9_]+" + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ ! -d "$PLUGIN_DATA_ROOT/$SERVICE" ]] || dokku_log_fail "$PLUGIN_SERVICE service $SERVICE already exists" + SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + LINKS_FILE="$SERVICE_ROOT/LINKS" + + service_parse_args "${@:2}" + + if ! service_image_exists "$SERVICE"; then + if [[ "$PLUGIN_DISABLE_PULL" == "true" ]]; then + dokku_log_warn "${PLUGIN_DISABLE_PULL_VARIABLE} environment variable detected. Not running pull command." 1>&2 + dokku_log_warn " docker image pull ${IMAGE}" 1>&2 + dokku_log_warn "$PLUGIN_SERVICE service creation failed" + exit 1 + fi + "$DOCKER_BIN" image pull "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" || dokku_log_fail "$PLUGIN_SERVICE image $PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION pull failed" + fi + + plugn trigger service-action pre-create "$PLUGIN_COMMAND_PREFIX" "$SERVICE" + mkdir -p "$SERVICE_ROOT" || dokku_log_fail "Unable to create service directory" + mkdir -p "$SERVICE_ROOT/data" || dokku_log_fail "Unable to create service data directory" + touch "$LINKS_FILE" + + PASSWORD=$(openssl rand -hex 16) + if [[ -n "$SERVICE_PASSWORD" ]]; then + PASSWORD="$SERVICE_PASSWORD" + dokku_log_warn "Specified password may not be as secure as the auto-generated password" + fi + echo "$PASSWORD" >"$SERVICE_ROOT/PASSWORD" + chmod 640 "$SERVICE_ROOT/PASSWORD" + + service_commit_config "$SERVICE" + write_database_name "$SERVICE" + plugn trigger service-action post-create "$PLUGIN_COMMAND_PREFIX" "$SERVICE" + service_create_container "$SERVICE" + plugn trigger service-action post-create-complete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" +} + +service_create_container() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_HOST_ROOT="$PLUGIN_DATA_HOST_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local PASSWORD="$(service_password "$SERVICE")" + local DATABASE_NAME="$(get_database_name "$SERVICE")" + local PREVIOUS_ID + + if [[ -f "$SERVICE_ROOT/CONFIG_OPTIONS" ]]; then + export CONFIG_OPTIONS="$(cat "$SERVICE_ROOT/CONFIG_OPTIONS")" + fi + + local network_alias="$(service_dns_hostname "$SERVICE")" + + rm -f "$SERVICE_ROOT/ID" + declare -a DOCKER_ARGS + DOCKER_ARGS=() + DOCKER_ARGS+=("--cidfile=$SERVICE_ROOT/ID") + DOCKER_ARGS+=("--env-file=$SERVICE_ROOT/ENV") + DOCKER_ARGS+=("--env=POSTGRES_PASSWORD=$PASSWORD") + DOCKER_ARGS+=("--hostname=$SERVICE_NAME") + DOCKER_ARGS+=("--label=dokku.service=$PLUGIN_COMMAND_PREFIX") + DOCKER_ARGS+=("--label=dokku=service") + DOCKER_ARGS+=("--name=$SERVICE_NAME") + DOCKER_ARGS+=("--restart=always") + DOCKER_ARGS+=("--volume=$SERVICE_HOST_ROOT/data:/var/lib/postgresql/data") + + declare -a LINK_CONTAINER_DOCKER_ARGS + LINK_CONTAINER_DOCKER_ARGS=() + LINK_CONTAINER_DOCKER_ARGS+=("--rm") + LINK_CONTAINER_DOCKER_ARGS+=("--link") + LINK_CONTAINER_DOCKER_ARGS+=("$SERVICE_NAME:$network_alias") + + [[ -f "$SERVICE_ROOT/SERVICE_MEMORY" ]] && SERVICE_MEMORY="$(cat "$SERVICE_ROOT/SERVICE_MEMORY")" + if [[ -n "$SERVICE_MEMORY" ]]; then + DOCKER_ARGS+=("--memory=${SERVICE_MEMORY}m") + fi + + [[ -f "$SERVICE_ROOT/SHM_SIZE" ]] && SERVICE_SHM_SIZE="$(cat "$SERVICE_ROOT/SHM_SIZE")" + if [[ -n "$SERVICE_SHM_SIZE" ]]; then + DOCKER_ARGS+=("--shm-size=${SERVICE_SHM_SIZE}") + fi + + [[ -f "$SERVICE_ROOT/IMAGE" ]] && PLUGIN_IMAGE="$(cat "$SERVICE_ROOT/IMAGE")" + [[ -f "$SERVICE_ROOT/IMAGE_VERSION" ]] && PLUGIN_IMAGE_VERSION="$(cat "$SERVICE_ROOT/IMAGE_VERSION")" + + local network="$(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "initial-network")" + if [[ -n "$network" ]]; then + DOCKER_ARGS+=("--network=${network}") + DOCKER_ARGS+=("--network-alias=${network_alias}") + LINK_CONTAINER_DOCKER_ARGS+=("--network=${network}") + fi + + # shellcheck disable=SC2086 + suppress_output "$DOCKER_BIN" container create "${DOCKER_ARGS[@]}" "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" $CONFIG_OPTIONS + + if [[ -n "$(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-create-network")" ]]; then + dokku_log_verbose_quiet "Connecting to networks after container create" + while read -r line || [[ -n "$line" ]]; do + dokku_log_verbose_quiet "- $line" + "$DOCKER_BIN" network connect --alias "$network_alias" "$line" "$SERVICE_NAME" + done < <(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-create-network" | tr "," "\n") + fi + suppress_output "$DOCKER_BIN" container start "$(cat "$SERVICE_ROOT/ID")" + service_port_reconcile_status "$SERVICE" + + if [[ -n "$(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-start-network")" ]]; then + dokku_log_verbose_quiet "Connecting to networks after container start" + while read -r line || [[ -n "$line" ]]; do + dokku_log_verbose_quiet "- $line" + "$DOCKER_BIN" network connect --alias "$network_alias" "$line" "$SERVICE_NAME" + done < <(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-start-network" | tr "," "\n") + fi + + dokku_log_verbose_quiet "Waiting for container to be ready" + if ! suppress_output "$DOCKER_BIN" container run "${LINK_CONTAINER_DOCKER_ARGS[@]}" "$PLUGIN_WAIT_IMAGE" -c "$network_alias:$PLUGIN_DATASTORE_WAIT_PORT"; then + dokku_log_info2_quiet "Start of $SERVICE container output" + dokku_container_log_verbose_quiet "$SERVICE_NAME" + dokku_log_info2_quiet "End of $SERVICE container output" + return 1 + fi + + dokku_log_verbose_quiet "Creating container database" + "$DOCKER_BIN" container exec "$SERVICE_NAME" su - postgres -c "createdb -E utf8 $DATABASE_NAME" 2>/dev/null || dokku_log_verbose_quiet 'Already exists' + + dokku_log_verbose_quiet "Securing connection to database" + service_pause "$SERVICE" >/dev/null + "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scripts/create_ssl_certs.sh" "$SERVICE_ROOT" &>/dev/null + "$DOCKER_BIN" container run --rm -i -v "$SERVICE_HOST_ROOT/data:/var/lib/postgresql/data" -v "$SERVICE_HOST_ROOT/certs:/var/lib/postgresql/certs" "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" bash -s <"$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scripts/enable_ssl.sh" &>/dev/null + rm -rf "$SERVICE_HOST_ROOT/certs" + + suppress_output "$DOCKER_BIN" container start "$(cat "$SERVICE_ROOT/ID")" + service_port_reconcile_status "$SERVICE" + + dokku_log_info2 "$PLUGIN_SERVICE container created: $SERVICE" + service_info "$SERVICE" +} + +service_export() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local DATABASE_NAME="$(get_database_name "$SERVICE")" + local PASSWORD="$(service_password "$SERVICE")" + + [[ -n $SSH_TTY ]] && stty -opost + "$DOCKER_BIN" container exec "$SERVICE_NAME" env PGPASSWORD="$PASSWORD" pg_dump -Fc --no-acl --no-owner -h localhost -U postgres -w "$DATABASE_NAME" + status=$? + [[ -n $SSH_TTY ]] && stty opost + exit $status +} + +service_import() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_HOST_ROOT="$PLUGIN_DATA_HOST_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local DATABASE_NAME="$(get_database_name "$SERVICE")" + local PASSWORD="$(service_password "$SERVICE")" + + if [[ -t 0 ]]; then + dokku_log_fail "No data provided on stdin." + fi + "$DOCKER_BIN" container exec -i "$SERVICE_NAME" env PGPASSWORD="$PASSWORD" pg_restore -h localhost -cO --if-exists -d "$DATABASE_NAME" -U postgres -w +} + +service_start() { + local SERVICE="$1" + local QUIET="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local ID=$("$DOCKER_BIN" container ps -aq --no-trunc --filter "status=running" --filter "name=^/$SERVICE_NAME$") || true + if [[ -n $ID ]]; then + [[ -z $QUIET ]] && dokku_log_warn "Service is already started" + if [[ ! -f "$SERVICE_ROOT/ID" ]] || [[ "$(cat "$SERVICE_ROOT/ID")" != "$ID" ]]; then + [[ -z $QUIET ]] && dokku_log_warn "Updating local container ID" + echo "$ID" >"$SERVICE_ROOT/ID" + fi + return 0 + fi + + dokku_log_info2_quiet "Starting container" + local PREVIOUS_ID=$("$DOCKER_BIN" container ps -aq --no-trunc --filter "status=exited" --filter "name=^/$SERVICE_NAME$") || true + local PASSWORD="$(service_password "$SERVICE")" + + if [[ -n $PREVIOUS_ID ]]; then + "$DOCKER_BIN" container start "$PREVIOUS_ID" >/dev/null + service_port_reconcile_status "$SERVICE" + dokku_log_info2 "Container started" + elif service_image_exists "$SERVICE" && [[ -n "$PASSWORD" ]]; then + service_create_container "$SERVICE" + else + if ! service_image_exists "$SERVICE"; then + [[ -f "$SERVICE_ROOT/IMAGE" ]] && PLUGIN_IMAGE="$(cat "$SERVICE_ROOT/IMAGE")" + [[ -f "$SERVICE_ROOT/IMAGE_VERSION" ]] && PLUGIN_IMAGE_VERSION="$(cat "$SERVICE_ROOT/IMAGE_VERSION")" + dokku_log_verbose_quiet "Missing image $PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION for $SERVICE" + else + dokku_log_verbose_quiet "Neither container nor valid configuration exists for $SERVICE" + fi + fi +} + +service_url() { + local SERVICE="$1" + local SERVICE_DNS_HOSTNAME="$(service_dns_hostname "$SERVICE")" + local DATABASE_NAME="$(get_database_name "$SERVICE")" + local PASSWORD="$(service_password "$SERVICE")" + echo "$PLUGIN_SCHEME://postgres:$PASSWORD@$SERVICE_DNS_HOSTNAME:${PLUGIN_DATASTORE_PORTS[0]}/$DATABASE_NAME" +} diff --git a/plugins/postgres/help-functions b/plugins/postgres/help-functions new file mode 100755 index 000000000..3ff6dc9c4 --- /dev/null +++ b/plugins/postgres/help-functions @@ -0,0 +1,320 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +export SUBCOMMAND_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/subcommands" + +fn-help() { + declare CMD="$1" + local cmd EXIT_CODE + + if [[ "$CMD" == "help" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:help" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:default" ]]; then + fn-help-all "$@" + exit 0 + fi + + pushd "$SUBCOMMAND_ROOT" >/dev/null 2>&1 + for cmd in *; do + if [[ "$CMD" == "${PLUGIN_COMMAND_PREFIX}:$cmd" ]]; then + "$SUBCOMMAND_ROOT/$cmd" "$@" + EXIT_CODE="$?" + exit "$EXIT_CODE" + fi + done + popd >/dev/null 2>&1 + + exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +} + +fn-help-all() { + declare CMD="$1" SUBCOMMAND="$2" + local CMD_OUTPUT BLUE BOLD FULL_OUTPUT NORMAL + FULL_OUTPUT=true + + if [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:help" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:default" ]]; then + BOLD="$(fn-help-fancy-tput bold)" + NORMAL="$(fn-help-fancy-color "\033[m")" + BLUE="$(fn-help-fancy-color "\033[0;34m")" + CYAN="$(fn-help-fancy-color "\033[1;36m")" + if [[ -n "$SUBCOMMAND" ]] && [[ "$SUBCOMMAND" != "--all" ]]; then + fn-help-contents-subcommand "$SUBCOMMAND" "$FULL_OUTPUT" + return "$?" + fi + + echo -e "${BOLD}usage${NORMAL}: dokku ${PLUGIN_COMMAND_PREFIX}[:COMMAND]" + echo '' + echo -e "${BOLD}List your $PLUGIN_COMMAND_PREFIX services.${NORMAL}" + echo '' + echo -e "${BLUE}Example:${NORMAL}" + echo '' + echo " \$ dokku $PLUGIN_COMMAND_PREFIX:list" + echo '' + fn-help-list-example | column -c5 -t -s, + echo '' + echo -e "dokku ${BOLD}${PLUGIN_COMMAND_PREFIX}${NORMAL} commands: (get help with ${CYAN}dokku ${PLUGIN_COMMAND_PREFIX}:help SUBCOMMAND${NORMAL})" + echo '' + fn-help-contents | sort | column -c2 -t -s, + echo '' + elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then + fn-help-contents + else + cat </dev/null 2>&1 + for cmd in *; do + fn-help-contents-subcommand "$cmd" || true + done +} + +fn-help-contents-subcommand() { + declare SUBCOMMAND="$1" FULL_OUTPUT="$2" + local HELP_TMPDIR=$(mktemp -d --tmpdir) + local UNCLEAN_FILE="${HELP_TMPDIR}/cmd-unclean" CLEAN_FILE="${HELP_TMPDIR}/cmd-clean" + local BOLD CMD_OUTPUT CYAN EXAMPLE LIGHT_GRAY NORMAL + trap 'rm -rf "$HELP_TMPDIR" > /dev/null' RETURN INT TERM EXIT + + rm -rf "$UNCLEAN_FILE" "$CLEAN_FILE" + cat "$SUBCOMMAND_ROOT/$SUBCOMMAND" >"$UNCLEAN_FILE" + + fn-help-subcommand-sanitize "$UNCLEAN_FILE" "$CLEAN_FILE" + if ! is_implemented_command "$SUBCOMMAND"; then + return 1 + fi + + args="$(fn-help-subcommand-args "$CLEAN_FILE" "$FULL_OUTPUT")" + SUBCOMMAND=":$SUBCOMMAND" + [[ "$SUBCOMMAND" == ":default" ]] && SUBCOMMAND="" + cmd_line="$(echo -e "${SUBCOMMAND} ${args}" | sed -e 's/[[:space:]]*$//')" + desc="$(grep desc "$CLEAN_FILE" | head -1)" + eval "$desc" + + BLUE="$(fn-help-fancy-color "\033[0;34m")" + BOLD="$(fn-help-fancy-tput bold)" + CYAN="$(fn-help-fancy-color "\033[1;36m")" + NORMAL="$(fn-help-fancy-color "\033[m")" + LIGHT_GRAY="$(fn-help-fancy-color "\033[2;37m")" + LIGHT_RED="$(fn-help-fancy-color "\033[1;31m")" + CMD_OUTPUT="$(echo -e " ${PLUGIN_COMMAND_PREFIX}${cmd_line}, ${LIGHT_GRAY}${desc}${NORMAL}")" + if [[ "$FULL_OUTPUT" != "true" ]]; then + echo "$CMD_OUTPUT" + return 0 + fi + + echo -e "${BOLD}usage:${NORMAL} dokku ${PLUGIN_COMMAND_PREFIX}${cmd_line}" + echo '' + echo -e "${BOLD}${desc}${NORMAL}" + echo '' + + ARGS="$(fn-help-subcommand-list-args "$CLEAN_FILE")" + if [[ -n "$ARGS" ]]; then + echo -e "${CYAN}arguments:${NORMAL}" + echo '' + echo "$ARGS" | column -c2 -t -s, + echo '' + fi + + FLAGS="$(fn-help-subcommand-list-flags "$CLEAN_FILE")" + if [[ -n "$FLAGS" ]]; then + echo -e "${BLUE}flags:${NORMAL}" + echo '' + echo "$FLAGS" | column -c2 -t -s, + echo '' + fi + + EXAMPLE="$(fn-help-subcommand-example "$CLEAN_FILE")" + if [[ -n "$EXAMPLE" ]]; then + echo -e "${LIGHT_RED}examples:${NORMAL}" + echo '' + echo "$EXAMPLE" + echo '' + fi + + return 0 +} + +fn-help-fancy-tput() { + declare desc="a wrapper around tput" + + if [[ -n "$DOKKU_NO_COLOR" ]] || [[ "$TERM" == "unknown" ]] || [[ "$TERM" == "dumb" ]]; then + return + fi + + tput "$@" +} + +fn-help-fancy-color() { + declare desc="a wrapper around colors" + + if [[ -n "$DOKKU_NO_COLOR" ]] || [[ "$TERM" == "unknown" ]] || [[ "$TERM" == "dumb" ]]; then + return + fi + + echo "$@" +} + +fn-help-list-example() { + # shellcheck disable=SC2034 + declare desc="return $PLUGIN_COMMAND_PREFIX plugin help content" + cat <* ]] && line="\n ${BOLD}${line}${NORMAL}" + # shellcheck disable=SC2001 + [[ "$line" == " "* ]] && line=" ${OTHER_GRAY}$(echo "$line" | sed -e 's/^[[:space:]]*//')${NORMAL}" + echo -e "${NEWLINE}${line}" + LAST_LINE="sentence" + NEWLINE="\n" + fi + done +} + +fn-help-subcommand-list-args() { + declare FUNC_FILE="$1" + local EXAMPLE LIGHT_GRAY NORMAL + + FLAGS=$(grep "#A" "$FUNC_FILE" | cut -d'A' -f2- | sed -e 's/^[[:space:]]*//' || true) + if [[ -z "$FLAGS" ]]; then + return 0 + fi + + NORMAL="$(fn-help-fancy-color "\033[m")" + LIGHT_GRAY="$(fn-help-fancy-color "\033[2;37m")" + + _fn-help-apply-shell-expansion "$FLAGS" | while read -r line; do + echo -e "$(echo "$line" | cut -d',' -f1),${LIGHT_GRAY}$(echo "$line" | cut -d',' -f2-)${NORMAL}" + done +} + +fn-help-subcommand-list-flags() { + declare FUNC_FILE="$1" + local EXAMPLE LIGHT_GRAY NORMAL + + FLAGS=$(grep "#F" "$FUNC_FILE" | cut -d'F' -f2- | sed -e 's/^[[:space:]]*//' || true) + if [[ -z "$FLAGS" ]]; then + return 0 + fi + + NORMAL="$(fn-help-fancy-color "\033[m")" + LIGHT_GRAY="$(fn-help-fancy-color "\033[2;37m")" + + _fn-help-apply-shell-expansion "$FLAGS" | while read -r line; do + echo -e "$(echo "$line" | cut -d',' -f1),${LIGHT_GRAY}$(echo "$line" | cut -d',' -f2-)${NORMAL}" + done +} + +fn-help-subcommand-sanitize() { + declare FUNC_FILE="$1" OUTGOING_FUNC_FILE="$2" + local FUNCTION_FOUND=false + local IFS OIFS + + touch "$OUTGOING_FUNC_FILE" + + OIFS="$IFS" + IFS=, + while read -r p; do + IFS="$OIFS" + if [[ "$p" == *"-cmd \"\$@\""* ]] || [[ "$p" == "" ]]; then + continue + fi + + if [[ "$FUNCTION_FOUND" == true ]]; then + echo "$p" >>"$OUTGOING_FUNC_FILE" + continue + fi + + if [[ "$p" == *"()"* ]]; then + FUNCTION_FOUND=true + echo "$p" >>"$OUTGOING_FUNC_FILE" + continue + fi + done <"$FUNC_FILE" +} + +_fn-help-apply-shell-expansion() { + declare desc="expand environment variables for a shell command" + declare data="$1" + declare delimiter="__apply_shell_expansion_delimiter__" + declare command="cat <<$delimiter"$'\n'"$data"$'\n'"$delimiter" + eval "$command" +} diff --git a/plugins/postgres/install b/plugins/postgres/install new file mode 100755 index 000000000..f8fc40418 --- /dev/null +++ b/plugins/postgres/install @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-install() { + pull-docker-image() { + declare IMAGE="$1" + if [[ "$PLUGIN_DISABLE_PULL" == "true" ]]; then + echo " ! ${PLUGIN_DISABLE_PULL_VARIABLE} environment variable detected. Not running pull command." 1>&2 + echo " ! docker image pull ${IMAGE}" 1>&2 + return + fi + if [[ "$("$DOCKER_BIN" image ls -q "${IMAGE}" 2>/dev/null)" == "" ]]; then + "$DOCKER_BIN" image pull "${IMAGE}" + fi + } + + fn-plugin-property-setup "$PLUGIN_COMMAND_PREFIX" + pull-docker-image "${PLUGIN_IMAGE}:${PLUGIN_IMAGE_VERSION}" + pull-docker-image "$PLUGIN_BUSYBOX_IMAGE" + pull-docker-image "$PLUGIN_AMBASSADOR_IMAGE" + pull-docker-image "$PLUGIN_S3BACKUP_IMAGE" + pull-docker-image "$PLUGIN_WAIT_IMAGE" + + mkdir -p "$PLUGIN_DATA_ROOT" || echo "Failed to create $PLUGIN_SERVICE data directory" + chown "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "$PLUGIN_DATA_ROOT" + + mkdir -p "$PLUGIN_CONFIG_ROOT" || echo "Failed to create $PLUGIN_SERVICE config directory" + chown "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "$PLUGIN_CONFIG_ROOT" + + rm -f "/etc/sudoers.d/dokku-${PLUGIN_COMMAND_PREFIX}*" + _SUDOERS_FILE="/etc/sudoers.d/dokku-${PLUGIN_COMMAND_PREFIX}" + + touch "$_SUDOERS_FILE" + cat >"$_SUDOERS_FILE" <"$SERVICE_ROOT/IMAGE" + echo "${image##*:}" >"$SERVICE_ROOT/IMAGE_VERSION" + fi + fi + + chown "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "$SERVICE_ROOT/IMAGE" "$SERVICE_ROOT/IMAGE_VERSION" + + if [[ -f "$SERVICE_ROOT/${PLUGIN_VARIABLE}_CONFIG_OPTIONS" ]]; then + mv "$SERVICE_ROOT/${PLUGIN_VARIABLE}_CONFIG_OPTIONS" "$SERVICE_ROOT/CONFIG_OPTIONS" + chown "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "$SERVICE_ROOT/CONFIG_OPTIONS" + fi + done +} + +plugin-install "$@" diff --git a/plugins/postgres/plugin.toml b/plugins/postgres/plugin.toml new file mode 100644 index 000000000..9e0c5d367 --- /dev/null +++ b/plugins/postgres/plugin.toml @@ -0,0 +1,4 @@ +[plugin] +description = "dokku postgres service plugin" +version = "1.44.0" +[plugin.config] diff --git a/plugins/postgres/post-app-clone-setup b/plugins/postgres/post-app-clone-setup new file mode 100755 index 000000000..c5d762d2c --- /dev/null +++ b/plugins/postgres/post-app-clone-setup @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-post-app-clone-setup() { + declare OLD_APP_NAME="$1" NEW_APP_NAME="$2" + + for SERVICE in $(fn-services-list false); do + if in_links_file "$SERVICE" "$OLD_APP_NAME"; then + add_to_links_file "$SERVICE" "$NEW_APP_NAME" + fi + done +} + +plugin-post-app-clone-setup "$@" diff --git a/plugins/postgres/post-app-rename-setup b/plugins/postgres/post-app-rename-setup new file mode 100755 index 000000000..6c1e16b16 --- /dev/null +++ b/plugins/postgres/post-app-rename-setup @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-post-app-rename-setup() { + declare OLD_APP_NAME="$1" NEW_APP_NAME="$2" + + for SERVICE in $(fn-services-list false); do + if in_links_file "$SERVICE" "$OLD_APP_NAME"; then + add_to_links_file "$SERVICE" "$NEW_APP_NAME" + fi + done +} + +plugin-post-app-rename-setup "$@" diff --git a/plugins/postgres/pre-delete b/plugins/postgres/pre-delete new file mode 100755 index 000000000..d31546478 --- /dev/null +++ b/plugins/postgres/pre-delete @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +APP="$1" +for SERVICE in $(fn-services-list false); do + [[ -n "$SERVICE" ]] || continue + dokku_log_verbose_quiet "Unlinking from $SERVICE" + remove_from_links_file "$(basename "$SERVICE")" "$APP" +done diff --git a/plugins/postgres/pre-restore b/plugins/postgres/pre-restore new file mode 100755 index 000000000..c3322028f --- /dev/null +++ b/plugins/postgres/pre-restore @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-pre-restore() { + declare SCHEDULER="$1" APP="$2" + local status + + if [[ "$SCHEDULER" != "docker-local" ]]; then + return + fi + + for SERVICE in $(fn-services-list false); do + if ! in_links_file "$SERVICE" "$APP"; then + continue + fi + + status="$(service_status "$SERVICE")" + if [[ "$status" == "running" ]]; then + continue + fi + + if [[ "$status" == "restarting" ]]; then + dokku_log_warn "$PLUGIN_SERVICE service $SERVICE is restarting and may cause issues with linked app $APP" + continue + fi + + dokku_log_warn "$PLUGIN_SERVICE service $SERVICE is not running, issuing service start" + service_start "$SERVICE" + done +} + +plugin-pre-restore "$@" diff --git a/plugins/postgres/pre-start b/plugins/postgres/pre-start new file mode 100755 index 000000000..b3d83a8e4 --- /dev/null +++ b/plugins/postgres/pre-start @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-pre-start() { + declare APP="$1" + local status + + for SERVICE in $(fn-services-list false); do + if ! in_links_file "$SERVICE" "$APP"; then + continue + fi + + status="$(service_status "$SERVICE")" + if [[ "$status" == "running" ]]; then + continue + fi + + if [[ "$status" == "restarting" ]]; then + dokku_log_warn "$PLUGIN_SERVICE service $SERVICE is restarting and may cause issues with linked app $APP" + continue + fi + + dokku_log_warn "$PLUGIN_SERVICE service $SERVICE is not running, issuing service start" + service_start "$SERVICE" + done +} + +plugin-pre-start "$@" diff --git a/plugins/postgres/scripts/create_ssl_certs.sh b/plugins/postgres/scripts/create_ssl_certs.sh new file mode 100755 index 000000000..ecca0256a --- /dev/null +++ b/plugins/postgres/scripts/create_ssl_certs.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +set -e + +postgres_service_dir="$1" + +cd "$postgres_service_dir" +mkdir certs && cd certs +openssl req -new -newkey rsa:4096 -x509 -days 365000 -nodes -out server.crt -keyout server.key -batch diff --git a/plugins/postgres/scripts/enable_ssl.sh b/plugins/postgres/scripts/enable_ssl.sh new file mode 100755 index 000000000..d8ccfd5b9 --- /dev/null +++ b/plugins/postgres/scripts/enable_ssl.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +set -e + +cd /var/lib/postgresql/data + +cp ../certs/* . +chown postgres:postgres server.key +chmod 600 server.key + +sed -i "s/^#ssl = off/ssl = on/" postgresql.conf +sed -i "s/^#ssl_ciphers =.*/ssl_ciphers = 'AES256+EECDH:AES256+EDH'/" postgresql.conf diff --git a/plugins/postgres/service-list b/plugins/postgres/service-list new file mode 100755 index 000000000..6fc54e90d --- /dev/null +++ b/plugins/postgres/service-list @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-service-list() { + declare desc="allows listing all services for use by other dokku plugins" + declare SERVICE_TYPE="$1" + + if [[ -n "$SERVICE_TYPE" ]] && [[ "$SERVICE_TYPE" != "$PLUGIN_COMMAND_PREFIX" ]]; then + return + fi + + for service in $(fn-services-list false); do + echo "$PLUGIN_COMMAND_PREFIX:$service" + done +} + +plugin-service-list "$@" diff --git a/plugins/postgres/subcommands/app-links b/plugins/postgres/subcommands/app-links new file mode 100755 index 000000000..6b0a0eb04 --- /dev/null +++ b/plugins/postgres/subcommands/app-links @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-app-links-cmd() { + #E list all $PLUGIN_COMMAND_PREFIX services that are linked to the 'playground' app. + #E dokku $PLUGIN_COMMAND_PREFIX:app-links playground + #A app, app to run command against + declare desc="list all $PLUGIN_SERVICE service links for a given app" + local cmd="$PLUGIN_COMMAND_PREFIX:app-links" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare APP="$1" + APP=${APP:="$DOKKU_APP_NAME"} + + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + verify_app_name "$APP" + service_app_links "$APP" +} + +service-app-links-cmd "$@" diff --git a/plugins/postgres/subcommands/backup b/plugins/postgres/subcommands/backup new file mode 100755 index 000000000..354ed9b67 --- /dev/null +++ b/plugins/postgres/subcommands/backup @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-cmd() { + #E backup the 'lollipop' service to the 'my-s3-bucket' bucket on AWS + #E dokku $PLUGIN_COMMAND_PREFIX:backup lollipop my-s3-bucket --use-iam + #E restore a backup file (assuming it was extracted via 'tar -xf backup.tgz') + #E dokku $PLUGIN_COMMAND_PREFIX:import lollipop < backup-folder/export + #F -u|--use-iam, use the IAM profile associated with the current server + #A service, service to run command against + #A bucket-name, name of the s3 bucket to upload backups to + declare desc="create a backup of the $PLUGIN_SERVICE service to an existing s3 bucket" + local cmd="$PLUGIN_COMMAND_PREFIX:backup" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" BUCKET_NAME="$2" USE_IAM_OPTIONAL_FLAG="$3" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$BUCKET_NAME" ]] && dokku_log_fail "Please specify an aws bucket for the backup" + verify_service_name "$SERVICE" + service_backup "$SERVICE" "$BUCKET_NAME" "$USE_IAM_OPTIONAL_FLAG" +} + +service-backup-cmd "$@" diff --git a/plugins/postgres/subcommands/backup-auth b/plugins/postgres/subcommands/backup-auth new file mode 100755 index 000000000..050397bcc --- /dev/null +++ b/plugins/postgres/subcommands/backup-auth @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-auth-cmd() { + #E setup s3 backup authentication + #E dokku $PLUGIN_COMMAND_PREFIX:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY + #E setup s3 backup authentication with different region + #E dokku $PLUGIN_COMMAND_PREFIX:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION + #E setup s3 backup authentication with different signature version and endpoint + #E dokku $PLUGIN_COMMAND_PREFIX:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION AWS_SIGNATURE_VERSION ENDPOINT_URL + #E more specific example for minio auth + #E dokku $PLUGIN_COMMAND_PREFIX:backup-auth lollipop MINIO_ACCESS_KEY_ID MINIO_SECRET_ACCESS_KEY us-east-1 s3v4 https://YOURMINIOSERVICE + #A service, service to run command against + #A access-key-id, an amazon AWS_ACCESS_KEY_ID + #A aws-secret-access-key, an amazon AWS_SECRET_ACCESS_KEY + #A aws-default-region, (optional) a valid amazon S3 region + #A aws-signature-version, (optional) the AWS signature version to use when signing S3 requests + #A endpoint-url, (optional) an aws endpoint to upload to + declare desc="set up authentication for backups on the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-auth" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" AWS_ACCESS_KEY_ID="$2" AWS_SECRET_ACCESS_KEY="$3" AWS_DEFAULT_REGION="$4" AWS_SIGNATURE_VERSION="$5" ENDPOINT_URL="$6" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$AWS_ACCESS_KEY_ID" ]] && dokku_log_fail "Please specify an aws access key id" + [[ -z "$AWS_SECRET_ACCESS_KEY" ]] && dokku_log_fail "Please specify an aws secret access key" + verify_service_name "$SERVICE" + service_backup_auth "$SERVICE" "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY" "$AWS_DEFAULT_REGION" "$AWS_SIGNATURE_VERSION" "$ENDPOINT_URL" +} + +service-backup-auth-cmd "$@" diff --git a/plugins/postgres/subcommands/backup-deauth b/plugins/postgres/subcommands/backup-deauth new file mode 100755 index 000000000..19cbe8aff --- /dev/null +++ b/plugins/postgres/subcommands/backup-deauth @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-deauth-cmd() { + #E remove s3 authentication + #E dokku $PLUGIN_COMMAND_PREFIX:backup-deauth lollipop + #A service, service to run command against + declare desc="remove backup authentication for the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-deauth" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_backup_deauth "$SERVICE" +} + +service-backup-deauth-cmd "$@" diff --git a/plugins/postgres/subcommands/backup-schedule b/plugins/postgres/subcommands/backup-schedule new file mode 100755 index 000000000..594c74846 --- /dev/null +++ b/plugins/postgres/subcommands/backup-schedule @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-schedule-cmd() { + #E schedule a backup + #E > 'schedule' is a crontab expression, eg. "0 3 * * *" for each day at 3am + #E dokku $PLUGIN_COMMAND_PREFIX:backup-schedule lollipop "0 3 * * *" my-s3-bucket + #E schedule a backup and authenticate via iam + #E dokku $PLUGIN_COMMAND_PREFIX:backup-schedule lollipop "0 3 * * *" my-s3-bucket --use-iam + #F -u|--use-iam, use the IAM profile associated with the current server + #A service, service to run command against + #A schedule, a cron schedule to run backups on + #A bucket-name, name of the s3 bucket to upload backups to + declare desc="schedule a backup of the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-schedule" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" SCHEDULE="$2" BUCKET_NAME="$3" USE_IAM_OPTIONAL_FLAG="$4" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$SCHEDULE" ]] && dokku_log_fail "Please specify a schedule for the backup" + [[ -z "$BUCKET_NAME" ]] && dokku_log_fail "Please specify an aws bucket for the backup" + verify_service_name "$SERVICE" + service_backup_schedule "$SERVICE" "$SCHEDULE" "$BUCKET_NAME" "$USE_IAM_OPTIONAL_FLAG" +} + +service-backup-schedule-cmd "$@" diff --git a/plugins/postgres/subcommands/backup-schedule-cat b/plugins/postgres/subcommands/backup-schedule-cat new file mode 100755 index 000000000..56a388ac5 --- /dev/null +++ b/plugins/postgres/subcommands/backup-schedule-cat @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-schedule-cat-cmd() { + #E cat the contents of the configured backup cronfile for the service + #E dokku $PLUGIN_COMMAND_PREFIX:backup-schedule-cat lollipop + #A service, service to run command against + declare desc="cat the contents of the configured backup cronfile for the service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-schedule-cat" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_backup_schedule_cat "$SERVICE" +} + +service-backup-schedule-cat-cmd "$@" diff --git a/plugins/postgres/subcommands/backup-set-encryption b/plugins/postgres/subcommands/backup-set-encryption new file mode 100755 index 000000000..27cb86e80 --- /dev/null +++ b/plugins/postgres/subcommands/backup-set-encryption @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-set-encryption-cmd() { + #E set the GPG-compatible passphrase for encrypting backups for backups + #E dokku $PLUGIN_COMMAND_PREFIX:backup-set-encryption lollipop + #A service, service to run command against + #A passphrase, a GPG-compatible passphrase + declare desc="set encryption for all future backups of $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-set-encryption" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" PASSPHRASE="$2" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$PASSPHRASE" ]] && dokku_log_fail "Please specify a GPG backup passphrase" + verify_service_name "$SERVICE" + service_backup_set_encryption "$SERVICE" "$PASSPHRASE" +} + +service-backup-set-encryption-cmd "$@" diff --git a/plugins/postgres/subcommands/backup-set-public-key-encryption b/plugins/postgres/subcommands/backup-set-public-key-encryption new file mode 100755 index 000000000..d058bb203 --- /dev/null +++ b/plugins/postgres/subcommands/backup-set-public-key-encryption @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-set-public-key-encryption-cmd() { + #E set the GPG Public Key for encrypting backups + #E dokku $PLUGIN_COMMAND_PREFIX:backup-set-public-key-encryption lollipop + #A service, service to run command against + #A public-key-id, a GPG Public Key ID (or fingerprint) to use for encryption. Must be uploaded to the GPG keyserver beforehand. + declare desc="set GPG Public Key encryption for all future backups of $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-set-public-key-encryption" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" PUBLIC_KEY_ID="$2" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$PUBLIC_KEY_ID" ]] && dokku_log_fail "Please specify a valid GPG Public Key ID (or fingerprint)" + verify_service_name "$SERVICE" + service_backup_set_public_key_encryption "$SERVICE" "$PUBLIC_KEY_ID" +} + +service-backup-set-public-key-encryption-cmd "$@" diff --git a/plugins/postgres/subcommands/backup-unschedule b/plugins/postgres/subcommands/backup-unschedule new file mode 100755 index 000000000..874a22a07 --- /dev/null +++ b/plugins/postgres/subcommands/backup-unschedule @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-unschedule-cmd() { + #E remove the scheduled backup from cron + #E dokku $PLUGIN_COMMAND_PREFIX:backup-unschedule lollipop + #A service, service to run command against + declare desc="unschedule the backup of the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-unschedule" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_backup_unschedule "$SERVICE" +} + +service-backup-unschedule-cmd "$@" diff --git a/plugins/postgres/subcommands/backup-unset-encryption b/plugins/postgres/subcommands/backup-unset-encryption new file mode 100755 index 000000000..c046a5511 --- /dev/null +++ b/plugins/postgres/subcommands/backup-unset-encryption @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-unset-encryption-cmd() { + #E unset the GPG encryption passphrase for backups + #E dokku $PLUGIN_COMMAND_PREFIX:backup-unset-encryption lollipop + #A service, service to run command against + declare desc="unset encryption for future backups of the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-unset-encryption" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_backup_unset_encryption "$SERVICE" +} + +service-backup-unset-encryption-cmd "$@" diff --git a/plugins/postgres/subcommands/backup-unset-public-key-encryption b/plugins/postgres/subcommands/backup-unset-public-key-encryption new file mode 100755 index 000000000..8e0352f8d --- /dev/null +++ b/plugins/postgres/subcommands/backup-unset-public-key-encryption @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-unset-public-key-encryption-cmd() { + #E unset the GPG Public Key encryption for backups + #E dokku $PLUGIN_COMMAND_PREFIX:backup-unset-public-key-encryption lollipop + #A service, service to run command against + declare desc="unset GPG Public Key encryption for future backups of the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-unset-public-key-encryption" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" # TODO: [22.03.2024 by Mykola] + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_backup_unset_public_key_encryption "$SERVICE" # TODO: [22.03.2024 by Mykola] +} + +service-backup-unset-encryption-cmd "$@" diff --git a/plugins/postgres/subcommands/clone b/plugins/postgres/subcommands/clone new file mode 100755 index 000000000..b9d99a330 --- /dev/null +++ b/plugins/postgres/subcommands/clone @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-clone-cmd() { + #E you can clone an existing service to a new one + #E dokku $PLUGIN_COMMAND_PREFIX:clone lollipop lollipop-2 + #A service, service to run command against + #A new-service, name of new service + #F -c|--config-options "--args --go=here", extra arguments to pass to the container create command + #F -C|--custom-env "USER=alpha;HOST=beta", semi-colon delimited environment variables to start the service with + #F -i|--image IMAGE, the image name to start the service with + #F -I|--image-version IMAGE_VERSION, the image version to start the service with + #F -m|--memory MEMORY, container memory limit in megabytes (default: unlimited) + #F -N|--initial-network INITIAL_NETWORK, the initial network to attach the service to + #F -p|--password PASSWORD, override the user-level service password + #F -P|--post-create-network NETWORKS, a comma-separated list of networks to attach the service container to after service creation + #F -r|--root-password PASSWORD, override the root-level service password + #F -S|--post-start-network NETWORKS, a comma-separated list of networks to attach the service container to after service start + #F -s|--shm-size SHM_SIZE, override shared memory size for $PLUGIN_COMMAND_PREFIX docker container + declare desc="create container then copy data from into " + local cmd="$PLUGIN_COMMAND_PREFIX:clone" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" NEW_SERVICE="$2" CLONE_FLAGS_LIST=("${@:3}") + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$NEW_SERVICE" ]] && dokku_log_fail "Please specify a name for the new service" + verify_service_name "$SERVICE" + if service_exists "$NEW_SERVICE"; then + dokku_log_fail "Invalid service name $NEW_SERVICE. Verify the service name is not already in use." + fi + + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ID="$(cat "$SERVICE_ROOT/ID")" + is_container_status "$ID" "Running" || dokku_log_fail "Service ${SERVICE} container is not running" + + PLUGIN_IMAGE=$(service_version "$SERVICE" | grep -o "^.*:" | sed -r "s/://g") + PLUGIN_IMAGE_VERSION=$(service_version "$SERVICE" | grep -o ":.*$" | sed -r "s/://g") + + service_parse_args "${@:3}" + + dokku_log_info2 "Cloning $SERVICE to $NEW_SERVICE @ $PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" + service_create "$NEW_SERVICE" "${@:3}" + dokku_log_info1 "Copying data from $SERVICE to $NEW_SERVICE" + service_export "$SERVICE" | service_import "$NEW_SERVICE" >/dev/null 2>&1 || true + dokku_log_info2 "Done" +} + +service-clone-cmd "$@" diff --git a/plugins/postgres/subcommands/connect b/plugins/postgres/subcommands/connect new file mode 100755 index 000000000..ad6abcaed --- /dev/null +++ b/plugins/postgres/subcommands/connect @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-connect-cmd() { + #E connect to the service via the $PLUGIN_COMMAND_PREFIX connection tool + #E > NOTE: disconnecting from ssh while running this command may leave zombie processes due to moby/moby#9098 + #E dokku $PLUGIN_COMMAND_PREFIX:connect lollipop + #A service, service to run command against + declare desc="connect to the service via the $PLUGIN_COMMAND_PREFIX connection tool" + local cmd="$PLUGIN_COMMAND_PREFIX:connect" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_connect "$SERVICE" +} + +service-connect-cmd "$@" diff --git a/plugins/postgres/subcommands/create b/plugins/postgres/subcommands/create new file mode 100755 index 000000000..7112f18f1 --- /dev/null +++ b/plugins/postgres/subcommands/create @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-create-cmd() { + #E create a $PLUGIN_COMMAND_PREFIX service named lollipop + #E dokku $PLUGIN_COMMAND_PREFIX:create lollipop + #E you can also specify the image and image version to use for the service. + #E it *must* be compatible with the ${PLUGIN_IMAGE} image. + #E export ${PLUGIN_VARIABLE}_IMAGE="${PLUGIN_IMAGE}" + #E export ${PLUGIN_VARIABLE}_IMAGE_VERSION="${PLUGIN_IMAGE_VERSION}" + #E dokku $PLUGIN_COMMAND_PREFIX:create lollipop + #E you can also specify custom environment variables to start + #E the ${PLUGIN_COMMAND_PREFIX} service in semicolon-separated form. + #E export ${PLUGIN_VARIABLE}_CUSTOM_ENV="USER=alpha;HOST=beta" + #E dokku $PLUGIN_COMMAND_PREFIX:create lollipop + #A service, service to run command against + #F -c|--config-options "--args --go=here", extra arguments to pass to the container create command + #F -C|--custom-env "USER=alpha;HOST=beta", semi-colon delimited environment variables to start the service with + #F -i|--image IMAGE, the image name to start the service with + #F -I|--image-version IMAGE_VERSION, the image version to start the service with + #F -m|--memory MEMORY, container memory limit in megabytes (default: unlimited) + #F -N|--initial-network INITIAL_NETWORK, the initial network to attach the service to + #F -p|--password PASSWORD, override the user-level service password + #F -P|--post-create-network NETWORKS, a comma-separated list of networks to attach the service container to after service creation + #F -r|--root-password PASSWORD, override the root-level service password + #F -S|--post-start-network NETWORKS, a comma-separated list of networks to attach the service container to after service start + #F -s|--shm-size SHM_SIZE, override shared memory size for $PLUGIN_COMMAND_PREFIX docker container + declare desc="create a $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:create" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" CREATE_FLAGS_LIST=("${@:2}") + + service_create "$SERVICE" "${@:2}" +} + +service-create-cmd "$@" diff --git a/plugins/postgres/subcommands/destroy b/plugins/postgres/subcommands/destroy new file mode 100755 index 000000000..cffe32d0e --- /dev/null +++ b/plugins/postgres/subcommands/destroy @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-destroy-cmd() { + #E destroy the service, it's data, and the running container + #E dokku $PLUGIN_COMMAND_PREFIX:destroy lollipop + #A service, service to run command against + #F -f|--force, force destroy without asking for confirmation + declare desc="delete the $PLUGIN_SERVICE service/data/container if there are no links left" + local cmd="$PLUGIN_COMMAND_PREFIX:destroy" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" FORCE_FLAG="$2" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + LINKS_FILE="$SERVICE_ROOT/LINKS" + SERVICE_HOST_ROOT="$PLUGIN_DATA_HOST_ROOT/$SERVICE" + SERVICE_NAME="$(get_service_name "$SERVICE")" + + [[ -s "$LINKS_FILE" ]] && dokku_log_fail "Cannot delete linked service" + + if [[ "$FORCE_FLAG" == "force" ]] || [[ "$FORCE_FLAG" == "-f" ]] || [[ "$FORCE_FLAG" == "--force" ]]; then + DOKKU_APPS_FORCE_DELETE=1 + fi + if [[ -z "$DOKKU_APPS_FORCE_DELETE" ]]; then + dokku_log_warn "WARNING: Potentially Destructive Action" + dokku_log_warn "This command will destroy $SERVICE $PLUGIN_SERVICE service." + dokku_log_warn "To proceed, type \"$SERVICE\"" + echo "" + + read -rp "> " service_name + if [[ "$service_name" != "$SERVICE" ]]; then + dokku_log_warn "Confirmation did not match $SERVICE. Aborted." + exit 1 + fi + fi + + dokku_log_info2_quiet "Deleting $SERVICE" + plugn trigger service-action pre-delete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" + service_backup_unschedule "$SERVICE" + service_container_rm "$SERVICE" + + dokku_log_verbose_quiet "Removing data" + "$DOCKER_BIN" container run --rm -v "$SERVICE_HOST_ROOT/data:/data" -v "$SERVICE_HOST_ROOT/$PLUGIN_CONFIG_SUFFIX:/config" "$PLUGIN_BUSYBOX_IMAGE" chmod 777 -R /config /data + rm -rf "$SERVICE_ROOT" + + fn-plugin-property-destroy "$PLUGIN_COMMAND_PREFIX" "$SERVICE" + + plugn trigger service-action post-delete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" + dokku_log_info2 "$PLUGIN_SERVICE container deleted: $SERVICE" +} + +service-destroy-cmd "$@" diff --git a/plugins/postgres/subcommands/enter b/plugins/postgres/subcommands/enter new file mode 100755 index 000000000..9062bebe7 --- /dev/null +++ b/plugins/postgres/subcommands/enter @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-enter-cmd() { + #E a bash prompt can be opened against a running service. + #E filesystem changes will not be saved to disk. + #E > NOTE: disconnecting from ssh while running this command may leave zombie processes due to moby/moby#9098 + #E dokku $PLUGIN_COMMAND_PREFIX:enter lollipop + #E you may also run a command directly against the service. + #E filesystem changes will not be saved to disk. + #E dokku $PLUGIN_COMMAND_PREFIX:enter lollipop touch /tmp/test + #A service, service to run command against + declare desc="enter or run a command in a running $PLUGIN_SERVICE service container" + local cmd="$PLUGIN_COMMAND_PREFIX:enter" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + verify_service_name "$SERVICE" + dokku_log_info1_quiet "Filesystem changes may not persist after container restarts" + service_enter "$SERVICE" "${@:2}" +} + +service-enter-cmd "$@" diff --git a/plugins/postgres/subcommands/exists b/plugins/postgres/subcommands/exists new file mode 100755 index 000000000..6fd1bfc9a --- /dev/null +++ b/plugins/postgres/subcommands/exists @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-exists-cmd() { + #E here we check if the lollipop $PLUGIN_COMMAND_PREFIX service exists. + #E dokku $PLUGIN_COMMAND_PREFIX:exists lollipop + #A service, service to run command against + declare desc="check if the $PLUGIN_SERVICE service exists" + local cmd="$PLUGIN_COMMAND_PREFIX:exists" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + dokku_log_info1 "Service $SERVICE exists" +} + +service-exists-cmd "$@" diff --git a/plugins/postgres/subcommands/export b/plugins/postgres/subcommands/export new file mode 100755 index 000000000..d77dd479d --- /dev/null +++ b/plugins/postgres/subcommands/export @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-export-cmd() { + #E by default, datastore output is exported to stdout + #E dokku $PLUGIN_COMMAND_PREFIX:export lollipop + #E you can redirect this output to a file + #E dokku $PLUGIN_COMMAND_PREFIX:export lollipop > data.dump + #A service, service to run command against + declare desc="export a dump of the $PLUGIN_SERVICE service database" + local cmd="$PLUGIN_COMMAND_PREFIX:export" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_export "$SERVICE" +} + +service-export-cmd "$@" diff --git a/plugins/postgres/subcommands/expose b/plugins/postgres/subcommands/expose new file mode 100755 index 000000000..ab541d697 --- /dev/null +++ b/plugins/postgres/subcommands/expose @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-expose-cmd() { + #E expose the service on the service's normal ports, allowing access to it from the public interface (0.0.0.0) + #E dokku $PLUGIN_COMMAND_PREFIX:expose lollipop ${PLUGIN_DATASTORE_PORTS[@]} + #E expose the service on the service's normal ports, with the first on a specified ip address (127.0.0.1) + #E dokku $PLUGIN_COMMAND_PREFIX:expose lollipop 127.0.0.1:${PLUGIN_DATASTORE_PORTS[@]} + #A service, service to run command against + #A ports, a list of ports to run against + declare desc="expose a $PLUGIN_SERVICE service on custom host:port if provided (random port on the 0.0.0.0 interface if otherwise unspecified)" + local cmd="$PLUGIN_COMMAND_PREFIX:expose" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" PORTS_LIST=("${@:2}") + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_port_expose "$SERVICE" "${@:2}" +} + +service-expose-cmd "$@" diff --git a/plugins/postgres/subcommands/import b/plugins/postgres/subcommands/import new file mode 100755 index 000000000..441f415f6 --- /dev/null +++ b/plugins/postgres/subcommands/import @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-import-cmd() { + #E import a datastore dump + #E dokku $PLUGIN_COMMAND_PREFIX:import lollipop < data.dump + #A service, service to run command against + declare desc="import a dump into the $PLUGIN_SERVICE service database" + local cmd="$PLUGIN_COMMAND_PREFIX:import" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_import "$SERVICE" +} + +service-import-cmd "$@" diff --git a/plugins/postgres/subcommands/info b/plugins/postgres/subcommands/info new file mode 100755 index 000000000..f9970d3a4 --- /dev/null +++ b/plugins/postgres/subcommands/info @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-info-cmd() { + #E get connection information as follows: + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop + #E you can also retrieve a specific piece of service info via flags: + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --config-dir + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --data-dir + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --dsn + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --exposed-ports + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --id + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --internal-ip + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --initial-network + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --links + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --post-create-network + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --post-start-network + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --service-root + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --status + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --version + #A service, service to run command against + #F --config-dir, show the service configuration directory + #F --data-dir, show the service data directory + #F --dsn, show the service DSN + #F --exposed-ports, show service exposed ports + #F --id, show the service container id + #F --internal-ip, show the service internal ip + #F --initial-network, show the initial network being connected to + #F --links, show the service app links + #F --post-create-network, show the networks to attach to after service container creation + #F --post-start-network, show the networks to attach to after service container start + #F --service-root, show the service root directory + #F --status, show the service running status + #F --version, show the service image version + declare desc="print the service information" + local cmd="$PLUGIN_COMMAND_PREFIX:info" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" INFO_FLAG="$2" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_info "$SERVICE" "$INFO_FLAG" +} + +service-info-cmd "$@" diff --git a/plugins/postgres/subcommands/link b/plugins/postgres/subcommands/link new file mode 100755 index 000000000..3d8e15347 --- /dev/null +++ b/plugins/postgres/subcommands/link @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-link-cmd() { + #E a $PLUGIN_COMMAND_PREFIX service can be linked to a container. + #E this will use native docker links via the docker-options plugin. + #E here we link it to our 'playground' app. + #E > NOTE: this will restart your app + #E dokku $PLUGIN_COMMAND_PREFIX:link lollipop playground + #E the following environment variables will be set automatically by docker + #E (not on the app itself, so they won’t be listed when calling dokku config): + #E + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_NAME=/lollipop/DATABASE + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_PORT=tcp://172.17.0.1:${PLUGIN_DATASTORE_PORTS[0]} + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_PORT_${PLUGIN_DATASTORE_PORTS[0]}_TCP=tcp://172.17.0.1:${PLUGIN_DATASTORE_PORTS[0]} + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_PORT_${PLUGIN_DATASTORE_PORTS[0]}_TCP_PROTO=tcp + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_PORT_${PLUGIN_DATASTORE_PORTS[0]}_TCP_PORT=${PLUGIN_DATASTORE_PORTS[0]} + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_PORT_${PLUGIN_DATASTORE_PORTS[0]}_TCP_ADDR=172.17.0.1 + #E + #E the following will be set on the linked application by default: + #E + #E ${PLUGIN_DEFAULT_ALIAS}_URL=${PLUGIN_SCHEME}://lollipop:SOME_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-lollipop:${PLUGIN_DATASTORE_PORTS[0]}/lollipop + #E + #E the host exposed here only works internally in docker containers. + #E if you want your container to be reachable from outside, you should + #E use the 'expose' subcommand. another service can be linked to your app: + #E dokku $PLUGIN_COMMAND_PREFIX:link other_service playground + #E it is possible to change the protocol for ${PLUGIN_DEFAULT_ALIAS}_URL by setting the + #E environment variable ${PLUGIN_VARIABLE}_DATABASE_SCHEME on the app. doing so will + #E after linking will cause the plugin to think the service is not + #E linked, and we advise you to unlink before proceeding. + #E dokku config:set playground ${PLUGIN_VARIABLE}_DATABASE_SCHEME=${PLUGIN_SCHEME}2 + #E dokku $PLUGIN_COMMAND_PREFIX:link lollipop playground + #E this will cause ${PLUGIN_DEFAULT_ALIAS}_URL to be set as: + #E + #E ${PLUGIN_SCHEME}2://lollipop:SOME_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-lollipop:${PLUGIN_DATASTORE_PORTS[0]}/lollipop + #A service, service to run command against + #A app, app to run command against + #F -a|--alias "BLUE_DATABASE", an alternative alias to use for linking to an app via environment variable + #F -q|--querystring "pool=5", ampersand delimited querystring arguments to append to the service link + #F -n|--no-restart "false", whether or not to restart the app on link (default: true) + declare desc="link the $PLUGIN_SERVICE service to the app" + local cmd="$PLUGIN_COMMAND_PREFIX:link" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" APP="$2" LINK_FLAGS_LIST=("${@:3}") + APP=${APP:="$DOKKU_APP_NAME"} + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + verify_app_name "$APP" + verify_service_name "$SERVICE" + + service_parse_args "${@:3}" + service_link "$SERVICE" "$APP" +} + +service-link-cmd "$@" diff --git a/plugins/postgres/subcommands/linked b/plugins/postgres/subcommands/linked new file mode 100755 index 000000000..5d61d79ed --- /dev/null +++ b/plugins/postgres/subcommands/linked @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-linked-cmd() { + #E here we check if the lollipop $PLUGIN_COMMAND_PREFIX service is linked to the 'playground' app. + #E dokku $PLUGIN_COMMAND_PREFIX:linked lollipop playground + #A service, service to run command against + #A app, app to run command against + declare desc="check if the $PLUGIN_SERVICE service is linked to an app" + local cmd="$PLUGIN_COMMAND_PREFIX:linked" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" APP="$2" + APP=${APP:="$DOKKU_APP_NAME"} + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + verify_app_name "$APP" + verify_service_name "$SERVICE" + service_is_linked "$SERVICE" "$APP" +} + +service-linked-cmd "$@" diff --git a/plugins/postgres/subcommands/links b/plugins/postgres/subcommands/links new file mode 100755 index 000000000..35586aad5 --- /dev/null +++ b/plugins/postgres/subcommands/links @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-links-cmd() { + #E list all apps linked to the 'lollipop' $PLUGIN_COMMAND_PREFIX service. + #E dokku $PLUGIN_COMMAND_PREFIX:links lollipop + #A service, service to run command against + declare desc="list all apps linked to the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:links" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_links "$SERVICE" +} + +service-links-cmd "$@" diff --git a/plugins/postgres/subcommands/list b/plugins/postgres/subcommands/list new file mode 100755 index 000000000..cebe31c13 --- /dev/null +++ b/plugins/postgres/subcommands/list @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-list-cmd() { + #E list all services + #E dokku $PLUGIN_COMMAND_PREFIX:list + declare desc="list all $PLUGIN_SERVICE services" + local cmd="$PLUGIN_COMMAND_PREFIX:list" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + + service_list +} + +service-list-cmd "$@" diff --git a/plugins/postgres/subcommands/logs b/plugins/postgres/subcommands/logs new file mode 100755 index 000000000..7f2e90eac --- /dev/null +++ b/plugins/postgres/subcommands/logs @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-logs-cmd() { + #E you can tail logs for a particular service: + #E dokku $PLUGIN_COMMAND_PREFIX:logs lollipop + #E by default, logs will not be tailed, but you can do this with the --tail flag: + #E dokku $PLUGIN_COMMAND_PREFIX:logs lollipop --tail + #E the default tail setting is to show all logs, but an initial count can also be specified + #E dokku $PLUGIN_COMMAND_PREFIX:logs lollipop --tail 5 + #A service, service to run command against + #F -t|--tail [], do not stop when end of the logs are reached and wait for additional output + declare desc="print the most recent log(s) for this service" + local cmd="$PLUGIN_COMMAND_PREFIX:logs" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" TAIL_FLAG="$2" TAIL_NUM_OPTIONAL="${3:-all}" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_logs "$SERVICE" "$TAIL_FLAG" "$TAIL_NUM_OPTIONAL" +} + +service-logs-cmd "$@" diff --git a/plugins/postgres/subcommands/pause b/plugins/postgres/subcommands/pause new file mode 100755 index 000000000..91d51fe69 --- /dev/null +++ b/plugins/postgres/subcommands/pause @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-pause-cmd() { + #E pause the running container for the service + #E dokku $PLUGIN_COMMAND_PREFIX:pause lollipop + #A service, service to run command against + declare desc="pause a running $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:pause" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_pause "$SERVICE" +} + +service-pause-cmd "$@" diff --git a/plugins/postgres/subcommands/promote b/plugins/postgres/subcommands/promote new file mode 100755 index 000000000..ddf35ada0 --- /dev/null +++ b/plugins/postgres/subcommands/promote @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-promote-cmd() { + #E if you have a $PLUGIN_COMMAND_PREFIX service linked to an app and try to link another $PLUGIN_COMMAND_PREFIX service + #E another link environment variable will be generated automatically: + #E + #E DOKKU_${PLUGIN_DEFAULT_ALIAS}_BLUE_URL=${PLUGIN_SCHEME}://other_service:ANOTHER_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-other-service:${PLUGIN_DATASTORE_PORTS[0]}/other_service + #E + #E you can promote the new service to be the primary one + #E > NOTE: this will restart your app + #E dokku $PLUGIN_COMMAND_PREFIX:promote other_service playground + #E this will replace ${PLUGIN_DEFAULT_ALIAS}_URL with the url from other_service and generate + #E another environment variable to hold the previous value if necessary. + #E you could end up with the following for example: + #E + #E ${PLUGIN_DEFAULT_ALIAS}_URL=${PLUGIN_SCHEME}://other_service:ANOTHER_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-other-service:${PLUGIN_DATASTORE_PORTS[0]}/other_service + #E DOKKU_${PLUGIN_DEFAULT_ALIAS}_BLUE_URL=${PLUGIN_SCHEME}://other_service:ANOTHER_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-other-service:${PLUGIN_DATASTORE_PORTS[0]}/other_service + #E DOKKU_${PLUGIN_DEFAULT_ALIAS}_SILVER_URL=${PLUGIN_SCHEME}://lollipop:SOME_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-lollipop:${PLUGIN_DATASTORE_PORTS[0]}/lollipop + #A service, service to run command against + #A app, app to run command against + declare desc="promote service as ${PLUGIN_DEFAULT_ALIAS}_URL in " + local cmd="$PLUGIN_COMMAND_PREFIX:promote" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" APP="$2" + APP=${APP:="$DOKKU_APP_NAME"} + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + verify_service_name "$SERVICE" + verify_app_name "$APP" + service_promote "$SERVICE" "$APP" +} + +service-promote-cmd "$@" diff --git a/plugins/postgres/subcommands/restart b/plugins/postgres/subcommands/restart new file mode 100755 index 000000000..9ff6b030d --- /dev/null +++ b/plugins/postgres/subcommands/restart @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-restart-cmd() { + #E restart the service + #E dokku $PLUGIN_COMMAND_PREFIX:restart lollipop + #A service, service to run command against + declare desc="graceful shutdown and restart of the $PLUGIN_SERVICE service container" + local cmd="$PLUGIN_COMMAND_PREFIX:restart" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_pause "$SERVICE" + service_start "$SERVICE" + dokku_log_info1 "Please call dokku ps:restart on all linked apps" +} + +service-restart-cmd "$@" diff --git a/plugins/postgres/subcommands/set b/plugins/postgres/subcommands/set new file mode 100755 index 000000000..0bcf474b5 --- /dev/null +++ b/plugins/postgres/subcommands/set @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +source "$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)/common-functions" + +service-set-cmd() { + #E set the network to attach after the service container is started + #E dokku $PLUGIN_COMMAND_PREFIX:set lollipop post-create-network custom-network + #E set multiple networks + #E dokku $PLUGIN_COMMAND_PREFIX:set lollipop post-create-network custom-network,other-network + #E unset the post-create-network value + #E dokku $PLUGIN_COMMAND_PREFIX:set lollipop post-create-network + #A service, service to run command against + #A key, property name to set + #A value, optional property value to set or empty to unset key + declare desc="set or clear a property for a service" + local cmd="$PLUGIN_COMMAND_PREFIX:set" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" KEY="$2" VALUE="$3" + local VALID_KEYS=("initial-network" "post-create-network" "post-start-network") + verify_service_name "$SERVICE" + + [[ -z "$KEY" ]] && dokku_log_fail "No key specified" + + if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then + dokku_log_fail "Invalid key specified, valid keys include: initial-network, post-create-network, post-start-network" + fi + + if [[ -n "$VALUE" ]]; then + dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}" + fn-plugin-property-write "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$KEY" "$VALUE" + else + dokku_log_info2_quiet "Unsetting ${KEY}" + if [[ "$KEY" == "rev-env-var" ]]; then + fn-plugin-property-write "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$KEY" "$VALUE" + else + fn-plugin-property-delete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$KEY" + fi + fi +} + +service-set-cmd "$@" diff --git a/plugins/postgres/subcommands/start b/plugins/postgres/subcommands/start new file mode 100755 index 000000000..c928f3a6e --- /dev/null +++ b/plugins/postgres/subcommands/start @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-start-cmd() { + #E start the service + #E dokku $PLUGIN_COMMAND_PREFIX:start lollipop + #A service, service to run command against + declare desc="start a previously stopped $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:start" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_start "$SERVICE" +} + +service-start-cmd "$@" diff --git a/plugins/postgres/subcommands/stop b/plugins/postgres/subcommands/stop new file mode 100755 index 000000000..9680ea503 --- /dev/null +++ b/plugins/postgres/subcommands/stop @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-stop-cmd() { + #E stop the service and removes the running container + #E dokku $PLUGIN_COMMAND_PREFIX:stop lollipop + #A service, service to run command against + declare desc="stop a running $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:stop" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_container_rm "$SERVICE" +} + +service-stop-cmd "$@" diff --git a/plugins/postgres/subcommands/unexpose b/plugins/postgres/subcommands/unexpose new file mode 100755 index 000000000..a3e990a24 --- /dev/null +++ b/plugins/postgres/subcommands/unexpose @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-unexpose-cmd() { + #E unexpose the service, removing access to it from the public interface (0.0.0.0) + #E dokku $PLUGIN_COMMAND_PREFIX:unexpose lollipop + #A service, service to run command against + declare desc="unexpose a previously exposed $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:unexpose" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_port_unexpose "$SERVICE" +} + +service-unexpose-cmd "$@" diff --git a/plugins/postgres/subcommands/unlink b/plugins/postgres/subcommands/unlink new file mode 100755 index 000000000..63c4c97f4 --- /dev/null +++ b/plugins/postgres/subcommands/unlink @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-unlink-cmd() { + #E you can unlink a $PLUGIN_COMMAND_PREFIX service + #E > NOTE: this will restart your app and unset related environment variables + #E dokku $PLUGIN_COMMAND_PREFIX:unlink lollipop playground + #A service, service to run command against + #A app, app to run command against + #F -n|--no-restart "false", whether or not to restart the app on unlink (default: true) + declare desc="unlink the $PLUGIN_SERVICE service from the app" + local cmd="$PLUGIN_COMMAND_PREFIX:unlink" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" APP="$2" + APP=${APP:="$DOKKU_APP_NAME"} + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + verify_service_name "$SERVICE" + verify_app_name "$APP" + service_unlink "$SERVICE" "$APP" +} + +service-unlink-cmd "$@" diff --git a/plugins/postgres/subcommands/upgrade b/plugins/postgres/subcommands/upgrade new file mode 100755 index 000000000..3eeccde88 --- /dev/null +++ b/plugins/postgres/subcommands/upgrade @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" +source "$PLUGIN_AVAILABLE_PATH/ps/functions" + +service-upgrade-cmd() { + #E you can upgrade an existing service to a new image or image-version + #E dokku $PLUGIN_COMMAND_PREFIX:upgrade lollipop + #A service, service to run command against + #F -c|--config-options "--args --go=here", extra arguments to pass to the container create command + #F -C|--custom-env "USER=alpha;HOST=beta", semi-colon delimited environment variables to start the service with + #F -i|--image IMAGE, the image name to start the service with + #F -I|--image-version IMAGE_VERSION, the image version to start the service with + #F -N|--initial-network INITIAL_NETWORK, the initial network to attach the service to + #F -P|--post-create-network NETWORKS, a comma-separated list of networks to attach the service container to after service creation + #F -R|--restart-apps "true", whether or not to force an app restart (default: false) + #F -S|--post-start-network NETWORKS, a comma-separated list of networks to attach the service container to after service start + #F -s|--shm-size SHM_SIZE, override shared memory size for $PLUGIN_COMMAND_PREFIX docker container + declare desc="upgrade service to the specified versions" + local cmd="$PLUGIN_COMMAND_PREFIX:upgrade" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" UPGRADE_FLAGS_LIST=("${@:2}") + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + + service_parse_args "${@:2}" + + if ! service_image_exists "$SERVICE" "$PLUGIN_IMAGE" "$PLUGIN_IMAGE_VERSION"; then + if [[ "$PLUGIN_DISABLE_PULL" == "true" ]]; then + dokku_log_warn "${PLUGIN_DISABLE_PULL_VARIABLE} environment variable detected. Not running pull command." 1>&2 + dokku_log_warn " docker image pull ${IMAGE}" 1>&2 + dokku_log_warn "$PLUGIN_SERVICE service $SERVICE upgrade failed" + exit 1 + fi + "$DOCKER_BIN" image pull "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" || dokku_log_fail "$PLUGIN_SERVICE image $PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION pull failed" + fi + + local NEW_PLUGIN_IMAGE_TAG="$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" + if [[ "$(service_version "$SERVICE")" == "$NEW_PLUGIN_IMAGE_TAG" ]]; then + dokku_log_info1 "Service $SERVICE already running $NEW_PLUGIN_IMAGE_TAG" + return + fi + + service_commit_config "$SERVICE" + + dokku_log_info2 "Upgrading $SERVICE to $NEW_PLUGIN_IMAGE_TAG" + if [[ "$SERVICE_RESTART_APPS" == "true" ]]; then + dokku_log_info2 "Stopping all linked services" + for app in $(service_linked_apps "$SERVICE"); do + [[ "$app" == "-" ]] && continue + ps_stop "$app" + done + fi + + dokku_log_info2 "Stopping $SERVICE" + service_container_rm "$SERVICE" + service_start "$SERVICE" "${@:2}" + + if [[ "$SERVICE_RESTART_APPS" == "true" ]]; then + dokku_log_info2 "Starting all linked services" + for app in $(service_linked_apps "$SERVICE"); do + [[ "$app" == "-" ]] && continue + ps_start "$app" + done + fi + + dokku_log_info2 "Done" +} + +service-upgrade-cmd "$@" diff --git a/plugins/postgres/tests/hook_pre_delete.bats b/plugins/postgres/tests/hook_pre_delete.bats new file mode 100755 index 000000000..600b8b047 --- /dev/null +++ b/plugins/postgres/tests/hook_pre_delete.bats @@ -0,0 +1,20 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku apps:create my-app + dokku "$PLUGIN_COMMAND_PREFIX:create" ls + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app >&2 +} + +teardown() { + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app >&2 + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ls + dokku --force apps:destroy my-app || true +} + +@test "($PLUGIN_COMMAND_PREFIX:hook:pre-delete) removes app from links file when destroying app" { + [[ -n $(<"$PLUGIN_DATA_ROOT/ls/LINKS") ]] + dokku --force apps:destroy my-app + [[ -z $(<"$PLUGIN_DATA_ROOT/ls/LINKS") ]] +} diff --git a/plugins/postgres/tests/link_networks.bats b/plugins/postgres/tests/link_networks.bats new file mode 100755 index 000000000..3d3763817 --- /dev/null +++ b/plugins/postgres/tests/link_networks.bats @@ -0,0 +1,289 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" ls + dokku network:create custom-network +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ls || true + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" lsa || true + dokku network:destroy --force custom-network +} + +@test "($PLUGIN_COMMAND_PREFIX:set) set initial-network" { + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls initial-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --initial-network + echo "output: $output" + echo "status: $status" + assert_output "custom-network" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge 0 + assert_output_contains custom-network + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{range $k,$alias := $v.DNSNames}}{{printf "alias:%s\n" $alias}}{{end}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "alias:dokku.$PLUGIN_COMMAND_PREFIX.ls" + assert_output_contains "alias:dokku-$PLUGIN_COMMAND_PREFIX-ls" + + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls initial-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --initial-network + echo "output: $output" + echo "status: $status" + assert_output "" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 +} + +@test "($PLUGIN_COMMAND_PREFIX:set) set post-create-network" { + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls post-create-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --post-create-network + echo "output: $output" + echo "status: $status" + assert_output "custom-network" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains custom-network + assert_output_contains bridge + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{range $k,$alias := $v.DNSNames}}{{printf "alias:%s\n" $alias}}{{end}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "alias:dokku.$PLUGIN_COMMAND_PREFIX.ls" + assert_output_contains "alias:dokku-$PLUGIN_COMMAND_PREFIX-ls" + + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls post-create-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --post-create-network + echo "output: $output" + echo "status: $status" + assert_output "" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 +} + +@test "($PLUGIN_COMMAND_PREFIX:set) set an post-start-network" { + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls post-start-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --post-start-network + echo "output: $output" + echo "status: $status" + assert_output "custom-network" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{range $k,$alias := $v.DNSNames}}{{printf "alias:%s\n" $alias}}{{end}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "alias:dokku.$PLUGIN_COMMAND_PREFIX.ls" + assert_output_contains "alias:dokku-$PLUGIN_COMMAND_PREFIX-ls" + + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls post-start-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --post-start-network + echo "output: $output" + echo "status: $status" + assert_output "" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 +} + +@test "($PLUGIN_COMMAND_PREFIX:create) flags" { + run dokku "$PLUGIN_COMMAND_PREFIX:create" lsa --initial-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect "dokku.$PLUGIN_COMMAND_PREFIX.lsa" -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge 0 + assert_output_contains custom-network + + run dokku "$PLUGIN_COMMAND_PREFIX:destroy" lsa --force + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:create" lsa --post-create-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect "dokku.$PLUGIN_COMMAND_PREFIX.lsa" -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.lsa -f '{{range $net,$v := .NetworkSettings.Networks}}{{range $k,$alias := $v.DNSNames}}{{printf "alias:%s\n" $alias}}{{end}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "alias:dokku.$PLUGIN_COMMAND_PREFIX.lsa" + assert_output_contains "alias:dokku-$PLUGIN_COMMAND_PREFIX-lsa" + + run dokku "$PLUGIN_COMMAND_PREFIX:destroy" lsa --force + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:create" lsa --post-start-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect "dokku.$PLUGIN_COMMAND_PREFIX.lsa" -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network + + run dokku "$PLUGIN_COMMAND_PREFIX:destroy" lsa --force + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/plugins/postgres/tests/service_clone.bats b/plugins/postgres/tests/service_clone.bats new file mode 100755 index 000000000..e14809bc0 --- /dev/null +++ b/plugins/postgres/tests/service_clone.bats @@ -0,0 +1,47 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:clone) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:clone" + assert_contains "${lines[*]}" "Please specify a valid name for the service" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:clone) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:clone" not_existing_service new_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:clone) error when new service isn't provided" { + run dokku "$PLUGIN_COMMAND_PREFIX:clone" l + assert_contains "${lines[*]}" "Please specify a name for the new service" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:clone) error when new service already exists" { + dokku "$PLUGIN_COMMAND_PREFIX:create" new_service + run dokku "$PLUGIN_COMMAND_PREFIX:clone" l new_service + assert_contains "${lines[*]}" "Invalid service name new_service" + assert_failure + + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" new_service +} + +@test "($PLUGIN_COMMAND_PREFIX:clone) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:clone" l new_service + [[ -f $PLUGIN_DATA_ROOT/new_service/ID ]] + assert_contains "${lines[*]}" "Copying data from l to new_service" + assert_contains "${lines[*]}" "Done" + assert_success + + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" new_service +} diff --git a/plugins/postgres/tests/service_connect.bats b/plugins/postgres/tests/service_connect.bats new file mode 100755 index 000000000..4af0a2b19 --- /dev/null +++ b/plugins/postgres/tests/service_connect.bats @@ -0,0 +1,26 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:connect) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:connect" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:connect) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:connect" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:connect) success" { + skip "Connect hangs indefinitely without input" + run dokku "$PLUGIN_COMMAND_PREFIX:connect" l + assert_success +} diff --git a/plugins/postgres/tests/service_create.bats b/plugins/postgres/tests/service_create.bats new file mode 100755 index 000000000..e9ac62dd8 --- /dev/null +++ b/plugins/postgres/tests/service_create.bats @@ -0,0 +1,27 @@ +#!/usr/bin/env bats +load test_helper + +@test "($PLUGIN_COMMAND_PREFIX:create) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:create" l + assert_contains "${lines[*]}" "container created: l" + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:create) service with dashes" { + run dokku "$PLUGIN_COMMAND_PREFIX:create" service-with-dashes + assert_contains "${lines[*]}" "container created: service-with-dashes" + assert_contains "${lines[*]}" "dokku-$PLUGIN_COMMAND_PREFIX-service-with-dashes" + assert_contains "${lines[*]}" "service_with_dashes" + + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" service-with-dashes +} + +@test "($PLUGIN_COMMAND_PREFIX:create) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:create" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:create) error when there is an invalid name specified" { + run dokku "$PLUGIN_COMMAND_PREFIX:create" d.erp + assert_failure +} diff --git a/plugins/postgres/tests/service_destroy.bats b/plugins/postgres/tests/service_destroy.bats new file mode 100755 index 000000000..98695ead2 --- /dev/null +++ b/plugins/postgres/tests/service_destroy.bats @@ -0,0 +1,30 @@ +#!/usr/bin/env bats +load test_helper + +@test "($PLUGIN_COMMAND_PREFIX:destroy) success with --force" { + dokku "$PLUGIN_COMMAND_PREFIX:create" l + run dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + assert_contains "${lines[*]}" "container deleted: l" +} + +@test "($PLUGIN_COMMAND_PREFIX:destroy) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:destroy" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:destroy) error when container does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:destroy" non_existing_container + assert_contains "${lines[*]}" "service non_existing_container does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:destroy) error when container is linked to an app" { + dokku "$PLUGIN_COMMAND_PREFIX:create" l + dokku apps:create app + dokku "$PLUGIN_COMMAND_PREFIX:link" l app + run dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + assert_contains "${lines[*]}" "Cannot delete linked service" + + dokku "$PLUGIN_COMMAND_PREFIX:unlink" l app + run dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + assert_contains "${lines[*]}" "container deleted: l" +} diff --git a/plugins/postgres/tests/service_export.bats b/plugins/postgres/tests/service_export.bats new file mode 100755 index 000000000..845702c52 --- /dev/null +++ b/plugins/postgres/tests/service_export.bats @@ -0,0 +1,39 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:export) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:export" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:export) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:export" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:export) success with SSH_TTY" { + if [[ -n "$GITHUB_WORKFLOW" ]]; then + skip "No tty is available on Github Actions" + fi + export SSH_TTY=$(tty) + run dokku "$PLUGIN_COMMAND_PREFIX:export" l + echo "output: $output" + echo "status: $status" + assert_exit_status 0 +} + +@test "($PLUGIN_COMMAND_PREFIX:export) success without SSH_TTY" { + unset SSH_TTY + run dokku "$PLUGIN_COMMAND_PREFIX:export" l + echo "output: $output" + echo "status: $status" + assert_exit_status 0 +} diff --git a/plugins/postgres/tests/service_expose.bats b/plugins/postgres/tests/service_expose.bats new file mode 100755 index 000000000..0bfa302c7 --- /dev/null +++ b/plugins/postgres/tests/service_expose.bats @@ -0,0 +1,66 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" ls +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ls +} + +@test "($PLUGIN_COMMAND_PREFIX:expose) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:expose" + echo "output: $output" + echo "status: $status" + assert_failure + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:expose) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:expose" not_existing_service + echo "output: $output" + echo "status: $status" + assert_failure + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:expose) error when already exposed" { + run dokku "$PLUGIN_COMMAND_PREFIX:expose" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:expose" ls + echo "output: $output" + echo "status: $status" + assert_failure + assert_contains "${lines[*]}" "Service ls already exposed on port(s)" + + run sudo rm "$PLUGIN_DATA_ROOT/ls/PORT" + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:expose" ls + echo "output: $output" + echo "status: $status" + assert_success + assert_contains "${lines[*]}" "Service ls has an untracked expose container, removing" +} + +@test "($PLUGIN_COMMAND_PREFIX:expose) success when not providing custom ports" { + run dokku "$PLUGIN_COMMAND_PREFIX:expose" ls + echo "output: $output" + echo "status: $status" + assert_success + [[ "${lines[*]}" =~ exposed\ on\ port\(s\)\ \[container\-\>host\]\:\ [[:digit:]]+ ]] +} + +@test "($PLUGIN_COMMAND_PREFIX:expose) success when providing custom ports" { + run dokku "$PLUGIN_COMMAND_PREFIX:expose" ls 4242 + echo "output: $output" + echo "status: $status" + assert_success + assert_contains "${lines[*]}" "exposed on port(s) [container->host]: 5432->4242" +} diff --git a/plugins/postgres/tests/service_import.bats b/plugins/postgres/tests/service_import.bats new file mode 100755 index 000000000..7356892c2 --- /dev/null +++ b/plugins/postgres/tests/service_import.bats @@ -0,0 +1,41 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l + echo "data" | tee "/tmp/fake.dump" +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + rm -f "/tmp/fake.dump" +} + +@test "($PLUGIN_COMMAND_PREFIX:import) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:import" + assert_contains "${lines[*]}" "Please specify a valid name for the service" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:import) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:import" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:import) error when data is not provided" { + if [[ -n "$GITHUB_WORKFLOW" ]]; then + skip "No tty is available on Github Actions" + fi + run dokku "$PLUGIN_COMMAND_PREFIX:import" l + assert_contains "${lines[*]}" "No data provided on stdin" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:import) success" { + skip "The fake dump is hard to work with in tests" + run dokku "$PLUGIN_COMMAND_PREFIX:import" l <"/tmp/fake.dump" + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/plugins/postgres/tests/service_info.bats b/plugins/postgres/tests/service_info.bats new file mode 100755 index 000000000..77ffb8775 --- /dev/null +++ b/plugins/postgres/tests/service_info.bats @@ -0,0 +1,75 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:info) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:info" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:info) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:info" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:info) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:info" l + local password="$(sudo cat "$PLUGIN_DATA_ROOT/l/PASSWORD")" + assert_contains "${lines[*]}" "postgres://postgres:$password@dokku-postgres-l:5432/l" +} + +@test "($PLUGIN_COMMAND_PREFIX:info) replaces underscores by dash in hostname" { + dokku "$PLUGIN_COMMAND_PREFIX:create" test_with_underscores + run dokku "$PLUGIN_COMMAND_PREFIX:info" test_with_underscores + local password="$(sudo cat "$PLUGIN_DATA_ROOT/test_with_underscores/PASSWORD")" + assert_contains "${lines[*]}" "postgres://postgres:$password@dokku-postgres-test-with-underscores:5432/test_with_underscores" + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" test_with_underscores +} + +@test "($PLUGIN_COMMAND_PREFIX:info) success with flag" { + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --dsn + local password="$(sudo cat "$PLUGIN_DATA_ROOT/l/PASSWORD")" + assert_output "postgres://postgres:$password@dokku-postgres-l:5432/l" + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --config-dir + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --data-dir + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --dsn + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --exposed-ports + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --id + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --internal-ip + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --links + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --service-root + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --status + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --version + assert_success +} + +@test "($PLUGIN_COMMAND_PREFIX:info) error when invalid flag" { + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --invalid-flag + assert_failure +} diff --git a/plugins/postgres/tests/service_link.bats b/plugins/postgres/tests/service_link.bats new file mode 100755 index 000000000..4bc172916 --- /dev/null +++ b/plugins/postgres/tests/service_link.bats @@ -0,0 +1,142 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" ls + dokku "$PLUGIN_COMMAND_PREFIX:create" ms + dokku apps:create my-app +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ms + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ls + dokku --force apps:destroy my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "Please specify a valid name for the service" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:link) error when the app argument is missing" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "Please specify an app to run the command on" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:link) error when the app does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls not_existing_app + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "App not_existing_app does not exist" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:link) error when the service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" not_existing_service my-app + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "service not_existing_service does not exist" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:link) error when the service is already linked to app" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "Already linked as DATABASE_URL" + assert_failure + + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) exports DATABASE_URL to app" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + echo "output: $output" + echo "status: $status" + url=$(dokku config:get my-app DATABASE_URL) + password="$(sudo cat "$PLUGIN_DATA_ROOT/ls/PASSWORD")" + assert_contains "$url" "postgres://postgres:$password@dokku-postgres-ls:5432/ls" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) generates an alternate config url when DATABASE_URL already in use" { + dokku config:set my-app DATABASE_URL=postgres://user:pass@host:5432/db + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + run dokku config my-app + assert_contains "${lines[*]}" "DOKKU_POSTGRES_AQUA_URL" + assert_success + + dokku "$PLUGIN_COMMAND_PREFIX:link" ms my-app + run dokku config my-app + assert_contains "${lines[*]}" "DOKKU_POSTGRES_BLACK_URL" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ms my-app + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) links to app with docker-options" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + run dokku docker-options:report my-app + assert_contains "${lines[*]}" "--link dokku.postgres.ls:dokku-postgres-ls" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) uses apps POSTGRES_DATABASE_SCHEME variable" { + dokku config:set my-app POSTGRES_DATABASE_SCHEME=postgres2 + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + url=$(dokku config:get my-app DATABASE_URL) + password="$(sudo cat "$PLUGIN_DATA_ROOT/ls/PASSWORD")" + assert_contains "$url" "postgres2://postgres:$password@dokku-postgres-ls:5432/ls" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) adds a querystring" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app --querystring "pool=5" + url=$(dokku config:get my-app DATABASE_URL) + assert_contains "$url" "?pool=5" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) uses a specified config url when alias is specified" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app --alias "ALIAS" + url=$(dokku config:get my-app ALIAS_URL) + password="$(sudo cat "$PLUGIN_DATA_ROOT/ls/PASSWORD")" + assert_contains "$url" "postgres://postgres:$password@dokku-postgres-ls:5432/ls" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) respects --no-restart" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + echo "output: $output" + echo "status: $status" + assert_output_contains "Skipping restart of linked app" 0 + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app --no-restart + echo "output: $output" + echo "status: $status" + assert_output_contains "Skipping restart of linked app" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/plugins/postgres/tests/service_list.bats b/plugins/postgres/tests/service_list.bats new file mode 100755 index 000000000..8ace1312a --- /dev/null +++ b/plugins/postgres/tests/service_list.bats @@ -0,0 +1,22 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:list) with no exposed ports, no linked apps" { + run dokku --quiet "$PLUGIN_COMMAND_PREFIX:list" + assert_output "l" +} + +@test "($PLUGIN_COMMAND_PREFIX:list) when there are no services" { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + run dokku "$PLUGIN_COMMAND_PREFIX:list" + assert_output "${lines[*]}" "There are no $PLUGIN_SERVICE services" + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} diff --git a/plugins/postgres/tests/service_logs.bats b/plugins/postgres/tests/service_logs.bats new file mode 100755 index 000000000..d12585cdd --- /dev/null +++ b/plugins/postgres/tests/service_logs.bats @@ -0,0 +1,42 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:logs) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:logs" + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "Please specify a valid name for the service" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:logs) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:logs" not_existing_service + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "service not_existing_service does not exist" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:logs) success when not tailing" { + skip "This may fail if there is no log output" + run dokku "$PLUGIN_COMMAND_PREFIX:logs" l + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "($PLUGIN_COMMAND_PREFIX:logs) success when tailing" { + skip "This will hang as it waits for log output" + run dokku "$PLUGIN_COMMAND_PREFIX:logs" l -t + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/plugins/postgres/tests/service_pause.bats b/plugins/postgres/tests/service_pause.bats new file mode 100755 index 000000000..a2a2a696c --- /dev/null +++ b/plugins/postgres/tests/service_pause.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:pause) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:pause" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:pause) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:pause" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:pause) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:pause" l + assert_success +} diff --git a/plugins/postgres/tests/service_promote.bats b/plugins/postgres/tests/service_promote.bats new file mode 100755 index 000000000..6636f6c17 --- /dev/null +++ b/plugins/postgres/tests/service_promote.bats @@ -0,0 +1,62 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l + dokku apps:create my-app + dokku "$PLUGIN_COMMAND_PREFIX:link" l my-app +} + +teardown() { + dokku "$PLUGIN_COMMAND_PREFIX:unlink" l my-app + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + dokku --force apps:destroy my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:promote" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) error when the app argument is missing" { + run dokku "$PLUGIN_COMMAND_PREFIX:promote" l + assert_contains "${lines[*]}" "Please specify an app to run the command on" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) error when the app does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:promote" l not_existing_app + assert_contains "${lines[*]}" "App not_existing_app does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) error when the service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:promote" not_existing_service my-app + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) error when the service is already promoted" { + run dokku "$PLUGIN_COMMAND_PREFIX:promote" l my-app + assert_contains "${lines[*]}" "already promoted as DATABASE_URL" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) changes DATABASE_URL" { + password="$(sudo cat "$PLUGIN_DATA_ROOT/l/PASSWORD")" + dokku config:set my-app "DATABASE_URL=postgres://u:p@host:5432/db" "DOKKU_POSTGRES_BLUE_URL=postgres://postgres:$password@dokku-postgres-l:5432/l" + dokku "$PLUGIN_COMMAND_PREFIX:promote" l my-app + url=$(dokku config:get my-app DATABASE_URL) + assert_equal "$url" "postgres://postgres:$password@dokku-postgres-l:5432/l" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) creates new config url when needed" { + password="$(sudo cat "$PLUGIN_DATA_ROOT/l/PASSWORD")" + dokku config:set my-app "DATABASE_URL=postgres://u:p@host:5432/db" "DOKKU_POSTGRES_BLUE_URL=postgres://postgres:$password@dokku-postgres-l:5432/l" + dokku "$PLUGIN_COMMAND_PREFIX:promote" l my-app + run dokku config my-app + assert_contains "${lines[*]}" "DOKKU_POSTGRES_" +} +@test "($PLUGIN_COMMAND_PREFIX:promote) uses POSTGRES_DATABASE_SCHEME variable" { + password="$(sudo cat "$PLUGIN_DATA_ROOT/l/PASSWORD")" + dokku config:set my-app "POSTGRES_DATABASE_SCHEME=postgres2" "DATABASE_URL=postgres://u:p@host:5432/db" "DOKKU_POSTGRES_BLUE_URL=postgres2://postgres:$password@dokku-postgres-l:5432/l" + dokku "$PLUGIN_COMMAND_PREFIX:promote" l my-app + url=$(dokku config:get my-app DATABASE_URL) + assert_contains "$url" "postgres2://postgres:$password@dokku-postgres-l:5432/l" +} diff --git a/plugins/postgres/tests/service_restart.bats b/plugins/postgres/tests/service_restart.bats new file mode 100755 index 000000000..e74cc4752 --- /dev/null +++ b/plugins/postgres/tests/service_restart.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:restart) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:restart" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:restart) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:restart" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:restart) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:restart" l + assert_success +} diff --git a/plugins/postgres/tests/service_start.bats b/plugins/postgres/tests/service_start.bats new file mode 100755 index 000000000..adff1d20e --- /dev/null +++ b/plugins/postgres/tests/service_start.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:start) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:start" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:start) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:start" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:start) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:start" l + assert_success +} diff --git a/plugins/postgres/tests/service_stop.bats b/plugins/postgres/tests/service_stop.bats new file mode 100755 index 000000000..826bc917c --- /dev/null +++ b/plugins/postgres/tests/service_stop.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:stop) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:stop" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:stop) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:stop" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:stop) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:stop" l + assert_success +} diff --git a/plugins/postgres/tests/service_unexpose.bats b/plugins/postgres/tests/service_unexpose.bats new file mode 100755 index 000000000..1442728f5 --- /dev/null +++ b/plugins/postgres/tests/service_unexpose.bats @@ -0,0 +1,27 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:unexpose) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:unexpose" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:unexpose) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:unexpose" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:unexpose) success" { + dokku "$PLUGIN_COMMAND_PREFIX:expose" l + run dokku "$PLUGIN_COMMAND_PREFIX:unexpose" l + [[ ! -f $PLUGIN_DATA_ROOT/PORT ]] + assert_contains "${lines[*]}" "Service l unexposed" +} diff --git a/plugins/postgres/tests/service_unlink.bats b/plugins/postgres/tests/service_unlink.bats new file mode 100755 index 000000000..164baf2a7 --- /dev/null +++ b/plugins/postgres/tests/service_unlink.bats @@ -0,0 +1,77 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku apps:create my-app + dokku "$PLUGIN_COMMAND_PREFIX:create" ls +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ls + dokku --force apps:destroy my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) error when the app argument is missing" { + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls + assert_contains "${lines[*]}" "Please specify an app to run the command on" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) error when the app does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls not_existing_app + assert_contains "${lines[*]}" "App not_existing_app does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) error when the service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" not_existing_service my-app + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) error when service not linked to app" { + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + assert_contains "${lines[*]}" "Not linked to app my-app" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) removes link from docker-options" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app >&2 + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + + check_value="Docker options build: Docker options deploy: --restart=on-failure:10 Docker options run:" + options=$(dokku --quiet docker-options:report my-app | xargs) + assert_equal "$options" "$check_value" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) unsets config url from app" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app >&2 + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + config=$(dokku config:get my-app DATABASE_URL || true) + assert_equal "$config" "" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) respects --no-restart" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + echo "output: $output" + echo "status: $status" + assert_output_contains "Skipping restart of linked app" 0 + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app --no-restart + echo "output: $output" + echo "status: $status" + assert_output_contains "Skipping restart of linked app" + assert_success +} diff --git a/plugins/postgres/tests/setup.sh b/plugins/postgres/tests/setup.sh new file mode 100644 index 000000000..c48b0c139 --- /dev/null +++ b/plugins/postgres/tests/setup.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $TRACE ]] && set -x +sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 762E3157 +echo "deb http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" | sudo tee /etc/apt/sources.list.d/nginx.list +curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add - + +sudo mkdir -p /etc/nginx +sudo curl https://raw.githubusercontent.com/dokku/dokku/master/tests/dhparam.pem -o /etc/nginx/dhparam.pem + +echo "dokku dokku/skip_key_file boolean true" | sudo debconf-set-selections +wget https://raw.githubusercontent.com/dokku/dokku/master/bootstrap.sh +if [[ "$DOKKU_VERSION" == "master" ]]; then + sudo bash bootstrap.sh +else + sudo DOKKU_TAG="$DOKKU_VERSION" bash bootstrap.sh +fi +echo "Dokku version $DOKKU_VERSION" + +export DOKKU_LIB_ROOT="/var/lib/dokku" +export DOKKU_PLUGINS_ROOT="$DOKKU_LIB_ROOT/plugins/available" +pushd "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")" >/dev/null +source "config" +popd >/dev/null +sudo rm -rf "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX" +sudo mkdir -p "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX" "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/subcommands" "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/scripts" "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/templates" +sudo find ./ -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX" \; +[[ -d "./scripts" ]] && sudo find ./scripts -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/scripts" \; +[[ -d "./subcommands" ]] && sudo find ./subcommands -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/subcommands" \; +[[ -d "./templates" ]] && sudo find ./templates -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/templates" \; +sudo mkdir -p "$PLUGIN_CONFIG_ROOT" "$PLUGIN_DATA_ROOT" +sudo dokku plugin:enable "$PLUGIN_COMMAND_PREFIX" +sudo dokku plugin:install diff --git a/plugins/postgres/tests/shellcheck-exclude b/plugins/postgres/tests/shellcheck-exclude new file mode 100644 index 000000000..9fbac741c --- /dev/null +++ b/plugins/postgres/tests/shellcheck-exclude @@ -0,0 +1,8 @@ +# SC1090 - Can't follow non-constant source. Use a directive to specify location - https://github.com/koalaman/shellcheck/wiki/SC1090 +# SC1091 - Not following - +# SC2034 - Variable appears unused. Verify it or export it - https://github.com/koalaman/shellcheck/wiki/SC2034 +# SC2155 - Declare and assign separately to avoid masking return values - https://github.com/koalaman/shellcheck/wiki/SC2155 +# SC2206 - Quote to prevent word splitting/globbing, or split robustly with mapfile or read -a - https://github.com/koalaman/shellcheck/wiki/SC2206 +# SC2207 - Prefer mapfile or read -a to split command output (or quote to avoid splitting) - https://github.com/koalaman/shellcheck/wiki/SC2207 +# SC2220 - Invalid flags are not handled. Add a *) case - https://github.com/koalaman/shellcheck/wiki/SC2220 +# SC2230 - which is non-standard. Use builtin 'command -v' instead - https://github.com/koalaman/shellcheck/wiki/SC2230 diff --git a/plugins/postgres/tests/shellcheck-to-junit b/plugins/postgres/tests/shellcheck-to-junit new file mode 100755 index 000000000..f2ebf795f --- /dev/null +++ b/plugins/postgres/tests/shellcheck-to-junit @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from __future__ import print_function +import argparse +import collections +import datetime +import re +import socket +import sys + +from xml.etree import ElementTree + + +def CDATA(text=None): + element = ElementTree.Element('![CDATA[') + element.text = text + return element + + +def _serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs): + + if elem.tag == '![CDATA[': + write("\n<{}{}]]>\n".format(elem.tag, elem.text)) + if elem.tail: + write(ElementTree._escape_cdata(elem.tail)) + else: + return ElementTree._original_serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs) + + +ElementTree._original_serialize_xml = ElementTree._serialize_xml +ElementTree._serialize_xml = ElementTree._serialize['xml'] = _serialize_xml + + +def read_in(): + lines = sys.stdin.readlines() + for i in range(len(lines)): + lines[i] = lines[i].rstrip() + return lines + + +def process_lines(lines): + files = {} + current_file = None + previous_line = None + line_no = None + new_issues = [] + code = None + + RE_VIOLATION = re.compile(r"\^-- (SC[\w]+): (.*)") + RE_VIOLATION_NEW = re.compile(r"\^[-]+\^ (SC[\w]+): (.*)") + + for line in lines: + # start a new block + if line == '': + if current_file is not None: + file_data = files.get(current_file, {}) + files[current_file] = file_data + + issue_data = file_data.get(line_no, {}) + issue_data['code'] = code + files[current_file][line_no] = issue_data + + issues = issue_data.get('issues', []) + issues.extend(new_issues) + issue_data['issues'] = issues + + files[current_file][line_no] = issue_data + + code = None + current_file = None + line_no = None + elif line.startswith('In ./') and not previous_line: + current_file = line.split(' ')[1].replace('./', '') + line_no = line.split(' ')[3] + new_issues = [] + code = None + elif code is None and len(new_issues) == 0: + code = line + else: + match = RE_VIOLATION.match(line.strip()) + if not match: + match = RE_VIOLATION_NEW.match(line.strip()) + + if not match: + if 'https://www.shellcheck.net/wiki/SC' in line: + continue + if 'For more information:' == line: + continue + print('Error: Issue parsing line "{0}"'.format(line.strip())) + else: + new_issues.append({ + 'shellcheck_id': match.group(1), + 'message': match.group(2), + 'original_message': line + }) + + previous_line = line + + return files + + +def output_junit(files, args): + timestamp = datetime.datetime.now().replace(microsecond=0).isoformat() + failures = 0 + for file, data in files.items(): + for line, issue_data in data.items(): + code = issue_data.get('code') + for issue in issue_data.get('issues', []): + failures += 1 + + tests = 0 + if args.files: + with open(args.files, 'r') as f: + tests = len(f.readlines()) + + root = ElementTree.Element("testsuite", + name="shellcheck", + tests="{0}".format(tests), + failures="{0}".format(failures), + errors="0", + skipped="0", + timestamp=timestamp, + time="0", + hostname=socket.gethostname()) + + properties = ElementTree.SubElement(root, "properties") + if args.exclude: + ElementTree.SubElement(properties, + "property", + name="exclude", + value=args.exclude) + + if args.files: + with open(args.files, 'r') as f: + lines = f.readlines() + for i in range(len(lines)): + file = lines[i].rstrip().replace('./', '') + data = files.get(file, None) + if data: + for line, issue_data in data.items(): + code = issue_data.get('code') + for issue in issue_data.get('issues', []): + testcase = ElementTree.SubElement(root, + "testcase", + classname=file, + name=file, + time="0") + shellcheck_id = issue.get('shellcheck_id') + message = 'line {0}: {1}'.format( + line, issue.get('message')) + original_message = issue.get('original_message') + e = ElementTree.Element("failure", + type=shellcheck_id, + message=message) + cdata = CDATA("\n".join([code, original_message])) + e.append(cdata) + testcase.append(e) + ElementTree.SubElement(root, + "testcase", + classname=file, + name=file, + time="0") + + ElementTree.SubElement(root, "system-out") + ElementTree.SubElement(root, "system-err") + + content = ElementTree.tostring(root, encoding='UTF-8', method='xml') + if args.output: + with open(args.output, 'w') as f: + try: + f.write(content) + except TypeError: + f.write(content.decode("utf-8")) + + +def main(): + parser = argparse.ArgumentParser( + description='Process shellcheck output to junit.') + parser.add_argument('--output', + dest='output', + action='store', + default=None, + help='file to write shellcheck output') + parser.add_argument('--files', + dest='files', + action='store', + default=None, + help='a file containing a list of all files processed by shellcheck') + parser.add_argument('--exclude', + dest='exclude', + action='store', + default=None, + help='a comma-separated list of rules being excluded by shellcheck') + args = parser.parse_args() + + lines = read_in() + files = process_lines(lines) + files = collections.OrderedDict(sorted(files.items())) + output_junit(files, args) + for line in lines: + print(line) + + +if __name__ == '__main__': + main() diff --git a/plugins/postgres/tests/test_helper.bash b/plugins/postgres/tests/test_helper.bash new file mode 100755 index 000000000..fb397ab7d --- /dev/null +++ b/plugins/postgres/tests/test_helper.bash @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +export DOKKU_LIB_ROOT="/var/lib/dokku" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" + +flunk() { + { + if [ "$#" -eq 0 ]; then + cat - + else + echo "$*" + fi + } + return 1 +} + +assert_equal() { + if [ "$1" != "$2" ]; then + { + echo "expected: $1" + echo "actual: $2" + } | flunk + fi +} + +# ShellCheck doesn't know about $status from Bats +# shellcheck disable=SC2154 +assert_exit_status() { + assert_equal "$1" "$status" +} + +# ShellCheck doesn't know about $status from Bats +# shellcheck disable=SC2154 +# shellcheck disable=SC2120 +assert_success() { + if [ "$status" -ne 0 ]; then + flunk "command failed with exit status $status" + elif [ "$#" -gt 0 ]; then + assert_output "$1" + fi +} + +assert_failure() { + if [[ "$status" -eq 0 ]]; then + flunk "expected failed exit status" + elif [[ "$#" -gt 0 ]]; then + assert_output "$1" + fi +} + +assert_exists() { + if [ ! -f "$1" ]; then + flunk "expected file to exist: $1" + fi +} + +assert_contains() { + if [[ "$1" != *"$2"* ]]; then + flunk "expected $2 to be in: $1" + fi +} + +# ShellCheck doesn't know about $output from Bats +# shellcheck disable=SC2154 +assert_output() { + local expected + if [ $# -eq 0 ]; then + expected="$(cat -)" + else + expected="$1" + fi + assert_equal "$expected" "$output" +} + +# ShellCheck doesn't know about $output from Bats +# shellcheck disable=SC2154 +assert_output_contains() { + local input="$output" + local expected="$1" + local count="${2:-1}" + local found=0 + until [ "${input/$expected/}" = "$input" ]; do + input="${input/$expected/}" + found=$((found + 1)) + done + assert_equal "$count" "$found" +} diff --git a/plugins/postgres/update b/plugins/postgres/update new file mode 120000 index 000000000..f7ffc47af --- /dev/null +++ b/plugins/postgres/update @@ -0,0 +1 @@ +install \ No newline at end of file diff --git a/plugins/redis/.devcontainer/20_init_plugin b/plugins/redis/.devcontainer/20_init_plugin new file mode 100755 index 000000000..55205064a --- /dev/null +++ b/plugins/redis/.devcontainer/20_init_plugin @@ -0,0 +1,24 @@ +#!/bin/bash +set -eo pipefail + +log-info() { + declare desc="Log info formatter" + echo " $*" 1>&2 +} + +log-fail() { + declare desc="Log fail formatter" + echo "! $*" 1>&2 + exit 1 +} + +main() { + dokku plugin:install + + # built in the Dockerfile + PLUGIN_NAME="$(source /tmp/.env && echo "$PLUGIN_NAME")" + PLUGIN_VARIABLE="$(source /tmp/.env && echo "$PLUGIN_VARIABLE")" + echo "export ${PLUGIN_VARIABLE}_HOST_ROOT=${SERVICE_HOST_ROOT}/$PLUGIN_NAME" >/etc/default/dokku +} + +main "$@" diff --git a/plugins/redis/.devcontainer/Dockerfile b/plugins/redis/.devcontainer/Dockerfile new file mode 100644 index 000000000..8a6e852f5 --- /dev/null +++ b/plugins/redis/.devcontainer/Dockerfile @@ -0,0 +1,26 @@ +FROM dokku/dokku:latest + +RUN apt-get update +RUN apt-get install --no-install-recommends -y build-essential file nano && \ + apt-get clean autoclean && \ + apt-get autoremove --yes && \ + rm -rf /var/lib/apt/lists/* && \ + mkdir -p /mnt/dokku/home/dokku /mnt/dokku/var/lib/dokku/config /mnt/dokku/var/lib/dokku/data /mnt/dokku/var/lib/dokku/services && \ + chown -R dokku:dokku /mnt/dokku/home/dokku /mnt/dokku/var/lib/dokku/config /mnt/dokku/var/lib/dokku/data /mnt/dokku/var/lib/dokku/services && \ + echo "dokku.me" > /home/dokku/VHOST + +ADD https://raw.githubusercontent.com/dokku/dokku/master/tests/dhparam.pem /mnt/dokku/etc/nginx/dhparam.pem + +COPY .devcontainer/20_init_plugin /etc/my_init.d/20_init_plugin +COPY .devcontainer/bin/ /usr/local/bin/ + +COPY . . + +RUN source /tmp/config && \ + echo "export ${PLUGIN_DISABLE_PULL_VARIABLE}=true" > /tmp/.env && \ + echo "export PLUGIN_NAME=${PLUGIN_COMMAND_PREFIX}" >> /tmp/.env && \ + echo "export PLUGIN_VARIABLE=${PLUGIN_VARIABLE}" >> /tmp/.env + +RUN source /tmp/.env && \ + dokku plugin:install file:///tmp --name $PLUGIN_NAME && \ + make ci-dependencies diff --git a/plugins/redis/.devcontainer/bin/copy-file b/plugins/redis/.devcontainer/bin/copy-file new file mode 100755 index 000000000..a97030753 --- /dev/null +++ b/plugins/redis/.devcontainer/bin/copy-file @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +main() { + PLUGIN_NAME="$(source /tmp/.env && echo "$PLUGIN_NAME")" + cp "$1" "/var/lib/dokku/plugins/enabled/$PLUGIN_NAME/$1" +} + +main "$@" diff --git a/plugins/redis/.devcontainer/devcontainer.json b/plugins/redis/.devcontainer/devcontainer.json new file mode 100644 index 000000000..430d2f138 --- /dev/null +++ b/plugins/redis/.devcontainer/devcontainer.json @@ -0,0 +1,16 @@ +{ + "build": { + "dockerfile": "Dockerfile", + "context": ".." + }, + "containerEnv": { + "SERVICE_HOST_ROOT": "${localWorkspaceFolder}/tmp/data" + }, + "initializeCommand": ["mkdir", "-p", "tmp/data"], + "mounts": [ + "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind", + "source=${localWorkspaceFolder}/tmp/data/,target=/var/lib/dokku/services/,type=bind" + ], + "overrideCommand": false, + "runArgs": ["--init"] +} diff --git a/plugins/redis/.editorconfig b/plugins/redis/.editorconfig new file mode 100644 index 000000000..514928225 --- /dev/null +++ b/plugins/redis/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +insert_final_newline = true +indent_style = space +indent_size = 2 + +[Makefile] +insert_final_newline = true +indent_style = tab +indent_size = 4 + +[*.mk] +insert_final_newline = true +indent_style = tab +indent_size = 4 diff --git a/plugins/redis/.github/dependabot.yml b/plugins/redis/.github/dependabot.yml new file mode 100644 index 000000000..9d4798567 --- /dev/null +++ b/plugins/redis/.github/dependabot.yml @@ -0,0 +1,12 @@ +--- +version: 2 +updates: + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/plugins/redis/.github/labels.yml b/plugins/redis/.github/labels.yml new file mode 100644 index 000000000..0ccbb96e5 --- /dev/null +++ b/plugins/redis/.github/labels.yml @@ -0,0 +1,57 @@ +labels: +- name: bc-break + color: eb6420 +- name: blocks release + color: "000000" +- name: 'difficulty: easy' + color: c5def5 +- name: 'difficulty: hard' + color: e99695 +- name: 'difficulty: medium' + color: fef2c0 +- name: hacktoberfest + color: b0581d +- name: 'needs: documentation' + color: c2e0c6 +- name: 'needs: more info' + color: c2e0c6 +- name: 'needs: rebase' + color: c2e0c6 +- name: 'needs: tests' + color: c2e0c6 +- name: 'status: duplicate' + color: cccccc +- name: 'status: fix-provided' + color: c5def5 +- name: 'status: future' + color: c5def5 +- name: 'status: has plan' + color: c5def5 +- name: 'status: invalid' + color: cccccc +- name: 'status: merge for next minor' + color: c5def5 +- name: 'status: merge for next patch' + color: c5def5 +- name: 'status: wontfix' + color: cccccc +- name: 'type: bug' + color: e01b1b +- name: 'type: documentation' + color: 0052cc +- name: 'type: enhancement' + color: 09ab3c +- name: 'type: question' + color: cc317c +- name: 'type: refactor' + color: 0052cc +- name: 'type: rfc' + color: 0052cc +- name: 'type: roadmap' + color: 0052cc +- name: 'type: service' + color: "5319e7" +- name: 'type: support' + color: cc317c +- name: 'type: tests' + color: 0052cc diff --git a/plugins/redis/.github/workflows/ci.yml b/plugins/redis/.github/workflows/ci.yml new file mode 100644 index 000000000..279c272ad --- /dev/null +++ b/plugins/redis/.github/workflows/ci.yml @@ -0,0 +1,100 @@ +--- +name: CI + +# yamllint disable-line rule:truthy +on: + pull_request: + branches: + - "*" + push: + branches: + - master + +concurrency: + group: build-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + unit-tests-master: + name: unit-tests + runs-on: ubuntu-24.04 + env: + DOKKU_VERSION: master + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - run: make setup + + - run: sudo sysctl -w vm.max_map_count=262144 + + - run: | + git fetch -q origin master + changed=$(git --no-pager diff --name-only $GITHUB_SHA..origin/master) + if [ $changed = "Dockerfile" ]; then + echo "Please run 'make generate' to update the image version in the README.md" + else + make generate + if ! git diff --quiet README.md; then + echo "Please run 'make generate'" + git status --short + git --no-pager diff README.md + exit 1 + fi + fi + + - run: make test + + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: tmp/test-results + path: test-results + + unit-tests-0_19_0: + name: unit-tests-0.19.0 + runs-on: ubuntu-24.04 + env: + DOKKU_TAG: v0.19.0 + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - run: make setup + + - run: sudo sysctl -w vm.max_map_count=262144 + + - run: | + git fetch -q origin master + changed=$(git --no-pager diff --name-only $GITHUB_SHA..origin/master) + if [ $changed = "Dockerfile" ]; then + echo "Please run 'make generate' to update the image version in the README.md" + else + make generate + if ! git diff --quiet README.md; then + echo "Please run 'make generate'" + git status --short + git --no-pager diff README.md + exit 1 + fi + fi + + - run: make test + + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: tmp/test-results + path: test-results diff --git a/plugins/redis/.github/workflows/tagged-release.yml b/plugins/redis/.github/workflows/tagged-release.yml new file mode 100644 index 000000000..0ac7c3df9 --- /dev/null +++ b/plugins/redis/.github/workflows/tagged-release.yml @@ -0,0 +1,20 @@ +--- +name: "tagged-release" + +# yamllint disable-line rule:truthy +on: + push: + tags: + - "*" + +jobs: + tagged-release: + name: tagged-release + runs-on: ubuntu-24.04 + + steps: + - name: Release + uses: softprops/action-gh-release@v2.3.4 + with: + generate_release_notes: true + make_latest: "true" diff --git a/plugins/redis/.gitignore b/plugins/redis/.gitignore new file mode 100644 index 000000000..dd7b12a23 --- /dev/null +++ b/plugins/redis/.gitignore @@ -0,0 +1,3 @@ +/tmp +.vagrant +bootstrap.sh diff --git a/plugins/redis/Dockerfile b/plugins/redis/Dockerfile new file mode 100644 index 000000000..ae47ce594 --- /dev/null +++ b/plugins/redis/Dockerfile @@ -0,0 +1 @@ +FROM redis:8.2.1 diff --git a/plugins/redis/LICENSE.txt b/plugins/redis/LICENSE.txt new file mode 100644 index 000000000..d1e7e803c --- /dev/null +++ b/plugins/redis/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright (C) 2020 Jose Diaz-Gonzalez + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/redis/Makefile b/plugins/redis/Makefile new file mode 100644 index 000000000..d14111ab9 --- /dev/null +++ b/plugins/redis/Makefile @@ -0,0 +1,98 @@ +HARDWARE = $(shell uname -m) +SYSTEM_NAME = $(shell uname -s | tr '[:upper:]' '[:lower:]') +ARCH = $(shell dpkg --print-architecture) +SHFMT_VERSION = 3.0.2 +XUNIT_TO_GITHUB_VERSION = 0.3.0 +XUNIT_READER_VERSION = 0.1.0 + + +bats: +ifeq ($(SYSTEM_NAME),darwin) +ifneq ($(shell bats --version >/dev/null 2>&1 ; echo $$?),0) + brew install bats-core +endif +else + git clone https://github.com/bats-core/bats-core.git /tmp/bats + cd /tmp/bats && sudo ./install.sh /usr/local + rm -rf /tmp/bats +endif + +shellcheck: +ifneq ($(shell shellcheck --version >/dev/null 2>&1 ; echo $$?),0) +ifeq ($(SYSTEM_NAME),darwin) + brew install shellcheck +else +ifeq ($(ARCH),arm64) + sudo add-apt-repository 'deb http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse' +else + sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu jammy-backports main restricted universe multiverse' +endif + sudo rm -rf /var/lib/apt/lists/* && sudo apt-get clean + sudo apt-get update -qq && sudo apt-get install -qq -y shellcheck +endif +endif + +shfmt: +ifneq ($(shell shfmt --version >/dev/null 2>&1 ; echo $$?),0) +ifeq ($(shfmt),Darwin) + brew install shfmt +else + wget -qO /tmp/shfmt https://github.com/mvdan/sh/releases/download/v$(SHFMT_VERSION)/shfmt_v$(SHFMT_VERSION)_linux_amd64 + chmod +x /tmp/shfmt + sudo mv /tmp/shfmt /usr/local/bin/shfmt +endif +endif + +readlink: +ifeq ($(shell uname),Darwin) +ifeq ($(shell greadlink > /dev/null 2>&1 ; echo $$?),127) + brew install coreutils +endif + ln -nfs `which greadlink` tests/bin/readlink +endif + +ci-dependencies: shellcheck bats readlink + +lint-setup: + @mkdir -p tmp/test-results/shellcheck tmp/shellcheck + @find . -not -path '*/\.*' -type f | xargs file | grep text | awk -F ':' '{ print $$1 }' | xargs head -n1 | egrep -B1 "bash" | grep "==>" | awk '{ print $$2 }' > tmp/shellcheck/test-files + @cat tests/shellcheck-exclude | sed -n -e '/^# SC/p' | cut -d' ' -f2 | paste -d, -s - > tmp/shellcheck/exclude + +lint: lint-setup + # these are disabled due to their expansive existence in the codebase. we should clean it up though + @cat tests/shellcheck-exclude | sed -n -e '/^# SC/p' + @echo linting... + @cat tmp/shellcheck/test-files | xargs shellcheck -e $(shell cat tmp/shellcheck/exclude) | tests/shellcheck-to-junit --output tmp/test-results/shellcheck/results.xml --files tmp/shellcheck/test-files --exclude $(shell cat tmp/shellcheck/exclude) + +unit-tests: + @echo running unit tests... + @mkdir -p tmp/test-results/bats + @cd tests && echo "executing tests: $(shell cd tests ; ls *.bats | xargs)" + cd tests && bats --report-formatter junit --timing -o ../tmp/test-results/bats *.bats + +tmp/xunit-reader: + mkdir -p tmp + curl -o tmp/xunit-reader.tgz -sL https://github.com/josegonzalez/go-xunit-reader/releases/download/v$(XUNIT_READER_VERSION)/xunit-reader_$(XUNIT_READER_VERSION)_$(SYSTEM_NAME)_$(HARDWARE).tgz + tar xf tmp/xunit-reader.tgz -C tmp + chmod +x tmp/xunit-reader + +setup: + bash tests/setup.sh + $(MAKE) ci-dependencies + +test: lint unit-tests + +report: tmp/xunit-reader + tmp/xunit-reader -p 'tmp/test-results/bats/*.xml' + tmp/xunit-reader -p 'tmp/test-results/shellcheck/*.xml' + +.PHONY: clean +clean: + rm -f README.md + +.PHONY: generate +generate: clean README.md + +.PHONY: README.md +README.md: + bin/generate diff --git a/plugins/redis/README.md b/plugins/redis/README.md new file mode 100644 index 000000000..41d44eef6 --- /dev/null +++ b/plugins/redis/README.md @@ -0,0 +1,774 @@ +# dokku redis [![Build Status](https://img.shields.io/github/actions/workflow/status/dokku/dokku-redis/ci.yml?branch=master&style=flat-square "Build Status")](https://github.com/dokku/dokku-redis/actions/workflows/ci.yml?query=branch%3Amaster) [![IRC Network](https://img.shields.io/badge/irc-libera-blue.svg?style=flat-square "IRC Libera")](https://webchat.libera.chat/?channels=dokku) + +Official redis plugin for dokku. Currently defaults to installing [redis 8.2.1](https://hub.docker.com/_/redis/). + +## Requirements + +- dokku 0.19.x+ +- docker 1.8.x + +## Installation + +```shell +# on 0.19.x+ +sudo dokku plugin:install https://github.com/dokku/dokku-redis.git --name redis +``` + +## Commands + +``` +redis:app-links # list all redis service links for a given app +redis:backup [--use-iam] # create a backup of the redis service to an existing s3 bucket +redis:backup-auth # set up authentication for backups on the redis service +redis:backup-deauth # remove backup authentication for the redis service +redis:backup-schedule [--use-iam] # schedule a backup of the redis service +redis:backup-schedule-cat # cat the contents of the configured backup cronfile for the service +redis:backup-set-encryption # set encryption for all future backups of redis service +redis:backup-set-public-key-encryption # set GPG Public Key encryption for all future backups of redis service +redis:backup-unschedule # unschedule the backup of the redis service +redis:backup-unset-encryption # unset encryption for future backups of the redis service +redis:backup-unset-public-key-encryption # unset GPG Public Key encryption for future backups of the redis service +redis:clone [--clone-flags...] # create container then copy data from into +redis:connect # connect to the service via the redis connection tool +redis:create [--create-flags...] # create a redis service +redis:destroy [-f|--force] # delete the redis service/data/container if there are no links left +redis:enter # enter or run a command in a running redis service container +redis:exists # check if the redis service exists +redis:export # export a dump of the redis service database +redis:expose # expose a redis service on custom host:port if provided (random port on the 0.0.0.0 interface if otherwise unspecified) +redis:import # import a dump into the redis service database +redis:info [--single-info-flag] # print the service information +redis:link [--link-flags...] # link the redis service to the app +redis:linked # check if the redis service is linked to an app +redis:links # list all apps linked to the redis service +redis:list # list all redis services +redis:logs [-t|--tail] # print the most recent log(s) for this service +redis:pause # pause a running redis service +redis:promote # promote service as REDIS_URL in +redis:restart # graceful shutdown and restart of the redis service container +redis:set # set or clear a property for a service +redis:start # start a previously stopped redis service +redis:stop # stop a running redis service +redis:unexpose # unexpose a previously exposed redis service +redis:unlink # unlink the redis service from the app +redis:upgrade [--upgrade-flags...] # upgrade service to the specified versions +``` + +## Usage + +Help for any commands can be displayed by specifying the command as an argument to redis:help. Plugin help output in conjunction with any files in the `docs/` folder is used to generate the plugin documentation. Please consult the `redis:help` command for any undocumented commands. + +### Basic Usage + +### create a redis service + +```shell +# usage +dokku redis:create [--create-flags...] +``` + +flags: + +- `-c|--config-options "--args --go=here"`: extra arguments to pass to the container create command (default: `None`) +- `-C|--custom-env "USER=alpha;HOST=beta"`: semi-colon delimited environment variables to start the service with +- `-i|--image IMAGE`: the image name to start the service with +- `-I|--image-version IMAGE_VERSION`: the image version to start the service with +- `-m|--memory MEMORY`: container memory limit in megabytes (default: unlimited) +- `-N|--initial-network INITIAL_NETWORK`: the initial network to attach the service to +- `-p|--password PASSWORD`: override the user-level service password +- `-P|--post-create-network NETWORKS`: a comma-separated list of networks to attach the service container to after service creation +- `-r|--root-password PASSWORD`: override the root-level service password +- `-S|--post-start-network NETWORKS`: a comma-separated list of networks to attach the service container to after service start +- `-s|--shm-size SHM_SIZE`: override shared memory size for redis docker container + +Create a redis service named lollipop: + +```shell +dokku redis:create lollipop +``` + +You can also specify the image and image version to use for the service. It *must* be compatible with the redis image. + +```shell +export REDIS_IMAGE="redis" +export REDIS_IMAGE_VERSION="${PLUGIN_IMAGE_VERSION}" +dokku redis:create lollipop +``` + +You can also specify custom environment variables to start the redis service in semicolon-separated form. + +```shell +export REDIS_CUSTOM_ENV="USER=alpha;HOST=beta" +dokku redis:create lollipop +``` + +### print the service information + +```shell +# usage +dokku redis:info [--single-info-flag] +``` + +flags: + +- `--config-dir`: show the service configuration directory +- `--data-dir`: show the service data directory +- `--dsn`: show the service DSN +- `--exposed-ports`: show service exposed ports +- `--id`: show the service container id +- `--internal-ip`: show the service internal ip +- `--initial-network`: show the initial network being connected to +- `--links`: show the service app links +- `--post-create-network`: show the networks to attach to after service container creation +- `--post-start-network`: show the networks to attach to after service container start +- `--service-root`: show the service root directory +- `--status`: show the service running status +- `--version`: show the service image version + +Get connection information as follows: + +```shell +dokku redis:info lollipop +``` + +You can also retrieve a specific piece of service info via flags: + +```shell +dokku redis:info lollipop --config-dir +dokku redis:info lollipop --data-dir +dokku redis:info lollipop --dsn +dokku redis:info lollipop --exposed-ports +dokku redis:info lollipop --id +dokku redis:info lollipop --internal-ip +dokku redis:info lollipop --initial-network +dokku redis:info lollipop --links +dokku redis:info lollipop --post-create-network +dokku redis:info lollipop --post-start-network +dokku redis:info lollipop --service-root +dokku redis:info lollipop --status +dokku redis:info lollipop --version +``` + +### list all redis services + +```shell +# usage +dokku redis:list +``` + +List all services: + +```shell +dokku redis:list +``` + +### print the most recent log(s) for this service + +```shell +# usage +dokku redis:logs [-t|--tail] +``` + +flags: + +- `-t|--tail []`: do not stop when end of the logs are reached and wait for additional output + +You can tail logs for a particular service: + +```shell +dokku redis:logs lollipop +``` + +By default, logs will not be tailed, but you can do this with the --tail flag: + +```shell +dokku redis:logs lollipop --tail +``` + +The default tail setting is to show all logs, but an initial count can also be specified: + +```shell +dokku redis:logs lollipop --tail 5 +``` + +### link the redis service to the app + +```shell +# usage +dokku redis:link [--link-flags...] +``` + +flags: + +- `-a|--alias "BLUE_DATABASE"`: an alternative alias to use for linking to an app via environment variable +- `-q|--querystring "pool=5"`: ampersand delimited querystring arguments to append to the service link +- `-n|--no-restart "false"`: whether or not to restart the app on link (default: true) + +A redis service can be linked to a container. This will use native docker links via the docker-options plugin. Here we link it to our `playground` app. + +> NOTE: this will restart your app + +```shell +dokku redis:link lollipop playground +``` + +The following environment variables will be set automatically by docker (not on the app itself, so they won’t be listed when calling dokku config): + +``` +DOKKU_REDIS_LOLLIPOP_NAME=/lollipop/DATABASE +DOKKU_REDIS_LOLLIPOP_PORT=tcp://172.17.0.1:6379 +DOKKU_REDIS_LOLLIPOP_PORT_6379_TCP=tcp://172.17.0.1:6379 +DOKKU_REDIS_LOLLIPOP_PORT_6379_TCP_PROTO=tcp +DOKKU_REDIS_LOLLIPOP_PORT_6379_TCP_PORT=6379 +DOKKU_REDIS_LOLLIPOP_PORT_6379_TCP_ADDR=172.17.0.1 +``` + +The following will be set on the linked application by default: + +``` +REDIS_URL=redis://:SOME_PASSWORD@dokku-redis-lollipop:6379 +``` + +The host exposed here only works internally in docker containers. If you want your container to be reachable from outside, you should use the `expose` subcommand. Another service can be linked to your app: + +```shell +dokku redis:link other_service playground +``` + +It is possible to change the protocol for `REDIS_URL` by setting the environment variable `REDIS_DATABASE_SCHEME` on the app. Doing so will after linking will cause the plugin to think the service is not linked, and we advise you to unlink before proceeding. + +```shell +dokku config:set playground REDIS_DATABASE_SCHEME=redis2 +dokku redis:link lollipop playground +``` + +This will cause `REDIS_URL` to be set as: + +``` +redis2://:SOME_PASSWORD@dokku-redis-lollipop:6379 +``` + +### unlink the redis service from the app + +```shell +# usage +dokku redis:unlink +``` + +flags: + +- `-n|--no-restart "false"`: whether or not to restart the app on unlink (default: true) + +You can unlink a redis service: + +> NOTE: this will restart your app and unset related environment variables + +```shell +dokku redis:unlink lollipop playground +``` + +### set or clear a property for a service + +```shell +# usage +dokku redis:set +``` + +Set the network to attach after the service container is started: + +```shell +dokku redis:set lollipop post-create-network custom-network +``` + +Set multiple networks: + +```shell +dokku redis:set lollipop post-create-network custom-network,other-network +``` + +Unset the post-create-network value: + +```shell +dokku redis:set lollipop post-create-network +``` + +### Service Lifecycle + +The lifecycle of each service can be managed through the following commands: + +### connect to the service via the redis connection tool + +```shell +# usage +dokku redis:connect +``` + +Connect to the service via the redis connection tool: + +> NOTE: disconnecting from ssh while running this command may leave zombie processes due to moby/moby#9098 + +```shell +dokku redis:connect lollipop +``` + +### enter or run a command in a running redis service container + +```shell +# usage +dokku redis:enter +``` + +A bash prompt can be opened against a running service. Filesystem changes will not be saved to disk. + +> NOTE: disconnecting from ssh while running this command may leave zombie processes due to moby/moby#9098 + +```shell +dokku redis:enter lollipop +``` + +You may also run a command directly against the service. Filesystem changes will not be saved to disk. + +```shell +dokku redis:enter lollipop touch /tmp/test +``` + +### expose a redis service on custom host:port if provided (random port on the 0.0.0.0 interface if otherwise unspecified) + +```shell +# usage +dokku redis:expose +``` + +Expose the service on the service's normal ports, allowing access to it from the public interface (`0.0.0.0`): + +```shell +dokku redis:expose lollipop 6379 +``` + +Expose the service on the service's normal ports, with the first on a specified ip address (127.0.0.1): + +```shell +dokku redis:expose lollipop 127.0.0.1:6379 +``` + +### unexpose a previously exposed redis service + +```shell +# usage +dokku redis:unexpose +``` + +Unexpose the service, removing access to it from the public interface (`0.0.0.0`): + +```shell +dokku redis:unexpose lollipop +``` + +### promote service as REDIS_URL in + +```shell +# usage +dokku redis:promote +``` + +If you have a redis service linked to an app and try to link another redis service another link environment variable will be generated automatically: + +``` +DOKKU_REDIS_BLUE_URL=redis://:ANOTHER_PASSWORD@dokku-redis-other-service:6379/other_service +``` + +You can promote the new service to be the primary one: + +> NOTE: this will restart your app + +```shell +dokku redis:promote other_service playground +``` + +This will replace `REDIS_URL` with the url from other_service and generate another environment variable to hold the previous value if necessary. You could end up with the following for example: + +``` +REDIS_URL=redis://:ANOTHER_PASSWORD@dokku-redis-other-service:6379/other_service +DOKKU_REDIS_BLUE_URL=redis://:ANOTHER_PASSWORD@dokku-redis-other-service:6379/other_service +DOKKU_REDIS_SILVER_URL=redis://:SOME_PASSWORD@dokku-redis-lollipop:6379/lollipop +``` + +### start a previously stopped redis service + +```shell +# usage +dokku redis:start +``` + +Start the service: + +```shell +dokku redis:start lollipop +``` + +### stop a running redis service + +```shell +# usage +dokku redis:stop +``` + +Stop the service and removes the running container: + +```shell +dokku redis:stop lollipop +``` + +### pause a running redis service + +```shell +# usage +dokku redis:pause +``` + +Pause the running container for the service: + +```shell +dokku redis:pause lollipop +``` + +### graceful shutdown and restart of the redis service container + +```shell +# usage +dokku redis:restart +``` + +Restart the service: + +```shell +dokku redis:restart lollipop +``` + +### upgrade service to the specified versions + +```shell +# usage +dokku redis:upgrade [--upgrade-flags...] +``` + +flags: + +- `-c|--config-options "--args --go=here"`: extra arguments to pass to the container create command (default: `None`) +- `-C|--custom-env "USER=alpha;HOST=beta"`: semi-colon delimited environment variables to start the service with +- `-i|--image IMAGE`: the image name to start the service with +- `-I|--image-version IMAGE_VERSION`: the image version to start the service with +- `-N|--initial-network INITIAL_NETWORK`: the initial network to attach the service to +- `-P|--post-create-network NETWORKS`: a comma-separated list of networks to attach the service container to after service creation +- `-R|--restart-apps "true"`: whether or not to force an app restart (default: false) +- `-S|--post-start-network NETWORKS`: a comma-separated list of networks to attach the service container to after service start +- `-s|--shm-size SHM_SIZE`: override shared memory size for redis docker container + +You can upgrade an existing service to a new image or image-version: + +```shell +dokku redis:upgrade lollipop +``` + +### Service Automation + +Service scripting can be executed using the following commands: + +### list all redis service links for a given app + +```shell +# usage +dokku redis:app-links +``` + +List all redis services that are linked to the `playground` app. + +```shell +dokku redis:app-links playground +``` + +### create container then copy data from into + +```shell +# usage +dokku redis:clone [--clone-flags...] +``` + +flags: + +- `-c|--config-options "--args --go=here"`: extra arguments to pass to the container create command (default: `None`) +- `-C|--custom-env "USER=alpha;HOST=beta"`: semi-colon delimited environment variables to start the service with +- `-i|--image IMAGE`: the image name to start the service with +- `-I|--image-version IMAGE_VERSION`: the image version to start the service with +- `-m|--memory MEMORY`: container memory limit in megabytes (default: unlimited) +- `-N|--initial-network INITIAL_NETWORK`: the initial network to attach the service to +- `-p|--password PASSWORD`: override the user-level service password +- `-P|--post-create-network NETWORKS`: a comma-separated list of networks to attach the service container to after service creation +- `-r|--root-password PASSWORD`: override the root-level service password +- `-S|--post-start-network NETWORKS`: a comma-separated list of networks to attach the service container to after service start +- `-s|--shm-size SHM_SIZE`: override shared memory size for redis docker container + +You can clone an existing service to a new one: + +```shell +dokku redis:clone lollipop lollipop-2 +``` + +### check if the redis service exists + +```shell +# usage +dokku redis:exists +``` + +Here we check if the lollipop redis service exists. + +```shell +dokku redis:exists lollipop +``` + +### check if the redis service is linked to an app + +```shell +# usage +dokku redis:linked +``` + +Here we check if the lollipop redis service is linked to the `playground` app. + +```shell +dokku redis:linked lollipop playground +``` + +### list all apps linked to the redis service + +```shell +# usage +dokku redis:links +``` + +List all apps linked to the `lollipop` redis service. + +```shell +dokku redis:links lollipop +``` + +### Data Management + +The underlying service data can be imported and exported with the following commands: + +### import a dump into the redis service database + +```shell +# usage +dokku redis:import +``` + +Import a datastore dump: + +```shell +dokku redis:import lollipop < data.dump +``` + +### export a dump of the redis service database + +```shell +# usage +dokku redis:export +``` + +By default, datastore output is exported to stdout: + +```shell +dokku redis:export lollipop +``` + +You can redirect this output to a file: + +```shell +dokku redis:export lollipop > data.dump +``` + +### Backups + +Datastore backups are supported via AWS S3 and S3 compatible services like [minio](https://github.com/minio/minio). + +You may skip the `backup-auth` step if your dokku install is running within EC2 and has access to the bucket via an IAM profile. In that case, use the `--use-iam` option with the `backup` command. + +Backups can be performed using the backup commands: + +### set up authentication for backups on the redis service + +```shell +# usage +dokku redis:backup-auth +``` + +Setup s3 backup authentication: + +```shell +dokku redis:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY +``` + +Setup s3 backup authentication with different region: + +```shell +dokku redis:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION +``` + +Setup s3 backup authentication with different signature version and endpoint: + +```shell +dokku redis:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION AWS_SIGNATURE_VERSION ENDPOINT_URL +``` + +More specific example for minio auth: + +```shell +dokku redis:backup-auth lollipop MINIO_ACCESS_KEY_ID MINIO_SECRET_ACCESS_KEY us-east-1 s3v4 https://YOURMINIOSERVICE +``` + +### remove backup authentication for the redis service + +```shell +# usage +dokku redis:backup-deauth +``` + +Remove s3 authentication: + +```shell +dokku redis:backup-deauth lollipop +``` + +### create a backup of the redis service to an existing s3 bucket + +```shell +# usage +dokku redis:backup [--use-iam] +``` + +flags: + +- `-u|--use-iam`: use the IAM profile associated with the current server + +Backup the `lollipop` service to the `my-s3-bucket` bucket on `AWS`:` + +```shell +dokku redis:backup lollipop my-s3-bucket --use-iam +``` + +Restore a backup file (assuming it was extracted via `tar -xf backup.tgz`): + +```shell +dokku redis:import lollipop < backup-folder/export +``` + +### set encryption for all future backups of redis service + +```shell +# usage +dokku redis:backup-set-encryption +``` + +Set the GPG-compatible passphrase for encrypting backups for backups: + +```shell +dokku redis:backup-set-encryption lollipop +``` + +### set GPG Public Key encryption for all future backups of redis service + +```shell +# usage +dokku redis:backup-set-public-key-encryption +``` + +Set the `GPG` Public Key for encrypting backups: + +```shell +dokku redis:backup-set-public-key-encryption lollipop +``` + +### unset encryption for future backups of the redis service + +```shell +# usage +dokku redis:backup-unset-encryption +``` + +Unset the `GPG` encryption passphrase for backups: + +```shell +dokku redis:backup-unset-encryption lollipop +``` + +### unset GPG Public Key encryption for future backups of the redis service + +```shell +# usage +dokku redis:backup-unset-public-key-encryption +``` + +Unset the `GPG` Public Key encryption for backups: + +```shell +dokku redis:backup-unset-public-key-encryption lollipop +``` + +### schedule a backup of the redis service + +```shell +# usage +dokku redis:backup-schedule [--use-iam] +``` + +flags: + +- `-u|--use-iam`: use the IAM profile associated with the current server + +Schedule a backup: + +> 'schedule' is a crontab expression, eg. "0 3 * * *" for each day at 3am + +```shell +dokku redis:backup-schedule lollipop "0 3 * * *" my-s3-bucket +``` + +Schedule a backup and authenticate via iam: + +```shell +dokku redis:backup-schedule lollipop "0 3 * * *" my-s3-bucket --use-iam +``` + +### cat the contents of the configured backup cronfile for the service + +```shell +# usage +dokku redis:backup-schedule-cat +``` + +Cat the contents of the configured backup cronfile for the service: + +```shell +dokku redis:backup-schedule-cat lollipop +``` + +### unschedule the backup of the redis service + +```shell +# usage +dokku redis:backup-unschedule +``` + +Remove the scheduled backup from cron: + +```shell +dokku redis:backup-unschedule lollipop +``` + +### Disabling `docker image pull` calls + +If you wish to disable the `docker image pull` calls that the plugin triggers, you may set the `REDIS_DISABLE_PULL` environment variable to `true`. Once disabled, you will need to pull the service image you wish to deploy as shown in the `stderr` output. + +Please ensure the proper images are in place when `docker image pull` is disabled. diff --git a/plugins/redis/Vagrantfile b/plugins/redis/Vagrantfile new file mode 100644 index 000000000..4e6545b64 --- /dev/null +++ b/plugins/redis/Vagrantfile @@ -0,0 +1,27 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +BOX_NAME = ENV["BOX_NAME"] || "bento/ubuntu-24.04" +BOX_MEMORY = ENV["BOX_MEMORY"] || "2048" +DOKKU_VERSION = "master" + +Vagrant.configure(2) do |config| + config.vm.box = BOX_NAME + config.ssh.forward_agent = true + + config.vm.provider :virtualbox do |vb| + vb.customize ["modifyvm", :id, "--memory", BOX_MEMORY] + end + + config.vm.provider :vmware_fusion do |v, override| + v.vmx["memsize"] = BOX_MEMORY + end + + config.vm.define "default", primary: true do |vm| + vm.vm.synced_folder File.dirname(__FILE__), "/vagrant" + + vm.vm.provision :shell, :inline => "apt -q update && apt -y -qq install git software-properties-common" + vm.vm.provision :shell, :inline => "cd /vagrant && DOKKU_VERSION=#{DOKKU_VERSION} make setup" + vm.vm.provision :shell, :inline => "cd /vagrant && DOKKU_TRACE=1 DOKKU_VERSION=#{DOKKU_VERSION} make test" + end +end diff --git a/plugins/redis/bin/generate b/plugins/redis/bin/generate new file mode 100755 index 000000000..1a8e5c538 --- /dev/null +++ b/plugins/redis/bin/generate @@ -0,0 +1,677 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from __future__ import print_function + +import os +import re + + +def compile( + service, + version, + variable, + alias, + image, + scheme, + ports, + sponsors, + options, + unimplemented, + dokku_version, +): + prefix = "\n\n".join( + [ + header(service), + description(service, image, version), + ] + ) + + if len(sponsors) > 0: + prefix += "\n\n" + prefix += sponsors_section(service, sponsors) + + return ( + "\n\n".join( + [ + prefix, + requirements_section(dokku_version), + installation_section(service, dokku_version), + commands_section( + service, variable, alias, image, scheme, ports, unimplemented + ), + usage_section( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + ), + ] + ) + .replace("\n\n\n\n\n", "\n") + .replace("\n\n\n\n", "\n") + .replace("\n\n\n", "\n\n") + ) + + +def header(service): + return " ".join( + [ + f"# dokku {service}", + f'[![Build Status](https://img.shields.io/github/actions/workflow/status/dokku/dokku-{service}/ci.yml?branch=master&style=flat-square "Build Status")](https://github.com/dokku/dokku-{service}/actions/workflows/ci.yml?query=branch%3Amaster)', + f'[![IRC Network](https://img.shields.io/badge/irc-libera-blue.svg?style=flat-square "IRC Libera")](https://webchat.libera.chat/?channels=dokku)', + ] + ) + + +def description(service, full_image, version): + base = "_" + image = full_image + if "/" in full_image: + base = "r/" + full_image.split("/")[0] + image = full_image.split("/")[1] + + return f"Official {service} plugin for dokku. Currently defaults to installing [{full_image} {version}](https://hub.docker.com/{base}/{image}/)." + + +def sponsors_section(service, sponsors): + if len(sponsors) == 0: + return "" + + sponsor_data = [ + "## Sponsors", + "", + f"The {service} plugin was generously sponsored by the following:", + "", + ] + sponsor_data.extend([f"- [{s}](https://github.com/{s})" for s in sponsors]) + + return "\n".join(sponsor_data) + + +def requirements_section(dokku_version): + return "\n".join( + [ + "## Requirements", + "", + f"- dokku {dokku_version}", + "- docker 1.8.x", + ] + ) + + +def installation_section(service, dokku_version): + return "\n".join( + [ + "## Installation", + "", + "```shell", + f"# on {dokku_version}", + f"sudo dokku plugin:install https://github.com/dokku/dokku-{service}.git --name {service}", + "```", + ] + ) + + +def commands_section(service, variable, alias, image, scheme, ports, unimplemented): + content = [ + "## Commands", + "", + "```", + ] + + subcommands = os.listdir("subcommands") + subcommands.sort() + + command_list = [] + descriptions = [] + for filename in subcommands: + if filename in unimplemented: + continue + data = command_data(filename, service, variable, alias, image, scheme, ports) + description = data["description"] + arguments = data["arguments_string"] + + command_list.append(f"{service}:{filename} {arguments}") + descriptions.append(description) + + maxlen = max(map(len, command_list)) + if maxlen > 50: + maxlen = 50 + for command, description in zip(command_list, descriptions): + space_count = maxlen - len(command) + content.append("{0}{1} # {2}".format(command, " " * space_count, description)) + + content.append("```") + return "\n".join(content) + + +def usage_section( + service, variable, alias, image, scheme, ports, options, unimplemented +): + return "\n\n".join( + [ + "## Usage", + f"Help for any commands can be displayed by specifying the command as an argument to {service}:help. Plugin help output in conjunction with any files in the `docs/` folder is used to generate the plugin documentation. Please consult the `{service}:help` command for any undocumented commands.", + usage_intro( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + usage_lifecycle( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + usage_automation( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + usage_data_management( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + usage_backup( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + usage_docker_pull( + service, variable, alias, image, scheme, ports, options, unimplemented + ), + ] + ) + + +def usage_intro(service, variable, alias, image, scheme, ports, options, unimplemented): + commands = ["create", "info", "list", "logs", "link", "unlink", "set"] + content = ["### Basic Usage"] + + return fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, + ) + + +def usage_lifecycle( + service, variable, alias, image, scheme, ports, options, unimplemented +): + commands = [ + "connect", + "enter", + "expose", + "unexpose", + "promote", + "start", + "stop", + "pause", + "restart", + "upgrade", + ] + content = [ + "### Service Lifecycle", + "", + "The lifecycle of each service can be managed through the following commands:", + "", + ] + + return fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, + ) + + +def usage_automation( + service, variable, alias, image, scheme, ports, options, unimplemented +): + commands = ["app-links", "clone", "exists", "linked", "links"] + content = [ + "### Service Automation", + "", + "Service scripting can be executed using the following commands:", + "", + ] + + return fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, + ) + + +def usage_data_management( + service, variable, alias, image, scheme, ports, options, unimplemented +): + commands = ["import", "export"] + content = [ + "### Data Management", + "", + "The underlying service data can be imported and exported with the following commands:", + "", + ] + + return fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, + ) + + +def usage_backup( + service, variable, alias, image, scheme, ports, options, unimplemented +): + commands = [ + "backup-auth", + "backup-deauth", + "backup", + "backup-set-encryption", + "backup-set-public-key-encryption", + "backup-unset-encryption", + "backup-unset-public-key-encryption", + "backup-schedule", + "backup-schedule-cat", + "backup-unschedule", + ] + content = [ + "### Backups", + "", + "Datastore backups are supported via AWS S3 and S3 compatible services like [minio](https://github.com/minio/minio).", + "", + "You may skip the `backup-auth` step if your dokku install is running within EC2 and has access to the bucket via an IAM profile. In that case, use the `--use-iam` option with the `backup` command.", + "", + "Backups can be performed using the backup commands:", + "", + ] + + return fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, + ) + + +def usage_docker_pull( + service, variable, alias, image, scheme, ports, options, unimplemented +): + service_prefix = service.upper() + return "\n".join( + [ + "### Disabling `docker image pull` calls", + "", + f"If you wish to disable the `docker image pull` calls that the plugin triggers, you may set the `{service_prefix}_DISABLE_PULL` environment variable to `true`. Once disabled, you will need to pull the service image you wish to deploy as shown in the `stderr` output.", + "", + "Please ensure the proper images are in place when `docker image pull` is disabled.", + ] + ) + + +def fetch_commands_content( + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + commands, + content, +): + i = 0 + for command in commands: + output = command_help( + command, + service, + variable, + alias, + image, + scheme, + ports, + options, + unimplemented, + ) + if output == "": + continue + content.append(output) + i += 1 + + if i == 0: + return "" + + return "\n".join(content) + + +def parse_args(line): + line = line.strip() + arguments = [] + for arg in re.findall("([A-Z_]+)", line): + arg = arg.replace("_", "-").lower() + if arg.endswith("optional-flag"): + arg = arg.replace("-optional-flag", "") + arguments.append(f"[--{arg}]") + elif arg.endswith("-flag"): + if arg == "info-flag": + arguments.append(f"[--single-info-flag]") + else: + arg = arg.replace("-flag", "") + first_letter = arg[0] + arguments.append(f"[-{first_letter}|--{arg}]") + elif arg.endswith("-flags-list"): + arg = arg.replace("-list", "") + arguments.append(f"[--{arg}...]") + elif arg.endswith("list"): + arg = arg.replace("-list", "") + arguments.append(f"<{arg}...>") + else: + arguments.append(f"<{arg}>") + return " ".join(arguments) + + +def command_help( + command, service, variable, alias, image, scheme, ports, options, unimplemented +): + if command in unimplemented: + return "" + + data = command_data(command, service, variable, alias, image, scheme, ports) + content = [ + f"### {data['description']}", + "", + "```shell", + "# usage", + f"dokku {service}:{command} {data['arguments_string']}".strip(), + "```", + ] + + # if len(data["arguments"]) > 0: + # content.append("") + # content.append("arguments:") + # content.append("") + # for argument in data["arguments"]: + # content.append(f"- {argument}") + + if len(data["flags"]) > 0: + content.append("") + content.append("flags:") + content.append("") + for flag in data["flags"]: + if "--config-options" in flag and options != "": + flag = f"{flag} (default: `{options}`)" + content.append(f"- {flag}") + + if len(data["examples"]) > 0: + content.append("") + content.append(data["examples"]) + + doc_file = os.path.join("docs", f"{command}.md") + if os.path.isfile(doc_file): + content.append("") + with open(doc_file) as f: + content.append(f.read()) + + return "\n" + "\n".join(content) + + +def command_data(command, service, variable, alias, image, scheme, ports): + description = None + arguments = [] + arguments_string = "" + example_lines = [] + flags = [] + with open(os.path.join("subcommands", command)) as f: + for line in f.readlines(): + line = line.strip() + line = line.replace("$PLUGIN_SERVICE", service) + line = line.replace("$PLUGIN_COMMAND_PREFIX", service) + line = line.replace("${PLUGIN_COMMAND_PREFIX}", service) + line = line.replace("${PLUGIN_VARIABLE}", variable) + line = line.replace("${PLUGIN_DEFAULT_ALIAS}", alias) + line = line.replace("${PLUGIN_IMAGE}", image) + line = line.replace("${PLUGIN_SCHEME}", scheme) + line = line.replace("${PLUGIN_DATASTORE_PORTS[0]}", ports[0]) + line = line.replace("${PLUGIN_DATASTORE_PORTS[@]}", " ".join(ports)) + + if "declare desc" in line: + description = re.search('"(.+)"', line).group(1) + elif "$1" in line: + arguments_string = parse_args(line) + elif line.startswith("#A "): + argument = line.replace("#A ", "") + parts = [a.strip() for a in argument.split(",", 1)] + arguments.append(f"`{parts[0]}`: {parts[1]}") + elif line.startswith("#F "): + flag = line.replace("#F ", "") + parts = [a.strip() for a in flag.split(",", 1)] + flags.append(f"`{parts[0]}`: {parts[1]}") + elif line.startswith("#E "): + example_lines.append(line.replace("#E ", "")) + + examples = [] + sentence_lines = [] + command_lines = [] + codeblock_lines = [] + blockquote_lines = [] + for line in example_lines: + if line.startswith("export") or line.startswith("dokku"): + if len(blockquote_lines) > 0: + examples.append("\n" + process_blockquote(blockquote_lines)) + blockquote_lines = [] + if len(codeblock_lines) > 0: + examples.append("\n" + process_codeblock(codeblock_lines)) + codeblock_lines = [] + if len(sentence_lines) > 0: + examples.append("\n" + process_sentence(sentence_lines)) + sentence_lines = [] + + command_lines.append(line) + elif line.startswith(" "): + if len(blockquote_lines) > 0: + examples.append("\n" + process_blockquote(blockquote_lines)) + blockquote_lines = [] + if len(command_lines) > 0: + examples.append("\n" + process_command(command_lines)) + command_lines = [] + if len(sentence_lines) > 0: + examples.append("\n" + process_sentence(sentence_lines)) + sentence_lines = [] + + codeblock_lines.append(line.strip()) + elif line.startswith(">"): + if len(codeblock_lines) > 0: + examples.append("\n" + process_codeblock(codeblock_lines)) + codeblock_lines = [] + if len(command_lines) > 0: + examples.append("\n" + process_command(command_lines)) + command_lines = [] + if len(sentence_lines) > 0: + examples.append("\n" + process_sentence(sentence_lines)) + sentence_lines = [] + + blockquote_lines.append(line) + else: + if len(blockquote_lines) > 0: + examples.append("\n" + process_blockquote(blockquote_lines)) + blockquote_lines = [] + if len(codeblock_lines) > 0: + examples.append("\n" + process_codeblock(codeblock_lines)) + codeblock_lines = [] + if len(command_lines) > 0: + examples.append("\n" + process_command(command_lines)) + command_lines = [] + + sentence_lines.append(line) + + if len(blockquote_lines) > 0: + examples.append("\n" + process_blockquote(blockquote_lines)) + blockquote_lines = [] + if len(codeblock_lines) > 0: + examples.append("\n" + process_codeblock(codeblock_lines)) + codeblock_lines = [] + if len(command_lines) > 0: + examples.append("\n" + process_command(command_lines)) + command_lines = [] + if len(sentence_lines) > 0: + examples.append("\n" + process_sentence(sentence_lines)) + sentence_lines = [] + + return { + "description": description, + "arguments_string": arguments_string, + "arguments": arguments, + "flags": flags, + "examples": "\n".join(examples).strip(), + } + + +def process_sentence(sentence_lines): + sentence_lines = " ".join(sentence_lines) + sentences = ". ".join( + upperfirst(i.strip()) for i in sentence_lines.split(". ") + ).strip() + if not sentences.endswith(".") and not sentences.endswith(":"): + sentences += ":" + + text = [] + for sentence in sentences.split(". "): + parts = [] + for word in sentence.strip().split(" "): + if word.isupper() and len(word) > 1: + for ending in [":", "."]: + if word.endswith(ending): + word = "`{0}`{1}".format(word[:-1], ending) + else: + word = "`{0}`".format(word) + parts.append(word) + text.append(" ".join(parts)) + + text = ". ".join(text) + + # some cleanup + text = text.replace("(0.0.0.0)", "(`0.0.0.0`)") + text = text.replace("'", "`") + text = text.replace("`s", "'s") + text = text.replace("``", "`") + text = text.strip(" ") + + return text + + +def upperfirst(x): + return x[:1].upper() + x[1:] + + +def process_blockquote(blockquote_lines): + return "\n".join(blockquote_lines) + + +def process_command(command_lines): + command_lines = "\n".join(command_lines) + return f"```shell\n{command_lines}\n```" + + +def process_codeblock(codeblock_lines): + codeblock_lines = "\n".join(codeblock_lines) + return f"```\n{codeblock_lines}\n```" + + +def main(): + service = None + version = None + variable = None + image = None + alias = None + options = None + unimplemented = [] + + with open("Dockerfile") as f: + for line in f.readlines(): + if "FROM " in line: + image, version = line.split(" ")[1].split(":") + image = image.strip() + version = version.strip() + + with open("config") as f: + for line in f.readlines(): + if "PLUGIN_COMMAND_PREFIX=" in line: + service = re.search('"(.+)"', line).group(1) + if "PLUGIN_DEFAULT_ALIAS=" in line: + alias = re.search('"(.+)"', line).group(1) + if "PLUGIN_VARIABLE=" in line: + variable = re.search('"(.+)"', line).group(1) + if "PLUGIN_SCHEME=" in line: + scheme = re.search('"(.+)"', line).group(1) + if "PLUGIN_DATASTORE_PORTS=" in line: + ports = re.search("\((.+)\)", line).group(1).split(" ") + if "PLUGIN_UNIMPLEMENTED_SUBCOMMANDS=" in line: + match = re.search("\((.+)\)", line) + if match is not None: + unimplemented = [s.strip('"') for s in match.group(1).split(" ")] + + with open("config") as f: + for line in f.readlines(): + if f"{variable}_CONFIG_OPTIONS" in line: + match = re.search('"(.+)"', line) + if match is not None: + options = match.group(1) + + sponsors = [] + with open("plugin.toml") as f: + for line in f.readlines(): + if line.startswith("sponsors"): + sponsors = re.search('\[(["\w\s,_-]+)\]', line).group(1) + sponsors = [s.strip('"') for s in sponsors.split(",")] + + text = compile( + service, + version, + variable, + alias, + image, + scheme, + ports, + sponsors, + options, + unimplemented, + "0.19.x+", + ) + + base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + readme_file = os.path.join(base_path, "README.md") + with open(readme_file, "w") as f: + f.write(text + "\n") + + +if __name__ == "__main__": + main() diff --git a/plugins/redis/commands b/plugins/redis/commands new file mode 100755 index 000000000..0b22b42f5 --- /dev/null +++ b/plugins/redis/commands @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +[[ " help $PLUGIN_COMMAND_PREFIX:help $PLUGIN_COMMAND_PREFIX $PLUGIN_COMMAND_PREFIX:default " == *" $1 "* ]] || [[ "$1" == "$PLUGIN_COMMAND_PREFIX:"* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/help-functions" + +if [[ ! -d $PLUGIN_CONFIG_ROOT ]]; then + dokku_log_fail "$PLUGIN_SERVICE: Please run: sudo dokku plugin:install" +fi + +if [[ ! -d $PLUGIN_DATA_ROOT ]]; then + dokku_log_fail "$PLUGIN_SERVICE: Please run: sudo dokku plugin:install" +fi + +fn-help "$@" diff --git a/plugins/redis/common-functions b/plugins/redis/common-functions new file mode 100755 index 000000000..5c4108906 --- /dev/null +++ b/plugins/redis/common-functions @@ -0,0 +1,1056 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" + +add_to_links_file() { + declare desc="add an app to the service link file" + declare SERVICE="$1" APP="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + touch "$LINKS_FILE" + sed -i.bak "/^$APP\$/d" "$LINKS_FILE" && rm "$LINKS_FILE.bak" + echo "$APP" >>"$LINKS_FILE" + sort "$LINKS_FILE" -u -o "$LINKS_FILE" +} + +auth_service_filter() { + declare desc="calls user-service plugin trigger" + declare SERVICES=("$@") + local user_auth_count + + if [[ "${#SERVICES[@]}" -eq 0 ]]; then + return + fi + + user_auth_count="$(find "$PLUGIN_PATH"/enabled/*/user-auth-service 2>/dev/null | wc -l)" + + # no plugin trigger exists + if [[ $user_auth_count == 0 ]]; then + # echo out all the services since there is no plugin trigger + for SERVICE in "${SERVICES[@]}"; do + [[ -n "$SERVICE" ]] && echo "$SERVICE" + done + return 0 + fi + + # this plugin trigger exists in the core `20_events` plugin + if [[ "$user_auth_count" == 1 ]] && [[ -f "$PLUGIN_PATH"/enabled/20_events/user-auth-service ]]; then + # echo out all the services since there is no valid plugin trigger + for SERVICE in "${SERVICES[@]}"; do + [[ -n "$SERVICE" ]] && echo "$SERVICE" + done + return 0 + fi + + export SSH_USER=${SSH_USER:=$USER} + export SSH_NAME=${NAME:="default"} + # the output of this trigger should be all the services a user has access to + plugn trigger user-auth-service "$SSH_USER" "$SSH_NAME" "$PLUGIN_COMMAND_PREFIX" "${SERVICES[@]}" +} + +fn-services-list() { + declare desc="prints a filtered list of all local apps" + declare FILTER="$1" + local services=() + + pushd "$PLUGIN_DATA_ROOT" >/dev/null + for f in *; do + [[ -d $f ]] || continue + services+=("$f") + done + popd >/dev/null 2>&1 || pushd "/tmp" >/dev/null + + if [[ "${#services[@]}" -eq 0 ]]; then + return + fi + + if [[ "$FILTER" == "false" ]]; then + for service in "${services[@]}"; do + if [[ -n "$service" ]]; then + echo "$service" + fi + done + return + fi + + for service in $(auth_service_filter "${services[@]}" 2>/dev/null); do + if [[ -n "$service" ]]; then + echo "$service" + fi + done +} + +docker_ports_options() { + declare desc="export a list of exposed ports" + declare PORTS=("$@") + for ((i = 0; i < ${#PLUGIN_DATASTORE_PORTS[@]}; i++)); do + echo -n "-p ${PORTS[i]}:${PLUGIN_DATASTORE_PORTS[i]} " + done +} + +get_container_ip() { + declare desc="retrieve the ip address of a container" + declare CONTAINER_ID="$1" + "$DOCKER_BIN" container inspect --format '{{ .NetworkSettings.IPAddress }}' "$CONTAINER_ID" 2>/dev/null +} + +get_database_name() { + declare desc="retrieve a sanitized database name" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + + if [[ ! -f "$SERVICE_ROOT/DATABASE_NAME" ]]; then + echo "$SERVICE" >"$SERVICE_ROOT/DATABASE_NAME" + fi + + cat "$SERVICE_ROOT/DATABASE_NAME" +} + +get_random_ports() { + declare desc="retrieve N random ports" + declare iterations="${1:-1}" + for ((i = 0; i < iterations; i++)); do + local port=$RANDOM + local quit=0 + while [ "$quit" -ne 1 ]; do + netstat -an | grep $port >/dev/null + # shellcheck disable=SC2181 + if [ $? -gt 0 ]; then + quit=1 + else + port=$((port + 1)) + fi + done + echo $port + done +} + +get_service_name() { + declare desc="retrieve a docker service label" + declare SERVICE="$1" + echo "dokku.${PLUGIN_COMMAND_PREFIX}.$SERVICE" +} + +get_url_from_config() { + declare desc="retrieve a given _URL from a list of configuration variables" + declare EXISTING_CONFIG="$1" CONFIG_VAR="$2" + echo "$EXISTING_CONFIG" | grep "$CONFIG_VAR" | sed "s/$CONFIG_VAR:\s*//" | xargs +} + +in_links_file() { + declare desc="check if a service LINKS file contains an app" + declare SERVICE="$1" APP="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + grep -qE "^$APP\$" "$LINKS_FILE" +} + +is_container_status() { + declare desc="return 0 or 1 depending upon whether a given container has a certain status" + declare CID="$1" STATUS="$2" + local TEMPLATE="{{.State.$STATUS}}" + local CONTAINER_STATUS=$("$DOCKER_BIN" container inspect -f "$TEMPLATE" "$CID" 2>/dev/null || true) + + if [[ "$CONTAINER_STATUS" == "true" ]]; then + return 0 + fi + return 1 +} + +is_implemented_command() { + declare desc="return true if value ($1) is in list (all other arguments)" + declare CMD="$1" + CMD="$(echo "$CMD" | cut -d ':' -f2)" + + if [[ ${#PLUGIN_UNIMPLEMENTED_SUBCOMMANDS[@]} -eq 0 ]]; then + return 0 + fi + + local e + for e in "${PLUGIN_UNIMPLEMENTED_SUBCOMMANDS[@]}"; do + [[ "$e" == "$CMD" ]] && return 1 + done + return 0 +} + +is_valid_service_name() { + declare desc="validate a service name" + declare SERVICE="$1" + [[ -z "$SERVICE" ]] && return 1 + + if [[ "$SERVICE" =~ ^[A-Za-z0-9_-]+$ ]]; then + return 0 + fi + + return 1 +} + +remove_from_links_file() { + declare desc="remove an app from the service link file" + declare SERVICE="$1" APP="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + if [[ ! -f "$LINKS_FILE" ]]; then + return + fi + + sed -i.bak "/^$APP\$/d" "$LINKS_FILE" && rm "$LINKS_FILE.bak" + sort "$LINKS_FILE" -u -o "$LINKS_FILE" +} + +retry-docker-command() { + local ID="$1" COMMAND="$2" + local i=0 success=false + until [ $i -ge 100 ]; do + set +e + suppress_output "$DOCKER_BIN" container exec "$ID" sh -c "$COMMAND" + exit_code=$? + set -e + if [[ "$exit_code" == 0 ]]; then + success=true + break + fi + i=$((i + 1)) + sleep 1 + done + if [[ $i -gt 0 ]]; then + dokku_log_verbose "Container command retried ${i} time(s): ${COMMAND}" + fi + [[ "$success" == "true" ]] || dokku_log_fail "Failed to run command: ${COMMAND}" +} + +service_alternative_alias() { + declare desc="retrieve an alternative alias for a service" + declare EXISTING_CONFIG="$1" + local COLORS=(AQUA BLACK BLUE FUCHSIA GRAY GREEN LIME MAROON NAVY OLIVE PURPLE RED SILVER TEAL WHITE YELLOW) + local ALIAS + + for COLOR in "${COLORS[@]}"; do + ALIAS="${PLUGIN_ALT_ALIAS}_${COLOR}" + local IN_USE=$(echo "$EXISTING_CONFIG" | grep "${ALIAS}_URL") + if [[ -z "$IN_USE" ]]; then + break + fi + unset ALIAS + done + echo "$ALIAS" +} + +service_app_links() { + declare desc="output all service links for a given app" + declare APP="$1" + local LINKED_APP SERVICE SERVICE_ROOT + + for SERVICE in $(fn-services-list true); do + [[ -n "$SERVICE" ]] || continue + + SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + [[ -f "$SERVICE_ROOT/LINKS" ]] || continue + for LINKED_APP in $(<"$SERVICE_ROOT/LINKS"); do + if [[ "$LINKED_APP" == "$APP" ]]; then + echo "$SERVICE" + fi + done + done +} + +service_backup() { + declare desc="create a backup of a service to an existing s3 bucket" + declare SERVICE="$1" BUCKET_NAME="$2" USE_IAM_OPTIONAL_FLAG="$3" + local SERVICE_BACKUP_ROOT="$PLUGIN_DATA_ROOT/$SERVICE/backup" + local BACKUP_ENCRYPTION_CONFIG_ROOT="$PLUGIN_DATA_ROOT/$SERVICE/backup-encryption" + local AWS_ACCESS_KEY_ID_FILE="$SERVICE_BACKUP_ROOT/AWS_ACCESS_KEY_ID" + local AWS_SECRET_ACCESS_KEY_FILE="$SERVICE_BACKUP_ROOT/AWS_SECRET_ACCESS_KEY" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ID="$(cat "$SERVICE_ROOT/ID")" + local BACKUP_PARAMETERS="" + + if [[ -z "$USE_IAM_OPTIONAL_FLAG" ]]; then + [[ ! -f "$AWS_ACCESS_KEY_ID_FILE" ]] && dokku_log_fail "Missing AWS_ACCESS_KEY_ID file" + [[ ! -f "$AWS_SECRET_ACCESS_KEY_FILE" ]] && dokku_log_fail "Missing AWS_SECRET_ACCESS_KEY file" + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e AWS_ACCESS_KEY_ID=$(cat "$AWS_ACCESS_KEY_ID_FILE") -e AWS_SECRET_ACCESS_KEY=$(cat "$AWS_SECRET_ACCESS_KEY_FILE")" + elif [[ "$USE_IAM_OPTIONAL_FLAG" != "--use-iam" ]] && [[ "$USE_IAM_OPTIONAL_FLAG" != "-u" ]]; then + dokku_log_fail "Provide AWS credentials or use the --use-iam flag" + fi + + BACKUP_TMPDIR=$(mktemp -d --tmpdir) + trap 'rm -rf "$BACKUP_TMPDIR" > /dev/null' RETURN INT TERM EXIT + + "$DOCKER_BIN" container inspect "$ID" >/dev/null 2>&1 || dokku_log_fail "Service container does not exist" + is_container_status "$ID" "Running" || dokku_log_fail "Service container is not running" + + (service_export "$SERVICE" >"${BACKUP_TMPDIR}/export") + + # Build parameter list for s3backup tool + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e BUCKET_NAME=$BUCKET_NAME" + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e BACKUP_NAME=${PLUGIN_COMMAND_PREFIX}-${SERVICE}" + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -v ${BACKUP_TMPDIR}:/backup" + + if [[ -f "$SERVICE_BACKUP_ROOT/AWS_DEFAULT_REGION" ]]; then + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e AWS_DEFAULT_REGION=$(cat "$SERVICE_BACKUP_ROOT/AWS_DEFAULT_REGION")" + fi + + if [[ -f "$SERVICE_BACKUP_ROOT/AWS_SIGNATURE_VERSION" ]]; then + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e AWS_SIGNATURE_VERSION=$(cat "$SERVICE_BACKUP_ROOT/AWS_SIGNATURE_VERSION")" + fi + + if [[ -f "$SERVICE_BACKUP_ROOT/ENDPOINT_URL" ]]; then + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e ENDPOINT_URL=$(cat "$SERVICE_BACKUP_ROOT/ENDPOINT_URL")" + fi + + if [[ -f "$BACKUP_ENCRYPTION_CONFIG_ROOT/ENCRYPTION_KEY" ]]; then + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e ENCRYPTION_KEY=$(cat "$BACKUP_ENCRYPTION_CONFIG_ROOT/ENCRYPTION_KEY")" + fi + + if [[ -f "$BACKUP_ENCRYPTION_CONFIG_ROOT/ENCRYPT_WITH_PUBLIC_KEY_ID" ]]; then + BACKUP_PARAMETERS="$BACKUP_PARAMETERS -e ENCRYPT_WITH_PUBLIC_KEY_ID=$(cat "$BACKUP_ENCRYPTION_CONFIG_ROOT/ENCRYPT_WITH_PUBLIC_KEY_ID")" + fi + + # shellcheck disable=SC2086 + "$DOCKER_BIN" container run --rm $BACKUP_PARAMETERS "$PLUGIN_S3BACKUP_IMAGE" +} + +service_commit_config() { + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local CONFIG_VARIABLE="${PLUGIN_VARIABLE}_CONFIG_OPTIONS" + local ENV_VARIABLE="${PLUGIN_VARIABLE}_CUSTOM_ENV" + + custom_env="${!ENV_VARIABLE}" + [[ -n "$SERVICE_CUSTOM_ENV" ]] && custom_env="$SERVICE_CUSTOM_ENV" + if [[ -n $custom_env ]]; then + echo "$custom_env" | tr ';' "\n" >"$SERVICE_ROOT/ENV" + else + echo "" >"$SERVICE_ROOT/ENV" + fi + + config_options="${!CONFIG_VARIABLE}" + [[ -n "$PLUGIN_CONFIG_OPTIONS" ]] && config_options="$PLUGIN_CONFIG_OPTIONS" + if [[ -n "$config_options" ]]; then + echo "$config_options" >"$SERVICE_ROOT/CONFIG_OPTIONS" + else + echo "" >"$SERVICE_ROOT/CONFIG_OPTIONS" + fi + + if [[ -n "$SERVICE_MEMORY" ]]; then + echo "$SERVICE_MEMORY" >"$SERVICE_ROOT/SERVICE_MEMORY" + fi + + if [[ -n "$SERVICE_SHM_SIZE" ]]; then + echo "$SERVICE_SHM_SIZE" >"$SERVICE_ROOT/SHM_SIZE" + fi + + if [[ -n "$PLUGIN_IMAGE" ]]; then + echo "$PLUGIN_IMAGE" >"$SERVICE_ROOT/IMAGE" + fi + + if [[ -n "$PLUGIN_IMAGE_VERSION" ]]; then + echo "$PLUGIN_IMAGE_VERSION" >"$SERVICE_ROOT/IMAGE_VERSION" + fi + + if [[ -n "$SERVICE_INITIAL_NETWORK" ]]; then + fn-plugin-property-write "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "initial-network" "$SERVICE_INITIAL_NETWORK" + fi + + if [[ -n "$SERVICE_POST_CREATE_NETWORK" ]]; then + fn-plugin-property-write "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-create-network" "$SERVICE_POST_CREATE_NETWORK" + fi + + if [[ -n "$SERVICE_POST_START_NETWORK" ]]; then + fn-plugin-property-write "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-start-network" "$SERVICE_POST_START_NETWORK" + fi +} + +service_backup_auth() { + declare desc="set up authentication" + declare SERVICE="$1" AWS_ACCESS_KEY_ID="$2" AWS_SECRET_ACCESS_KEY="$3" AWS_DEFAULT_REGION="$4" AWS_SIGNATURE_VERSION="$5" ENDPOINT_URL="$6" + local SERVICE_BACKUP_ROOT="$PLUGIN_DATA_ROOT/$SERVICE/backup" + + mkdir "$SERVICE_BACKUP_ROOT" + echo "$AWS_ACCESS_KEY_ID" >"$SERVICE_BACKUP_ROOT/AWS_ACCESS_KEY_ID" + echo "$AWS_SECRET_ACCESS_KEY" >"$SERVICE_BACKUP_ROOT/AWS_SECRET_ACCESS_KEY" + + if [[ -n "$AWS_DEFAULT_REGION" ]]; then + echo "$AWS_DEFAULT_REGION" >"$SERVICE_BACKUP_ROOT/AWS_DEFAULT_REGION" + fi + + if [[ -n "$AWS_SIGNATURE_VERSION" ]]; then + echo "$AWS_SIGNATURE_VERSION" >"$SERVICE_BACKUP_ROOT/AWS_SIGNATURE_VERSION" + fi + + if [[ -n "$ENDPOINT_URL" ]]; then + echo "$ENDPOINT_URL" >"$SERVICE_BACKUP_ROOT/ENDPOINT_URL" + fi +} + +service_backup_deauth() { + declare desc="remove authentication" + declare SERVICE="$1" + local SERVICE_ROOT="${PLUGIN_DATA_ROOT}/${SERVICE}" + local SERVICE_BACKUP_ROOT="${SERVICE_ROOT}/backup/" + + rm -rf "$SERVICE_BACKUP_ROOT" +} + +service_backup_schedule() { + declare desc="schedules a backup of the service" + declare SERVICE="$1" SCHEDULE="$2" BUCKET_NAME="$3" USE_IAM_OPTIONAL_FLAG="$4" + local DOKKU_BIN="$(which dokku)" + local CRON_FILE="/etc/cron.d/dokku-${PLUGIN_COMMAND_PREFIX}-${SERVICE}" + local TMP_CRON_FILE="${PLUGIN_DATA_ROOT}/.TMP_CRON_FILE" + + if [[ -n "$USE_IAM_OPTIONAL_FLAG" ]] && [[ "$USE_IAM_OPTIONAL_FLAG" != "--use-iam" ]] && [[ "$USE_IAM_OPTIONAL_FLAG" != "-u" ]]; then + dokku_log_fail "Invalid flag provided, only '--use-iam' allowed" + fi + + echo "${SCHEDULE} dokku ${DOKKU_BIN} ${PLUGIN_COMMAND_PREFIX}:backup ${SERVICE} ${BUCKET_NAME} ${USE_IAM_OPTIONAL_FLAG}" >"$TMP_CRON_FILE" + sudo /bin/mv "$TMP_CRON_FILE" "$CRON_FILE" + sudo /bin/chown root:root "$CRON_FILE" + sudo /bin/chmod 644 "$CRON_FILE" +} + +service_backup_schedule_cat() { + declare desc="cat the contents of the configured backup cronfile for the service" + declare SERVICE="$1" + local CRON_FILE="/etc/cron.d/dokku-${PLUGIN_COMMAND_PREFIX}-${SERVICE}" + + if [[ ! -f "$CRON_FILE" ]]; then + dokku_log_fail "There is no scheduled backup for ${SERVICE}." + fi + + cat "$CRON_FILE" +} + +service_backup_set_encryption() { + declare desc="set up backup encryption" + declare SERVICE="$1" ENCRYPTION_KEY="$2" + local SERVICE_ROOT="${PLUGIN_DATA_ROOT}/${SERVICE}" + local SERVICE_BACKUP_ENCRYPTION_ROOT="${SERVICE_ROOT}/backup-encryption/" + + mkdir "$SERVICE_BACKUP_ENCRYPTION_ROOT" + echo "$ENCRYPTION_KEY" >"${SERVICE_BACKUP_ENCRYPTION_ROOT}/ENCRYPTION_KEY" +} + +service_backup_set_public_key_encryption() { + declare desc="set up backup GPG Public Key encryption" + declare SERVICE="$1" ENCRYPT_WITH_PUBLIC_KEY_ID="$2" + local SERVICE_ROOT="${PLUGIN_DATA_ROOT}/${SERVICE}" + local SERVICE_BACKUP_ENCRYPTION_ROOT="${SERVICE_ROOT}/backup-encryption/" + + mkdir "$SERVICE_BACKUP_ENCRYPTION_ROOT" + echo "$ENCRYPT_WITH_PUBLIC_KEY_ID" >"${SERVICE_BACKUP_ENCRYPTION_ROOT}/ENCRYPT_WITH_PUBLIC_KEY_ID" +} + +service_backup_unschedule() { + declare desc="unschedule the backup of the service" + declare SERVICE="$1" + local CRON_FILE="/etc/cron.d/dokku-${PLUGIN_COMMAND_PREFIX}-${SERVICE}" + + sudo /bin/rm -f "$CRON_FILE" +} + +service_backup_unset_encryption() { + declare desc="remove backup encryption" + declare SERVICE="$1" + local SERVICE_ROOT="${PLUGIN_DATA_ROOT}/${SERVICE}" + local SERVICE_BACKUP_ENCRYPTION_ROOT="${SERVICE_ROOT}/backup-encryption/" + + rm -rf "$SERVICE_BACKUP_ENCRYPTION_ROOT" +} + +service_backup_unset_encryption() { + declare desc="remove backup encryption" + declare SERVICE="$1" + local SERVICE_ROOT="${PLUGIN_DATA_ROOT}/${SERVICE}" + local SERVICE_BACKUP_ENCRYPTION_ROOT="${SERVICE_ROOT}/backup-encryption/" + + rm -rf "$SERVICE_BACKUP_ENCRYPTION_ROOT" +} + +service_container_rm() { + declare desc="stop a service and remove the running container" + declare SERVICE="$1" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local ID + + service_pause "$SERVICE" + ID=$("$DOCKER_BIN" container ps -aq --no-trunc --filter "name=^/$SERVICE_NAME$") || true + # this may be 'true' in tests... + if [[ -z "$ID" ]] || [[ "$ID" == "true" ]]; then + return 0 + fi + + dokku_log_verbose_quiet "Removing container" + "$DOCKER_BIN" container update --restart=no "$SERVICE_NAME" >/dev/null 2>&1 + if ! "$DOCKER_BIN" container rm "$SERVICE_NAME" >/dev/null 2>&1; then + dokku_log_fail "Unable to remove container for service $SERVICE" + fi +} + +service_dns_hostname() { + declare desc="retrieve the alias of a service" + declare SERVICE="$1" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + echo "$SERVICE_NAME" | tr ._ - +} + +service_enter() { + declare desc="enter running app container of specified proc type" + declare SERVICE="$1" && shift 1 + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ID="$(cat "$SERVICE_ROOT/ID")" + + "$DOCKER_BIN" container inspect "$ID" >/dev/null 2>&1 || dokku_log_fail "Service container does not exist" + is_container_status "$ID" "Running" || dokku_log_fail "Service container is not running" + + local EXEC_CMD="" + has_tty && local DOKKU_RUN_OPTS+=" -i -t" + # shellcheck disable=SC2086 + "$DOCKER_BIN" container exec $DOKKU_RUN_OPTS $ID $EXEC_CMD "${@:-/bin/bash}" +} + +service_exists() { + declare desc="returns 0 or 1 depending on whether service exists or not" + declare SERVICE="$1" + [[ -z "$SERVICE" ]] && return 1 + [[ -d "$PLUGIN_DATA_ROOT/$SERVICE" ]] && return 0 + return 1 +} + +service_exposed_ports() { + declare desc="list exposed ports for a service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PORT_FILE="$SERVICE_ROOT/PORT" + [[ ! -f $PORT_FILE ]] && echo '-' && return 0 + local PORTS=($(cat "$PORT_FILE")) + for ((i = 0; i < ${#PLUGIN_DATASTORE_PORTS[@]}; i++)); do + echo -n "${PLUGIN_DATASTORE_PORTS[i]}->${PORTS[i]} " + done +} + +service_image_exists() { + declare desc="check if the current image exists" + declare SERVICE="$1" PLUGIN_IMAGE="${2:-$PLUGIN_IMAGE}" PLUGIN_IMAGE_VERSION="${3:-$PLUGIN_IMAGE_VERSION}" + local plugin_image="$PLUGIN_IMAGE" + local plugin_image_version="$PLUGIN_IMAGE_VERSION" + + if [[ -z "$PLUGIN_IMAGE" ]] && [[ -f "$SERVICE_ROOT/IMAGE" ]]; then + plugin_image="$(cat "$SERVICE_ROOT/IMAGE")" + fi + + if [[ -z "$PLUGIN_IMAGE_VERSION" ]] && [[ -f "$SERVICE_ROOT/IMAGE_VERSION" ]]; then + plugin_image_version="$(cat "$SERVICE_ROOT/IMAGE_VERSION")" + fi + + local IMAGE="$plugin_image:$plugin_image_version" + + if [[ "$("$DOCKER_BIN" image ls -q "$IMAGE" 2>/dev/null)" == "" ]]; then + return 1 + fi + + return 0 +} + +service_info() { + declare desc="retrieve information about a given service" + declare SERVICE="$1" INFO_FLAG="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_URL=$(service_url "$SERVICE") + local PORT_FILE="$SERVICE_ROOT/PORT" + local SERVICE_CONTAINER_ID="$(cat "$SERVICE_ROOT/ID")" + local flag key valid_flags + + local flag_map=( + "--config-dir: ${SERVICE_ROOT}/${PLUGIN_CONFIG_SUFFIX}" + "--config-options: $(cat "$SERVICE_ROOT/CONFIG_OPTIONS" 2>/dev/null || true)" + "--data-dir: ${SERVICE_ROOT}/data" + "--dsn: ${SERVICE_URL}" + "--exposed-ports: $(service_exposed_ports "$SERVICE")" + "--id: ${SERVICE_CONTAINER_ID}" + "--internal-ip: $(get_container_ip "${SERVICE_CONTAINER_ID}")" + "--initial-network: $(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "initial-network")" + "--links: $(service_linked_apps "$SERVICE")" + "--post-create-network: $(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-create-network")" + "--post-start-network: $(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-start-network")" + "--service-root: ${SERVICE_ROOT}" + "--status: $(service_status "$SERVICE")" + "--version: $(service_version "$SERVICE")" + ) + if [[ -z "$INFO_FLAG" ]]; then + dokku_log_info2_quiet "$SERVICE $PLUGIN_COMMAND_PREFIX service information" + for flag in "${flag_map[@]}"; do + key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" + dokku_log_verbose "$(printf "%-20s %-25s" "${key^}" "${flag#*: }")" + done + else + local match=false + for flag in "${flag_map[@]}"; do + valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)" + if [[ "$flag" == "${INFO_FLAG}:"* ]]; then + echo "${flag#*: }" && match=true + fi + done + [[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}" + fi +} + +service_is_linked() { + declare desc="link a service to an application" + declare SERVICE="$1" APP="$2" + update_plugin_scheme_for_app "$APP" + local SERVICE_URL=$(service_url "$SERVICE") + local EXISTING_CONFIG=$(config_all "$APP") + local LINK=$(echo "$EXISTING_CONFIG" | grep "$SERVICE_URL" | cut -d: -f1) || true + if [[ -z $LINK ]]; then + dokku_log_warn "Service $SERVICE is not linked to $APP" + exit 1 + fi + dokku_log_info1 "Service $SERVICE is linked to $APP" +} + +service_link() { + declare desc="link a service to an application" + declare SERVICE="$1" APP="$2" + update_plugin_scheme_for_app "$APP" + local SERVICE_URL=$(service_url "$SERVICE") + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local EXISTING_CONFIG=$(config_all "$APP") + local LINK=$(echo "$EXISTING_CONFIG" | grep "$SERVICE_URL" | cut -d: -f1) || true + local SERVICE_DNS_HOSTNAME=$(service_dns_hostname "$SERVICE") + local LINKS_FILE="$SERVICE_ROOT/LINKS" + local ALIAS="$PLUGIN_DEFAULT_ALIAS" + local DEFAULT_ALIAS + + if [[ -n "$SERVICE_ALIAS" ]]; then + ALIAS="$SERVICE_ALIAS" + ALIAS_IN_USE=$(echo "$EXISTING_CONFIG" | grep "${ALIAS}_URL") || true + [[ -n "$ALIAS_IN_USE" ]] && dokku_log_fail "Specified alias $ALIAS already in use" + else + DEFAULT_ALIAS=$(echo "$EXISTING_CONFIG" | grep "${PLUGIN_DEFAULT_ALIAS}_URL") || true + if [[ -n "$DEFAULT_ALIAS" ]]; then + ALIAS=$(service_alternative_alias "$EXISTING_CONFIG") + fi + [[ -z "$ALIAS" ]] && dokku_log_fail "Unable to use default or generated URL alias" + fi + + [[ -n $LINK ]] && dokku_log_fail "Already linked as $LINK" + plugn trigger service-action pre-link "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" + add_to_links_file "$SERVICE" "$APP" + + if declare -f -F add_passed_docker_option >/dev/null; then + # shellcheck disable=SC2034 + local passed_phases=(build deploy run) + add_passed_docker_option passed_phases[@] "--link $SERVICE_NAME:$SERVICE_DNS_HOSTNAME" + else + dokku docker-options:add "$APP" build,deploy,run "--link $SERVICE_NAME:$SERVICE_DNS_HOSTNAME" + fi + [[ -n "$SERVICE_QUERYSTRING" ]] && SERVICE_URL="${SERVICE_URL}?${SERVICE_QUERYSTRING}" + plugn trigger service-action post-link "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" + if [[ "$DOKKU_GLOBAL_FLAGS" == *"--no-restart"* ]] || [[ "$SERVICE_RESTART_APPS" == "false" ]]; then + config_set --no-restart "$APP" "${ALIAS}_URL=$SERVICE_URL" + dokku_log_verbose "Skipping restart of linked app" + else + config_set "$APP" "${ALIAS}_URL=$SERVICE_URL" + fi + plugn trigger service-action post-link-complete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" +} + +service_linked_apps() { + declare desc="list all apps linked to a service for info output" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + touch "$LINKS_FILE" + [[ -z $(<"$LINKS_FILE") ]] && echo '-' && return 0 + + tr '\n' ' ' <"$LINKS_FILE" +} + +service_links() { + declare desc="list all apps linked to a service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + touch "$LINKS_FILE" + [[ -z $(<"$LINKS_FILE") ]] && return 0 + + cat "$LINKS_FILE" +} + +service_list() { + declare desc="list all services and their status" + + mapfile -t services < <(fn-services-list true) + if [[ "${#services[@]}" -eq 0 ]] || [[ -z "$services" ]]; then + dokku_log_warn "There are no $PLUGIN_SERVICE services" + return + fi + + dokku_log_info2_quiet "$PLUGIN_SERVICE services" + for service in "${services[@]}"; do + echo "${service}" + done +} + +service_logs() { + declare desc="display logs for a service" + declare SERVICE="$1" TAIL_FLAG="$2" TAIL_COUNT="$3" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ID=$(cat "$SERVICE_ROOT/ID") + local RE_INTEGER='^[0-9]+$' + + DOKKU_LOGS_ARGS="--tail $TAIL_COUNT" + if [[ "$TAIL_FLAG" == "-t" ]] || [[ "$TAIL_FLAG" == "--tail" ]]; then + DOKKU_LOGS_ARGS+=" --follow" + fi + + "$DOCKER_BIN" container inspect "$ID" >/dev/null 2>&1 || dokku_log_fail "Service container does not exist" + is_container_status "$ID" "Running" || dokku_log_warn "Service logs may not be output as service is not running" + + # shellcheck disable=SC2086 + "$DOCKER_BIN" container logs $DOKKU_LOGS_ARGS "$ID" 2>&1 +} + +service_parse_args() { + declare desc="cli arg parser" + local next_index=1 + local skip=false + local args=("$@") + + for arg in "$@"; do + shift + case "$arg" in + "--alias") set -- "$@" "-a" ;; + "--config-options") set -- "$@" "-c" ;; + "--custom-env") set -- "$@" "-C" ;; + "--database") set -- "$@" "-d" ;; + "--image-version") set -- "$@" "-I" ;; + "--initial-network") set -- "$@" "-N" ;; + "--image") set -- "$@" "-i" ;; + "--memory") set -- "$@" "-m" ;; + "--no-restart") set -- "$@" "-n" ;; + "--password") set -- "$@" "-p" ;; + "--post-create-network") set -- "$@" "-P" ;; + "--post-start-network") set -- "$@" "-S" ;; + "--querystring") set -- "$@" "-q" ;; + "--restart-apps") set -- "$@" "-R" ;; + "--root-password") set -- "$@" "-r" ;; + "--shm-size") set -- "$@" "-s" ;; + "--user") set -- "$@" "-u" ;; + *) set -- "$@" "$arg" ;; + esac + done + + OPTIND=1 + while getopts "na:c:C:d:i:I:m:n:N:p:P:q:R:r:s:S:u:" opt; do + case "$opt" in + a) + SERVICE_ALIAS="${OPTARG^^}" + export SERVICE_ALIAS="${SERVICE_ALIAS%_URL}" + ;; + c) + export PLUGIN_CONFIG_OPTIONS=$OPTARG + ;; + C) + export SERVICE_CUSTOM_ENV=$OPTARG + ;; + d) + export SERVICE_DATABASE=$OPTARG + ;; + i) + export PLUGIN_IMAGE=$OPTARG + ;; + I) + export PLUGIN_IMAGE_VERSION=$OPTARG + ;; + m) + export SERVICE_MEMORY=$OPTARG + ;; + n) + export SERVICE_RESTART_APPS=false + ;; + N) + export SERVICE_INITIAL_NETWORK=$OPTARG + ;; + p) + export SERVICE_PASSWORD=$OPTARG + ;; + P) + export SERVICE_POST_CREATE_NETWORK=$OPTARG + ;; + q) + export SERVICE_QUERYSTRING=${OPTARG#"?"} + ;; + R) + export SERVICE_RESTART_APPS=$OPTARG + ;; + r) + export SERVICE_ROOT_PASSWORD=$OPTARG + ;; + s) + export SERVICE_SHM_SIZE=$OPTARG + ;; + S) + export SERVICE_POST_START_NETWORK=$OPTARG + ;; + u) + export SERVICE_USER=$OPTARG + ;; + esac + done + shift "$((OPTIND - 1))" # remove options from positional parameters +} + +service_password() { + declare desc="fetch the password for a service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PASSWORD_FILE="$SERVICE_ROOT/PASSWORD" + if [[ -f "$PASSWORD_FILE" ]]; then + cat "$PASSWORD_FILE" + fi +} + +service_root_password() { + declare desc="fetch the root password for a service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PASSWORD_FILE="$SERVICE_ROOT/ROOTPASSWORD" + if [[ -f "$PASSWORD_FILE" ]]; then + cat "$PASSWORD_FILE" + fi +} + +service_port_expose() { + declare desc="wrapper for exposing service ports" + declare SERVICE="$1" PORTS=(${@:2}) + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PORT_FILE="$SERVICE_ROOT/PORT" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local EXPOSED_NAME="$SERVICE_NAME.ambassador" + + if [[ ${#PORTS[@]} -eq 0 ]]; then + # shellcheck disable=SC2206 + PORTS=(${PORTS[@]:-$(get_random_ports ${#PLUGIN_DATASTORE_PORTS[@]})}) + fi + + [[ "${#PORTS[@]}" != "${#PLUGIN_DATASTORE_PORTS[@]}" ]] && dokku_log_fail "${#PLUGIN_DATASTORE_PORTS[@]} ports to be exposed need to be provided in the following order: ${PLUGIN_DATASTORE_PORTS[*]}" + + if [[ -s "$PORT_FILE" ]]; then + # shellcheck disable=SC2207 + PORTS=($(cat "$PORT_FILE")) + dokku_log_fail "Service $SERVICE already exposed on port(s) ${PORTS[*]}" + fi + + if "$DOCKER_BIN" container inspect "$EXPOSED_NAME" >/dev/null 2>&1; then + dokku_log_warn "Service $SERVICE has an untracked expose container, removing" + "$DOCKER_BIN" container stop "$EXPOSED_NAME" >/dev/null 2>&1 || true + suppress_output "$DOCKER_BIN" container rm "$EXPOSED_NAME" + fi + + echo "${PORTS[@]}" >"$PORT_FILE" + + service_start "$SERVICE" "true" + service_port_reconcile_status "$SERVICE" + dokku_log_info1 "Service $SERVICE exposed on port(s) [container->host]: $(service_exposed_ports "$SERVICE")" +} + +service_port_unexpose() { + declare desc="wrapper for pausing exposed service ports" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PORT_FILE="$SERVICE_ROOT/PORT" + + rm -rf "$PORT_FILE" + service_port_reconcile_status "$SERVICE" + dokku_log_info1 "Service $SERVICE unexposed" +} + +service_port_reconcile_status() { + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PORT_FILE="$SERVICE_ROOT/PORT" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local EXPOSED_NAME="$SERVICE_NAME.ambassador" + + if [[ ! -s "$PORT_FILE" ]]; then + if "$DOCKER_BIN" container inspect "$EXPOSED_NAME" >/dev/null 2>&1; then + "$DOCKER_BIN" container stop "$EXPOSED_NAME" >/dev/null 2>&1 || true + suppress_output "$DOCKER_BIN" container rm "$EXPOSED_NAME" + return $? + fi + return + fi + + if is_container_status "$EXPOSED_NAME" "Running"; then + return + fi + + if "$DOCKER_BIN" container inspect "$EXPOSED_NAME" >/dev/null 2>&1; then + suppress_output "$DOCKER_BIN" container start "$EXPOSED_NAME" + return $? + fi + + # shellcheck disable=SC2207 + PORTS=($(cat "$PORT_FILE")) + # shellcheck disable=SC2046 + "$DOCKER_BIN" container run -d --link "$SERVICE_NAME:$PLUGIN_COMMAND_PREFIX" --name "$EXPOSED_NAME" $(docker_ports_options "${PORTS[@]}") --restart always --label dokku=ambassador --label "dokku.ambassador=$PLUGIN_COMMAND_PREFIX" "$PLUGIN_AMBASSADOR_IMAGE" >/dev/null +} + +service_promote() { + declare desc="promote a secondary service to the primary env var" + declare SERVICE="$1" APP="$2" + local PLUGIN_DEFAULT_CONFIG_VAR="${PLUGIN_DEFAULT_ALIAS}_URL" + local EXISTING_CONFIG=$(config_all "$APP") + update_plugin_scheme_for_app "$APP" + local SERVICE_URL=$(service_url "$SERVICE") + local CONFIG_VARS=($(echo "$EXISTING_CONFIG" | grep "$SERVICE_URL" | cut -d: -f1)) || true + local PREVIOUS_DEFAULT_URL=$(get_url_from_config "$EXISTING_CONFIG" "$PLUGIN_DEFAULT_CONFIG_VAR") + + [[ -z ${CONFIG_VARS[*]} ]] && dokku_log_fail "Not linked to app $APP" + [[ ${CONFIG_VARS[*]} =~ $PLUGIN_DEFAULT_CONFIG_VAR ]] && dokku_log_fail "Service $1 already promoted as $PLUGIN_DEFAULT_CONFIG_VAR" + + local NEW_CONFIG_VARS="" + if [[ -n $PREVIOUS_DEFAULT_URL ]]; then + local PREVIOUS_ALIAS=$(echo "$EXISTING_CONFIG" | grep "$PREVIOUS_DEFAULT_URL" | grep -v "$PLUGIN_DEFAULT_CONFIG_VAR") || true + if [[ -z $PREVIOUS_ALIAS ]]; then + local ALIAS=$(service_alternative_alias "$EXISTING_CONFIG") + NEW_CONFIG_VARS+="${ALIAS}_URL=$PREVIOUS_DEFAULT_URL " + fi + fi + local PROMOTE_URL=$(get_url_from_config "$EXISTING_CONFIG" "${CONFIG_VARS[0]}") + NEW_CONFIG_VARS+="$PLUGIN_DEFAULT_CONFIG_VAR=$PROMOTE_URL" + + # shellcheck disable=SC2086 + config_set "$APP" $NEW_CONFIG_VARS +} + +service_set_alias() { + declare desc="sets the alias in use for a service" + declare SERVICE="$1" ALIAS="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ALIAS_FILE="$SERVICE_ROOT/ALIAS" + + touch "$ALIAS_FILE" + echo "$ALIAS" >"$ALIAS_FILE" +} + +service_status() { + declare desc="display the status of a service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ID="$(cat "$SERVICE_ROOT/ID")" + local CONTAINER_STATUS + + CONTAINER_STATUS=$("$DOCKER_BIN" container inspect -f "{{.State.Status}}" "$ID" 2>/dev/null || true) + [[ -n "$CONTAINER_STATUS" ]] && echo "$CONTAINER_STATUS" && return 0 + echo "missing" && return 0 +} + +service_pause() { + declare desc="pause a running service" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local ID=$("$DOCKER_BIN" container ps -aq --no-trunc --filter "name=^/$SERVICE_NAME$") || true + [[ -z $ID ]] && dokku_log_warn "Service is already paused" && return 0 + + if [[ -n $ID ]]; then + dokku_log_info2_quiet "Pausing container" + "$DOCKER_BIN" container stop "$SERVICE_NAME" >/dev/null + if "$DOCKER_BIN" container inspect "$ID" >/dev/null 2>&1; then + "$DOCKER_BIN" container stop "$SERVICE_NAME.ambassador" >/dev/null 2>&1 || true + fi + dokku_log_verbose_quiet "Container paused" + else + dokku_log_verbose_quiet "No container exists for $SERVICE" + fi +} + +service_unlink() { + declare desc="unlink an application from a service" + declare SERVICE="$1" APP="$2" + update_plugin_scheme_for_app "$APP" + local SERVICE_URL=$(service_url "$SERVICE") + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local EXISTING_CONFIG=$(config_all "$APP") + local SERVICE_DNS_HOSTNAME=$(service_dns_hostname "$SERVICE") + local LINK=($(echo "$EXISTING_CONFIG" | grep "$SERVICE_URL" | cut -d: -f1)) || true + + plugn trigger service-action pre-unlink "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" + remove_from_links_file "$SERVICE" "$APP" + + if declare -f -F add_passed_docker_option >/dev/null; then + # shellcheck disable=SC2034 + local passed_phases=(build deploy run) + remove_passed_docker_option passed_phases[@] "--link $SERVICE_NAME:$SERVICE_DNS_HOSTNAME" + else + dokku docker-options:remove "$APP" build,deploy,run "--link $SERVICE_NAME:$SERVICE_DNS_HOSTNAME" + fi + + [[ -z ${LINK[*]} ]] && dokku_log_fail "Not linked to app $APP" + plugn trigger service-action post-unlink "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" + if [[ "$DOKKU_GLOBAL_FLAGS" == *"--no-restart"* ]] || [[ "$SERVICE_RESTART_APPS" == "false" ]]; then + config_unset --no-restart "$APP" "${LINK[@]}" + dokku_log_verbose "Skipping restart of linked app" + else + config_unset "$APP" "${LINK[@]}" + fi + plugn trigger service-action post-unlink-complete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$APP" +} + +service_version() { + declare desc="display the running version for an image" + declare SERVICE="$1" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + "$DOCKER_BIN" container inspect -f '{{.Config.Image}}' "$SERVICE_NAME" 2>/dev/null || true +} + +update_plugin_scheme_for_app() { + declare desc="retrieve the updated plugin scheme" + declare APP="$1" + local DATABASE_SCHEME + + DATABASE_SCHEME=$(config_get "$APP" "${PLUGIN_VARIABLE}_DATABASE_SCHEME" || true) + PLUGIN_SCHEME=${DATABASE_SCHEME:-$PLUGIN_SCHEME} +} + +verify_service_name() { + declare desc="verify that a service exists" + declare SERVICE="$@" + + if [[ -z "$SERVICE" ]]; then + dokku_log_fail "SERVICE must not be empty" + fi + + if [[ ! -d "$PLUGIN_DATA_ROOT/$SERVICE" ]]; then + dokku_log_fail "$PLUGIN_SERVICE service $SERVICE does not exist" + fi + + SERVICE="$(auth_service_filter "$SERVICE")" + if [[ -z "$SERVICE" ]]; then + dokku_log_fail "$PLUGIN_SERVICE service $SERVICE does not exist" + fi + + return 0 +} + +write_database_name() { + declare desc="write a sanitized database name" + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + + # some datastores do not like special characters in database names + # so we need to normalize them out + echo "$SERVICE" | tr .- _ >"$SERVICE_ROOT/DATABASE_NAME" +} diff --git a/plugins/redis/config b/plugins/redis/config new file mode 100755 index 000000000..affcd0abb --- /dev/null +++ b/plugins/redis/config @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +export REDIS_IMAGE=${REDIS_IMAGE:="$(awk -F '[ :]' '{print $2}' "${_DIR}/Dockerfile")"} +export REDIS_IMAGE_VERSION=${REDIS_IMAGE_VERSION:="$(awk -F '[ :]' '{print $3}' "${_DIR}/Dockerfile")"} +export REDIS_ROOT=${REDIS_ROOT:="$DOKKU_LIB_ROOT/services/redis"} +export DOKKU_LIB_HOST_ROOT=${DOKKU_LIB_HOST_ROOT:=$DOKKU_LIB_ROOT} +export REDIS_HOST_ROOT=${REDIS_HOST_ROOT:="$DOKKU_LIB_HOST_ROOT/services/redis"} + +export PLUGIN_UNIMPLEMENTED_SUBCOMMANDS=() +export PLUGIN_COMMAND_PREFIX="redis" +export PLUGIN_CONFIG_ROOT=${PLUGIN_CONFIG_ROOT:="$DOKKU_LIB_ROOT/config/$PLUGIN_COMMAND_PREFIX"} +export PLUGIN_DATA_ROOT=$REDIS_ROOT +export PLUGIN_DATA_HOST_ROOT=$REDIS_HOST_ROOT +export PLUGIN_DATASTORE_PORTS=(6379) +export PLUGIN_DATASTORE_WAIT_PORT=6379 +export PLUGIN_DEFAULT_ALIAS="REDIS" +export PLUGIN_DISABLE_PULL=${REDIS_DISABLE_PULL:=} +export PLUGIN_DISABLE_PULL_VARIABLE="REDIS_DISABLE_PULL" +export PLUGIN_ALT_ALIAS="DOKKU_REDIS" +export PLUGIN_IMAGE=$REDIS_IMAGE +export PLUGIN_IMAGE_VERSION=$REDIS_IMAGE_VERSION +export PLUGIN_SCHEME="redis" +export PLUGIN_SERVICE="Redis" +export PLUGIN_VARIABLE="REDIS" +export PLUGIN_BASE_PATH="$PLUGIN_PATH" +export PLUGIN_CONFIG_SUFFIX="config" +if [[ -n $DOKKU_API_VERSION ]]; then + export PLUGIN_BASE_PATH="$PLUGIN_ENABLED_PATH" +fi + +export PLUGIN_BUSYBOX_IMAGE=${PLUGIN_BUSYBOX_IMAGE:=busybox:1.37.0-uclibc} +export PLUGIN_AMBASSADOR_IMAGE=${PLUGIN_AMBASSADOR_IMAGE:=dokku/ambassador:0.8.2} +export PLUGIN_S3BACKUP_IMAGE=${PLUGIN_S3BACKUP_IMAGE:=dokku/s3backup:0.18.0} +export PLUGIN_WAIT_IMAGE=${PLUGIN_WAIT_IMAGE:=dokku/wait:0.9.3} + +export REDIS_CONFIG_OPTIONS=${REDIS_CONFIG_OPTIONS:=""} diff --git a/plugins/redis/docs/README.md b/plugins/redis/docs/README.md new file mode 100644 index 000000000..468382b40 --- /dev/null +++ b/plugins/redis/docs/README.md @@ -0,0 +1,4 @@ +# Extra Documentation + +The documentation in this folder is supplemental to using this plugin. It is injected automatically into the plugin's readme during documentation generation. + diff --git a/plugins/redis/functions b/plugins/redis/functions new file mode 100755 index 000000000..ec5060583 --- /dev/null +++ b/plugins/redis/functions @@ -0,0 +1,234 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" +if [[ -f "$PLUGIN_AVAILABLE_PATH/docker-options/functions" ]]; then + source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" +fi + +service_connect() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local PASSWORD="$(service_password "$SERVICE")" + local SERVICE_TTY_OPTS + has_tty && SERVICE_TTY_OPTS="-t" + + "$DOCKER_BIN" container exec --env=LANG=C.UTF-8 --env=LC_ALL=C.UTF-8 -i $SERVICE_TTY_OPTS "$SERVICE_NAME" redis-cli -a "$PASSWORD" +} + +service_create() { + local SERVICE="$1" + is_valid_service_name "$SERVICE" || dokku_log_fail "Please specify a valid name for the service. Valid characters are: [A-Za-z0-9_]+" + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ ! -d "$PLUGIN_DATA_ROOT/$SERVICE" ]] || dokku_log_fail "$PLUGIN_SERVICE service $SERVICE already exists" + SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + LINKS_FILE="$SERVICE_ROOT/LINKS" + + service_parse_args "${@:2}" + + if ! service_image_exists "$SERVICE"; then + if [[ "$PLUGIN_DISABLE_PULL" == "true" ]]; then + dokku_log_warn "${PLUGIN_DISABLE_PULL_VARIABLE} environment variable detected. Not running pull command." 1>&2 + dokku_log_warn " docker image pull ${IMAGE}" 1>&2 + dokku_log_warn "$PLUGIN_SERVICE service creation failed" + exit 1 + fi + "$DOCKER_BIN" image pull "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" || dokku_log_fail "$PLUGIN_SERVICE image $PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION pull failed" + fi + + plugn trigger service-action pre-create "$PLUGIN_COMMAND_PREFIX" "$SERVICE" + mkdir -p "$SERVICE_ROOT" || dokku_log_fail "Unable to create service directory" + mkdir -p "$SERVICE_ROOT/data" || dokku_log_fail "Unable to create service data directory" + mkdir -p "$SERVICE_ROOT/$PLUGIN_CONFIG_SUFFIX" || dokku_log_fail "Unable to create service config directory" + touch "$LINKS_FILE" + + REDIS_SERVICE_CONFIG="$SERVICE_ROOT/$PLUGIN_CONFIG_SUFFIX/redis.conf" + + if [[ -z $REDIS_CONFIG_PATH ]]; then + echo "# requirepass" > $REDIS_SERVICE_CONFIG + else + cp "$REDIS_CONFIG_PATH" "$REDIS_SERVICE_CONFIG" || dokku_log_fail "Unable to copy the ${REDIS_CONFIG_PATH} to the config directory" + fi + PASSWORD=$(openssl rand -hex 32) + if [[ -n "$SERVICE_PASSWORD" ]]; then + PASSWORD="$SERVICE_PASSWORD" + dokku_log_warn "Specified password may not be as secure as the auto-generated password" + fi + echo "$PASSWORD" >"$SERVICE_ROOT/PASSWORD" + chmod 640 "$SERVICE_ROOT/PASSWORD" + sed -i.bak "s/# requirepass.*/requirepass ${PASSWORD}/" "$REDIS_SERVICE_CONFIG" && rm "$SERVICE_ROOT/$PLUGIN_CONFIG_SUFFIX/redis.conf.bak" + + service_commit_config "$SERVICE" + write_database_name "$SERVICE" + plugn trigger service-action post-create "$PLUGIN_COMMAND_PREFIX" "$SERVICE" + service_create_container "$SERVICE" + plugn trigger service-action post-create-complete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" +} + +service_create_container() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_HOST_ROOT="$PLUGIN_DATA_HOST_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + + if [[ -f "$SERVICE_ROOT/CONFIG_OPTIONS" ]]; then + export CONFIG_OPTIONS="$(cat "$SERVICE_ROOT/CONFIG_OPTIONS")" + fi + + local network_alias="$(service_dns_hostname "$SERVICE")" + + rm -f "$SERVICE_ROOT/ID" + declare -a DOCKER_ARGS + DOCKER_ARGS=() + DOCKER_ARGS+=("--cidfile=$SERVICE_ROOT/ID") + DOCKER_ARGS+=("--env-file=$SERVICE_ROOT/ENV") + DOCKER_ARGS+=("--hostname=$SERVICE_NAME") + DOCKER_ARGS+=("--label=dokku.service=$PLUGIN_COMMAND_PREFIX") + DOCKER_ARGS+=("--label=dokku=service") + DOCKER_ARGS+=("--name=$SERVICE_NAME") + DOCKER_ARGS+=("--restart=always") + DOCKER_ARGS+=("--volume=$SERVICE_HOST_ROOT/$PLUGIN_CONFIG_SUFFIX:/usr/local/etc/redis") + DOCKER_ARGS+=("--volume=$SERVICE_HOST_ROOT/data:/data") + + declare -a LINK_CONTAINER_DOCKER_ARGS + LINK_CONTAINER_DOCKER_ARGS=() + LINK_CONTAINER_DOCKER_ARGS+=("--rm") + LINK_CONTAINER_DOCKER_ARGS+=("--link") + LINK_CONTAINER_DOCKER_ARGS+=("$SERVICE_NAME:$network_alias") + + [[ -f "$SERVICE_ROOT/SERVICE_MEMORY" ]] && SERVICE_MEMORY="$(cat "$SERVICE_ROOT/SERVICE_MEMORY")" + if [[ -n "$SERVICE_MEMORY" ]]; then + DOCKER_ARGS+=("--memory=${SERVICE_MEMORY}m") + fi + + [[ -f "$SERVICE_ROOT/SHM_SIZE" ]] && SERVICE_SHM_SIZE="$(cat "$SERVICE_ROOT/SHM_SIZE")" + if [[ -n "$SERVICE_SHM_SIZE" ]]; then + DOCKER_ARGS+=("--shm-size=${SERVICE_SHM_SIZE}") + fi + + [[ -f "$SERVICE_ROOT/IMAGE" ]] && PLUGIN_IMAGE="$(cat "$SERVICE_ROOT/IMAGE")" + [[ -f "$SERVICE_ROOT/IMAGE_VERSION" ]] && PLUGIN_IMAGE_VERSION="$(cat "$SERVICE_ROOT/IMAGE_VERSION")" + + local network="$(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "initial-network")" + if [[ -n "$network" ]]; then + DOCKER_ARGS+=("--network=${network}") + DOCKER_ARGS+=("--network-alias=${network_alias}") + LINK_CONTAINER_DOCKER_ARGS+=("--network=${network}") + fi + + # shellcheck disable=SC2086 + suppress_output "$DOCKER_BIN" container create "${DOCKER_ARGS[@]}" "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" redis-server /usr/local/etc/redis/redis.conf --bind 0.0.0.0 $CONFIG_OPTIONS + + if [[ -n "$(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-create-network")" ]]; then + dokku_log_verbose_quiet "Connecting to networks after container create" + while read -r line || [[ -n "$line" ]]; do + dokku_log_verbose_quiet "- $line" + "$DOCKER_BIN" network connect --alias "$network_alias" "$line" "$SERVICE_NAME" + done < <(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-create-network" | tr "," "\n") + fi + suppress_output "$DOCKER_BIN" container start "$(cat "$SERVICE_ROOT/ID")" + service_port_reconcile_status "$SERVICE" + + if [[ -n "$(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-start-network")" ]]; then + dokku_log_verbose_quiet "Connecting to networks after container start" + while read -r line || [[ -n "$line" ]]; do + dokku_log_verbose_quiet "- $line" + "$DOCKER_BIN" network connect --alias "$network_alias" "$line" "$SERVICE_NAME" + done < <(fn-plugin-property-get "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "post-start-network" | tr "," "\n") + fi + + dokku_log_verbose_quiet "Waiting for container to be ready" + if ! suppress_output "$DOCKER_BIN" container run "${LINK_CONTAINER_DOCKER_ARGS[@]}" "$PLUGIN_WAIT_IMAGE" -c "$network_alias:$PLUGIN_DATASTORE_WAIT_PORT"; then + dokku_log_info2_quiet "Start of $SERVICE container output" + dokku_container_log_verbose_quiet "$SERVICE_NAME" + dokku_log_info2_quiet "End of $SERVICE container output" + return 1 + fi + + dokku_log_info2 "$PLUGIN_SERVICE container created: $SERVICE" + service_info "$SERVICE" +} + +service_export() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local PASSWORD="$(service_password "$SERVICE")" + + [[ -n $SSH_TTY ]] && stty -opost + LASTSAVE1=$("$DOCKER_BIN" container exec "$SERVICE_NAME" bash -c "echo LASTSAVE | redis-cli -a ${PASSWORD}") + "$DOCKER_BIN" container exec "$SERVICE_NAME" bash -c "echo BGSAVE | redis-cli -a ${PASSWORD}" >/dev/null 2>&1 + LASTSAVE2=$("$DOCKER_BIN" container exec "$SERVICE_NAME" bash -c "echo LASTSAVE | redis-cli -a ${PASSWORD}") + + until [[ "$LASTSAVE1" != "$LASTSAVE2" ]]; do + LASTSAVE2=$("$DOCKER_BIN" container exec "$SERVICE_NAME" bash -c "echo LASTSAVE | redis-cli -a ${PASSWORD}") + sleep 5 + done + + "$DOCKER_BIN" container exec "$SERVICE_NAME" cat /data/dump.rdb + status=$? + [[ -n $SSH_TTY ]] && stty opost + exit $status +} + +service_import() { + local SERVICE="$1" + SERVICE_HOST_ROOT="$PLUGIN_DATA_HOST_ROOT/$SERVICE" + SERVICE_NAME="$(get_service_name "$SERVICE")" + + if [[ -t 0 ]]; then + dokku_log_fail "No data provided on stdin." + fi + suppress_output service_container_rm "$SERVICE" + "$DOCKER_BIN" container run --rm -v "$SERVICE_HOST_ROOT/data:/data" "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" bash -c "rm -f /data/dump.rdb" + cat >"$SERVICE_HOST_ROOT/data/dump.rdb" + "$DOCKER_BIN" container run --rm -v "$SERVICE_HOST_ROOT/data:/data" "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" bash -c "chown redis: /data/dump.rdb" + suppress_output service_start "$SERVICE" +} + +service_start() { + local SERVICE="$1" + local QUIET="$2" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_NAME="$(get_service_name "$SERVICE")" + local ID=$("$DOCKER_BIN" container ps -aq --no-trunc --filter "status=running" --filter "name=^/$SERVICE_NAME$") || true + if [[ -n $ID ]]; then + [[ -z $QUIET ]] && dokku_log_warn "Service is already started" + if [[ ! -f "$SERVICE_ROOT/ID" ]] || [[ "$(cat "$SERVICE_ROOT/ID")" != "$ID" ]]; then + [[ -z $QUIET ]] && dokku_log_warn "Updating local container ID" + echo "$ID" >"$SERVICE_ROOT/ID" + fi + return 0 + fi + + dokku_log_info2_quiet "Starting container" + local PREVIOUS_ID=$("$DOCKER_BIN" container ps -aq --no-trunc --filter "status=exited" --filter "name=^/$SERVICE_NAME$") || true + + if [[ -n $PREVIOUS_ID ]]; then + "$DOCKER_BIN" container start "$PREVIOUS_ID" >/dev/null + service_port_reconcile_status "$SERVICE" + dokku_log_info2 "Container started" + elif service_image_exists "$SERVICE"; then + service_create_container "$SERVICE" + else + if ! service_image_exists "$SERVICE"; then + [[ -f "$SERVICE_ROOT/IMAGE" ]] && PLUGIN_IMAGE="$(cat "$SERVICE_ROOT/IMAGE")" + [[ -f "$SERVICE_ROOT/IMAGE_VERSION" ]] && PLUGIN_IMAGE_VERSION="$(cat "$SERVICE_ROOT/IMAGE_VERSION")" + dokku_log_verbose_quiet "Missing image $PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION for $SERVICE" + else + dokku_log_verbose_quiet "Neither container nor valid configuration exists for $SERVICE" + fi + fi +} + +service_url() { + local SERVICE="$1" + local PASSWORD="$(service_password "$SERVICE")" + local SERVICE_DNS_HOSTNAME="$(service_dns_hostname "$SERVICE")" + echo "$PLUGIN_SCHEME://:$PASSWORD@$SERVICE_DNS_HOSTNAME:${PLUGIN_DATASTORE_PORTS[0]}" +} diff --git a/plugins/redis/help-functions b/plugins/redis/help-functions new file mode 100755 index 000000000..3ff6dc9c4 --- /dev/null +++ b/plugins/redis/help-functions @@ -0,0 +1,320 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +export SUBCOMMAND_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/subcommands" + +fn-help() { + declare CMD="$1" + local cmd EXIT_CODE + + if [[ "$CMD" == "help" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:help" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:default" ]]; then + fn-help-all "$@" + exit 0 + fi + + pushd "$SUBCOMMAND_ROOT" >/dev/null 2>&1 + for cmd in *; do + if [[ "$CMD" == "${PLUGIN_COMMAND_PREFIX}:$cmd" ]]; then + "$SUBCOMMAND_ROOT/$cmd" "$@" + EXIT_CODE="$?" + exit "$EXIT_CODE" + fi + done + popd >/dev/null 2>&1 + + exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +} + +fn-help-all() { + declare CMD="$1" SUBCOMMAND="$2" + local CMD_OUTPUT BLUE BOLD FULL_OUTPUT NORMAL + FULL_OUTPUT=true + + if [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:help" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:default" ]]; then + BOLD="$(fn-help-fancy-tput bold)" + NORMAL="$(fn-help-fancy-color "\033[m")" + BLUE="$(fn-help-fancy-color "\033[0;34m")" + CYAN="$(fn-help-fancy-color "\033[1;36m")" + if [[ -n "$SUBCOMMAND" ]] && [[ "$SUBCOMMAND" != "--all" ]]; then + fn-help-contents-subcommand "$SUBCOMMAND" "$FULL_OUTPUT" + return "$?" + fi + + echo -e "${BOLD}usage${NORMAL}: dokku ${PLUGIN_COMMAND_PREFIX}[:COMMAND]" + echo '' + echo -e "${BOLD}List your $PLUGIN_COMMAND_PREFIX services.${NORMAL}" + echo '' + echo -e "${BLUE}Example:${NORMAL}" + echo '' + echo " \$ dokku $PLUGIN_COMMAND_PREFIX:list" + echo '' + fn-help-list-example | column -c5 -t -s, + echo '' + echo -e "dokku ${BOLD}${PLUGIN_COMMAND_PREFIX}${NORMAL} commands: (get help with ${CYAN}dokku ${PLUGIN_COMMAND_PREFIX}:help SUBCOMMAND${NORMAL})" + echo '' + fn-help-contents | sort | column -c2 -t -s, + echo '' + elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then + fn-help-contents + else + cat </dev/null 2>&1 + for cmd in *; do + fn-help-contents-subcommand "$cmd" || true + done +} + +fn-help-contents-subcommand() { + declare SUBCOMMAND="$1" FULL_OUTPUT="$2" + local HELP_TMPDIR=$(mktemp -d --tmpdir) + local UNCLEAN_FILE="${HELP_TMPDIR}/cmd-unclean" CLEAN_FILE="${HELP_TMPDIR}/cmd-clean" + local BOLD CMD_OUTPUT CYAN EXAMPLE LIGHT_GRAY NORMAL + trap 'rm -rf "$HELP_TMPDIR" > /dev/null' RETURN INT TERM EXIT + + rm -rf "$UNCLEAN_FILE" "$CLEAN_FILE" + cat "$SUBCOMMAND_ROOT/$SUBCOMMAND" >"$UNCLEAN_FILE" + + fn-help-subcommand-sanitize "$UNCLEAN_FILE" "$CLEAN_FILE" + if ! is_implemented_command "$SUBCOMMAND"; then + return 1 + fi + + args="$(fn-help-subcommand-args "$CLEAN_FILE" "$FULL_OUTPUT")" + SUBCOMMAND=":$SUBCOMMAND" + [[ "$SUBCOMMAND" == ":default" ]] && SUBCOMMAND="" + cmd_line="$(echo -e "${SUBCOMMAND} ${args}" | sed -e 's/[[:space:]]*$//')" + desc="$(grep desc "$CLEAN_FILE" | head -1)" + eval "$desc" + + BLUE="$(fn-help-fancy-color "\033[0;34m")" + BOLD="$(fn-help-fancy-tput bold)" + CYAN="$(fn-help-fancy-color "\033[1;36m")" + NORMAL="$(fn-help-fancy-color "\033[m")" + LIGHT_GRAY="$(fn-help-fancy-color "\033[2;37m")" + LIGHT_RED="$(fn-help-fancy-color "\033[1;31m")" + CMD_OUTPUT="$(echo -e " ${PLUGIN_COMMAND_PREFIX}${cmd_line}, ${LIGHT_GRAY}${desc}${NORMAL}")" + if [[ "$FULL_OUTPUT" != "true" ]]; then + echo "$CMD_OUTPUT" + return 0 + fi + + echo -e "${BOLD}usage:${NORMAL} dokku ${PLUGIN_COMMAND_PREFIX}${cmd_line}" + echo '' + echo -e "${BOLD}${desc}${NORMAL}" + echo '' + + ARGS="$(fn-help-subcommand-list-args "$CLEAN_FILE")" + if [[ -n "$ARGS" ]]; then + echo -e "${CYAN}arguments:${NORMAL}" + echo '' + echo "$ARGS" | column -c2 -t -s, + echo '' + fi + + FLAGS="$(fn-help-subcommand-list-flags "$CLEAN_FILE")" + if [[ -n "$FLAGS" ]]; then + echo -e "${BLUE}flags:${NORMAL}" + echo '' + echo "$FLAGS" | column -c2 -t -s, + echo '' + fi + + EXAMPLE="$(fn-help-subcommand-example "$CLEAN_FILE")" + if [[ -n "$EXAMPLE" ]]; then + echo -e "${LIGHT_RED}examples:${NORMAL}" + echo '' + echo "$EXAMPLE" + echo '' + fi + + return 0 +} + +fn-help-fancy-tput() { + declare desc="a wrapper around tput" + + if [[ -n "$DOKKU_NO_COLOR" ]] || [[ "$TERM" == "unknown" ]] || [[ "$TERM" == "dumb" ]]; then + return + fi + + tput "$@" +} + +fn-help-fancy-color() { + declare desc="a wrapper around colors" + + if [[ -n "$DOKKU_NO_COLOR" ]] || [[ "$TERM" == "unknown" ]] || [[ "$TERM" == "dumb" ]]; then + return + fi + + echo "$@" +} + +fn-help-list-example() { + # shellcheck disable=SC2034 + declare desc="return $PLUGIN_COMMAND_PREFIX plugin help content" + cat <* ]] && line="\n ${BOLD}${line}${NORMAL}" + # shellcheck disable=SC2001 + [[ "$line" == " "* ]] && line=" ${OTHER_GRAY}$(echo "$line" | sed -e 's/^[[:space:]]*//')${NORMAL}" + echo -e "${NEWLINE}${line}" + LAST_LINE="sentence" + NEWLINE="\n" + fi + done +} + +fn-help-subcommand-list-args() { + declare FUNC_FILE="$1" + local EXAMPLE LIGHT_GRAY NORMAL + + FLAGS=$(grep "#A" "$FUNC_FILE" | cut -d'A' -f2- | sed -e 's/^[[:space:]]*//' || true) + if [[ -z "$FLAGS" ]]; then + return 0 + fi + + NORMAL="$(fn-help-fancy-color "\033[m")" + LIGHT_GRAY="$(fn-help-fancy-color "\033[2;37m")" + + _fn-help-apply-shell-expansion "$FLAGS" | while read -r line; do + echo -e "$(echo "$line" | cut -d',' -f1),${LIGHT_GRAY}$(echo "$line" | cut -d',' -f2-)${NORMAL}" + done +} + +fn-help-subcommand-list-flags() { + declare FUNC_FILE="$1" + local EXAMPLE LIGHT_GRAY NORMAL + + FLAGS=$(grep "#F" "$FUNC_FILE" | cut -d'F' -f2- | sed -e 's/^[[:space:]]*//' || true) + if [[ -z "$FLAGS" ]]; then + return 0 + fi + + NORMAL="$(fn-help-fancy-color "\033[m")" + LIGHT_GRAY="$(fn-help-fancy-color "\033[2;37m")" + + _fn-help-apply-shell-expansion "$FLAGS" | while read -r line; do + echo -e "$(echo "$line" | cut -d',' -f1),${LIGHT_GRAY}$(echo "$line" | cut -d',' -f2-)${NORMAL}" + done +} + +fn-help-subcommand-sanitize() { + declare FUNC_FILE="$1" OUTGOING_FUNC_FILE="$2" + local FUNCTION_FOUND=false + local IFS OIFS + + touch "$OUTGOING_FUNC_FILE" + + OIFS="$IFS" + IFS=, + while read -r p; do + IFS="$OIFS" + if [[ "$p" == *"-cmd \"\$@\""* ]] || [[ "$p" == "" ]]; then + continue + fi + + if [[ "$FUNCTION_FOUND" == true ]]; then + echo "$p" >>"$OUTGOING_FUNC_FILE" + continue + fi + + if [[ "$p" == *"()"* ]]; then + FUNCTION_FOUND=true + echo "$p" >>"$OUTGOING_FUNC_FILE" + continue + fi + done <"$FUNC_FILE" +} + +_fn-help-apply-shell-expansion() { + declare desc="expand environment variables for a shell command" + declare data="$1" + declare delimiter="__apply_shell_expansion_delimiter__" + declare command="cat <<$delimiter"$'\n'"$data"$'\n'"$delimiter" + eval "$command" +} diff --git a/plugins/redis/install b/plugins/redis/install new file mode 100755 index 000000000..f8fc40418 --- /dev/null +++ b/plugins/redis/install @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-install() { + pull-docker-image() { + declare IMAGE="$1" + if [[ "$PLUGIN_DISABLE_PULL" == "true" ]]; then + echo " ! ${PLUGIN_DISABLE_PULL_VARIABLE} environment variable detected. Not running pull command." 1>&2 + echo " ! docker image pull ${IMAGE}" 1>&2 + return + fi + if [[ "$("$DOCKER_BIN" image ls -q "${IMAGE}" 2>/dev/null)" == "" ]]; then + "$DOCKER_BIN" image pull "${IMAGE}" + fi + } + + fn-plugin-property-setup "$PLUGIN_COMMAND_PREFIX" + pull-docker-image "${PLUGIN_IMAGE}:${PLUGIN_IMAGE_VERSION}" + pull-docker-image "$PLUGIN_BUSYBOX_IMAGE" + pull-docker-image "$PLUGIN_AMBASSADOR_IMAGE" + pull-docker-image "$PLUGIN_S3BACKUP_IMAGE" + pull-docker-image "$PLUGIN_WAIT_IMAGE" + + mkdir -p "$PLUGIN_DATA_ROOT" || echo "Failed to create $PLUGIN_SERVICE data directory" + chown "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "$PLUGIN_DATA_ROOT" + + mkdir -p "$PLUGIN_CONFIG_ROOT" || echo "Failed to create $PLUGIN_SERVICE config directory" + chown "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "$PLUGIN_CONFIG_ROOT" + + rm -f "/etc/sudoers.d/dokku-${PLUGIN_COMMAND_PREFIX}*" + _SUDOERS_FILE="/etc/sudoers.d/dokku-${PLUGIN_COMMAND_PREFIX}" + + touch "$_SUDOERS_FILE" + cat >"$_SUDOERS_FILE" <"$SERVICE_ROOT/IMAGE" + echo "${image##*:}" >"$SERVICE_ROOT/IMAGE_VERSION" + fi + fi + + chown "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "$SERVICE_ROOT/IMAGE" "$SERVICE_ROOT/IMAGE_VERSION" + + if [[ -f "$SERVICE_ROOT/${PLUGIN_VARIABLE}_CONFIG_OPTIONS" ]]; then + mv "$SERVICE_ROOT/${PLUGIN_VARIABLE}_CONFIG_OPTIONS" "$SERVICE_ROOT/CONFIG_OPTIONS" + chown "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "$SERVICE_ROOT/CONFIG_OPTIONS" + fi + done +} + +plugin-install "$@" diff --git a/plugins/redis/plugin.toml b/plugins/redis/plugin.toml new file mode 100644 index 000000000..c6eb2c268 --- /dev/null +++ b/plugins/redis/plugin.toml @@ -0,0 +1,4 @@ +[plugin] +description = "dokku redis service plugin" +version = "1.40.4" +[plugin.config] diff --git a/plugins/redis/post-app-clone-setup b/plugins/redis/post-app-clone-setup new file mode 100755 index 000000000..c5d762d2c --- /dev/null +++ b/plugins/redis/post-app-clone-setup @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-post-app-clone-setup() { + declare OLD_APP_NAME="$1" NEW_APP_NAME="$2" + + for SERVICE in $(fn-services-list false); do + if in_links_file "$SERVICE" "$OLD_APP_NAME"; then + add_to_links_file "$SERVICE" "$NEW_APP_NAME" + fi + done +} + +plugin-post-app-clone-setup "$@" diff --git a/plugins/redis/post-app-rename-setup b/plugins/redis/post-app-rename-setup new file mode 100755 index 000000000..6c1e16b16 --- /dev/null +++ b/plugins/redis/post-app-rename-setup @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-post-app-rename-setup() { + declare OLD_APP_NAME="$1" NEW_APP_NAME="$2" + + for SERVICE in $(fn-services-list false); do + if in_links_file "$SERVICE" "$OLD_APP_NAME"; then + add_to_links_file "$SERVICE" "$NEW_APP_NAME" + fi + done +} + +plugin-post-app-rename-setup "$@" diff --git a/plugins/redis/pre-delete b/plugins/redis/pre-delete new file mode 100755 index 000000000..d31546478 --- /dev/null +++ b/plugins/redis/pre-delete @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +APP="$1" +for SERVICE in $(fn-services-list false); do + [[ -n "$SERVICE" ]] || continue + dokku_log_verbose_quiet "Unlinking from $SERVICE" + remove_from_links_file "$(basename "$SERVICE")" "$APP" +done diff --git a/plugins/redis/pre-restore b/plugins/redis/pre-restore new file mode 100755 index 000000000..c3322028f --- /dev/null +++ b/plugins/redis/pre-restore @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-pre-restore() { + declare SCHEDULER="$1" APP="$2" + local status + + if [[ "$SCHEDULER" != "docker-local" ]]; then + return + fi + + for SERVICE in $(fn-services-list false); do + if ! in_links_file "$SERVICE" "$APP"; then + continue + fi + + status="$(service_status "$SERVICE")" + if [[ "$status" == "running" ]]; then + continue + fi + + if [[ "$status" == "restarting" ]]; then + dokku_log_warn "$PLUGIN_SERVICE service $SERVICE is restarting and may cause issues with linked app $APP" + continue + fi + + dokku_log_warn "$PLUGIN_SERVICE service $SERVICE is not running, issuing service start" + service_start "$SERVICE" + done +} + +plugin-pre-restore "$@" diff --git a/plugins/redis/pre-start b/plugins/redis/pre-start new file mode 100755 index 000000000..b3d83a8e4 --- /dev/null +++ b/plugins/redis/pre-start @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-pre-start() { + declare APP="$1" + local status + + for SERVICE in $(fn-services-list false); do + if ! in_links_file "$SERVICE" "$APP"; then + continue + fi + + status="$(service_status "$SERVICE")" + if [[ "$status" == "running" ]]; then + continue + fi + + if [[ "$status" == "restarting" ]]; then + dokku_log_warn "$PLUGIN_SERVICE service $SERVICE is restarting and may cause issues with linked app $APP" + continue + fi + + dokku_log_warn "$PLUGIN_SERVICE service $SERVICE is not running, issuing service start" + service_start "$SERVICE" + done +} + +plugin-pre-start "$@" diff --git a/plugins/redis/service-list b/plugins/redis/service-list new file mode 100755 index 000000000..6fc54e90d --- /dev/null +++ b/plugins/redis/service-list @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin-service-list() { + declare desc="allows listing all services for use by other dokku plugins" + declare SERVICE_TYPE="$1" + + if [[ -n "$SERVICE_TYPE" ]] && [[ "$SERVICE_TYPE" != "$PLUGIN_COMMAND_PREFIX" ]]; then + return + fi + + for service in $(fn-services-list false); do + echo "$PLUGIN_COMMAND_PREFIX:$service" + done +} + +plugin-service-list "$@" diff --git a/plugins/redis/subcommands/app-links b/plugins/redis/subcommands/app-links new file mode 100755 index 000000000..6b0a0eb04 --- /dev/null +++ b/plugins/redis/subcommands/app-links @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-app-links-cmd() { + #E list all $PLUGIN_COMMAND_PREFIX services that are linked to the 'playground' app. + #E dokku $PLUGIN_COMMAND_PREFIX:app-links playground + #A app, app to run command against + declare desc="list all $PLUGIN_SERVICE service links for a given app" + local cmd="$PLUGIN_COMMAND_PREFIX:app-links" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare APP="$1" + APP=${APP:="$DOKKU_APP_NAME"} + + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + verify_app_name "$APP" + service_app_links "$APP" +} + +service-app-links-cmd "$@" diff --git a/plugins/redis/subcommands/backup b/plugins/redis/subcommands/backup new file mode 100755 index 000000000..354ed9b67 --- /dev/null +++ b/plugins/redis/subcommands/backup @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-cmd() { + #E backup the 'lollipop' service to the 'my-s3-bucket' bucket on AWS + #E dokku $PLUGIN_COMMAND_PREFIX:backup lollipop my-s3-bucket --use-iam + #E restore a backup file (assuming it was extracted via 'tar -xf backup.tgz') + #E dokku $PLUGIN_COMMAND_PREFIX:import lollipop < backup-folder/export + #F -u|--use-iam, use the IAM profile associated with the current server + #A service, service to run command against + #A bucket-name, name of the s3 bucket to upload backups to + declare desc="create a backup of the $PLUGIN_SERVICE service to an existing s3 bucket" + local cmd="$PLUGIN_COMMAND_PREFIX:backup" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" BUCKET_NAME="$2" USE_IAM_OPTIONAL_FLAG="$3" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$BUCKET_NAME" ]] && dokku_log_fail "Please specify an aws bucket for the backup" + verify_service_name "$SERVICE" + service_backup "$SERVICE" "$BUCKET_NAME" "$USE_IAM_OPTIONAL_FLAG" +} + +service-backup-cmd "$@" diff --git a/plugins/redis/subcommands/backup-auth b/plugins/redis/subcommands/backup-auth new file mode 100755 index 000000000..050397bcc --- /dev/null +++ b/plugins/redis/subcommands/backup-auth @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-auth-cmd() { + #E setup s3 backup authentication + #E dokku $PLUGIN_COMMAND_PREFIX:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY + #E setup s3 backup authentication with different region + #E dokku $PLUGIN_COMMAND_PREFIX:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION + #E setup s3 backup authentication with different signature version and endpoint + #E dokku $PLUGIN_COMMAND_PREFIX:backup-auth lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION AWS_SIGNATURE_VERSION ENDPOINT_URL + #E more specific example for minio auth + #E dokku $PLUGIN_COMMAND_PREFIX:backup-auth lollipop MINIO_ACCESS_KEY_ID MINIO_SECRET_ACCESS_KEY us-east-1 s3v4 https://YOURMINIOSERVICE + #A service, service to run command against + #A access-key-id, an amazon AWS_ACCESS_KEY_ID + #A aws-secret-access-key, an amazon AWS_SECRET_ACCESS_KEY + #A aws-default-region, (optional) a valid amazon S3 region + #A aws-signature-version, (optional) the AWS signature version to use when signing S3 requests + #A endpoint-url, (optional) an aws endpoint to upload to + declare desc="set up authentication for backups on the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-auth" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" AWS_ACCESS_KEY_ID="$2" AWS_SECRET_ACCESS_KEY="$3" AWS_DEFAULT_REGION="$4" AWS_SIGNATURE_VERSION="$5" ENDPOINT_URL="$6" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$AWS_ACCESS_KEY_ID" ]] && dokku_log_fail "Please specify an aws access key id" + [[ -z "$AWS_SECRET_ACCESS_KEY" ]] && dokku_log_fail "Please specify an aws secret access key" + verify_service_name "$SERVICE" + service_backup_auth "$SERVICE" "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY" "$AWS_DEFAULT_REGION" "$AWS_SIGNATURE_VERSION" "$ENDPOINT_URL" +} + +service-backup-auth-cmd "$@" diff --git a/plugins/redis/subcommands/backup-deauth b/plugins/redis/subcommands/backup-deauth new file mode 100755 index 000000000..19cbe8aff --- /dev/null +++ b/plugins/redis/subcommands/backup-deauth @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-deauth-cmd() { + #E remove s3 authentication + #E dokku $PLUGIN_COMMAND_PREFIX:backup-deauth lollipop + #A service, service to run command against + declare desc="remove backup authentication for the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-deauth" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_backup_deauth "$SERVICE" +} + +service-backup-deauth-cmd "$@" diff --git a/plugins/redis/subcommands/backup-schedule b/plugins/redis/subcommands/backup-schedule new file mode 100755 index 000000000..594c74846 --- /dev/null +++ b/plugins/redis/subcommands/backup-schedule @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-schedule-cmd() { + #E schedule a backup + #E > 'schedule' is a crontab expression, eg. "0 3 * * *" for each day at 3am + #E dokku $PLUGIN_COMMAND_PREFIX:backup-schedule lollipop "0 3 * * *" my-s3-bucket + #E schedule a backup and authenticate via iam + #E dokku $PLUGIN_COMMAND_PREFIX:backup-schedule lollipop "0 3 * * *" my-s3-bucket --use-iam + #F -u|--use-iam, use the IAM profile associated with the current server + #A service, service to run command against + #A schedule, a cron schedule to run backups on + #A bucket-name, name of the s3 bucket to upload backups to + declare desc="schedule a backup of the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-schedule" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" SCHEDULE="$2" BUCKET_NAME="$3" USE_IAM_OPTIONAL_FLAG="$4" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$SCHEDULE" ]] && dokku_log_fail "Please specify a schedule for the backup" + [[ -z "$BUCKET_NAME" ]] && dokku_log_fail "Please specify an aws bucket for the backup" + verify_service_name "$SERVICE" + service_backup_schedule "$SERVICE" "$SCHEDULE" "$BUCKET_NAME" "$USE_IAM_OPTIONAL_FLAG" +} + +service-backup-schedule-cmd "$@" diff --git a/plugins/redis/subcommands/backup-schedule-cat b/plugins/redis/subcommands/backup-schedule-cat new file mode 100755 index 000000000..56a388ac5 --- /dev/null +++ b/plugins/redis/subcommands/backup-schedule-cat @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-schedule-cat-cmd() { + #E cat the contents of the configured backup cronfile for the service + #E dokku $PLUGIN_COMMAND_PREFIX:backup-schedule-cat lollipop + #A service, service to run command against + declare desc="cat the contents of the configured backup cronfile for the service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-schedule-cat" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_backup_schedule_cat "$SERVICE" +} + +service-backup-schedule-cat-cmd "$@" diff --git a/plugins/redis/subcommands/backup-set-encryption b/plugins/redis/subcommands/backup-set-encryption new file mode 100755 index 000000000..27cb86e80 --- /dev/null +++ b/plugins/redis/subcommands/backup-set-encryption @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-set-encryption-cmd() { + #E set the GPG-compatible passphrase for encrypting backups for backups + #E dokku $PLUGIN_COMMAND_PREFIX:backup-set-encryption lollipop + #A service, service to run command against + #A passphrase, a GPG-compatible passphrase + declare desc="set encryption for all future backups of $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-set-encryption" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" PASSPHRASE="$2" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$PASSPHRASE" ]] && dokku_log_fail "Please specify a GPG backup passphrase" + verify_service_name "$SERVICE" + service_backup_set_encryption "$SERVICE" "$PASSPHRASE" +} + +service-backup-set-encryption-cmd "$@" diff --git a/plugins/redis/subcommands/backup-set-public-key-encryption b/plugins/redis/subcommands/backup-set-public-key-encryption new file mode 100755 index 000000000..d058bb203 --- /dev/null +++ b/plugins/redis/subcommands/backup-set-public-key-encryption @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-set-public-key-encryption-cmd() { + #E set the GPG Public Key for encrypting backups + #E dokku $PLUGIN_COMMAND_PREFIX:backup-set-public-key-encryption lollipop + #A service, service to run command against + #A public-key-id, a GPG Public Key ID (or fingerprint) to use for encryption. Must be uploaded to the GPG keyserver beforehand. + declare desc="set GPG Public Key encryption for all future backups of $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-set-public-key-encryption" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" PUBLIC_KEY_ID="$2" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$PUBLIC_KEY_ID" ]] && dokku_log_fail "Please specify a valid GPG Public Key ID (or fingerprint)" + verify_service_name "$SERVICE" + service_backup_set_public_key_encryption "$SERVICE" "$PUBLIC_KEY_ID" +} + +service-backup-set-public-key-encryption-cmd "$@" diff --git a/plugins/redis/subcommands/backup-unschedule b/plugins/redis/subcommands/backup-unschedule new file mode 100755 index 000000000..874a22a07 --- /dev/null +++ b/plugins/redis/subcommands/backup-unschedule @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-unschedule-cmd() { + #E remove the scheduled backup from cron + #E dokku $PLUGIN_COMMAND_PREFIX:backup-unschedule lollipop + #A service, service to run command against + declare desc="unschedule the backup of the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-unschedule" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_backup_unschedule "$SERVICE" +} + +service-backup-unschedule-cmd "$@" diff --git a/plugins/redis/subcommands/backup-unset-encryption b/plugins/redis/subcommands/backup-unset-encryption new file mode 100755 index 000000000..c046a5511 --- /dev/null +++ b/plugins/redis/subcommands/backup-unset-encryption @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-unset-encryption-cmd() { + #E unset the GPG encryption passphrase for backups + #E dokku $PLUGIN_COMMAND_PREFIX:backup-unset-encryption lollipop + #A service, service to run command against + declare desc="unset encryption for future backups of the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-unset-encryption" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_backup_unset_encryption "$SERVICE" +} + +service-backup-unset-encryption-cmd "$@" diff --git a/plugins/redis/subcommands/backup-unset-public-key-encryption b/plugins/redis/subcommands/backup-unset-public-key-encryption new file mode 100755 index 000000000..8e0352f8d --- /dev/null +++ b/plugins/redis/subcommands/backup-unset-public-key-encryption @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-backup-unset-public-key-encryption-cmd() { + #E unset the GPG Public Key encryption for backups + #E dokku $PLUGIN_COMMAND_PREFIX:backup-unset-public-key-encryption lollipop + #A service, service to run command against + declare desc="unset GPG Public Key encryption for future backups of the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:backup-unset-public-key-encryption" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" # TODO: [22.03.2024 by Mykola] + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_backup_unset_public_key_encryption "$SERVICE" # TODO: [22.03.2024 by Mykola] +} + +service-backup-unset-encryption-cmd "$@" diff --git a/plugins/redis/subcommands/clone b/plugins/redis/subcommands/clone new file mode 100755 index 000000000..b9d99a330 --- /dev/null +++ b/plugins/redis/subcommands/clone @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-clone-cmd() { + #E you can clone an existing service to a new one + #E dokku $PLUGIN_COMMAND_PREFIX:clone lollipop lollipop-2 + #A service, service to run command against + #A new-service, name of new service + #F -c|--config-options "--args --go=here", extra arguments to pass to the container create command + #F -C|--custom-env "USER=alpha;HOST=beta", semi-colon delimited environment variables to start the service with + #F -i|--image IMAGE, the image name to start the service with + #F -I|--image-version IMAGE_VERSION, the image version to start the service with + #F -m|--memory MEMORY, container memory limit in megabytes (default: unlimited) + #F -N|--initial-network INITIAL_NETWORK, the initial network to attach the service to + #F -p|--password PASSWORD, override the user-level service password + #F -P|--post-create-network NETWORKS, a comma-separated list of networks to attach the service container to after service creation + #F -r|--root-password PASSWORD, override the root-level service password + #F -S|--post-start-network NETWORKS, a comma-separated list of networks to attach the service container to after service start + #F -s|--shm-size SHM_SIZE, override shared memory size for $PLUGIN_COMMAND_PREFIX docker container + declare desc="create container then copy data from into " + local cmd="$PLUGIN_COMMAND_PREFIX:clone" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" NEW_SERVICE="$2" CLONE_FLAGS_LIST=("${@:3}") + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$NEW_SERVICE" ]] && dokku_log_fail "Please specify a name for the new service" + verify_service_name "$SERVICE" + if service_exists "$NEW_SERVICE"; then + dokku_log_fail "Invalid service name $NEW_SERVICE. Verify the service name is not already in use." + fi + + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local ID="$(cat "$SERVICE_ROOT/ID")" + is_container_status "$ID" "Running" || dokku_log_fail "Service ${SERVICE} container is not running" + + PLUGIN_IMAGE=$(service_version "$SERVICE" | grep -o "^.*:" | sed -r "s/://g") + PLUGIN_IMAGE_VERSION=$(service_version "$SERVICE" | grep -o ":.*$" | sed -r "s/://g") + + service_parse_args "${@:3}" + + dokku_log_info2 "Cloning $SERVICE to $NEW_SERVICE @ $PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" + service_create "$NEW_SERVICE" "${@:3}" + dokku_log_info1 "Copying data from $SERVICE to $NEW_SERVICE" + service_export "$SERVICE" | service_import "$NEW_SERVICE" >/dev/null 2>&1 || true + dokku_log_info2 "Done" +} + +service-clone-cmd "$@" diff --git a/plugins/redis/subcommands/connect b/plugins/redis/subcommands/connect new file mode 100755 index 000000000..ad6abcaed --- /dev/null +++ b/plugins/redis/subcommands/connect @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-connect-cmd() { + #E connect to the service via the $PLUGIN_COMMAND_PREFIX connection tool + #E > NOTE: disconnecting from ssh while running this command may leave zombie processes due to moby/moby#9098 + #E dokku $PLUGIN_COMMAND_PREFIX:connect lollipop + #A service, service to run command against + declare desc="connect to the service via the $PLUGIN_COMMAND_PREFIX connection tool" + local cmd="$PLUGIN_COMMAND_PREFIX:connect" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_connect "$SERVICE" +} + +service-connect-cmd "$@" diff --git a/plugins/redis/subcommands/create b/plugins/redis/subcommands/create new file mode 100755 index 000000000..7112f18f1 --- /dev/null +++ b/plugins/redis/subcommands/create @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-create-cmd() { + #E create a $PLUGIN_COMMAND_PREFIX service named lollipop + #E dokku $PLUGIN_COMMAND_PREFIX:create lollipop + #E you can also specify the image and image version to use for the service. + #E it *must* be compatible with the ${PLUGIN_IMAGE} image. + #E export ${PLUGIN_VARIABLE}_IMAGE="${PLUGIN_IMAGE}" + #E export ${PLUGIN_VARIABLE}_IMAGE_VERSION="${PLUGIN_IMAGE_VERSION}" + #E dokku $PLUGIN_COMMAND_PREFIX:create lollipop + #E you can also specify custom environment variables to start + #E the ${PLUGIN_COMMAND_PREFIX} service in semicolon-separated form. + #E export ${PLUGIN_VARIABLE}_CUSTOM_ENV="USER=alpha;HOST=beta" + #E dokku $PLUGIN_COMMAND_PREFIX:create lollipop + #A service, service to run command against + #F -c|--config-options "--args --go=here", extra arguments to pass to the container create command + #F -C|--custom-env "USER=alpha;HOST=beta", semi-colon delimited environment variables to start the service with + #F -i|--image IMAGE, the image name to start the service with + #F -I|--image-version IMAGE_VERSION, the image version to start the service with + #F -m|--memory MEMORY, container memory limit in megabytes (default: unlimited) + #F -N|--initial-network INITIAL_NETWORK, the initial network to attach the service to + #F -p|--password PASSWORD, override the user-level service password + #F -P|--post-create-network NETWORKS, a comma-separated list of networks to attach the service container to after service creation + #F -r|--root-password PASSWORD, override the root-level service password + #F -S|--post-start-network NETWORKS, a comma-separated list of networks to attach the service container to after service start + #F -s|--shm-size SHM_SIZE, override shared memory size for $PLUGIN_COMMAND_PREFIX docker container + declare desc="create a $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:create" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" CREATE_FLAGS_LIST=("${@:2}") + + service_create "$SERVICE" "${@:2}" +} + +service-create-cmd "$@" diff --git a/plugins/redis/subcommands/destroy b/plugins/redis/subcommands/destroy new file mode 100755 index 000000000..cffe32d0e --- /dev/null +++ b/plugins/redis/subcommands/destroy @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-destroy-cmd() { + #E destroy the service, it's data, and the running container + #E dokku $PLUGIN_COMMAND_PREFIX:destroy lollipop + #A service, service to run command against + #F -f|--force, force destroy without asking for confirmation + declare desc="delete the $PLUGIN_SERVICE service/data/container if there are no links left" + local cmd="$PLUGIN_COMMAND_PREFIX:destroy" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" FORCE_FLAG="$2" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + LINKS_FILE="$SERVICE_ROOT/LINKS" + SERVICE_HOST_ROOT="$PLUGIN_DATA_HOST_ROOT/$SERVICE" + SERVICE_NAME="$(get_service_name "$SERVICE")" + + [[ -s "$LINKS_FILE" ]] && dokku_log_fail "Cannot delete linked service" + + if [[ "$FORCE_FLAG" == "force" ]] || [[ "$FORCE_FLAG" == "-f" ]] || [[ "$FORCE_FLAG" == "--force" ]]; then + DOKKU_APPS_FORCE_DELETE=1 + fi + if [[ -z "$DOKKU_APPS_FORCE_DELETE" ]]; then + dokku_log_warn "WARNING: Potentially Destructive Action" + dokku_log_warn "This command will destroy $SERVICE $PLUGIN_SERVICE service." + dokku_log_warn "To proceed, type \"$SERVICE\"" + echo "" + + read -rp "> " service_name + if [[ "$service_name" != "$SERVICE" ]]; then + dokku_log_warn "Confirmation did not match $SERVICE. Aborted." + exit 1 + fi + fi + + dokku_log_info2_quiet "Deleting $SERVICE" + plugn trigger service-action pre-delete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" + service_backup_unschedule "$SERVICE" + service_container_rm "$SERVICE" + + dokku_log_verbose_quiet "Removing data" + "$DOCKER_BIN" container run --rm -v "$SERVICE_HOST_ROOT/data:/data" -v "$SERVICE_HOST_ROOT/$PLUGIN_CONFIG_SUFFIX:/config" "$PLUGIN_BUSYBOX_IMAGE" chmod 777 -R /config /data + rm -rf "$SERVICE_ROOT" + + fn-plugin-property-destroy "$PLUGIN_COMMAND_PREFIX" "$SERVICE" + + plugn trigger service-action post-delete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" + dokku_log_info2 "$PLUGIN_SERVICE container deleted: $SERVICE" +} + +service-destroy-cmd "$@" diff --git a/plugins/redis/subcommands/enter b/plugins/redis/subcommands/enter new file mode 100755 index 000000000..9062bebe7 --- /dev/null +++ b/plugins/redis/subcommands/enter @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-enter-cmd() { + #E a bash prompt can be opened against a running service. + #E filesystem changes will not be saved to disk. + #E > NOTE: disconnecting from ssh while running this command may leave zombie processes due to moby/moby#9098 + #E dokku $PLUGIN_COMMAND_PREFIX:enter lollipop + #E you may also run a command directly against the service. + #E filesystem changes will not be saved to disk. + #E dokku $PLUGIN_COMMAND_PREFIX:enter lollipop touch /tmp/test + #A service, service to run command against + declare desc="enter or run a command in a running $PLUGIN_SERVICE service container" + local cmd="$PLUGIN_COMMAND_PREFIX:enter" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + verify_service_name "$SERVICE" + dokku_log_info1_quiet "Filesystem changes may not persist after container restarts" + service_enter "$SERVICE" "${@:2}" +} + +service-enter-cmd "$@" diff --git a/plugins/redis/subcommands/exists b/plugins/redis/subcommands/exists new file mode 100755 index 000000000..6fd1bfc9a --- /dev/null +++ b/plugins/redis/subcommands/exists @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-exists-cmd() { + #E here we check if the lollipop $PLUGIN_COMMAND_PREFIX service exists. + #E dokku $PLUGIN_COMMAND_PREFIX:exists lollipop + #A service, service to run command against + declare desc="check if the $PLUGIN_SERVICE service exists" + local cmd="$PLUGIN_COMMAND_PREFIX:exists" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + dokku_log_info1 "Service $SERVICE exists" +} + +service-exists-cmd "$@" diff --git a/plugins/redis/subcommands/export b/plugins/redis/subcommands/export new file mode 100755 index 000000000..d77dd479d --- /dev/null +++ b/plugins/redis/subcommands/export @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-export-cmd() { + #E by default, datastore output is exported to stdout + #E dokku $PLUGIN_COMMAND_PREFIX:export lollipop + #E you can redirect this output to a file + #E dokku $PLUGIN_COMMAND_PREFIX:export lollipop > data.dump + #A service, service to run command against + declare desc="export a dump of the $PLUGIN_SERVICE service database" + local cmd="$PLUGIN_COMMAND_PREFIX:export" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_export "$SERVICE" +} + +service-export-cmd "$@" diff --git a/plugins/redis/subcommands/expose b/plugins/redis/subcommands/expose new file mode 100755 index 000000000..ab541d697 --- /dev/null +++ b/plugins/redis/subcommands/expose @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-expose-cmd() { + #E expose the service on the service's normal ports, allowing access to it from the public interface (0.0.0.0) + #E dokku $PLUGIN_COMMAND_PREFIX:expose lollipop ${PLUGIN_DATASTORE_PORTS[@]} + #E expose the service on the service's normal ports, with the first on a specified ip address (127.0.0.1) + #E dokku $PLUGIN_COMMAND_PREFIX:expose lollipop 127.0.0.1:${PLUGIN_DATASTORE_PORTS[@]} + #A service, service to run command against + #A ports, a list of ports to run against + declare desc="expose a $PLUGIN_SERVICE service on custom host:port if provided (random port on the 0.0.0.0 interface if otherwise unspecified)" + local cmd="$PLUGIN_COMMAND_PREFIX:expose" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" PORTS_LIST=("${@:2}") + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_port_expose "$SERVICE" "${@:2}" +} + +service-expose-cmd "$@" diff --git a/plugins/redis/subcommands/import b/plugins/redis/subcommands/import new file mode 100755 index 000000000..441f415f6 --- /dev/null +++ b/plugins/redis/subcommands/import @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-import-cmd() { + #E import a datastore dump + #E dokku $PLUGIN_COMMAND_PREFIX:import lollipop < data.dump + #A service, service to run command against + declare desc="import a dump into the $PLUGIN_SERVICE service database" + local cmd="$PLUGIN_COMMAND_PREFIX:import" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + is_implemented_command "$cmd" || dokku_log_fail "Not yet implemented" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_import "$SERVICE" +} + +service-import-cmd "$@" diff --git a/plugins/redis/subcommands/info b/plugins/redis/subcommands/info new file mode 100755 index 000000000..f9970d3a4 --- /dev/null +++ b/plugins/redis/subcommands/info @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-info-cmd() { + #E get connection information as follows: + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop + #E you can also retrieve a specific piece of service info via flags: + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --config-dir + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --data-dir + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --dsn + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --exposed-ports + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --id + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --internal-ip + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --initial-network + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --links + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --post-create-network + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --post-start-network + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --service-root + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --status + #E dokku $PLUGIN_COMMAND_PREFIX:info lollipop --version + #A service, service to run command against + #F --config-dir, show the service configuration directory + #F --data-dir, show the service data directory + #F --dsn, show the service DSN + #F --exposed-ports, show service exposed ports + #F --id, show the service container id + #F --internal-ip, show the service internal ip + #F --initial-network, show the initial network being connected to + #F --links, show the service app links + #F --post-create-network, show the networks to attach to after service container creation + #F --post-start-network, show the networks to attach to after service container start + #F --service-root, show the service root directory + #F --status, show the service running status + #F --version, show the service image version + declare desc="print the service information" + local cmd="$PLUGIN_COMMAND_PREFIX:info" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" INFO_FLAG="$2" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_info "$SERVICE" "$INFO_FLAG" +} + +service-info-cmd "$@" diff --git a/plugins/redis/subcommands/link b/plugins/redis/subcommands/link new file mode 100755 index 000000000..661ce4998 --- /dev/null +++ b/plugins/redis/subcommands/link @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-link-cmd() { + #E a $PLUGIN_COMMAND_PREFIX service can be linked to a container. + #E this will use native docker links via the docker-options plugin. + #E here we link it to our 'playground' app. + #E > NOTE: this will restart your app + #E dokku $PLUGIN_COMMAND_PREFIX:link lollipop playground + #E the following environment variables will be set automatically by docker + #E (not on the app itself, so they won’t be listed when calling dokku config): + #E + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_NAME=/lollipop/DATABASE + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_PORT=tcp://172.17.0.1:${PLUGIN_DATASTORE_PORTS[0]} + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_PORT_${PLUGIN_DATASTORE_PORTS[0]}_TCP=tcp://172.17.0.1:${PLUGIN_DATASTORE_PORTS[0]} + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_PORT_${PLUGIN_DATASTORE_PORTS[0]}_TCP_PROTO=tcp + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_PORT_${PLUGIN_DATASTORE_PORTS[0]}_TCP_PORT=${PLUGIN_DATASTORE_PORTS[0]} + #E DOKKU_${PLUGIN_VARIABLE}_LOLLIPOP_PORT_${PLUGIN_DATASTORE_PORTS[0]}_TCP_ADDR=172.17.0.1 + #E + #E the following will be set on the linked application by default: + #E + #E ${PLUGIN_DEFAULT_ALIAS}_URL=${PLUGIN_SCHEME}://:SOME_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-lollipop:${PLUGIN_DATASTORE_PORTS[0]} + #E + #E the host exposed here only works internally in docker containers. + #E if you want your container to be reachable from outside, you should + #E use the 'expose' subcommand. another service can be linked to your app: + #E dokku $PLUGIN_COMMAND_PREFIX:link other_service playground + #E it is possible to change the protocol for ${PLUGIN_DEFAULT_ALIAS}_URL by setting the + #E environment variable ${PLUGIN_VARIABLE}_DATABASE_SCHEME on the app. doing so will + #E after linking will cause the plugin to think the service is not + #E linked, and we advise you to unlink before proceeding. + #E dokku config:set playground ${PLUGIN_VARIABLE}_DATABASE_SCHEME=${PLUGIN_SCHEME}2 + #E dokku $PLUGIN_COMMAND_PREFIX:link lollipop playground + #E this will cause ${PLUGIN_DEFAULT_ALIAS}_URL to be set as: + #E + #E ${PLUGIN_SCHEME}2://:SOME_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-lollipop:${PLUGIN_DATASTORE_PORTS[0]} + #A service, service to run command against + #A app, app to run command against + #F -a|--alias "BLUE_DATABASE", an alternative alias to use for linking to an app via environment variable + #F -q|--querystring "pool=5", ampersand delimited querystring arguments to append to the service link + #F -n|--no-restart "false", whether or not to restart the app on link (default: true) + declare desc="link the $PLUGIN_SERVICE service to the app" + local cmd="$PLUGIN_COMMAND_PREFIX:link" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" APP="$2" LINK_FLAGS_LIST=("${@:3}") + APP=${APP:="$DOKKU_APP_NAME"} + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + verify_app_name "$APP" + verify_service_name "$SERVICE" + + service_parse_args "${@:3}" + service_link "$SERVICE" "$APP" +} + +service-link-cmd "$@" diff --git a/plugins/redis/subcommands/linked b/plugins/redis/subcommands/linked new file mode 100755 index 000000000..5d61d79ed --- /dev/null +++ b/plugins/redis/subcommands/linked @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-linked-cmd() { + #E here we check if the lollipop $PLUGIN_COMMAND_PREFIX service is linked to the 'playground' app. + #E dokku $PLUGIN_COMMAND_PREFIX:linked lollipop playground + #A service, service to run command against + #A app, app to run command against + declare desc="check if the $PLUGIN_SERVICE service is linked to an app" + local cmd="$PLUGIN_COMMAND_PREFIX:linked" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" APP="$2" + APP=${APP:="$DOKKU_APP_NAME"} + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + verify_app_name "$APP" + verify_service_name "$SERVICE" + service_is_linked "$SERVICE" "$APP" +} + +service-linked-cmd "$@" diff --git a/plugins/redis/subcommands/links b/plugins/redis/subcommands/links new file mode 100755 index 000000000..35586aad5 --- /dev/null +++ b/plugins/redis/subcommands/links @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-links-cmd() { + #E list all apps linked to the 'lollipop' $PLUGIN_COMMAND_PREFIX service. + #E dokku $PLUGIN_COMMAND_PREFIX:links lollipop + #A service, service to run command against + declare desc="list all apps linked to the $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:links" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local LINKS_FILE="$SERVICE_ROOT/LINKS" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_links "$SERVICE" +} + +service-links-cmd "$@" diff --git a/plugins/redis/subcommands/list b/plugins/redis/subcommands/list new file mode 100755 index 000000000..cebe31c13 --- /dev/null +++ b/plugins/redis/subcommands/list @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-list-cmd() { + #E list all services + #E dokku $PLUGIN_COMMAND_PREFIX:list + declare desc="list all $PLUGIN_SERVICE services" + local cmd="$PLUGIN_COMMAND_PREFIX:list" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + + service_list +} + +service-list-cmd "$@" diff --git a/plugins/redis/subcommands/logs b/plugins/redis/subcommands/logs new file mode 100755 index 000000000..7f2e90eac --- /dev/null +++ b/plugins/redis/subcommands/logs @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-logs-cmd() { + #E you can tail logs for a particular service: + #E dokku $PLUGIN_COMMAND_PREFIX:logs lollipop + #E by default, logs will not be tailed, but you can do this with the --tail flag: + #E dokku $PLUGIN_COMMAND_PREFIX:logs lollipop --tail + #E the default tail setting is to show all logs, but an initial count can also be specified + #E dokku $PLUGIN_COMMAND_PREFIX:logs lollipop --tail 5 + #A service, service to run command against + #F -t|--tail [], do not stop when end of the logs are reached and wait for additional output + declare desc="print the most recent log(s) for this service" + local cmd="$PLUGIN_COMMAND_PREFIX:logs" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" TAIL_FLAG="$2" TAIL_NUM_OPTIONAL="${3:-all}" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_logs "$SERVICE" "$TAIL_FLAG" "$TAIL_NUM_OPTIONAL" +} + +service-logs-cmd "$@" diff --git a/plugins/redis/subcommands/pause b/plugins/redis/subcommands/pause new file mode 100755 index 000000000..91d51fe69 --- /dev/null +++ b/plugins/redis/subcommands/pause @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-pause-cmd() { + #E pause the running container for the service + #E dokku $PLUGIN_COMMAND_PREFIX:pause lollipop + #A service, service to run command against + declare desc="pause a running $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:pause" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_pause "$SERVICE" +} + +service-pause-cmd "$@" diff --git a/plugins/redis/subcommands/promote b/plugins/redis/subcommands/promote new file mode 100755 index 000000000..80b543214 --- /dev/null +++ b/plugins/redis/subcommands/promote @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-promote-cmd() { + #E if you have a $PLUGIN_COMMAND_PREFIX service linked to an app and try to link another $PLUGIN_COMMAND_PREFIX service + #E another link environment variable will be generated automatically: + #E + #E DOKKU_${PLUGIN_DEFAULT_ALIAS}_BLUE_URL=${PLUGIN_SCHEME}://:ANOTHER_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-other-service:${PLUGIN_DATASTORE_PORTS[0]}/other_service + #E + #E you can promote the new service to be the primary one + #E > NOTE: this will restart your app + #E dokku $PLUGIN_COMMAND_PREFIX:promote other_service playground + #E this will replace ${PLUGIN_DEFAULT_ALIAS}_URL with the url from other_service and generate + #E another environment variable to hold the previous value if necessary. + #E you could end up with the following for example: + #E + #E ${PLUGIN_DEFAULT_ALIAS}_URL=${PLUGIN_SCHEME}://:ANOTHER_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-other-service:${PLUGIN_DATASTORE_PORTS[0]}/other_service + #E DOKKU_${PLUGIN_DEFAULT_ALIAS}_BLUE_URL=${PLUGIN_SCHEME}://:ANOTHER_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-other-service:${PLUGIN_DATASTORE_PORTS[0]}/other_service + #E DOKKU_${PLUGIN_DEFAULT_ALIAS}_SILVER_URL=${PLUGIN_SCHEME}://:SOME_PASSWORD@dokku-${PLUGIN_COMMAND_PREFIX}-lollipop:${PLUGIN_DATASTORE_PORTS[0]}/lollipop + #A service, service to run command against + #A app, app to run command against + declare desc="promote service as ${PLUGIN_DEFAULT_ALIAS}_URL in " + local cmd="$PLUGIN_COMMAND_PREFIX:promote" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" APP="$2" + APP=${APP:="$DOKKU_APP_NAME"} + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + verify_service_name "$SERVICE" + verify_app_name "$APP" + service_promote "$SERVICE" "$APP" +} + +service-promote-cmd "$@" diff --git a/plugins/redis/subcommands/restart b/plugins/redis/subcommands/restart new file mode 100755 index 000000000..9ff6b030d --- /dev/null +++ b/plugins/redis/subcommands/restart @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-restart-cmd() { + #E restart the service + #E dokku $PLUGIN_COMMAND_PREFIX:restart lollipop + #A service, service to run command against + declare desc="graceful shutdown and restart of the $PLUGIN_SERVICE service container" + local cmd="$PLUGIN_COMMAND_PREFIX:restart" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_pause "$SERVICE" + service_start "$SERVICE" + dokku_log_info1 "Please call dokku ps:restart on all linked apps" +} + +service-restart-cmd "$@" diff --git a/plugins/redis/subcommands/set b/plugins/redis/subcommands/set new file mode 100755 index 000000000..0bcf474b5 --- /dev/null +++ b/plugins/redis/subcommands/set @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +source "$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)/common-functions" + +service-set-cmd() { + #E set the network to attach after the service container is started + #E dokku $PLUGIN_COMMAND_PREFIX:set lollipop post-create-network custom-network + #E set multiple networks + #E dokku $PLUGIN_COMMAND_PREFIX:set lollipop post-create-network custom-network,other-network + #E unset the post-create-network value + #E dokku $PLUGIN_COMMAND_PREFIX:set lollipop post-create-network + #A service, service to run command against + #A key, property name to set + #A value, optional property value to set or empty to unset key + declare desc="set or clear a property for a service" + local cmd="$PLUGIN_COMMAND_PREFIX:set" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" KEY="$2" VALUE="$3" + local VALID_KEYS=("initial-network" "post-create-network" "post-start-network") + verify_service_name "$SERVICE" + + [[ -z "$KEY" ]] && dokku_log_fail "No key specified" + + if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then + dokku_log_fail "Invalid key specified, valid keys include: initial-network, post-create-network, post-start-network" + fi + + if [[ -n "$VALUE" ]]; then + dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}" + fn-plugin-property-write "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$KEY" "$VALUE" + else + dokku_log_info2_quiet "Unsetting ${KEY}" + if [[ "$KEY" == "rev-env-var" ]]; then + fn-plugin-property-write "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$KEY" "$VALUE" + else + fn-plugin-property-delete "$PLUGIN_COMMAND_PREFIX" "$SERVICE" "$KEY" + fi + fi +} + +service-set-cmd "$@" diff --git a/plugins/redis/subcommands/start b/plugins/redis/subcommands/start new file mode 100755 index 000000000..c928f3a6e --- /dev/null +++ b/plugins/redis/subcommands/start @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-start-cmd() { + #E start the service + #E dokku $PLUGIN_COMMAND_PREFIX:start lollipop + #A service, service to run command against + declare desc="start a previously stopped $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:start" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_start "$SERVICE" +} + +service-start-cmd "$@" diff --git a/plugins/redis/subcommands/stop b/plugins/redis/subcommands/stop new file mode 100755 index 000000000..9680ea503 --- /dev/null +++ b/plugins/redis/subcommands/stop @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-stop-cmd() { + #E stop the service and removes the running container + #E dokku $PLUGIN_COMMAND_PREFIX:stop lollipop + #A service, service to run command against + declare desc="stop a running $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:stop" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_container_rm "$SERVICE" +} + +service-stop-cmd "$@" diff --git a/plugins/redis/subcommands/unexpose b/plugins/redis/subcommands/unexpose new file mode 100755 index 000000000..a3e990a24 --- /dev/null +++ b/plugins/redis/subcommands/unexpose @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-unexpose-cmd() { + #E unexpose the service, removing access to it from the public interface (0.0.0.0) + #E dokku $PLUGIN_COMMAND_PREFIX:unexpose lollipop + #A service, service to run command against + declare desc="unexpose a previously exposed $PLUGIN_SERVICE service" + local cmd="$PLUGIN_COMMAND_PREFIX:unexpose" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + service_port_unexpose "$SERVICE" +} + +service-unexpose-cmd "$@" diff --git a/plugins/redis/subcommands/unlink b/plugins/redis/subcommands/unlink new file mode 100755 index 000000000..63c4c97f4 --- /dev/null +++ b/plugins/redis/subcommands/unlink @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" + +service-unlink-cmd() { + #E you can unlink a $PLUGIN_COMMAND_PREFIX service + #E > NOTE: this will restart your app and unset related environment variables + #E dokku $PLUGIN_COMMAND_PREFIX:unlink lollipop playground + #A service, service to run command against + #A app, app to run command against + #F -n|--no-restart "false", whether or not to restart the app on unlink (default: true) + declare desc="unlink the $PLUGIN_SERVICE service from the app" + local cmd="$PLUGIN_COMMAND_PREFIX:unlink" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" APP="$2" + APP=${APP:="$DOKKU_APP_NAME"} + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + verify_service_name "$SERVICE" + verify_app_name "$APP" + service_unlink "$SERVICE" "$APP" +} + +service-unlink-cmd "$@" diff --git a/plugins/redis/subcommands/upgrade b/plugins/redis/subcommands/upgrade new file mode 100755 index 000000000..3eeccde88 --- /dev/null +++ b/plugins/redis/subcommands/upgrade @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" +source "$PLUGIN_AVAILABLE_PATH/ps/functions" + +service-upgrade-cmd() { + #E you can upgrade an existing service to a new image or image-version + #E dokku $PLUGIN_COMMAND_PREFIX:upgrade lollipop + #A service, service to run command against + #F -c|--config-options "--args --go=here", extra arguments to pass to the container create command + #F -C|--custom-env "USER=alpha;HOST=beta", semi-colon delimited environment variables to start the service with + #F -i|--image IMAGE, the image name to start the service with + #F -I|--image-version IMAGE_VERSION, the image version to start the service with + #F -N|--initial-network INITIAL_NETWORK, the initial network to attach the service to + #F -P|--post-create-network NETWORKS, a comma-separated list of networks to attach the service container to after service creation + #F -R|--restart-apps "true", whether or not to force an app restart (default: false) + #F -S|--post-start-network NETWORKS, a comma-separated list of networks to attach the service container to after service start + #F -s|--shm-size SHM_SIZE, override shared memory size for $PLUGIN_COMMAND_PREFIX docker container + declare desc="upgrade service to the specified versions" + local cmd="$PLUGIN_COMMAND_PREFIX:upgrade" argv=("$@") + [[ ${argv[0]} == "$cmd" ]] && shift 1 + declare SERVICE="$1" UPGRADE_FLAGS_LIST=("${@:2}") + + [[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a valid name for the service" + verify_service_name "$SERVICE" + + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + + service_parse_args "${@:2}" + + if ! service_image_exists "$SERVICE" "$PLUGIN_IMAGE" "$PLUGIN_IMAGE_VERSION"; then + if [[ "$PLUGIN_DISABLE_PULL" == "true" ]]; then + dokku_log_warn "${PLUGIN_DISABLE_PULL_VARIABLE} environment variable detected. Not running pull command." 1>&2 + dokku_log_warn " docker image pull ${IMAGE}" 1>&2 + dokku_log_warn "$PLUGIN_SERVICE service $SERVICE upgrade failed" + exit 1 + fi + "$DOCKER_BIN" image pull "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" || dokku_log_fail "$PLUGIN_SERVICE image $PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION pull failed" + fi + + local NEW_PLUGIN_IMAGE_TAG="$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" + if [[ "$(service_version "$SERVICE")" == "$NEW_PLUGIN_IMAGE_TAG" ]]; then + dokku_log_info1 "Service $SERVICE already running $NEW_PLUGIN_IMAGE_TAG" + return + fi + + service_commit_config "$SERVICE" + + dokku_log_info2 "Upgrading $SERVICE to $NEW_PLUGIN_IMAGE_TAG" + if [[ "$SERVICE_RESTART_APPS" == "true" ]]; then + dokku_log_info2 "Stopping all linked services" + for app in $(service_linked_apps "$SERVICE"); do + [[ "$app" == "-" ]] && continue + ps_stop "$app" + done + fi + + dokku_log_info2 "Stopping $SERVICE" + service_container_rm "$SERVICE" + service_start "$SERVICE" "${@:2}" + + if [[ "$SERVICE_RESTART_APPS" == "true" ]]; then + dokku_log_info2 "Starting all linked services" + for app in $(service_linked_apps "$SERVICE"); do + [[ "$app" == "-" ]] && continue + ps_start "$app" + done + fi + + dokku_log_info2 "Done" +} + +service-upgrade-cmd "$@" diff --git a/plugins/redis/tests/hook_pre_delete.bats b/plugins/redis/tests/hook_pre_delete.bats new file mode 100755 index 000000000..600b8b047 --- /dev/null +++ b/plugins/redis/tests/hook_pre_delete.bats @@ -0,0 +1,20 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku apps:create my-app + dokku "$PLUGIN_COMMAND_PREFIX:create" ls + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app >&2 +} + +teardown() { + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app >&2 + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ls + dokku --force apps:destroy my-app || true +} + +@test "($PLUGIN_COMMAND_PREFIX:hook:pre-delete) removes app from links file when destroying app" { + [[ -n $(<"$PLUGIN_DATA_ROOT/ls/LINKS") ]] + dokku --force apps:destroy my-app + [[ -z $(<"$PLUGIN_DATA_ROOT/ls/LINKS") ]] +} diff --git a/plugins/redis/tests/link_networks.bats b/plugins/redis/tests/link_networks.bats new file mode 100755 index 000000000..3d3763817 --- /dev/null +++ b/plugins/redis/tests/link_networks.bats @@ -0,0 +1,289 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" ls + dokku network:create custom-network +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ls || true + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" lsa || true + dokku network:destroy --force custom-network +} + +@test "($PLUGIN_COMMAND_PREFIX:set) set initial-network" { + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls initial-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --initial-network + echo "output: $output" + echo "status: $status" + assert_output "custom-network" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge 0 + assert_output_contains custom-network + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{range $k,$alias := $v.DNSNames}}{{printf "alias:%s\n" $alias}}{{end}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "alias:dokku.$PLUGIN_COMMAND_PREFIX.ls" + assert_output_contains "alias:dokku-$PLUGIN_COMMAND_PREFIX-ls" + + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls initial-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --initial-network + echo "output: $output" + echo "status: $status" + assert_output "" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 +} + +@test "($PLUGIN_COMMAND_PREFIX:set) set post-create-network" { + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls post-create-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --post-create-network + echo "output: $output" + echo "status: $status" + assert_output "custom-network" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains custom-network + assert_output_contains bridge + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{range $k,$alias := $v.DNSNames}}{{printf "alias:%s\n" $alias}}{{end}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "alias:dokku.$PLUGIN_COMMAND_PREFIX.ls" + assert_output_contains "alias:dokku-$PLUGIN_COMMAND_PREFIX-ls" + + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls post-create-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --post-create-network + echo "output: $output" + echo "status: $status" + assert_output "" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 +} + +@test "($PLUGIN_COMMAND_PREFIX:set) set an post-start-network" { + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls post-start-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --post-start-network + echo "output: $output" + echo "status: $status" + assert_output "custom-network" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{range $k,$alias := $v.DNSNames}}{{printf "alias:%s\n" $alias}}{{end}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "alias:dokku.$PLUGIN_COMMAND_PREFIX.ls" + assert_output_contains "alias:dokku-$PLUGIN_COMMAND_PREFIX-ls" + + run dokku "$PLUGIN_COMMAND_PREFIX:set" ls post-start-network + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" ls --post-start-network + echo "output: $output" + echo "status: $status" + assert_output "" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:stop" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:start" ls + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.ls -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network 0 +} + +@test "($PLUGIN_COMMAND_PREFIX:create) flags" { + run dokku "$PLUGIN_COMMAND_PREFIX:create" lsa --initial-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect "dokku.$PLUGIN_COMMAND_PREFIX.lsa" -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge 0 + assert_output_contains custom-network + + run dokku "$PLUGIN_COMMAND_PREFIX:destroy" lsa --force + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:create" lsa --post-create-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect "dokku.$PLUGIN_COMMAND_PREFIX.lsa" -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network + + run docker inspect dokku.$PLUGIN_COMMAND_PREFIX.lsa -f '{{range $net,$v := .NetworkSettings.Networks}}{{range $k,$alias := $v.DNSNames}}{{printf "alias:%s\n" $alias}}{{end}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "alias:dokku.$PLUGIN_COMMAND_PREFIX.lsa" + assert_output_contains "alias:dokku-$PLUGIN_COMMAND_PREFIX-lsa" + + run dokku "$PLUGIN_COMMAND_PREFIX:destroy" lsa --force + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:create" lsa --post-start-network custom-network + echo "output: $output" + echo "status: $status" + assert_success + + run docker inspect "dokku.$PLUGIN_COMMAND_PREFIX.lsa" -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s\n" $net}}{{end}}' + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains bridge + assert_output_contains custom-network + + run dokku "$PLUGIN_COMMAND_PREFIX:destroy" lsa --force + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/plugins/redis/tests/service_clone.bats b/plugins/redis/tests/service_clone.bats new file mode 100755 index 000000000..e14809bc0 --- /dev/null +++ b/plugins/redis/tests/service_clone.bats @@ -0,0 +1,47 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:clone) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:clone" + assert_contains "${lines[*]}" "Please specify a valid name for the service" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:clone) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:clone" not_existing_service new_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:clone) error when new service isn't provided" { + run dokku "$PLUGIN_COMMAND_PREFIX:clone" l + assert_contains "${lines[*]}" "Please specify a name for the new service" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:clone) error when new service already exists" { + dokku "$PLUGIN_COMMAND_PREFIX:create" new_service + run dokku "$PLUGIN_COMMAND_PREFIX:clone" l new_service + assert_contains "${lines[*]}" "Invalid service name new_service" + assert_failure + + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" new_service +} + +@test "($PLUGIN_COMMAND_PREFIX:clone) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:clone" l new_service + [[ -f $PLUGIN_DATA_ROOT/new_service/ID ]] + assert_contains "${lines[*]}" "Copying data from l to new_service" + assert_contains "${lines[*]}" "Done" + assert_success + + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" new_service +} diff --git a/plugins/redis/tests/service_connect.bats b/plugins/redis/tests/service_connect.bats new file mode 100755 index 000000000..4af0a2b19 --- /dev/null +++ b/plugins/redis/tests/service_connect.bats @@ -0,0 +1,26 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:connect) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:connect" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:connect) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:connect" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:connect) success" { + skip "Connect hangs indefinitely without input" + run dokku "$PLUGIN_COMMAND_PREFIX:connect" l + assert_success +} diff --git a/plugins/redis/tests/service_create.bats b/plugins/redis/tests/service_create.bats new file mode 100755 index 000000000..eeacc3203 --- /dev/null +++ b/plugins/redis/tests/service_create.bats @@ -0,0 +1,26 @@ +#!/usr/bin/env bats +load test_helper + +@test "($PLUGIN_COMMAND_PREFIX:create) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:create" l + assert_contains "${lines[*]}" "container created: l" + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:create) service with dashes" { + run dokku "$PLUGIN_COMMAND_PREFIX:create" service-with-dashes + assert_contains "${lines[*]}" "container created: service-with-dashes" + assert_contains "${lines[*]}" "dokku-$PLUGIN_COMMAND_PREFIX-service-with-dashes" + + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" service-with-dashes +} + +@test "($PLUGIN_COMMAND_PREFIX:create) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:create" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:create) error when there is an invalid name specified" { + run dokku "$PLUGIN_COMMAND_PREFIX:create" d.erp + assert_failure +} diff --git a/plugins/redis/tests/service_destroy.bats b/plugins/redis/tests/service_destroy.bats new file mode 100755 index 000000000..98695ead2 --- /dev/null +++ b/plugins/redis/tests/service_destroy.bats @@ -0,0 +1,30 @@ +#!/usr/bin/env bats +load test_helper + +@test "($PLUGIN_COMMAND_PREFIX:destroy) success with --force" { + dokku "$PLUGIN_COMMAND_PREFIX:create" l + run dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + assert_contains "${lines[*]}" "container deleted: l" +} + +@test "($PLUGIN_COMMAND_PREFIX:destroy) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:destroy" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:destroy) error when container does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:destroy" non_existing_container + assert_contains "${lines[*]}" "service non_existing_container does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:destroy) error when container is linked to an app" { + dokku "$PLUGIN_COMMAND_PREFIX:create" l + dokku apps:create app + dokku "$PLUGIN_COMMAND_PREFIX:link" l app + run dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + assert_contains "${lines[*]}" "Cannot delete linked service" + + dokku "$PLUGIN_COMMAND_PREFIX:unlink" l app + run dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + assert_contains "${lines[*]}" "container deleted: l" +} diff --git a/plugins/redis/tests/service_export.bats b/plugins/redis/tests/service_export.bats new file mode 100755 index 000000000..845702c52 --- /dev/null +++ b/plugins/redis/tests/service_export.bats @@ -0,0 +1,39 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:export) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:export" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:export) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:export" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:export) success with SSH_TTY" { + if [[ -n "$GITHUB_WORKFLOW" ]]; then + skip "No tty is available on Github Actions" + fi + export SSH_TTY=$(tty) + run dokku "$PLUGIN_COMMAND_PREFIX:export" l + echo "output: $output" + echo "status: $status" + assert_exit_status 0 +} + +@test "($PLUGIN_COMMAND_PREFIX:export) success without SSH_TTY" { + unset SSH_TTY + run dokku "$PLUGIN_COMMAND_PREFIX:export" l + echo "output: $output" + echo "status: $status" + assert_exit_status 0 +} diff --git a/plugins/redis/tests/service_expose.bats b/plugins/redis/tests/service_expose.bats new file mode 100755 index 000000000..2a5dbf971 --- /dev/null +++ b/plugins/redis/tests/service_expose.bats @@ -0,0 +1,66 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" ls +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ls +} + +@test "($PLUGIN_COMMAND_PREFIX:expose) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:expose" + echo "output: $output" + echo "status: $status" + assert_failure + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:expose) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:expose" not_existing_service + echo "output: $output" + echo "status: $status" + assert_failure + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:expose) error when already exposed" { + run dokku "$PLUGIN_COMMAND_PREFIX:expose" ls + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:expose" ls + echo "output: $output" + echo "status: $status" + assert_failure + assert_contains "${lines[*]}" "Service ls already exposed on port(s)" + + run sudo rm "$PLUGIN_DATA_ROOT/ls/PORT" + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:expose" ls + echo "output: $output" + echo "status: $status" + assert_success + assert_contains "${lines[*]}" "Service ls has an untracked expose container, removing" +} + +@test "($PLUGIN_COMMAND_PREFIX:expose) success when not providing custom ports" { + run dokku "$PLUGIN_COMMAND_PREFIX:expose" ls + echo "output: $output" + echo "status: $status" + assert_success + [[ "${lines[*]}" =~ exposed\ on\ port\(s\)\ \[container\-\>host\]\:\ [[:digit:]]+ ]] +} + +@test "($PLUGIN_COMMAND_PREFIX:expose) success when providing custom ports" { + run dokku "$PLUGIN_COMMAND_PREFIX:expose" ls 4242 + echo "output: $output" + echo "status: $status" + assert_success + assert_contains "${lines[*]}" "exposed on port(s) [container->host]: 6379->4242" +} diff --git a/plugins/redis/tests/service_import.bats b/plugins/redis/tests/service_import.bats new file mode 100755 index 000000000..11c1cf6f4 --- /dev/null +++ b/plugins/redis/tests/service_import.bats @@ -0,0 +1,40 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l + curl -o "/tmp/fake.rdb" https://raw.githubusercontent.com/sripathikrishnan/redis-rdb-tools/master/tests/dumps/dictionary.rdb +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + rm -f "/tmp/fake.rdb" +} + +@test "($PLUGIN_COMMAND_PREFIX:import) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:import" + assert_contains "${lines[*]}" "Please specify a valid name for the service" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:import) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:import" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:import) error when data is not provided" { + if [[ -n "$GITHUB_WORKFLOW" ]]; then + skip "No tty is available on Github Actions" + fi + run dokku "$PLUGIN_COMMAND_PREFIX:import" l + assert_contains "${lines[*]}" "No data provided on stdin" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:import) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:import" l <"/tmp/fake.rdb" + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/plugins/redis/tests/service_info.bats b/plugins/redis/tests/service_info.bats new file mode 100755 index 000000000..984f2f1d2 --- /dev/null +++ b/plugins/redis/tests/service_info.bats @@ -0,0 +1,75 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:info) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:info" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:info) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:info" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:info) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:info" l + password="$(sudo cat "$PLUGIN_DATA_ROOT/l/PASSWORD")" + assert_contains "${lines[*]}" "redis://:$password@dokku-redis-l:6379" +} + +@test "($PLUGIN_COMMAND_PREFIX:info) replaces underscores by dash in hostname" { + dokku "$PLUGIN_COMMAND_PREFIX:create" test_with_underscores + run dokku "$PLUGIN_COMMAND_PREFIX:info" test_with_underscores + password="$(sudo cat "$PLUGIN_DATA_ROOT/test_with_underscores/PASSWORD")" + assert_contains "${lines[*]}" "redis://:$password@dokku-redis-test-with-underscores:6379" + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" test_with_underscores +} + +@test "($PLUGIN_COMMAND_PREFIX:info) success with flag" { + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --dsn + password="$(sudo cat "$PLUGIN_DATA_ROOT/l/PASSWORD")" + assert_output "redis://:$password@dokku-redis-l:6379" + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --config-dir + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --data-dir + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --dsn + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --exposed-ports + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --id + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --internal-ip + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --links + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --service-root + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --status + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --version + assert_success +} + +@test "($PLUGIN_COMMAND_PREFIX:info) error when invalid flag" { + run dokku "$PLUGIN_COMMAND_PREFIX:info" l --invalid-flag + assert_failure +} diff --git a/plugins/redis/tests/service_link.bats b/plugins/redis/tests/service_link.bats new file mode 100755 index 000000000..90de7062d --- /dev/null +++ b/plugins/redis/tests/service_link.bats @@ -0,0 +1,142 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" ls + dokku "$PLUGIN_COMMAND_PREFIX:create" ms + dokku apps:create my-app +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ms + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ls + dokku --force apps:destroy my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "Please specify a valid name for the service" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:link) error when the app argument is missing" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "Please specify an app to run the command on" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:link) error when the app does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls not_existing_app + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "App not_existing_app does not exist" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:link) error when the service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" not_existing_service my-app + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "service not_existing_service does not exist" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:link) error when the service is already linked to app" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "Already linked as REDIS_URL" + assert_failure + + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) exports REDIS_URL to app" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + echo "output: $output" + echo "status: $status" + url=$(dokku config:get my-app REDIS_URL) + password="$(sudo cat "$PLUGIN_DATA_ROOT/ls/PASSWORD")" + assert_contains "$url" "redis://:$password@dokku-redis-ls:6379" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) generates an alternate config url when REDIS_URL already in use" { + dokku config:set my-app REDIS_URL=redis://host:6379 + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + run dokku config my-app + assert_contains "${lines[*]}" "DOKKU_REDIS_AQUA_URL" + assert_success + + dokku "$PLUGIN_COMMAND_PREFIX:link" ms my-app + run dokku config my-app + assert_contains "${lines[*]}" "DOKKU_REDIS_BLACK_URL" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ms my-app + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) links to app with docker-options" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + run dokku docker-options:report my-app + assert_contains "${lines[*]}" "--link dokku.redis.ls:dokku-redis-ls" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) uses apps REDIS_DATABASE_SCHEME variable" { + dokku config:set my-app REDIS_DATABASE_SCHEME=redis2 + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + url=$(dokku config:get my-app REDIS_URL) + password="$(sudo cat "$PLUGIN_DATA_ROOT/ls/PASSWORD")" + assert_contains "$url" "redis2://:$password@dokku-redis-ls:6379" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) adds a querystring" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app --querystring "pool=5" + url=$(dokku config:get my-app REDIS_URL) + assert_contains "$url" "?pool=5" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) uses a specified config url when alias is specified" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app --alias "ALIAS" + url=$(dokku config:get my-app ALIAS_URL) + password="$(sudo cat "$PLUGIN_DATA_ROOT/ls/PASSWORD")" + assert_contains "$url" "redis://:$password@dokku-redis-ls:6379" + assert_success + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:link) respects --no-restart" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + echo "output: $output" + echo "status: $status" + assert_output_contains "Skipping restart of linked app" 0 + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app --no-restart + echo "output: $output" + echo "status: $status" + assert_output_contains "Skipping restart of linked app" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/plugins/redis/tests/service_list.bats b/plugins/redis/tests/service_list.bats new file mode 100755 index 000000000..8ace1312a --- /dev/null +++ b/plugins/redis/tests/service_list.bats @@ -0,0 +1,22 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:list) with no exposed ports, no linked apps" { + run dokku --quiet "$PLUGIN_COMMAND_PREFIX:list" + assert_output "l" +} + +@test "($PLUGIN_COMMAND_PREFIX:list) when there are no services" { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + run dokku "$PLUGIN_COMMAND_PREFIX:list" + assert_output "${lines[*]}" "There are no $PLUGIN_SERVICE services" + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} diff --git a/plugins/redis/tests/service_logs.bats b/plugins/redis/tests/service_logs.bats new file mode 100755 index 000000000..d12585cdd --- /dev/null +++ b/plugins/redis/tests/service_logs.bats @@ -0,0 +1,42 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:logs) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:logs" + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "Please specify a valid name for the service" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:logs) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:logs" not_existing_service + echo "output: $output" + echo "status: $status" + assert_contains "${lines[*]}" "service not_existing_service does not exist" + assert_failure +} + +@test "($PLUGIN_COMMAND_PREFIX:logs) success when not tailing" { + skip "This may fail if there is no log output" + run dokku "$PLUGIN_COMMAND_PREFIX:logs" l + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "($PLUGIN_COMMAND_PREFIX:logs) success when tailing" { + skip "This will hang as it waits for log output" + run dokku "$PLUGIN_COMMAND_PREFIX:logs" l -t + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/plugins/redis/tests/service_pause.bats b/plugins/redis/tests/service_pause.bats new file mode 100755 index 000000000..a2a2a696c --- /dev/null +++ b/plugins/redis/tests/service_pause.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:pause) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:pause" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:pause) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:pause" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:pause) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:pause" l + assert_success +} diff --git a/plugins/redis/tests/service_promote.bats b/plugins/redis/tests/service_promote.bats new file mode 100755 index 000000000..a3e79cbfd --- /dev/null +++ b/plugins/redis/tests/service_promote.bats @@ -0,0 +1,63 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l + dokku apps:create my-app + dokku "$PLUGIN_COMMAND_PREFIX:link" l my-app +} + +teardown() { + dokku "$PLUGIN_COMMAND_PREFIX:unlink" l my-app + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l + dokku --force apps:destroy my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:promote" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) error when the app argument is missing" { + run dokku "$PLUGIN_COMMAND_PREFIX:promote" l + assert_contains "${lines[*]}" "Please specify an app to run the command on" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) error when the app does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:promote" l not_existing_app + assert_contains "${lines[*]}" "App not_existing_app does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) error when the service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:promote" not_existing_service my-app + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) error when the service is already promoted" { + run dokku "$PLUGIN_COMMAND_PREFIX:promote" l my-app + assert_contains "${lines[*]}" "already promoted as REDIS_URL" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) changes REDIS_URL" { + password="$(sudo cat "$PLUGIN_DATA_ROOT/l/PASSWORD")" + dokku config:set my-app "REDIS_URL=redis://:p@host:6379/db" "DOKKU_REDIS_BLUE_URL=redis://:$password@dokku-redis-l:6379" + dokku "$PLUGIN_COMMAND_PREFIX:promote" l my-app + url=$(dokku config:get my-app REDIS_URL) + assert_equal "$url" "redis://:$password@dokku-redis-l:6379" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) creates new config url when needed" { + password="$(sudo cat "$PLUGIN_DATA_ROOT/l/PASSWORD")" + dokku config:set my-app "REDIS_URL=redis://:p@host:6379/db" "DOKKU_REDIS_BLUE_URL=redis://:$password@dokku-redis-l:6379" + dokku "$PLUGIN_COMMAND_PREFIX:promote" l my-app + run dokku config my-app + assert_contains "${lines[*]}" "DOKKU_REDIS_" +} + +@test "($PLUGIN_COMMAND_PREFIX:promote) uses REDIS_DATABASE_SCHEME variable" { + password="$(sudo cat "$PLUGIN_DATA_ROOT/l/PASSWORD")" + dokku config:set my-app "REDIS_DATABASE_SCHEME=redis2" "REDIS_URL=redis://:p@host:6379" "DOKKU_REDIS_BLUE_URL=redis2://:$password@dokku-redis-l:6379" + dokku "$PLUGIN_COMMAND_PREFIX:promote" l my-app + url=$(dokku config:get my-app REDIS_URL) + assert_equal "$url" "redis2://:$password@dokku-redis-l:6379" +} diff --git a/plugins/redis/tests/service_restart.bats b/plugins/redis/tests/service_restart.bats new file mode 100755 index 000000000..e74cc4752 --- /dev/null +++ b/plugins/redis/tests/service_restart.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:restart) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:restart" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:restart) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:restart" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:restart) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:restart" l + assert_success +} diff --git a/plugins/redis/tests/service_start.bats b/plugins/redis/tests/service_start.bats new file mode 100755 index 000000000..adff1d20e --- /dev/null +++ b/plugins/redis/tests/service_start.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:start) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:start" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:start) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:start" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:start) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:start" l + assert_success +} diff --git a/plugins/redis/tests/service_stop.bats b/plugins/redis/tests/service_stop.bats new file mode 100755 index 000000000..826bc917c --- /dev/null +++ b/plugins/redis/tests/service_stop.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:stop) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:stop" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:stop) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:stop" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:stop) success" { + run dokku "$PLUGIN_COMMAND_PREFIX:stop" l + assert_success +} diff --git a/plugins/redis/tests/service_unexpose.bats b/plugins/redis/tests/service_unexpose.bats new file mode 100755 index 000000000..1442728f5 --- /dev/null +++ b/plugins/redis/tests/service_unexpose.bats @@ -0,0 +1,27 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku "$PLUGIN_COMMAND_PREFIX:create" l +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" l +} + +@test "($PLUGIN_COMMAND_PREFIX:unexpose) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:unexpose" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:unexpose) error when service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:unexpose" not_existing_service + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:unexpose) success" { + dokku "$PLUGIN_COMMAND_PREFIX:expose" l + run dokku "$PLUGIN_COMMAND_PREFIX:unexpose" l + [[ ! -f $PLUGIN_DATA_ROOT/PORT ]] + assert_contains "${lines[*]}" "Service l unexposed" +} diff --git a/plugins/redis/tests/service_unlink.bats b/plugins/redis/tests/service_unlink.bats new file mode 100755 index 000000000..211698094 --- /dev/null +++ b/plugins/redis/tests/service_unlink.bats @@ -0,0 +1,77 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + dokku apps:create my-app + dokku "$PLUGIN_COMMAND_PREFIX:create" ls +} + +teardown() { + dokku --force "$PLUGIN_COMMAND_PREFIX:destroy" ls + dokku --force apps:destroy my-app +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) error when there are no arguments" { + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" + assert_contains "${lines[*]}" "Please specify a valid name for the service" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) error when the app argument is missing" { + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls + assert_contains "${lines[*]}" "Please specify an app to run the command on" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) error when the app does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls not_existing_app + assert_contains "${lines[*]}" "App not_existing_app does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) error when the service does not exist" { + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" not_existing_service my-app + assert_contains "${lines[*]}" "service not_existing_service does not exist" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) error when service not linked to app" { + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + assert_contains "${lines[*]}" "Not linked to app my-app" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) removes link from docker-options" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app >&2 + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + + check_value="Docker options build: Docker options deploy: --restart=on-failure:10 Docker options run:" + options=$(dokku --quiet docker-options:report my-app | xargs) + assert_equal "$options" "$check_value" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) unsets config url from app" { + dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app >&2 + dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + config=$(dokku config:get my-app REDIS_URL || true) + assert_equal "$config" "" +} + +@test "($PLUGIN_COMMAND_PREFIX:unlink) respects --no-restart" { + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app + echo "output: $output" + echo "status: $status" + assert_output_contains "Skipping restart of linked app" 0 + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:link" ls my-app + echo "output: $output" + echo "status: $status" + assert_success + + run dokku "$PLUGIN_COMMAND_PREFIX:unlink" ls my-app --no-restart + echo "output: $output" + echo "status: $status" + assert_output_contains "Skipping restart of linked app" + assert_success +} diff --git a/plugins/redis/tests/setup.sh b/plugins/redis/tests/setup.sh new file mode 100644 index 000000000..c48b0c139 --- /dev/null +++ b/plugins/redis/tests/setup.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $TRACE ]] && set -x +sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 762E3157 +echo "deb http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" | sudo tee /etc/apt/sources.list.d/nginx.list +curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add - + +sudo mkdir -p /etc/nginx +sudo curl https://raw.githubusercontent.com/dokku/dokku/master/tests/dhparam.pem -o /etc/nginx/dhparam.pem + +echo "dokku dokku/skip_key_file boolean true" | sudo debconf-set-selections +wget https://raw.githubusercontent.com/dokku/dokku/master/bootstrap.sh +if [[ "$DOKKU_VERSION" == "master" ]]; then + sudo bash bootstrap.sh +else + sudo DOKKU_TAG="$DOKKU_VERSION" bash bootstrap.sh +fi +echo "Dokku version $DOKKU_VERSION" + +export DOKKU_LIB_ROOT="/var/lib/dokku" +export DOKKU_PLUGINS_ROOT="$DOKKU_LIB_ROOT/plugins/available" +pushd "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")" >/dev/null +source "config" +popd >/dev/null +sudo rm -rf "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX" +sudo mkdir -p "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX" "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/subcommands" "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/scripts" "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/templates" +sudo find ./ -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX" \; +[[ -d "./scripts" ]] && sudo find ./scripts -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/scripts" \; +[[ -d "./subcommands" ]] && sudo find ./subcommands -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/subcommands" \; +[[ -d "./templates" ]] && sudo find ./templates -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/templates" \; +sudo mkdir -p "$PLUGIN_CONFIG_ROOT" "$PLUGIN_DATA_ROOT" +sudo dokku plugin:enable "$PLUGIN_COMMAND_PREFIX" +sudo dokku plugin:install diff --git a/plugins/redis/tests/shellcheck-exclude b/plugins/redis/tests/shellcheck-exclude new file mode 100644 index 000000000..9fbac741c --- /dev/null +++ b/plugins/redis/tests/shellcheck-exclude @@ -0,0 +1,8 @@ +# SC1090 - Can't follow non-constant source. Use a directive to specify location - https://github.com/koalaman/shellcheck/wiki/SC1090 +# SC1091 - Not following - +# SC2034 - Variable appears unused. Verify it or export it - https://github.com/koalaman/shellcheck/wiki/SC2034 +# SC2155 - Declare and assign separately to avoid masking return values - https://github.com/koalaman/shellcheck/wiki/SC2155 +# SC2206 - Quote to prevent word splitting/globbing, or split robustly with mapfile or read -a - https://github.com/koalaman/shellcheck/wiki/SC2206 +# SC2207 - Prefer mapfile or read -a to split command output (or quote to avoid splitting) - https://github.com/koalaman/shellcheck/wiki/SC2207 +# SC2220 - Invalid flags are not handled. Add a *) case - https://github.com/koalaman/shellcheck/wiki/SC2220 +# SC2230 - which is non-standard. Use builtin 'command -v' instead - https://github.com/koalaman/shellcheck/wiki/SC2230 diff --git a/plugins/redis/tests/shellcheck-to-junit b/plugins/redis/tests/shellcheck-to-junit new file mode 100755 index 000000000..f2ebf795f --- /dev/null +++ b/plugins/redis/tests/shellcheck-to-junit @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from __future__ import print_function +import argparse +import collections +import datetime +import re +import socket +import sys + +from xml.etree import ElementTree + + +def CDATA(text=None): + element = ElementTree.Element('![CDATA[') + element.text = text + return element + + +def _serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs): + + if elem.tag == '![CDATA[': + write("\n<{}{}]]>\n".format(elem.tag, elem.text)) + if elem.tail: + write(ElementTree._escape_cdata(elem.tail)) + else: + return ElementTree._original_serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs) + + +ElementTree._original_serialize_xml = ElementTree._serialize_xml +ElementTree._serialize_xml = ElementTree._serialize['xml'] = _serialize_xml + + +def read_in(): + lines = sys.stdin.readlines() + for i in range(len(lines)): + lines[i] = lines[i].rstrip() + return lines + + +def process_lines(lines): + files = {} + current_file = None + previous_line = None + line_no = None + new_issues = [] + code = None + + RE_VIOLATION = re.compile(r"\^-- (SC[\w]+): (.*)") + RE_VIOLATION_NEW = re.compile(r"\^[-]+\^ (SC[\w]+): (.*)") + + for line in lines: + # start a new block + if line == '': + if current_file is not None: + file_data = files.get(current_file, {}) + files[current_file] = file_data + + issue_data = file_data.get(line_no, {}) + issue_data['code'] = code + files[current_file][line_no] = issue_data + + issues = issue_data.get('issues', []) + issues.extend(new_issues) + issue_data['issues'] = issues + + files[current_file][line_no] = issue_data + + code = None + current_file = None + line_no = None + elif line.startswith('In ./') and not previous_line: + current_file = line.split(' ')[1].replace('./', '') + line_no = line.split(' ')[3] + new_issues = [] + code = None + elif code is None and len(new_issues) == 0: + code = line + else: + match = RE_VIOLATION.match(line.strip()) + if not match: + match = RE_VIOLATION_NEW.match(line.strip()) + + if not match: + if 'https://www.shellcheck.net/wiki/SC' in line: + continue + if 'For more information:' == line: + continue + print('Error: Issue parsing line "{0}"'.format(line.strip())) + else: + new_issues.append({ + 'shellcheck_id': match.group(1), + 'message': match.group(2), + 'original_message': line + }) + + previous_line = line + + return files + + +def output_junit(files, args): + timestamp = datetime.datetime.now().replace(microsecond=0).isoformat() + failures = 0 + for file, data in files.items(): + for line, issue_data in data.items(): + code = issue_data.get('code') + for issue in issue_data.get('issues', []): + failures += 1 + + tests = 0 + if args.files: + with open(args.files, 'r') as f: + tests = len(f.readlines()) + + root = ElementTree.Element("testsuite", + name="shellcheck", + tests="{0}".format(tests), + failures="{0}".format(failures), + errors="0", + skipped="0", + timestamp=timestamp, + time="0", + hostname=socket.gethostname()) + + properties = ElementTree.SubElement(root, "properties") + if args.exclude: + ElementTree.SubElement(properties, + "property", + name="exclude", + value=args.exclude) + + if args.files: + with open(args.files, 'r') as f: + lines = f.readlines() + for i in range(len(lines)): + file = lines[i].rstrip().replace('./', '') + data = files.get(file, None) + if data: + for line, issue_data in data.items(): + code = issue_data.get('code') + for issue in issue_data.get('issues', []): + testcase = ElementTree.SubElement(root, + "testcase", + classname=file, + name=file, + time="0") + shellcheck_id = issue.get('shellcheck_id') + message = 'line {0}: {1}'.format( + line, issue.get('message')) + original_message = issue.get('original_message') + e = ElementTree.Element("failure", + type=shellcheck_id, + message=message) + cdata = CDATA("\n".join([code, original_message])) + e.append(cdata) + testcase.append(e) + ElementTree.SubElement(root, + "testcase", + classname=file, + name=file, + time="0") + + ElementTree.SubElement(root, "system-out") + ElementTree.SubElement(root, "system-err") + + content = ElementTree.tostring(root, encoding='UTF-8', method='xml') + if args.output: + with open(args.output, 'w') as f: + try: + f.write(content) + except TypeError: + f.write(content.decode("utf-8")) + + +def main(): + parser = argparse.ArgumentParser( + description='Process shellcheck output to junit.') + parser.add_argument('--output', + dest='output', + action='store', + default=None, + help='file to write shellcheck output') + parser.add_argument('--files', + dest='files', + action='store', + default=None, + help='a file containing a list of all files processed by shellcheck') + parser.add_argument('--exclude', + dest='exclude', + action='store', + default=None, + help='a comma-separated list of rules being excluded by shellcheck') + args = parser.parse_args() + + lines = read_in() + files = process_lines(lines) + files = collections.OrderedDict(sorted(files.items())) + output_junit(files, args) + for line in lines: + print(line) + + +if __name__ == '__main__': + main() diff --git a/plugins/redis/tests/test_helper.bash b/plugins/redis/tests/test_helper.bash new file mode 100755 index 000000000..fb397ab7d --- /dev/null +++ b/plugins/redis/tests/test_helper.bash @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +export DOKKU_LIB_ROOT="/var/lib/dokku" +source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" + +flunk() { + { + if [ "$#" -eq 0 ]; then + cat - + else + echo "$*" + fi + } + return 1 +} + +assert_equal() { + if [ "$1" != "$2" ]; then + { + echo "expected: $1" + echo "actual: $2" + } | flunk + fi +} + +# ShellCheck doesn't know about $status from Bats +# shellcheck disable=SC2154 +assert_exit_status() { + assert_equal "$1" "$status" +} + +# ShellCheck doesn't know about $status from Bats +# shellcheck disable=SC2154 +# shellcheck disable=SC2120 +assert_success() { + if [ "$status" -ne 0 ]; then + flunk "command failed with exit status $status" + elif [ "$#" -gt 0 ]; then + assert_output "$1" + fi +} + +assert_failure() { + if [[ "$status" -eq 0 ]]; then + flunk "expected failed exit status" + elif [[ "$#" -gt 0 ]]; then + assert_output "$1" + fi +} + +assert_exists() { + if [ ! -f "$1" ]; then + flunk "expected file to exist: $1" + fi +} + +assert_contains() { + if [[ "$1" != *"$2"* ]]; then + flunk "expected $2 to be in: $1" + fi +} + +# ShellCheck doesn't know about $output from Bats +# shellcheck disable=SC2154 +assert_output() { + local expected + if [ $# -eq 0 ]; then + expected="$(cat -)" + else + expected="$1" + fi + assert_equal "$expected" "$output" +} + +# ShellCheck doesn't know about $output from Bats +# shellcheck disable=SC2154 +assert_output_contains() { + local input="$output" + local expected="$1" + local count="${2:-1}" + local found=0 + until [ "${input/$expected/}" = "$input" ]; do + input="${input/$expected/}" + found=$((found + 1)) + done + assert_equal "$count" "$found" +} diff --git a/plugins/redis/update b/plugins/redis/update new file mode 120000 index 000000000..f7ffc47af --- /dev/null +++ b/plugins/redis/update @@ -0,0 +1 @@ +install \ No newline at end of file