diff --git a/docs/appendices/0.21.0-migration-guide.md b/docs/appendices/0.21.0-migration-guide.md index b3b479f91..ccdeb9122 100644 --- a/docs/appendices/0.21.0-migration-guide.md +++ b/docs/appendices/0.21.0-migration-guide.md @@ -7,3 +7,11 @@ The `tls` name is no longer a reserved app name, and can be used by applications ## Deprecations - `git#git_deploy_branch()` is deprecated in favor of `plugn trigger git-deploy-branch`. +- The `config` command is deprecated in favor of `config:show`. + - Usage of this command in conjunction with either the `--export` or `--shell` flag is deprecated in favor of `config:export --format` with the correct format value (`exports` or `shell`, respectively). + +## Removals + +The `apps:destroy` command no longer takes a second argument `force`. Instead, this can be passed as the `--force` flag, either globally or as a flag to the command itself. + +The `proxy:enable` and `proxy:disable` no longer restart apps when setting the `DOKKU_DISABLE_PROXY` environment variable. A future release will disable usage of `DOKKU_DISABLE_PROXY` altogether. diff --git a/docs/deployment/application-management.md b/docs/deployment/application-management.md index 0ba5e7fd1..cdfba06ab 100644 --- a/docs/deployment/application-management.md +++ b/docs/deployment/application-management.md @@ -103,6 +103,16 @@ dokku --force apps:destroy node-js-app Destroying node-js-app (including all add-ons) ``` +The `--force` flag can also be specified on the command vs globally: + +```shell +dokku apps:destroy --force node-js-app +``` + +``` +Destroying node-js-app (including all add-ons) +``` + Destroying an application will unlink all linked services and destroy any config related to the application. Note that linked services will retain their data for later use (or removal). ### Renaming a deployed app diff --git a/plugins/apps/functions.go b/plugins/apps/functions.go index adc7e960d..4327fa6a8 100644 --- a/plugins/apps/functions.go +++ b/plugins/apps/functions.go @@ -86,26 +86,6 @@ func destroyApp(appName string) error { return nil } -func getAppName(args []string) (appName string, err error) { - if len(args) >= 1 { - appName = args[0] - } else { - err = errors.New("Please specify an app to run the command on") - } - - return -} - -func getNewAppName(args []string) (appName string, err error) { - if len(args) >= 2 { - appName = args[1] - } else { - err = errors.New("Please specify an new app name") - } - - return -} - func listImagesByAppLabel(appName string) ([]string, error) { command := []string{ common.DockerBin(), diff --git a/plugins/apps/src/subcommands/subcommands.go b/plugins/apps/src/subcommands/subcommands.go index 17cd59c60..218031a12 100644 --- a/plugins/apps/src/subcommands/subcommands.go +++ b/plugins/apps/src/subcommands/subcommands.go @@ -22,44 +22,57 @@ func main() { skipDeploy := args.Bool("skip-deploy", false, "--skip-deploy: skip deploy of the new app") ignoreExisting := args.Bool("ignore-existing", false, "--ignore-existing: exit 0 if new app already exists") args.Parse(os.Args[2:]) - err = apps.CommandClone(args.Args(), *skipDeploy, *ignoreExisting) + oldAppName := args.Arg(0) + newAppName := args.Arg(1) + err = apps.CommandClone(oldAppName, newAppName, *skipDeploy, *ignoreExisting) case "create": args := flag.NewFlagSet("apps:create", flag.ExitOnError) args.Parse(os.Args[2:]) - err = apps.CommandCreate(args.Args()) + appName := args.Arg(0) + err = apps.CommandCreate(appName) case "destroy": args := flag.NewFlagSet("apps:destroy", flag.ExitOnError) + force := args.Bool("force", false, "--force: force destroy without confirmation") args.Parse(os.Args[2:]) - err = apps.CommandDestroy(args.Args()) + appName := args.Arg(0) + err = apps.CommandDestroy(appName, *force) case "exists": args := flag.NewFlagSet("apps:exists", flag.ExitOnError) args.Parse(os.Args[2:]) - err = apps.CommandExists(args.Args()) + appName := args.Arg(0) + err = apps.CommandExists(appName) case "list": args := flag.NewFlagSet("apps:list", flag.ExitOnError) args.Parse(os.Args[2:]) - err = apps.CommandList(args.Args()) + err = apps.CommandList() case "lock": args := flag.NewFlagSet("apps:lock", flag.ExitOnError) args.Parse(os.Args[2:]) - err = apps.CommandLock(args.Args()) + appName := args.Arg(0) + err = apps.CommandLock(appName) case "locked": args := flag.NewFlagSet("apps:locked", flag.ExitOnError) args.Parse(os.Args[2:]) - err = apps.CommandLocked(args.Args()) + appName := args.Arg(0) + err = apps.CommandLocked(appName) case "rename": args := flag.NewFlagSet("apps:rename", flag.ExitOnError) skipDeploy := args.Bool("skip-deploy", false, "--skip-deploy: skip deploy of the new app") args.Parse(os.Args[2:]) - err = apps.CommandRename(args.Args(), *skipDeploy) + oldAppName := args.Arg(0) + newAppName := args.Arg(1) + err = apps.CommandRename(oldAppName, newAppName, *skipDeploy) case "report": args := flag.NewFlagSet("apps:report", flag.ExitOnError) args.Parse(os.Args[2:]) - err = apps.CommandReport(args.Args()) + appName := args.Arg(0) + infoFlag := args.Arg(1) + err = apps.CommandReport(appName, infoFlag) case "unlock": args := flag.NewFlagSet("apps:unlock", flag.ExitOnError) args.Parse(os.Args[2:]) - err = apps.CommandUnlock(args.Args()) + appName := args.Arg(0) + err = apps.CommandUnlock(appName) default: common.LogFail(fmt.Sprintf("Invalid plugin subcommand call: %s", subcommand)) } diff --git a/plugins/apps/subcommands.go b/plugins/apps/subcommands.go index 7495910f8..e9d2ce8e1 100644 --- a/plugins/apps/subcommands.go +++ b/plugins/apps/subcommands.go @@ -10,30 +10,28 @@ import ( ) // CommandClone clones an app -func CommandClone(args []string, skipDeploy bool, ignoreExisting bool) error { - oldAppName, err := getAppName(args) - if err != nil { +func CommandClone(oldAppName string, newAppName string, skipDeploy bool, ignoreExisting bool) error { + if oldAppName == "" { + return errors.New("Please specify an app to run the command on") + } + + if newAppName == "" { + return errors.New("Please specify an new app name") + } + + if err := common.IsValidAppName(oldAppName); err != nil { return err } - newAppName, err := getNewAppName(args) - if err != nil { + if err := common.IsValidAppName(newAppName); err != nil { return err } - if err = common.IsValidAppName(oldAppName); err != nil { + if err := appExists(oldAppName); err != nil { return err } - if err = common.IsValidAppName(newAppName); err != nil { - return err - } - - if err = appExists(oldAppName); err != nil { - return err - } - - if err = appExists(newAppName); err == nil { + if err := appExists(newAppName); err == nil { if ignoreExisting { common.LogWarn("Name is already taken") return nil @@ -43,11 +41,11 @@ func CommandClone(args []string, skipDeploy bool, ignoreExisting bool) error { } common.LogInfo1Quiet(fmt.Sprintf("Cloning %s to %s", oldAppName, newAppName)) - if err = createApp(newAppName); err != nil { + if err := createApp(newAppName); err != nil { return err } - if err = common.PlugnTrigger("post-app-clone-setup", []string{oldAppName, newAppName}...); err != nil { + if err := common.PlugnTrigger("post-app-clone-setup", []string{oldAppName, newAppName}...); err != nil { return err } @@ -55,7 +53,7 @@ func CommandClone(args []string, skipDeploy bool, ignoreExisting bool) error { os.Setenv("SKIP_REBUILD", "true") } - if err = common.PlugnTrigger("post-app-clone", []string{oldAppName, newAppName}...); err != nil { + if err := common.PlugnTrigger("post-app-clone", []string{oldAppName, newAppName}...); err != nil { return err } @@ -63,44 +61,38 @@ func CommandClone(args []string, skipDeploy bool, ignoreExisting bool) error { } // CommandCreate creates app via command line -func CommandCreate(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandCreate(appName string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } return createApp(appName) } // CommandDestroy destroys an app -func CommandDestroy(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandDestroy(appName string, force bool) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } - if len(args) >= 2 { - force := args[1] - if force == "force" { - os.Setenv("DOKKU_APPS_FORCE_DELETE", "1") - } + if force { + os.Setenv("DOKKU_APPS_FORCE_DELETE", "1") } return destroyApp(appName) } // CommandExists checks if an app exists -func CommandExists(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandExists(appName string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } return appExists(appName) } // CommandList lists all apps -func CommandList(args []string) error { +func CommandList() error { common.LogInfo2Quiet("My Apps") apps, err := common.DokkuApps() if err != nil { @@ -116,10 +108,9 @@ func CommandList(args []string) error { } // CommandLock locks an app for deployment -func CommandLock(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandLock(appName string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } if err := common.VerifyAppName(appName); err != nil { @@ -127,7 +118,7 @@ func CommandLock(args []string) error { } lockfilePath := fmt.Sprintf("%v/.deploy.lock", common.AppRoot(appName)) - if _, err = os.Create(lockfilePath); err != nil { + if _, err := os.Create(lockfilePath); err != nil { return errors.New("Unable to create deploy lock") } @@ -136,10 +127,9 @@ func CommandLock(args []string) error { } // CommandLocked checks if an app is locked for deployment -func CommandLocked(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandLocked(appName string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } if err := common.VerifyAppName(appName); err != nil { @@ -155,44 +145,42 @@ func CommandLocked(args []string) error { } // CommandRename renames an app -func CommandRename(args []string, skipDeploy bool) error { - oldAppName, err := getAppName(args) - if err != nil { +func CommandRename(oldAppName string, newAppName string, skipDeploy bool) error { + if oldAppName == "" { + return errors.New("Please specify an app to run the command on") + } + + if newAppName == "" { + return errors.New("Please specify an new app name") + } + + if err := common.IsValidAppName(oldAppName); err != nil { return err } - newAppName, err := getNewAppName(args) - if err != nil { + if err := common.IsValidAppName(newAppName); err != nil { return err } - if err = common.IsValidAppName(oldAppName); err != nil { + if err := appExists(oldAppName); err != nil { return err } - if err = common.IsValidAppName(newAppName); err != nil { - return err - } - - if err = appExists(oldAppName); err != nil { - return err - } - - if err = appExists(newAppName); err == nil { + if err := appExists(newAppName); err == nil { return errors.New("Name is already taken") } common.LogInfo1Quiet(fmt.Sprintf("Renaming %s to %s", oldAppName, newAppName)) - if err = createApp(newAppName); err != nil { + if err := createApp(newAppName); err != nil { return err } - if err = common.PlugnTrigger("post-app-rename-setup", []string{oldAppName, newAppName}...); err != nil { + if err := common.PlugnTrigger("post-app-rename-setup", []string{oldAppName, newAppName}...); err != nil { return err } os.Setenv("DOKKU_APPS_FORCE_DELETE", "1") - if err = destroyApp(oldAppName); err != nil { + if err := destroyApp(oldAppName); err != nil { return err } @@ -200,7 +188,7 @@ func CommandRename(args []string, skipDeploy bool) error { os.Setenv("SKIP_REBUILD", "true") } - if err = common.PlugnTrigger("post-app-rename", []string{oldAppName, newAppName}...); err != nil { + if err := common.PlugnTrigger("post-app-rename", []string{oldAppName, newAppName}...); err != nil { return err } @@ -208,15 +196,9 @@ func CommandRename(args []string, skipDeploy bool) error { } // CommandReport displays an app report for one or more apps -func CommandReport(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err - } - - infoFlag := "" - if len(args) > 1 { - infoFlag = args[1] +func CommandReport(appName string, infoFlag string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } if strings.HasPrefix(appName, "--") { @@ -241,10 +223,9 @@ func CommandReport(args []string) error { } // CommandUnlock unlocks an app for deployment -func CommandUnlock(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandUnlock(appName string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } if err := common.VerifyAppName(appName); err != nil { @@ -252,17 +233,15 @@ func CommandUnlock(args []string) error { } lockfilePath := fmt.Sprintf("%v/.deploy.lock", common.AppRoot(appName)) - _, err = os.Stat(lockfilePath) - if !os.IsNotExist(err) { + if _, err := os.Stat(lockfilePath); !os.IsNotExist(err) { common.LogWarn("A deploy may be in progress.") common.LogWarn("Removing the app lock will not stop in progress deploys.") } - err = os.Remove(lockfilePath) - if err == nil { - common.LogInfo1("Deploy lock removed") - return nil + if err := os.Remove(lockfilePath); err != nil { + return errors.New("Unable to remove deploy lock") } - return errors.New("Unable to remove deploy lock") + common.LogInfo1("Deploy lock removed") + return nil } diff --git a/plugins/buildpacks/functions.go b/plugins/buildpacks/functions.go index 13f2141be..b7088c3cf 100644 --- a/plugins/buildpacks/functions.go +++ b/plugins/buildpacks/functions.go @@ -2,14 +2,26 @@ package buildpacks import ( "errors" + "fmt" + "regexp" + "strings" ) -func getAppName(args []string) (appName string, err error) { - if len(args) >= 1 { - appName = args[0] - } else { - err = errors.New("Please specify an app to run the command on") +func validBuildpackURL(buildpack string) (string, error) { + if buildpack == "" { + return buildpack, errors.New("Must specify a buildpack to add") } - return + reHerokuValue := regexp.MustCompile(`(?m)^([\w]+\/[\w]+)$`) + if found := reHerokuValue.Find([]byte(buildpack)); found != nil { + parts := strings.SplitN(buildpack, "/", 2) + return fmt.Sprintf("https://github.com/%s/heroku-buildpack-%s.git", parts[0], parts[1]), nil + } + + reString := regexp.MustCompile(`(?m)^(http|https|git)(:\/\/|@)([^\/:]+)[\/:]([^\/:]+)\/(.+)(.git(#derp)?)?$`) + if found := reString.Find([]byte(buildpack)); found != nil { + return buildpack, nil + } + + return buildpack, fmt.Errorf("Invalid buildpack specified: %v", buildpack) } diff --git a/plugins/buildpacks/src/subcommands/subcommands.go b/plugins/buildpacks/src/subcommands/subcommands.go index 212898cfe..fe5e46179 100644 --- a/plugins/buildpacks/src/subcommands/subcommands.go +++ b/plugins/buildpacks/src/subcommands/subcommands.go @@ -21,30 +21,39 @@ func main() { args := flag.NewFlagSet("buildpacks:add", flag.ExitOnError) index := args.Int("index", 0, "--index: the 1-based index of the URL in the list of URLs") args.Parse(os.Args[2:]) - err = buildpacks.CommandAdd(args.Args(), *index) + appName := args.Arg(0) + buildpack := args.Arg(1) + err = buildpacks.CommandAdd(appName, buildpack, *index) case "clear": args := flag.NewFlagSet("buildpacks:clear", flag.ExitOnError) args.Parse(os.Args[2:]) - err = buildpacks.CommandClear(args.Args()) + appName := args.Arg(0) + err = buildpacks.CommandClear(appName) case "list": args := flag.NewFlagSet("buildpacks:list", flag.ExitOnError) args.Parse(os.Args[2:]) - buildpacks.CommandList(args.Args()) + appName := args.Arg(0) + err = buildpacks.CommandList(appName) case "remove": args := flag.NewFlagSet("buildpacks:remove", flag.ExitOnError) index := args.Int("index", 0, "--index: the 1-based index of the URL in the list of URLs") args.Parse(os.Args[2:]) - err = buildpacks.CommandRemove(args.Args(), *index) + appName := args.Arg(0) + buildpack := args.Arg(1) + err = buildpacks.CommandRemove(appName, buildpack, *index) case "report": - flag.Parse() - appName := flag.Arg(1) - infoFlag := flag.Arg(2) - buildpacks.CommandReport(appName, infoFlag) + args := flag.NewFlagSet("buildpacks:report", flag.ExitOnError) + args.Parse(os.Args[2:]) + appName := args.Arg(0) + infoFlag := args.Arg(1) + err = buildpacks.CommandReport(appName, infoFlag) case "set": args := flag.NewFlagSet("buildpacks:set", flag.ExitOnError) index := args.Int("index", 0, "--index: the 1-based index of the URL in the list of URLs") args.Parse(os.Args[2:]) - err = buildpacks.CommandSet(args.Args(), *index) + appName := args.Arg(0) + buildpack := args.Arg(1) + err = buildpacks.CommandSet(appName, buildpack, *index) default: common.LogFail(fmt.Sprintf("Invalid plugin subcommand call: %s", subcommand)) } diff --git a/plugins/buildpacks/subcommands.go b/plugins/buildpacks/subcommands.go index 7335d0ca3..53a97c963 100644 --- a/plugins/buildpacks/subcommands.go +++ b/plugins/buildpacks/subcommands.go @@ -3,71 +3,38 @@ package buildpacks import ( "errors" "fmt" - "regexp" "strings" "github.com/dokku/dokku/plugins/common" ) -func validBuildpackURL(buildpack string) (string, error) { - if buildpack == "" { - return buildpack, errors.New("Must specify a buildpack to add") - } - - reHerokuValue := regexp.MustCompile(`(?m)^([\w]+\/[\w]+)$`) - if found := reHerokuValue.Find([]byte(buildpack)); found != nil { - parts := strings.SplitN(buildpack, "/", 2) - return fmt.Sprintf("https://github.com/%s/heroku-buildpack-%s.git", parts[0], parts[1]), nil - } - - reString := regexp.MustCompile(`(?m)^(http|https|git)(:\/\/|@)([^\/:]+)[\/:]([^\/:]+)\/(.+)(.git(#derp)?)?$`) - if found := reString.Find([]byte(buildpack)); found != nil { - return buildpack, nil - } - - return buildpack, fmt.Errorf("Invalid buildpack specified: %v", buildpack) -} - // CommandAdd implements buildpacks:add -func CommandAdd(args []string, index int) (err error) { - var appName string - appName, err = getAppName(args) - if err != nil { - return +func CommandAdd(appName string, buildpack string, index int) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } - buildpack := "" - if len(args) >= 2 { - buildpack = args[1] - } - - buildpack, err = validBuildpackURL(buildpack) + buildpack, err := validBuildpackURL(buildpack) if err != nil { return err } - err = common.PropertyListAdd("buildpacks", appName, "buildpacks", buildpack, index) - return + return common.PropertyListAdd("buildpacks", appName, "buildpacks", buildpack, index) } // CommandClear implements buildpacks:clear -func CommandClear(args []string) (err error) { - var appName string - appName, err = getAppName(args) - if err != nil { - return +func CommandClear(appName string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } - common.PropertyDelete("buildpacks", appName, "buildpacks") - return + return common.PropertyDelete("buildpacks", appName, "buildpacks") } // CommandList implements buildpacks:list -func CommandList(args []string) (err error) { - var appName string - appName, err = getAppName(args) - if err != nil { - return +func CommandList(appName string) (err error) { + if appName == "" { + return errors.New("Please specify an app to run the command on") } buildpacks, err := common.PropertyListGet("buildpacks", appName, "buildpacks") @@ -83,17 +50,11 @@ func CommandList(args []string) (err error) { } // CommandRemove implements buildpacks:remove -func CommandRemove(args []string, index int) (err error) { - var appName string - appName, err = getAppName(args) - if err != nil { - return +func CommandRemove(appName string, buildpack string, index int) (err error) { + if appName == "" { + return errors.New("Please specify an app to run the command on") } - buildpack := "" - if len(args) >= 2 { - buildpack = args[1] - } if index != 0 && buildpack != "" { err = errors.New("Please choose either index or Buildpack, but not both") return @@ -164,23 +125,15 @@ func CommandReport(appName string, infoFlag string) error { } return ReportSingleApp(appName, infoFlag) - } // CommandSet implements buildpacks:set -func CommandSet(args []string, index int) (err error) { - var appName string - appName, err = getAppName(args) - if err != nil { - return +func CommandSet(appName string, buildpack string, index int) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } - buildpack := "" - if len(args) >= 2 { - buildpack = args[1] - } - - buildpack, err = validBuildpackURL(buildpack) + buildpack, err := validBuildpackURL(buildpack) if err != nil { return err } @@ -189,10 +142,5 @@ func CommandSet(args []string, index int) (err error) { index-- } - err = common.PropertyListSet("buildpacks", appName, "buildpacks", buildpack, index) - if err != nil { - return - } - - return + return common.PropertyListSet("buildpacks", appName, "buildpacks", buildpack, index) } diff --git a/plugins/common/common.go b/plugins/common/common.go index 43772117e..ce60f3c47 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -527,6 +527,15 @@ func RightPad(str string, length int, pad string) string { return str + times(pad, length-len(str)) } +// ShiftString removes the first and returns that entry as well as the rest of the list +func ShiftString(a []string) (string, []string) { + if len(a) == 0 { + return "", a + } + + return a[0], a[1:] +} + // StripInlineComments removes bash-style comment from input line func StripInlineComments(text string) string { bytes := []byte(text) diff --git a/plugins/common/properties.go b/plugins/common/properties.go index 5c5971fab..437ac12ce 100644 --- a/plugins/common/properties.go +++ b/plugins/common/properties.go @@ -64,6 +64,10 @@ func PropertyClone(pluginName string, oldAppName string, newAppName string) erro func PropertyDelete(pluginName string, appName string, property string) error { propertyPath := getPropertyPath(pluginName, appName, property) if err := os.Remove(propertyPath); err != nil { + if !PropertyExists(pluginName, appName, property) { + return nil + } + return fmt.Errorf("Unable to remove %s property %s.%s", pluginName, appName, property) } diff --git a/plugins/config/Makefile b/plugins/config/Makefile index 2a0a3943b..afca09a61 100644 --- a/plugins/config/Makefile +++ b/plugins/config/Makefile @@ -1,4 +1,4 @@ -SUBCOMMANDS = subcommands/export subcommands/get subcommands/set subcommands/unset subcommands/keys subcommands/bundle +SUBCOMMANDS = subcommands/bundle subcommands/export subcommands/get subcommands/keys subcommands/show subcommands/set subcommands/unset TRIGGERS = triggers/config-get triggers/config-get-global BUILD = commands subcommands triggers PLUGIN_NAME = config diff --git a/plugins/config/config.go b/plugins/config/config.go index 4947492f3..37734261a 100644 --- a/plugins/config/config.go +++ b/plugins/config/config.go @@ -1,6 +1,7 @@ package config import ( + "errors" "fmt" "os" "regexp" @@ -129,6 +130,27 @@ func triggerUpdate(appName string, operation string, args []string) { } } +//getEnvironment for the given app (global config if appName is empty). Merge with global environment if merged is true. +func getEnvironment(appName string, merged bool) (env *Env) { + var err error + if appName != "" && merged { + env, err = LoadMergedAppEnv(appName) + } else { + env, err = loadAppOrGlobalEnv(appName) + } + if err != nil { + common.LogFail(err.Error()) + } + return env +} + +func getAppNameOrGlobal(appName string, global bool) (string, error) { + if appName == "" && !global { + return appName, errors.New("Please specify an app to run the command on or --global") + } + return appName, nil +} + func loadAppOrGlobalEnv(appName string) (env *Env, err error) { if appName == "" || appName == "--global" { return LoadGlobalEnv() diff --git a/plugins/config/src/commands/commands.go b/plugins/config/src/commands/commands.go index 750271173..2a35a4cca 100644 --- a/plugins/config/src/commands/commands.go +++ b/plugins/config/src/commands/commands.go @@ -21,11 +21,12 @@ Additional commands:` helpContent = ` config (|--global), Pretty-print an app or global environment - config:bundle (|--global) [--merged], Bundle environment into tarfile - config:clear (|--global), Clears environment variables - config:export (|--global) [--envfile], Export a global or app environment - config:get (|--global) KEY, Display a global or app-specific config value - config:keys (|--global) [--merged], Show keys set in environment + config:bundle [--merged] (|--global), Bundle environment into tarfile + config:clear [--no-restart] (|--global), Clears environment variables + config:export [--format=FORMAT] [--merged] (|--global), Export a global or app environment + config:get [--quoted] (|--global) KEY, Display a global or app-specific config value + config:keys [--merged] (|--global), Show keys set in environment + config:show [--merged] (|--global), Show keys set in environment config:set [--encoded] [--no-restart] (|--global) KEY1=VALUE1 [KEY2=VALUE2 ...], Set one or more config vars config:unset [--no-restart] (|--global) KEY1 [KEY2 ...], Unset one or more config vars ` @@ -37,14 +38,19 @@ func main() { cmd := flag.Arg(0) switch cmd { - case "config", "config:show": + case "config": + common.LogWarn("Deprecated: Use the 'config:show' command instead") args := flag.NewFlagSet("config:show", flag.ExitOnError) global := args.Bool("global", false, "--global: use the global environment") shell := args.Bool("shell", false, "--shell: in a single-line for usage in command-line utilities [deprecated]") export := args.Bool("export", false, "--export: print the env as eval-compatible exports [deprecated]") merged := args.Bool("merged", false, "--merged: display the app's environment merged with the global environment") args.Parse(os.Args[2:]) - config.CommandShow(args.Args(), *global, *shell, *export, *merged) + appName := args.Arg(0) + err := config.CommandShow(appName, *global, *merged, *shell, *export) + if err != nil { + common.LogFail(err.Error()) + } case "config:help": usage() case "help": diff --git a/plugins/config/src/subcommands/subcommands.go b/plugins/config/src/subcommands/subcommands.go index 9f9eeae7a..2d7cdb04a 100644 --- a/plugins/config/src/subcommands/subcommands.go +++ b/plugins/config/src/subcommands/subcommands.go @@ -10,58 +10,104 @@ import ( "github.com/dokku/dokku/plugins/config" ) +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() { parts := strings.Split(os.Args[0], "/") subcommand := parts[len(parts)-1] + var err error + var appName string switch subcommand { case "bundle": args := flag.NewFlagSet("config: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:]) - config.CommandBundle(args.Args(), *global, *merged) + if !*global { + appName = args.Arg(0) + } + err = config.CommandBundle(appName, *global, *merged) case "clear": args := flag.NewFlagSet("config: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:]) - config.CommandClear(args.Args(), *global, *noRestart) + if !*global { + appName = args.Arg(0) + } + err = config.CommandClear(appName, *global, *noRestart) case "export": args := flag.NewFlagSet("config: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: [ exports | envfile | docker-args | shell | pretty | json | json-list ] which format to export as)") args.Parse(os.Args[2:]) - config.CommandExport(args.Args(), *global, *merged, *format) + if !*global { + appName = args.Arg(0) + } + err = config.CommandExport(appName, *global, *merged, *format) case "get": args := flag.NewFlagSet("config: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:]) - config.CommandGet(args.Args(), *global, *quoted) + if !*global { + appName = args.Arg(0) + } + keys := getKeys(args.Args(), *global) + err = config.CommandGet(appName, keys, *global, *quoted) case "keys": args := flag.NewFlagSet("config: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:]) - config.CommandKeys(args.Args(), *global, *merged) + if !*global { + appName = args.Arg(0) + } + err = config.CommandKeys(appName, *global, *merged) + case "show": + args := flag.NewFlagSet("config: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.CommandShow(appName, *global, *merged, false, false) case "set": args := flag.NewFlagSet("config: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:]) - config.CommandSet(args.Args(), *global, *noRestart, *encoded) + if !*global { + appName = args.Arg(0) + } + pairs := getKeys(args.Args(), *global) + err = config.CommandSet(appName, pairs, *global, *noRestart, *encoded) case "unset": args := flag.NewFlagSet("config: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:]) - config.CommandUnset(args.Args(), *global, *noRestart) + if !*global { + appName = args.Arg(0) + } + keys := getKeys(args.Args(), *global) + err = config.CommandUnset(appName, keys, *global, *noRestart) default: common.LogFail(fmt.Sprintf("Invalid plugin subcommand call: %s", subcommand)) } + if err != nil { + common.LogFail(err.Error()) + } } diff --git a/plugins/config/subcommands.go b/plugins/config/subcommands.go index e9336573b..331deb165 100644 --- a/plugins/config/subcommands.go +++ b/plugins/config/subcommands.go @@ -2,6 +2,7 @@ package config import ( "encoding/base64" + "errors" "fmt" "os" "strings" @@ -9,16 +10,154 @@ import ( "github.com/dokku/dokku/plugins/common" ) -//CommandShow implements config:show -func CommandShow(args []string, global bool, shell bool, export bool, merged bool) { - appName, _ := getCommonArgs(global, args) +// CommandBundle implements config:bundle +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) +} + +// CommandClear implements config:clear +func CommandClear(appName string, global bool, noRestart bool) error { + appName, err := getAppNameOrGlobal(appName, global) + if err != nil { + return err + } + + return UnsetAll(appName, !noRestart) +} + +// CommandExport implements config:export +func CommandExport(appName string, global bool, merged bool, format string) error { + appName, err := getAppNameOrGlobal(appName, global) + if err != nil { + return err + } + + env := getEnvironment(appName, merged) + exportType := ExportFormatExports + suffix := "\n" + + exportTypes := map[string]ExportFormat{ + "exports": ExportFormatExports, + "envfile": ExportFormatEnvfile, + "docker-args": ExportFormatDockerArgs, + "shell": ExportFormatShell, + "pretty": ExportFormatPretty, + "json": ExportFormatJSON, + "json-list": ExportFormatJSONList, + } + + exportType, ok := exportTypes[format] + if !ok { + return fmt.Errorf("Unknown export format: %v", format) + } + + if exportType == ExportFormatShell { + suffix = " " + } + + exported := env.Export(exportType) + fmt.Print(exported + suffix) + return nil +} + +// CommandGet implements config:get +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 +} + +// CommandKeys implements config:keys +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 +} + +// CommandSet implements config:set +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) +} + +// CommandShow implements config:show +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 { - common.LogFail("Only one of --shell and --export can be given") + 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" @@ -28,157 +167,20 @@ func CommandShow(args []string, global bool, shell bool, export bool, merged boo common.LogInfo2Quiet(contextName + " env vars") fmt.Println(env.Export(ExportFormatPretty)) } + + return nil } -//CommandGet implements config:get -func CommandGet(args []string, global bool, quoted bool) { - appName, keys := getCommonArgs(global, args) - if len(keys) > 1 { - common.LogFail(fmt.Sprintf("Unexpected argument(s): %v", keys[1:])) +// CommandUnset implements config:unset +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 { - common.LogFail("Expected: key") + return fmt.Errorf("At least one key must be given") } - if value, ok := Get(appName, keys[0]); !ok { - os.Exit(1) - } else { - if quoted { - fmt.Printf("'%s'\n", singleQuoteEscape(value)) - } else { - fmt.Printf("%s\n", value) - } - } -} -//CommandClear implements config:clear -func CommandClear(args []string, global bool, noRestart bool) { - appName, _ := getCommonArgs(global, args) - err := UnsetAll(appName, !noRestart) - if err != nil { - common.LogFail(err.Error()) - } -} - -//CommandUnset implements config:unset -func CommandUnset(args []string, global bool, noRestart bool) { - appName, keys := getCommonArgs(global, args) - if len(keys) == 0 { - common.LogFail("At least one key must be given") - } - err := UnsetMany(appName, keys, !noRestart) - if err != nil { - common.LogFail(err.Error()) - } -} - -//CommandSet implements config:set -func CommandSet(args []string, global bool, noRestart bool, encoded bool) { - appName, pairs := getCommonArgs(global, args) - if len(pairs) == 0 { - common.LogFail("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 { - common.LogFail("Invalid env pair: " + e) - } - key, value := parts[0], parts[1] - if encoded { - decoded, err := base64.StdEncoding.DecodeString(value) - if err != nil { - common.LogFail(fmt.Sprintf("%s for key '%s'", err.Error(), key)) - } - value = string(decoded) - } - updated[key] = value - } - err := SetMany(appName, updated, !noRestart) - if err != nil { - common.LogFail(err.Error()) - } -} - -//CommandKeys implements config:keys -func CommandKeys(args []string, global bool, merged bool) { - appName, trailingArgs := getCommonArgs(global, args) - if len(trailingArgs) > 0 { - common.LogFail(fmt.Sprintf("Trailing argument(s): %v", trailingArgs)) - } - env := getEnvironment(appName, merged) - for _, k := range env.Keys() { - fmt.Println(k) - } -} - -//CommandExport implements config:export -func CommandExport(args []string, global bool, merged bool, format string) { - appName, trailingArgs := getCommonArgs(global, args) - if len(trailingArgs) > 0 { - common.LogFail(fmt.Sprintf("Trailing argument(s): %v", trailingArgs)) - } - env := getEnvironment(appName, merged) - exportType := ExportFormatExports - suffix := "\n" - switch format { - case "exports": - exportType = ExportFormatExports - case "envfile": - exportType = ExportFormatEnvfile - case "docker-args": - exportType = ExportFormatDockerArgs - case "shell": - exportType = ExportFormatShell - suffix = " " - case "pretty": - exportType = ExportFormatPretty - case "json": - exportType = ExportFormatJSON - case "json-list": - exportType = ExportFormatJSONList - default: - common.LogFail(fmt.Sprintf("Unknown export format: %v", format)) - } - exported := env.Export(exportType) - fmt.Print(exported + suffix) -} - -//CommandBundle implements config:bundle -func CommandBundle(args []string, global bool, merged bool) { - appName, trailingArgs := getCommonArgs(global, args) - if len(trailingArgs) > 0 { - common.LogFail(fmt.Sprintf("Trailing argument(s): %v", trailingArgs)) - } - env := getEnvironment(appName, merged) - env.ExportBundle(os.Stdout) -} - -//getEnvironment for the given app (global config if appName is empty). Merge with global environment if merged is true. -func getEnvironment(appName string, merged bool) (env *Env) { - var err error - if appName != "" && merged { - env, err = LoadMergedAppEnv(appName) - } else { - env, err = loadAppOrGlobalEnv(appName) - } - if err != nil { - common.LogFail(err.Error()) - } - return env -} - -//getCommonArgs extracts common positional args (appName and keys) -func getCommonArgs(global bool, args []string) (appName string, keys []string) { - keys = args - if !global { - if len(args) > 0 { - appName = args[0] - } - if appName == "" { - common.LogFail("Please specify an app or --global") - } else { - keys = args[1:] - } - } - return appName, keys + return UnsetMany(appName, keys, !noRestart) } diff --git a/plugins/network/network.go b/plugins/network/network.go index 30e7b8825..380731ae0 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -25,17 +25,19 @@ var ( ) // BuildConfig builds network config files -func BuildConfig(appName string) { +func BuildConfig(appName string) error { if err := common.VerifyAppName(appName); err != nil { - common.LogFail(err.Error()) + return err } + if !common.IsDeployed(appName) { - return + return nil } + appRoot := common.AppRoot(appName) scaleFile := strings.Join([]string{appRoot, "DOKKU_SCALE"}, "/") if !common.FileExists(scaleFile) { - return + return nil } image := common.GetAppImageName(appName, "", "") @@ -43,8 +45,9 @@ func BuildConfig(appName string) { common.LogInfo1(fmt.Sprintf("Ensuring network configuration is in sync for %s", appName)) lines, err := common.FileToSlice(scaleFile) if err != nil { - return + return err } + for _, line := range lines { if line == "" || strings.HasPrefix(line, "#") { continue @@ -91,6 +94,8 @@ func BuildConfig(appName string) { } } } + + return nil } // GetContainerIpaddress returns the ipaddr for a given app container diff --git a/plugins/network/src/subcommands/subcommands.go b/plugins/network/src/subcommands/subcommands.go index b83d6aa5a..cb9ac377e 100644 --- a/plugins/network/src/subcommands/subcommands.go +++ b/plugins/network/src/subcommands/subcommands.go @@ -14,56 +14,60 @@ import ( func main() { parts := strings.Split(os.Args[0], "/") subcommand := parts[len(parts)-1] - flag.Parse() + var err error switch subcommand { case "create": - networkName := flag.Arg(1) - err := network.CommandCreate(networkName) - if err != nil { - common.LogFail(err.Error()) - } + args := flag.NewFlagSet("network:create", flag.ExitOnError) + args.Parse(os.Args[2:]) + networkName := args.Arg(0) + err = network.CommandCreate(networkName) case "destroy": - networkName := flag.Arg(1) - forceDestroy := false - if flag.Arg(2) == "force" { - forceDestroy = true - } - err := network.CommandDestroy(networkName, forceDestroy) - if err != nil { - common.LogFail(err.Error()) - } + args := flag.NewFlagSet("network:destroy", flag.ExitOnError) + force := args.Bool("force", false, "--force: force destroy without confirmation") + args.Parse(os.Args[2:]) + networkName := args.Arg(0) + err = network.CommandDestroy(networkName, *force) case "exists": - networkName := flag.Arg(1) - err := network.CommandExists(networkName) - if err != nil { - common.LogFail(err.Error()) - } + args := flag.NewFlagSet("network:exists", flag.ExitOnError) + args.Parse(os.Args[2:]) + networkName := args.Arg(0) + err = network.CommandExists(networkName) case "info": - err := network.CommandInfo() - if err != nil { - common.LogFail(err.Error()) - } + args := flag.NewFlagSet("network:info", flag.ExitOnError) + args.Parse(os.Args[2:]) + err = network.CommandInfo() case "list": - err := network.CommandList() - if err != nil { - common.LogFail(err.Error()) - } + args := flag.NewFlagSet("network:list", flag.ExitOnError) + args.Parse(os.Args[2:]) + err = network.CommandList() case "rebuild": - appName := flag.Arg(1) - network.BuildConfig(appName) + args := flag.NewFlagSet("network:rebuild", flag.ExitOnError) + args.Parse(os.Args[2:]) + appName := args.Arg(0) + err = network.BuildConfig(appName) case "rebuildall": - network.CommandRebuildall() + args := flag.NewFlagSet("network:rebuildall", flag.ExitOnError) + args.Parse(os.Args[2:]) + err = network.CommandRebuildall() case "report": - appName := flag.Arg(1) - infoFlag := flag.Arg(2) - network.CommandReport(appName, infoFlag) + args := flag.NewFlagSet("network:report", flag.ExitOnError) + args.Parse(os.Args[2:]) + appName := args.Arg(0) + infoFlag := args.Arg(1) + err = network.CommandReport(appName, infoFlag) case "set": - appName := flag.Arg(1) - property := flag.Arg(2) - value := flag.Arg(3) - network.CommandSet(appName, property, value) + args := flag.NewFlagSet("network:set", flag.ExitOnError) + args.Parse(os.Args[2:]) + appName := args.Arg(0) + property := args.Arg(1) + value := args.Arg(2) + err = network.CommandSet(appName, property, value) default: common.LogFail(fmt.Sprintf("Invalid plugin subcommand call: %s", subcommand)) } + + if err != nil { + common.LogFail(err.Error()) + } } diff --git a/plugins/network/subcommands.go b/plugins/network/subcommands.go index 12d07e20f..7253d7f7b 100644 --- a/plugins/network/subcommands.go +++ b/plugins/network/subcommands.go @@ -106,14 +106,20 @@ func CommandList() error { } // CommandRebuildall rebuilds network settings for all apps -func CommandRebuildall() { +func CommandRebuildall() error { apps, err := common.DokkuApps() if err != nil { - common.LogFail(err.Error()) + return err } + for _, appName := range apps { - BuildConfig(appName) + err = BuildConfig(appName) + if err != nil { + return err + } } + + return nil } // CommandReport displays a network report for one or more apps @@ -140,7 +146,7 @@ func CommandReport(appName string, infoFlag string) error { } // CommandSet set or clear a network property for an app -func CommandSet(appName string, property string, value string) { +func CommandSet(appName string, property string, value string) error { if property == "bind-all-interfaces" && value == "" { value = "false" } @@ -156,13 +162,14 @@ func CommandSet(appName string, property string, value string) { } if attachProperites[property] { if invalidNetworks[value] { - common.LogFail("Invalid network name specified for attach") + return errors.New("Invalid network name specified for attach") } if isConflictingPropertyValue(appName, property, value) { - common.LogFail("Network name already associated with this app") + return errors.New("Network name already associated with this app") } } common.CommandPropertySet("network", appName, property, value, DefaultProperties) + return nil } diff --git a/plugins/proxy/proxy.go b/plugins/proxy/proxy.go index 70a4406fb..57fc08e9e 100644 --- a/plugins/proxy/proxy.go +++ b/plugins/proxy/proxy.go @@ -93,16 +93,6 @@ func filterAppProxyPorts(appName string, scheme string, hostPort int) []PortMap return filteredProxyMaps } -func getAppName(args []string) (appName string, err error) { - if len(args) >= 1 { - appName = args[0] - } else { - err = errors.New("Please specify an app to run the command on") - } - - return -} - func getAppProxyType(appName string) string { return config.GetWithDefault(appName, "DOKKU_APP_PROXY_TYPE", "nginx") } diff --git a/plugins/proxy/src/subcommands/subcommands.go b/plugins/proxy/src/subcommands/subcommands.go index e15730b8d..bc2c49fa7 100644 --- a/plugins/proxy/src/subcommands/subcommands.go +++ b/plugins/proxy/src/subcommands/subcommands.go @@ -19,40 +19,55 @@ func main() { switch subcommand { case "disable": args := flag.NewFlagSet("proxy:disable", flag.ExitOnError) + skipRestart := args.Bool("no-restart", false, "--no-restart: skip restart of the app") args.Parse(os.Args[2:]) - err = proxy.CommandDisable(args.Args()) + appName := args.Arg(0) + err = proxy.CommandDisable(appName, *skipRestart) case "enable": args := flag.NewFlagSet("proxy:enable", flag.ExitOnError) args.Parse(os.Args[2:]) - err = proxy.CommandEnable(args.Args()) + appName := args.Arg(0) + err = proxy.CommandEnable(appName) case "ports": args := flag.NewFlagSet("proxy:ports", flag.ExitOnError) args.Parse(os.Args[2:]) - err = proxy.CommandPorts(args.Args()) + appName := args.Arg(0) + err = proxy.CommandPorts(appName) case "ports-add": args := flag.NewFlagSet("proxy:ports-add", flag.ExitOnError) args.Parse(os.Args[2:]) - err = proxy.CommandPortsAdd(args.Args()) + appName := args.Arg(0) + _, portMaps := common.ShiftString(args.Args()) + err = proxy.CommandPortsAdd(appName, portMaps) case "ports-clear": args := flag.NewFlagSet("proxy:ports-clear", flag.ExitOnError) args.Parse(os.Args[2:]) - err = proxy.CommandPortsClear(args.Args()) + appName := args.Arg(0) + err = proxy.CommandPortsClear(appName) case "ports-remove": args := flag.NewFlagSet("proxy:ports-remove", flag.ExitOnError) args.Parse(os.Args[2:]) - err = proxy.CommandPortsRemove(args.Args()) + appName := args.Arg(0) + _, portMaps := common.ShiftString(args.Args()) + err = proxy.CommandPortsRemove(appName, portMaps) case "ports-set": args := flag.NewFlagSet("proxy:ports-set", flag.ExitOnError) args.Parse(os.Args[2:]) - err = proxy.CommandPortsSet(args.Args()) + appName := args.Arg(0) + _, portMaps := common.ShiftString(args.Args()) + err = proxy.CommandPortsSet(appName, portMaps) case "report": args := flag.NewFlagSet("proxy:report", flag.ExitOnError) args.Parse(os.Args[2:]) - err = proxy.CommandReport(args.Args()) + appName := args.Arg(0) + infoFlag := args.Arg(1) + err = proxy.CommandReport(appName, infoFlag) case "set": args := flag.NewFlagSet("proxy:set", flag.ExitOnError) args.Parse(os.Args[2:]) - err = proxy.CommandSet(args.Args()) + appName := args.Arg(0) + proxyType := args.Arg(1) + err = proxy.CommandSet(appName, proxyType) default: common.LogFail(fmt.Sprintf("Invalid plugin subcommand call: %s", subcommand)) } diff --git a/plugins/proxy/subcommands.go b/plugins/proxy/subcommands.go index e3a5abccf..7252fb19d 100644 --- a/plugins/proxy/subcommands.go +++ b/plugins/proxy/subcommands.go @@ -9,10 +9,9 @@ import ( ) // CommandDisable disables the proxy for app via command line -func CommandDisable(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandDisable(appName string, skipRestart bool) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } if !IsAppProxyEnabled(appName) { @@ -21,16 +20,11 @@ func CommandDisable(args []string) error { } common.LogInfo1("Disabling proxy for app") - restart := true - if len(args) >= 2 && args[1] == "--no-restart" { - restart = false - } - entries := map[string]string{ "DOKKU_DISABLE_PROXY": "1", } - if err := config.SetMany(appName, entries, restart); err != nil { + if err := config.SetMany(appName, entries, false); err != nil { return err } @@ -38,10 +32,9 @@ func CommandDisable(args []string) error { } // CommandEnable enables the proxy for app via command line -func CommandEnable(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandEnable(appName string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } if IsAppProxyEnabled(appName) { @@ -50,13 +43,8 @@ func CommandEnable(args []string) error { } common.LogInfo1("Enabling proxy for app") - restart := true - if len(args) >= 2 && args[1] == "--no-restart" { - restart = false - } - keys := []string{"DOKKU_DISABLE_PROXY"} - if err := config.UnsetMany(appName, keys, restart); err != nil { + if err := config.UnsetMany(appName, keys, false); err != nil { return err } @@ -64,28 +52,25 @@ func CommandEnable(args []string) error { } // CommandPorts is a cmd wrapper to list proxy port mappings for an app -func CommandPorts(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandPorts(appName string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } return listAppProxyPorts(appName) } // CommandPortsAdd adds proxy port mappings to an app -func CommandPortsAdd(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandPortsAdd(appName string, portMaps []string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } - if len(args) == 1 { + if len(portMaps) == 0 { return errors.New("No port mapping specified") } - stringPortMap := strings.Join(args[1:], " ") - proxyPortMap, err := parseProxyPortMapString(stringPortMap) + proxyPortMap, err := parseProxyPortMapString(strings.Join(portMaps, " ")) if err != nil { return err } @@ -98,15 +83,13 @@ func CommandPortsAdd(args []string) error { } // CommandPortsClear clears all proxy port mappings for an app -func CommandPortsClear(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandPortsClear(appName string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } keys := []string{"DOKKU_PROXY_PORT_MAP"} - err = config.UnsetMany(appName, keys, false) - if err != nil { + if err := config.UnsetMany(appName, keys, false); err != nil { return err } @@ -114,18 +97,16 @@ func CommandPortsClear(args []string) error { } // CommandPortsRemove removes specific proxy port mappings from an app -func CommandPortsRemove(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandPortsRemove(appName string, portMaps []string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } - if len(args) == 1 { + if len(portMaps) == 0 { return errors.New("No port mapping specified") } - stringPortMap := strings.Join(args[1:], " ") - proxyPortMap, err := parseProxyPortMapString(stringPortMap) + proxyPortMap, err := parseProxyPortMapString(strings.Join(portMaps, " ")) if err != nil { return err } @@ -138,18 +119,16 @@ func CommandPortsRemove(args []string) error { } // CommandPortsSet sets proxy port mappings for an app -func CommandPortsSet(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandPortsSet(appName string, portMaps []string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } - if len(args) == 1 { + if len(portMaps) == 0 { return errors.New("No port mapping specified") } - stringPortMap := strings.Join(args[1:], " ") - proxyPortMap, err := parseProxyPortMapString(stringPortMap) + proxyPortMap, err := parseProxyPortMapString(strings.Join(portMaps, " ")) if err != nil { return err } @@ -162,17 +141,7 @@ func CommandPortsSet(args []string) error { } // CommandReport displays a proxy report for one or more apps -func CommandReport(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err - } - - infoFlag := "" - if len(args) > 1 { - infoFlag = args[1] - } - +func CommandReport(appName string, infoFlag string) error { if strings.HasPrefix(appName, "--") { infoFlag = appName appName = "" @@ -195,17 +164,15 @@ func CommandReport(args []string) error { } // CommandSet sets a proxy for an app -func CommandSet(args []string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandSet(appName string, proxyType string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } - if len(appName) < 2 { + if len(proxyType) < 2 { return errors.New("Please specify a proxy type") } - proxyType := args[1] entries := map[string]string{ "DOKKU_APP_PROXY_TYPE": proxyType, } diff --git a/plugins/repo/src/subcommands/subcommands.go b/plugins/repo/src/subcommands/subcommands.go index 4194cb147..3ef36c88c 100644 --- a/plugins/repo/src/subcommands/subcommands.go +++ b/plugins/repo/src/subcommands/subcommands.go @@ -14,16 +14,18 @@ import ( func main() { parts := strings.Split(os.Args[0], "/") subcommand := parts[len(parts)-1] - flag.Parse() var err error switch subcommand { case "gc": - flag.Parse() - appName := flag.Arg(1) + args := flag.NewFlagSet("repo:gc", flag.ExitOnError) + args.Parse(os.Args[2:]) + appName := args.Arg(0) err = repo.CommandGc(appName) case "purge-cache": - appName := flag.Arg(1) + args := flag.NewFlagSet("repo:purge-cache", flag.ExitOnError) + args.Parse(os.Args[2:]) + appName := args.Arg(0) err = repo.CommandPurgeCache(appName) default: common.LogFail(fmt.Sprintf("Invalid plugin subcommand call: %s", subcommand)) diff --git a/plugins/resource/functions.go b/plugins/resource/functions.go new file mode 100644 index 000000000..72f9dcc56 --- /dev/null +++ b/plugins/resource/functions.go @@ -0,0 +1,129 @@ +package resource + +import ( + "fmt" + + "github.com/dokku/dokku/plugins/common" +) + +func clearByResourceType(appName string, processType string, resourceType string) { + noun := "limits" + if resourceType == "reserve" { + noun = "reservation" + } + + message := fmt.Sprintf("clearing %v %v", appName, noun) + if processType != "_default_" && processType != "" { + message = fmt.Sprintf("%v (%v)", message, processType) + } + common.LogInfo2Quiet(message) + + if processType == "" { + resources, err := common.PropertyGetAll("resource", appName) + if err != nil { + return + } + for key := range resources { + common.PropertyDelete("resource", appName, key) + } + } else { + resources := []string{ + "cpu", + "memory", + "memory-swap", + "network", + "network-ingress", + "network-egress", + "nvidia-gpu", + } + + for _, key := range resources { + property := propertyKey(processType, resourceType, key) + common.PropertyDelete("resource", appName, property) + } + } +} + +func setResourceType(appName string, processType string, r Resource, resourceType string) error { + if len(processType) == 0 { + processType = "_default_" + } + + resources := map[string]string{ + "cpu": r.CPU, + "memory": r.Memory, + "memory-swap": r.MemorySwap, + "network": r.Network, + "network-ingress": r.NetworkIngress, + "network-egress": r.NetworkEgress, + "nvidia-gpu": r.NvidiaGPU, + } + + hasValues := false + for _, value := range resources { + if value != "" { + hasValues = true + } + } + + if !hasValues { + reportResourceType(appName, processType, resourceType) + return nil + } + + noun := "limits" + if resourceType == "reserve" { + noun = "reservation" + } + message := fmt.Sprintf("Setting resource %v for %v", noun, appName) + if processType != "_default_" { + message = fmt.Sprintf("%v (%v)", message, processType) + } + common.LogInfo2Quiet(message) + + for key, value := range resources { + if value == "" { + continue + } + + common.LogVerbose(fmt.Sprintf("%v: %v", key, value)) + property := propertyKey(processType, resourceType, key) + err := common.PropertyWrite("resource", appName, property, value) + if err != nil { + return err + } + } + + return nil +} + +func reportResourceType(appName string, processType string, resourceType string) { + noun := "limits" + if resourceType == "reserve" { + noun = "reservation" + } + + message := fmt.Sprintf("resource %v %v information", noun, appName) + if processType == "_default_" { + message = fmt.Sprintf("%v [defaults]", message) + } else { + message = fmt.Sprintf("%v (%v)", message, processType) + } + common.LogInfo2Quiet(message) + + resources := []string{ + "cpu", + "memory", + "memory-swap", + "network", + "network-ingress", + "network-egress", + "nvidia-gpu", + } + + for _, key := range resources { + property := propertyKey(processType, resourceType, key) + value := common.PropertyGet("resource", appName, property) + common.LogVerbose(fmt.Sprintf("%v: %v", key, value)) + } +} diff --git a/plugins/resource/resource.go b/plugins/resource/resource.go index 04fcef4b3..7d0ea4c4c 100644 --- a/plugins/resource/resource.go +++ b/plugins/resource/resource.go @@ -1,7 +1,6 @@ package resource import ( - "errors" "fmt" "github.com/dokku/dokku/plugins/common" @@ -63,16 +62,3 @@ func GetResourceValue(appName string, processType string, resourceType string, k func propertyKey(processType string, resourceType string, key string) string { return fmt.Sprintf("%v.%v.%v", processType, resourceType, key) } - -func getAppName(args []string) (string, error) { - if len(args) < 1 { - return "", errors.New("Please specify an app to run the command on") - } - - appName := args[0] - if err := common.VerifyAppName(appName); err != nil { - return "", err - } - - return appName, nil -} diff --git a/plugins/resource/src/subcommands/subcommands.go b/plugins/resource/src/subcommands/subcommands.go index 713fa04c9..b6fbf99ea 100644 --- a/plugins/resource/src/subcommands/subcommands.go +++ b/plugins/resource/src/subcommands/subcommands.go @@ -28,6 +28,7 @@ func main() { networkEgress := args.String("network-egress", "", "network-egress: The amount of egress network bandwidth to limit processes by") nvidiaGpu := args.String("nvidia-gpu", "", "nvidia-gpu: The number of Nvidia GPUs to limit a process to") args.Parse(os.Args[2:]) + appName := args.Arg(0) resources := resource.Resource{ CPU: *cpu, @@ -39,17 +40,19 @@ func main() { NvidiaGPU: *nvidiaGpu, } - err = resource.CommandLimit(args.Args(), *processType, resources) + err = resource.CommandLimit(appName, *processType, resources) case "limit-clear": args := flag.NewFlagSet("resource:limit-clear", flag.ExitOnError) processType := args.String("process-type", "", "process-type: A process type to clear") args.Parse(os.Args[2:]) - err = resource.CommandLimitClear(args.Args(), *processType) + appName := args.Arg(0) + err = resource.CommandLimitClear(appName, *processType) case "report": - flag.Parse() - appName := flag.Arg(1) - infoFlag := flag.Arg(2) - resource.CommandReport(appName, infoFlag) + args := flag.NewFlagSet("resource:report", flag.ExitOnError) + args.Parse(os.Args[2:]) + appName := args.Arg(0) + infoFlag := args.Arg(1) + err = resource.CommandReport(appName, infoFlag) case "reserve": args := flag.NewFlagSet("resource:reserve", flag.ExitOnError) processType := args.String("process-type", "", "process-type: A process type to manage") @@ -61,6 +64,7 @@ func main() { networkEgress := args.String("network-egress", "", "network-egress: The amount of egress network bandwidth to reserve for processes") nvidiaGpu := args.String("nvidia-gpu", "", "nvidia-gpu: The number of Nvidia GPUs to resource for a process") args.Parse(os.Args[2:]) + appName := args.Arg(0) resources := resource.Resource{ CPU: *cpu, @@ -72,12 +76,13 @@ func main() { NvidiaGPU: *nvidiaGpu, } - err = resource.CommandReserve(args.Args(), *processType, resources) + err = resource.CommandReserve(appName, *processType, resources) case "reserve-clear": args := flag.NewFlagSet("resource:reserve-clear", flag.ExitOnError) processType := args.String("process-type", "", "process-type: A process type to clear") args.Parse(os.Args[2:]) - err = resource.CommandReserveClear(args.Args(), *processType) + appName := args.Arg(0) + err = resource.CommandReserveClear(appName, *processType) default: common.LogFail(fmt.Sprintf("Invalid plugin subcommand call: %s", subcommand)) } diff --git a/plugins/resource/subcommands.go b/plugins/resource/subcommands.go index 8107dceaf..32cc9e055 100644 --- a/plugins/resource/subcommands.go +++ b/plugins/resource/subcommands.go @@ -1,27 +1,25 @@ package resource import ( - "fmt" + "errors" "strings" "github.com/dokku/dokku/plugins/common" ) // CommandLimit implements resource:limit -func CommandLimit(args []string, processType string, r Resource) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandLimit(appName string, processType string, r Resource) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } return setResourceType(appName, processType, r, "limit") } // CommandLimitClear implements resource:limit-clear -func CommandLimitClear(args []string, processType string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandLimitClear(appName string, processType string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } clearByResourceType(appName, processType, "limit") @@ -52,145 +50,20 @@ func CommandReport(appName string, infoFlag string) error { } // CommandReserve implements resource:reserve -func CommandReserve(args []string, processType string, r Resource) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandReserve(appName string, processType string, r Resource) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } return setResourceType(appName, processType, r, "reserve") } // CommandReserveClear implements resource:reserve-clear -func CommandReserveClear(args []string, processType string) error { - appName, err := getAppName(args) - if err != nil { - return err +func CommandReserveClear(appName string, processType string) error { + if appName == "" { + return errors.New("Please specify an app to run the command on") } clearByResourceType(appName, processType, "reserve") return nil } - -func clearByResourceType(appName string, processType string, resourceType string) { - noun := "limits" - if resourceType == "reserve" { - noun = "reservation" - } - - message := fmt.Sprintf("clearing %v %v", appName, noun) - if processType != "_default_" && processType != "" { - message = fmt.Sprintf("%v (%v)", message, processType) - } - common.LogInfo2Quiet(message) - - if processType == "" { - resources, err := common.PropertyGetAll("resource", appName) - if err != nil { - return - } - for key := range resources { - common.PropertyDelete("resource", appName, key) - } - } else { - resources := []string{ - "cpu", - "memory", - "memory-swap", - "network", - "network-ingress", - "network-egress", - "nvidia-gpu", - } - - for _, key := range resources { - property := propertyKey(processType, resourceType, key) - common.PropertyDelete("resource", appName, property) - } - } -} - -func setResourceType(appName string, processType string, r Resource, resourceType string) error { - if len(processType) == 0 { - processType = "_default_" - } - - resources := map[string]string{ - "cpu": r.CPU, - "memory": r.Memory, - "memory-swap": r.MemorySwap, - "network": r.Network, - "network-ingress": r.NetworkIngress, - "network-egress": r.NetworkEgress, - "nvidia-gpu": r.NvidiaGPU, - } - - hasValues := false - for _, value := range resources { - if value != "" { - hasValues = true - } - } - - if !hasValues { - reportResourceType(appName, processType, resourceType) - return nil - } - - noun := "limits" - if resourceType == "reserve" { - noun = "reservation" - } - message := fmt.Sprintf("Setting resource %v for %v", noun, appName) - if processType != "_default_" { - message = fmt.Sprintf("%v (%v)", message, processType) - } - common.LogInfo2Quiet(message) - - for key, value := range resources { - if value == "" { - continue - } - - common.LogVerbose(fmt.Sprintf("%v: %v", key, value)) - property := propertyKey(processType, resourceType, key) - err := common.PropertyWrite("resource", appName, property, value) - if err != nil { - return err - } - } - - return nil -} - -func reportResourceType(appName string, processType string, resourceType string) { - noun := "limits" - if resourceType == "reserve" { - noun = "reservation" - } - - message := fmt.Sprintf("resource %v %v information", noun, appName) - if processType == "_default_" { - message = fmt.Sprintf("%v [defaults]", message) - } else { - message = fmt.Sprintf("%v (%v)", message, processType) - } - common.LogInfo2Quiet(message) - - resources := []string{ - "cpu", - "memory", - "memory-swap", - "network", - "network-ingress", - "network-egress", - "nvidia-gpu", - } - - for _, key := range resources { - property := propertyKey(processType, resourceType, key) - value := common.PropertyGet("resource", appName, property) - common.LogVerbose(fmt.Sprintf("%v: %v", key, value)) - } - return -} diff --git a/tests/unit/20_config.bats b/tests/unit/20_config.bats index 0fbcca455..c0221ab80 100644 --- a/tests/unit/20_config.bats +++ b/tests/unit/20_config.bats @@ -80,7 +80,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_output "FAILED: At least one env pair must be given" - [ "$status" -eq 1 ] + assert_failure run /bin/bash -c "dokku config:get $TEST_APP test_var1 | grep true" echo "output: $output" @@ -105,7 +105,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_output "FAILED: At least one env pair must be given" - [ "$status" -eq 1 ] + assert_failure run /bin/bash -c "dokku --app $TEST_APP config:get test_var1 | grep true" echo "output: $output" @@ -150,7 +150,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_output "FAILED: At least one key must be given" - [ "$status" -eq 1 ] + assert_failure } @test "(config) global config (herokuish)" { diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index 32a612619..5ca474caa 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -19,6 +19,11 @@ SKIPPED_TEST_ERR_MSG="previous test failed! skipping remaining tests..." # skips remaining tests on first failure global_setup() { [[ ! -f "${BATS_PARENT_TMPNAME}.skip" ]] || skip "$SKIPPED_TEST_ERR_MSG" + + dokku apps:list + docker container ls + free -m + docker container ls --quiet | xargs -n1 docker container rm -f || true } global_teardown() {