diff --git a/plugins/docker-options/Makefile b/plugins/docker-options/Makefile new file mode 100644 index 000000000..0e9019a9f --- /dev/null +++ b/plugins/docker-options/Makefile @@ -0,0 +1,6 @@ +SUBCOMMANDS = subcommands/add subcommands/clear subcommands/remove subcommands/report +TRIGGERS = triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-delete triggers/report +BUILD = commands subcommands triggers +PLUGIN_NAME = docker-options + +include ../../common.mk diff --git a/plugins/docker-options/commands b/plugins/docker-options/commands deleted file mode 100755 index f404d4a31..000000000 --- a/plugins/docker-options/commands +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -[[ " help docker-options:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" -source "$PLUGIN_AVAILABLE_PATH/docker-options/help-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -case "$1" in - help | docker-options:help) - cmd-docker-options-help "$@" - ;; - - *) - exit "$DOKKU_NOT_IMPLEMENTED_EXIT" - ;; - -esac diff --git a/plugins/docker-options/functions b/plugins/docker-options/functions deleted file mode 100755 index e28e78f47..000000000 --- a/plugins/docker-options/functions +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" - -AVAILABLE_PHASES=(build deploy run) - -fn-get-phase-file-path() { - declare desc="return docker-options config file path for specified phase" - local APP="$1" PHASE="$2" - local PHASE_FILE="${DOKKU_ROOT}/${APP}/DOCKER_OPTIONS_${PHASE^^}" - echo "$PHASE_FILE" -} - -get_phase_file_path() { - declare desc="return docker-options config file path for specified phase" - local phase_file_prefix="DOCKER_OPTIONS_" - local phase=$1 - [[ "$APP" && "$phase" ]] || dokku_log_fail "Error: phase_file_path is incomplete." - fn-get-phase-file-path "$APP" "$phase" -} - -get_phases() { - declare desc="returns array of passed passes if all are in allowed array" - local passed_phases_list="$1" - # shellcheck disable=SC2001 - local -r phases_allowed=$(sed -e 's/ /\|/g' <<<"${AVAILABLE_PHASES[@]}") - local phase - local passed_phases - if [[ -n "$passed_phases_list" ]]; then - OIFS=$IFS - IFS=',' - read -ra passed_phases <<<"$passed_phases_list" - IFS=$OIFS - for phase in "${passed_phases[@]}"; do - [[ "$phase" == @($phases_allowed) ]] || dokku_log_fail "Phase(s) must be one of [${AVAILABLE_PHASES[*]}]" - done - echo "${passed_phases[@]}" - fi -} - -create_phase_file_if_required() { - declare desc="create phase file" - local phase_file_path="$1" - [[ -f "$phase_file_path" ]] || { - touch "$phase_file_path" - } -} - -display_phase_options() { - declare desc="print docker-options for specified phase" - local phase="$1" - local phase_file_path="$2" - echo "${phase^} options:" - sed -e 's/^/ /' "$phase_file_path" -} - -display_all_phases_options() { - declare desc="print docker-options for all phases" - local phases=("${AVAILABLE_PHASES[@]}") - for phase in "${phases[@]}"; do - local phase_file_path="$(get_phase_file_path "$phase")" - if [[ -s "$phase_file_path" ]]; then - display_phase_options "$phase" "$phase_file_path" - fi - done -} - -display_passed_phases_options() { - declare desc="print docker options for list of specified phases" - local passed_phases=("${!1}") - local phase - for phase in "${passed_phases[@]}"; do - local phase_file_path="$(get_phase_file_path "$phase")" - if [[ ! -s "$phase_file_path" ]]; then - echo "${phase^} options: none" - else - display_phase_options "$phase" "$phase_file_path" - fi - done -} - -add_passed_docker_option() { - declare desc="adds docker option to specified phases" - local passed_phases=("${!1}") - shift - local passed_option_string="$*" - local phase - for phase in "${passed_phases[@]}"; do - local phase_file_path="$(get_phase_file_path "$phase")" - create_phase_file_if_required "$phase_file_path" - echo "${passed_option_string}" >>"$phase_file_path" - local all_phase_options="$(<"$phase_file_path")" - echo -e "${all_phase_options}" | sed '/^$/d' | sort -u >"$phase_file_path" - done -} - -remove_passed_docker_option() { - declare desc="removes docker option from specified phases" - local passed_phases=("${!1}") - shift - local passed_option_string="$*" - local phase - for phase in "${passed_phases[@]}"; do - local phase_file_path="$(get_phase_file_path "$phase")" - [[ ! -s "$phase_file_path" ]] || { - local all_phase_options="$(<"$phase_file_path")" - local all_phase_options=$(echo -e "${all_phase_options}" | sed "s#^${passed_option_string}\$##") - echo -e "${all_phase_options}" | sed '/^$/d' | sort -u >"$phase_file_path" - } - done -} diff --git a/plugins/docker-options/go.mod b/plugins/docker-options/go.mod index cc765fc5b..b76b55128 100644 --- a/plugins/docker-options/go.mod +++ b/plugins/docker-options/go.mod @@ -2,7 +2,10 @@ module github.com/dokku/dokku/plugins/docker-options go 1.26.2 -require github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000 +require ( + github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000 + github.com/spf13/pflag v1.0.10 +) require ( github.com/alexellis/go-execute/v2 v2.2.1 // indirect diff --git a/plugins/docker-options/go.sum b/plugins/docker-options/go.sum index d7b54471f..c97fc9665 100644 --- a/plugins/docker-options/go.sum +++ b/plugins/docker-options/go.sum @@ -32,6 +32,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= diff --git a/plugins/docker-options/help-functions b/plugins/docker-options/help-functions deleted file mode 100755 index 71d8c427a..000000000 --- a/plugins/docker-options/help-functions +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-docker-options-help() { - declare desc="help command" - declare CMD="$1" - local plugin_name="docker-options" - local plugin_description="Manage docker options for an app" - - if [[ "$CMD" == "${plugin_name}:help" ]]; then - echo -e "Usage: dokku ${plugin_name}[:COMMAND]" - echo '' - echo "$plugin_description" - echo '' - echo 'Additional commands:' - fn-help-content | sort | column -c2 -t -s, - elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then - fn-help-content - else - cat < OPTION, Add Docker option to app for phase (comma separated phase list) - docker-options:clear [phase(s)], Clear a docker options from application with an optional phase (comma separated phase list) - docker-options:remove OPTION, Remove Docker option from app for phase (comma separated phase list) - docker-options:report [] [], Displays a docker options report for one or more apps -help_content -} diff --git a/plugins/docker-options/internal-functions b/plugins/docker-options/internal-functions deleted file mode 100755 index 5634f4f28..000000000 --- a/plugins/docker-options/internal-functions +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-docker-options-report() { - declare desc="displays a docker options report for one or more apps" - declare cmd="docker-options:report" - [[ "$1" == "$cmd" ]] && shift 1 - declare APP="$1" INFO_FLAG="$2" - - if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then - INFO_FLAG="$APP" - APP="" - fi - - if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then - INFO_FLAG="true" - fi - - if [[ -z "$APP" ]]; then - for app in $(dokku_apps); do - cmd-docker-options-report-single "$app" "$INFO_FLAG" | tee || true - done - else - cmd-docker-options-report-single "$APP" "$INFO_FLAG" - fi -} - -cmd-docker-options-report-single() { - declare APP="$1" INFO_FLAG="$2" - if [[ "$INFO_FLAG" == "true" ]]; then - INFO_FLAG="" - fi - verify_app_name "$APP" - local flag_map=( - "--docker-options-build: $(fn-docker-options "$APP" build)" - "--docker-options-deploy: $(fn-docker-options "$APP" deploy)" - "--docker-options-run: $(fn-docker-options "$APP" run)" - ) - - if [[ -z "$INFO_FLAG" ]]; then - dokku_log_info2_quiet "$APP docker options information" - for flag in "${flag_map[@]}"; do - key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" - dokku_log_verbose "$(printf "%-30s %-25s" "${key^}" "${flag#*: }")" - done - else - local match=false - local value_exists=false - for flag in "${flag_map[@]}"; do - valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)" - if [[ "$flag" == "${INFO_FLAG}:"* ]]; then - value=${flag#*: } - size="${#value}" - if [[ "$size" -ne 0 ]]; then - echo "$value" && match=true && value_exists=true - else - match=true - fi - fi - done - [[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}" - echo "" - fi -} - -fn-docker-options() { - declare APP="$1" PHASE="$2" - local PHASE_FILE="$(fn-get-phase-file-path "$APP" "$PHASE")" - if [[ -r "$PHASE_FILE" ]]; then - tr '\n' ' ' <"$PHASE_FILE" - fi -} diff --git a/plugins/docker-options/internal-functions.go b/plugins/docker-options/internal-functions.go new file mode 100644 index 000000000..70cb95513 --- /dev/null +++ b/plugins/docker-options/internal-functions.go @@ -0,0 +1,45 @@ +package dockeroptions + +import ( + "errors" + "fmt" + "strings" +) + +var availablePhases = []string{"build", "deploy", "run"} + +func parsePhases(phasesArg string) ([]string, error) { + if phasesArg == "" { + return nil, nil + } + + phases := strings.Split(phasesArg, ",") + for _, phase := range phases { + valid := false + for _, allowed := range availablePhases { + if phase == allowed { + valid = true + break + } + } + if !valid { + return nil, fmt.Errorf("Phase(s) must be one of [%s]", strings.Join(availablePhases, " ")) + } + } + + return phases, nil +} + +// ErrIfReservedFlagsUsed returns a not-yet-implemented error when --process or --global are passed. +// Both flags are reserved for the upcoming process-scoped docker options work (issue #2441). +func ErrIfReservedFlagsUsed(processes []string, global bool) error { + if len(processes) == 0 && !global { + return nil + } + + if len(processes) > 0 && global { + return errors.New("--process and --global flags are mutually exclusive") + } + + return errors.New("--process and --global flags are reserved for a future release and not yet implemented") +} diff --git a/plugins/docker-options/post-app-clone-setup b/plugins/docker-options/post-app-clone-setup deleted file mode 100755 index 9b36b329d..000000000 --- a/plugins/docker-options/post-app-clone-setup +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" - -trigger-docker-options-post-app-clone-setup() { - declare desc="copies docker options over" - declare trigger="post-app-clone-setup" - declare OLD_APP="$1" NEW_APP="$2" - - declare -a phases - phases=("build" "deploy" "run") - for phase in "${phases[@]}"; do - if [[ -f "$(fn-get-phase-file-path "$OLD_APP" "$phase")" ]]; then - cp "$(fn-get-phase-file-path "$OLD_APP" "$phase")" "$(fn-get-phase-file-path "$NEW_APP" "$phase")" - fi - done -} - -trigger-docker-options-post-app-clone-setup "$@" diff --git a/plugins/docker-options/post-app-rename-setup b/plugins/docker-options/post-app-rename-setup deleted file mode 100755 index 0688252a6..000000000 --- a/plugins/docker-options/post-app-rename-setup +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" - -trigger-docker-options-post-app-rename-setup() { - declare desc="copies docker options over" - declare trigger="post-app-rename-setup" - declare OLD_APP="$1" NEW_APP="$2" - - declare -a phases - phases=("build" "deploy" "run") - for phase in "${phases[@]}"; do - if [[ -f "$(fn-get-phase-file-path "$OLD_APP" "$phase")" ]]; then - cp "$(fn-get-phase-file-path "$OLD_APP" "$phase")" "$(fn-get-phase-file-path "$NEW_APP" "$phase")" - fi - done -} - -trigger-docker-options-post-app-rename-setup "$@" diff --git a/plugins/docker-options/post-delete b/plugins/docker-options/post-delete deleted file mode 100755 index 3505fb6a8..000000000 --- a/plugins/docker-options/post-delete +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" - -trigger-docker-options-post-delete() { - declare desc="deletes docker options files for an app" - declare trigger="post-delete" - declare APP="$1" - - rm -f "$(fn-get-phase-file-path "$APP" "build")" "$(fn-get-phase-file-path "$APP" "deploy")" "$(fn-get-phase-file-path "$APP" "run")" -} - -trigger-docker-options-post-delete "$@" diff --git a/plugins/docker-options/report b/plugins/docker-options/report deleted file mode 100755 index 722271960..000000000 --- a/plugins/docker-options/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/docker-options/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-docker-options-report-single "$@" diff --git a/plugins/docker-options/report.go b/plugins/docker-options/report.go new file mode 100644 index 000000000..20c6117e0 --- /dev/null +++ b/plugins/docker-options/report.go @@ -0,0 +1,50 @@ +package dockeroptions + +import ( + "strings" + + "github.com/dokku/dokku/plugins/common" +) + +// ReportSingleApp displays the docker options report for a single app +func ReportSingleApp(appName string, format string, infoFlag string) error { + if err := common.VerifyAppName(appName); err != nil { + return err + } + + flags := map[string]common.ReportFunc{ + "--docker-options-build": reportBuildOptions, + "--docker-options-deploy": reportDeployOptions, + "--docker-options-run": reportRunOptions, + } + + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + + trimPrefix := false + uppercaseFirstCharacter := true + infoFlags := common.CollectReport(appName, infoFlag, flags) + return common.ReportSingleApp("docker options", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) +} + +func reportBuildOptions(appName string) string { + return joinPhaseOptions(appName, "build") +} + +func reportDeployOptions(appName string) string { + return joinPhaseOptions(appName, "deploy") +} + +func reportRunOptions(appName string) string { + return joinPhaseOptions(appName, "run") +} + +func joinPhaseOptions(appName string, phase string) string { + options, err := GetDockerOptionsForPhase(appName, phase) + if err != nil || len(options) == 0 { + return "" + } + return strings.Join(options, " ") +} diff --git a/plugins/docker-options/src/commands/commands.go b/plugins/docker-options/src/commands/commands.go new file mode 100644 index 000000000..c8c85c6bb --- /dev/null +++ b/plugins/docker-options/src/commands/commands.go @@ -0,0 +1,57 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strconv" + "strings" + + "github.com/dokku/dokku/plugins/common" +) + +const ( + helpHeader = `Usage: dokku docker-options[:COMMAND] + +Manage docker options for an app + +Additional commands:` + + helpContent = ` + docker-options:add OPTION, Add Docker option to app for phase (comma separated phase list) + docker-options:clear [phase(s)], Clear a docker options from application with an optional phase (comma separated phase list) + docker-options:remove OPTION, Remove Docker option from app for phase (comma separated phase list) + docker-options:report [] [], Displays a docker options report for one or more apps` +) + +func main() { + flag.Usage = usage + flag.Parse() + + cmd := flag.Arg(0) + switch cmd { + case "docker-options", "docker-options:help": + usage() + case "help": + result, err := common.CallExecCommand(common.ExecCommandInput{ + Command: "ps", + Args: []string{"-o", "command=", strconv.Itoa(os.Getppid())}, + }) + if err == nil && strings.Contains(result.StdoutContents(), "--all") { + fmt.Println(helpContent) + } else { + fmt.Print("\n docker-options, Manage docker options for an app\n") + } + default: + dokkuNotImplementExitCode, err := strconv.Atoi(os.Getenv("DOKKU_NOT_IMPLEMENTED_EXIT")) + if err != nil { + fmt.Println("failed to retrieve DOKKU_NOT_IMPLEMENTED_EXIT environment variable") + dokkuNotImplementExitCode = 10 + } + os.Exit(dokkuNotImplementExitCode) + } +} + +func usage() { + common.CommandUsage(helpHeader, helpContent) +} diff --git a/plugins/docker-options/src/subcommands/subcommands.go b/plugins/docker-options/src/subcommands/subcommands.go new file mode 100644 index 000000000..d70e97263 --- /dev/null +++ b/plugins/docker-options/src/subcommands/subcommands.go @@ -0,0 +1,109 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" + dockeroptions "github.com/dokku/dokku/plugins/docker-options" + + flag "github.com/spf13/pflag" +) + +func main() { + parts := strings.Split(os.Args[0], "/") + subcommand := parts[len(parts)-1] + + var err error + switch subcommand { + case "add": + args := flag.NewFlagSet("docker-options:add", flag.ExitOnError) + args.SetInterspersed(false) + processes := args.StringSlice("process", []string{}, "process types to scope this option to (reserved for future use)") + global := args.Bool("global", false, "explicitly mark as a global option (reserved for future use)") + args.Parse(os.Args[2:]) + + if err = dockeroptions.ErrIfReservedFlagsUsed(*processes, *global); err != nil { + break + } + + positional := args.Args() + appName := "" + phases := "" + var optionParts []string + if len(positional) > 0 { + appName = positional[0] + } + if len(positional) > 1 { + phases = positional[1] + } + if len(positional) > 2 { + optionParts = positional[2:] + } + err = dockeroptions.CommandAdd(appName, phases, strings.Join(optionParts, " ")) + case "remove": + args := flag.NewFlagSet("docker-options:remove", flag.ExitOnError) + args.SetInterspersed(false) + processes := args.StringSlice("process", []string{}, "process types to scope this option to (reserved for future use)") + global := args.Bool("global", false, "explicitly mark as a global option (reserved for future use)") + args.Parse(os.Args[2:]) + + if err = dockeroptions.ErrIfReservedFlagsUsed(*processes, *global); err != nil { + break + } + + positional := args.Args() + appName := "" + phases := "" + var optionParts []string + if len(positional) > 0 { + appName = positional[0] + } + if len(positional) > 1 { + phases = positional[1] + } + if len(positional) > 2 { + optionParts = positional[2:] + } + err = dockeroptions.CommandRemove(appName, phases, strings.Join(optionParts, " ")) + case "clear": + args := flag.NewFlagSet("docker-options:clear", flag.ExitOnError) + args.SetInterspersed(false) + processes := args.StringSlice("process", []string{}, "process types to scope this option to (reserved for future use)") + global := args.Bool("global", false, "explicitly mark as a global option (reserved for future use)") + args.Parse(os.Args[2:]) + + if err = dockeroptions.ErrIfReservedFlagsUsed(*processes, *global); err != nil { + break + } + + positional := args.Args() + appName := "" + phases := "" + if len(positional) > 0 { + appName = positional[0] + } + if len(positional) > 1 { + phases = positional[1] + } + err = dockeroptions.CommandClear(appName, phases) + case "report": + args := flag.NewFlagSet("docker-options:report", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") + osArgs, infoFlag, flagErr := common.ParseReportArgs("docker-options", os.Args[2:]) + if flagErr == nil { + args.Parse(osArgs) + appName := args.Arg(0) + err = dockeroptions.CommandReport(appName, *format, infoFlag) + } else { + err = flagErr + } + default: + err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/docker-options/src/triggers/triggers.go b/plugins/docker-options/src/triggers/triggers.go new file mode 100644 index 000000000..f65ff8554 --- /dev/null +++ b/plugins/docker-options/src/triggers/triggers.go @@ -0,0 +1,41 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" + dockeroptions "github.com/dokku/dokku/plugins/docker-options" +) + +func main() { + parts := strings.Split(os.Args[0], "/") + trigger := parts[len(parts)-1] + flag.Parse() + + var err error + switch trigger { + case "post-app-clone-setup": + oldAppName := flag.Arg(0) + newAppName := flag.Arg(1) + err = dockeroptions.TriggerPostAppCloneSetup(oldAppName, newAppName) + case "post-app-rename-setup": + oldAppName := flag.Arg(0) + newAppName := flag.Arg(1) + err = dockeroptions.TriggerPostAppRenameSetup(oldAppName, newAppName) + case "post-delete": + appName := flag.Arg(0) + err = dockeroptions.TriggerPostDelete(appName) + case "report": + appName := flag.Arg(0) + err = dockeroptions.ReportSingleApp(appName, "", "") + default: + err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/docker-options/subcommands.go b/plugins/docker-options/subcommands.go new file mode 100644 index 000000000..f68606245 --- /dev/null +++ b/plugins/docker-options/subcommands.go @@ -0,0 +1,105 @@ +package dockeroptions + +import ( + "errors" + "fmt" + "os" + + "github.com/dokku/dokku/plugins/common" +) + +// CommandAdd adds a docker option to the specified phases for an app +func CommandAdd(appName string, phasesArg string, option string) error { + if err := common.VerifyAppName(appName); err != nil { + return err + } + + phases, err := parsePhases(phasesArg) + if err != nil { + return err + } + + if option == "" { + return errors.New("Please specify docker options to add to the phase") + } + + return AddDockerOptionToPhases(appName, phases, option) +} + +// CommandRemove removes a docker option from the specified phases for an app +func CommandRemove(appName string, phasesArg string, option string) error { + if err := common.VerifyAppName(appName); err != nil { + return err + } + + phases, err := parsePhases(phasesArg) + if err != nil { + return err + } + + if option == "" { + return errors.New("Please specify docker options to remove from the phase") + } + + return RemoveDockerOptionFromPhases(appName, phases, option) +} + +// CommandClear removes all docker options for an app, optionally limited to a list of phases +func CommandClear(appName string, phasesArg string) error { + if err := common.VerifyAppName(appName); err != nil { + return err + } + + if phasesArg == "" { + common.LogInfo1(fmt.Sprintf("Clearing docker-options for %s on all phases", appName)) + for _, phase := range availablePhases { + if err := removePhaseFile(appName, phase); err != nil { + return err + } + } + return nil + } + + phases, err := parsePhases(phasesArg) + if err != nil { + return err + } + + for _, phase := range phases { + common.LogInfo1(fmt.Sprintf("Clearing docker-options for %s on phase %s", appName, phase)) + if err := removePhaseFile(appName, phase); err != nil { + return err + } + } + + return nil +} + +// CommandReport displays a docker-options report for one or more apps +func CommandReport(appName string, format string, infoFlag string) error { + if appName == "" { + apps, err := common.DokkuApps() + if err != nil { + if errors.Is(err, common.NoAppsExist) { + common.LogWarn(err.Error()) + return nil + } + return err + } + for _, name := range apps { + if err := ReportSingleApp(name, format, infoFlag); err != nil { + return err + } + } + return nil + } + + return ReportSingleApp(appName, format, infoFlag) +} + +func removePhaseFile(appName string, phase string) error { + if err := os.Remove(getPhaseFilePath(appName, phase)); err != nil && !os.IsNotExist(err) { + return err + } + return nil +} diff --git a/plugins/docker-options/subcommands/add b/plugins/docker-options/subcommands/add deleted file mode 100755 index 98233ad1a..000000000 --- a/plugins/docker-options/subcommands/add +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" - -cmd-docker-options-add() { - declare desc="Add a docker option to application" - declare cmd="docker-options:add" - [[ "$1" == "$cmd" ]] && shift 1 - declare APP="$1" - - verify_app_name "$APP" - read -ra passed_phases <<<"$(get_phases "$2")" - shift 2 # everything else passed is the docker option - local passed_docker_option="$*" - [[ -z "$passed_docker_option" ]] && dokku_log_fail "Please specify docker options to add to the phase" - add_passed_docker_option passed_phases[@] "${passed_docker_option[@]}" -} - -cmd-docker-options-add "$@" diff --git a/plugins/docker-options/subcommands/clear b/plugins/docker-options/subcommands/clear deleted file mode 100755 index 83c0501dd..000000000 --- a/plugins/docker-options/subcommands/clear +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" - -cmd-docker-options-clear() { - declare desc="clear a docker options from application" - declare cmd="docker-options:clear" - [[ "$1" == "$cmd" ]] && shift 1 - declare APP="$1" PHASES="$2" - - verify_app_name "$APP" - - if [[ -z "$PHASES" ]]; then - dokku_log_info1 "Clearing docker-options for $APP on all phases" - rm -f "$(fn-get-phase-file-path "$APP" "build")" "$(fn-get-phase-file-path "$APP" "deploy")" "$(fn-get-phase-file-path "$APP" "run")" - return - fi - - read -ra passed_phases <<<"$(get_phases "$PHASES")" - for phase in "${passed_phases[@]}"; do - dokku_log_info1 "Clearing docker-options for $APP on phase $phase" - rm -f "$(fn-get-phase-file-path "$APP" "$phase")" - done -} - -cmd-docker-options-clear "$@" diff --git a/plugins/docker-options/subcommands/default b/plugins/docker-options/subcommands/default deleted file mode 100755 index 409079c9b..000000000 --- a/plugins/docker-options/subcommands/default +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_AVAILABLE_PATH/docker-options/help-functions" - -cmd-docker-options-help "docker-options:help" diff --git a/plugins/docker-options/subcommands/remove b/plugins/docker-options/subcommands/remove deleted file mode 100755 index bddbbcdbd..000000000 --- a/plugins/docker-options/subcommands/remove +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" - -cmd-docker-options-remove() { - declare desc="Remove a docker option from application" - declare cmd="docker-options:remove" - [[ "$1" == "$cmd" ]] && shift 1 - declare APP="$1" - - verify_app_name "$APP" - read -ra passed_phases <<<"$(get_phases "$2")" - shift 2 # everything else passed is the docker option - [[ -z ${passed_docker_option="$@"} ]] && dokku_log_fail "Please specify docker options to remove from the phase" - remove_passed_docker_option passed_phases[@] "${passed_docker_option[@]}" -} - -cmd-docker-options-remove "$@" diff --git a/plugins/docker-options/subcommands/report b/plugins/docker-options/subcommands/report deleted file mode 100755 index 5985f4add..000000000 --- a/plugins/docker-options/subcommands/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/docker-options/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-docker-options-report "$@" diff --git a/plugins/docker-options/triggers.go b/plugins/docker-options/triggers.go new file mode 100644 index 000000000..9e63a06fc --- /dev/null +++ b/plugins/docker-options/triggers.go @@ -0,0 +1,65 @@ +package dockeroptions + +import ( + "fmt" + "io" + "os" + + "github.com/dokku/dokku/plugins/common" +) + +// TriggerPostAppCloneSetup copies docker option files 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 +} + +// TriggerPostAppRenameSetup copies docker option files from the old app name to the new app name +func TriggerPostAppRenameSetup(oldAppName string, newAppName string) error { + for _, phase := range availablePhases { + if err := copyPhaseFile(oldAppName, newAppName, phase); err != nil { + return err + } + } + 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 + } + } + return nil +} + +func copyPhaseFile(srcApp string, dstApp string, phase string) error { + srcPath := getPhaseFilePath(srcApp, phase) + if !common.FileExists(srcPath) { + return nil + } + + src, err := os.Open(srcPath) + if err != nil { + return fmt.Errorf("Unable to open docker options phase file %s.%s: %s", srcApp, phase, err.Error()) + } + defer src.Close() + + dstPath := getPhaseFilePath(dstApp, phase) + dst, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return fmt.Errorf("Unable to create docker options phase file %s.%s: %s", dstApp, phase, err.Error()) + } + defer dst.Close() + + if _, err := io.Copy(dst, src); err != nil { + return fmt.Errorf("Unable to copy docker options phase file %s.%s to %s.%s: %s", srcApp, phase, dstApp, phase, err.Error()) + } + + return nil +}