Ctrl K
ComunidadGuías…

internal/flux/latest_test.go

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

Archivo original ↗Preguntar a KiBot
package flux

import (
	"context"
	"testing"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic/fake"

	"github.com/sophotechlabs/spinoza/internal/api"
	"github.com/sophotechlabs/spinoza/internal/charts"
	"github.com/sophotechlabs/spinoza/internal/discovery"
)

type stubCharts struct {
	versions map[string]string
	warmed   []string
}

func (s *stubCharts) Latest(repo charts.Repo, chart string) string {
	return s.versions[repo.URL+"|"+chart]
}

func (s *stubCharts) Warm(repo charts.Repo, chart string) {
	s.warmed = append(s.warmed, repo.URL+"|"+chart)
}

var (
	releaseGVR = schema.GroupVersionResource{
		Group:    "helm.toolkit.fluxcd.io",
		Version:  "v2",
		Resource: "helmreleases",
	}
	repoGVR = schema.GroupVersionResource{
		Group:    "source.toolkit.fluxcd.io",
		Version:  "v1",
		Resource: "helmrepositories",
	}
)

func latestDescs() map[string]api.ResourceDescriptor {
	return map[string]api.ResourceDescriptor{
		discovery.Key("helm.toolkit.fluxcd.io", "v2", "helmreleases"): {
			Group: "helm.toolkit.fluxcd.io", Version: "v2", Resource: "helmreleases",
			Kind: "HelmRelease", Namespaced: true,
		},
		discovery.Key("source.toolkit.fluxcd.io", "v1", "helmrepositories"): {
			Group: "source.toolkit.fluxcd.io", Version: "v1", Resource: "helmrepositories",
			Kind: "HelmRepository", Namespaced: true,
		},
	}
}

func newRepo(name, url, repoType string) *unstructured.Unstructured {
	spec := map[string]any{"url": url}
	if repoType != "" {
		spec["type"] = repoType
	}
	return &unstructured.Unstructured{Object: map[string]any{
		"apiVersion": "source.toolkit.fluxcd.io/v1",
		"kind":       "HelmRepository",
		"metadata":   map[string]any{"name": name, "namespace": "flux-system"},
		"spec":       spec,
	}}
}

func newRelease(namespace, name, chart, sourceName, sourceNs, revision string) *unstructured.Unstructured {
	sourceRef := map[string]any{"kind": "HelmRepository", "name": sourceName}
	if sourceNs != "" {
		sourceRef["namespace"] = sourceNs
	}
	return &unstructured.Unstructured{Object: map[string]any{
		"apiVersion": "helm.toolkit.fluxcd.io/v2",
		"kind":       "HelmRelease",
		"metadata":   map[string]any{"name": name, "namespace": namespace},
		"spec": map[string]any{
			"chart": map[string]any{
				"spec": map[string]any{"chart": chart, "sourceRef": sourceRef},
			},
		},
		"status": map[string]any{
			"history": []any{
				map[string]any{"chartVersion": revision},
			},
		},
	}}
}

func latestClient(objs ...runtime.Object) *fake.FakeDynamicClient {
	kinds := map[schema.GroupVersionResource]string{
		releaseGVR: "HelmReleaseList",
		repoGVR:    "HelmRepositoryList",
	}
	return fake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), kinds, objs...)
}

func releaseRow(t *testing.T, dash api.FluxDashboard, name string) api.FluxResource {
	t.Helper()
	for _, group := range dash.Groups {
		if group.Name != "Helm Releases" {
			continue
		}
		for _, resource := range group.Resources {
			if resource.Name == name {
				return resource
			}
		}
	}
	t.Fatalf("release %q not found in %+v", name, dash.Groups)
	return api.FluxResource{}
}

func TestBuildMarksAnOutdatedRelease(t *testing.T) {
	client := latestClient(
		newRepo("podinfo", "https://example.test/charts", ""),
		newRelease("apps", "podinfo", "podinfo", "podinfo", "flux-system", "6.14.0"),
	)
	index := &stubCharts{versions: map[string]string{"https://example.test/charts|podinfo": "6.15.1"}}

	dash := Build(context.Background(), listerFor(client), latestDescs(), index)

	row := releaseRow(t, dash, "podinfo")
	if row.Latest != "6.15.1" {
		t.Fatalf("latest = %q, want 6.15.1", row.Latest)
	}
	if !row.Outdated {
		t.Fatalf("outdated = false, want true")
	}
	if len(index.warmed) != 1 || index.warmed[0] != "https://example.test/charts|podinfo" {
		t.Fatalf("warmed = %v", index.warmed)
	}
}

func TestBuildLeavesACurrentReleaseUnmarked(t *testing.T) {
	client := latestClient(
		newRepo("podinfo", "https://example.test/charts", ""),
		newRelease("apps", "podinfo", "podinfo", "podinfo", "flux-system", "6.15.1"),
	)
	index := &stubCharts{versions: map[string]string{"https://example.test/charts|podinfo": "6.15.1"}}

	row := releaseRow(t, Build(context.Background(), listerFor(client), latestDescs(), index), "podinfo")

	if row.Latest != "6.15.1" {
		t.Fatalf("latest = %q", row.Latest)
	}
	if row.Outdated {
		t.Fatalf("outdated = true for an up-to-date release")
	}
}

func TestBuildDefaultsTheSourceNamespaceToTheRelease(t *testing.T) {
	client := latestClient(
		newRepo("podinfo", "https://example.test/charts", ""),
		newRelease("flux-system", "podinfo", "podinfo", "podinfo", "", "6.14.0"),
	)
	index := &stubCharts{versions: map[string]string{"https://example.test/charts|podinfo": "6.15.1"}}

	row := releaseRow(t, Build(context.Background(), listerFor(client), latestDescs(), index), "podinfo")

	if row.Latest != "6.15.1" {
		t.Fatalf("latest = %q, want the same-namespace source to resolve", row.Latest)
	}
}

func TestBuildFlagsOCIRepositories(t *testing.T) {
	client := latestClient(
		newRepo("keycloak", "oci://registry.test/team", "oci"),
		newRelease("keycloak", "keycloak", "keycloak", "keycloak", "flux-system", "0.21.14"),
	)
	index := &stubCharts{versions: map[string]string{"oci://registry.test/team|keycloak": "0.22.0"}}

	dash := Build(context.Background(), listerFor(client), latestDescs(), index)

	if !releaseRow(t, dash, "keycloak").Outdated {
		t.Fatalf("expected the oci-sourced release to be marked outdated")
	}
}

func TestBuildSkipsReleasesWithoutAResolvableSource(t *testing.T) {
	cases := map[string]*unstructured.Unstructured{
		"unknown repository": newRelease("apps", "a", "podinfo", "missing", "flux-system", "1.0.0"),
		"no chart name":      newRelease("apps", "b", "", "podinfo", "flux-system", "1.0.0"),
		"no source name":     newRelease("apps", "c", "podinfo", "", "flux-system", "1.0.0"),
	}
	for name, release := range cases {
		t.Run(name, func(t *testing.T) {
			client := latestClient(newRepo("podinfo", "https://example.test/charts", ""), release)
			index := &stubCharts{versions: map[string]string{"https://example.test/charts|podinfo": "9.9.9"}}

			dash := Build(context.Background(), listerFor(client), latestDescs(), index)

			if releaseRow(t, dash, release.GetName()).Latest != "" {
				t.Fatalf("expected no latest version for %s", name)
			}
		})
	}
}

func TestBuildSkipsANonHelmRepositorySource(t *testing.T) {
	release := newRelease("apps", "chart-from-git", "podinfo", "flux-system", "flux-system", "1.0.0")
	if err := unstructured.SetNestedField(release.Object, "GitRepository", "spec", "chart", "spec", "sourceRef", "kind"); err != nil {
		t.Fatalf("seed: %v", err)
	}
	client := latestClient(newRepo("flux-system", "https://example.test/charts", ""), release)
	index := &stubCharts{versions: map[string]string{"https://example.test/charts|podinfo": "9.9.9"}}

	dash := Build(context.Background(), listerFor(client), latestDescs(), index)

	if releaseRow(t, dash, "chart-from-git").Latest != "" {
		t.Fatalf("a git chart source must not get a latest version")
	}
}

func TestBuildSkipsARepositoryWithoutAURL(t *testing.T) {
	client := latestClient(
		newRepo("podinfo", "", ""),
		newRelease("apps", "podinfo", "podinfo", "podinfo", "flux-system", "6.14.0"),
	)
	index := &stubCharts{versions: map[string]string{"|podinfo": "6.15.1"}}

	if releaseRow(t, Build(context.Background(), listerFor(client), latestDescs(), index), "podinfo").Latest != "" {
		t.Fatalf("a repository without a url must be skipped")
	}
}

func TestBuildSkipsARepositoryAimedAtThisMachine(t *testing.T) {
	cases := []string{
		"http://169.254.169.254/latest/meta-data",
		"http://127.0.0.1:9090/charts",
		"file:///etc/passwd",
	}
	for _, repoURL := range cases {
		t.Run(repoURL, func(t *testing.T) {
			client := latestClient(
				newRepo("podinfo", repoURL, ""),
				newRelease("apps", "podinfo", "podinfo", "podinfo", "flux-system", "6.14.0"),
			)
			index := &stubCharts{versions: map[string]string{repoURL + "|podinfo": "6.15.1"}}

			row := releaseRow(t, Build(context.Background(), listerFor(client), latestDescs(), index), "podinfo")

			if row.Latest != "" {
				t.Fatalf("latest = %q, want a repository the cluster aimed at us skipped", row.Latest)
			}
			if len(index.warmed) != 0 {
				t.Fatalf("warmed = %v, want no fetch at all", index.warmed)
			}
		})
	}
}

func TestBuildWithoutAChartIndex(t *testing.T) {
	client := latestClient(
		newRepo("podinfo", "https://example.test/charts", ""),
		newRelease("apps", "podinfo", "podinfo", "podinfo", "flux-system", "6.14.0"),
	)

	row := releaseRow(t, Build(context.Background(), listerFor(client), latestDescs(), nil), "podinfo")

	if row.Latest != "" {
		t.Fatalf("latest = %q, want empty when no index is configured", row.Latest)
	}
}

func TestRepositoryIndexSkipsAListThatFailed(t *testing.T) {
	got := repoIndex(t.Context(), failingLister{}, latestDescs())

	if len(got) != 0 {
		t.Fatalf("repositories = %v, want none from a failed list", got)
	}
}

func TestLatestVersionToleratesARowWithoutItsObject(t *testing.T) {
	rows := map[string][]api.FluxResource{
		"Helm Releases": {{Name: "podinfo", Revision: "6.14.0"}},
	}
	index := &stubCharts{versions: map[string]string{}}

	applyLatest(rows, map[string][]*unstructured.Unstructured{}, nil, index)

	if rows["Helm Releases"][0].Latest != "" {
		t.Fatalf("row = %+v, want no version without its source object", rows["Helm Releases"][0])
	}
	if len(index.warmed) != 0 {
		t.Fatalf("warmed = %v, want no repository fetch", index.warmed)
	}
}