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, + } + +}