@@ -17,6 +17,7 @@ limitations under the License.
17
17
package sync
18
18
19
19
import (
20
+ "fmt"
20
21
"maps"
21
22
"strings"
22
23
@@ -29,7 +30,7 @@ import (
29
30
"sigs.k8s.io/controller-runtime/pkg/reconcile"
30
31
)
31
32
32
- func stripMetadata (obj * unstructured.Unstructured ) * unstructured. Unstructured {
33
+ func stripMetadata (obj * unstructured.Unstructured ) error {
33
34
obj .SetCreationTimestamp (metav1.Time {})
34
35
obj .SetFinalizers (nil )
35
36
obj .SetGeneration (0 )
@@ -39,70 +40,100 @@ func stripMetadata(obj *unstructured.Unstructured) *unstructured.Unstructured {
39
40
obj .SetUID ("" )
40
41
obj .SetSelfLink ("" )
41
42
42
- stripAnnotations (obj )
43
- stripLabels (obj )
43
+ if err := stripAnnotations (obj ); err != nil {
44
+ return fmt .Errorf ("failed to strip annotations: %w" , err )
45
+ }
46
+ if err := stripLabels (obj ); err != nil {
47
+ return fmt .Errorf ("failed to strip labels: %w" , err )
48
+ }
44
49
45
- return obj
50
+ return nil
46
51
}
47
52
48
- func stripAnnotations (obj * unstructured.Unstructured ) * unstructured. Unstructured {
49
- annotations := obj . GetAnnotations ()
50
- if annotations == nil {
51
- return obj
53
+ func setNestedMapOmitempty (obj * unstructured.Unstructured , value map [ string ] string , path ... string ) error {
54
+ if len ( value ) == 0 {
55
+ unstructured . RemoveNestedField ( obj . Object , path ... )
56
+ return nil
52
57
}
53
58
54
- delete ( annotations , "kcp.io/cluster" )
55
- delete ( annotations , "kubectl.kubernetes.io/last-applied-configuration" )
59
+ return unstructured . SetNestedStringMap ( obj . Object , value , path ... )
60
+ }
56
61
57
- maps .DeleteFunc (annotations , func (annotation string , _ string ) bool {
58
- return strings .HasPrefix (annotation , relatedObjectAnnotationPrefix )
59
- })
62
+ func stripAnnotations (obj * unstructured.Unstructured ) error {
63
+ annotations := obj .GetAnnotations ()
64
+ if annotations == nil {
65
+ return nil
66
+ }
60
67
61
- obj .SetAnnotations (annotations )
68
+ if err := setNestedMapOmitempty (obj , filterUnsyncableAnnotations (annotations ), "metadata" , "annotations" ); err != nil {
69
+ return err
70
+ }
62
71
63
- return obj
72
+ return nil
64
73
}
65
74
66
- func stripLabels (obj * unstructured.Unstructured ) * unstructured. Unstructured {
75
+ func stripLabels (obj * unstructured.Unstructured ) error {
67
76
labels := obj .GetLabels ()
68
77
if labels == nil {
69
- return obj
78
+ return nil
70
79
}
71
80
72
- for _ , label := range ignoredRemoteLabels . UnsortedList () {
73
- delete ( labels , label )
81
+ if err := setNestedMapOmitempty ( obj , filterUnsyncableLabels ( labels ), "metadata" , "labels" ); err != nil {
82
+ return err
74
83
}
75
84
76
- obj .SetLabels (labels )
77
-
78
- return obj
85
+ return nil
79
86
}
80
87
81
- // ignoredRemoteLabels are labels we never want to copy from the remote to local objects.
82
- var ignoredRemoteLabels = sets .New [ string ] (
88
+ // unsyncableLabels are labels we never want to copy from the remote to local objects.
89
+ var unsyncableLabels = sets .New (
83
90
remoteObjectClusterLabel ,
84
- remoteObjectNamespaceLabel ,
85
- remoteObjectNameLabel ,
91
+ remoteObjectNamespaceHashLabel ,
92
+ remoteObjectNameHashLabel ,
86
93
)
87
94
88
- // filterRemoteLabels removes all unwanted remote labels and returns a new label set.
89
- func filterRemoteLabels (remoteLabels labels.Set ) labels.Set {
90
- filteredLabels := labels.Set {}
95
+ // filterUnsyncableLabels removes all unwanted remote labels and returns a new label set.
96
+ func filterUnsyncableLabels (original labels.Set ) labels.Set {
97
+ return filterLabels (original , unsyncableLabels )
98
+ }
99
+
100
+ // unsyncableAnnotations are annotations we never want to copy from the remote to local objects.
101
+ var unsyncableAnnotations = sets .New (
102
+ "kcp.io/cluster" ,
103
+ "kubectl.kubernetes.io/last-applied-configuration" ,
104
+ remoteObjectNamespaceAnnotation ,
105
+ remoteObjectNameAnnotation ,
106
+ remoteObjectWorkspacePathAnnotation ,
107
+ )
91
108
92
- for k , v := range remoteLabels {
93
- if ! ignoredRemoteLabels .Has (k ) {
94
- filteredLabels [k ] = v
109
+ // filterUnsyncableAnnotations removes all unwanted remote annotations and returns a new label set.
110
+ func filterUnsyncableAnnotations (original labels.Set ) labels.Set {
111
+ filtered := filterLabels (original , unsyncableAnnotations )
112
+
113
+ maps .DeleteFunc (filtered , func (annotation string , _ string ) bool {
114
+ return strings .HasPrefix (annotation , relatedObjectAnnotationPrefix )
115
+ })
116
+
117
+ return filtered
118
+ }
119
+
120
+ func filterLabels (original labels.Set , forbidList sets.Set [string ]) labels.Set {
121
+ filtered := labels.Set {}
122
+ for k , v := range original {
123
+ if ! forbidList .Has (k ) {
124
+ filtered [k ] = v
95
125
}
96
126
}
97
127
98
- return filteredLabels
128
+ return filtered
99
129
}
100
130
101
131
func RemoteNameForLocalObject (localObj ctrlruntimeclient.Object ) * reconcile.Request {
102
132
labels := localObj .GetLabels ()
133
+ annotations := localObj .GetAnnotations ()
103
134
clusterName := labels [remoteObjectClusterLabel ]
104
- namespace := labels [ remoteObjectNamespaceLabel ]
105
- name := labels [ remoteObjectNameLabel ]
135
+ namespace := annotations [ remoteObjectNamespaceAnnotation ]
136
+ name := annotations [ remoteObjectNameAnnotation ]
106
137
107
138
// reject/ignore invalid/badly labelled object
108
139
if clusterName == "" || name == "" {
@@ -117,3 +148,43 @@ func RemoteNameForLocalObject(localObj ctrlruntimeclient.Object) *reconcile.Requ
117
148
},
118
149
}
119
150
}
151
+
152
+ // threeWayDiffMetadata is used when updating an object. Since the lastKnownState for any object
153
+ // does not contain syncer-related metadata, this function determines whether labels/annotations are
154
+ // missing by comparing the desired* sets with the current state on the destObj.
155
+ // If a label/annotation is found to be missing or wrong, this function will set it on the sourceObj.
156
+ // This is confusing at first, but the source object here is just a DeepCopy from the actual source
157
+ // object and the caller is not meant to persist changes on the source object. The reason the changes
158
+ // are performed on the source object is so that when creating the patch later on (which is done by
159
+ // comparing the source object with the lastKnownState), the patch will contain the necessary changes.
160
+ func threeWayDiffMetadata (sourceObj , destObj * unstructured.Unstructured , desiredLabels , desiredAnnotations labels.Set ) {
161
+ destLabels := destObj .GetLabels ()
162
+ sourceLabels := sourceObj .GetLabels ()
163
+
164
+ for label , value := range desiredLabels {
165
+ if destValue , ok := destLabels [label ]; ! ok || destValue != value {
166
+ if sourceLabels == nil {
167
+ sourceLabels = map [string ]string {}
168
+ }
169
+
170
+ sourceLabels [label ] = value
171
+ }
172
+ }
173
+
174
+ sourceObj .SetLabels (sourceLabels )
175
+
176
+ destAnnotations := destObj .GetAnnotations ()
177
+ sourceAnnotations := sourceObj .GetAnnotations ()
178
+
179
+ for label , value := range desiredAnnotations {
180
+ if destValue , ok := destAnnotations [label ]; ! ok || destValue != value {
181
+ if sourceAnnotations == nil {
182
+ sourceAnnotations = map [string ]string {}
183
+ }
184
+
185
+ sourceAnnotations [label ] = value
186
+ }
187
+ }
188
+
189
+ sourceObj .SetAnnotations (sourceAnnotations )
190
+ }
0 commit comments