feat: scope docker-options to specific procfile processes
Adds a `--process` flag (repeatable) to docker-options:add/remove/clear/list and the new docker-options:list subcommand for querying a single process+phase pair. Process scoping is supported only for the deploy phase since build runs once per app and run covers ad-hoc commands and cron tasks where no Procfile process type is available. Storage moves from `$DOKKU_ROOT/$APP/DOCKER_OPTIONS_*` files to property lists under `/var/lib/dokku/config/docker-options/$APP/{processType}.{phase}`, with `_default_` as the sentinel for app-wide options. The install trigger migrates pre-existing DOCKER_OPTIONS_* files into property lists once and renames them to `.migrated`; a global marker makes re-runs strictly no-op. The legacy docker-args-{build,deploy,run} bash triggers are reimplemented in Go alongside a new docker-args-process-deploy trigger that surfaces per-process options to the scheduler. The :report command exposes one dynamic flag per configured `process.deploy` pair (e.g. `--docker-options-deploy.web`) and supports `--format json`. There is no `--global` flag; omitting `--process` keeps the historical default behaviour, since `--global` elsewhere in dokku means "across all apps". Closes #2441.
This commit is contained in:
@@ -1,31 +1,141 @@
|
||||
package dockeroptions
|
||||
|
||||
// TriggerPostAppCloneSetup copies docker option files from the source app to the cloned app
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
// TriggerInstall sets up the docker-options property directory and migrates
|
||||
// any pre-existing DOCKER_OPTIONS_* files into property lists.
|
||||
func TriggerInstall() error {
|
||||
if err := common.PropertySetup("docker-options"); err != nil {
|
||||
return fmt.Errorf("Unable to install the docker-options plugin: %v", err)
|
||||
}
|
||||
|
||||
if err := migrateLegacyDockerOptionsFiles(); err != nil {
|
||||
return fmt.Errorf("Unable to migrate legacy docker-options files: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TriggerPostAppCloneSetup copies docker option properties from the source app
|
||||
// to the cloned app.
|
||||
func TriggerPostAppCloneSetup(oldAppName string, newAppName string) error {
|
||||
for _, phase := range availablePhases {
|
||||
if err := copyPhaseFile(oldAppName, newAppName, phase); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return common.PropertyClone("docker-options", oldAppName, newAppName)
|
||||
}
|
||||
|
||||
// TriggerPostAppRenameSetup copies docker option files from the old app name to the new app name
|
||||
// TriggerPostAppRenameSetup moves docker option properties from the old app
|
||||
// name to the new one.
|
||||
func TriggerPostAppRenameSetup(oldAppName string, newAppName string) error {
|
||||
for _, phase := range availablePhases {
|
||||
if err := copyPhaseFile(oldAppName, newAppName, phase); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := common.PropertyClone("docker-options", oldAppName, newAppName); err != nil {
|
||||
return err
|
||||
}
|
||||
return common.PropertyDestroy("docker-options", oldAppName)
|
||||
}
|
||||
|
||||
// TriggerPostDelete removes the docker option properties for an app and
|
||||
// cleans up any leftover migrated legacy files.
|
||||
func TriggerPostDelete(appName string) error {
|
||||
if err := common.PropertyDestroy("docker-options", appName); err != nil {
|
||||
return err
|
||||
}
|
||||
removeMigratedLegacyFiles(appName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TriggerPostDelete deletes the docker option files for an app
|
||||
func TriggerPostDelete(appName string) error {
|
||||
for _, phase := range availablePhases {
|
||||
if err := removePhaseFile(appName, phase); err != nil {
|
||||
return err
|
||||
}
|
||||
// TriggerDockerArgs implements the legacy docker-args-{build,deploy,run}
|
||||
// triggers. It echoes stdin verbatim, then appends the default-scope options
|
||||
// for the given phase after applying image-source-type filtering.
|
||||
func TriggerDockerArgs(phase, appName, imageSourceType, processType string) error {
|
||||
stdin, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := os.Stdout.Write(stdin); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
options, err := GetDockerOptionsForProcessPhase(appName, DefaultProcessType, phase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
emitFilteredOptions(phase, imageSourceType, options)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TriggerDockerArgsProcessDeploy implements docker-args-process-deploy. It
|
||||
// echoes stdin verbatim, then appends the deploy-phase options for the
|
||||
// process-specific scope (filtered by image source type). Default-scope
|
||||
// options are emitted by TriggerDockerArgs (docker-args-deploy), which the
|
||||
// scheduler invokes alongside the per-process trigger.
|
||||
func TriggerDockerArgsProcessDeploy(appName, imageSourceType, processType string) error {
|
||||
stdin, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := os.Stdout.Write(stdin); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if processType == "" || processType == DefaultProcessType {
|
||||
return nil
|
||||
}
|
||||
|
||||
options, err := GetDockerOptionsForProcessPhase(appName, processType, "deploy")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
emitFilteredOptions("deploy", imageSourceType, options)
|
||||
return nil
|
||||
}
|
||||
|
||||
// emitFilteredOptions writes filtered docker options to stdout, prefixed with
|
||||
// a single space so concatenation with prior output stays well-formed. It
|
||||
// reproduces the filtering historically performed by the bash docker-args-*
|
||||
// triggers.
|
||||
func emitFilteredOptions(phase, imageSourceType string, options []string) {
|
||||
for _, option := range options {
|
||||
option = strings.TrimSpace(option)
|
||||
if option == "" || strings.HasPrefix(option, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(option, "--restart") {
|
||||
if phase == "deploy" {
|
||||
fmt.Printf(" %s", option)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch imageSourceType {
|
||||
case "dockerfile", "nixpacks", "railpack":
|
||||
if hasAnyPrefix(option, "--link", "-v", "--volume") {
|
||||
continue
|
||||
}
|
||||
case "herokuish":
|
||||
if hasAnyPrefix(option, "--file", "--build-args") {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf(" %s", option)
|
||||
}
|
||||
}
|
||||
|
||||
func hasAnyPrefix(s string, prefixes ...string) bool {
|
||||
for _, prefix := range prefixes {
|
||||
if s == prefix || strings.HasPrefix(s, prefix+"=") || strings.HasPrefix(s, prefix+" ") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user