Repository: rancher/fleet
Pull request #3119 (2024-12-03 16:04:12)

[backport v2.10/v0.11] [SURE-8794] Deduplicate status messages (#3042)
(cherry picked from commit a8cafe4322a574dc4edd87ce9f6c6da905bf9afd)

<!-- Specify the issue ID that this pull request is solving -->
Refers to #3118  
<!-- Make sure that the referenced issue provides steps to reproduce it -->

<!-- Describe the changes introduced by this pull request -->

<!--
  Please provide a unit, integration (`./integrationtests/`) or e2e (`./e2e/`) test if possible.
-->


Patch:
diff --git a/.github/workflows/typos.yaml b/.github/workflows/typos.yaml
index d6c54093..a5e6db16 100644
--- a/.github/workflows/typos.yaml
+++ b/.github/workflows/typos.yaml
@@ -11,5 +11,5 @@ jobs:
       uses: actions/checkout@v4
 
     - name: Check spelling of file.txt
-      uses: crate-ci/typos@master
+      uses: crate-ci/typos@v1.27.3
 
diff --git a/internal/cmd/agent/deployer/summary/summarizers.go b/internal/cmd/agent/deployer/summary/summarizers.go
index 1085da51..de5c0a15 100644
--- a/internal/cmd/agent/deployer/summary/summarizers.go
+++ b/internal/cmd/agent/deployer/summary/summarizers.go
@@ -133,6 +133,7 @@ func init() {
 		checkOwner,
 		checkApplyOwned,
 		checkCattleTypes,
+		kStatusSummarizer,
 	}
 }
 
@@ -480,3 +481,38 @@ func checkApplyOwned(obj data.Object, conditions []Condition, summary fleetv1.Su
 
 	return summary
 }
+
+func kStatusSummarizer(obj data.Object, _ []Condition, summary fleetv1.Summary) fleetv1.Summary {
+	result, err := kstatus.Compute(&unstructured.Unstructured{Object: obj})
+	if err != nil {
+		return summary
+	}
+
+	switch result.Status {
+	case kstatus.InProgressStatus:
+		summary.Transitioning = true
+	case kstatus.FailedStatus:
+		summary.Error = true
+	case kstatus.CurrentStatus:
+	case kstatus.TerminatingStatus:
+		summary.Transitioning = true
+	case kstatus.UnknownStatus:
+	}
+
+	if result.Message != "" {
+		// Deduplicate status messages (https://github.com/rancher/fleet/issues/2859)
+		messages := make(map[string]bool)
+		var resultMessages []string
+		for _, message := range strings.Split(result.Message, "; ") {
+			if _, ok := messages[message]; ok {
+				continue
+			}
+			messages[message] = true
+			resultMessages = append(resultMessages, message)
+		}
+
+		summary.Message = append(summary.Message, strings.Join(resultMessages, "; "))
+	}
+
+	return summary
+}
diff --git a/internal/helmdeployer/kustomize/kstatus.go b/internal/helmdeployer/kustomize/kstatus.go
deleted file mode 100644
index a4f6425e..00000000
--- a/internal/helmdeployer/kustomize/kstatus.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package kustomize
-
-import (
-	"github.com/rancher/fleet/internal/cmd/agent/deployer/data"
-	"github.com/rancher/fleet/internal/cmd/agent/deployer/summary"
-	fleetv1 "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1/summary"
-
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
-)
-
-func init() {
-	summary.Summarizers = append(summary.Summarizers, KStatusSummarizer)
-}
-
-func KStatusSummarizer(obj data.Object, conditions []summary.Condition, summary fleetv1.Summary) fleetv1.Summary {
-	result, err := status.Compute(&unstructured.Unstructured{Object: obj})
-	if err != nil {
-		return summary
-	}
-
-	switch result.Status {
-	case status.InProgressStatus:
-		summary.Transitioning = true
-	case status.FailedStatus:
-		summary.Error = true
-	case status.CurrentStatus:
-	case status.TerminatingStatus:
-		summary.Transitioning = true
-	case status.UnknownStatus:
-	}
-
-	if result.Message != "" {
-		summary.Message = append(summary.Message, result.Message)
-	}
-
-	return summary
-}


Test patch:
diff --git a/internal/cmd/agent/deployer/summary/suite_test.go b/internal/cmd/agent/deployer/summary/suite_test.go
new file mode 100644
index 00000000..103f35d0
--- /dev/null
+++ b/internal/cmd/agent/deployer/summary/suite_test.go
@@ -0,0 +1,13 @@
+package summary_test
+
+import (
+	"testing"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+)
+
+func TestSummary(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Summary Suite")
+}
diff --git a/internal/cmd/agent/deployer/summary/summarizers_test.go b/internal/cmd/agent/deployer/summary/summarizers_test.go
new file mode 100644
index 00000000..584f2038
--- /dev/null
+++ b/internal/cmd/agent/deployer/summary/summarizers_test.go
@@ -0,0 +1,56 @@
+package summary
+
+import (
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/rancher/fleet/internal/cmd/agent/deployer/data"
+	fleetv1 "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1/summary"
+)
+
+var _ = Describe("Summary", func() {
+	When("testing kStatusSummarizer", func() {
+		newObj := func(message string) data.Object {
+			return data.Object{
+				"status": map[string]interface{}{
+					"conditions": []interface{}{
+						map[string]interface{}{
+							"type":    "Ready",
+							"status":  "False",
+							"message": message,
+						},
+					},
+				},
+			}
+		}
+
+		It("should deduplicate messages", func() {
+			obj := newObj("message1; message2; message1; message1; message2")
+			summary := kStatusSummarizer(obj, nil, fleetv1.Summary{})
+			Expect(summary.Message).To(Equal([]string{"message1; message2"}))
+			Expect(summary.Transitioning).To(BeTrue())
+			Expect(summary.Error).To(BeFalse())
+		})
+
+		It("should not alter the message that doesn't contain duplicates", func() {
+			obj := newObj("message1; message2; message3")
+			summary := kStatusSummarizer(obj, nil, fleetv1.Summary{})
+			Expect(summary.Message).To(Equal([]string{"message1; message2; message3"}))
+		})
+
+		It("should not deduplicate messages with the wrong separator", func() {
+			separators := []string{", ", " ", " , ", "[", "]", "{", "}"}
+			for _, sep := range separators {
+				obj := newObj("message1" + sep + "message1")
+				summary := kStatusSummarizer(obj, nil, fleetv1.Summary{})
+				Expect(summary.Message).To(Equal([]string{"message1" + sep + "message1"}))
+			}
+		})
+
+		It("should not fail on an empty message", func() {
+			obj := newObj("")
+			summary := kStatusSummarizer(obj, nil, fleetv1.Summary{})
+			Expect(summary.Message).To(BeEmpty())
+		})
+	})
+})