@@ -17,6 +17,7 @@ limitations under the License.
1717package sync
1818
1919import (
20+ "fmt"
2021 "maps"
2122 "strings"
2223
@@ -29,7 +30,7 @@ import (
2930 "sigs.k8s.io/controller-runtime/pkg/reconcile"
3031)
3132
32- func stripMetadata (obj * unstructured.Unstructured ) * unstructured. Unstructured {
33+ func stripMetadata (obj * unstructured.Unstructured ) error {
3334 obj .SetCreationTimestamp (metav1.Time {})
3435 obj .SetFinalizers (nil )
3536 obj .SetGeneration (0 )
@@ -39,70 +40,100 @@ func stripMetadata(obj *unstructured.Unstructured) *unstructured.Unstructured {
3940 obj .SetUID ("" )
4041 obj .SetSelfLink ("" )
4142
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+ }
4449
45- return obj
50+ return nil
4651}
4752
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
5257 }
5358
54- delete ( annotations , "kcp.io/cluster" )
55- delete ( annotations , "kubectl.kubernetes.io/last-applied-configuration" )
59+ return unstructured . SetNestedStringMap ( obj . Object , value , path ... )
60+ }
5661
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+ }
6067
61- obj .SetAnnotations (annotations )
68+ if err := setNestedMapOmitempty (obj , filterUnsyncableAnnotations (annotations ), "metadata" , "annotations" ); err != nil {
69+ return err
70+ }
6271
63- return obj
72+ return nil
6473}
6574
66- func stripLabels (obj * unstructured.Unstructured ) * unstructured. Unstructured {
75+ func stripLabels (obj * unstructured.Unstructured ) error {
6776 labels := obj .GetLabels ()
6877 if labels == nil {
69- return obj
78+ return nil
7079 }
7180
72- for _ , label := range ignoredRemoteLabels . UnsortedList () {
73- delete ( labels , label )
81+ if err := setNestedMapOmitempty ( obj , filterUnsyncableLabels ( labels ), "metadata" , "labels" ); err != nil {
82+ return err
7483 }
7584
76- obj .SetLabels (labels )
77-
78- return obj
85+ return nil
7986}
8087
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 (
8390 remoteObjectClusterLabel ,
84- remoteObjectNamespaceLabel ,
85- remoteObjectNameLabel ,
91+ remoteObjectNamespaceHashLabel ,
92+ remoteObjectNameHashLabel ,
8693)
8794
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+ )
91108
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
95125 }
96126 }
97127
98- return filteredLabels
128+ return filtered
99129}
100130
101131func RemoteNameForLocalObject (localObj ctrlruntimeclient.Object ) * reconcile.Request {
102132 labels := localObj .GetLabels ()
133+ annotations := localObj .GetAnnotations ()
103134 clusterName := labels [remoteObjectClusterLabel ]
104- namespace := labels [ remoteObjectNamespaceLabel ]
105- name := labels [ remoteObjectNameLabel ]
135+ namespace := annotations [ remoteObjectNamespaceAnnotation ]
136+ name := annotations [ remoteObjectNameAnnotation ]
106137
107138 // reject/ignore invalid/badly labelled object
108139 if clusterName == "" || name == "" {
@@ -117,3 +148,43 @@ func RemoteNameForLocalObject(localObj ctrlruntimeclient.Object) *reconcile.Requ
117148 },
118149 }
119150}
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