Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions internal/operator-controller/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import (

"github.com/santhosh-tekuri/jsonschema/v6"
"sigs.k8s.io/yaml"

"github.com/operator-framework/api/pkg/operators/v1alpha1"
)

const (
Expand All @@ -47,6 +49,10 @@ const (
FormatSingleNamespaceInstallMode = "singleNamespaceInstallMode"
)

// DeploymentConfig is a type alias for v1alpha1.SubscriptionConfig
// to maintain clear naming in the OLMv1 context while reusing the v0 type.
type DeploymentConfig = v1alpha1.SubscriptionConfig
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we weren't going to use SubscriptionConfig because of the extra selector field?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the RFC:

NOTE: The v0 SubscriptionConfig struct also has a [Selector](https://github.com/operator-framework/api/blob/master/pkg/operators/v1alpha1/subscription_types.go#L47) field, however it is completely ignored by the v0 controller. According to the comment in the code, it was meant to: 
Select which pods/ReplicaSets would receive the subscription’s configuration
Match pod template labels(similar to Deployment selectors) 
However, the code that extracts subscription config overrides(pkg/controller/operators/olm/overrides/config.go) extracts all other fields like NodeSelector, Tolerations, Resources and are subsequently applied, but the Selector field is never extracted. 

OLMv1 will maintain the same behavior—accepting but ignoring this field.

Notice that there's no applySelectorConfig() function, so we're just ignoring that field.

With our wish to reuse v0 SubscriptionConfig for maintainability, we carry over nuance/s into v1. Silently ignoring the selector field being one of them.

We have to remember to document this explicitly for our users everywhere for added clarity.


// SchemaProvider lets each package format type describe what configuration it accepts.
//
// Different package format types provide schemas in different ways:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package generators
import (
"cmp"
"fmt"
"reflect"
"slices"
"strconv"
"strings"
Expand All @@ -21,6 +22,7 @@ import (
"github.com/operator-framework/api/pkg/operators/v1alpha1"
registrybundle "github.com/operator-framework/operator-registry/pkg/lib/bundle"

"github.com/operator-framework/operator-controller/internal/operator-controller/config"
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle"
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render"
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util"
Expand Down Expand Up @@ -98,6 +100,9 @@ func BundleCSVDeploymentGenerator(rv1 *bundle.RegistryV1, opts render.Options) (
ensureCorrectDeploymentCertVolumes(deploymentResource, *secretInfo)
}

// Apply deployment configuration if provided
applyCustomConfigToDeployment(deploymentResource, opts.DeploymentConfig)

objs = append(objs, deploymentResource)
}
return objs, nil
Expand Down Expand Up @@ -578,3 +583,214 @@ func getWebhookNamespaceSelector(targetNamespaces []string) *metav1.LabelSelecto
}
return nil
}

// applyCustomConfigToDeployment applies the deployment configuration to all containers in the deployment.
// It follows OLMv0 behavior for applying configuration to deployments.
// See https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go
func applyCustomConfigToDeployment(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
if config == nil {
return
}

// Apply all configuration modifications following OLMv0 behavior
applyEnvironmentConfig(deployment, config)
applyEnvironmentFromConfig(deployment, config)
applyVolumeConfig(deployment, config)
applyVolumeMountConfig(deployment, config)
applyTolerationsConfig(deployment, config)
applyResourcesConfig(deployment, config)
applyNodeSelectorConfig(deployment, config)
applyAffinityConfig(deployment, config)
applyAnnotationsConfig(deployment, config)
}

// applyEnvironmentConfig applies environment variables to all containers in the deployment.
// Environment variables from config override existing environment variables with the same name.
// This follows OLMv0 behavior:
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L11-L27
func applyEnvironmentConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
if len(config.Env) == 0 {
return
}

for i := range deployment.Spec.Template.Spec.Containers {
container := &deployment.Spec.Template.Spec.Containers[i]

// Create a map to track existing env var names for override behavior
existingEnvMap := make(map[string]int)
for idx, env := range container.Env {
existingEnvMap[env.Name] = idx
}

// Apply config env vars, overriding existing ones with same name
for _, configEnv := range config.Env {
if existingIdx, exists := existingEnvMap[configEnv.Name]; exists {
// Override existing env var
container.Env[existingIdx] = configEnv
} else {
// Append new env var
container.Env = append(container.Env, configEnv)
}
}
}
}

// applyEnvironmentFromConfig appends EnvFrom sources to all containers in the deployment.
// Duplicate EnvFrom sources are not added.
// This follows OLMv0 behavior:
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L65-L81
func applyEnvironmentFromConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
if len(config.EnvFrom) == 0 {
return
}

for i := range deployment.Spec.Template.Spec.Containers {
container := &deployment.Spec.Template.Spec.Containers[i]

// Check for duplicates before appending
for _, configEnvFrom := range config.EnvFrom {
isDuplicate := false
for _, existingEnvFrom := range container.EnvFrom {
if reflect.DeepEqual(existingEnvFrom, configEnvFrom) {
isDuplicate = true
break
}
}
if !isDuplicate {
container.EnvFrom = append(container.EnvFrom, configEnvFrom)
}
}
}
}

// applyVolumeConfig appends volumes to the deployment's pod spec.
// This follows OLMv0 behavior:
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L104-L117
func applyVolumeConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
if len(config.Volumes) == 0 {
return
}

deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes, config.Volumes...)
}

// applyVolumeMountConfig appends volume mounts to all containers in the deployment.
// This follows OLMv0 behavior:
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L149-L165
func applyVolumeMountConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
if len(config.VolumeMounts) == 0 {
return
}

for i := range deployment.Spec.Template.Spec.Containers {
container := &deployment.Spec.Template.Spec.Containers[i]
container.VolumeMounts = append(container.VolumeMounts, config.VolumeMounts...)
}
}

// applyTolerationsConfig appends tolerations to the deployment's pod spec.
// Duplicate tolerations are not added.
// This follows OLMv0 behavior:
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L197-L209
func applyTolerationsConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
if len(config.Tolerations) == 0 {
return
}

// Check for duplicates before appending
for _, configToleration := range config.Tolerations {
isDuplicate := false
for _, existingToleration := range deployment.Spec.Template.Spec.Tolerations {
if reflect.DeepEqual(existingToleration, configToleration) {
isDuplicate = true
break
}
}
if !isDuplicate {
deployment.Spec.Template.Spec.Tolerations = append(deployment.Spec.Template.Spec.Tolerations, configToleration)
}
}
}

// applyResourcesConfig applies resource requirements to all containers in the deployment.
// This completely replaces existing resource requirements.
// This follows OLMv0 behavior:
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L236-L255
func applyResourcesConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
if config.Resources == nil {
return
}

for i := range deployment.Spec.Template.Spec.Containers {
container := &deployment.Spec.Template.Spec.Containers[i]
container.Resources = *config.Resources
}
}

// applyNodeSelectorConfig applies node selector to the deployment's pod spec.
// This completely replaces existing node selector.
// This follows OLMv0 behavior:
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L257-L271
func applyNodeSelectorConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
if config.NodeSelector == nil {
return
}

deployment.Spec.Template.Spec.NodeSelector = config.NodeSelector
}

// applyAffinityConfig applies affinity configuration to the deployment's pod spec.
// This selectively overrides non-nil affinity sub-attributes.
// This follows OLMv0 behavior:
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L273-L341
func applyAffinityConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
if config.Affinity == nil {
return
}

if deployment.Spec.Template.Spec.Affinity == nil {
deployment.Spec.Template.Spec.Affinity = &corev1.Affinity{}
}

if config.Affinity.NodeAffinity != nil {
deployment.Spec.Template.Spec.Affinity.NodeAffinity = config.Affinity.NodeAffinity
}

if config.Affinity.PodAffinity != nil {
deployment.Spec.Template.Spec.Affinity.PodAffinity = config.Affinity.PodAffinity
}

if config.Affinity.PodAntiAffinity != nil {
deployment.Spec.Template.Spec.Affinity.PodAntiAffinity = config.Affinity.PodAntiAffinity
}
}

// applyAnnotationsConfig applies annotations to the deployment and its pod template.
// Existing deployment and pod annotations take precedence over config annotations (no override).
// This follows OLMv0 behavior:
// https://github.com/operator-framework/operator-lifecycle-manager/blob/v0.39.0/pkg/controller/operators/olm/overrides/inject/inject.go#L343-L378
func applyAnnotationsConfig(deployment *appsv1.Deployment, config *config.DeploymentConfig) {
if len(config.Annotations) == 0 {
return
}

// Apply to deployment metadata
if deployment.Annotations == nil {
deployment.Annotations = make(map[string]string)
}
for key, value := range config.Annotations {
if _, exists := deployment.Annotations[key]; !exists {
deployment.Annotations[key] = value
}
}

// Apply to pod template metadata
if deployment.Spec.Template.Annotations == nil {
deployment.Spec.Template.Annotations = make(map[string]string)
}
for key, value := range config.Annotations {
if _, exists := deployment.Spec.Template.Annotations[key]; !exists {
deployment.Spec.Template.Annotations[key] = value
}
}
}
Loading
Loading