diff --git a/cmd/config_ci.go b/cmd/config_ci.go index 2c51dbd803..155458685d 100644 --- a/cmd/config_ci.go +++ b/cmd/config_ci.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "io" + "regexp" "strings" "github.com/ory/viper" @@ -11,8 +12,13 @@ import ( "knative.dev/func/cmd/common" "knative.dev/func/pkg/ci/github" fn "knative.dev/func/pkg/functions" + "knative.dev/func/pkg/version" ) +// gitDescribeSuffix matches the "-N-gHASH" suffix produced by git describe +// on commits that are not exactly on a release tag. +var gitDescribeSuffix = regexp.MustCompile(`-\d+-g[0-9a-f]+$`) + // ciGeneratorFactory creates a CIGenerator from resolved CLI flag values. // Using a factory allows tests to capture the resolved config and inject // a mock generator without running the real implementation. @@ -250,6 +256,7 @@ func newConfigAndLoadedFunc( RegistryUserVar: viper.GetString(registryUserVariableNameFlag), RegistryPassSecret: viper.GetString(registryPassSecretNameFlag), RegistryUrlVar: viper.GetString(registryUrlVariableNameFlag), + FuncCliVersion: gitDescribeSuffix.ReplaceAllString(version.Kver, ""), RegistryLogin: viper.GetBool(registryLoginFlag), SelfHostedRunner: viper.GetBool(selfHostedRunnerFlag), RemoteBuild: viper.GetBool(remoteBuildFlag), diff --git a/cmd/config_ci_int_test.go b/cmd/config_ci_int_test.go index e606eec6a9..1831e51dc5 100644 --- a/cmd/config_ci_int_test.go +++ b/cmd/config_ci_int_test.go @@ -231,7 +231,7 @@ func assertDefaultWorkflow(t *testing.T, actualGw string) { assert.Assert(t, yamlContains(actualGw, "Install func cli")) assert.Assert(t, yamlContains(actualGw, "functions-dev/action@main")) - assert.Assert(t, yamlContains(actualGw, "version: knative-v1.22.0")) + assert.Assert(t, yamlContains(actualGw, "version: "+github.DefaultFuncCliVersion)) assert.Assert(t, yamlContains(actualGw, "name: func")) assert.Assert(t, yamlContains(actualGw, "Deploy function")) diff --git a/pkg/ci/github/common.go b/pkg/ci/github/common.go index 5fd105c86c..d41a95a874 100644 --- a/pkg/ci/github/common.go +++ b/pkg/ci/github/common.go @@ -1,8 +1,28 @@ package github -import "fmt" +import ( + "fmt" + "slices" -func determineBuilder(runtime string, remote bool) (string, error) { + "knative.dev/func/pkg/builders" +) + +func determineBuilder(runtime, builder string, remote bool) (string, error) { + defaultBuilder, err := runtimeDefaultBuilder(runtime, remote) + if err != nil { + return "", err + } + if builder == "" { + return defaultBuilder, nil + } + all := builders.All() + if !slices.Contains(all, builder) { + return "", builders.ErrUnknownBuilder{Name: builder, Known: all} + } + return builder, nil +} + +func runtimeDefaultBuilder(runtime string, remote bool) (string, error) { switch runtime { case "go": if remote { diff --git a/pkg/ci/github/generator.go b/pkg/ci/github/generator.go index 6d5ed696d2..9278ade3b0 100644 --- a/pkg/ci/github/generator.go +++ b/pkg/ci/github/generator.go @@ -78,7 +78,7 @@ func (g *workflowGenerator) Generate(ctx context.Context, f fn.Function) error { return fmt.Errorf("function root path can not be empty") } - githubWorkflow, err := newGitHubWorkflow(g.cfg, f.Runtime, g.messageWriter) + githubWorkflow, err := newGitHubWorkflow(g.cfg, f.Runtime, f.Build.Builder, g.messageWriter) if err != nil { return err } @@ -89,7 +89,7 @@ func (g *workflowGenerator) Generate(ctx context.Context, f fn.Function) error { if g.verbose { // best-effort user message; errors are non-critical - _ = PrintConfiguration(g.cfg, f.Runtime, g.messageWriter) + _ = PrintConfiguration(g.cfg, f.Runtime, f.Build.Builder, g.messageWriter) return nil } diff --git a/pkg/ci/github/generator_test.go b/pkg/ci/github/generator_test.go index fb458431f7..f52ce368e4 100644 --- a/pkg/ci/github/generator_test.go +++ b/pkg/ci/github/generator_test.go @@ -518,6 +518,46 @@ func TestCIGenerator_BuilderForRuntime(t *testing.T) { } } +func TestCIGenerator_BuilderConfiguredInFuncYaml(t *testing.T) { + testCases := []struct { + name string + runtime string + builder string + }{ + { + name: "node function with s2i builder configured", + runtime: "node", + builder: "s2i", + }, + { + name: "python function with pack builder configured", + runtime: "python", + builder: "pack", + }, + { + name: "quarkus function with s2i builder configured", + runtime: "quarkus", + builder: "s2i", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // GIVEN + opts := defaultOpts() + opts.goFn.Runtime = tc.runtime + opts.goFn.Build.Builder = tc.builder + + // WHEN + result := runGenerateWorkflow(t, opts) + + // THEN + assert.NilError(t, result.executeErr) + assert.Assert(t, strings.Contains(result.gwYamlString, "FUNC_BUILDER: "+tc.builder)) + }) + } +} + func TestCIGenerator_BuilderForRuntimeError(t *testing.T) { // GIVEN opts := defaultOpts() @@ -530,6 +570,31 @@ func TestCIGenerator_BuilderForRuntimeError(t *testing.T) { assert.Error(t, result.executeErr, "no builder support for runtime: zig") } +func TestCIGenerator_UnsupportedRuntimeErrorsEvenWithExplicitBuilder(t *testing.T) { + // GIVEN + opts := defaultOpts() + opts.goFn.Runtime = "zig" + opts.goFn.Build.Builder = "pack" + + // WHEN + result := runGenerateWorkflow(t, opts) + + // THEN + assert.Error(t, result.executeErr, "no builder support for runtime: zig") +} + +func TestCIGenerator_InvalidBuilderErrors(t *testing.T) { + // GIVEN + opts := defaultOpts() + opts.goFn.Build.Builder = "pakc" + + // WHEN + result := runGenerateWorkflow(t, opts) + + // THEN + assert.Error(t, result.executeErr, `"pakc" is not a known builder. Available builders are "host", "pack" and "s2i"`) +} + // --------------------- // END: Broad Unit Tests @@ -566,6 +631,7 @@ func defaultOpts() opts { RegistryUserVar: github.DefaultRegistryUserVariableName, RegistryPassSecret: github.DefaultRegistryPassSecretName, RegistryUrlVar: github.DefaultRegistryUrlVariableName, + FuncCliVersion: github.DefaultFuncCliVersion, RegistryLogin: github.DefaultRegistryLogin, SelfHostedRunner: github.DefaultSelfHostedRunner, RemoteBuild: github.DefaultRemoteBuild, @@ -643,7 +709,7 @@ func assertDefaultWorkflow(t *testing.T, actualGw string) { assert.Assert(t, yamlContains(actualGw, "Install func cli")) assert.Assert(t, yamlContains(actualGw, "functions-dev/action@main")) - assert.Assert(t, yamlContains(actualGw, "version: knative-v1.22.0")) + assert.Assert(t, yamlContains(actualGw, "version: "+github.DefaultFuncCliVersion)) assert.Assert(t, yamlContains(actualGw, "name: func")) assert.Assert(t, yamlContains(actualGw, "Deploy function")) @@ -676,7 +742,7 @@ func assertSemiDefaultWorkflow(t *testing.T, actualGw string) { assert.Assert(t, yamlContains(actualGw, "Install func cli")) assert.Assert(t, yamlContains(actualGw, "functions-dev/action@main")) - assert.Assert(t, yamlContains(actualGw, "version: knative-v1.22.0")) + assert.Assert(t, yamlContains(actualGw, "version: "+github.DefaultFuncCliVersion)) assert.Assert(t, yamlContains(actualGw, "name: func")) assert.Assert(t, yamlContains(actualGw, "Deploy function")) diff --git a/pkg/ci/github/printer.go b/pkg/ci/github/printer.go index 962801d373..4412c77709 100644 --- a/pkg/ci/github/printer.go +++ b/pkg/ci/github/printer.go @@ -49,8 +49,8 @@ Create the following Secret on github.com: %s ` ) -func PrintConfiguration(cfg WorkflowConfig, runtime string, w io.Writer) error { - builder, err := determineBuilder(runtime, cfg.RemoteBuild) +func PrintConfiguration(cfg WorkflowConfig, runtime, builder string, w io.Writer) error { + builder, err := determineBuilder(runtime, builder, cfg.RemoteBuild) if err != nil { return err } diff --git a/pkg/ci/github/printer_test.go b/pkg/ci/github/printer_test.go index 7b718b78fa..f06c30763c 100644 --- a/pkg/ci/github/printer_test.go +++ b/pkg/ci/github/printer_test.go @@ -29,7 +29,7 @@ func TestPrintConfigurationFail(t *testing.T) { t.Run("main layout write fails", func(t *testing.T) { w := &failWriter{failOnCall: 1, err: errWrite} - err := PrintConfiguration(WorkflowConfig{}, "go", w) + err := PrintConfiguration(WorkflowConfig{}, "go", "", w) assert.Error(t, err, errWrite.Error()) }) @@ -38,7 +38,7 @@ func TestPrintConfigurationFail(t *testing.T) { w := &failWriter{failOnCall: 2, err: errWrite} opts := WorkflowConfig{RegistryLogin: true} - err := PrintConfiguration(opts, "go", w) + err := PrintConfiguration(opts, "go", "", w) assert.Error(t, err, errWrite.Error()) }) @@ -47,7 +47,7 @@ func TestPrintConfigurationFail(t *testing.T) { w := &failWriter{failOnCall: 2, err: errWrite} opts := WorkflowConfig{RegistryLogin: false} - err := PrintConfiguration(opts, "go", w) + err := PrintConfiguration(opts, "go", "", w) assert.Error(t, err, errWrite.Error()) }) @@ -56,7 +56,7 @@ func TestPrintConfigurationFail(t *testing.T) { runtime := "ruby" expectedErr := fmt.Errorf("no builder support for runtime: %s", runtime) - err := PrintConfiguration(WorkflowConfig{}, runtime, &bytes.Buffer{}) + err := PrintConfiguration(WorkflowConfig{}, runtime, "", &bytes.Buffer{}) assert.Error(t, err, expectedErr.Error()) }) diff --git a/pkg/ci/github/workflow.go b/pkg/ci/github/workflow.go index 85403760e2..fd20aed1f7 100644 --- a/pkg/ci/github/workflow.go +++ b/pkg/ci/github/workflow.go @@ -14,7 +14,7 @@ import ( var ErrWorkflowExists = errors.New("existing GitHub workflow detected, overwrite using the --force option") const ( - defaultFuncCliVersion = "knative-v1.22.0" + DefaultFuncCliVersion = "knative-v1.23.0" DefaultPlatform = "github" DefaultGitHubWorkflowDir = ".github/workflows" DefaultGitHubWorkflowFilename = "func-deploy.yaml" @@ -45,7 +45,8 @@ type WorkflowConfig struct { RegistryLoginUrlVar, RegistryUserVar, RegistryPassSecret, - RegistryUrlVar string + RegistryUrlVar, + FuncCliVersion string RegistryLogin, SelfHostedRunner, RemoteBuild, @@ -74,6 +75,7 @@ func defaultWorkflowConfig() WorkflowConfig { RegistryUserVar: DefaultRegistryUserVariableName, RegistryPassSecret: DefaultRegistryPassSecretName, RegistryUrlVar: DefaultRegistryUrlVariableName, + FuncCliVersion: DefaultFuncCliVersion, RegistryLogin: DefaultRegistryLogin, SelfHostedRunner: DefaultSelfHostedRunner, RemoteBuild: DefaultRemoteBuild, @@ -111,6 +113,9 @@ func setEmptyFieldsToDefaults(defaults WorkflowConfig) WorkflowConfig { if defaults.RegistryUrlVar == "" { defaults.RegistryUrlVar = DefaultRegistryUrlVariableName } + if defaults.FuncCliVersion == "" { + defaults.FuncCliVersion = DefaultFuncCliVersion + } return defaults } @@ -143,15 +148,15 @@ type step struct { With map[string]string `yaml:"with,omitempty"` } -func newGitHubWorkflow(cfg WorkflowConfig, runtime string, messageWriter io.Writer) (*workflow, error) { +func newGitHubWorkflow(cfg WorkflowConfig, runtime, builder string, messageWriter io.Writer) (*workflow, error) { var steps []step steps = createCheckoutStep(steps) steps = createRuntimeTestStep(cfg, runtime, messageWriter, steps) steps = createK8ContextStep(cfg, steps) steps = createRegistryLoginStep(cfg, steps) - steps = createFuncCLIInstallStep(steps) + steps = createFuncCLIInstallStep(cfg, steps) - steps, err := createFuncDeployStep(cfg, runtime, steps) + steps, err := createFuncDeployStep(cfg, runtime, builder, steps) if err != nil { return nil, err } @@ -223,20 +228,20 @@ func createRegistryLoginStep(opts WorkflowConfig, steps []step) []step { return append(steps, *loginToContainerRegistry) } -func createFuncCLIInstallStep(steps []step) []step { +func createFuncCLIInstallStep(cfg WorkflowConfig, steps []step) []step { installFuncCli := newStep("Install func cli"). withUses("functions-dev/action@main"). - withActionConfig("version", defaultFuncCliVersion). + withActionConfig("version", cfg.FuncCliVersion). withActionConfig("name", "func") return append(steps, *installFuncCli) } -func createFuncDeployStep(opts WorkflowConfig, runtime string, steps []step) ([]step, error) { +func createFuncDeployStep(opts WorkflowConfig, runtime, builder string, steps []step) ([]step, error) { deployFuncStep := newStep("Deploy function"). withEnv("FUNC_VERBOSE", "true") - builder, err := determineBuilder(runtime, opts.RemoteBuild) + builder, err := determineBuilder(runtime, builder, opts.RemoteBuild) if err != nil { return nil, err } diff --git a/pkg/ci/github/workflow_test.go b/pkg/ci/github/workflow_test.go index 9c1fca1708..1a9e943d3c 100644 --- a/pkg/ci/github/workflow_test.go +++ b/pkg/ci/github/workflow_test.go @@ -18,7 +18,7 @@ func TestGitHubWorkflow_Export(t *testing.T) { // WHEN opts := WorkflowConfig{} - gw, workflowErr := newGitHubWorkflow(WorkflowConfig{}, "go", &bytes.Buffer{}) + gw, workflowErr := newGitHubWorkflow(WorkflowConfig{}, "go", "", &bytes.Buffer{}) assert.NilError(t, workflowErr, "unexpected error when creating GitHub Workflow") exportErr := gw.Export(opts.fnGitHubWorkflowFilepath("path/to/functions"), bufferWriter, true, &bytes.Buffer{})