refactor: migrate docker-options subcommands to go
Ports the user-facing CLI subcommands and lifecycle triggers from bash to go, mirroring the layout used by the ports plugin. The on-disk format (`DOCKER_OPTIONS_BUILD/DEPLOY/RUN` files), the `docker-args-build/deploy/run` triggers, and every exported helper consumed by other plugins are unchanged. Reserves the `--process` and `--global` flags on add, remove, and clear so usage today exits with a not-yet-implemented error and the surface is locked in ahead of the process-scoping work tracked in #2441.
This commit is contained in:
6
plugins/docker-options/Makefile
Normal file
6
plugins/docker-options/Makefile
Normal file
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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 <<help_desc
|
||||
$plugin_name, $plugin_description
|
||||
help_desc
|
||||
fi
|
||||
}
|
||||
|
||||
fn-help-content() {
|
||||
declare desc="return help content"
|
||||
cat <<help_content
|
||||
docker-options:add <app> <phase(s)> OPTION, Add Docker option to app for phase (comma separated phase list)
|
||||
docker-options:clear <app> [phase(s)], Clear a docker options from application with an optional phase (comma separated phase list)
|
||||
docker-options:remove <app> <phase(s)> OPTION, Remove Docker option from app for phase (comma separated phase list)
|
||||
docker-options:report [<app>] [<flag>], Displays a docker options report for one or more apps
|
||||
help_content
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
45
plugins/docker-options/internal-functions.go
Normal file
45
plugins/docker-options/internal-functions.go
Normal file
@@ -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")
|
||||
}
|
||||
@@ -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 "$@"
|
||||
@@ -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 "$@"
|
||||
@@ -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 "$@"
|
||||
@@ -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 "$@"
|
||||
50
plugins/docker-options/report.go
Normal file
50
plugins/docker-options/report.go
Normal file
@@ -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, " ")
|
||||
}
|
||||
57
plugins/docker-options/src/commands/commands.go
Normal file
57
plugins/docker-options/src/commands/commands.go
Normal file
@@ -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 <app> <phase(s)> OPTION, Add Docker option to app for phase (comma separated phase list)
|
||||
docker-options:clear <app> [phase(s)], Clear a docker options from application with an optional phase (comma separated phase list)
|
||||
docker-options:remove <app> <phase(s)> OPTION, Remove Docker option from app for phase (comma separated phase list)
|
||||
docker-options:report [<app>] [<flag>], 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)
|
||||
}
|
||||
109
plugins/docker-options/src/subcommands/subcommands.go
Normal file
109
plugins/docker-options/src/subcommands/subcommands.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
41
plugins/docker-options/src/triggers/triggers.go
Normal file
41
plugins/docker-options/src/triggers/triggers.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
105
plugins/docker-options/subcommands.go
Normal file
105
plugins/docker-options/subcommands.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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 "$@"
|
||||
@@ -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 "$@"
|
||||
@@ -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"
|
||||
@@ -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 "$@"
|
||||
@@ -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 "$@"
|
||||
65
plugins/docker-options/triggers.go
Normal file
65
plugins/docker-options/triggers.go
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user