Repository: rancher/fleet
Pull request #2522 (2024-06-14 12:43:17)

Ignore status fields during drift detection
<!-- Specify the issue ID that this pull request is solving -->
Refers to #2521

Use a new `StatusNormalizer` when comparing deployed resources against the desired spec, since `status` is normally not part of it.


Patch:
diff --git a/internal/cmd/agent/deployer/normalizers/norm.go b/internal/cmd/agent/deployer/normalizers/norm.go
index a47164c8..64f826ac 100644
--- a/internal/cmd/agent/deployer/normalizers/norm.go
+++ b/internal/cmd/agent/deployer/normalizers/norm.go
@@ -22,6 +22,8 @@ func (n Norm) Normalize(un *unstructured.Unstructured) error {
 func New(lives objectset.ObjectByGVK, additions ...diff.Normalizer) Norm {
 	n := Norm{
 		normalizers: []diff.Normalizer{
+			// Status fields are normally subresources which can't be influenced by resource updates
+			&StatusNormalizer{},
 			&MutatingWebhookNormalizer{
 				Live: lives,
 			},
diff --git a/internal/cmd/agent/deployer/normalizers/status.go b/internal/cmd/agent/deployer/normalizers/status.go
new file mode 100644
index 00000000..26f4605b
--- /dev/null
+++ b/internal/cmd/agent/deployer/normalizers/status.go
@@ -0,0 +1,13 @@
+package normalizers
+
+import (
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+)
+
+// StatusNormalizer removes a top-level "status" fields from the object, if present
+type StatusNormalizer struct{}
+
+func (_ StatusNormalizer) Normalize(un *unstructured.Unstructured) error {
+	unstructured.RemoveNestedField(un.Object, "status")
+	return nil
+}


Test patch:
diff --git a/e2e/assets/drift/correction-enabled/gitrepo.yaml b/e2e/assets/drift/correction-enabled/gitrepo.yaml
index 10b527a5..f7cb2a42 100644
--- a/e2e/assets/drift/correction-enabled/gitrepo.yaml
+++ b/e2e/assets/drift/correction-enabled/gitrepo.yaml
@@ -9,3 +9,4 @@ spec:
     enabled: true
   paths:
     - drift
+    - drift-ignore-status
diff --git a/e2e/drift/drift_test.go b/e2e/drift/drift_test.go
index d42b6fd1..0de9030e 100644
--- a/e2e/drift/drift_test.go
+++ b/e2e/drift/drift_test.go
@@ -184,6 +184,17 @@ var _ = Describe("Drift", Ordered, func() {
 					`{"name":"http","port":1234,"protocol":"TCP","targetPort":"http-web-svc"}]}}`))
 			})
 		})
+
+		Context("Resource manifests containing status fields", func() {
+			// Status must be ignored for drift correction, despite being part of the manifests
+			It("Is marked as ready", func() {
+				bundleName := "drift-correction-test-drift-ignore-status"
+				Eventually(func() bool {
+					b := getBundle(bundleName, k)
+					return b.Status.Summary.Ready == 1
+				}).Should(BeTrue())
+			})
+		})
 	})
 
 	When("Drift correction is enabled with force", func() {
diff --git a/internal/cmd/agent/deployer/normalizers/status_test.go b/internal/cmd/agent/deployer/normalizers/status_test.go
new file mode 100644
index 00000000..69fedebc
--- /dev/null
+++ b/internal/cmd/agent/deployer/normalizers/status_test.go
@@ -0,0 +1,55 @@
+package normalizers
+
+import (
+	"errors"
+	"testing"
+
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+func TestStatusNormalizer_Normalize(t *testing.T) {
+	tests := []struct {
+		name  string
+		obj   runtime.Object
+		check func(object runtime.Object) error
+	}{
+		{
+			name: "object with status",
+			obj: &corev1.Pod{
+				Status: corev1.PodStatus{
+					PodIP: "1.2.3.4",
+				},
+			},
+			check: func(obj runtime.Object) error {
+				if obj.(*corev1.Pod).Status.PodIP != "" {
+					return errors.New("status was not removed")
+				}
+				return nil
+			},
+		},
+		{
+			name:  "object without status",
+			obj:   &corev1.ConfigMap{},
+			check: func(_ runtime.Object) error { return nil },
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			un, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tt.obj)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if err := (StatusNormalizer{}).Normalize(&unstructured.Unstructured{Object: un}); err != nil {
+				t.Fatal(err)
+			}
+			if err := runtime.DefaultUnstructuredConverter.FromUnstructured(un, tt.obj); err != nil {
+				t.Fatal(err)
+			}
+			if err := tt.check(tt.obj); err != nil {
+				t.Error(err)
+			}
+		})
+	}
+}