Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions cmd/ci/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"path/filepath"

"github.com/ory/viper"
"knative.dev/func/cmd/common"
)

const (
Expand Down Expand Up @@ -66,12 +67,33 @@ type CIConfig struct {
useWorkflowDispatch bool
}

func NewCIGitHubConfig() CIConfig {
func NewCIGitHubConfig(
currentBranch common.CurrentBranchFunc,
workingDir common.WorkDirFunc,
) (CIConfig, error) {
path := viper.GetString(PathFlag)
if path == "" || path == "." {
cwd, err := workingDir()
if err != nil {
return CIConfig{}, err
}
path = cwd
}

branch := viper.GetString(BranchFlag)
if branch == "" {
var err error
branch, err = currentBranch(path)
if err != nil {
return CIConfig{}, err
}
}

return CIConfig{
githubWorkflowDir: DefaultGitHubWorkflowDir,
githubWorkflowFilename: DefaultGitHubWorkflowFilename,
path: viper.GetString(PathFlag),
branch: viper.GetString(BranchFlag),
path: path,
branch: branch,
workflowName: viper.GetString(WorkflowNameFlag),
kubeconfigSecret: viper.GetString(KubeconfigSecretNameFlag),
registryLoginUrlVar: viper.GetString(RegistryLoginUrlVariableNameFlag),
Expand All @@ -82,7 +104,7 @@ func NewCIGitHubConfig() CIConfig {
useSelfHostedRunner: viper.GetBool(UseSelfHostedRunnerFlag),
useRemoteBuild: viper.GetBool(UseRemoteBuildFlag),
useWorkflowDispatch: viper.GetBool(WorkflowDispatchFlag),
}
}, nil
}

func (cc *CIConfig) FnGitHubWorkflowDir(fnRoot string) string {
Expand Down
7 changes: 6 additions & 1 deletion cmd/ci/workflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ import (

"gotest.tools/v3/assert"
"knative.dev/func/cmd/ci"
"knative.dev/func/cmd/common"
)

func TestGitHubWorkflow_Export(t *testing.T) {
// GIVEN
gw := ci.NewGitHubWorkflow(ci.NewCIGitHubConfig())
cfg, _ := ci.NewCIGitHubConfig(
common.CurrentBranchStub("", nil),
common.WorkDirStub("", nil),
)
gw := ci.NewGitHubWorkflow(cfg)
bufferWriter := ci.NewBufferWriter()

// WHEN
Expand Down
12 changes: 7 additions & 5 deletions cmd/ci/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,34 @@ const (
var DefaultWorkflowWriter = &fileWriter{}

type WorkflowWriter interface {
Write(path string, p []byte) error
Write(path string, raw []byte) error
}

type fileWriter struct{}

func (fw *fileWriter) Write(path string, p []byte) error {
func (fw *fileWriter) Write(path string, raw []byte) error {
if err := os.MkdirAll(filepath.Dir(path), dirPerm); err != nil {
return err
}

if err := os.WriteFile(path, p, filePerm); err != nil {
if err := os.WriteFile(path, raw, filePerm); err != nil {
return err
}

return nil
}

type bufferWriter struct {
Path string
Buffer *bytes.Buffer
}

func NewBufferWriter() *bufferWriter {
return &bufferWriter{Buffer: &bytes.Buffer{}}
}

func (bw *bufferWriter) Write(_ string, p []byte) error {
_, err := bw.Buffer.Write(p)
func (bw *bufferWriter) Write(path string, raw []byte) error {
bw.Path = path
_, err := bw.Buffer.Write(raw)
return err
}
89 changes: 89 additions & 0 deletions cmd/common/common.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package common

import (
"errors"
"fmt"
"os"
"os/exec"
"strings"

fn "knative.dev/func/pkg/functions"
)
Expand Down Expand Up @@ -75,3 +79,88 @@ func (m MockLoaderSaver) Load(path string) (fn.Function, error) {
func (m MockLoaderSaver) Save(f fn.Function) error {
return m.SaveFn(f)
}

// CurrentBranchFunc is a function type that retrieves the current git branch for a given path.
type CurrentBranchFunc func(path string) (string, error)

// DefaultCurrentBranch is the default implementation for getting the current git branch.
var DefaultCurrentBranch CurrentBranchFunc = NewGitCliWrapper().CurrentBranch

type gitCliWrapper struct {
gitCmd string
}

// NewGitCliWrapper creates a new git CLI wrapper using FUNC_GIT env var or "git" as default.
func NewGitCliWrapper() *gitCliWrapper {
gitCmd := os.Getenv("FUNC_GIT")
if gitCmd == "" {
gitCmd = "git"
}

return &gitCliWrapper{gitCmd}
}

// CurrentBranch returns the current git branch name for the repository at the given path.
func (g *gitCliWrapper) CurrentBranch(path string) (string, error) {
if _, err := os.Stat(path); err != nil {
return "", err
}

branch, err := g.execGitCmdWith("-C", path, "symbolic-ref", "--short", "HEAD")
if err != nil {
return "", fmt.Errorf("could not detect git branch for '%s'. "+
"Has git been initialized for this Function? %w", path, err)
}

return branch, nil
}

// Init initializes a new git repository at the given path with the specified branch.
func (g *gitCliWrapper) Init(path, branch string) (string, error) {
if _, err := os.Stat(path); err != nil {
return "", err
}

if branch == "" {
return "", fmt.Errorf("branch cannot be empty")
}

return g.execGitCmdWith("init", "-b", branch, path)
}

func (g *gitCliWrapper) execGitCmdWith(args ...string) (string, error) {
result, err := exec.Command(g.gitCmd, args...).Output()
if err == nil {
return strings.TrimSpace(string(result)), nil
}

var exitErr *exec.ExitError
argsJoined := strings.Join(args, " ")
if errors.As(err, &exitErr) {
return "", fmt.Errorf("git %s failed: %w\nstderr: %s", argsJoined, err, string(exitErr.Stderr))
}

return "", fmt.Errorf("git %s failed: %w", argsJoined, err)
}

// CurrentBranchStub creates a stub CurrentBranchFunc that returns the provided output or error.
var CurrentBranchStub = func(output string, err error) CurrentBranchFunc {
return func(_ string) (string, error) {
if err != nil {
return "", err
}

return output, nil
}
}

// WorkDirFunc is a function type that retrieves the current working directory.
type WorkDirFunc func() (string, error)

// DefaultWorkDir is the default implementation for getting the current working directory.
var DefaultWorkDir WorkDirFunc = os.Getwd

// WorkDirStub creates a stub WorkDirFunc that returns the provided directory or error.
var WorkDirStub = func(dir string, err error) WorkDirFunc {
return func() (string, error) { return dir, err }
}
70 changes: 68 additions & 2 deletions cmd/common/common_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package common_test

import (
"fmt"
"os"
"path/filepath"
"testing"

"gotest.tools/v3/assert"
Expand All @@ -10,8 +13,10 @@ import (
fnTest "knative.dev/func/pkg/testing"
)

const mainBranch = "main"

func TestDefaultLoaderSaver_SuccessfulLoad(t *testing.T) {
existingFunc := cmdTest.CreateFuncInTempDir(t, "ls-func")
existingFunc := cmdTest.CreateFuncWithGitInTempDir(t, "ls-func")

actualFunc, err := common.DefaultLoaderSaver.Load(existingFunc.Root)

Expand All @@ -34,7 +39,7 @@ func TestDefaultLoaderSaver_IsNotInitializedError_WhenNoFuncAtPath(t *testing.T)
}

func TestDefaultLoaderSaver_SuccessfulSave(t *testing.T) {
existingFunc := cmdTest.CreateFuncInTempDir(t, "")
existingFunc := cmdTest.CreateFuncWithGitInTempDir(t, "")
name := "environment"
value := "test"
existingFunc.Run.Envs.Add(name, value)
Expand All @@ -52,3 +57,64 @@ func TestDefaultLoaderSaver_ForwardsSaveError(t *testing.T) {

assert.Error(t, err, "function root path is required")
}

func TestGitCliWrapper_Init_InitializesRepo(t *testing.T) {
tempDir := fnTest.FromTempDirectory(t)

_, err := common.NewGitCliWrapper().Init(tempDir, mainBranch)
_, statErr := os.Stat(filepath.Join(tempDir, ".git"))

assert.NilError(t, err)
assert.NilError(t, statErr)
}

func TestGitCliWrapper_Init_ErrorForNonExistentPath(t *testing.T) {
_, err := common.NewGitCliWrapper().Init("/non-existing-path", mainBranch)

assert.Assert(t, os.IsNotExist(err))
}

func TestGitCliWrapper_Init_ErrorForEmptyBranch(t *testing.T) {
_, err := common.NewGitCliWrapper().Init(t.TempDir(), "")

assert.Error(t, err, "branch cannot be empty")
}

func TestGitCliWrapper_CurrentBranch_ReturnsBranchName(t *testing.T) {
tempDir := fnTest.FromTempDirectory(t)
_, initErr := common.NewGitCliWrapper().Init(tempDir, mainBranch)

actualBranch, err := common.NewGitCliWrapper().CurrentBranch(tempDir)

assert.NilError(t, initErr)
assert.NilError(t, err)
assert.Assert(t, actualBranch == mainBranch)
}

func TestGitCliWrapper_CurrentBranch_ErrorForNonExistentPath(t *testing.T) {
_, err := common.NewGitCliWrapper().CurrentBranch("/non-existing-path")

assert.Assert(t, os.IsNotExist(err))
}

func TestGitCliWrapper_CurrentBranch_ErrorForNonGitDirectory(t *testing.T) {
tempDir := fnTest.FromTempDirectory(t)
expectedErrMsg := fmt.Errorf("could not detect git branch for '%s'. "+
"Has git been initialized for this Function?", tempDir)

_, err := common.NewGitCliWrapper().CurrentBranch(tempDir)

assert.ErrorContains(t, err, expectedErrMsg.Error())
assert.ErrorContains(t, err, "failed")
assert.ErrorContains(t, err, "stderr")
}

func TestGitCliWrapper_CurrentBranch_ErrorWhenCommandNotFound(t *testing.T) {
tempDir := fnTest.FromTempDirectory(t)
t.Setenv("FUNC_GIT", "nonexistent-git-command")

_, err := common.NewGitCliWrapper().CurrentBranch(tempDir)

assert.ErrorContains(t, err, "failed")
assert.ErrorContains(t, err, "nonexistent-git-command")
}
10 changes: 8 additions & 2 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ import (
fn "knative.dev/func/pkg/functions"
)

func NewConfigCmd(loaderSaver common.FunctionLoaderSaver, writer ci.WorkflowWriter, newClient ClientFactory) *cobra.Command {
func NewConfigCmd(
loaderSaver common.FunctionLoaderSaver,
writer ci.WorkflowWriter,
currentBranch common.CurrentBranchFunc,
workingDir common.WorkDirFunc,
newClient ClientFactory,
) *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "Configure a function",
Expand Down Expand Up @@ -42,7 +48,7 @@ or from the directory specified with --path.
cmd.AddCommand(NewConfigVolumesCmd())

if os.Getenv(ci.ConfigCIFeatureFlag) == "true" {
cmd.AddCommand(NewConfigCICmd(loaderSaver, writer))
cmd.AddCommand(NewConfigCICmd(loaderSaver, writer, currentBranch, workingDir))
}

return cmd
Expand Down
18 changes: 14 additions & 4 deletions cmd/config_ci.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import (
"knative.dev/func/cmd/common"
)

func NewConfigCICmd(loaderSaver common.FunctionLoaderSaver, writer ci.WorkflowWriter) *cobra.Command {
func NewConfigCICmd(
loaderSaver common.FunctionLoaderSaver,
writer ci.WorkflowWriter,
currentBranch common.CurrentBranchFunc,
workingDir common.WorkDirFunc,
) *cobra.Command {
cmd := &cobra.Command{
Use: "ci",
Short: "Generate a GitHub Workflow for function deployment",
Expand All @@ -28,7 +33,7 @@ func NewConfigCICmd(loaderSaver common.FunctionLoaderSaver, writer ci.WorkflowWr
ci.RegistryUrlVariableNameFlag,
),
RunE: func(cmd *cobra.Command, args []string) (err error) {
return runConfigCIGitHub(cmd, loaderSaver, writer)
return runConfigCIGitHub(cmd, loaderSaver, writer, currentBranch, workingDir)
},
}

Expand Down Expand Up @@ -67,7 +72,7 @@ func NewConfigCICmd(loaderSaver common.FunctionLoaderSaver, writer ci.WorkflowWr

cmd.Flags().String(
ci.BranchFlag,
ci.DefaultBranch,
"",
"Use a custom branch name in the workflow",
)

Expand Down Expand Up @@ -108,8 +113,13 @@ func runConfigCIGitHub(
cmd *cobra.Command,
fnLoaderSaver common.FunctionLoaderSaver,
writer ci.WorkflowWriter,
currentBranch common.CurrentBranchFunc,
workingDir common.WorkDirFunc,
) error {
cfg := ci.NewCIGitHubConfig()
cfg, err := ci.NewCIGitHubConfig(currentBranch, workingDir)
if err != nil {
return err
}

f, err := fnLoaderSaver.Load(cfg.Path())
if err != nil {
Expand Down
Loading
Loading