@@ -21,6 +21,7 @@ import (
2121 "github.com/operator-framework/api/pkg/operators/v1alpha1"
2222 registrybundle "github.com/operator-framework/operator-registry/pkg/lib/bundle"
2323
24+ "github.com/operator-framework/operator-controller/internal/operator-controller/config"
2425 "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle"
2526 "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render"
2627 "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util"
@@ -98,6 +99,9 @@ func BundleCSVDeploymentGenerator(rv1 *bundle.RegistryV1, opts render.Options) (
9899 ensureCorrectDeploymentCertVolumes (deploymentResource , * secretInfo )
99100 }
100101
102+ // Apply deployment configuration if provided
103+ applyCustomConfigToDeployment (deploymentResource , opts .DeploymentConfig )
104+
101105 objs = append (objs , deploymentResource )
102106 }
103107 return objs , nil
@@ -578,3 +582,251 @@ func getWebhookNamespaceSelector(targetNamespaces []string) *metav1.LabelSelecto
578582 }
579583 return nil
580584}
585+
586+ // applyCustomConfigToDeployment applies the deployment configuration to all containers in the deployment.
587+ // It follows OLMv0 behavior for applying configuration to deployments.
588+ // See https://github.com/operator-framework/operator-lifecycle-manager/blob/master/pkg/controller/operators/catalog/subscription/config_types.go
589+ func applyCustomConfigToDeployment (deployment * appsv1.Deployment , config * config.DeploymentConfig ) {
590+ if config == nil {
591+ return
592+ }
593+
594+ // Apply all configuration modifications following OLMv0 behavior
595+ applyEnvironmentConfig (deployment , config )
596+ applyEnvironmentFromConfig (deployment , config )
597+ applyVolumeConfig (deployment , config )
598+ applyVolumeMountConfig (deployment , config )
599+ applyTolerationsConfig (deployment , config )
600+ applyResourcesConfig (deployment , config )
601+ applyNodeSelectorConfig (deployment , config )
602+ applyAffinityConfig (deployment , config )
603+ applyAnnotationsConfig (deployment , config )
604+ }
605+
606+ // applyEnvironmentConfig applies environment variables to all containers in the deployment.
607+ // Environment variables from config override existing environment variables with the same name.
608+ // This follows OLMv0 behavior:
609+ // https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L11-L27
610+ func applyEnvironmentConfig (deployment * appsv1.Deployment , config * config.DeploymentConfig ) {
611+ if len (config .Env ) == 0 {
612+ return
613+ }
614+
615+ for i := range deployment .Spec .Template .Spec .Containers {
616+ container := & deployment .Spec .Template .Spec .Containers [i ]
617+
618+ // Create a map to track existing env var names for override behavior
619+ existingEnvMap := make (map [string ]int )
620+ for idx , env := range container .Env {
621+ existingEnvMap [env .Name ] = idx
622+ }
623+
624+ // Apply config env vars, overriding existing ones with same name
625+ for _ , configEnv := range config .Env {
626+ if existingIdx , exists := existingEnvMap [configEnv .Name ]; exists {
627+ // Override existing env var
628+ container .Env [existingIdx ] = configEnv
629+ } else {
630+ // Append new env var
631+ container .Env = append (container .Env , configEnv )
632+ }
633+ }
634+ }
635+ }
636+
637+ // applyEnvironmentFromConfig appends EnvFrom sources to all containers in the deployment.
638+ // Duplicate EnvFrom sources are not added.
639+ // This follows OLMv0 behavior:
640+ // https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L65-L81
641+ func applyEnvironmentFromConfig (deployment * appsv1.Deployment , config * config.DeploymentConfig ) {
642+ if len (config .EnvFrom ) == 0 {
643+ return
644+ }
645+
646+ for i := range deployment .Spec .Template .Spec .Containers {
647+ container := & deployment .Spec .Template .Spec .Containers [i ]
648+
649+ // Check for duplicates before appending
650+ for _ , configEnvFrom := range config .EnvFrom {
651+ isDuplicate := false
652+ for _ , existingEnvFrom := range container .EnvFrom {
653+ if envFromEquals (existingEnvFrom , configEnvFrom ) {
654+ isDuplicate = true
655+ break
656+ }
657+ }
658+ if ! isDuplicate {
659+ container .EnvFrom = append (container .EnvFrom , configEnvFrom )
660+ }
661+ }
662+ }
663+ }
664+
665+ // envFromEquals checks if two EnvFromSource objects are equal.
666+ func envFromEquals (a , b corev1.EnvFromSource ) bool {
667+ if a .Prefix != b .Prefix {
668+ return false
669+ }
670+
671+ if (a .ConfigMapRef == nil ) != (b .ConfigMapRef == nil ) {
672+ return false
673+ }
674+ if a .ConfigMapRef != nil && b .ConfigMapRef != nil {
675+ if a .ConfigMapRef .Name != b .ConfigMapRef .Name {
676+ return false
677+ }
678+ }
679+
680+ if (a .SecretRef == nil ) != (b .SecretRef == nil ) {
681+ return false
682+ }
683+ if a .SecretRef != nil && b .SecretRef != nil {
684+ if a .SecretRef .Name != b .SecretRef .Name {
685+ return false
686+ }
687+ }
688+
689+ return true
690+ }
691+
692+ // applyVolumeConfig appends volumes to the deployment's pod spec.
693+ // This follows OLMv0 behavior:
694+ // https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L104-L117
695+ func applyVolumeConfig (deployment * appsv1.Deployment , config * config.DeploymentConfig ) {
696+ if len (config .Volumes ) == 0 {
697+ return
698+ }
699+
700+ deployment .Spec .Template .Spec .Volumes = append (deployment .Spec .Template .Spec .Volumes , config .Volumes ... )
701+ }
702+
703+ // applyVolumeMountConfig appends volume mounts to all containers in the deployment.
704+ // This follows OLMv0 behavior:
705+ // https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L149-L165
706+ func applyVolumeMountConfig (deployment * appsv1.Deployment , config * config.DeploymentConfig ) {
707+ if len (config .VolumeMounts ) == 0 {
708+ return
709+ }
710+
711+ for i := range deployment .Spec .Template .Spec .Containers {
712+ container := & deployment .Spec .Template .Spec .Containers [i ]
713+ container .VolumeMounts = append (container .VolumeMounts , config .VolumeMounts ... )
714+ }
715+ }
716+
717+ // applyTolerationsConfig appends tolerations to the deployment's pod spec.
718+ // Duplicate tolerations are not added.
719+ // This follows OLMv0 behavior:
720+ // https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L197-L209
721+ func applyTolerationsConfig (deployment * appsv1.Deployment , config * config.DeploymentConfig ) {
722+ if len (config .Tolerations ) == 0 {
723+ return
724+ }
725+
726+ // Check for duplicates before appending
727+ for _ , configToleration := range config .Tolerations {
728+ isDuplicate := false
729+ for _ , existingToleration := range deployment .Spec .Template .Spec .Tolerations {
730+ if tolerationEquals (existingToleration , configToleration ) {
731+ isDuplicate = true
732+ break
733+ }
734+ }
735+ if ! isDuplicate {
736+ deployment .Spec .Template .Spec .Tolerations = append (deployment .Spec .Template .Spec .Tolerations , configToleration )
737+ }
738+ }
739+ }
740+
741+ // tolerationEquals checks if two Toleration objects are equal.
742+ func tolerationEquals (a , b corev1.Toleration ) bool {
743+ return a .Key == b .Key &&
744+ a .Operator == b .Operator &&
745+ a .Value == b .Value &&
746+ a .Effect == b .Effect &&
747+ ((a .TolerationSeconds == nil && b .TolerationSeconds == nil ) ||
748+ (a .TolerationSeconds != nil && b .TolerationSeconds != nil && * a .TolerationSeconds == * b .TolerationSeconds ))
749+ }
750+
751+ // applyResourcesConfig applies resource requirements to all containers in the deployment.
752+ // This completely replaces existing resource requirements.
753+ // This follows OLMv0 behavior:
754+ // https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L236-L255
755+ func applyResourcesConfig (deployment * appsv1.Deployment , config * config.DeploymentConfig ) {
756+ if config .Resources == nil {
757+ return
758+ }
759+
760+ for i := range deployment .Spec .Template .Spec .Containers {
761+ container := & deployment .Spec .Template .Spec .Containers [i ]
762+ container .Resources = * config .Resources
763+ }
764+ }
765+
766+ // applyNodeSelectorConfig applies node selector to the deployment's pod spec.
767+ // This completely replaces existing node selector.
768+ // This follows OLMv0 behavior:
769+ // https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L257-L271
770+ func applyNodeSelectorConfig (deployment * appsv1.Deployment , config * config.DeploymentConfig ) {
771+ if config .NodeSelector == nil {
772+ return
773+ }
774+
775+ deployment .Spec .Template .Spec .NodeSelector = config .NodeSelector
776+ }
777+
778+ // applyAffinityConfig applies affinity configuration to the deployment's pod spec.
779+ // This selectively overrides non-nil affinity sub-attributes.
780+ // This follows OLMv0 behavior:
781+ // https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L273-L341
782+ func applyAffinityConfig (deployment * appsv1.Deployment , config * config.DeploymentConfig ) {
783+ if config .Affinity == nil {
784+ return
785+ }
786+
787+ if deployment .Spec .Template .Spec .Affinity == nil {
788+ deployment .Spec .Template .Spec .Affinity = & corev1.Affinity {}
789+ }
790+
791+ if config .Affinity .NodeAffinity != nil {
792+ deployment .Spec .Template .Spec .Affinity .NodeAffinity = config .Affinity .NodeAffinity
793+ }
794+
795+ if config .Affinity .PodAffinity != nil {
796+ deployment .Spec .Template .Spec .Affinity .PodAffinity = config .Affinity .PodAffinity
797+ }
798+
799+ if config .Affinity .PodAntiAffinity != nil {
800+ deployment .Spec .Template .Spec .Affinity .PodAntiAffinity = config .Affinity .PodAntiAffinity
801+ }
802+ }
803+
804+ // applyAnnotationsConfig applies annotations to the deployment and its pod template.
805+ // Existing deployment and pod annotations take precedence over config annotations (no override).
806+ // This follows OLMv0 behavior:
807+ // https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L343-L378
808+ func applyAnnotationsConfig (deployment * appsv1.Deployment , config * config.DeploymentConfig ) {
809+ if len (config .Annotations ) == 0 {
810+ return
811+ }
812+
813+ // Apply to deployment metadata
814+ if deployment .Annotations == nil {
815+ deployment .Annotations = make (map [string ]string )
816+ }
817+ for key , value := range config .Annotations {
818+ if _ , exists := deployment .Annotations [key ]; ! exists {
819+ deployment .Annotations [key ] = value
820+ }
821+ }
822+
823+ // Apply to pod template metadata
824+ if deployment .Spec .Template .Annotations == nil {
825+ deployment .Spec .Template .Annotations = make (map [string ]string )
826+ }
827+ for key , value := range config .Annotations {
828+ if _ , exists := deployment .Spec .Template .Annotations [key ]; ! exists {
829+ deployment .Spec .Template .Annotations [key ] = value
830+ }
831+ }
832+ }
0 commit comments