@@ -772,29 +772,103 @@ 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+ return na .RequiredDuringSchedulingIgnoredDuringExecution == nil &&
792+ len (na .PreferredDuringSchedulingIgnoredDuringExecution ) == 0
793+ }
794+
795+ // isPodAffinityEmpty checks if a PodAffinity object is semantically empty.
796+ func isPodAffinityEmpty (pa * corev1.PodAffinity ) bool {
797+ if pa == nil {
798+ return true
799+ }
800+ return len (pa .RequiredDuringSchedulingIgnoredDuringExecution ) == 0 &&
801+ len (pa .PreferredDuringSchedulingIgnoredDuringExecution ) == 0
802+ }
803+
804+ // isPodAntiAffinityEmpty checks if a PodAntiAffinity object is semantically empty.
805+ func isPodAntiAffinityEmpty (paa * corev1.PodAntiAffinity ) bool {
806+ if paa == nil {
807+ return true
808+ }
809+ return len (paa .RequiredDuringSchedulingIgnoredDuringExecution ) == 0 &&
810+ len (paa .PreferredDuringSchedulingIgnoredDuringExecution ) == 0
811+ }
812+
775813// 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
814+ // This follows OLMv0 behavior where:
815+ // - nil affinity means "don't touch" the deployment's existing affinity
816+ // - empty affinity ({}) means "erase" the deployment's existing affinity
817+ // - non-nil sub-attributes override the corresponding deployment sub-attributes
818+ // - nil sub-attributes within a non-empty affinity are left unchanged
819+ // - empty sub-attributes ({}) erase the corresponding deployment sub-attributes
820+ //
821+ // See: https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L273-L341
779822func applyAffinityConfig (deployment * appsv1.Deployment , config * config.DeploymentConfig ) {
780823 if config .Affinity == nil {
781824 return
782825 }
783826
784- if deployment .Spec .Template .Spec .Affinity == nil {
785- deployment .Spec .Template .Spec .Affinity = & corev1.Affinity {}
827+ podSpec := & deployment .Spec .Template .Spec
828+
829+ // Check if the config specifies an empty affinity object with all fields unset.
830+ // This is different from having empty sub-fields - an empty affinity {} with no fields
831+ // means erase everything, while affinity with empty sub-fields means selectively erase.
832+ configHasNoFields := config .Affinity .NodeAffinity == nil &&
833+ config .Affinity .PodAffinity == nil &&
834+ config .Affinity .PodAntiAffinity == nil
835+
836+ if configHasNoFields {
837+ // Config is affinity: {} with no fields - erase entire affinity
838+ podSpec .Affinity = nil
839+ return
840+ }
841+
842+ if podSpec .Affinity == nil {
843+ podSpec .Affinity = & corev1.Affinity {}
786844 }
787845
788846 if config .Affinity .NodeAffinity != nil {
789- deployment .Spec .Template .Spec .Affinity .NodeAffinity = config .Affinity .NodeAffinity
847+ if isNodeAffinityEmpty (config .Affinity .NodeAffinity ) {
848+ podSpec .Affinity .NodeAffinity = nil
849+ } else {
850+ podSpec .Affinity .NodeAffinity = config .Affinity .NodeAffinity
851+ }
790852 }
791853
792854 if config .Affinity .PodAffinity != nil {
793- deployment .Spec .Template .Spec .Affinity .PodAffinity = config .Affinity .PodAffinity
855+ if isPodAffinityEmpty (config .Affinity .PodAffinity ) {
856+ podSpec .Affinity .PodAffinity = nil
857+ } else {
858+ podSpec .Affinity .PodAffinity = config .Affinity .PodAffinity
859+ }
794860 }
795861
796862 if config .Affinity .PodAntiAffinity != nil {
797- deployment .Spec .Template .Spec .Affinity .PodAntiAffinity = config .Affinity .PodAntiAffinity
863+ if isPodAntiAffinityEmpty (config .Affinity .PodAntiAffinity ) {
864+ podSpec .Affinity .PodAntiAffinity = nil
865+ } else {
866+ podSpec .Affinity .PodAntiAffinity = config .Affinity .PodAntiAffinity
867+ }
868+ }
869+
870+ if isAffinityEmpty (podSpec .Affinity ) {
871+ podSpec .Affinity = nil
798872 }
799873}
800874
0 commit comments