@@ -267,7 +267,7 @@ func removeWebhookMutation(predictedLive, live *unstructured.Unstructured, gvkPa
267267 }
268268
269269 // 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 )
271271 typedPredictedLive = typedPredictedLive .RemoveItems (nonArgoFieldsSet )
272272
273273 // 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
289289 return & unstructured.Unstructured {Object : pl }, nil
290290}
291291
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
309339 }
310- })
311- return adjustedFieldsToRemove
340+ }
312341 }
313- // If no violations, return the original set to remove
314- return fieldsToRemove
342+
343+ return false
315344}
316345
317346func jsonStrToUnstructured (jsonString string ) (* unstructured.Unstructured , error ) {
0 commit comments