diff --git a/cmd/common/common.go b/cmd/common/common.go new file mode 100644 index 0000000000..3d7f621500 --- /dev/null +++ b/cmd/common/common.go @@ -0,0 +1,47 @@ +package common + +import ( + "fmt" + + fn "knative.dev/func/pkg/functions" +) + +// DefaultLoaderSaver implements FunctionLoaderSaver composite interface +var DefaultLoaderSaver FunctionLoaderSaver = standardLoaderSaver{} + +// FunctionLoader loads a function from a filesystem path. +type FunctionLoader interface { + Load(path string) (fn.Function, error) +} + +// FunctionSaver persists a function to storage. +type FunctionSaver interface { + Save(f fn.Function) error +} + +// FunctionLoaderSaver combines loading and saving capabilities for functions. +type FunctionLoaderSaver interface { + FunctionLoader + FunctionSaver +} + +type standardLoaderSaver struct{} + +// Load creates and validates a function from the given filesystem path. +func (s standardLoaderSaver) Load(path string) (fn.Function, error) { + f, err := fn.NewFunction(path) + if err != nil { + return fn.Function{}, fmt.Errorf("failed to create new function (path: %q): %w", path, err) + } + + if !f.Initialized() { + return fn.Function{}, fn.NewErrNotInitialized(f.Root) + } + + return f, nil +} + +// Save writes the function configuration to disk. +func (s standardLoaderSaver) Save(f fn.Function) error { + return f.Write() +} diff --git a/cmd/common/common_test.go b/cmd/common/common_test.go new file mode 100644 index 0000000000..78a7ea3531 --- /dev/null +++ b/cmd/common/common_test.go @@ -0,0 +1,54 @@ +package common_test + +import ( + "testing" + + "gotest.tools/v3/assert" + "knative.dev/func/cmd/common" + cmdTest "knative.dev/func/cmd/testing" + fn "knative.dev/func/pkg/functions" + fnTest "knative.dev/func/pkg/testing" +) + +func TestDefaultLoaderSaver_SuccessfulLoad(t *testing.T) { + existingFunc := cmdTest.CreateFuncInTempDir(t, "ls-func") + + actualFunc, err := common.DefaultLoaderSaver.Load(existingFunc.Root) + + assert.NilError(t, err) + assert.Equal(t, existingFunc.Name, actualFunc.Name) +} + +func TestDefaultLoaderSaver_GenericFuncCreateError_WhenFuncPathInvalid(t *testing.T) { + _, err := common.DefaultLoaderSaver.Load("/non-existing-path") + + assert.ErrorContains(t, err, "failed to create new function") +} + +func TestDefaultLoaderSaver_IsNotInitializedError_WhenNoFuncAtPath(t *testing.T) { + expectedErrMsg := fn.NewErrNotInitialized(fnTest.Cwd()).Error() + + _, err := common.DefaultLoaderSaver.Load(fnTest.Cwd()) + + assert.Error(t, err, expectedErrMsg) +} + +func TestDefaultLoaderSaver_SuccessfulSave(t *testing.T) { + existingFunc := cmdTest.CreateFuncInTempDir(t, "") + name := "environment" + value := "test" + existingFunc.Run.Envs.Add(name, value) + + saveErr := common.DefaultLoaderSaver.Save(existingFunc) + actualFunc, loadErr := common.DefaultLoaderSaver.Load(existingFunc.Root) + + assert.NilError(t, saveErr) + assert.NilError(t, loadErr) + assert.Equal(t, actualFunc.Run.Envs.Slice()[0], "environment=test") +} + +func TestDefaultLoaderSaver_ForwardsSaveError(t *testing.T) { + err := common.DefaultLoaderSaver.Save(fn.Function{}) + + assert.Error(t, err, "function root path is required") +} diff --git a/cmd/config.go b/cmd/config.go index ad5910403e..2dadccc235 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -7,43 +7,12 @@ import ( "github.com/ory/viper" "github.com/spf13/cobra" + "knative.dev/func/cmd/common" "knative.dev/func/pkg/config" fn "knative.dev/func/pkg/functions" ) -type functionLoader interface { - Load(path string) (fn.Function, error) -} - -type functionSaver interface { - Save(f fn.Function) error -} - -type functionLoaderSaver interface { - functionLoader - functionSaver -} - -type standardLoaderSaver struct{} - -func (s standardLoaderSaver) Load(path string) (fn.Function, error) { - f, err := fn.NewFunction(path) - if err != nil { - return fn.Function{}, fmt.Errorf("failed to create new function (path: %q): %w", path, err) - } - if !f.Initialized() { - return fn.Function{}, fn.NewErrNotInitialized(f.Root) - } - return f, nil -} - -func (s standardLoaderSaver) Save(f fn.Function) error { - return f.Write() -} - -var defaultLoaderSaver standardLoaderSaver - -func NewConfigCmd(loadSaver functionLoaderSaver, newClient ClientFactory) *cobra.Command { +func NewConfigCmd(loadSaver common.FunctionLoaderSaver, newClient ClientFactory) *cobra.Command { cmd := &cobra.Command{ Use: "config", Short: "Configure a function", @@ -75,7 +44,7 @@ or from the directory specified with --path. func runConfigCmd(cmd *cobra.Command, args []string) (err error) { - function, err := initConfigCommand(defaultLoaderSaver) + function, err := initConfigCommand(common.DefaultLoaderSaver) if err != nil { return } @@ -117,7 +86,7 @@ func runConfigCmd(cmd *cobra.Command, args []string) (err error) { case "Environment variables": err = runAddEnvsPrompt(cmd.Context(), function) case "Labels": - err = runAddLabelsPrompt(cmd.Context(), function, defaultLoaderSaver) + err = runAddLabelsPrompt(cmd.Context(), function, common.DefaultLoaderSaver) case "Git": err = runConfigGitSetCmd(cmd, NewClient) } @@ -128,7 +97,7 @@ func runConfigCmd(cmd *cobra.Command, args []string) (err error) { case "Environment variables": err = runRemoveEnvsPrompt(function) case "Labels": - err = runRemoveLabelsPrompt(function, defaultLoaderSaver) + err = runRemoveLabelsPrompt(function, common.DefaultLoaderSaver) case "Git": err = runConfigGitRemoveCmd(cmd, NewClient) } @@ -163,7 +132,7 @@ func newConfigCmdConfig() configCmdConfig { } } -func initConfigCommand(loader functionLoader) (fn.Function, error) { +func initConfigCommand(loader common.FunctionLoader) (fn.Function, error) { config := newConfigCmdConfig() function, err := loader.Load(config.Path) diff --git a/cmd/config_envs.go b/cmd/config_envs.go index 3363917219..4d03a0f347 100644 --- a/cmd/config_envs.go +++ b/cmd/config_envs.go @@ -13,13 +13,14 @@ import ( "github.com/ory/viper" "github.com/spf13/cobra" + "knative.dev/func/cmd/common" "knative.dev/func/pkg/config" fn "knative.dev/func/pkg/functions" "knative.dev/func/pkg/k8s" "knative.dev/func/pkg/utils" ) -func NewConfigEnvsCmd(loadSaver functionLoaderSaver) *cobra.Command { +func NewConfigEnvsCmd(loadSaver common.FunctionLoaderSaver) *cobra.Command { cmd := &cobra.Command{ Use: "envs", Short: "List and manage configured environment variable for a function", @@ -64,7 +65,7 @@ the current directory or from the directory specified with --path. return cmd } -func NewConfigEnvsAddCmd(loadSaver functionLoaderSaver) *cobra.Command { +func NewConfigEnvsAddCmd(loadSaver common.FunctionLoaderSaver) *cobra.Command { cmd := &cobra.Command{ Use: "add", Short: "Add environment variable to the function configuration", @@ -139,7 +140,7 @@ set environment variable from a secret return cmd } -func NewConfigEnvsRemoveCmd(loadSaver functionLoaderSaver) *cobra.Command { +func NewConfigEnvsRemoveCmd(loadSaver common.FunctionLoaderSaver) *cobra.Command { cmd := &cobra.Command{ Use: "remove", Short: "Remove environment variable from the function configuration", diff --git a/cmd/config_labels.go b/cmd/config_labels.go index 269fa26e0f..389a8a0735 100644 --- a/cmd/config_labels.go +++ b/cmd/config_labels.go @@ -11,12 +11,13 @@ import ( "github.com/ory/viper" "github.com/spf13/cobra" + "knative.dev/func/cmd/common" "knative.dev/func/pkg/config" fn "knative.dev/func/pkg/functions" "knative.dev/func/pkg/utils" ) -func NewConfigLabelsCmd(loaderSaver functionLoaderSaver) *cobra.Command { +func NewConfigLabelsCmd(loaderSaver common.FunctionLoaderSaver) *cobra.Command { var configLabelsCmd = &cobra.Command{ Use: "labels", Short: "List and manage configured labels for a function", @@ -184,7 +185,7 @@ func listLabels(f fn.Function, w io.Writer, outputFormat Format) error { } } -func runAddLabelsPrompt(_ context.Context, f fn.Function, saver functionSaver) (err error) { +func runAddLabelsPrompt(_ context.Context, f fn.Function, saver common.FunctionSaver) (err error) { insertToIndex := 0 @@ -317,7 +318,7 @@ func runAddLabelsPrompt(_ context.Context, f fn.Function, saver functionSaver) ( return } -func runRemoveLabelsPrompt(f fn.Function, saver functionSaver) (err error) { +func runRemoveLabelsPrompt(f fn.Function, saver common.FunctionSaver) (err error) { if len(f.Deploy.Labels) == 0 { fmt.Println("There aren't any configured labels") return diff --git a/cmd/config_volumes.go b/cmd/config_volumes.go index 6a8a9ce514..c0b3926351 100644 --- a/cmd/config_volumes.go +++ b/cmd/config_volumes.go @@ -8,6 +8,7 @@ import ( "github.com/AlecAivazis/survey/v2" "github.com/spf13/cobra" + "knative.dev/func/cmd/common" "knative.dev/func/pkg/config" fn "knative.dev/func/pkg/functions" "knative.dev/func/pkg/k8s" @@ -26,7 +27,7 @@ the current directory or from the directory specified with --path. SuggestFor: []string{"vol", "volums", "vols"}, PreRunE: bindEnv("path", "verbose"), RunE: func(cmd *cobra.Command, args []string) (err error) { - function, err := initConfigCommand(defaultLoaderSaver) + function, err := initConfigCommand(common.DefaultLoaderSaver) if err != nil { return } @@ -85,7 +86,7 @@ For non-interactive usage, use flags to specify the volume type and configuratio SuggestFor: []string{"ad", "create", "insert", "append"}, PreRunE: bindEnv("path", "verbose", "type", "source", "mount-path", "read-only", "size", "medium"), RunE: func(cmd *cobra.Command, args []string) (err error) { - function, err := initConfigCommand(defaultLoaderSaver) + function, err := initConfigCommand(common.DefaultLoaderSaver) if err != nil { return } @@ -129,7 +130,7 @@ For non-interactive usage, use the --mount-path flag to specify which volume to SuggestFor: []string{"del", "delete", "rmeove"}, PreRunE: bindEnv("path", "verbose", "mount-path"), RunE: func(cmd *cobra.Command, args []string) (err error) { - function, err := initConfigCommand(defaultLoaderSaver) + function, err := initConfigCommand(common.DefaultLoaderSaver) if err != nil { return } diff --git a/cmd/root.go b/cmd/root.go index f476f8b8d9..225cccc228 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -15,6 +15,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "knative.dev/client/pkg/util" + "knative.dev/func/cmd/common" "knative.dev/func/cmd/templates" "knative.dev/func/pkg/config" fn "knative.dev/func/pkg/functions" @@ -100,7 +101,7 @@ Learn more about Knative at: https://knative.dev`, cfg.Name), { Header: "System Commands:", Commands: []*cobra.Command{ - NewConfigCmd(defaultLoaderSaver, newClient), + NewConfigCmd(common.DefaultLoaderSaver, newClient), NewLanguagesCmd(newClient), NewTemplatesCmd(newClient), NewRepositoryCmd(newClient), @@ -315,7 +316,7 @@ func mergeEnvs(envs []fn.Env, envToUpdate *util.OrderedMap, envToRemove []string return envs, counter, nil } -// addConfirmFlag ensures common text/wording when the --path flag is used +// addConfirmFlag ensures common text/wording when the --confirm flag is used func addConfirmFlag(cmd *cobra.Command, dflt bool) { cmd.Flags().BoolP("confirm", "c", dflt, "Prompt to confirm options interactively ($FUNC_CONFIRM)") } @@ -325,7 +326,7 @@ func addPathFlag(cmd *cobra.Command) { cmd.Flags().StringP("path", "p", "", "Path to the function. Default is current directory ($FUNC_PATH)") } -// addVerboseFlag ensures common text/wording when the --path flag is used +// addVerboseFlag ensures common text/wording when the --verbose flag is used func addVerboseFlag(cmd *cobra.Command, dflt bool) { cmd.Flags().BoolP("verbose", "v", dflt, "Print verbose logs ($FUNC_VERBOSE)") } diff --git a/cmd/testing/factory.go b/cmd/testing/factory.go new file mode 100644 index 0000000000..ae11cd0561 --- /dev/null +++ b/cmd/testing/factory.go @@ -0,0 +1,29 @@ +package testing + +import ( + "testing" + + "gotest.tools/v3/assert" + fn "knative.dev/func/pkg/functions" + fnTest "knative.dev/func/pkg/testing" +) + +// CreateFuncInTempDir creates and initializes a Go function in a temporary +// directory for testing. +func CreateFuncInTempDir(t *testing.T, fnName string) fn.Function { + t.Helper() + + var name string + if fnName == "" { + name = "go-func" + } + + result, err := fn.New().Init(fn.Function{ + Name: name, + Runtime: "go", + Root: fnTest.FromTempDirectory(t), + }) + assert.NilError(t, err) + + return result +}