diff --git a/docs/appendices/0.21.0-migration-guide.md b/docs/appendices/0.21.0-migration-guide.md index b3b479f91..77785ea8d 100644 --- a/docs/appendices/0.21.0-migration-guide.md +++ b/docs/appendices/0.21.0-migration-guide.md @@ -7,3 +7,8 @@ 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`. + +## 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. + 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/common/common.go b/plugins/common/common.go index 43772117e..23f4188bf 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)) } +// Shift 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)