@@ -772,29 +772,104 @@ func applyNodeSelectorConfig(deployment *appsv1.Deployment, config *config.Deplo
772772 deployment .Spec .Template .Spec .NodeSelector = config .NodeSelector
773773}
774774
775+ // isAffinityEmpty checks if an Affinity object is semantically empty.
776+ // This accounts for YAML unmarshaling which creates empty slices instead of nil.
777+ func isAffinityEmpty (a * corev1.Affinity ) bool {
778+ if a == nil {
779+ return true
780+ }
781+ return isNodeAffinityEmpty (a .NodeAffinity ) &&
782+ isPodAffinityEmpty (a .PodAffinity ) &&
783+ isPodAntiAffinityEmpty (a .PodAntiAffinity )
784+ }
785+
786+ // isNodeAffinityEmpty checks if a NodeAffinity object is semantically empty.
787+ func isNodeAffinityEmpty (na * corev1.NodeAffinity ) bool {
788+ if na == nil {
789+ return true
790+ }
791+ requiredEmpty := na .RequiredDuringSchedulingIgnoredDuringExecution == nil ||
792+ len (na .RequiredDuringSchedulingIgnoredDuringExecution .NodeSelectorTerms ) == 0
793+ return requiredEmpty && len (na .PreferredDuringSchedulingIgnoredDuringExecution ) == 0
794+ }
795+
796+ // isPodAffinityEmpty checks if a PodAffinity object is semantically empty.
797+ func isPodAffinityEmpty (pa * corev1.PodAffinity ) bool {
798+ if pa == nil {
799+ return true
800+ }
801+ return len (pa .RequiredDuringSchedulingIgnoredDuringExecution ) == 0 &&
802+ len (pa .PreferredDuringSchedulingIgnoredDuringExecution ) == 0
803+ }
804+
805+ // isPodAntiAffinityEmpty checks if a PodAntiAffinity object is semantically empty.
806+ func isPodAntiAffinityEmpty (paa * corev1.PodAntiAffinity ) bool {
807+ if paa == nil {
808+ return true
809+ }
810+ return len (paa .RequiredDuringSchedulingIgnoredDuringExecution ) == 0 &&
811+ len (paa .PreferredDuringSchedulingIgnoredDuringExecution ) == 0
812+ }
813+
775814// applyAffinityConfig applies affinity configuration to the deployment's pod spec.
776- // This selectively overrides non-nil affinity sub-attributes.
777- // This follows OLMv0 behavior:
778- // https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L273-L341
815+ // This follows OLMv0 behavior where:
816+ // - nil affinity means "don't touch" the deployment's existing affinity
817+ // - empty affinity ({}) means "erase" the deployment's existing affinity
818+ // - non-nil sub-attributes override the corresponding deployment sub-attributes
819+ // - nil sub-attributes within a non-empty affinity are left unchanged
820+ // - empty sub-attributes ({}) erase the corresponding deployment sub-attributes
821+ //
822+ // See: https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L273-L341
779823func applyAffinityConfig (deployment * appsv1.Deployment , config * config.DeploymentConfig ) {
780824 if config .Affinity == nil {
781825 return
782826 }
783827
784- if deployment .Spec .Template .Spec .Affinity == nil {
785- deployment .Spec .Template .Spec .Affinity = & corev1.Affinity {}
828+ podSpec := & deployment .Spec .Template .Spec
829+
830+ // Check if the config specifies an empty affinity object with all fields unset.
831+ // This is different from having empty sub-fields - an empty affinity {} with no fields
832+ // means erase everything, while affinity with empty sub-fields means selectively erase.
833+ configHasNoFields := config .Affinity .NodeAffinity == nil &&
834+ config .Affinity .PodAffinity == nil &&
835+ config .Affinity .PodAntiAffinity == nil
836+
837+ if configHasNoFields {
838+ // Config is affinity: {} with no fields - erase entire affinity
839+ podSpec .Affinity = nil
840+ return
841+ }
842+
843+ if podSpec .Affinity == nil {
844+ podSpec .Affinity = & corev1.Affinity {}
786845 }
787846
788847 if config .Affinity .NodeAffinity != nil {
789- deployment .Spec .Template .Spec .Affinity .NodeAffinity = config .Affinity .NodeAffinity
848+ if isNodeAffinityEmpty (config .Affinity .NodeAffinity ) {
849+ podSpec .Affinity .NodeAffinity = nil
850+ } else {
851+ podSpec .Affinity .NodeAffinity = config .Affinity .NodeAffinity
852+ }
790853 }
791854
792855 if config .Affinity .PodAffinity != nil {
793- deployment .Spec .Template .Spec .Affinity .PodAffinity = config .Affinity .PodAffinity
856+ if isPodAffinityEmpty (config .Affinity .PodAffinity ) {
857+ podSpec .Affinity .PodAffinity = nil
858+ } else {
859+ podSpec .Affinity .PodAffinity = config .Affinity .PodAffinity
860+ }
794861 }
795862
796863 if config .Affinity .PodAntiAffinity != nil {
797- deployment .Spec .Template .Spec .Affinity .PodAntiAffinity = config .Affinity .PodAntiAffinity
864+ if isPodAntiAffinityEmpty (config .Affinity .PodAntiAffinity ) {
865+ podSpec .Affinity .PodAntiAffinity = nil
866+ } else {
867+ podSpec .Affinity .PodAntiAffinity = config .Affinity .PodAntiAffinity
868+ }
869+ }
870+
871+ if isAffinityEmpty (podSpec .Affinity ) {
872+ podSpec .Affinity = nil
798873 }
799874}
800875
0 commit comments