@@ -149,7 +149,7 @@ func (u *Updater) Apply(ctx context.Context, baseObj *unstructured.Unstructured)
149149 return resolveErr
150150 }
151151 if ! resolved {
152- return updateErr
152+ return fmt . Errorf ( "refreshing object not considered safe during status update: %w" , updateErr )
153153 }
154154 return fmt .Errorf ("status update conflict" ) // retriable error.
155155 } else if updateErr != nil {
@@ -182,7 +182,7 @@ func (u *Updater) Apply(ctx context.Context, baseObj *unstructured.Unstructured)
182182 return resolveErr
183183 }
184184 if ! resolved {
185- return updateErr
185+ return fmt . Errorf ( "refreshing object not considered safe during update: %w" , updateErr )
186186 }
187187 return fmt .Errorf ("update conflict due to externally-managed status conditions" ) // retriable error.
188188 } else if updateErr != nil {
@@ -197,11 +197,11 @@ func (u *Updater) Apply(ctx context.Context, baseObj *unstructured.Unstructured)
197197
198198func isSafeForUpdate (logger logr.Logger , inMemory * unstructured.Unstructured , onCluster * unstructured.Unstructured ) bool {
199199 // Compare metadata (excluding resourceVersion).
200- inMemoryMetadata := metadataWithoutResourceVersion (inMemory )
201- onClusterMetadata := metadataWithoutResourceVersion (onCluster )
200+ inMemoryMetadata := reducedMetadata (inMemory )
201+ onClusterMetadata := reducedMetadata (onCluster )
202202 if ! reflect .DeepEqual (inMemoryMetadata , onClusterMetadata ) {
203- // Diff in generation . Nothing we can do about it -> Fail.
204- logger .V (1 ).Info ("Not refreshing object due to generation mismatch" ,
203+ // Diff in metadata . Nothing we can do about it -> Fail.
204+ logger .V (1 ).Info ("Not refreshing object due to metadata mismatch" ,
205205 "namespace" , inMemory .GetNamespace (),
206206 "name" , inMemory .GetName (),
207207 "gkv" , inMemory .GroupVersionKind (),
@@ -221,21 +221,42 @@ func isSafeForUpdate(logger logr.Logger, inMemory *unstructured.Unstructured, on
221221 return true
222222}
223223
224- func metadataWithoutResourceVersion (u * unstructured.Unstructured ) map [string ]interface {} {
224+ // recudedMetadata returns the metadata of the given unstructured object,
225+ // excluding a few fields which should not be taken into account
226+ // for equality checks. The excluded fields are:
227+ //
228+ // - metadata.resourceVersion
229+ // - metadata.managedFields
230+ // - metadata.annotation."kubectl.kubernetes.io/last-applied-configuration"
231+ func reducedMetadata (u * unstructured.Unstructured ) map [string ]interface {} {
225232 metadata , ok := u .Object ["metadata" ].(map [string ]interface {})
226233 if ! ok {
227234 return nil
228235 }
229- modifiedMetadata := make (map [string ]interface {}, len (metadata ))
230- for k , v := range metadata {
231- if k == "resourceVersion" {
232- continue
233- }
234- modifiedMetadata [k ] = v
236+ modifiedMetadata := excludeFromMap (metadata , "resourceVersion" , "managedFields" )
237+ if annotations , found := modifiedMetadata ["annotations" ].(map [string ]interface {}); found {
238+ modifiedMetadata ["annotations" ] = excludeFromMap (annotations , "kubectl.kubernetes.io/last-applied-configuration" )
235239 }
240+
236241 return modifiedMetadata
237242}
238243
244+ // excludeFromMap returns a new map that contains all key-value pairs from inputMap
245+ // except those whose keys are in the keys slice.
246+ func excludeFromMap (inputMap map [string ]interface {}, keys ... string ) map [string ]interface {} {
247+ resultMap := make (map [string ]interface {}, len (inputMap ))
248+ excludeKeys := make (map [string ]struct {})
249+ for _ , k := range keys {
250+ excludeKeys [k ] = struct {}{}
251+ }
252+ for k , v := range inputMap {
253+ if _ , found := excludeKeys [k ]; ! found {
254+ resultMap [k ] = v
255+ }
256+ }
257+ return resultMap
258+ }
259+
239260func (u * Updater ) tryRefresh (ctx context.Context , obj * unstructured.Unstructured ) (bool , error ) {
240261 // Re-fetch object with client.
241262 current := & unstructured.Unstructured {}
0 commit comments