@@ -37,9 +37,12 @@ func IgnoreMutationWebhookFields() patch.CalculateOption {
3737 return current , modified , nil
3838 }
3939
40+ // Check if ScaleOps is managing resources in EITHER pod
41+ isScaleOpsManaged := isScaleOpsManagedPod (currentPod ) || isScaleOpsManagedPod (modifiedPod )
42+
4043 // Remove fields that mutation webhooks commonly modify
41- currentPod = cleanMutationWebhookFields (currentPod )
42- modifiedPod = cleanMutationWebhookFields (modifiedPod )
44+ currentPod = cleanMutationWebhookFields (currentPod , isScaleOpsManaged )
45+ modifiedPod = cleanMutationWebhookFields (modifiedPod , isScaleOpsManaged )
4346
4447 currentBytes , err := json .Marshal (currentPod )
4548 if err != nil {
@@ -55,14 +58,137 @@ func IgnoreMutationWebhookFields() patch.CalculateOption {
5558 }
5659}
5760
58- func cleanMutationWebhookFields (pod * corev1.Pod ) * corev1.Pod {
61+ // isScaleOpsManagedPod checks if a pod is managed by ScaleOps
62+ func isScaleOpsManagedPod (pod * corev1.Pod ) bool {
63+ return pod .Annotations != nil && (pod .Annotations ["scaleops.sh/managed-containers" ] != "" ||
64+ pod .Annotations ["scaleops.sh/pod-owner-grouping" ] != "" )
65+ }
66+
67+ func cleanMutationWebhookFields (pod * corev1.Pod , isScaleOpsManaged bool ) * corev1.Pod {
5968 // Create a copy to avoid modifying the original
6069 cleaned := pod .DeepCopy ()
6170
6271 // Remove mutation webhook annotations that should not trigger reconciliation
6372 if cleaned .Annotations != nil {
73+ // Gatekeeper annotations
6474 delete (cleaned .Annotations , "gatekeeper.sh/mutation-id" )
6575 delete (cleaned .Annotations , "gatekeeper.sh/mutations" )
76+
77+ // ScaleOps annotations
78+ delete (cleaned .Annotations , "scaleops.sh/admission" )
79+ delete (cleaned .Annotations , "scaleops.sh/applied-policy" )
80+ delete (cleaned .Annotations , "scaleops.sh/last-applied-resources" )
81+ delete (cleaned .Annotations , "scaleops.sh/managed-containers" )
82+ delete (cleaned .Annotations , "scaleops.sh/managed-keep-limit-cpu" )
83+ delete (cleaned .Annotations , "scaleops.sh/managed-keep-limit-memory" )
84+ delete (cleaned .Annotations , "scaleops.sh/origin-resources" )
85+ delete (cleaned .Annotations , "scaleops.sh/pod-owner-grouping" )
86+ delete (cleaned .Annotations , "scaleops.sh/pod-owner-identifier" )
87+
88+ // Remove the last-applied annotation that may contain ScaleOps fields
89+ // Note: This is regenerated on updates by the k8s-objectmatcher library
90+ delete (cleaned .Annotations , "banzaicloud.com/last-applied" )
91+
92+ // If annotations map is empty, set to nil to normalize comparison
93+ if len (cleaned .Annotations ) == 0 {
94+ cleaned .Annotations = nil
95+ }
96+ }
97+
98+ // Remove ScaleOps labels
99+ if cleaned .Labels != nil {
100+ delete (cleaned .Labels , "scaleops.sh/applied-recommendation" )
101+ delete (cleaned .Labels , "scaleops.sh/managed" )
102+ delete (cleaned .Labels , "scaleops.sh/managed-unevictable" )
103+ delete (cleaned .Labels , "scaleops.sh/pod-owner-grouping" )
104+ delete (cleaned .Labels , "scaleops.sh/pod-owner-identifier" )
105+
106+ // If labels map is empty, set to nil to normalize comparison
107+ if len (cleaned .Labels ) == 0 {
108+ cleaned .Labels = nil
109+ }
110+ }
111+
112+ // Remove ScaleOps-added affinity rules (preferred scheduling only)
113+ if cleaned .Spec .Affinity != nil {
114+ if cleaned .Spec .Affinity .NodeAffinity != nil {
115+ // Remove preferred node affinity added by ScaleOps (node-packing)
116+ if cleaned .Spec .Affinity .NodeAffinity .PreferredDuringSchedulingIgnoredDuringExecution != nil {
117+ var filtered []corev1.PreferredSchedulingTerm
118+ for _ , term := range cleaned .Spec .Affinity .NodeAffinity .PreferredDuringSchedulingIgnoredDuringExecution {
119+ // Keep only terms that are NOT ScaleOps node-packing preferences
120+ isScaleOpsTerm := false
121+ for _ , expr := range term .Preference .MatchExpressions {
122+ if expr .Key == "scaleops.sh/node-packing" {
123+ isScaleOpsTerm = true
124+ break
125+ }
126+ }
127+ if ! isScaleOpsTerm {
128+ filtered = append (filtered , term )
129+ }
130+ }
131+ if len (filtered ) == 0 {
132+ cleaned .Spec .Affinity .NodeAffinity .PreferredDuringSchedulingIgnoredDuringExecution = nil
133+ } else {
134+ cleaned .Spec .Affinity .NodeAffinity .PreferredDuringSchedulingIgnoredDuringExecution = filtered
135+ }
136+ }
137+ // Clean up empty NodeAffinity
138+ if cleaned .Spec .Affinity .NodeAffinity .PreferredDuringSchedulingIgnoredDuringExecution == nil &&
139+ cleaned .Spec .Affinity .NodeAffinity .RequiredDuringSchedulingIgnoredDuringExecution == nil {
140+ cleaned .Spec .Affinity .NodeAffinity = nil
141+ }
142+ }
143+
144+ if cleaned .Spec .Affinity .PodAffinity != nil {
145+ // Remove preferred pod affinity added by ScaleOps (managed-unevictable)
146+ if cleaned .Spec .Affinity .PodAffinity .PreferredDuringSchedulingIgnoredDuringExecution != nil {
147+ var filtered []corev1.WeightedPodAffinityTerm
148+ for _ , term := range cleaned .Spec .Affinity .PodAffinity .PreferredDuringSchedulingIgnoredDuringExecution {
149+ // Keep only terms that are NOT ScaleOps managed-unevictable preferences
150+ isScaleOpsTerm := false
151+ if term .PodAffinityTerm .LabelSelector != nil {
152+ for _ , expr := range term .PodAffinityTerm .LabelSelector .MatchExpressions {
153+ if expr .Key == "scaleops.sh/managed-unevictable" {
154+ isScaleOpsTerm = true
155+ break
156+ }
157+ }
158+ }
159+ if ! isScaleOpsTerm {
160+ filtered = append (filtered , term )
161+ }
162+ }
163+ if len (filtered ) == 0 {
164+ cleaned .Spec .Affinity .PodAffinity .PreferredDuringSchedulingIgnoredDuringExecution = nil
165+ } else {
166+ cleaned .Spec .Affinity .PodAffinity .PreferredDuringSchedulingIgnoredDuringExecution = filtered
167+ }
168+ }
169+ // Clean up empty PodAffinity
170+ if cleaned .Spec .Affinity .PodAffinity .PreferredDuringSchedulingIgnoredDuringExecution == nil &&
171+ cleaned .Spec .Affinity .PodAffinity .RequiredDuringSchedulingIgnoredDuringExecution == nil {
172+ cleaned .Spec .Affinity .PodAffinity = nil
173+ }
174+ }
175+
176+ // Clean up empty Affinity
177+ if cleaned .Spec .Affinity .NodeAffinity == nil &&
178+ cleaned .Spec .Affinity .PodAffinity == nil &&
179+ cleaned .Spec .Affinity .PodAntiAffinity == nil {
180+ cleaned .Spec .Affinity = nil
181+ }
182+ }
183+
184+ // Clean resources if ScaleOps is managing them
185+ if isScaleOpsManaged {
186+ for i := range cleaned .Spec .InitContainers {
187+ cleaned .Spec .InitContainers [i ].Resources = corev1.ResourceRequirements {}
188+ }
189+ for i := range cleaned .Spec .Containers {
190+ cleaned .Spec .Containers [i ].Resources = corev1.ResourceRequirements {}
191+ }
66192 }
67193
68194 // Clean security context fields commonly set by PSPs/Gatekeeper
0 commit comments