Ctrl K
ComunidadGuías…

internal/helm/actions_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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")
	}
}