From 8c55183d10b889980032c3b043b3e7c339b4fb63 Mon Sep 17 00:00:00 2001 From: gauron99 Date: Tue, 20 Oct 2026 19:12:27 +0200 Subject: [PATCH] fix(funccli): deploy with --management-disabled func deploy can create or update a Function CR after a deploy (func v1.23+). Today it does this only for local deploys, so the operator's remote deploys are not affected. Once func does it for remote deploys too, every operator rebuild would also rewrite the spec of the Function CR it deploys from. Pass --management-disabled so func leaves the CR alone; the operator keeps writing only its status. The flag first shipped in func v1.23.0, like --image-pull-secret, which the operator already passes. --- internal/funccli/manager.go | 3 ++ internal/funccli/manager_test.go | 70 ++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 internal/funccli/manager_test.go diff --git a/internal/funccli/manager.go b/internal/funccli/manager.go index b9eb9755..fbd14936 100644 --- a/internal/funccli/manager.go +++ b/internal/funccli/manager.go @@ -215,6 +215,9 @@ func (m *managerImpl) Deploy(ctx context.Context, repoPath string, namespace str "deploy", "--remote", "--namespace", namespace, + // The operator owns the Function CR it deploys from: func must not create + // it or rewrite its spec after the deploy + "--management-disabled", } if opts.RegistryAuthFile != "" { diff --git a/internal/funccli/manager_test.go b/internal/funccli/manager_test.go new file mode 100644 index 00000000..919e6bc1 --- /dev/null +++ b/internal/funccli/manager_test.go @@ -0,0 +1,70 @@ +package funccli + +import ( + "context" + "os" + "path/filepath" + "slices" + "strings" + "testing" + + "github.com/go-logr/logr" +) + +// newFakeCLIManager returns a manager whose func binary writes its arguments, +// one per line, to the returned file. +func newFakeCLIManager(t *testing.T) (*managerImpl, string) { + t.Helper() + + dir := t.TempDir() + script := "#!/bin/sh\nprintf '%s\\n' \"$@\" > \"$(dirname \"$0\")/args\"\n" + if err := os.WriteFile(filepath.Join(dir, binaryName), []byte(script), 0755); err != nil { + t.Fatalf("failed to write fake func binary: %v", err) + } + + m, err := NewManager(logr.Discard(), dir, 0, true) + if err != nil { + t.Fatalf("NewManager() error = %v", err) + } + + return m, filepath.Join(dir, "args") +} + +func TestDeploy_Args(t *testing.T) { + tests := []struct { + name string + opts DeployOptions + want []string + }{ + { + name: "no options", + want: []string{"deploy", "--remote", "--namespace", "ns", "--management-disabled"}, + }, + { + name: "registry auth file and image pull secret", + opts: DeployOptions{RegistryAuthFile: "/auth.json", ImagePullSecret: "pull-secret"}, + want: []string{"deploy", "--remote", "--namespace", "ns", "--management-disabled", + "--registry-authfile", "/auth.json", "--image-pull-secret", "pull-secret"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m, argsFile := newFakeCLIManager(t) + + if err := m.Deploy(context.Background(), t.TempDir(), "ns", tt.opts); err != nil { + t.Fatalf("Deploy() error = %v", err) + } + + out, err := os.ReadFile(argsFile) + if err != nil { + t.Fatalf("failed to read func arguments: %v", err) + } + + got := strings.Split(strings.TrimSuffix(string(out), "\n"), "\n") + if !slices.Equal(got, tt.want) { + t.Errorf("func called with %q, want %q", got, tt.want) + } + }) + } +}