feat: migrate environment variables to plugin properties

Standardize how environment variables are migrated to properties during
install triggers and migrate all remaining DOKKU_* config vars to their
appropriate plugin properties.

Adds a reusable MigrateConfigToProperties() function in the common
package with Transform callback and ListProperty support, plus a
migrate-config-to-property subcommand for the prop binary so shell
plugins can use the same code path.

Migrated variables and their new property owners:
- DOKKU_APP_PROXY_TYPE/DOKKU_PROXY_TYPE → proxy type
- DOKKU_DISABLE_PROXY → proxy disabled
- DOKKU_PROXY_PORT → ports proxy-port
- DOKKU_PROXY_SSL_PORT → ports proxy-ssl-port
- DOKKU_APP_RESTORE → ps restore
- DOKKU_SKIP_DEPLOY → ps skip-deploy
- DOKKU_START_CMD → ps start-cmd
- DOKKU_DOCKERFILE_START_CMD → ps dockerfile-start-cmd
- DOKKU_DISABLE_APP_AUTOCREATION → apps disable-autocreation
- DOKKU_APP_SHELL → scheduler shell
- DOKKU_SKIP_CLEANUP → builder skip-cleanup
- DOKKU_CHECKS_DISABLED → checks disabled
- DOKKU_CHECKS_SKIPPED → checks skipped
- DOKKU_CHECKS_WAIT → checks wait
- DOKKU_CHECKS_TIMEOUT → checks timeout
- DOKKU_CHECKS_ATTEMPTS → checks attempts
- DOKKU_DEFAULT_CHECKS_WAIT → checks default-wait
- DOKKU_SKIP_ALL_CHECKS → checks disabled (legacy)
- DOKKU_SKIP_DEFAULT_CHECKS → checks skipped (legacy)

Also refactors existing bespoke migration loops in scheduler, ports, ps,
builder, and nginx-vhosts plugins to use the standardized utility.

Removes DOKKU_PARALLEL_ARGUMENTS from documentation (unused in core).
Deprecates fn-migrate-config-to-property bash function.

closes #1558
This commit is contained in:
Jose Diaz-Gonzalez
2026-04-25 05:11:07 -04:00
parent 71ec612fbd
commit 1308e21947
45 changed files with 604 additions and 468 deletions

View File

@@ -548,6 +548,121 @@ func PropertySetupApp(pluginName string, appName string) error {
return nil
}
// MigrateConfigEntry describes a single config-to-property migration
type MigrateConfigEntry struct {
// ConfigVar is the per-app environment variable name (e.g. "DOKKU_APP_PROXY_TYPE")
ConfigVar string
// GlobalConfigVar is the global environment variable name (e.g. "DOKKU_PROXY_TYPE"), empty if none
GlobalConfigVar string
// Property is the target property name (e.g. "type")
Property string
// Transform is an optional value transformation function applied before writing
Transform func(value string) string
// ListProperty indicates the value should be written as a list property via PropertyListWrite
ListProperty bool
}
// MigrateConfigToProperties migrates config variables to properties for a given plugin
// across all apps and optionally globally. It is idempotent: if the property already
// exists, the migration is skipped for that app/entry.
func MigrateConfigToProperties(pluginName string, entries []MigrateConfigEntry) error {
apps, err := UnfilteredDokkuApps()
if err != nil && !errors.Is(err, NoAppsExist) {
return nil
}
for _, entry := range entries {
if entry.GlobalConfigVar != "" {
if err := migrateConfigEntry(pluginName, "--global", entry.GlobalConfigVar, entry); err != nil {
return err
}
}
for _, appName := range apps {
if entry.ConfigVar == "" {
continue
}
if err := migrateConfigEntry(pluginName, appName, entry.ConfigVar, entry); err != nil {
return err
}
}
}
return nil
}
// migrateConfigEntry migrates a single config variable to a property for a given app
func migrateConfigEntry(pluginName string, appName string, configVar string, entry MigrateConfigEntry) error {
if entry.ListProperty {
if exists, _ := PropertyListLength(pluginName, appName, entry.Property); exists > 0 {
return nil
}
} else if PropertyExists(pluginName, appName, entry.Property) {
return nil
}
triggerName := "config-get"
triggerArgs := []string{appName, configVar}
if appName == "--global" {
triggerName = "config-get-global"
triggerArgs = []string{configVar}
}
results, _ := CallPlugnTrigger(PlugnTriggerInput{
Trigger: triggerName,
Args: triggerArgs,
})
value := results.StdoutContents()
if value == "" {
return nil
}
if entry.Transform != nil {
value = entry.Transform(value)
}
if appName == "--global" {
LogInfo1(fmt.Sprintf("Migrating deprecated global %s to %s %s property. Use 'dokku %s:set --global %s <value>' to manage this going forward.", configVar, pluginName, entry.Property, pluginName, entry.Property))
} else {
LogInfo1(fmt.Sprintf("Migrating deprecated %s to %s %s property for %s. Use 'dokku %s:set %s %s <value>' to manage this going forward.", configVar, pluginName, entry.Property, appName, pluginName, appName, entry.Property))
}
if entry.ListProperty {
values := strings.Split(value, " ")
if err := PropertyListWrite(pluginName, appName, entry.Property, values); err != nil {
return err
}
} else {
if err := PropertyWrite(pluginName, appName, entry.Property, value); err != nil {
return err
}
}
unsetTrigger := "config-unset"
unsetArgs := []string{appName, configVar}
if appName == "--global" {
unsetArgs = []string{"--global", configVar}
}
_, err := CallPlugnTrigger(PlugnTriggerInput{
Trigger: unsetTrigger,
Args: unsetArgs,
Env: map[string]string{
"DOKKU_QUIET_OUTPUT": "1",
},
})
if err != nil {
LogWarn(err.Error())
}
return nil
}
func getPropertyPath(pluginName string, appName string, property string) string {
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
return filepath.Join(pluginAppConfigRoot, property)