@@ -267,7 +267,7 @@ func removeWebhookMutation(predictedLive, live *unstructured.Unstructured, gvkPa
267
267
}
268
268
269
269
// In case any of the removed fields cause schema violations, we will keep those fields
270
- nonArgoFieldsSet = safelyRemoveFieldsSet (typedPredictedLive , nonArgoFieldsSet )
270
+ nonArgoFieldsSet = filterOutCompositeKeyFields (typedPredictedLive , nonArgoFieldsSet )
271
271
typedPredictedLive = typedPredictedLive .RemoveItems (nonArgoFieldsSet )
272
272
273
273
// Apply the predicted live state to the live state to get a diff without mutation webhook fields
@@ -289,29 +289,58 @@ func removeWebhookMutation(predictedLive, live *unstructured.Unstructured, gvkPa
289
289
return & unstructured.Unstructured {Object : pl }, nil
290
290
}
291
291
292
- // safelyRemoveFieldSet will validate if removing the fieldsToRemove set from predictedLive maintains
293
- // a valid schema. If removing a field in fieldsToRemove is invalid and breaks the schema, it is not safe
294
- // to remove and will be skipped from removal from predictedLive.
295
- func safelyRemoveFieldsSet (predictedLive * typed.TypedValue , fieldsToRemove * fieldpath.Set ) * fieldpath.Set {
296
- // In some cases, we cannot remove fields due to violation of the predicted live schema. In such cases we validate the removal
297
- // of each field and only include it if the removal is valid.
298
- testPredictedLive := predictedLive .RemoveItems (fieldsToRemove )
299
- err := testPredictedLive .Validate ()
300
- if err != nil {
301
- adjustedFieldsToRemove := fieldpath .NewSet ()
302
- fieldsToRemove .Iterate (func (p fieldpath.Path ) {
303
- singleFieldSet := fieldpath .NewSet (p )
304
- testSingleRemoval := predictedLive .RemoveItems (singleFieldSet )
305
- // Check if removing this single field maintains a valid schema
306
- if testSingleRemoval .Validate () == nil {
307
- // If valid, add this field to the adjusted set to remove
308
- adjustedFieldsToRemove .Insert (p )
292
+ // filterOutCompositeKeyFields filters out fields that are part of composite keys in associative lists.
293
+ // These fields must be preserved to maintain list element identity during merge operations.
294
+ func filterOutCompositeKeyFields (_ * typed.TypedValue , fieldsToRemove * fieldpath.Set ) * fieldpath.Set {
295
+ filteredFields := fieldpath .NewSet ()
296
+
297
+ fieldsToRemove .Iterate (func (fieldPath fieldpath.Path ) {
298
+ isCompositeKey := isCompositeKeyField (fieldPath )
299
+ if ! isCompositeKey {
300
+ // Only keep fields that are NOT composite keys - these are safe to remove
301
+ filteredFields .Insert (fieldPath )
302
+ }
303
+ })
304
+
305
+ return filteredFields
306
+ }
307
+
308
+ // isCompositeKeyField checks if a field path represents a field that is part of a composite key
309
+ // in an associative list by examining the PathElement structure.
310
+ // Example: .spec.containers[name="nginx"].ports[containerPort=80,protocol="TCP"].protocol
311
+ // The path elements include:
312
+ // - PathElement{Key: {name: "nginx"}} - single key (not composite)
313
+ // - PathElement{Key: {containerPort: 80, protocol: "TCP"}} - composite key with 2 fields
314
+ func isCompositeKeyField (fieldPath fieldpath.Path ) bool {
315
+ if len (fieldPath ) == 0 {
316
+ return false
317
+ }
318
+
319
+ // Get the last path element
320
+ lastElement := fieldPath [len (fieldPath )- 1 ]
321
+ if lastElement .FieldName == nil {
322
+ return false
323
+ }
324
+ finalFieldName := * lastElement .FieldName
325
+
326
+ // Look backwards through the path to find the most recent associative list key
327
+ for i := len (fieldPath ) - 2 ; i >= 0 ; i -- {
328
+ pe := fieldPath [i ]
329
+ if pe .Key == nil {
330
+ continue
331
+ }
332
+ if len (* pe .Key ) <= 1 {
333
+ continue
334
+ }
335
+ // This is a composite key
336
+ for _ , keyField := range * pe .Key {
337
+ if keyField .Name == finalFieldName {
338
+ return true
309
339
}
310
- })
311
- return adjustedFieldsToRemove
340
+ }
312
341
}
313
- // If no violations, return the original set to remove
314
- return fieldsToRemove
342
+
343
+ return false
315
344
}
316
345
317
346
func jsonStrToUnstructured (jsonString string ) (* unstructured.Unstructured , error ) {
0 commit comments