diff --git a/plugins/config/Makefile b/plugins/config/Makefile index 7be217943..6a4bef9ec 100644 --- a/plugins/config/Makefile +++ b/plugins/config/Makefile @@ -1,6 +1,12 @@ 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 -BUILD = commands subcommands triggers +BUILD = commands config_sub subcommands triggers PLUGIN_NAME = config +clean-config_sub: + rm -rf config_sub + +config_sub: clean-config_sub **/**/config_sub.go + go build -ldflags="-s -w" $(GO_ARGS) -o config_sub src/config_sub/config_sub.go + include ../../common.mk diff --git a/plugins/config/functions b/plugins/config/functions index 9cb3dccbf..fa3b4e0e3 100755 --- a/plugins/config/functions +++ b/plugins/config/functions @@ -7,7 +7,7 @@ config_sub() { declare desc="executes a config subcommand" local name="$1" shift - "$PLUGIN_AVAILABLE_PATH/config/subcommands/$name" "config:$name" "$@" + "$PLUGIN_AVAILABLE_PATH/config/config_sub" "$name" "$@" } config_export() { diff --git a/plugins/config/functions.go b/plugins/config/functions.go index 13407fbfb..28997d25a 100644 --- a/plugins/config/functions.go +++ b/plugins/config/functions.go @@ -1,13 +1,16 @@ package config -import "fmt" +import ( + "encoding/base64" + "errors" + "fmt" + "os" + "strings" -func export(appName string, global bool, merged bool, format string) error { - appName, err := getAppNameOrGlobal(appName, global) - if err != nil { - return err - } + "github.com/dokku/dokku/plugins/common" +) +func export(appName string, merged bool, format string) error { env := getEnvironment(appName, merged) exportType := ExportFormatExports suffix := "\n" @@ -37,3 +40,113 @@ func export(appName string, global bool, merged bool, format string) error { fmt.Print(exported + suffix) return nil } + +// SubBundle implements the logic for config:bundle without app name validation +func SubBundle(appName string, merged bool) error { + env := getEnvironment(appName, merged) + return env.ExportBundle(os.Stdout) +} + +// SubClear implements the logic for config:clear without app name validation +func SubClear(appName string, noRestart bool) error { + return UnsetAll(appName, !noRestart) +} + +// SubExport implements the logic for config:export without app name validation +func SubExport(appName string, merged bool, format string) error { + return export(appName, merged, format) +} + +// SubGet implements the logic for config:get without app name validation +func SubGet(appName string, keys []string, quoted bool) error { + if len(keys) == 0 { + return errors.New("Expected: key") + } + + if len(keys) != 1 { + return fmt.Errorf("Unexpected argument(s): %v", keys[1:]) + } + + value, ok := Get(appName, keys[0]) + if !ok { + os.Exit(1) + return nil + } + + if quoted { + fmt.Printf("'%s'\n", singleQuoteEscape(value)) + } else { + fmt.Printf("%s\n", value) + } + + return nil +} + +// SubKeys implements the logic for config:keys without app name validation +func SubKeys(appName string, merged bool) error { + env := getEnvironment(appName, merged) + for _, k := range env.Keys() { + fmt.Println(k) + } + return nil +} + +// SubSet implements the logic for config:set without app name validation +func SubSet(appName string, pairs []string, noRestart bool, encoded bool) error { + if len(pairs) == 0 { + return errors.New("At least one env pair must be given") + } + + updated := make(map[string]string) + for _, e := range pairs { + parts := strings.SplitN(e, "=", 2) + if len(parts) == 1 { + return fmt.Errorf("Invalid env pair: %v", e) + } + + key, value := parts[0], parts[1] + if encoded { + decoded, err := base64.StdEncoding.DecodeString(value) + if err != nil { + return fmt.Errorf("%s for key '%s'", err.Error(), key) + } + value = string(decoded) + } + updated[key] = value + } + + return SetMany(appName, updated, !noRestart) +} + +// SubShow implements the logic for config:show without app name validation +func SubShow(appName string, merged bool, shell bool, export bool) error { + env := getEnvironment(appName, merged) + if shell && export { + return errors.New("Only one of --shell and --export can be given") + } + if shell { + common.LogWarn("Deprecated: Use 'config:export --format shell' instead") + fmt.Print(env.Export(ExportFormatShell)) + } else if export { + common.LogWarn("Deprecated: Use 'config:export --format exports' instead") + fmt.Println(env.Export(ExportFormatExports)) + } else { + contextName := "global" + if appName != "" { + contextName = appName + } + common.LogInfo2Quiet(contextName + " env vars") + fmt.Println(env.Export(ExportFormatPretty)) + } + + return nil +} + +// SubUnset implements the logic for config:unset without app name validation +func SubUnset(appName string, keys []string, noRestart bool) error { + if len(keys) == 0 { + return fmt.Errorf("At least one key must be given") + } + + return UnsetMany(appName, keys, !noRestart) +} diff --git a/plugins/config/src/config_sub/config_sub.go b/plugins/config/src/config_sub/config_sub.go new file mode 100644 index 000000000..42dc7d70e --- /dev/null +++ b/plugins/config/src/config_sub/config_sub.go @@ -0,0 +1,112 @@ +package main + +import ( + "fmt" + "os" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/config" + + flag "github.com/spf13/pflag" +) + +func getKeys(args []string, global bool) []string { + keys := args + if !global && len(keys) > 0 { + keys = keys[1:] + } + return keys +} + +// main entrypoint to all subcommands +func main() { + action := os.Args[1] + + var err error + appName := "--global" + switch action { + case "bundle": + args := flag.NewFlagSet("bundle", flag.ExitOnError) + global := args.Bool("global", false, "--global: use the global environment") + merged := args.Bool("merged", false, "--merged: merge app environment and global environment") + args.Parse(os.Args[2:]) + if !*global { + appName = args.Arg(0) + } + err = config.SubBundle(appName, *merged) + case "clear": + args := flag.NewFlagSet("clear", flag.ExitOnError) + global := args.Bool("global", false, "--global: use the global environment") + noRestart := args.Bool("no-restart", false, "--no-restart: no restart") + args.Parse(os.Args[2:]) + if !*global { + appName = args.Arg(0) + } + err = config.SubClear(appName, *noRestart) + case "export": + args := flag.NewFlagSet("export", flag.ExitOnError) + global := args.Bool("global", false, "--global: use the global environment") + merged := args.Bool("merged", false, "--merged: merge app environment and global environment") + format := args.String("format", "exports", "--format: [ docker-args | docker-args-keys | exports | envfile | json | json-list | pack-keys | pretty | shell ] which format to export as)") + args.Parse(os.Args[2:]) + if !*global { + appName = args.Arg(0) + } + err = config.SubExport(appName, *merged, *format) + case "get": + args := flag.NewFlagSet("get", flag.ExitOnError) + global := args.Bool("global", false, "--global: use the global environment") + quoted := args.Bool("quoted", false, "--quoted: get the value quoted") + args.Parse(os.Args[2:]) + if !*global { + appName = args.Arg(0) + } + keys := getKeys(args.Args(), *global) + err = config.SubGet(appName, keys, *quoted) + case "keys": + args := flag.NewFlagSet("keys", flag.ExitOnError) + global := args.Bool("global", false, "--global: use the global environment") + merged := args.Bool("merged", false, "--merged: merge app environment and global environment") + args.Parse(os.Args[2:]) + if !*global { + appName = args.Arg(0) + } + err = config.SubKeys(appName, *merged) + case "show": + args := flag.NewFlagSet("show", flag.ExitOnError) + global := args.Bool("global", false, "--global: use the global environment") + merged := args.Bool("merged", false, "--merged: display the app's environment merged with the global environment") + args.Parse(os.Args[2:]) + if !*global { + appName = args.Arg(0) + } + err = config.SubShow(appName, *merged, false, false) + case "set": + args := flag.NewFlagSet("set", flag.ExitOnError) + global := args.Bool("global", false, "--global: use the global environment") + encoded := args.Bool("encoded", false, "--encoded: interpret VALUEs as base64") + noRestart := args.Bool("no-restart", false, "--no-restart: no restart") + args.Parse(os.Args[2:]) + if !*global { + appName = args.Arg(0) + } + pairs := getKeys(args.Args(), *global) + err = config.SubSet(appName, pairs, *noRestart, *encoded) + case "unset": + args := flag.NewFlagSet("unset", flag.ExitOnError) + global := args.Bool("global", false, "--global: use the global environment") + noRestart := args.Bool("no-restart", false, "--no-restart: no restart") + args.Parse(os.Args[2:]) + if !*global { + appName = args.Arg(0) + } + keys := getKeys(args.Args(), *global) + err = config.SubUnset(appName, keys, *noRestart) + default: + err = fmt.Errorf("Invalid plugin config_sub call: %s", action) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/config/subcommands.go b/plugins/config/subcommands.go index 033b45dd2..fe7e252d6 100644 --- a/plugins/config/subcommands.go +++ b/plugins/config/subcommands.go @@ -1,156 +1,83 @@ package config -import ( - "encoding/base64" - "errors" - "fmt" - "os" - "strings" - - "github.com/dokku/dokku/plugins/common" -) - -// CommandBundle implements config:bundle +// CommandBundle creates a tarball of a .env.d directory +// containing env vars for the app func CommandBundle(appName string, global bool, merged bool) error { appName, err := getAppNameOrGlobal(appName, global) if err != nil { return err } - env := getEnvironment(appName, merged) - return env.ExportBundle(os.Stdout) + return SubBundle(appName, merged) } -// CommandClear implements config:clear +// CommandClear unsets all environment variables in use func CommandClear(appName string, global bool, noRestart bool) error { appName, err := getAppNameOrGlobal(appName, global) if err != nil { return err } - return UnsetAll(appName, !noRestart) + return SubClear(appName, noRestart) } -// CommandExport implements config:export +// CommandExport outputs all env vars (merged or not, global or not) +// in the specified format for consumption by other tools func CommandExport(appName string, global bool, merged bool, format string) error { - return export(appName, global, merged, format) + appName, err := getAppNameOrGlobal(appName, global) + if err != nil { + return err + } + + return SubExport(appName, merged, format) } -// CommandGet implements config:get +// CommandGet gets the value for the specified environment variable func CommandGet(appName string, keys []string, global bool, quoted bool) error { appName, err := getAppNameOrGlobal(appName, global) if err != nil { return err } - if len(keys) == 0 { - return errors.New("Expected: key") - } - - if len(keys) != 1 { - return fmt.Errorf("Unexpected argument(s): %v", keys[1:]) - } - - value, ok := Get(appName, keys[0]) - if !ok { - os.Exit(1) - return nil - } - - if quoted { - fmt.Printf("'%s'\n", singleQuoteEscape(value)) - } else { - fmt.Printf("%s\n", value) - } - - return nil + return SubGet(appName, keys, quoted) } -// CommandKeys implements config:keys +// CommandKeys shows the keys set for the specified environment func CommandKeys(appName string, global bool, merged bool) error { appName, err := getAppNameOrGlobal(appName, global) if err != nil { return err } - env := getEnvironment(appName, merged) - for _, k := range env.Keys() { - fmt.Println(k) - } - return nil + return SubKeys(appName, merged) } -// CommandSet implements config:set +// CommandSet sets one or more environment variable pairs func CommandSet(appName string, pairs []string, global bool, noRestart bool, encoded bool) error { appName, err := getAppNameOrGlobal(appName, global) if err != nil { return err } - if len(pairs) == 0 { - return errors.New("At least one env pair must be given") - } - - updated := make(map[string]string) - for _, e := range pairs { - parts := strings.SplitN(e, "=", 2) - if len(parts) == 1 { - return fmt.Errorf("Invalid env pair: %v", e) - } - - key, value := parts[0], parts[1] - if encoded { - decoded, err := base64.StdEncoding.DecodeString(value) - if err != nil { - return fmt.Errorf("%s for key '%s'", err.Error(), key) - } - value = string(decoded) - } - updated[key] = value - } - - return SetMany(appName, updated, !noRestart) + return SubSet(appName, pairs, noRestart, encoded) } -// CommandShow implements config:show +// CommandShow pretty-prints the specified environment vaiables func CommandShow(appName string, global bool, merged bool, shell bool, export bool) error { appName, err := getAppNameOrGlobal(appName, global) if err != nil { return err } - env := getEnvironment(appName, merged) - if shell && export { - return errors.New("Only one of --shell and --export can be given") - } - if shell { - common.LogWarn("Deprecated: Use 'config:export --format shell' instead") - fmt.Print(env.Export(ExportFormatShell)) - } else if export { - common.LogWarn("Deprecated: Use 'config:export --format exports' instead") - fmt.Println(env.Export(ExportFormatExports)) - } else { - contextName := "global" - if appName != "" { - contextName = appName - } - common.LogInfo2Quiet(contextName + " env vars") - fmt.Println(env.Export(ExportFormatPretty)) - } - - return nil + return SubShow(appName, merged, shell, export) } -// CommandUnset implements config:unset +// CommandUnset unsets one or more keys in a specified environment func CommandUnset(appName string, keys []string, global bool, noRestart bool) error { appName, err := getAppNameOrGlobal(appName, global) if err != nil { return err } - if len(keys) == 0 { - return fmt.Errorf("At least one key must be given") - } - - return UnsetMany(appName, keys, !noRestart) + return SubUnset(appName, keys, noRestart) } diff --git a/plugins/config/triggers.go b/plugins/config/triggers.go index 7bcfbb50f..3014f6d26 100644 --- a/plugins/config/triggers.go +++ b/plugins/config/triggers.go @@ -16,7 +16,13 @@ func TriggerConfigExport(appName string, global string, merged string, format st if err != nil { return err } - return export(appName, g, m, format) + + appName, err = getAppNameOrGlobal(appName, g) + if err != nil { + return err + } + + return export(appName, m, format) } // TriggerConfigGet returns an app config value by key