diff --git a/docs/appendices/0.35.0-migration-guide.md b/docs/appendices/0.35.0-migration-guide.md new file mode 100644 index 00000000000..4082878a9e5 --- /dev/null +++ b/docs/appendices/0.35.0-migration-guide.md @@ -0,0 +1,5 @@ +# 0.35.0 Migration Guide + +## Changes + +- The path on disk to both the global `ENV` file and the `ENV` file for a given app has been moved. Users should reference environment variables via the provided plugin triggers rather than directly sourcing the ENV files. Existing ENV files are left untouched and will be removed on the subsequent Dokku install. diff --git a/docs/configuration/environment-variables.md b/docs/configuration/environment-variables.md index 508a649cf43..366e8d74e3b 100644 --- a/docs/configuration/environment-variables.md +++ b/docs/configuration/environment-variables.md @@ -22,12 +22,12 @@ Environment variables are available both at run time and during the application For buildpack deploys, Dokku will create a `/app/.env` file that can be used for legacy buildpacks. Note that this is _not_ updated when `config:set` or `config:unset` is called, and is only written during a `deploy` or `ps:rebuild`. Developers are encouraged to instead read from the application environment directly, as the proper values will be available then. > [!NOTE] -> Global `ENV` files are sourced before app-specific `ENV` files. This means that app-specific variables will take precedence over global variables. Configuring your global `ENV` file is manual, and should be considered potentially dangerous as configuration applies to all applications. +> Global environment variables are sourced before app-specific environment variables. This means that app-specific variables will take precedence over global variables. Configuring global environment variables should be considered potentially dangerous as configuration applies to all applications. You can set multiple environment variables at once: ```shell -dokku config:set node-js-app ENV=prod COMPILE_ASSETS=1 +dokku config:set node-js-app APP_ENV=prod COMPILE_ASSETS=1 ``` Whitespace and special characters get tricky. If you are using dokku locally you don't need to do any special escaping. If you are using dokku over ssh you will need to backslash-escape spaces: @@ -45,7 +45,7 @@ dokku config:set --encoded node-js-app KEY="$(base64 ~/.ssh/id_rsa)" When setting or unsetting environment variables, you may wish to avoid an application restart. This is useful when developing plugins or when setting multiple environment variables in a scripted manner. To do so, use the `--no-restart` flag: ```shell -dokku config:set --no-restart node-js-app ENV=prod +dokku config:set --no-restart node-js-app APP_ENV=prod ``` If you wish to have the variables output in an `eval`-compatible form, you can use the `config:export` command @@ -54,7 +54,7 @@ If you wish to have the variables output in an `eval`-compatible form, you can u dokku config:export node-js-app # outputs variables in the form: # -# export ENV='prod' +# export APP_ENV='prod' # export COMPILE_ASSETS='1' # source in all the node-js-app app environment variables @@ -68,7 +68,7 @@ dokku config:export --format shell node-js-app # outputs variables in the form: # -# ENV='prod' COMPILE_ASSETS='1' +# APP_ENV='prod' COMPILE_ASSETS='1' ``` ## Special Config Variables diff --git a/docs/getting-started/upgrading/index.md b/docs/getting-started/upgrading/index.md index a78534e7c38..7effb60108d 100644 --- a/docs/getting-started/upgrading/index.md +++ b/docs/getting-started/upgrading/index.md @@ -18,6 +18,7 @@ Docker releases updates periodically to their engine. We recommend reading their Before upgrading, check the migration guides to get comfortable with new features and prepare your deployment to be upgraded. +- [Upgrading to 0.35](/docs/appendices/0.35.0-migration-guide.md) - [Upgrading to 0.34](/docs/appendices/0.34.0-migration-guide.md) - [Upgrading to 0.33](/docs/appendices/0.33.0-migration-guide.md) - [Upgrading to 0.32](/docs/appendices/0.32.0-migration-guide.md) diff --git a/plugins/common/common_test.go b/plugins/common/common_test.go index 562d8d9a85d..d89ae42d7c0 100644 --- a/plugins/common/common_test.go +++ b/plugins/common/common_test.go @@ -10,11 +10,11 @@ import ( var ( testAppName = "test-app-1" - testAppDir = strings.Join([]string{"/home/dokku/", testAppName}, "") + testAppDir = strings.Join([]string{"/var/lib/dokku/config/config/", testAppName}, "") testEnvFile = strings.Join([]string{testAppDir, "/ENV"}, "") testEnvLine = "export testKey=TESTING" testAppName2 = "01-test-app-1" - testAppDir2 = strings.Join([]string{"/home/dokku/", testAppName2}, "") + testAppDir2 = strings.Join([]string{"/var/lib/dokku/config/config/", testAppName2}, "") testEnvFile2 = strings.Join([]string{testAppDir2, "/ENV"}, "") testEnvLine2 = "export testKey=TESTING" ) diff --git a/plugins/common/properties.go b/plugins/common/properties.go index f0f1c23d223..6ab5a3a27b8 100644 --- a/plugins/common/properties.go +++ b/plugins/common/properties.go @@ -535,6 +535,15 @@ func PropertySetup(pluginName string) error { }) } +// PropertySetupApp creates the plugin config root for a given app +func PropertySetupApp(pluginName string, appName string) error { + if err := PropertySetup(pluginName); err != nil { + return err + } + + return makePluginAppPropertyPath(pluginName, appName) +} + func getPropertyPath(pluginName string, appName string, property string) string { pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName) return filepath.Join(pluginAppConfigRoot, property) diff --git a/plugins/config/.gitignore b/plugins/config/.gitignore index 3b7e2f512c3..63f3adbe899 100644 --- a/plugins/config/.gitignore +++ b/plugins/config/.gitignore @@ -5,3 +5,4 @@ /config-* /config_sub /post-* +/install diff --git a/plugins/config/Makefile b/plugins/config/Makefile index 86ffcb36bbb..1fb99619a84 100644 --- a/plugins/config/Makefile +++ b/plugins/config/Makefile @@ -1,6 +1,6 @@ GOARCH ?= amd64 SUBCOMMANDS = subcommands/bundle subcommands/clear subcommands/export subcommands/get subcommands/keys subcommands/show subcommands/set subcommands/unset -TRIGGERS = triggers/config-export triggers/config-get triggers/config-get-global triggers/post-app-clone-setup triggers/post-app-rename-setup +TRIGGERS = triggers/config-export triggers/config-get triggers/config-get-global triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-create triggers/post-delete BUILD = commands config_sub subcommands triggers PLUGIN_NAME = config diff --git a/plugins/config/config_test.go b/plugins/config/config_test.go index e6b9abfce72..e5a422a7e6a 100644 --- a/plugins/config/config_test.go +++ b/plugins/config/config_test.go @@ -2,6 +2,7 @@ package config import ( "os" + "path/filepath" "strings" "testing" @@ -12,9 +13,9 @@ import ( var ( testAppName = "test-app-1" - dokkuRoot = common.MustGetEnv("DOKKU_ROOT") - testAppDir = strings.Join([]string{dokkuRoot, testAppName}, "/") - globalConfigFile = strings.Join([]string{dokkuRoot, "ENV"}, "/") + dokkuLibRoot = common.MustGetEnv("DOKKU_LIB_ROOT") + testAppDir = filepath.Join(dokkuLibRoot, "config", testAppName) + globalConfigFile = filepath.Join(dokkuLibRoot, "config", "--global", "ENV") ) func setupTests() (err error) { diff --git a/plugins/config/environment.go b/plugins/config/environment.go index 877939e5f74..2651a0f9132 100644 --- a/plugins/config/environment.go +++ b/plugins/config/environment.go @@ -353,9 +353,9 @@ func loadFromFile(name string, filename string) (env *Env, err error) { } func getAppFile(appName string) (string, error) { - return filepath.Join(common.MustGetEnv("DOKKU_ROOT"), appName, "ENV"), nil + return filepath.Join(common.MustGetEnv("DOKKU_LIB_ROOT"), "config", appName, "ENV"), nil } func getGlobalFile() string { - return filepath.Join(common.MustGetEnv("DOKKU_ROOT"), "ENV") + return filepath.Join(common.MustGetEnv("DOKKU_ROOT"), "config", "--global", "ENV") } diff --git a/plugins/config/src/triggers/triggers.go b/plugins/config/src/triggers/triggers.go index 1f8f58732f0..fb7f9b076ba 100644 --- a/plugins/config/src/triggers/triggers.go +++ b/plugins/config/src/triggers/triggers.go @@ -36,6 +36,8 @@ func main() { case "config-get-global": key := flag.Arg(0) err = config.TriggerConfigGetGlobal(key) + case "install": + err = config.TriggerInstall() case "post-app-clone-setup": oldAppName := flag.Arg(0) newAppName := flag.Arg(1) @@ -44,6 +46,12 @@ func main() { oldAppName := flag.Arg(0) newAppName := flag.Arg(1) err = config.TriggerPostAppRenameSetup(oldAppName, newAppName) + case "post-create": + appName := flag.Arg(0) + err = config.TriggerPostCreate(appName) + case "post-delete": + appName := flag.Arg(0) + err = config.TriggerPostDelete(appName) default: err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) } diff --git a/plugins/config/triggers.go b/plugins/config/triggers.go index af96d114ca9..c9e01e353e6 100644 --- a/plugins/config/triggers.go +++ b/plugins/config/triggers.go @@ -2,7 +2,11 @@ package config import ( "fmt" + "os" + "path/filepath" "strconv" + + "github.com/dokku/dokku/plugins/common" ) // TriggerConfigExport returns a global config value by key @@ -45,6 +49,65 @@ func TriggerConfigGetGlobal(key string) error { return nil } +// TriggerInstall runs the install step for the config plugin +func TriggerInstall() error { + if err := common.PropertySetup("config"); err != nil { + return fmt.Errorf("Unable to install the config plugin: %s", err.Error()) + } + + apps, err := common.UnfilteredDokkuApps() + if err != nil { + return nil + } + + // migrate all created-at values from app mod-time to property + for _, appName := range apps { + oldEnvFile := filepath.Join(common.AppRoot(appName) + "ENV") + isMigrated := common.PropertyGetDefault("config", appName, "env-migrated", "") + // delete the old file on the next install + if isMigrated == "true" { + if err := os.RemoveAll(oldEnvFile); err != nil { + return fmt.Errorf("Unable to remove old ENV file: %s", err.Error()) + } + continue + } + + // skip if the file doesn't exist + if _, err := os.Stat(oldEnvFile); err != nil { + if err := common.PropertyWrite("config", appName, "env-migrated", "true"); err != nil { + return fmt.Errorf("Unable to set env-migrated property: %s", err.Error()) + } + continue + } + + if err := common.PropertySetupApp("config", appName); err != nil { + return fmt.Errorf("Unable to setup app environment: %s", err.Error()) + } + + // merge in the old env into the new env + oldEnv, err := loadFromFile(appName, oldEnvFile) + if err != nil { + return fmt.Errorf("Unable to load old environment: %s", err.Error()) + } + + env, err := LoadAppEnv(appName) + if err != nil { + return fmt.Errorf("Unable to load app environment: %s", err.Error()) + } + + env.Merge(oldEnv) + if err := env.Write(); err != nil { + return fmt.Errorf("Unable to write app environment: %s", err.Error()) + } + + if err := common.PropertyWrite("config", appName, "env-migrated", "true"); err != nil { + return fmt.Errorf("Unable to set env-migrated property: %s", err.Error()) + } + } + + return nil +} + // TriggerPostAppCloneSetup creates new buildpacks files func TriggerPostAppCloneSetup(oldAppName string, newAppName string) error { oldEnv, err := LoadAppEnv(oldAppName) @@ -84,3 +147,13 @@ func TriggerPostAppRenameSetup(oldAppName string, newAppName string) error { return nil } + +// TriggerPostCreate ensures apps have the correct config structure +func TriggerPostCreate(appName string) error { + return common.PropertySetupApp("config", appName) +} + +// TriggerPostDelete destroys the config data for a given app container +func TriggerPostDelete(appName string) error { + return common.PropertyDestroy("config", appName) +} diff --git a/tests/unit/config-oddities.bats b/tests/unit/config-oddities.bats index 011d46e168d..7ec94b7f1d1 100644 --- a/tests/unit/config-oddities.bats +++ b/tests/unit/config-oddities.bats @@ -4,16 +4,16 @@ load test_helper setup() { global_setup - [[ -f ${DOKKU_ROOT}/ENV ]] && mv -f ${DOKKU_ROOT}/ENV ${DOKKU_ROOT}/ENV.bak - sudo -H -u dokku /bin/bash -c "echo 'export global_test=true' > ${DOKKU_ROOT}/ENV" + mkdir -p "${DOKKU_LIB_ROOT}/config/--global" + [[ -f ${DOKKU_LIB_ROOT}/config/--global/ENV ]] && mv -f ${DOKKU_LIB_ROOT}/config/--global/ENV ${DOKKU_LIB_ROOT}/config/--global/ENV.bak + sudo -H -u dokku /bin/bash -c "echo 'export global_test=true' > ${DOKKU_LIB_ROOT}/config/--global/ENV" create_app } teardown() { destroy_app - ls -la ${DOKKU_ROOT} - if [[ -f ${DOKKU_ROOT}/ENV.bak ]]; then - mv -f ${DOKKU_ROOT}/ENV.bak ${DOKKU_ROOT}/ENV + if [[ -f ${DOKKU_LIB_ROOT}/config/--global/ENV.bak ]]; then + mv -f ${DOKKU_LIB_ROOT}/config/--global/ENV.bak ${DOKKU_LIB_ROOT}/config/--global/ENV fi global_teardown } diff --git a/tests/unit/config.bats b/tests/unit/config.bats index d8d8d180886..826e59039db 100644 --- a/tests/unit/config.bats +++ b/tests/unit/config.bats @@ -4,16 +4,16 @@ load test_helper setup() { global_setup - [[ -f ${DOKKU_ROOT}/ENV ]] && mv -f ${DOKKU_ROOT}/ENV ${DOKKU_ROOT}/ENV.bak - sudo -H -u dokku /bin/bash -c "echo 'export global_test=true' > ${DOKKU_ROOT}/ENV" + mkdir -p "${DOKKU_LIB_ROOT}/config/--global" + [[ -f ${DOKKU_LIB_ROOT}/config/--global/ENV ]] && mv -f ${DOKKU_LIB_ROOT}/config/--global/ENV ${DOKKU_LIB_ROOT}/config/--global/ENV.bak + sudo -H -u dokku /bin/bash -c "echo 'export global_test=true' > ${DOKKU_LIB_ROOT}/config/--global/ENV" create_app } teardown() { destroy_app - ls -la ${DOKKU_ROOT} - if [[ -f ${DOKKU_ROOT}/ENV.bak ]]; then - mv -f ${DOKKU_ROOT}/ENV.bak ${DOKKU_ROOT}/ENV + if [[ -f ${DOKKU_LIB_ROOT}/config/--global/ENV.bak ]]; then + mv -f ${DOKKU_LIB_ROOT}/config/--global/ENV.bak ${DOKKU_LIB_ROOT}/config/--global/ENV fi global_teardown }