From 57e12362af18f48624a9c303c070846e1645e08d Mon Sep 17 00:00:00 2001 From: Luke K Date: Fri, 6 Nov 2020 17:04:37 +0900 Subject: [PATCH] feat!: remove create cli subcommand (#180) --- client.go | 63 +++++++---------- client_test.go | 135 +++++++++++++++++-------------------- cmd/create.go | 168 +++++++++++++++++++++------------------------- cmd/init.go | 136 ------------------------------------- docs/commands.md | 26 ++----- templates_test.go | 4 +- 6 files changed, 164 insertions(+), 368 deletions(-) delete mode 100644 cmd/init.go diff --git a/client.go b/client.go index adc73d3fa..4f04f1dc8 100644 --- a/client.go +++ b/client.go @@ -18,19 +18,18 @@ const ( // Client for managing Function instances. type Client struct { - verbose bool // print verbose logs - builder Builder // Builds a runnable image from Function source - pusher Pusher // Pushes the image assocaited with a Function. - deployer Deployer // Deploys or Updates a Function - runner Runner // Runs the Function locally - remover Remover // Removes remote services - lister Lister // Lists remote services - describer Describer - dnsProvider DNSProvider // Provider of DNS services - templates string // path to extensible templates - registry string // default registry for OCI image tags - domainSearchLimit int // max recursion when deriving domain - progressListener ProgressListener // progress listener + verbose bool // print verbose logs + builder Builder // Builds a runnable image from Function source + pusher Pusher // Pushes the image assocaited with a Function. + deployer Deployer // Deploys or Updates a Function + runner Runner // Runs the Function locally + remover Remover // Removes remote services + lister Lister // Lists remote services + describer Describer + dnsProvider DNSProvider // Provider of DNS services + templates string // path to extensible templates + registry string // default registry for OCI image tags + progressListener ProgressListener // progress listener } // Builder of Function source to runnable image. @@ -219,15 +218,6 @@ func WithDNSProvider(provider DNSProvider) Option { } } -// WithDomainSearchLimit sets the maximum levels of upward recursion used when -// attempting to derive effective DNS name from root path. Ignored if DNS was -// explicitly set via WithName. -func WithDomainSearchLimit(limit int) Option { - return func(c *Client) { - c.domainSearchLimit = limit - } -} - // WithTemplates sets the location to use for extensible templates. // Extensible templates are additional templates that exist on disk and are // not built into the binary. @@ -247,20 +237,14 @@ func WithRegistry(registry string) Option { } } -// Create a Function. -// Includes Initialization, Building, and Deploying. -func (c *Client) Create(cfg Function) (err error) { - c.progressListener.SetTotal(4) +// New Function. +// Use Create, Build and Deploy independently for lower level control. +func (c *Client) New(cfg Function) (err error) { + c.progressListener.SetTotal(3) defer c.progressListener.Done() - // Initialize, writing out a template implementation and a config file. - // TODO: the Function's Initialize parameters are slightly different than - // the Initializer interface, and can thus cause confusion (one passes an - // optional name the other passes root path). This could easily cause - // confusion and thus we may want to rename Initalizer to the more specific - // task it performs: ContextTemplateWriter or similar. - c.progressListener.Increment("Initializing new Function project") - err = c.Initialize(cfg) + // Create local template + err = c.Create(cfg) if err != nil { return } @@ -290,7 +274,7 @@ func (c *Client) Create(cfg Function) (err error) { return } - c.progressListener.Complete("Create complete") + c.progressListener.Complete("Done") // TODO: use the knative client during deployment such that the actual final // route can be returned from the deployment step, passed to the DNS Router @@ -301,9 +285,9 @@ func (c *Client) Create(cfg Function) (err error) { return } -// Initialize creates a new Function project locally using the settings -// provided on a Function object. -func (c *Client) Initialize(cfg Function) (err error) { +// Create a new Function project locally using the settings provided on a +// Function object. +func (c *Client) Create(cfg Function) (err error) { // Create project root directory, if it doesn't already exist if err = os.MkdirAll(cfg.Root, 0755); err != nil { @@ -322,9 +306,8 @@ func (c *Client) Initialize(cfg Function) (err error) { return } + // Map requested fields to the newly created function. f.Image = cfg.Image - - // Set the name to that provided. f.Name = cfg.Name // Assert runtime was provided, or default. diff --git a/client_test.go b/client_test.go index c7fbc8e80..9e6146143 100644 --- a/client_test.go +++ b/client_test.go @@ -16,84 +16,93 @@ import ( // by default. See TestRegistryRequired for details. const TestRegistry = "quay.io/alice" -// TestCreate completes without error using all defaults and zero values. The base case. -func TestCreate(t *testing.T) { +// TestNew Function completes without error using defaults and zero values. +// New is the superset of creating a new fully deployed Function, and +// thus implicitly tests Create, Build and Deploy, which are exposed +// by the client API for those who prefer manual transmissions. +func TestNew(t *testing.T) { root := "testdata/example.com/testCreate" // Root from which to run the test - if err := os.MkdirAll(root, 0700); err != nil { t.Fatal(err) } defer os.RemoveAll(root) + // New Client client := faas.New(faas.WithRegistry(TestRegistry)) - if err := client.Create(faas.Function{Root: root}); err != nil { + // New Function using Client + if err := client.New(faas.Function{Root: root}); err != nil { t.Fatal(err) } } -// TestCreateWritesTemplate to disk at root. -func TestCreateWritesTemplate(t *testing.T) { - // Create the root path for the function +// TestTemplateWrites ensures a template is written. +func TestTemplateWrites(t *testing.T) { root := "testdata/example.com/testCreateWrites" if err := os.MkdirAll(root, 0744); err != nil { t.Fatal(err) } defer os.RemoveAll(root) - // Create the function at root client := faas.New(faas.WithRegistry(TestRegistry)) if err := client.Create(faas.Function{Root: root}); err != nil { t.Fatal(err) } - // Test that the config file was written + // Assert file was written if _, err := os.Stat(filepath.Join(root, faas.ConfigFile)); os.IsNotExist(err) { t.Fatalf("Initialize did not result in '%v' being written to '%v'", faas.ConfigFile, root) } } -// TestCreateInitializedAborts ensures that a directory which contains an initialized -// function does not reinitialize -func TestCreateInitializedAborts(t *testing.T) { +// TestExtantAborts ensures that a directory which contains an extant +// Function does not reinitialize +func TestExtantAborts(t *testing.T) { root := "testdata/example.com/testCreateInitializedAborts" + if err := os.MkdirAll(root, 0744); err != nil { + t.Fatal(err) + } defer os.RemoveAll(root) - client := faas.New() - if err := client.Initialize(faas.Function{Root: root}); err != nil { + // New once + client := faas.New(faas.WithRegistry(TestRegistry)) + if err := client.New(faas.Function{Root: root}); err != nil { t.Fatal(err) } - if err := client.Initialize(faas.Function{Root: root}); err == nil { + // New again should fail as already initialized + if err := client.New(faas.Function{Root: root}); err == nil { t.Fatal("error expected initilizing a path already containing an initialized Function") } } -// TestCreateNonemptyDirectoryAborts ensures that a directory which contains any visible -// files aborts. -func TestCreateNonemptyDirectoryAborts(t *testing.T) { +// TestNonemptyDirectoryAborts ensures that a directory which contains any +// visible files aborts. +func TestNonemptyDirectoryAborts(t *testing.T) { root := "testdata/example.com/testCreateNonemptyDirectoryAborts" // contains only a single visible file. if err := os.MkdirAll(root, 0744); err != nil { t.Fatal(err) } defer os.RemoveAll(root) + + // An unexpected, non-hidden file. _, err := os.Create(root + "/file.txt") if err != nil { t.Fatal(err) } - client := faas.New() - if err := client.Initialize(faas.Function{Root: root}); err == nil { + client := faas.New(faas.WithRegistry(TestRegistry)) + if err := client.New(faas.Function{Root: root}); err == nil { t.Fatal("error expected initilizing a Function in a nonempty directory") } } -// TestCreateHiddenFilesIgnored ensures that initializing in a directory that +// TestHiddenFilesIgnored ensures that initializing in a directory that // only contains hidden files does not error, protecting against the naieve // implementation of aborting initialization if any files exist, which would // break functions tracked in source control (.git), or when used in // conjunction with other tools (.envrc, etc) -func TestCreateHiddenFilesIgnored(t *testing.T) { +func TestHiddenFilesIgnored(t *testing.T) { // Create a directory for the Function root := "testdata/example.com/testCreateHiddenFilesIgnored" if err := os.MkdirAll(root, 0744); err != nil { @@ -107,16 +116,15 @@ func TestCreateHiddenFilesIgnored(t *testing.T) { t.Fatal(err) } - client := faas.New() - var err error - if err = client.Initialize(faas.Function{Root: root}); err != nil { + client := faas.New(faas.WithRegistry(TestRegistry)) + if err := client.New(faas.Function{Root: root}); err != nil { t.Fatal(err) } } -// TestCreateDefaultRuntime ensures that the default runtime is applied to new +// TestDefaultRuntime ensures that the default runtime is applied to new // Functions and persisted. -func TestCreateDefaultRuntime(t *testing.T) { +func TestDefaultRuntime(t *testing.T) { // Create a root for the new Function root := "testdata/example.com/testCreateDefaultRuntime" if err := os.MkdirAll(root, 0744); err != nil { @@ -126,7 +134,7 @@ func TestCreateDefaultRuntime(t *testing.T) { // Create a new function at root with all defaults. client := faas.New(faas.WithRegistry(TestRegistry)) - if err := client.Create(faas.Function{Root: root}); err != nil { + if err := client.New(faas.Function{Root: root}); err != nil { t.Fatal(err) } @@ -142,9 +150,9 @@ func TestCreateDefaultRuntime(t *testing.T) { } } -// TestCreateDefaultTemplate ensures that the default template is +// TestDefaultTemplate ensures that the default template is // applied when not provided. -func TestCreateDefaultTrigger(t *testing.T) { +func TestDefaultTrigger(t *testing.T) { // TODO: need to either expose accessor for introspection, or compare // the files written to those in the embedded repisotory? } @@ -172,7 +180,7 @@ func TestExtensibleTemplates(t *testing.T) { faas.WithRegistry(TestRegistry)) // Create a Function specifying a template, 'json' that only exists in the extensible set - if err := client.Create(faas.Function{Root: root, Trigger: "boson-experimental/json"}); err != nil { + if err := client.New(faas.Function{Root: root, Trigger: "boson-experimental/json"}); err != nil { t.Fatal(err) } @@ -184,29 +192,6 @@ func TestExtensibleTemplates(t *testing.T) { } } -// TestCreateUnderivableName ensures that attempting to create a new Function -// when the name is underivable (and no explicit name is provided) generates -// an error. -func TestUnderivableName(t *testing.T) { - // Create a directory for the Function - root := "testdata/example.com/testUnderivableName" - if err := os.MkdirAll(root, 0700); err != nil { - t.Fatal(err) - } - defer os.RemoveAll(root) - - // Instantiation without an explicit service name, but no derivable service - // name (because of limiting path recursion) should fail. - client := faas.New(faas.WithDomainSearchLimit(0)) - - // create a Function with a missing name, but when the name is - // underivable (in this case due to limited recursion, but would equally - // apply if run from /tmp or similar) - if err := client.Create(faas.Function{Root: root}); err == nil { - t.Fatal("did not receive error creating with underivable name") - } -} - // TestUnsupportedRuntime generates an error. func TestUnsupportedRuntime(t *testing.T) { // Create a directory for the Function @@ -216,12 +201,11 @@ func TestUnsupportedRuntime(t *testing.T) { } defer os.RemoveAll(root) - client := faas.New() + client := faas.New(faas.WithRegistry(TestRegistry)) // create a Function call witn an unsupported runtime should bubble // the error generated by the underlying initializer. - var err error - if err = client.Create(faas.Function{Root: root, Runtime: "invalid"}); err == nil { + if err := client.New(faas.Function{Root: root, Runtime: "invalid"}); err == nil { t.Fatal("unsupported runtime did not generate error") } } @@ -244,7 +228,7 @@ func TestNamed(t *testing.T) { client := faas.New(faas.WithRegistry(TestRegistry)) - if err := client.Create(faas.Function{Root: root, Name: name}); err != nil { + if err := client.New(faas.Function{Root: root, Name: name}); err != nil { t.Fatal(err) } @@ -258,7 +242,7 @@ func TestNamed(t *testing.T) { } } -// TestRegistry ensures that a registry is required, and is +// TestRegistryRequired ensures that a registry is required, and is // prepended with the DefaultRegistry if a single token. // Registry is the namespace at the container image registry. // If not prepended with the registry, it will be defaulted: @@ -277,10 +261,11 @@ func TestRegistryRequired(t *testing.T) { defer os.RemoveAll(root) client := faas.New() - if err := client.Create(faas.Function{Root: root}); err == nil { + var err error + if err = client.New(faas.Function{Root: root}); err == nil { t.Fatal("did not receive expected error creating a Function without specifying Registry") } - + fmt.Println(err) } // TestDeriveImage ensures that the full image (tag) of the resultant OCI @@ -296,7 +281,7 @@ func TestDeriveImage(t *testing.T) { // Create the function which calculates fields such as name and image. client := faas.New(faas.WithRegistry(TestRegistry)) - if err := client.Create(faas.Function{Root: root}); err != nil { + if err := client.New(faas.Function{Root: root}); err != nil { t.Fatal(err) } @@ -328,7 +313,7 @@ func TestDeriveImageDefaultRegistry(t *testing.T) { // Rather than use TestRegistry, use a single-token name and expect // the DefaultRegistry to be prepended. client := faas.New(faas.WithRegistry("alice")) - if err := client.Create(faas.Function{Root: root}); err != nil { + if err := client.New(faas.Function{Root: root}); err != nil { t.Fatal(err) } @@ -347,7 +332,7 @@ func TestDeriveImageDefaultRegistry(t *testing.T) { // TestDelegation ensures that Create invokes each of the individual // subcomponents via delegation through Build, Push and // Deploy (and confirms expected fields calculated). -func TestCreateDelegates(t *testing.T) { +func TestNewDelegates(t *testing.T) { var ( root = "testdata/example.com/testCreateDelegates" // .. in which to initialize expectedName = "testCreateDelegates" // expected to be derived @@ -409,7 +394,7 @@ func TestCreateDelegates(t *testing.T) { // Invoke the creation, triggering the Function delegates, and // perform follow-up assertions that the Functions were indeed invoked. - if err := client.Create(faas.Function{Root: root}); err != nil { + if err := client.New(faas.Function{Root: root}); err != nil { t.Fatal(err) } @@ -437,7 +422,7 @@ func TestRun(t *testing.T) { // Create a client with the mock runner and the new test Function runner := mock.NewRunner() client := faas.New(faas.WithRegistry(TestRegistry), faas.WithRunner(runner)) - if err := client.Create(faas.Function{Root: root}); err != nil { + if err := client.New(faas.Function{Root: root}); err != nil { t.Fatal(err) } @@ -485,7 +470,7 @@ func TestUpdate(t *testing.T) { faas.WithDeployer(deployer)) // create the new Function which will be updated - if err := client.Create(faas.Function{Root: root}); err != nil { + if err := client.New(faas.Function{Root: root}); err != nil { t.Fatal(err) } @@ -556,7 +541,7 @@ func TestRemoveByPath(t *testing.T) { faas.WithRegistry(TestRegistry), faas.WithRemover(remover)) - if err := client.Create(faas.Function{Root: root}); err != nil { + if err := client.New(faas.Function{Root: root}); err != nil { t.Fatal(err) } @@ -674,13 +659,8 @@ func TestList(t *testing.T) { func TestListOutsideRoot(t *testing.T) { lister := mock.NewLister() - // Instantiate in the current working directory, with no name, and explicitly - // disallowing name path inferrence by limiting recursion. This emulates - // running the client (and subsequently list) from some arbitrary location - // without a derivable funciton context. - client := faas.New( - faas.WithDomainSearchLimit(0), - faas.WithLister(lister)) + // Instantiate in the current working directory, with no name. + client := faas.New(faas.WithLister(lister)) if _, err := client.List(); err != nil { t.Fatal(err) @@ -690,3 +670,8 @@ func TestListOutsideRoot(t *testing.T) { t.Fatal("list did not invoke lister implementation") } } + +// TODO: The tests which confirm an error is generated do not currently test +// that the expected error is received; just that any error is generated. +// This should be replaced with typed errors or at a minimum code prefixes +// on the string to avoid tests passing for unrelated errors. diff --git a/cmd/create.go b/cmd/create.go index 717d8621b..685f691ca 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -8,147 +8,129 @@ import ( "github.com/spf13/cobra" "github.com/boson-project/faas" - "github.com/boson-project/faas/buildpacks" - "github.com/boson-project/faas/docker" - "github.com/boson-project/faas/knative" - "github.com/boson-project/faas/progress" "github.com/boson-project/faas/prompt" ) 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 --registry) - $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("registry", "r", "", "Registry for built images, ex 'docker.io/myuser' or just 'myuser'. Optional if --image provided. - $FAAS_REGISTRY") - createCmd.Flags().StringP("runtime", "l", faas.DefaultRuntime, "Function runtime language/framework. Default runtime is 'go'. Available runtimes: 'node', 'quarkus' and 'go'. - $FAAS_RUNTIME") + createCmd.Flags().StringP("runtime", "l", faas.DefaultRuntime, "Function runtime language/framework. Default runtime is 'node'. Available runtimes: 'node', 'quarkus' and 'go'. - $FAAS_RUNTIME") createCmd.Flags().StringP("templates", "", filepath.Join(configPath(), "templates"), "Extensible templates path. - $FAAS_TEMPLATES") createCmd.Flags().StringP("trigger", "t", faas.DefaultTrigger, "Function trigger. Default trigger is 'http'. Available triggers: 'http' and 'events' - $FAAS_TRIGGER") - var err error - err = createCmd.RegisterFlagCompletionFunc("image", CompleteRegistryList) - if err != nil { - fmt.Println("Error while calling RegisterFlagCompletionFunc: ", err) - } - err = createCmd.RegisterFlagCompletionFunc("runtime", CompleteRuntimeList) - if err != nil { + if err := createCmd.RegisterFlagCompletionFunc("runtime", CompleteRuntimeList); err != nil { fmt.Println("Error while calling RegisterFlagCompletionFunc: ", err) } } var createCmd = &cobra.Command{ Use: "create ", - Short: "Create a new Function, including initialization of local files and deployment", - Long: `Create a new Function, including initialization of local files and deployment + Short: "Create a new Function project", + Long: `Create a new Function project Creates a new Function project at . If does not exist, it is -created. The Function name is the name of the leaf directory at . After -creating the project, a container image is created and is deployed. This -command wraps "init", "build" and "deploy" all up into one command. - -The runtime, trigger, image name, image registry, and namespace may all be -specified as flags on the command line, and will subsequently be the default -values when an image is built or a Function is deployed. If the image name and -image registry are both unspecified, the user will be prompted for an image -registry, and the image name can be inferred from that plus the function -name. The function name, namespace and image name are all persisted in the -project configuration file faas.yaml. +created. The Function name is the name of the leaf directory at . + +A project for a Node.js Function will be created by default. Specify an +alternate runtime with the --language or -l flag. Available alternates are +"quarkus" and "go". + +Use the --trigger or -t flag to specify the function invocation context. +By default, the trigger is "http". To create a Function for CloudEvents, use +"events". `, - SuggestFor: []string{"cerate", "new"}, - PreRunE: bindEnv("image", "namespace", "registry", "runtime", "templates", "trigger", "confirm"), + SuggestFor: []string{"inti", "new"}, + PreRunE: bindEnv("runtime", "templates", "trigger", "confirm"), RunE: runCreate, + // TODO: autocomplate Functions for runtime and trigger. } -func runCreate(cmd *cobra.Command, args []string) (err error) { - config := newCreateConfig(cmd, args).Prompt() +func runCreate(cmd *cobra.Command, args []string) error { + config := newCreateConfig(args).Prompt() function := faas.Function{ - Name: config.initConfig.Name, - Root: config.initConfig.Path, - Runtime: config.initConfig.Runtime, + Name: config.Name, + Root: config.Path, + Runtime: config.Runtime, Trigger: config.Trigger, - Image: config.Image, } - if function.Image == "" && config.Registry == "" { - fmt.Print("A registry for Function images is required. For example, 'docker.io/tigerteam'.\n\n") - config.Registry = prompt.ForString("Registry for Function images", "") - if config.Registry == "" { - return fmt.Errorf("Unable to determine Function image name") - } - } + client := faas.New( + faas.WithTemplates(config.Templates), + faas.WithVerbose(config.Verbose)) - // Defined in root command - verbose := viper.GetBool("verbose") + return client.Create(function) +} - builder := buildpacks.NewBuilder() - builder.Verbose = verbose +type createConfig struct { + // Name of the Function. + Name string - pusher := docker.NewPusher() - pusher.Verbose = verbose + // Absolute path to Function on disk. + Path string - deployer, err := knative.NewDeployer(config.Namespace) - if err != nil { - return - } - deployer.Verbose = verbose + // Runtime language/framework. + Runtime string - listener := progress.New() - listener.Verbose = verbose + // Templates is an optional path that, if it exists, will be used as a source + // for additional templates not included in the binary. If not provided + // explicitly as a flag (--templates) or env (FAAS_TEMPLATES), the default + // location is $XDG_CONFIG_HOME/templates ($HOME/.config/faas/templates) + Templates string - client := faas.New( - faas.WithVerbose(verbose), - faas.WithTemplates(config.Templates), - faas.WithRegistry(config.Registry), // for deriving image name when --image not provided explicitly. - faas.WithBuilder(builder), - faas.WithPusher(pusher), - faas.WithDeployer(deployer), - faas.WithProgressListener(listener)) + // Trigger is the form of the resultant Function, i.e. the Function signature + // and contextually avaialable resources. For example 'http' for a Function + // expected to be invoked via straight HTTP requests, or 'events' for a + // Function which will be invoked with CloudEvents. + Trigger string - return client.Create(function) -} + // Verbose output + Verbose bool -type createConfig struct { - initConfig - deployConfig - // Note that ambiguous references set to assume .initConfig + // Confirm: confirm values arrived upon from environment plus flags plus defaults, + // with interactive prompting (only applicable when attached to a TTY). + Confirm bool } -func newCreateConfig(cmd *cobra.Command, args []string) createConfig { +// newCreateConfig returns a config populated from the current execution context +// (args, flags and environment variables) +func newCreateConfig(args []string) createConfig { + var path string + if len(args) > 0 { + path = args[0] // If explicitly provided, use. + } + derivedName, derivedPath := deriveNameAndAbsolutePathFromPath(path) return createConfig{ - initConfig: newInitConfig(args), - deployConfig: newDeployConfig(cmd), + Name: derivedName, + Path: derivedPath, + Runtime: viper.GetString("runtime"), + Templates: viper.GetString("templates"), + Trigger: viper.GetString("trigger"), + Confirm: viper.GetBool("confirm"), + Verbose: viper.GetBool("verbose"), } } -// Prompt the user with value of config members, allowing for interactive changes. -// Skipped if not in an interactive terminal (non-TTY), or if --confirm (agree to -// all prompts) was not explicitly set. +// Prompt the user with value of config members, allowing for interaractive changes. +// Skipped if not in an interactive terminal (non-TTY), or if --confirm false (agree to +// all prompts) was set (default). func (c createConfig) Prompt() createConfig { - if !interactiveTerminal() || !c.initConfig.Confirm { + if !interactiveTerminal() || !c.Confirm { // Just print the basics if not confirming - fmt.Printf("Project path: %v\n", c.initConfig.Path) - fmt.Printf("Function name: %v\n", c.initConfig.Name) + fmt.Printf("Project path: %v\n", c.Path) + fmt.Printf("Function name: %v\n", c.Name) fmt.Printf("Runtime: %v\n", c.Runtime) fmt.Printf("Trigger: %v\n", c.Trigger) return c } - derivedName, derivedPath := deriveNameAndAbsolutePathFromPath(prompt.ForString("Project path", c.initConfig.Path, prompt.WithRequired(true))) + derivedName, derivedPath := deriveNameAndAbsolutePathFromPath(prompt.ForString("Project path", c.Path, prompt.WithRequired(true))) return createConfig{ - initConfig: initConfig{ - Name: derivedName, - Path: derivedPath, - Runtime: prompt.ForString("Runtime", c.Runtime), - Trigger: prompt.ForString("Trigger", c.Trigger), - // Templates intentionally omitted from prompt for being an edge case. - }, - deployConfig: deployConfig{ - buildConfig: buildConfig{ - Registry: prompt.ForString("Registry for Function images", c.buildConfig.Registry), - }, - Namespace: prompt.ForString("Override default deploy namespace", c.Namespace), - }, + Name: derivedName, + Path: derivedPath, + 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/init.go b/cmd/init.go deleted file mode 100644 index e00bffa79..000000000 --- a/cmd/init.go +++ /dev/null @@ -1,136 +0,0 @@ -package cmd - -import ( - "fmt" - "path/filepath" - - "github.com/ory/viper" - "github.com/spf13/cobra" - - "github.com/boson-project/faas" - "github.com/boson-project/faas/prompt" -) - -func init() { - root.AddCommand(initCmd) - initCmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all configuration options - $FAAS_CONFIRM") - initCmd.Flags().StringP("runtime", "l", faas.DefaultRuntime, "Function runtime language/framework. Default runtime is 'node'. Available runtimes: 'node', 'quarkus' and 'go'. - $FAAS_RUNTIME") - initCmd.Flags().StringP("templates", "", filepath.Join(configPath(), "templates"), "Extensible templates path. - $FAAS_TEMPLATES") - initCmd.Flags().StringP("trigger", "t", faas.DefaultTrigger, "Function trigger. Default trigger is 'http'. Available triggers: 'http' and 'events' - $FAAS_TRIGGER") - - if err := initCmd.RegisterFlagCompletionFunc("runtime", CompleteRuntimeList); err != nil { - fmt.Println("Error while calling RegisterFlagCompletionFunc: ", err) - } -} - -var initCmd = &cobra.Command{ - Use: "init ", - Short: "Initialize a new Function project", - Long: `Initializes a new Function project - -Creates a new Function project at . If does not exist, it is -created. The Function name is the name of the leaf directory at . - -A project for a Node.js Function will be created by default. Specify an -alternate runtime with the --language or -l flag. Available alternates are -"quarkus" and "go". - -Use the --trigger or -t flag to specify the function invocation context. -By default, the trigger is "http". To create a Function for CloudEvents, use -"events". -`, - SuggestFor: []string{"inti", "new"}, - PreRunE: bindEnv("runtime", "templates", "trigger", "confirm"), - RunE: runInit, - // TODO: autocomplate Functions for runtime and trigger. -} - -func runInit(cmd *cobra.Command, args []string) error { - config := newInitConfig(args).Prompt() - - function := faas.Function{ - Name: config.Name, - Root: config.Path, - Runtime: config.Runtime, - Trigger: config.Trigger, - } - - client := faas.New( - faas.WithTemplates(config.Templates), - faas.WithVerbose(config.Verbose)) - - return client.Initialize(function) -} - -type initConfig struct { - // Name of the Function. - Name string - - // Absolute path to Function on disk. - Path string - - // Runtime language/framework. - Runtime string - - // Templates is an optional path that, if it exists, will be used as a source - // for additional templates not included in the binary. If not provided - // explicitly as a flag (--templates) or env (FAAS_TEMPLATES), the default - // location is $XDG_CONFIG_HOME/templates ($HOME/.config/faas/templates) - Templates string - - // Trigger is the form of the resultant Function, i.e. the Function signature - // and contextually avaialable resources. For example 'http' for a Function - // expected to be invoked via straight HTTP requests, or 'events' for a - // Function which will be invoked with CloudEvents. - Trigger string - - // Verbose output - Verbose 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 -// (args, flags and environment variables) -func newInitConfig(args []string) initConfig { - var path string - if len(args) > 0 { - path = args[0] // If explicitly provided, use. - } - - derivedName, derivedPath := deriveNameAndAbsolutePathFromPath(path) - return initConfig{ - Name: derivedName, - Path: derivedPath, - Runtime: viper.GetString("runtime"), - Templates: viper.GetString("templates"), - Trigger: viper.GetString("trigger"), - Confirm: viper.GetBool("confirm"), - Verbose: viper.GetBool("verbose"), - } -} - -// Prompt the user with value of config members, allowing for interaractive changes. -// 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.Confirm { - // Just print the basics if not confirming - fmt.Printf("Project path: %v\n", c.Path) - fmt.Printf("Function name: %v\n", c.Name) - fmt.Printf("Runtime: %v\n", c.Runtime) - fmt.Printf("Trigger: %v\n", c.Trigger) - return c - } - - derivedName, derivedPath := deriveNameAndAbsolutePathFromPath(prompt.ForString("Project path", c.Path, prompt.WithRequired(true))) - return initConfig{ - Name: derivedName, - Path: derivedPath, - 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/docs/commands.md b/docs/commands.md index 696d16cad..26a6b64f8 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1,19 +1,19 @@ # CLI Commands -## `init` +## `create` Creates a new Function project at _`path`_. If _`path`_ is unspecified, assumes the current directory. If _`path`_ does not exist, it will be created. The function name is the name of the leaf directory at path. The user can specify the runtime and trigger with flags. Similar `kn` command: none. ```console -faas init [-l -t ] +faas create [-l -t ] ``` When run as a `kn` plugin. ```console -kn faas init [-l -t ] +kn faas create [-l -t ] ``` ## `build` @@ -54,7 +54,7 @@ kn faas run [-p ] Builds and deploys the Function project in the current directory. The user may specify a path to the project directory using the `--path` or `-p` flag. Reads the `faas.yaml` configuration file to determine the image name. An image and registry may be specified on the command line using the `--image` or `-i` and `--registry` or `-r` flag. -Derives the service name from the project name. There is no mechanism by which the user can specify the service name. The user must have already initialized the function using `faas init` or they will encounter an error. +Derives the service name from the project name. There is no mechanism by which the user can specify the service name. The user must have already initialized the function using `faas create` or they will encounter an error. If the Function is already deployed, it is updated with a new container image that is pushed to a container image registry, and the Knative Service is updated. @@ -105,24 +105,6 @@ When run as a `kn` plugin. kn faas list [-n -p ] ``` -## `create` - -Creates a new Function project at _`path`_. If _`path`_ does not exist, it is created. The function name is the name of the leaf directory at _`path`_. After creating the project, it builds a container image and deploys it. This command wraps `init`, `build` and `deploy` all up into one command. - -The user may specify the runtime, trigger, image name, image registry, and namespace as flags on the command line. If the image name and image registry are both unspecified, the user will be prompted for a registry name, and the image name can be inferred from that plus the function name. The function name, namespace and image name are all persisted in the project configuration file `faas.yaml`. - -Similar `kn` command: none. - -```console -faas create -r -l -t -i -n -``` - -When run as a `kn` plugin. - -```console -kn faas create -r -l -t -i -n -``` - ## `delete` Removes a deployed function from the cluster. The user may specify a function by name, path or if neither of those are provided, the current directory will be searched for a `faas.yaml` configuration file to determine the function to be removed. The namespace defaults to the value in `faas.yaml` or the namespace currently active in the user's Kubernetes configuration. The namespace may be specified on the command line, and if so this will overwrite the value in `faas.yaml`. diff --git a/templates_test.go b/templates_test.go index 10af2fa4f..cca95b9a0 100644 --- a/templates_test.go +++ b/templates_test.go @@ -19,7 +19,7 @@ func TestTemplatesEmbeddedFileMode(t *testing.T) { client := New() function := Function{Root: path, Runtime: "quarkus", Trigger: "events"} - if err := client.Initialize(function); err != nil { + if err := client.Create(function); err != nil { t.Fatal(err) } @@ -54,7 +54,7 @@ func TestTemplatesExtensibleFileMode(t *testing.T) { client := New(WithTemplates(templates)) function := Function{Root: path, Runtime: "quarkus", Trigger: template} - if err := client.Initialize(function); err != nil { + if err := client.Create(function); err != nil { t.Fatal(err) }