Skip to content

Commit

Permalink
feat: default to no confirmation prompts for CLI commands
Browse files Browse the repository at this point in the history
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: #91
Fixes: #90
Fixes: #89
  • Loading branch information
lance committed Sep 11, 2020
1 parent 6c16e65 commit 566d8f9
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 81 deletions.
2 changes: 1 addition & 1 deletion client.go
Expand Up @@ -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
}
Expand Down
49 changes: 33 additions & 16 deletions cmd/build.go
@@ -1,6 +1,8 @@
package cmd

import (
"fmt"

"github.com/ory/viper"
"github.com/spf13/cobra"

Expand All @@ -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
Expand All @@ -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)
}
Expand All @@ -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 {
Expand All @@ -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.
}
Expand Down
49 changes: 33 additions & 16 deletions cmd/create.go
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"path/filepath"

"github.com/ory/viper"
"github.com/spf13/cobra"

"github.com/boson-project/faas"
Expand All @@ -16,14 +17,14 @@ 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")
createCmd.Flags().StringP("repository", "r", "", "Repository for built images, ex 'docker.io/myuser' or just 'myuser'. Optional if --image provided. - $FAAS_REPOSITORY")
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)
Expand All @@ -40,7 +41,7 @@ var createCmd = &cobra.Command{
Use: "create <name> [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,
}

Expand All @@ -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),
Expand All @@ -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),
Expand Down
8 changes: 3 additions & 5 deletions cmd/delete.go
Expand Up @@ -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{
Expand All @@ -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,
}

Expand All @@ -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
Expand All @@ -61,15 +60,14 @@ 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"),
}
}

// 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.
func (c deleteConfig) Prompt() deleteConfig {
if !interactiveTerminal() || c.Yes {
if !interactiveTerminal() || !viper.GetBool("confirm") {
return c
}
return deleteConfig{
Expand Down
39 changes: 23 additions & 16 deletions cmd/deploy.go
@@ -1,6 +1,8 @@
package cmd

import (
"fmt"

"github.com/ory/viper"
"github.com/spf13/cobra"

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -78,20 +85,20 @@ 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"),
}
}

// 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.
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,
}
}

0 comments on commit 566d8f9

Please sign in to comment.