Skip to content

Commit edad0e7

Browse files
committed
feat: add config ci command and refactor interfaces
Add 'func config ci' command as foundation for GitHub Actions workflow generation. Extract function loader/saver interfaces into cmd/common package and create cmd/testing factory for reusable test helpers. Issue SRVOCF-744 Signed-off-by: Stanislav Jakuschevskij <[email protected]>
1 parent 3dae842 commit edad0e7

File tree

11 files changed

+14357
-14145
lines changed

11 files changed

+14357
-14145
lines changed

cmd/common/common.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package common
2+
3+
import (
4+
"fmt"
5+
6+
fn "knative.dev/func/pkg/functions"
7+
)
8+
9+
// DefaultLoaderSaver implements FunctionLoaderSaver composite interface
10+
var DefaultLoaderSaver standardLoaderSaver
11+
12+
// FunctionLoader loads a function from a filesystem path.
13+
type FunctionLoader interface {
14+
Load(path string) (fn.Function, error)
15+
}
16+
17+
// FunctionSaver persists a function to storage.
18+
type FunctionSaver interface {
19+
Save(f fn.Function) error
20+
}
21+
22+
// FunctionLoaderSaver combines loading and saving capabilities for functions.
23+
type FunctionLoaderSaver interface {
24+
FunctionLoader
25+
FunctionSaver
26+
}
27+
28+
type standardLoaderSaver struct{}
29+
30+
// Load creates and validates a function from the given filesystem path.
31+
func (s standardLoaderSaver) Load(path string) (fn.Function, error) {
32+
f, err := fn.NewFunction(path)
33+
if err != nil {
34+
return fn.Function{}, fmt.Errorf("failed to create new function (path: %q): %w", path, err)
35+
}
36+
37+
if !f.Initialized() {
38+
return fn.Function{}, fn.NewErrNotInitialized(f.Root)
39+
}
40+
41+
return f, nil
42+
}
43+
44+
// Save writes the function configuration to disk.
45+
func (s standardLoaderSaver) Save(f fn.Function) error {
46+
return f.Write()
47+
}

cmd/common/common_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package common_test
2+
3+
import (
4+
"testing"
5+
6+
"gotest.tools/v3/assert"
7+
"knative.dev/func/cmd/common"
8+
cmdTest "knative.dev/func/cmd/testing"
9+
fn "knative.dev/func/pkg/functions"
10+
fnTest "knative.dev/func/pkg/testing"
11+
)
12+
13+
func TestDefaultLoaderSaver_SuccessfulLoad(t *testing.T) {
14+
existingFunc := cmdTest.CreateFuncInTempDir(t, "ls-func")
15+
16+
actualFunc, err := common.DefaultLoaderSaver.Load(existingFunc.Root)
17+
18+
assert.NilError(t, err)
19+
assert.Equal(t, existingFunc.Name, actualFunc.Name)
20+
}
21+
22+
func TestDefaultLoaderSaver_GenericFuncCreateError_WhenFuncPathInvalid(t *testing.T) {
23+
_, err := common.DefaultLoaderSaver.Load("/non-existing-path")
24+
25+
assert.ErrorContains(t, err, "failed to create new function")
26+
}
27+
28+
func TestDefaultLoaderSaver_IsNotInitializedError_WhenNoFuncAtPath(t *testing.T) {
29+
expectedErrMsg := fn.NewErrNotInitialized(fnTest.Cwd()).Error()
30+
31+
_, err := common.DefaultLoaderSaver.Load(fnTest.Cwd())
32+
33+
assert.Error(t, err, expectedErrMsg)
34+
}
35+
36+
func TestDefaultLoaderSaver_SuccessfulSave(t *testing.T) {
37+
existingFunc := cmdTest.CreateFuncInTempDir(t, "")
38+
name := "environment"
39+
value := "test"
40+
existingFunc.Run.Envs = append(existingFunc.Run.Envs, fn.Env{Name: &name, Value: &value})
41+
42+
err := common.DefaultLoaderSaver.Save(existingFunc)
43+
44+
assert.NilError(t, err)
45+
}
46+
47+
func TestDefaultLoaderSaver_ForwardsSaveError(t *testing.T) {
48+
err := common.DefaultLoaderSaver.Save(fn.Function{})
49+
50+
assert.Error(t, err, "function root path is required")
51+
}

cmd/config.go

Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,12 @@ import (
77
"github.com/ory/viper"
88
"github.com/spf13/cobra"
99

10+
"knative.dev/func/cmd/common"
1011
"knative.dev/func/pkg/config"
1112
fn "knative.dev/func/pkg/functions"
1213
)
1314

14-
type functionLoader interface {
15-
Load(path string) (fn.Function, error)
16-
}
17-
18-
type functionSaver interface {
19-
Save(f fn.Function) error
20-
}
21-
22-
type functionLoaderSaver interface {
23-
functionLoader
24-
functionSaver
25-
}
26-
27-
type standardLoaderSaver struct{}
28-
29-
func (s standardLoaderSaver) Load(path string) (fn.Function, error) {
30-
f, err := fn.NewFunction(path)
31-
if err != nil {
32-
return fn.Function{}, fmt.Errorf("failed to create new function (path: %q): %w", path, err)
33-
}
34-
if !f.Initialized() {
35-
return fn.Function{}, fn.NewErrNotInitialized(f.Root)
36-
}
37-
return f, nil
38-
}
39-
40-
func (s standardLoaderSaver) Save(f fn.Function) error {
41-
return f.Write()
42-
}
43-
44-
var defaultLoaderSaver standardLoaderSaver
45-
46-
func NewConfigCmd(loadSaver functionLoaderSaver, newClient ClientFactory) *cobra.Command {
15+
func NewConfigCmd(loadSaver common.FunctionLoaderSaver, newClient ClientFactory) *cobra.Command {
4716
cmd := &cobra.Command{
4817
Use: "config",
4918
Short: "Configure a function",
@@ -69,13 +38,14 @@ or from the directory specified with --path.
6938
cmd.AddCommand(NewConfigLabelsCmd(loadSaver))
7039
cmd.AddCommand(NewConfigEnvsCmd(loadSaver))
7140
cmd.AddCommand(NewConfigVolumesCmd())
41+
cmd.AddCommand(NewConfigCICmd(loadSaver))
7242

7343
return cmd
7444
}
7545

7646
func runConfigCmd(cmd *cobra.Command, args []string) (err error) {
7747

78-
function, err := initConfigCommand(defaultLoaderSaver)
48+
function, err := initConfigCommand(common.DefaultLoaderSaver)
7949
if err != nil {
8050
return
8151
}
@@ -117,7 +87,7 @@ func runConfigCmd(cmd *cobra.Command, args []string) (err error) {
11787
case "Environment variables":
11888
err = runAddEnvsPrompt(cmd.Context(), function)
11989
case "Labels":
120-
err = runAddLabelsPrompt(cmd.Context(), function, defaultLoaderSaver)
90+
err = runAddLabelsPrompt(cmd.Context(), function, common.DefaultLoaderSaver)
12191
case "Git":
12292
err = runConfigGitSetCmd(cmd, NewClient)
12393
}
@@ -128,7 +98,7 @@ func runConfigCmd(cmd *cobra.Command, args []string) (err error) {
12898
case "Environment variables":
12999
err = runRemoveEnvsPrompt(function)
130100
case "Labels":
131-
err = runRemoveLabelsPrompt(function, defaultLoaderSaver)
101+
err = runRemoveLabelsPrompt(function, common.DefaultLoaderSaver)
132102
case "Git":
133103
err = runConfigGitRemoveCmd(cmd, NewClient)
134104
}
@@ -163,7 +133,7 @@ func newConfigCmdConfig() configCmdConfig {
163133
}
164134
}
165135

166-
func initConfigCommand(loader functionLoader) (fn.Function, error) {
136+
func initConfigCommand(loader common.FunctionLoader) (fn.Function, error) {
167137
config := newConfigCmdConfig()
168138

169139
function, err := loader.Load(config.Path)

cmd/config_ci.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"knative.dev/func/cmd/common"
6+
)
7+
8+
func NewConfigCICmd(loaderSaver common.FunctionLoaderSaver) *cobra.Command {
9+
cmd := &cobra.Command{
10+
Use: "ci",
11+
RunE: func(cmd *cobra.Command, args []string) (err error) {
12+
return runConfigCIGithub(loaderSaver)
13+
},
14+
}
15+
16+
addGithubFlag(cmd)
17+
18+
return cmd
19+
}
20+
21+
func runConfigCIGithub(
22+
loaderSaver common.FunctionLoaderSaver,
23+
) error {
24+
if _, err := initConfigCommand(loaderSaver); err != nil {
25+
return err
26+
}
27+
28+
return nil
29+
}
30+
31+
func addGithubFlag(cmd *cobra.Command) {
32+
cmd.Flags().BoolP(
33+
"github",
34+
"",
35+
false,
36+
"Generate GitHub Action ci workflow",
37+
)
38+
}

cmd/config_ci_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package cmd_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/spf13/cobra"
7+
"gotest.tools/v3/assert"
8+
fnCmd "knative.dev/func/cmd"
9+
"knative.dev/func/cmd/common"
10+
cmdTest "knative.dev/func/cmd/testing"
11+
fn "knative.dev/func/pkg/functions"
12+
fnTest "knative.dev/func/pkg/testing"
13+
)
14+
15+
func TestNewConfigCICmd_CommandExists(t *testing.T) {
16+
opts := ciOpts{withMockLoaderSaver: true, args: []string{"ci", "--github"}}
17+
cmd := configCIGithubCmd(t, opts)
18+
19+
err := cmd.Execute()
20+
21+
assert.NilError(t, err)
22+
}
23+
24+
func TestNewConfigCICmd_FailsWhenNotInitialized(t *testing.T) {
25+
expectedErrMsg := fn.NewErrNotInitialized(fnTest.Cwd()).Error()
26+
cmd := configCIGithubCmd(t, ciOpts{})
27+
28+
err := cmd.Execute()
29+
30+
assert.Error(t, err, expectedErrMsg)
31+
}
32+
33+
func TestNewConfigCICmd_SuccessWhenInitialized(t *testing.T) {
34+
cmd := configCIGithubCmd(t, ciOpts{withFuncInTempDir: true})
35+
36+
err := cmd.Execute()
37+
38+
assert.NilError(t, err)
39+
}
40+
41+
// START: Testing Framework
42+
// ------------------------
43+
type ciOpts struct {
44+
withMockLoaderSaver bool // default: false
45+
withFuncInTempDir bool // default: false
46+
args []string // default: ci --github
47+
}
48+
49+
func configCIGithubCmd(
50+
t *testing.T,
51+
opts ciOpts,
52+
) *cobra.Command {
53+
t.Helper()
54+
55+
if opts.withFuncInTempDir {
56+
_ = cmdTest.CreateFuncInTempDir(t, "github-ci-func")
57+
}
58+
59+
var loaderSaver common.FunctionLoaderSaver = common.DefaultLoaderSaver
60+
if opts.withMockLoaderSaver {
61+
loaderSaver = newMockLoaderSaver()
62+
}
63+
64+
args := opts.args
65+
if len(opts.args) == 0 {
66+
args = []string{"ci", "--github"}
67+
}
68+
69+
result := fnCmd.NewConfigCmd(loaderSaver, fnCmd.NewClient)
70+
result.SetArgs(args)
71+
72+
return result
73+
}

cmd/config_envs.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ import (
1313
"github.com/ory/viper"
1414
"github.com/spf13/cobra"
1515

16+
"knative.dev/func/cmd/common"
1617
"knative.dev/func/pkg/config"
1718
fn "knative.dev/func/pkg/functions"
1819
"knative.dev/func/pkg/k8s"
1920
"knative.dev/func/pkg/utils"
2021
)
2122

22-
func NewConfigEnvsCmd(loadSaver functionLoaderSaver) *cobra.Command {
23+
func NewConfigEnvsCmd(loadSaver common.FunctionLoaderSaver) *cobra.Command {
2324
cmd := &cobra.Command{
2425
Use: "envs",
2526
Short: "List and manage configured environment variable for a function",
@@ -64,7 +65,7 @@ the current directory or from the directory specified with --path.
6465
return cmd
6566
}
6667

67-
func NewConfigEnvsAddCmd(loadSaver functionLoaderSaver) *cobra.Command {
68+
func NewConfigEnvsAddCmd(loadSaver common.FunctionLoaderSaver) *cobra.Command {
6869
cmd := &cobra.Command{
6970
Use: "add",
7071
Short: "Add environment variable to the function configuration",
@@ -139,7 +140,7 @@ set environment variable from a secret
139140
return cmd
140141
}
141142

142-
func NewConfigEnvsRemoveCmd(loadSaver functionLoaderSaver) *cobra.Command {
143+
func NewConfigEnvsRemoveCmd(loadSaver common.FunctionLoaderSaver) *cobra.Command {
143144
cmd := &cobra.Command{
144145
Use: "remove",
145146
Short: "Remove environment variable from the function configuration",

cmd/config_labels.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ import (
1111
"github.com/ory/viper"
1212
"github.com/spf13/cobra"
1313

14+
"knative.dev/func/cmd/common"
1415
"knative.dev/func/pkg/config"
1516
fn "knative.dev/func/pkg/functions"
1617
"knative.dev/func/pkg/utils"
1718
)
1819

19-
func NewConfigLabelsCmd(loaderSaver functionLoaderSaver) *cobra.Command {
20+
func NewConfigLabelsCmd(loaderSaver common.FunctionLoaderSaver) *cobra.Command {
2021
var configLabelsCmd = &cobra.Command{
2122
Use: "labels",
2223
Short: "List and manage configured labels for a function",
@@ -184,7 +185,7 @@ func listLabels(f fn.Function, w io.Writer, outputFormat Format) error {
184185
}
185186
}
186187

187-
func runAddLabelsPrompt(_ context.Context, f fn.Function, saver functionSaver) (err error) {
188+
func runAddLabelsPrompt(_ context.Context, f fn.Function, saver common.FunctionSaver) (err error) {
188189

189190
insertToIndex := 0
190191

@@ -317,7 +318,7 @@ func runAddLabelsPrompt(_ context.Context, f fn.Function, saver functionSaver) (
317318
return
318319
}
319320

320-
func runRemoveLabelsPrompt(f fn.Function, saver functionSaver) (err error) {
321+
func runRemoveLabelsPrompt(f fn.Function, saver common.FunctionSaver) (err error) {
321322
if len(f.Deploy.Labels) == 0 {
322323
fmt.Println("There aren't any configured labels")
323324
return

0 commit comments

Comments
 (0)