package helm import ( "context" "errors" "strings" "testing" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" k8sfake "k8s.io/client-go/kubernetes/fake" k8stesting "k8s.io/client-go/testing" "github.com/sophotechlabs/spinoza/internal/api" ) type stubRunner struct { args [][]string envs [][]string out string err error available error } func (s *stubRunner) Run(_ context.Context, args, env []string) (string, error) { s.args = append(s.args, args) s.envs = append(s.envs, env) if s.err != nil { return "", s.err } return s.out, nil } func (s *stubRunner) Available() error { return s.available } func acting(t *testing.T, runner Runner, objs ...*corev1.Secret) *Service { t.Helper() client := k8sfake.NewClientset() for _, obj := range objs { _, err := client.CoreV1().Secrets(obj.Namespace).Create(context.Background(), obj, metav1.CreateOptions{}) if err != nil { t.Fatalf("seed secret: %v", err) } } return NewService(client, mirrorMeta(client), runner, nil, actionRepositories(), api.ContextRef{Name: "kind-spinoza"}) } func TestRollbackRunsHelmWithThePinnedContext(t *testing.T) { runner := &stubRunner{out: "Rollback was a success! Happy Helming!"} service := acting(t, runner, helmSecret(sampleRelease())) got, err := service.Rollback(context.Background(), "demo", "podinfo", 2) if err != nil { t.Fatalf("rollback: %v", err) } if len(runner.args) != 1 { t.Fatalf("helm ran %d times, want once", len(runner.args)) } args := strings.Join(runner.args[0], " ") if args != "rollback podinfo 2 --namespace demo --kube-context kind-spinoza" { t.Fatalf("args = %q", args) } if got.Revision != 2 { t.Fatalf("revision = %d, want 2", got.Revision) } if !strings.Contains(got.Message, "Happy Helming") { t.Fatalf("message = %q, want helm's own output", got.Message) } } func TestUninstallRunsHelmWithThePinnedContext(t *testing.T) { runner := &stubRunner{} service := acting(t, runner, helmSecret(sampleRelease())) got, err := service.Uninstall(context.Background(), "demo", "podinfo") if err != nil { t.Fatalf("uninstall: %v", err) } args := strings.Join(runner.args[0], " ") if args != "uninstall podinfo --namespace demo --kube-context kind-spinoza" { t.Fatalf("args = %q", args) } if got.Message != "uninstalled podinfo from demo" { t.Fatalf("message = %q, want a plain summary when helm says nothing", got.Message) } } func TestActionsTellHelmWhichDriverHoldsTheRelease(t *testing.T) { runner := &stubRunner{} entry := &corev1.ConfigMap{ Name: "sh.helm.release.v1.podinfo.v1", Namespace: "demo", Labels: map[string]string{"owner": "helm", "name": "podinfo", "version": "1"}, Data: map[string]string{releaseKey: "rubbish"}, } client := k8sfake.NewClientset(entry) service := NewService(client, mirrorMeta(client), runner, nil, nil, api.ContextRef{Name: "kind-spinoza"}) _, err := service.Uninstall(context.Background(), "demo", "podinfo") if err != nil { t.Fatalf("uninstall: %v", err) } if len(runner.envs[0]) != 1 || runner.envs[0][0] != "HELM_DRIVER=configmap" { t.Fatalf("env = %v, want the configmap driver named", runner.envs[0]) } } func TestActionsUseTheDriverHoldingTheNewestRevision(t *testing.T) { runner := &stubRunner{} old := sampleRelease() old.revision = 1 oldSecret := helmSecret(old) newest := sampleRelease() newest.revision = 2 newConfigMap := &corev1.ConfigMap{ Name: "sh.helm.release.v1.podinfo.v2", Namespace: "demo", Labels: map[string]string{ "owner": "helm", "name": "podinfo", "version": "2", }, Data: map[string]string{releaseKey: detailPayload(newest)}, } client := k8sfake.NewClientset(oldSecret, newConfigMap) service := NewService(client, mirrorMeta(client), runner, nil, nil, api.ContextRef{Name: "kind-spinoza"}) _, err := service.Uninstall(context.Background(), "demo", "podinfo") if err != nil { t.Fatalf("uninstall: %v", err) } if len(runner.envs[0]) != 1 || runner.envs[0][0] != "HELM_DRIVER=configmap" { t.Fatalf("env = %v, want the newest revision's configmap driver", runner.envs[0]) } } func TestDriverFallbackReportsAReleaseListFailure(t *testing.T) { want := errors.New("release storage was refused") client := k8sfake.NewClientset() client.PrependReactor("list", "secrets", func(k8stesting.Action) (bool, runtime.Object, error) { return true, nil, want }) service := NewService(client, nil, nil, nil, nil, api.ContextRef{Name: "kind-spinoza"}) _, err := service.driverFor(t.Context(), "prod", "web") if !errors.Is(err, want) { t.Fatalf("error = %v, want %v", err, want) } } func TestDriverFallbackRefusesAReleaseThatIsNotStored(t *testing.T) { client := k8sfake.NewClientset() service := NewService(client, nil, nil, nil, nil, api.ContextRef{Name: "kind-spinoza"}) _, err := service.driverFor(t.Context(), "prod", "web") if !errors.Is(err, ErrNoRelease) { t.Fatalf("error = %v, want %v", err, ErrNoRelease) } } func TestDriverFallbackUsesTheNewestStoredRevision(t *testing.T) { client := k8sfake.NewClientset( releaseSecret("sh.helm.release.v1.web.v1", "1"), releaseConfigMap("sh.helm.release.v1.web.v2", "2"), ) service := NewService(client, nil, nil, nil, nil, api.ContextRef{Name: "kind-spinoza"}) got, err := service.driverFor(t.Context(), "prod", "web") if err != nil { t.Fatalf("driver: %v", err) } if got != DriverConfigMap { t.Fatalf("driver = %q, want %q", got, DriverConfigMap) } } func TestDriverFallbackRefusesAnAmbiguousNewestRevision(t *testing.T) { client := k8sfake.NewClientset( releaseSecret("sh.helm.release.v1.web.v2", "2"), releaseConfigMap("sh.helm.release.v1.web.v2", "2"), ) service := NewService(client, nil, nil, nil, nil, api.ContextRef{Name: "kind-spinoza"}) _, err := service.driverFor(t.Context(), "prod", "web") if !errors.Is(err, errAmbiguousRevision) { t.Fatalf("error = %v, want %v", err, errAmbiguousRevision) } if !strings.Contains(err.Error(), "revision 2") { t.Fatalf("error = %q, want the duplicate revision named", err) } } func TestActionsSayNothingAboutTheDriverForTheDefaultOne(t *testing.T) { runner := &stubRunner{} service := acting(t, runner, helmSecret(sampleRelease())) _, err := service.Uninstall(context.Background(), "demo", "podinfo") if err != nil { t.Fatalf("uninstall: %v", err) } if len(runner.envs[0]) != 0 { t.Fatalf("env = %v, want nothing added for the default driver", runner.envs[0]) } } func TestHelmChildEnvironmentExcludesServerCredentials(t *testing.T) { t.Setenv("SPINOZA_AUTH_OIDC_CLIENT_SECRET", "must-not-leak") t.Setenv("HELM_CACHE_HOME", "/tmp/helm-cache") t.Setenv("KUBECONFIG", "/tmp/kubeconfig") env := helmEnvironment([]string{"not-an-assignment", "HELM_DRIVER=configmap"}) found := map[string]string{} for _, entry := range env { key, value, ok := strings.Cut(entry, "=") if !ok { t.Fatalf("environment entry = %q", entry) } found[key] = value } if _, ok := found["SPINOZA_AUTH_OIDC_CLIENT_SECRET"]; ok { t.Fatal("the oidc client secret entered the helm child environment") } if found["HELM_CACHE_HOME"] != "/tmp/helm-cache" { t.Fatalf("HELM_CACHE_HOME = %q", found["HELM_CACHE_HOME"]) } if found["KUBECONFIG"] != "/tmp/kubeconfig" { t.Fatalf("KUBECONFIG = %q", found["KUBECONFIG"]) } if found["HELM_DRIVER"] != "configmap" { t.Fatalf("HELM_DRIVER = %q", found["HELM_DRIVER"]) } } func TestActionsRefuseNamesThatCouldBeFlags(t *testing.T) { runner := &stubRunner{} service := acting(t, runner) _, rollbackErr := service.Rollback(context.Background(), "demo", "--kubeconfig=/etc/shadow", 1) _, uninstallErr := service.Uninstall(context.Background(), "--all-namespaces", "podinfo") if rollbackErr == nil || uninstallErr == nil { t.Fatal("a flag-shaped name was accepted") } if len(runner.args) != 0 { t.Fatalf("helm ran %d times for refused input", len(runner.args)) } } func TestARollbackNeedsARevision(t *testing.T) { runner := &stubRunner{} service := acting(t, runner, helmSecret(sampleRelease())) _, err := service.Rollback(context.Background(), "demo", "podinfo", 0) if err == nil { t.Fatal("a rollback with no revision was accepted") } if len(runner.args) != 0 { t.Fatal("helm ran without a revision to roll back to") } } func TestActionsRefuseAReleaseThatIsNotThere(t *testing.T) { runner := &stubRunner{} service := acting(t, runner) _, err := service.Uninstall(context.Background(), "demo", "ghost") if !errors.Is(err, ErrNoRelease) { t.Fatalf("err = %v, want it to say there is no such release", err) } if len(runner.args) != 0 { t.Fatal("helm ran for a release that does not exist") } } func TestAFailedHelmRunIsReported(t *testing.T) { runner := &stubRunner{err: errors.New("release: not found")} service := acting(t, runner, helmSecret(sampleRelease())) _, err := service.Rollback(context.Background(), "demo", "podinfo", 1) if err == nil { t.Fatal("a failed helm run reported success") } if !strings.Contains(err.Error(), "not found") { t.Fatalf("err = %v, want helm's own message", err) } } func TestSupportReportsAMissingHelm(t *testing.T) { service := acting(t, &stubRunner{available: ErrNoHelmBinary}) got := service.Support() if got.Available { t.Fatal("a missing helm was reported as available") } if !strings.Contains(got.Reason, "not found") { t.Fatalf("reason = %q, want it to say helm is missing", got.Reason) } if got.Binary != DefaultBinary { t.Fatalf("binary = %q, want %s", got.Binary, DefaultBinary) } } func TestSupportReportsAHelmItCanRun(t *testing.T) { got := acting(t, &stubRunner{}).Support() if !got.Available { t.Fatal("a runnable helm was reported as unavailable") } if got.Reason != "" { t.Fatalf("reason = %q, want none", got.Reason) } } func TestAServiceWithNoRunnerRefusesToAct(t *testing.T) { service := NewService(k8sfake.NewClientset(), mirrorMeta(k8sfake.NewClientset()), nil, nil, nil, api.ContextRef{Name: "kind-spinoza"}) support := service.Support() _, err := service.Uninstall(context.Background(), "demo", "podinfo") if support.Available { t.Fatal("a service with no runner claimed helm works") } if err == nil { t.Fatal("a service with no runner accepted an uninstall") } } func TestTheRealRunnerReportsAMissingBinary(t *testing.T) { runner := NewRunner("definitely-not-a-real-binary-name") err := runner.Available() if !errors.Is(err, ErrNoHelmBinary) { t.Fatalf("err = %v, want the missing-binary error", err) } } func TestRunningAMissingHelmBinaryReturnsTheStableError(t *testing.T) { runner := NewRunner("definitely-not-a-real-binary-name") _, err := runner.Run(t.Context(), []string{"version"}, nil) if !errors.Is(err, ErrNoHelmBinary) { t.Fatalf("err = %v, want the missing-binary error", err) } } func TestTheRealRunnerCarriesTheCommandOutput(t *testing.T) { runner := NewRunner("echo") out, err := runner.Run(context.Background(), []string{"hello", "there"}, nil) if err != nil { t.Fatalf("run: %v", err) } if out != "hello there" { t.Fatalf("out = %q, want the command's stdout", out) } } func TestTheRealRunnerRefusesOutputBeyondItsLimit(t *testing.T) { runner, ok := NewRunner("sh").(*helmRunner) if !ok { t.Fatal("NewRunner returned something other than the helm runner") } runner.outputLimit = 4 _, err := runner.Run(context.Background(), []string{"-c", "printf 12345"}, nil) if err == nil || !strings.Contains(err.Error(), "exceeded 4 bytes") { t.Fatalf("err = %v", err) } } func TestTheRealRunnerCarriesTheFailureMessage(t *testing.T) { runner := NewRunner("sh") _, err := runner.Run(context.Background(), []string{"-c", "echo boom >&2; exit 1"}, nil) if err == nil { t.Fatal("a failing command reported success") } if err.Error() != "boom" { t.Fatalf("err = %v, want the stderr line", err) } } func TestTheRealRunnerBoundsFailureOutput(t *testing.T) { runner, ok := NewRunner("sh").(*helmRunner) if !ok { t.Fatal("NewRunner returned something other than the helm runner") } runner.errorLimit = 4 _, err := runner.Run(context.Background(), []string{"-c", "printf 12345 >&2; exit 1"}, nil) if err == nil || !strings.Contains(err.Error(), "exceeded 4 bytes") { t.Fatalf("err = %v", err) } } func TestTheRealRunnerFallsBackToTheExitError(t *testing.T) { runner := NewRunner("sh") _, err := runner.Run(context.Background(), []string{"-c", "exit 3"}, nil) if err == nil { t.Fatal("a failing command with no stderr reported success") } } func TestTheRealRunnerPassesTheEnvironmentThrough(t *testing.T) { runner := NewRunner("sh") out, err := runner.Run(context.Background(), []string{"-c", "echo $HELM_DRIVER"}, []string{"HELM_DRIVER=configmap"}) if err != nil { t.Fatalf("run: %v", err) } if out != "configmap" { t.Fatalf("out = %q, want the driver from the added environment", out) } } func TestTheDefaultBinaryIsUsedWhenNoneIsNamed(t *testing.T) { runner, ok := NewRunner("").(*helmRunner) if !ok { t.Fatal("NewRunner returned something other than the helm runner") } if runner.binary != DefaultBinary { t.Fatalf("binary = %q, want %s", runner.binary, DefaultBinary) } } func TestTheRealRunnerAcceptsABinaryOnPath(t *testing.T) { if NewRunner("sh").Available() != nil { t.Fatal("sh was reported as missing") } } func TestActionsReportARefusedStorageRead(t *testing.T) { client := k8sfake.NewClientset() client.PrependReactor("list", "secrets", func(k8stesting.Action) (bool, runtime.Object, error) { return true, nil, errors.New("secrets is forbidden") }) service := NewService(client, mirrorMeta(client), &stubRunner{}, nil, nil, api.ContextRef{Name: "kind-spinoza"}) _, rollbackErr := service.Rollback(context.Background(), "demo", "podinfo", 1) _, uninstallErr := service.Uninstall(context.Background(), "demo", "podinfo") if rollbackErr == nil || uninstallErr == nil { t.Fatal("a refused storage read reported success") } } func TestAConfigMapListFailureIsReported(t *testing.T) { client := k8sfake.NewClientset() meta := mirrorMeta(client) meta.PrependReactor("list", "configmaps", func(k8stesting.Action) (bool, runtime.Object, error) { return true, nil, errors.New("configmaps is forbidden") }) service := NewService(client, meta, nil, nil, nil, api.ContextRef{Name: "kind-spinoza"}) _, err := service.List(context.Background()) if err == nil { t.Fatal("a refused configmap list reported success") } } func TestAConfigMapWithNoReleaseKeyIsSkipped(t *testing.T) { entry := &corev1.ConfigMap{ Name: "not-a-release", Namespace: "demo", Labels: map[string]string{"owner": "helm"}, Data: map[string]string{"other": "value"}, } service := NewService(k8sfake.NewClientset(entry), mirrorMeta(k8sfake.NewClientset(entry)), nil, nil, nil, api.ContextRef{Name: "kind-spinoza"}) got, err := service.List(context.Background()) if err != nil { t.Fatalf("list: %v", err) } if len(got.Releases) != 0 { t.Fatalf("releases = %v, want none", got.Releases) } } func TestValuesThatCannotBeMarshalledReadEmpty(t *testing.T) { got := valuesOf(payload{Config: map[string]any{"bad": func() {}}}) if got != "" { t.Fatalf("values = %q, want nothing when the config will not marshal", got) } } func TestAnActionNamesTheKubeconfigTheContextCameFrom(t *testing.T) { runner := &stubRunner{} client := k8sfake.NewClientset(helmSecret(sampleRelease())) service := NewService(client, mirrorMeta(client), runner, nil, nil, api.ContextRef{ Name: "kind-spinoza", Kubeconfig: "/tmp/spinoza.kubeconfig", }) _, err := service.Uninstall(context.Background(), "demo", "podinfo") if err != nil { t.Fatalf("uninstall: %v", err) } args := strings.Join(runner.args[0], " ") want := "uninstall podinfo --namespace demo --kube-context kind-spinoza" + " --kubeconfig /tmp/spinoza.kubeconfig" if args != want { t.Fatalf("args = %q, want %q", args, want) } } func TestAnActionLeavesHelmsOwnLookupAloneWhenNoFileIsNamed(t *testing.T) { runner := &stubRunner{} service := acting(t, runner, helmSecret(sampleRelease())) _, err := service.Uninstall(context.Background(), "demo", "podinfo") if err != nil { t.Fatalf("uninstall: %v", err) } if strings.Contains(strings.Join(runner.args[0], " "), "--kubeconfig") { t.Fatalf("args = %v, want no file forced on helm", runner.args[0]) } } func TestTheVariablesCloudCredentialPluginsReadReachHelm(t *testing.T) { read := []string{"AWS_PROFILE", "AWS_SHARED_CREDENTIALS_FILE", "CLOUDSDK_CONFIG", "GOOGLE_APPLICATION_CREDENTIALS", "AZURE_CONFIG_DIR"} for _, key := range read { t.Setenv(key, "/tmp/"+strings.ToLower(key)) } t.Setenv("SPINOZA_AUTH_OIDC_CLIENT_SECRET", "must-not-leak") env := helmEnvironment(nil) found := map[string]string{} for _, entry := range env { key, value, _ := strings.Cut(entry, "=") found[key] = value } for _, key := range read { if found[key] != "/tmp/"+strings.ToLower(key) { t.Fatalf("%s = %q, want it passed through so the kubeconfig's credential plugin can sign in", key, found[key]) } } if _, ok := found["SPINOZA_AUTH_OIDC_CLIENT_SECRET"]; ok { t.Fatal("the oidc client secret entered the helm child environment") } }