From 566d8f9255d532e88e72d5bce122bebaee88bc81 Mon Sep 17 00:00:00 2001 From: Lance Ball Date: Fri, 11 Sep 2020 09:42:19 -0400 Subject: [PATCH] feat: default to no confirmation prompts for CLI commands The CLI commands all printed confirmation prompts for the various flags they exposed. This commit modifies that logic, so that there is no longer a `-y` flag, but instead a `--confirm` or `-c` flag for each command, and prompts are only displayed if using this flag. In most cases, the derived values are printed even if not prompted for. In call cases where the user is prompted, I have removed the "Verbose" prompt, as that seems less like a configuration option that needs to be confirmed, and more like just a CLI option for the current run which we can just accept as-is. The text for the prompts has also been reduced to one or two words. Also added are some checks around image naming and repositories, short circuiting failures that could occur if these are not specified or are unknown. For example, if a user does `faas init` and then `faas deploy` we don't yet know what the image name should be - one hasn't been built. Fixes: https://github.com/boson-project/faas/issues/91 Fixes: https://github.com/boson-project/faas/issues/90 Fixes: https://github.com/boson-project/faas/issues/89 --- client.go | 2 +- cmd/build.go | 49 +++++++++++++++++++++++++++++++++---------------- cmd/create.go | 49 +++++++++++++++++++++++++++++++++---------------- cmd/delete.go | 8 +++----- cmd/deploy.go | 39 +++++++++++++++++++++++---------------- cmd/init.go | 41 ++++++++++++++++++++--------------------- cmd/root.go | 13 +++++++++++++ cmd/update.go | 31 +++++++++++++++++++++++++------ 8 files changed, 151 insertions(+), 81 deletions(-) diff --git a/client.go b/client.go index 061256e135..5ae57248bb 100644 --- a/client.go +++ b/client.go @@ -369,7 +369,7 @@ func (c *Client) Build(path string) (err error) { return } - // Derive Image from the path (preceidence is given to extant config) + // Derive Image from the path (precedence is given to extant config) if f.Image, err = DerivedImage(path, c.repository); err != nil { return } diff --git a/cmd/build.go b/cmd/build.go index 518855efbb..982bdd408b 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -1,6 +1,8 @@ package cmd import ( + "fmt" + "github.com/ory/viper" "github.com/spf13/cobra" @@ -11,22 +13,37 @@ import ( func init() { root.AddCommand(buildCmd) + buildCmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all configuration options - $FAAS_CONFIRM") buildCmd.Flags().StringP("image", "i", "", "Optional full image name, in form [registry]/[namespace]/[name]:[tag] for example quay.io/myrepo/project.name:latest (overrides --repository) - $FAAS_IMAGE") buildCmd.Flags().StringP("path", "p", cwd(), "Path to the Function project directory - $FAAS_PATH") buildCmd.Flags().StringP("repository", "r", "", "Repository for built images, ex 'docker.io/myuser' or just 'myuser'. Optional if --image provided. - $FAAS_REPOSITORY") - buildCmd.Flags().BoolP("yes", "y", false, "When in interactive mode (attached to a TTY) skip prompts. - $FAAS_YES") } var buildCmd = &cobra.Command{ Use: "build [options]", Short: "Build an existing Function project as an OCI image", SuggestFor: []string{"biuld", "buidl", "built"}, - PreRunE: bindEnv("image", "path", "repository", "yes"), + PreRunE: bindEnv("image", "path", "repository", "confirm"), RunE: runBuild, } func runBuild(cmd *cobra.Command, _ []string) (err error) { - config := newBuildConfig().Prompt() + config := newBuildConfig() + function, err := functionWithOverrides(config.Path, "", config.Image) + if err != nil { + return + } + + // If the Function does not yet have an image name, and one was not provided + // on the command line AND a --repository was not provided, then we need to + // prompt for a repository from which we can derive an image name. + if function.Image == "" && config.Repository == "" { + fmt.Print("A repository for Function images is required. For example, 'docker.io/tigerteam'.\n\n") + config.Repository = prompt.ForString("Repository for Function images", "") + if config.Repository == "" { + return fmt.Errorf("Unable to determine Function image name") + } + } builder := buildpacks.NewBuilder() builder.Verbose = config.Verbose @@ -36,10 +53,7 @@ func runBuild(cmd *cobra.Command, _ []string) (err error) { faas.WithRepository(config.Repository), // for deriving image name when --image not provided explicitly. faas.WithBuilder(builder)) - // overrideImage name for built images, if --image provided. - if err = overrideImage(config.Path, config.Image); err != nil { - return - } + config.Prompt() return client.Build(config.Path) } @@ -65,9 +79,9 @@ type buildConfig struct { // Verbose logging. Verbose bool - // Yes: agree to values arrived upon from environment plus flags plus defaults, - // and skip the interactive prompting (only applicable when attached to a TTY). - Yes bool + // Confirm: confirm values arrived upon from environment plus flags plus defaults, + // with interactive prompting (only applicable when attached to a TTY). + Confirm bool } func newBuildConfig() buildConfig { @@ -76,21 +90,24 @@ func newBuildConfig() buildConfig { Path: viper.GetString("path"), Repository: viper.GetString("repository"), Verbose: viper.GetBool("verbose"), // defined on root - Yes: viper.GetBool("yes"), + Confirm: viper.GetBool("confirm"), } } // Prompt the user with value of config members, allowing for interaractive changes. -// Skipped if not in an interactive terminal (non-TTY), or if --yes (agree to -// all prompts) was explicitly set. +// Skipped if not in an interactive terminal (non-TTY), or if --confirm false (agree to +// all prompts) was set (default). func (c buildConfig) Prompt() buildConfig { - if !interactiveTerminal() || c.Yes { + imageName := deriveImage(c.Image, c.Repository, c.Path) + if !interactiveTerminal() || !c.Confirm { + // If --confirm false or non-interactive, just print the image name + fmt.Printf("Building image: %v\n", imageName) return c } return buildConfig{ Path: prompt.ForString("Path to project directory", c.Path), - Image: prompt.ForString("Resulting image name", deriveImage(c.Image, c.Repository, c.Path), prompt.WithRequired(true)), - Verbose: prompt.ForBool("Verbose logging", c.Verbose), + Image: prompt.ForString("Image name", imageName, prompt.WithRequired(true)), + Verbose: c.Verbose, // Repository not prompted for as it would be confusing when combined with explicit image. Instead it is // inferred by the derived default for Image, which uses Repository for derivation. } diff --git a/cmd/create.go b/cmd/create.go index 8e97cc823f..04e4c49d42 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -4,6 +4,7 @@ import ( "fmt" "path/filepath" + "github.com/ory/viper" "github.com/spf13/cobra" "github.com/boson-project/faas" @@ -16,6 +17,7 @@ import ( func init() { root.AddCommand(createCmd) + createCmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all configuration options - $FAAS_CONFIRM") createCmd.Flags().StringP("image", "i", "", "Optional full image name, in form [registry]/[namespace]/[name]:[tag] for example quay.io/myrepo/project.name:latest (overrides --repository) - $FAAS_IMAGE") createCmd.Flags().StringP("namespace", "n", "", "Override namespace into which the Function is deployed (on supported platforms). Default is to use currently active underlying platform setting - $FAAS_NAMESPACE") createCmd.Flags().StringP("path", "p", cwd(), "Path to the new project directory - $FAAS_PATH") @@ -23,7 +25,6 @@ func init() { createCmd.Flags().StringP("runtime", "l", faas.DefaultRuntime, "Function runtime language/framework. - $FAAS_RUNTIME") createCmd.Flags().StringP("templates", "", filepath.Join(configPath(), "faas", "templates"), "Extensible templates path. - $FAAS_TEMPLATES") createCmd.Flags().StringP("trigger", "t", faas.DefaultTrigger, "Function trigger (ex: 'http','events') - $FAAS_TRIGGER") - createCmd.Flags().BoolP("yes", "y", false, "When in interactive mode (attached to a TTY) skip prompts. - $FAAS_YES") var err error err = createCmd.RegisterFlagCompletionFunc("image", CompleteRegistryList) @@ -40,7 +41,7 @@ var createCmd = &cobra.Command{ Use: "create [options]", Short: "Create a new Function, including initialization of local files and deployment.", SuggestFor: []string{"cerate", "new"}, - PreRunE: bindEnv("image", "namespace", "path", "repository", "runtime", "templates", "trigger", "yes"), + PreRunE: bindEnv("image", "namespace", "path", "repository", "runtime", "templates", "trigger", "confirm"), RunE: runCreate, } @@ -55,20 +56,31 @@ func runCreate(cmd *cobra.Command, args []string) (err error) { Image: config.Image, } + if function.Image == "" && config.Repository == "" { + fmt.Print("A repository for Function images is required. For example, 'docker.io/tigerteam'.\n\n") + config.Repository = prompt.ForString("Repository for Function images", "") + if config.Repository == "" { + return fmt.Errorf("Unable to determine Function image name") + } + } + + // Defined in root command + verbose := viper.GetBool("verbose") + builder := buildpacks.NewBuilder() - builder.Verbose = config.initConfig.Verbose + builder.Verbose = verbose pusher := docker.NewPusher() - pusher.Verbose = config.initConfig.Verbose + pusher.Verbose = verbose deployer := knative.NewDeployer() - deployer.Verbose = config.initConfig.Verbose + deployer.Verbose = verbose listener := progress.New() - listener.Verbose = config.initConfig.Verbose + listener.Verbose = verbose client := faas.New( - faas.WithVerbose(config.initConfig.Verbose), + faas.WithVerbose(verbose), faas.WithTemplates(config.Templates), faas.WithRepository(config.Repository), // for deriving image name when --image not provided explicitly. faas.WithBuilder(builder), @@ -95,20 +107,25 @@ func newCreateConfig(args []string) createConfig { } // Prompt the user with value of config members, allowing for interaractive changes. -// Skipped if not in an interactive terminal (non-TTY), or if --yes (agree to -// all prompts) was explicitly set. +// Skipped if not in an interactive terminal (non-TTY), or if --confirm (agree to +// all prompts) was not explicitly set. func (c createConfig) Prompt() createConfig { - if !interactiveTerminal() || c.initConfig.Yes { + name := deriveName(c.Name, c.initConfig.Path) + if !interactiveTerminal() || !c.initConfig.Confirm { + // Just print the basics if not confirming + fmt.Printf("Project path: %v\n", c.initConfig.Path) + fmt.Printf("Project name: %v\n", name) + fmt.Printf("Runtime: %v\n", c.Runtime) + fmt.Printf("Trigger: %v\n", c.Trigger) return c } return createConfig{ initConfig: initConfig{ - Path: prompt.ForString("Path to project directory", c.initConfig.Path), - Name: prompt.ForString("Function project name", deriveName(c.Name, c.initConfig.Path), prompt.WithRequired(true)), - Verbose: prompt.ForBool("Verbose logging", c.initConfig.Verbose), - Runtime: prompt.ForString("Runtime of source", c.Runtime), - Trigger: prompt.ForString("Function Trigger", c.Trigger), - // Templates intentiopnally omitted from prompt for being an edge case. + Path: prompt.ForString("Project path", c.initConfig.Path), + Name: prompt.ForString("Project name", name, prompt.WithRequired(true)), + Runtime: prompt.ForString("Runtime", c.Runtime), + Trigger: prompt.ForString("Trigger", c.Trigger), + // Templates intentionally omitted from prompt for being an edge case. }, buildConfig: buildConfig{ Repository: prompt.ForString("Repository for Function images", c.buildConfig.Repository), diff --git a/cmd/delete.go b/cmd/delete.go index 67e469f416..f13c9f7597 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -11,9 +11,9 @@ import ( func init() { root.AddCommand(deleteCmd) + deleteCmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all configuration options - $FAAS_CONFIRM") deleteCmd.Flags().StringP("path", "p", cwd(), "Path to the project which should be deleted - $FAAS_PATH") deleteCmd.Flags().StringP("namespace", "n", "", "Override namespace in which to search for Functions. Default is to use currently active underlying platform setting - $FAAS_NAMESPACE") - deleteCmd.Flags().BoolP("yes", "y", false, "When in interactive mode (attached to a TTY), skip prompting the user. - $FAAS_YES") } var deleteCmd = &cobra.Command{ @@ -22,7 +22,7 @@ var deleteCmd = &cobra.Command{ Long: `Removes the deployed Function by name, by explicit path, or by default for the current directory. No local files are deleted.`, SuggestFor: []string{"remove", "rm", "del"}, ValidArgsFunction: CompleteFunctionList, - PreRunE: bindEnv("path", "yes", "namespace"), + PreRunE: bindEnv("path", "confirm", "namespace"), RunE: runDelete, } @@ -46,7 +46,6 @@ type deleteConfig struct { Namespace string Path string Verbose bool - Yes bool } // newDeleteConfig returns a config populated from the current execution context @@ -61,7 +60,6 @@ func newDeleteConfig(args []string) deleteConfig { Namespace: viper.GetString("namespace"), Name: deriveName(name, viper.GetString("path")), // args[0] or derived Verbose: viper.GetBool("verbose"), // defined on root - Yes: viper.GetBool("yes"), } } @@ -69,7 +67,7 @@ func newDeleteConfig(args []string) deleteConfig { // Skipped if not in an interactive terminal (non-TTY), or if --yes (agree to // all prompts) was explicitly set. func (c deleteConfig) Prompt() deleteConfig { - if !interactiveTerminal() || c.Yes { + if !interactiveTerminal() || !viper.GetBool("confirm") { return c } return deleteConfig{ diff --git a/cmd/deploy.go b/cmd/deploy.go index 31107de353..b073bc4d5c 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -1,6 +1,8 @@ package cmd import ( + "fmt" + "github.com/ory/viper" "github.com/spf13/cobra" @@ -12,21 +14,31 @@ import ( func init() { root.AddCommand(deployCmd) + deployCmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all configuration options - $FAAS_CONFIRM") deployCmd.Flags().StringP("namespace", "n", "", "Override namespace into which the Function is deployed (on supported platforms). Default is to use currently active underlying platform setting - $FAAS_NAMESPACE") deployCmd.Flags().StringP("path", "p", cwd(), "Path to the function project directory - $FAAS_PATH") - deployCmd.Flags().BoolP("yes", "y", false, "When in interactive mode (attached to a TTY) skip prompts. - $FAAS_YES") } var deployCmd = &cobra.Command{ Use: "deploy", Short: "Deploy an existing Function project to a cluster", SuggestFor: []string{"delpoy", "deplyo"}, - PreRunE: bindEnv("namespace", "path", "yes"), + PreRunE: bindEnv("namespace", "path", "confirm"), RunE: runDeploy, } func runDeploy(cmd *cobra.Command, _ []string) (err error) { - config := newDeployConfig().Prompt() + config := newDeployConfig() + function, err := functionWithOverrides(config.Path, config.Namespace, "") + if err != nil { + return err + } + if function.Image == "" { + return fmt.Errorf("Cannot determine the Function image name. Have you built it yet?") + } + + // Confirm or print configuration + config.Prompt() pusher := docker.NewPusher() pusher.Verbose = config.Verbose @@ -39,11 +51,6 @@ func runDeploy(cmd *cobra.Command, _ []string) (err error) { faas.WithPusher(pusher), faas.WithDeployer(deployer)) - // overrieNamespace into which the function is deployed, if --namespace provided. - if err = overrideNamespace(config.Path, config.Namespace); err != nil { - return - } - return client.Deploy(config.Path) // NOTE: Namespace is optional, default is that used by k8s client @@ -66,9 +73,9 @@ type deployConfig struct { // Verbose logging. Verbose bool - // Yes: agree to values arrived upon from environment plus flags plus defaults, - // and skip the interactive prompting (only applicable when attached to a TTY). - Yes bool + // Confirm: confirm values arrived upon from environment plus flags plus defaults, + // with interactive prompting (only applicable when attached to a TTY). + Confirm bool } // newDeployConfig creates a buildConfig populated from command flags and @@ -78,7 +85,7 @@ func newDeployConfig() deployConfig { Namespace: viper.GetString("namespace"), Path: viper.GetString("path"), Verbose: viper.GetBool("verbose"), // defined on root - Yes: viper.GetBool("yes"), + Confirm: viper.GetBool("confirm"), } } @@ -86,12 +93,12 @@ func newDeployConfig() deployConfig { // Skipped if not in an interactive terminal (non-TTY), or if --yes (agree to // all prompts) was explicitly set. func (c deployConfig) Prompt() deployConfig { - if !interactiveTerminal() || c.Yes { + if !interactiveTerminal() || !c.Confirm { return c } return deployConfig{ - Namespace: prompt.ForString("Override default namespace (optional)", c.Namespace), - Path: prompt.ForString("Path to project directory", c.Path), - Verbose: prompt.ForBool("Verbose logging", c.Verbose), + Namespace: prompt.ForString("Namespace", c.Namespace), + Path: prompt.ForString("Project path", c.Path), + Verbose: c.Verbose, } } diff --git a/cmd/init.go b/cmd/init.go index 3aaaa8f287..2f8f8e035e 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -13,11 +13,11 @@ import ( func init() { root.AddCommand(initCmd) + initCmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all configuration options - $FAAS_CONFIRM") initCmd.Flags().StringP("path", "p", cwd(), "Path to the new project directory - $FAAS_PATH") initCmd.Flags().StringP("runtime", "l", faas.DefaultRuntime, "Function runtime language/framework. - $FAAS_RUNTIME") initCmd.Flags().StringP("templates", "", filepath.Join(configPath(), "faas", "templates"), "Extensible templates path. - $FAAS_TEMPLATES") initCmd.Flags().StringP("trigger", "t", faas.DefaultTrigger, "Function trigger (ex: 'http','events') - $FAAS_TRIGGER") - initCmd.Flags().BoolP("yes", "y", false, "When in interactive mode (attached to a TTY), skip prompts. - $FAAS_YES") if err := initCmd.RegisterFlagCompletionFunc("runtime", CompleteRuntimeList); err != nil { fmt.Println("Error while calling RegisterFlagCompletionFunc: ", err) @@ -28,7 +28,7 @@ var initCmd = &cobra.Command{ Use: "init [options]", Short: "Initialize a new Function project", SuggestFor: []string{"inti", "new"}, - PreRunE: bindEnv("path", "runtime", "templates", "trigger", "yes"), + PreRunE: bindEnv("path", "runtime", "templates", "trigger", "confirm"), RunE: runInit, // TODO: autocomplate Functions for runtime and trigger. } @@ -43,9 +43,7 @@ func runInit(cmd *cobra.Command, args []string) error { Trigger: config.Trigger, } - client := faas.New( - faas.WithVerbose(config.Verbose), - faas.WithTemplates(config.Templates)) + client := faas.New(faas.WithTemplates(config.Templates)) return client.Initialize(function) } @@ -72,12 +70,9 @@ type initConfig struct { // Function which will be invoked with CloudEvents. Trigger string - // Verbose logging enabled. - Verbose bool - - // Yes: agree to values arrived upon from environment plus flags plus defaults, - // and skip the interactive prompting (only applicable when attached to a TTY). - Yes bool + // Confirm: confirm values arrived upon from environment plus flags plus defaults, + // with interactive prompting (only applicable when attached to a TTY). + Confirm bool } // newInitConfig returns a config populated from the current execution context @@ -93,25 +88,29 @@ func newInitConfig(args []string) initConfig { Runtime: viper.GetString("runtime"), Templates: viper.GetString("templates"), Trigger: viper.GetString("trigger"), - Verbose: viper.GetBool("verbose"), // defined on root - Yes: viper.GetBool("yes"), + Confirm: viper.GetBool("confirm"), } } // Prompt the user with value of config members, allowing for interaractive changes. -// Skipped if not in an interactive terminal (non-TTY), or if --yes (agree to -// all prompts) was explicitly set. +// Skipped if not in an interactive terminal (non-TTY), or if --confirm false (agree to +// all prompts) was set (default). func (c initConfig) Prompt() initConfig { - if !interactiveTerminal() || c.Yes { + name := deriveName(c.Name, c.Path) + if !interactiveTerminal() || !c.Confirm { + // Just print the basics if not confirming + fmt.Printf("Project path: %v\n", c.Path) + fmt.Printf("Project name: %v\n", name) + fmt.Printf("Runtime: %v\n", c.Runtime) + fmt.Printf("Trigger: %v\n", c.Trigger) return c } return initConfig{ // TODO: Path should be prompted for and set prior to name attempting path derivation. Test/fix this if necessary. - Path: prompt.ForString("Path to project directory", c.Path), - Name: prompt.ForString("Function project name", deriveName(c.Name, c.Path), prompt.WithRequired(true)), - Verbose: prompt.ForBool("Verbose logging", c.Verbose), - Runtime: prompt.ForString("Runtime of source", c.Runtime), - Trigger: prompt.ForString("Function Trigger", c.Trigger), + Path: prompt.ForString("Project path", c.Path), + Name: prompt.ForString("Project name", name, prompt.WithRequired(true)), + Runtime: prompt.ForString("Runtime", c.Runtime), + Trigger: prompt.ForString("Trigger", c.Trigger), // Templates intentiopnally omitted from prompt for being an edge case. } } diff --git a/cmd/root.go b/cmd/root.go index 104cad0507..c97c076945 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -150,6 +150,19 @@ func overrideNamespace(root, override string) (err error) { return f.WriteConfig() } +// functionWithOverrides sets the namespace and image strings for the +// Function project at root, if provided, and returns the Function +// configuration values +func functionWithOverrides(root, namespace, image string) (f faas.Function, err error) { + if err = overrideNamespace(root, namespace); err != nil { + return + } + if err = overrideImage(root, image); err != nil { + return + } + return faas.NewFunction(root) +} + // deriveName returns the explicit value (if provided) or attempts to derive // from the given path. Path is defaulted to current working directory, where // a function configuration, if it exists and contains a name, is used. Lastly diff --git a/cmd/update.go b/cmd/update.go index f8876fafe9..ae6a969114 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -1,6 +1,8 @@ package cmd import ( + "fmt" + "github.com/ory/viper" "github.com/spf13/cobra" @@ -8,10 +10,12 @@ import ( "github.com/boson-project/faas/buildpacks" "github.com/boson-project/faas/docker" "github.com/boson-project/faas/knative" + "github.com/boson-project/faas/prompt" ) func init() { root.AddCommand(updateCmd) + updateCmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all configuration options - $FAAS_CONFIRM") updateCmd.Flags().StringP("namespace", "n", "", "Override namespace for the Function (on supported platforms). Default is to use currently active underlying platform setting - $FAAS_NAMESPACE") updateCmd.Flags().StringP("path", "p", cwd(), "Path to the Function project directory - $FAAS_PATH") updateCmd.Flags().StringP("repository", "r", "", "Repository for built images, ex 'docker.io/myuser' or just 'myuser'. - $FAAS_REPOSITORY") @@ -22,12 +26,20 @@ var updateCmd = &cobra.Command{ Short: "Update or create a deployed Function", Long: `Update deployed Function to match the current local state.`, SuggestFor: []string{"push", "deploy"}, - PreRunE: bindEnv("namespace", "path", "repository"), + PreRunE: bindEnv("namespace", "path", "repository", "confirm"), RunE: runUpdate, } func runUpdate(cmd *cobra.Command, args []string) (err error) { config := newUpdateConfig() + function, err := functionWithOverrides(config.Path, config.Namespace, "") + if err != nil { + return err + } + if function.Image == "" { + return fmt.Errorf("Cannot determine the Function image. Have you built it yet?") + } + config.Prompt() builder := buildpacks.NewBuilder() builder.Verbose = config.Verbose @@ -47,11 +59,6 @@ func runUpdate(cmd *cobra.Command, args []string) (err error) { faas.WithPusher(pusher), faas.WithUpdater(updater)) - // overrieNamespace to which the Function is pinned (deployed/updated etc) - if err = overrideNamespace(config.Path, config.Namespace); err != nil { - return - } - return client.Update(config.Path) } @@ -86,3 +93,15 @@ func newUpdateConfig() updateConfig { Verbose: viper.GetBool("verbose"), // defined on root } } + +func (c updateConfig) Prompt() updateConfig { + if !interactiveTerminal() || !viper.GetBool("confirm") { + return c + } + return updateConfig{ + Namespace: prompt.ForString("Namespace", c.Namespace), + Path: prompt.ForString("Project path", c.Path), + Verbose: c.Verbose, + } + +}