diff --git a/cmd/ci/config.go b/cmd/ci/config.go new file mode 100644 index 0000000000..568ee80cab --- /dev/null +++ b/cmd/ci/config.go @@ -0,0 +1,48 @@ +package ci + +import ( + "path/filepath" + + "github.com/ory/viper" +) + +const ( + ConfigCIFeatureFlag = "FUNC_ENABLE_CI_CONFIG" + + DefaultGithubWorkflowDir = ".github/workflows" + DefaultGithubWorkflowFilename = "func-deploy.yaml" + + PathOption = "path" + + WorkflowNameOption = "workflow-name" + DefaultWorkflowName = "Func Deploy" +) + +// CIConfig readonly CI configuration +type CIConfig struct { + githubWorkflowDir, + githubWorkflowFilename, + path, + workflowName string +} + +func NewCiGithubConfig() CIConfig { + return CIConfig{ + githubWorkflowDir: DefaultGithubWorkflowDir, + githubWorkflowFilename: DefaultGithubWorkflowFilename, + path: viper.GetString(PathOption), + workflowName: viper.GetString(WorkflowNameOption), + } +} + +func (cc *CIConfig) FnGithubWorkflowDir(fnRoot string) string { + return filepath.Join(fnRoot, cc.githubWorkflowDir) +} + +func (cc *CIConfig) FnGithubWorkflowFilepath(fnRoot string) string { + return filepath.Join(cc.FnGithubWorkflowDir(fnRoot), cc.githubWorkflowFilename) +} + +func (cc *CIConfig) Path() string { + return cc.path +} diff --git a/cmd/config.go b/cmd/config.go index 2dadccc235..15a406c444 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -38,6 +38,7 @@ or from the directory specified with --path. cmd.AddCommand(NewConfigLabelsCmd(loadSaver)) cmd.AddCommand(NewConfigEnvsCmd(loadSaver)) cmd.AddCommand(NewConfigVolumesCmd()) + cmd.AddCommand(NewConfigCICmd(loadSaver)) return cmd } diff --git a/cmd/config_ci.go b/cmd/config_ci.go new file mode 100644 index 0000000000..2f7135bdb0 --- /dev/null +++ b/cmd/config_ci.go @@ -0,0 +1,56 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "knative.dev/func/cmd/ci" + "knative.dev/func/cmd/common" +) + +func NewConfigCICmd(loaderSaver common.FunctionLoaderSaver) *cobra.Command { + cmd := &cobra.Command{ + Use: "ci", + Short: "Generate a Github Workflow for function deployment", + PreRunE: bindEnv( + ci.PathOption, + ci.WorkflowNameOption, + ), + RunE: func(cmd *cobra.Command, args []string) (err error) { + return runConfigCIGithub(cmd, loaderSaver) + }, + } + + addPathFlag(cmd) + cmd.Flags().String( + ci.WorkflowNameOption, + ci.DefaultWorkflowName, + "Use a custom workflow name", + ) + + return cmd +} + +func runConfigCIGithub( + cmd *cobra.Command, + fnLoaderSaver common.FunctionLoaderSaver, +) error { + if os.Getenv(ci.ConfigCIFeatureFlag) != "true" { + return fmt.Errorf("set %s to 'true' to use this feature", ci.ConfigCIFeatureFlag) + } + + cfg := ci.NewCiGithubConfig() + + f, err := fnLoaderSaver.Load(cfg.Path()) + if err != nil { + return err + } + + fmt.Fprintln(cmd.OutOrStdout(), "--------------------------- Function Github Workflow Generation ---------------------------") + fmt.Fprintf(cmd.OutOrStdout(), "Func name: %s\n", f.Name) + fmt.Fprintf(cmd.OutOrStdout(), "Func runtime: %s\n", f.Runtime) + + return fmt.Errorf("not implemented") +} diff --git a/cmd/config_ci_test.go b/cmd/config_ci_test.go new file mode 100644 index 0000000000..360c4c5800 --- /dev/null +++ b/cmd/config_ci_test.go @@ -0,0 +1,126 @@ +package cmd_test + +import ( + "fmt" + "testing" + + "gotest.tools/v3/assert" + fnCmd "knative.dev/func/cmd" + "knative.dev/func/cmd/ci" + "knative.dev/func/cmd/common" + cmdTest "knative.dev/func/cmd/testing" + fn "knative.dev/func/pkg/functions" + fnTest "knative.dev/func/pkg/testing" +) + +func TestNewConfigCICmd_RequiresFeatureFlag(t *testing.T) { + expectedErrMsg := fmt.Sprintf("set %s to 'true' to use this feature", ci.ConfigCIFeatureFlag) + + result := runConfigCiCmd(t, opts{enableFeature: false}) + + assert.Error(t, result.executeErr, expectedErrMsg) +} + +func TestNewConfigCICmd_CISubcommandExist(t *testing.T) { + // leave 'ci' to make this test explicitly use this subcommand + opts := opts{withFuncInTempDir: true, enableFeature: true, args: []string{"ci"}} + + result := runConfigCiCmd(t, opts) + + assert.Error(t, result.executeErr, "not implemented") +} + +func TestNewConfigCICmd_FailsWhenNotInitialized(t *testing.T) { + expectedErrMsg := fn.NewErrNotInitialized(fnTest.Cwd()).Error() + + result := runConfigCiCmd(t, opts{withFuncInTempDir: false, enableFeature: true}) + + assert.Error(t, result.executeErr, expectedErrMsg) +} + +func TestNewConfigCICmd_SuccessWhenInitialized(t *testing.T) { + result := runConfigCiCmd(t, defaultOpts()) + + assert.Error(t, result.executeErr, "not implemented") +} + +func TestNewConfigCICmd_FailsToLoadFuncWithWrongPath(t *testing.T) { + opts := defaultOpts() + opts.args = append(opts.args, "--path=nofunc") + expectedErrMsg := "failed to create new function" + + result := runConfigCiCmd(t, opts) + + assert.ErrorContains(t, result.executeErr, expectedErrMsg) +} + +func TestNewConfigCICmd_SuccessfulLoadWithCorrectPath(t *testing.T) { + tmpDir := t.TempDir() + opt := opts{withFuncInTempDir: false, enableFeature: true, args: []string{"ci", "--path=" + tmpDir}} + _, initErr := fn.New().Init( + fn.Function{Name: "github-ci-func", Runtime: "go", Root: tmpDir}, + ) + + result := runConfigCiCmd(t, opt) + + assert.NilError(t, initErr) + assert.Error(t, result.executeErr, "not implemented") +} + +// START: Testing Framework +// ------------------------ +type opts struct { + withFuncInTempDir bool + enableFeature bool + args []string +} + +// defaultOpts provides the most used options for tests +// - withFuncInTempDir: true, +// - enableFeature: true, +// - args: []string{"ci"}, +func defaultOpts() opts { + return opts{ + withFuncInTempDir: true, + enableFeature: true, + args: []string{"ci"}, + } +} + +type result struct { + executeErr error +} + +func runConfigCiCmd( + t *testing.T, + opts opts, +) result { + t.Helper() + + // PRE-RUN PREP + if opts.withFuncInTempDir { + _ = cmdTest.CreateFuncInTempDir(t, "github-ci-func") + } + + if opts.enableFeature { + t.Setenv(ci.ConfigCIFeatureFlag, "true") + } + + args := opts.args + if len(opts.args) == 0 { + args = []string{"ci"} + } + + cmd := fnCmd.NewConfigCmd( + common.DefaultLoaderSaver, + fnCmd.NewClient, + ) + cmd.SetArgs(args) + + // RUN + err := cmd.Execute() + + return result{ + err, + } +} diff --git a/cmd/testing/factory.go b/cmd/testing/factory.go index ae11cd0561..d40c9988a7 100644 --- a/cmd/testing/factory.go +++ b/cmd/testing/factory.go @@ -13,16 +13,14 @@ import ( func CreateFuncInTempDir(t *testing.T, fnName string) fn.Function { t.Helper() - var name string + name := fnName if fnName == "" { name = "go-func" } - result, err := fn.New().Init(fn.Function{ - Name: name, - Runtime: "go", - Root: fnTest.FromTempDirectory(t), - }) + result, err := fn.New().Init( + fn.Function{Name: name, Runtime: "go", Root: fnTest.FromTempDirectory(t)}, + ) assert.NilError(t, err) return result diff --git a/docs/reference/func_config.md b/docs/reference/func_config.md index ef2aea0ff3..0f318fdb2d 100644 --- a/docs/reference/func_config.md +++ b/docs/reference/func_config.md @@ -26,6 +26,7 @@ func config ### SEE ALSO * [func](func.md) - func manages Knative Functions +* [func config ci](func_config_ci.md) - Generate a Github Workflow for function deployment * [func config envs](func_config_envs.md) - List and manage configured environment variable for a function * [func config git](func_config_git.md) - Manage Git configuration of a function * [func config labels](func_config_labels.md) - List and manage configured labels for a function diff --git a/docs/reference/func_config_ci.md b/docs/reference/func_config_ci.md new file mode 100644 index 0000000000..06595ce8cf --- /dev/null +++ b/docs/reference/func_config_ci.md @@ -0,0 +1,20 @@ +## func config ci + +Generate a Github Workflow for function deployment + +``` +func config ci +``` + +### Options + +``` + -h, --help help for ci + -p, --path string Path to the function. Default is current directory ($FUNC_PATH) + --workflow-name string Use a custom workflow name (default "Func Deploy") +``` + +### SEE ALSO + +* [func config](func_config.md) - Configure a function +