Skip to content

Commit 8daa35d

Browse files
authored
feat: Add DeploymentConfig support to registry+v1 bundle renderer (#2469)
This PR implements **Phase 2** of the [Deployment Configuration RFC](https://docs.google.com/document/d/18O4qBvu5I4WIJgo5KU1opyUKcrfgk64xsI3tyXxmVEU/edit?tab=t.0): extending the OLMv1 bundle renderer to apply `DeploymentConfig` customizations to operator deployments. Building on the foundation from #2454, this PR enables the renderer to accept and apply deployment configuration when rendering registry+v1 bundles. The implementation follows OLMv0's behavior patterns to ensure compatibility and correctness. The next PR will wire up the config in the `ClusterExtension` controller by parsing `spec.install.config` to convert to `DeploymentConfig` and thread `DeploymentConfig` through the controller's render call chain
1 parent a72b79f commit 8daa35d

5 files changed

Lines changed: 896 additions & 0 deletions

File tree

internal/operator-controller/config/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import (
3333
"github.com/santhosh-tekuri/jsonschema/v6"
3434
"github.com/santhosh-tekuri/jsonschema/v6/kind"
3535
"sigs.k8s.io/yaml"
36+
37+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
3638
)
3739

3840
const (
@@ -48,6 +50,10 @@ const (
4850
FormatSingleNamespaceInstallMode = "singleNamespaceInstallMode"
4951
)
5052

53+
// DeploymentConfig is a type alias for v1alpha1.SubscriptionConfig
54+
// to maintain clear naming in the OLMv1 context while reusing the v0 type.
55+
type DeploymentConfig = v1alpha1.SubscriptionConfig
56+
5157
// SchemaProvider lets each package format type describe what configuration it accepts.
5258
//
5359
// Different package format types provide schemas in different ways:

internal/operator-controller/rukpak/render/registryv1/generators/generators.go

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package generators
33
import (
44
"cmp"
55
"fmt"
6+
"reflect"
67
"slices"
78
"strconv"
89
"strings"
@@ -21,6 +22,7 @@ import (
2122
"github.com/operator-framework/api/pkg/operators/v1alpha1"
2223
registrybundle "github.com/operator-framework/operator-registry/pkg/lib/bundle"
2324

25+
"github.com/operator-framework/operator-controller/internal/operator-controller/config"
2426
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle"
2527
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render"
2628
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util"
@@ -98,6 +100,9 @@ func BundleCSVDeploymentGenerator(rv1 *bundle.RegistryV1, opts render.Options) (
98100
ensureCorrectDeploymentCertVolumes(deploymentResource, *secretInfo)
99101
}
100102

103+
// Apply deployment configuration if provided
104+
applyCustomConfigToDeployment(deploymentResource, opts.DeploymentConfig)
105+
101106
objs = append(objs, deploymentResource)
102107
}
103108
return objs, nil
@@ -578,3 +583,214 @@ func getWebhookNamespaceSelector(targetNamespaces []string) *metav1.LabelSelecto
578583
}
579584
return nil
580585
}
586+
587+
// applyCustomConfigToDeployment applies the deployment configuration to all containers in the deployment.
588+
// It follows OLMv0 behavior for applying configuration to deployments.
589+
// See https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go
590+
func applyCustomConfigToDeployment(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
591+
if config == nil {
592+
return
593+
}
594+
595+
// Apply all configuration modifications following OLMv0 behavior
596+
applyEnvironmentConfig(deployment, config)
597+
applyEnvironmentFromConfig(deployment, config)
598+
applyVolumeConfig(deployment, config)
599+
applyVolumeMountConfig(deployment, config)
600+
applyTolerationsConfig(deployment, config)
601+
applyResourcesConfig(deployment, config)
602+
applyNodeSelectorConfig(deployment, config)
603+
applyAffinityConfig(deployment, config)
604+
applyAnnotationsConfig(deployment, config)
605+
}
606+
607+
// applyEnvironmentConfig applies environment variables to all containers in the deployment.
608+
// Environment variables from config override existing environment variables with the same name.
609+
// This follows OLMv0 behavior:
610+
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L11-L27
611+
func applyEnvironmentConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
612+
if len(config.Env) == 0 {
613+
return
614+
}
615+
616+
for i := range deployment.Spec.Template.Spec.Containers {
617+
container := &deployment.Spec.Template.Spec.Containers[i]
618+
619+
// Create a map to track existing env var names for override behavior
620+
existingEnvMap := make(map[string]int)
621+
for idx, env := range container.Env {
622+
existingEnvMap[env.Name] = idx
623+
}
624+
625+
// Apply config env vars, overriding existing ones with same name
626+
for _, configEnv := range config.Env {
627+
if existingIdx, exists := existingEnvMap[configEnv.Name]; exists {
628+
// Override existing env var
629+
container.Env[existingIdx] = configEnv
630+
} else {
631+
// Append new env var
632+
container.Env = append(container.Env, configEnv)
633+
}
634+
}
635+
}
636+
}
637+
638+
// applyEnvironmentFromConfig appends EnvFrom sources to all containers in the deployment.
639+
// Duplicate EnvFrom sources are not added.
640+
// This follows OLMv0 behavior:
641+
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L65-L81
642+
func applyEnvironmentFromConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
643+
if len(config.EnvFrom) == 0 {
644+
return
645+
}
646+
647+
for i := range deployment.Spec.Template.Spec.Containers {
648+
container := &deployment.Spec.Template.Spec.Containers[i]
649+
650+
// Check for duplicates before appending
651+
for _, configEnvFrom := range config.EnvFrom {
652+
isDuplicate := false
653+
for _, existingEnvFrom := range container.EnvFrom {
654+
if reflect.DeepEqual(existingEnvFrom, configEnvFrom) {
655+
isDuplicate = true
656+
break
657+
}
658+
}
659+
if !isDuplicate {
660+
container.EnvFrom = append(container.EnvFrom, configEnvFrom)
661+
}
662+
}
663+
}
664+
}
665+
666+
// applyVolumeConfig appends volumes to the deployment's pod spec.
667+
// This follows OLMv0 behavior:
668+
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L104-L117
669+
func applyVolumeConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
670+
if len(config.Volumes) == 0 {
671+
return
672+
}
673+
674+
deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes, config.Volumes...)
675+
}
676+
677+
// applyVolumeMountConfig appends volume mounts to all containers in the deployment.
678+
// This follows OLMv0 behavior:
679+
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L149-L165
680+
func applyVolumeMountConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
681+
if len(config.VolumeMounts) == 0 {
682+
return
683+
}
684+
685+
for i := range deployment.Spec.Template.Spec.Containers {
686+
container := &deployment.Spec.Template.Spec.Containers[i]
687+
container.VolumeMounts = append(container.VolumeMounts, config.VolumeMounts...)
688+
}
689+
}
690+
691+
// applyTolerationsConfig appends tolerations to the deployment's pod spec.
692+
// Duplicate tolerations are not added.
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#L197-L209
695+
func applyTolerationsConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
696+
if len(config.Tolerations) == 0 {
697+
return
698+
}
699+
700+
// Check for duplicates before appending
701+
for _, configToleration := range config.Tolerations {
702+
isDuplicate := false
703+
for _, existingToleration := range deployment.Spec.Template.Spec.Tolerations {
704+
if reflect.DeepEqual(existingToleration, configToleration) {
705+
isDuplicate = true
706+
break
707+
}
708+
}
709+
if !isDuplicate {
710+
deployment.Spec.Template.Spec.Tolerations = append(deployment.Spec.Template.Spec.Tolerations, configToleration)
711+
}
712+
}
713+
}
714+
715+
// applyResourcesConfig applies resource requirements to all containers in the deployment.
716+
// This completely replaces existing resource requirements.
717+
// This follows OLMv0 behavior:
718+
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L236-L255
719+
func applyResourcesConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
720+
if config.Resources == nil {
721+
return
722+
}
723+
724+
for i := range deployment.Spec.Template.Spec.Containers {
725+
container := &deployment.Spec.Template.Spec.Containers[i]
726+
container.Resources = *config.Resources
727+
}
728+
}
729+
730+
// applyNodeSelectorConfig applies node selector to the deployment's pod spec.
731+
// This completely replaces existing node selector.
732+
// This follows OLMv0 behavior:
733+
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L257-L271
734+
func applyNodeSelectorConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
735+
if config.NodeSelector == nil {
736+
return
737+
}
738+
739+
deployment.Spec.Template.Spec.NodeSelector = config.NodeSelector
740+
}
741+
742+
// applyAffinityConfig applies affinity configuration to the deployment's pod spec.
743+
// This selectively overrides non-nil affinity sub-attributes.
744+
// This follows OLMv0 behavior:
745+
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L273-L341
746+
func applyAffinityConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
747+
if config.Affinity == nil {
748+
return
749+
}
750+
751+
if deployment.Spec.Template.Spec.Affinity == nil {
752+
deployment.Spec.Template.Spec.Affinity = &corev1.Affinity{}
753+
}
754+
755+
if config.Affinity.NodeAffinity != nil {
756+
deployment.Spec.Template.Spec.Affinity.NodeAffinity = config.Affinity.NodeAffinity
757+
}
758+
759+
if config.Affinity.PodAffinity != nil {
760+
deployment.Spec.Template.Spec.Affinity.PodAffinity = config.Affinity.PodAffinity
761+
}
762+
763+
if config.Affinity.PodAntiAffinity != nil {
764+
deployment.Spec.Template.Spec.Affinity.PodAntiAffinity = config.Affinity.PodAntiAffinity
765+
}
766+
}
767+
768+
// applyAnnotationsConfig applies annotations to the deployment and its pod template.
769+
// Existing deployment and pod annotations take precedence over config annotations (no override).
770+
// This follows OLMv0 behavior:
771+
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L343-L378
772+
func applyAnnotationsConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
773+
if len(config.Annotations) == 0 {
774+
return
775+
}
776+
777+
// Apply to deployment metadata
778+
if deployment.Annotations == nil {
779+
deployment.Annotations = make(map[string]string)
780+
}
781+
for key, value := range config.Annotations {
782+
if _, exists := deployment.Annotations[key]; !exists {
783+
deployment.Annotations[key] = value
784+
}
785+
}
786+
787+
// Apply to pod template metadata
788+
if deployment.Spec.Template.Annotations == nil {
789+
deployment.Spec.Template.Annotations = make(map[string]string)
790+
}
791+
for key, value := range config.Annotations {
792+
if _, exists := deployment.Spec.Template.Annotations[key]; !exists {
793+
deployment.Spec.Template.Annotations[key] = value
794+
}
795+
}
796+
}

0 commit comments

Comments
 (0)