mirror of
https://github.com/open-cluster-management-io/ocm.git
synced 2026-08-23 22:26:49 +00:00
fix: keep annotations flag ordering consistent on each reconcile (#1644)
Signed-off-by: Artur Shad Nik <arturshadnik@gmail.com>
This commit is contained in:
+15
-6
@@ -3,6 +3,7 @@ package klusterletcontroller
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -58,6 +59,19 @@ var operatorOnlyFeatureGates = map[featuregate.Feature]bool{
|
||||
NetworkPolicies: true,
|
||||
}
|
||||
|
||||
// buildClusterAnnotationsString renders the ClusterAnnotations map into the
|
||||
// comma-separated "key1=value1,key2=value2" form consumed by the registration
|
||||
// agent's --cluster-annotations flag. Keys are sorted deterministically.
|
||||
func buildClusterAnnotationsString(annotations map[string]string) string {
|
||||
filtered := commonhelpers.FilterClusterAnnotations(annotations)
|
||||
arr := make([]string, 0, len(filtered))
|
||||
for k, v := range filtered {
|
||||
arr = append(arr, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
sort.Strings(arr)
|
||||
return strings.Join(arr, ",")
|
||||
}
|
||||
|
||||
func filterOperatorFeatureGates(features []operatorapiv1.FeatureGate) []operatorapiv1.FeatureGate {
|
||||
var filtered []operatorapiv1.FeatureGate
|
||||
for _, f := range features {
|
||||
@@ -422,12 +436,7 @@ func (n *klusterletController) sync(ctx context.Context, controllerContext facto
|
||||
klusterlet.Spec.RegistrationConfiguration.ClusterClaimConfiguration.ReservedClusterClaimSuffixes, ",")
|
||||
}
|
||||
|
||||
// construct cluster annotations string, the final format is "key1=value1,key2=value2"
|
||||
var annotationsArray []string
|
||||
for k, v := range commonhelpers.FilterClusterAnnotations(klusterlet.Spec.RegistrationConfiguration.ClusterAnnotations) {
|
||||
annotationsArray = append(annotationsArray, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
config.ClusterAnnotationsString = strings.Join(annotationsArray, ",")
|
||||
config.ClusterAnnotationsString = buildClusterAnnotationsString(klusterlet.Spec.RegistrationConfiguration.ClusterAnnotations)
|
||||
|
||||
// Set AddOnKubeClientRegistrationAuth from the Klusterlet spec
|
||||
if klusterlet.Spec.RegistrationConfiguration.AddOnKubeClientRegistrationDriver != nil &&
|
||||
|
||||
+67
@@ -1917,3 +1917,70 @@ func TestCleanWithMultipleKlusterletAgentNamespaces(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildClusterAnnotationsString verifies that the annotations flag string has consistent ordering across multiple runs.
|
||||
func TestBuildClusterAnnotationsString(t *testing.T) {
|
||||
const prefix = "agent.open-cluster-management.io"
|
||||
tests := []struct {
|
||||
name string
|
||||
annotations map[string]string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "nil map",
|
||||
annotations: nil,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "empty map",
|
||||
annotations: map[string]string{},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "single key",
|
||||
annotations: map[string]string{prefix + "/foo": "bar"},
|
||||
want: prefix + "/foo=bar",
|
||||
},
|
||||
{
|
||||
name: "keys sorted lexicographically",
|
||||
annotations: map[string]string{
|
||||
prefix + "/c": "3",
|
||||
prefix + "/a": "1",
|
||||
prefix + "/b": "2",
|
||||
},
|
||||
want: prefix + "/a=1," + prefix + "/b=2," + prefix + "/c=3",
|
||||
},
|
||||
{
|
||||
name: "non-prefixed keys are filtered out",
|
||||
annotations: map[string]string{
|
||||
prefix + "/keep": "yes",
|
||||
"other.example/drop": "no",
|
||||
},
|
||||
want: prefix + "/keep=yes",
|
||||
},
|
||||
{
|
||||
name: "many keys stable across many invocations",
|
||||
annotations: map[string]string{
|
||||
prefix + "/a": "1",
|
||||
prefix + "/b": "2",
|
||||
prefix + "/c": "3",
|
||||
prefix + "/d": "4",
|
||||
prefix + "/e": "5",
|
||||
prefix + "/f": "6",
|
||||
},
|
||||
want: prefix + "/a=1," + prefix + "/b=2," + prefix + "/c=3," + prefix + "/d=4," + prefix + "/e=5," + prefix + "/f=6",
|
||||
},
|
||||
}
|
||||
|
||||
const iterations = 1000
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for i := range iterations {
|
||||
got := buildClusterAnnotationsString(tt.annotations)
|
||||
if got != tt.want {
|
||||
t.Fatalf("iteration %d: got %q, want %q (non-deterministic output regresses cluster-annotations flag)", i, got, tt.want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user