Ctrl K
ComunidadGuías…

pkg/k8score/transform_test.go

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

Archivo original ↗Preguntar a KiBot
package k8score

import (
	"testing"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

// DropUnstructuredManagedFields is the SharedInformer transform used by
// the dynamic cache. The tests below pin its two invariants:
//   1. For any unstructured object, managedFields are gone but the
//      last-applied-configuration annotation is preserved (GitOps drift
//      detection depends on it — see pkg/gitops/insights/drift.go).
//   2. For CustomResourceDefinitions, the heavy fields (versions[].schema,
//      conversion) are gone while list-view fields (name, served/storage,
//      additionalPrinterColumns, spec.group, spec.names) survive.

func TestDropUnstructuredManagedFields_NonCRD(t *testing.T) {
	u := &unstructured.Unstructured{
		Object: map[string]any{
			"apiVersion": "cilium.io/v2",
			"kind":       "CiliumNetworkPolicy",
			"metadata": map[string]any{
				"name":      "allow-dns",
				"namespace": "default",
				"managedFields": []any{
					map[string]any{"manager": "kubectl"},
				},
				"annotations": map[string]any{
					"kubectl.kubernetes.io/last-applied-configuration": "{big blob}",
					"description": "allow DNS",
				},
			},
			"spec": map[string]any{"endpointSelector": map[string]any{}},
		},
	}

	out, err := DropUnstructuredManagedFields(u)
	if err != nil {
		t.Fatalf("unexpected err: %v", err)
	}
	got := out.(*unstructured.Unstructured)

	// managedFields gone
	if _, found, _ := unstructured.NestedSlice(got.Object, "metadata", "managedFields"); found {
		t.Error("managedFields should be stripped")
	}

	// last-applied-configuration is stripped from the cache. GitOps drift
	// detection still works because the insights resolver pulls live objects
	// via a dedicated direct-GET path that preserves the annotation — keeping
	// it off the informer cache so we don't retain it cluster-wide for core
	// kinds (apps/Deployment, /v1/Service, …) just to power a per-page diff.
	annotations := got.GetAnnotations()
	if _, present := annotations["kubectl.kubernetes.io/last-applied-configuration"]; present {
		t.Errorf("last-applied-configuration should be stripped from cache; got %v", annotations)
	}
	if annotations["description"] != "allow DNS" {
		t.Errorf("other annotations should be preserved, got %v", annotations)
	}

	// Non-CRD spec untouched
	if _, found, _ := unstructured.NestedMap(got.Object, "spec", "endpointSelector"); !found {
		t.Error("non-CRD spec fields should be preserved")
	}
}

func TestDropUnstructuredManagedFields_CRD(t *testing.T) {
	u := &unstructured.Unstructured{
		Object: map[string]any{
			"apiVersion": "apiextensions.k8s.io/v1",
			"kind":       "CustomResourceDefinition",
			"metadata": map[string]any{
				"name": "certificates.cert-manager.io",
				"managedFields": []any{
					map[string]any{"manager": "helm"},
				},
			},
			"spec": map[string]any{
				"group": "cert-manager.io",
				"names": map[string]any{
					"kind":     "Certificate",
					"plural":   "certificates",
					"singular": "certificate",
				},
				"scope": "Namespaced",
				"conversion": map[string]any{
					"strategy": "Webhook",
					"webhook": map[string]any{
						"clientConfig": map[string]any{
							"caBundle": "LS0tLS1CRUdJTi...a 4KB base64 blob...",
							"service":  map[string]any{"name": "cert-manager-webhook"},
						},
					},
				},
				"versions": []any{
					map[string]any{
						"name":    "v1",
						"served":  true,
						"storage": true,
						"schema": map[string]any{
							"openAPIV3Schema": map[string]any{
								"description": "A 50KB blob describing every property of a Certificate...",
								"properties": map[string]any{
									"spec": map[string]any{"type": "object"},
								},
							},
						},
						"additionalPrinterColumns": []any{
							map[string]any{"name": "Ready", "jsonPath": ".status.conditions[?(@.type=='Ready')].status"},
						},
					},
					map[string]any{
						"name":   "v1alpha1",
						"served": false,
						"schema": map[string]any{"openAPIV3Schema": map[string]any{}},
					},
				},
			},
		},
	}

	out, err := DropUnstructuredManagedFields(u)
	if err != nil {
		t.Fatalf("unexpected err: %v", err)
	}
	got := out.(*unstructured.Unstructured)

	// managedFields gone
	if _, found, _ := unstructured.NestedSlice(got.Object, "metadata", "managedFields"); found {
		t.Error("managedFields should be stripped on CRDs")
	}

	// Conversion gone (caBundle lives inside — would leak into cache otherwise)
	if _, found, _ := unstructured.NestedMap(got.Object, "spec", "conversion"); found {
		t.Error("spec.conversion should be stripped on CRDs")
	}

	// Versions still there but schema stripped from each
	versions, found, _ := unstructured.NestedSlice(got.Object, "spec", "versions")
	if !found {
		t.Fatal("spec.versions should be preserved (list-view column hint)")
	}
	if len(versions) != 2 {
		t.Errorf("expected 2 versions, got %d", len(versions))
	}
	for i, v := range versions {
		vm := v.(map[string]any)
		if _, ok := vm["schema"]; ok {
			t.Errorf("versions[%d].schema should be stripped, got %v", i, vm["schema"])
		}
	}

	// Identity fields preserved — these are what fleet aggregation and
	// Radar's own resource list rely on.
	v0 := versions[0].(map[string]any)
	if v0["name"] != "v1" {
		t.Errorf("versions[0].name should be preserved, got %v", v0["name"])
	}
	if v0["served"] != true {
		t.Errorf("versions[0].served should be preserved, got %v", v0["served"])
	}
	if v0["storage"] != true {
		t.Errorf("versions[0].storage should be preserved, got %v", v0["storage"])
	}
	if _, ok := v0["additionalPrinterColumns"]; !ok {
		t.Error("versions[0].additionalPrinterColumns should be preserved (drives list-view columns)")
	}

	group, _, _ := unstructured.NestedString(got.Object, "spec", "group")
	if group != "cert-manager.io" {
		t.Errorf("spec.group should be preserved, got %q", group)
	}
	names, _, _ := unstructured.NestedMap(got.Object, "spec", "names")
	if names["kind"] != "Certificate" {
		t.Errorf("spec.names should be preserved, got %v", names)
	}
	scope, _, _ := unstructured.NestedString(got.Object, "spec", "scope")
	if scope != "Namespaced" {
		t.Errorf("spec.scope should be preserved, got %q", scope)
	}
}

func TestDropUnstructuredManagedFields_NonUnstructuredInput(t *testing.T) {
	// Defensive: transform should be a no-op (not error) on unexpected input.
	// A transform error is fatal for the informer — we'd rather leak a
	// typed object than halt a watch.
	type foo struct{ Name string }
	in := &foo{Name: "x"}

	out, err := DropUnstructuredManagedFields(in)
	if err != nil {
		t.Fatalf("should not error on unexpected input type: %v", err)
	}
	if out != in {
		t.Error("should return input unchanged")
	}
}

func TestStripUnstructuredFields_StripsLastAppliedForResponses(t *testing.T) {
	u := &unstructured.Unstructured{
		Object: map[string]any{
			"metadata": map[string]any{
				"managedFields": []any{map[string]any{"manager": "kubectl"}},
				"annotations": map[string]any{
					"kubectl.kubernetes.io/last-applied-configuration": "{desired manifest}",
					"description": "keep me",
				},
			},
		},
	}
	got := StripUnstructuredFields(u)
	if _, found, _ := unstructured.NestedSlice(got.Object, "metadata", "managedFields"); found {
		t.Fatal("managedFields should be stripped")
	}
	annotations := got.GetAnnotations()
	if _, ok := annotations["kubectl.kubernetes.io/last-applied-configuration"]; ok {
		t.Fatalf("last-applied should be stripped from outward responses, got %v", annotations)
	}
	if annotations["description"] != "keep me" {
		t.Fatalf("other annotations should be preserved, got %v", annotations)
	}
}

func TestStripUnstructuredFieldsPreserveLastApplied_ForDrift(t *testing.T) {
	u := &unstructured.Unstructured{
		Object: map[string]any{
			"metadata": map[string]any{
				"managedFields": []any{map[string]any{"manager": "kubectl"}},
				"annotations": map[string]any{
					"kubectl.kubernetes.io/last-applied-configuration": "{desired manifest}",
				},
			},
		},
	}
	got := StripUnstructuredFieldsPreserveLastApplied(u)
	if _, found, _ := unstructured.NestedSlice(got.Object, "metadata", "managedFields"); found {
		t.Fatal("managedFields should be stripped")
	}
	if got.GetAnnotations()["kubectl.kubernetes.io/last-applied-configuration"] != "{desired manifest}" {
		t.Fatalf("last-applied should be preserved for drift, got %v", got.GetAnnotations())
	}
}

func TestDropUnstructuredManagedFields_CRDWithoutVersions(t *testing.T) {
	// Edge: a minimal CRD object (e.g. mid-reconcile) might have no
	// spec.versions yet. Transform must not panic.
	u := &unstructured.Unstructured{
		Object: map[string]any{
			"apiVersion": "apiextensions.k8s.io/v1",
			"kind":       "CustomResourceDefinition",
			"metadata":   map[string]any{"name": "brand-new.example.com"},
			"spec":       map[string]any{"group": "example.com"},
		},
	}

	out, err := DropUnstructuredManagedFields(u)
	if err != nil {
		t.Fatalf("unexpected err: %v", err)
	}
	if out == nil {
		t.Fatal("expected unchanged object, got nil")
	}
}

// TestDropManagedFields_TypedAsymmetricAnnotationHandling pins the intentional
// asymmetry between DropManagedFields (typed cache transform) and
// DropUnstructuredManagedFields (dynamic cache transform). The dynamic path
// preserves kubectl.kubernetes.io/last-applied-configuration for GitOps drift
// detection; the typed path strips it because typed-cache objects are not
// the path drift detection reads from.
//
// Both transforms remove managedFields. The mismatch is intentional. A
// "consistency cleanup" PR that aligned them in either direction would
// silently break one feature (drift) or regress memory (the strip exists
// for a reason); this test documents the contract so the next maintainer
// can see what's load-bearing.
func TestDropManagedFields_TypedStripsLastAppliedConfig(t *testing.T) {
	dep := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "x",
			Namespace: "default",
			Annotations: map[string]string{
				"kubectl.kubernetes.io/last-applied-configuration": "{blob}",
				"description": "keep me",
			},
			ManagedFields: []metav1.ManagedFieldsEntry{{Manager: "kubectl"}},
		},
	}
	out, err := DropManagedFields(dep)
	if err != nil {
		t.Fatalf("unexpected err: %v", err)
	}
	got := out.(*appsv1.Deployment)
	if len(got.ManagedFields) != 0 {
		t.Errorf("managedFields should be stripped, got %d entries", len(got.ManagedFields))
	}
	if _, present := got.Annotations["kubectl.kubernetes.io/last-applied-configuration"]; present {
		t.Errorf("typed path must strip last-applied-configuration (asymmetric with unstructured path); got %v", got.Annotations)
	}
	if got.Annotations["description"] != "keep me" {
		t.Errorf("other annotations should survive, got %v", got.Annotations)
	}
}

// Sanity check the asymmetry rule extends to other typed kinds: corev1.Pod is
// in DropManagedFields' explicit kind list. If the switch ever loses Pod,
// this test breaks.
func TestDropManagedFields_TypedStripsLastAppliedFromPod(t *testing.T) {
	pod := &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name: "x", Namespace: "default",
			Annotations: map[string]string{
				"kubectl.kubernetes.io/last-applied-configuration": "{blob}",
			},
		},
	}
	out, _ := DropManagedFields(pod)
	if _, present := out.(*corev1.Pod).Annotations["kubectl.kubernetes.io/last-applied-configuration"]; present {
		t.Errorf("Pod should also have last-applied stripped")
	}
}

func TestDropManagedFields_EventKeepsEventsAPITimes(t *testing.T) {
	first := metav1.NewMicroTime(metav1.Now().Add(-3600e9))
	last := metav1.NewMicroTime(metav1.Now().Time)
	out, err := DropManagedFields(&corev1.Event{
		EventTime: first,
		Series:    &corev1.EventSeries{Count: 4, LastObservedTime: last},
	})
	if err != nil {
		t.Fatal(err)
	}
	ev := out.(*corev1.Event)
	if !ev.EventTime.Equal(&first) || ev.Series == nil || !ev.Series.LastObservedTime.Equal(&last) {
		t.Fatalf("event times stripped: eventTime=%v series=%+v", ev.EventTime, ev.Series)
	}
}

func TestDropManagedFields_EventKeepsRelatedSubject(t *testing.T) {
	related := &corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "apps", Name: "worker", UID: "related-uid"}
	out, err := DropManagedFields(&corev1.Event{Related: related})
	if err != nil {
		t.Fatal(err)
	}
	got := out.(*corev1.Event).Related
	if got == nil || *got != *related {
		t.Fatalf("related Event subject stripped: %+v", got)
	}
}

func helmReleaseSecretForTest(payload []byte) *corev1.Secret {
	return &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "sh.helm.release.v1.web.v3",
			Namespace: "apps",
			Labels:    map[string]string{"owner": "helm", "name": "web", "status": "deployed", "version": "3"},
		},
		Type: HelmReleaseSecretType,
		Data: map[string][]byte{"release": payload},
	}
}

func TestDropManagedFields_HelmReleaseSecretKeepsDigestNotPayload(t *testing.T) {
	payload := make([]byte, 300_000)
	secret := helmReleaseSecretForTest(payload)

	// Watch-list informers run the transform twice on objects present at
	// startup, so a second pass must leave the same result.
	var digest []byte
	for pass := 1; pass <= 2; pass++ {
		out, err := DropManagedFields(secret)
		if err != nil {
			t.Fatalf("pass %d: unexpected err: %v", pass, err)
		}
		got := out.(*corev1.Secret)
		value, ok := got.Data["release"]
		if !ok {
			t.Fatalf("pass %d: release key dropped; key counts would change", pass)
		}
		if len(value) > 100 {
			t.Fatalf("pass %d: release payload kept in cache (%d bytes)", pass, len(value))
		}
		if pass == 1 {
			digest = append([]byte(nil), value...)
		} else if string(value) != string(digest) {
			t.Fatalf("second pass changed the digest: %q -> %q", digest, value)
		}
		if got.Labels["status"] != "deployed" {
			t.Fatalf("pass %d: labels changed: %v", pass, got.Labels)
		}
	}

	// A rewrite with a different payload must still compare as changed.
	other, _ := DropManagedFields(helmReleaseSecretForTest(append(payload[:len(payload):len(payload)], 1)))
	if string(other.(*corev1.Secret).Data["release"]) == string(digest) {
		t.Fatal("different payloads produced the same cached value")
	}
}

func TestWithoutHelmReleaseValues(t *testing.T) {
	out, _ := DropManagedFields(helmReleaseSecretForTest([]byte("payload")))
	cached := out.(*corev1.Secret)
	served := WithoutHelmReleaseValues(cached)
	if value, ok := served.Data["release"]; !ok || value != nil {
		t.Fatalf("served release = %q (present %v), want key with no value", value, ok)
	}
	if len(cached.Data["release"]) == 0 {
		t.Fatal("WithoutHelmReleaseValues modified the cached object")
	}

	opaque := &corev1.Secret{Type: corev1.SecretTypeOpaque, Data: map[string][]byte{"k": []byte("v")}}
	if WithoutHelmReleaseValues(opaque) != opaque {
		t.Fatal("an ordinary Secret was copied or changed")
	}
}

func TestDropManagedFields_OtherSecretsKeepValues(t *testing.T) {
	for _, secretType := range []corev1.SecretType{corev1.SecretTypeOpaque, corev1.SecretTypeTLS, corev1.SecretTypeDockerConfigJson, "helm.sh/release.v2"} {
		secret := &corev1.Secret{
			ObjectMeta: metav1.ObjectMeta{Name: "s", Namespace: "apps"},
			Type:       secretType,
			Data:       map[string][]byte{"key": []byte("value")},
		}
		out, err := DropManagedFields(secret)
		if err != nil {
			t.Fatalf("%s: unexpected err: %v", secretType, err)
		}
		if got := string(out.(*corev1.Secret).Data["key"]); got != "value" {
			t.Fatalf("%s: value = %q, want it kept", secretType, got)
		}
	}
}