From 0dbc4daee9b0df85fc4b73d3db136432e3ef52b3 Mon Sep 17 00:00:00 2001 From: Swarup Ghosh Date: Mon, 6 Apr 2026 13:56:13 +0530 Subject: [PATCH 1/2] feat: add API types for ExternalSecretsConfig component overrides (EP-1898) Extend the ExternalSecretsConfig API with: - annotations: global custom annotations for all operand Deployments and Pod templates - componentConfigs: per-component configuration overrides (Controller, Webhook, CertController, BitwardenSDKServer) - deploymentConfig: deployment-level overrides including revisionHistoryLimit - overrideEnv: custom environment variables per component with reserved prefix validation Also extends ComponentName enum with Webhook and CertController values, adds KVPair and Annotation types for structured annotation configuration, and includes comprehensive integration test coverage for all new fields and validation rules. Ref: https://github.com/openshift/enhancements/pull/1898 Co-Authored-By: Claude Opus 4.6 --- api/v1alpha1/external_secrets_config_types.go | 85 ++- api/v1alpha1/meta.go | 23 + .../externalsecretsconfig.testsuite.yaml | 678 +++++++++++++++++- api/v1alpha1/zz_generated.deepcopy.go | 86 +++ ...r.openshift.io_externalsecretsconfigs.yaml | 223 ++++++ 5 files changed, 1090 insertions(+), 5 deletions(-) diff --git a/api/v1alpha1/external_secrets_config_types.go b/api/v1alpha1/external_secrets_config_types.go index f06a7df79..2ffcd7203 100644 --- a/api/v1alpha1/external_secrets_config_types.go +++ b/api/v1alpha1/external_secrets_config_types.go @@ -1,6 +1,7 @@ package v1alpha1 import ( + corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -113,6 +114,35 @@ type ControllerConfig struct { // +kubebuilder:validation:Optional Labels map[string]string `json:"labels,omitempty"` + // annotations allows adding custom annotations to all external-secrets component + // Deployments and Pod templates. These annotations are applied globally to all + // operand components (Controller, Webhook, CertController, BitwardenSDKServer). + // These annotations are merged with any default annotations set by the operator. + // User-specified annotations take precedence over defaults in case of conflicts. + // Annotations with keys starting with kubernetes.io/, app.kubernetes.io/, openshift.io/, or k8s.io/ + // are reserved and cannot be overridden. + // + // +kubebuilder:validation:XValidation:rule="self.all(a, !['kubernetes.io/', 'app.kubernetes.io/', 'openshift.io/', 'k8s.io/'].exists(p, a.key.startsWith(p)))",message="annotations with reserved prefixes 'kubernetes.io/', 'app.kubernetes.io/', 'openshift.io/', 'k8s.io/' are not allowed" + // +kubebuilder:validation:MaxItems:=50 + // +kubebuilder:validation:Optional + // +listType=map + // +listMapKey=key + // +optional + Annotations []Annotation `json:"annotations,omitempty"` + + // componentConfigs allows specifying component-specific configuration overrides for + // individual external-secrets components (Controller, Webhook, CertController, BitwardenSDKServer). + // Each entry configures deployment-level overrides and custom environment variables for a single component. + // The componentName must be unique across all entries. + // + // +kubebuilder:validation:XValidation:rule="self.all(x, self.exists_one(y, x.componentName == y.componentName))",message="componentName must be unique across all componentConfig entries" + // +kubebuilder:validation:MaxItems:=4 + // +kubebuilder:validation:Optional + // +listType=map + // +listMapKey=componentName + // +optional + ComponentConfigs []ComponentConfig `json:"componentConfigs,omitempty"` + // networkPolicies specifies the list of network policy configurations // to be applied to external-secrets pods. // @@ -212,14 +242,61 @@ type CertProvidersConfig struct { CertManager *CertManagerConfig `json:"certManager,omitempty"` } -// ComponentName represents the different external-secrets components that can have network policies applied. +// ComponentConfig allows specifying configuration overrides for a single external-secrets component. +// This includes deployment-level configuration such as revisionHistoryLimit and custom environment variables. +type ComponentConfig struct { + // componentName specifies which deployment component this configuration applies to. + // Each component can only appear once in the componentConfigs list. + // +kubebuilder:validation:Enum:=ExternalSecretsCoreController;Webhook;CertController;BitwardenSDKServer + // +kubebuilder:validation:Required + ComponentName ComponentName `json:"componentName"` + + // deploymentConfig allows specifying deployment-level configuration overrides + // for the specified component. + // +kubebuilder:validation:Optional + // +optional + DeploymentConfig DeploymentConfig `json:"deploymentConfig,omitempty"` + + // overrideEnv allows setting custom environment variables for the component's container. + // These environment variables are merged with the default environment variables set by + // the operator. User-specified variables take precedence in case of conflicts. + // Environment variables with names starting with HOSTNAME, KUBERNETES_, or EXTERNAL_SECRETS_ are reserved + // and cannot be overridden. + // + // +kubebuilder:validation:XValidation:rule="self.all(e, !['HOSTNAME', 'KUBERNETES_', 'EXTERNAL_SECRETS_'].exists(p, e.name.startsWith(p)))",message="environment variable names with reserved prefixes 'HOSTNAME', 'KUBERNETES_', 'EXTERNAL_SECRETS_' are not allowed" + // +kubebuilder:validation:MaxItems:=50 + // +kubebuilder:validation:Optional + // +listType=map + // +listMapKey=name + // +optional + OverrideEnv []corev1.EnvVar `json:"overrideEnv,omitempty"` +} + +// DeploymentConfig allows specifying deployment-level configuration overrides. +type DeploymentConfig struct { + // revisionHistoryLimit specifies the number of old ReplicaSets to retain for rollback. + // Minimum value of 1 is enforced to ensure rollback capability. + // + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Optional + // +optional + RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty"` +} + +// ComponentName represents the different external-secrets components that can be configured. type ComponentName string const ( - // CoreController represents the external-secrets component + // CoreController represents the external-secrets core controller component. CoreController ComponentName = "ExternalSecretsCoreController" - // BitwardenSDKServer represents the bitwarden-sdk-server component + // Webhook represents the external-secrets webhook component. + Webhook ComponentName = "Webhook" + + // CertController represents the external-secrets cert-controller component. + CertController ComponentName = "CertController" + + // BitwardenSDKServer represents the bitwarden-sdk-server component. BitwardenSDKServer ComponentName = "BitwardenSDKServer" ) @@ -234,7 +311,7 @@ type NetworkPolicy struct { Name string `json:"name"` // componentName specifies which external-secrets component this network policy applies to. - // +kubebuilder:validation:Enum:=ExternalSecretsCoreController;BitwardenSDKServer + // +kubebuilder:validation:Enum:=ExternalSecretsCoreController;Webhook;CertController;BitwardenSDKServer // +kubebuilder:validation:Required ComponentName ComponentName `json:"componentName"` diff --git a/api/v1alpha1/meta.go b/api/v1alpha1/meta.go index 68a273d97..c66f6987e 100644 --- a/api/v1alpha1/meta.go +++ b/api/v1alpha1/meta.go @@ -112,6 +112,29 @@ type ProxyConfig struct { NoProxy string `json:"noProxy,omitempty"` } +// KVPair represents a generic key-value pair for configuration. +type KVPair struct { + // key is the name of the key-value pair. + // This field must be non-empty and can have a maximum of 317 characters. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=317 + // +kubebuilder:validation:Required + Key string `json:"key"` + + // value is the value of the key-value pair. + // This field can have a maximum of 4096 characters. + // +kubebuilder:validation:MaxLength:=4096 + // +kubebuilder:validation:Optional + Value string `json:"value,omitempty"` +} + +// Annotation represents a custom annotation key-value pair. +// It embeds KVPair inline for reusability. +type Annotation struct { + // Embedded KVPair provides key and value fields. + KVPair `json:",inline"` +} + // Mode indicates the operational state of the optional features. type Mode string diff --git a/api/v1alpha1/tests/externalsecretsconfig.operator.openshift.io/externalsecretsconfig.testsuite.yaml b/api/v1alpha1/tests/externalsecretsconfig.operator.openshift.io/externalsecretsconfig.testsuite.yaml index 720d7fc23..7583f3112 100644 --- a/api/v1alpha1/tests/externalsecretsconfig.operator.openshift.io/externalsecretsconfig.testsuite.yaml +++ b/api/v1alpha1/tests/externalsecretsconfig.operator.openshift.io/externalsecretsconfig.testsuite.yaml @@ -493,6 +493,500 @@ tests: webhookConfig: certificateCheckInterval: "15m" operatingNamespace: "test-ns" + - name: Should be able to create ExternalSecretsConfig with custom annotations + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "example.com/custom-annotation" + value: "my-value" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "example.com/custom-annotation" + value: "my-value" + - name: Should be able to create ExternalSecretsConfig with multiple annotations + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "example.com/annotation-one" + value: "value-one" + - key: "example.com/annotation-two" + value: "value-two" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "example.com/annotation-one" + value: "value-one" + - key: "example.com/annotation-two" + value: "value-two" + - name: Should fail to create with annotation using reserved prefix kubernetes.io/ + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "kubernetes.io/some-annotation" + value: "forbidden" + expectedError: "annotations with reserved prefixes 'kubernetes.io/', 'app.kubernetes.io/', 'openshift.io/', 'k8s.io/' are not allowed" + - name: Should fail to create with annotation using reserved prefix app.kubernetes.io/ + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "app.kubernetes.io/name" + value: "forbidden" + expectedError: "annotations with reserved prefixes 'kubernetes.io/', 'app.kubernetes.io/', 'openshift.io/', 'k8s.io/' are not allowed" + - name: Should fail to create with annotation using reserved prefix openshift.io/ + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "openshift.io/some-annotation" + value: "forbidden" + expectedError: "annotations with reserved prefixes 'kubernetes.io/', 'app.kubernetes.io/', 'openshift.io/', 'k8s.io/' are not allowed" + - name: Should fail to create with annotation using reserved prefix k8s.io/ + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "k8s.io/some-annotation" + value: "forbidden" + expectedError: "annotations with reserved prefixes 'kubernetes.io/', 'app.kubernetes.io/', 'openshift.io/', 'k8s.io/' are not allowed" + - name: Should fail to create with annotation missing required key + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - value: "value-without-key" + expectedError: "spec.controllerConfig.annotations[0].key: Required value" + - name: Should be able to create ExternalSecretsConfig with componentConfigs for Controller + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 5 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 5 + - name: Should be able to create ExternalSecretsConfig with componentConfigs for Webhook + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: Webhook + deploymentConfig: + revisionHistoryLimit: 3 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: Webhook + deploymentConfig: + revisionHistoryLimit: 3 + - name: Should be able to create ExternalSecretsConfig with componentConfigs for CertController + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: CertController + deploymentConfig: + revisionHistoryLimit: 3 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: CertController + deploymentConfig: + revisionHistoryLimit: 3 + - name: Should be able to create ExternalSecretsConfig with componentConfigs for BitwardenSDKServer + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: BitwardenSDKServer + deploymentConfig: + revisionHistoryLimit: 2 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: BitwardenSDKServer + deploymentConfig: + revisionHistoryLimit: 2 + - name: Should be able to create ExternalSecretsConfig with multiple componentConfigs + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 10 + - componentName: Webhook + deploymentConfig: + revisionHistoryLimit: 3 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 10 + - componentName: Webhook + deploymentConfig: + revisionHistoryLimit: 3 + - name: Should be able to create ExternalSecretsConfig with all four componentConfigs + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 10 + - componentName: Webhook + deploymentConfig: + revisionHistoryLimit: 3 + - componentName: CertController + deploymentConfig: + revisionHistoryLimit: 5 + - componentName: BitwardenSDKServer + deploymentConfig: + revisionHistoryLimit: 2 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 10 + - componentName: Webhook + deploymentConfig: + revisionHistoryLimit: 3 + - componentName: CertController + deploymentConfig: + revisionHistoryLimit: 5 + - componentName: BitwardenSDKServer + deploymentConfig: + revisionHistoryLimit: 2 + - name: Should fail to create with invalid componentName + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: InvalidComponent + deploymentConfig: + revisionHistoryLimit: 5 + expectedError: "spec.controllerConfig.componentConfigs[0].componentName: Unsupported value: \"InvalidComponent\"" + - name: Should fail to create with duplicate componentName in componentConfigs + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 5 + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 10 + expectedError: "spec.controllerConfig.componentConfigs[1].componentName: Duplicate value: \"ExternalSecretsCoreController\"" + - name: Should fail to create with revisionHistoryLimit less than 1 + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 0 + expectedError: "spec.controllerConfig.componentConfigs[0].deploymentConfig.revisionHistoryLimit: Invalid value: 0: spec.controllerConfig.componentConfigs[0].deploymentConfig.revisionHistoryLimit in body should be greater than or equal to 1" + - name: Should be able to create ExternalSecretsConfig with overrideEnv + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + overrideEnv: + - name: GOMAXPROCS + value: "4" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + overrideEnv: + - name: GOMAXPROCS + value: "4" + - name: Should be able to create ExternalSecretsConfig with multiple overrideEnv entries + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: Webhook + overrideEnv: + - name: GOMAXPROCS + value: "2" + - name: LOG_FORMAT + value: "json" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: Webhook + overrideEnv: + - name: GOMAXPROCS + value: "2" + - name: LOG_FORMAT + value: "json" + - name: Should fail to create with overrideEnv using reserved prefix HOSTNAME + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + overrideEnv: + - name: HOSTNAME + value: "custom-host" + expectedError: "environment variable names with reserved prefixes 'HOSTNAME', 'KUBERNETES_', 'EXTERNAL_SECRETS_' are not allowed" + - name: Should fail to create with overrideEnv using reserved prefix KUBERNETES_ + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + overrideEnv: + - name: KUBERNETES_SERVICE_HOST + value: "10.0.0.1" + expectedError: "environment variable names with reserved prefixes 'HOSTNAME', 'KUBERNETES_', 'EXTERNAL_SECRETS_' are not allowed" + - name: Should fail to create with overrideEnv using reserved prefix EXTERNAL_SECRETS_ + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + overrideEnv: + - name: EXTERNAL_SECRETS_CUSTOM_VAR + value: "not-allowed" + expectedError: "environment variable names with reserved prefixes 'HOSTNAME', 'KUBERNETES_', 'EXTERNAL_SECRETS_' are not allowed" + - name: Should be able to create ExternalSecretsConfig with componentConfigs and overrideEnv together + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 10 + overrideEnv: + - name: GOMAXPROCS + value: "4" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 10 + overrideEnv: + - name: GOMAXPROCS + value: "4" + - name: Should be able to create ExternalSecretsConfig with annotations and componentConfigs combined + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "example.com/custom-annotation" + value: "my-value" + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 10 + overrideEnv: + - name: GOMAXPROCS + value: "4" + - componentName: Webhook + deploymentConfig: + revisionHistoryLimit: 3 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "example.com/custom-annotation" + value: "my-value" + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 10 + overrideEnv: + - name: GOMAXPROCS + value: "4" + - componentName: Webhook + deploymentConfig: + revisionHistoryLimit: 3 + - name: Should be able to create componentConfig without deploymentConfig (only overrideEnv) + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: CertController + overrideEnv: + - name: CUSTOM_VAR + value: "custom-value" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: CertController + overrideEnv: + - name: CUSTOM_VAR + value: "custom-value" + - name: Should be able to create componentConfig without overrideEnv (only deploymentConfig) + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 7 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 7 + - name: Should fail to create with more than 4 componentConfigs + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 5 + - componentName: Webhook + deploymentConfig: + revisionHistoryLimit: 3 + - componentName: CertController + deploymentConfig: + revisionHistoryLimit: 2 + - componentName: BitwardenSDKServer + deploymentConfig: + revisionHistoryLimit: 1 + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 10 + expectedError: "spec.controllerConfig.componentConfigs: Too many: 5: must have at most 4 items" onUpdate: - name: Should be able to update labels in controller config resourceName: cluster @@ -596,4 +1090,186 @@ tests: bitwardenSecretManagerProvider: mode: Enabled secretRef: - name: "bitwarden-certs" \ No newline at end of file + name: "bitwarden-certs" + - name: Should be able to add annotations after creation + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "example.com/new-annotation" + value: "new-value" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "example.com/new-annotation" + value: "new-value" + - name: Should be able to update annotations + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "example.com/annotation" + value: "old-value" + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "example.com/annotation" + value: "new-value" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "example.com/annotation" + value: "new-value" + - name: Should be able to add componentConfigs after creation + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 5 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 5 + - name: Should be able to update revisionHistoryLimit + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 5 + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 10 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 10 + - name: Should be able to add overrideEnv after creation + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 5 + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 5 + overrideEnv: + - name: GOMAXPROCS + value: "4" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + deploymentConfig: + revisionHistoryLimit: 5 + overrideEnv: + - name: GOMAXPROCS + value: "4" + - name: Should fail to update with annotation using reserved prefix + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "example.com/annotation" + value: "valid" + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + annotations: + - key: "example.com/annotation" + value: "valid" + - key: "kubernetes.io/forbidden" + value: "not-allowed" + expectedError: "annotations with reserved prefixes 'kubernetes.io/', 'app.kubernetes.io/', 'openshift.io/', 'k8s.io/' are not allowed" + - name: Should fail to update with overrideEnv using reserved prefix + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + overrideEnv: + - name: GOMAXPROCS + value: "4" + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + componentConfigs: + - componentName: ExternalSecretsCoreController + overrideEnv: + - name: GOMAXPROCS + value: "4" + - name: HOSTNAME + value: "custom" + expectedError: "environment variable names with reserved prefixes 'HOSTNAME', 'KUBERNETES_', 'EXTERNAL_SECRETS_' are not allowed" \ No newline at end of file diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 99f6ec14b..c62f1acf7 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -27,6 +27,22 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Annotation) DeepCopyInto(out *Annotation) { + *out = *in + out.KVPair = in.KVPair +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Annotation. +func (in *Annotation) DeepCopy() *Annotation { + if in == nil { + return nil + } + out := new(Annotation) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ApplicationConfig) DeepCopyInto(out *ApplicationConfig) { *out = *in @@ -162,6 +178,29 @@ func (in *CommonConfigs) DeepCopy() *CommonConfigs { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ComponentConfig) DeepCopyInto(out *ComponentConfig) { + *out = *in + in.DeploymentConfig.DeepCopyInto(&out.DeploymentConfig) + if in.OverrideEnv != nil { + in, out := &in.OverrideEnv, &out.OverrideEnv + *out = make([]corev1.EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentConfig. +func (in *ComponentConfig) DeepCopy() *ComponentConfig { + if in == nil { + return nil + } + out := new(ComponentConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in @@ -214,6 +253,18 @@ func (in *ControllerConfig) DeepCopyInto(out *ControllerConfig) { (*out)[key] = val } } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make([]Annotation, len(*in)) + copy(*out, *in) + } + if in.ComponentConfigs != nil { + in, out := &in.ComponentConfigs, &out.ComponentConfigs + *out = make([]ComponentConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.NetworkPolicies != nil { in, out := &in.NetworkPolicies, &out.NetworkPolicies *out = make([]NetworkPolicy, len(*in)) @@ -253,6 +304,26 @@ func (in *ControllerStatus) DeepCopy() *ControllerStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentConfig) DeepCopyInto(out *DeploymentConfig) { + *out = *in + if in.RevisionHistoryLimit != nil { + in, out := &in.RevisionHistoryLimit, &out.RevisionHistoryLimit + *out = new(int32) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentConfig. +func (in *DeploymentConfig) DeepCopy() *DeploymentConfig { + if in == nil { + return nil + } + out := new(DeploymentConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExternalSecretsConfig) DeepCopyInto(out *ExternalSecretsConfig) { *out = *in @@ -471,6 +542,21 @@ func (in *GlobalConfig) DeepCopy() *GlobalConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KVPair) DeepCopyInto(out *KVPair) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KVPair. +func (in *KVPair) DeepCopy() *KVPair { + if in == nil { + return nil + } + out := new(KVPair) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkPolicy) DeepCopyInto(out *NetworkPolicy) { *out = *in diff --git a/config/crd/bases/operator.openshift.io_externalsecretsconfigs.yaml b/config/crd/bases/operator.openshift.io_externalsecretsconfigs.yaml index ae6890cdc..88581a81e 100644 --- a/config/crd/bases/operator.openshift.io_externalsecretsconfigs.yaml +++ b/config/crd/bases/operator.openshift.io_externalsecretsconfigs.yaml @@ -1173,6 +1173,46 @@ spec: for the controller to use while installing the `external-secrets` operand and the plugins. properties: + annotations: + description: |- + annotations allows adding custom annotations to all external-secrets component + Deployments and Pod templates. These annotations are applied globally to all + operand components (Controller, Webhook, CertController, BitwardenSDKServer). + These annotations are merged with any default annotations set by the operator. + User-specified annotations take precedence over defaults in case of conflicts. + Annotations with keys starting with kubernetes.io/, app.kubernetes.io/, openshift.io/, or k8s.io/ + are reserved and cannot be overridden. + items: + description: |- + Annotation represents a custom annotation key-value pair. + It embeds KVPair inline for reusability. + properties: + key: + description: |- + key is the name of the key-value pair. + This field must be non-empty and can have a maximum of 317 characters. + maxLength: 317 + minLength: 1 + type: string + value: + description: |- + value is the value of the key-value pair. + This field can have a maximum of 4096 characters. + maxLength: 4096 + type: string + required: + - key + type: object + maxItems: 50 + type: array + x-kubernetes-list-map-keys: + - key + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: annotations with reserved prefixes 'kubernetes.io/', + 'app.kubernetes.io/', 'openshift.io/', 'k8s.io/' are not allowed + rule: self.all(a, !['kubernetes.io/', 'app.kubernetes.io/', + 'openshift.io/', 'k8s.io/'].exists(p, a.key.startsWith(p))) certProvider: description: certProvider is for defining the configuration for certificate providers used to manage TLS certificates for webhook @@ -1263,6 +1303,187 @@ spec: rule: 'has(self.injectAnnotations) && self.injectAnnotations != ''false'' ? self.mode != ''Disabled'' : true' type: object + componentConfigs: + description: |- + componentConfigs allows specifying component-specific configuration overrides for + individual external-secrets components (Controller, Webhook, CertController, BitwardenSDKServer). + Each entry configures deployment-level overrides and custom environment variables for a single component. + The componentName must be unique across all entries. + items: + description: |- + ComponentConfig allows specifying configuration overrides for a single external-secrets component. + This includes deployment-level configuration such as revisionHistoryLimit and custom environment variables. + properties: + componentName: + description: |- + componentName specifies which deployment component this configuration applies to. + Each component can only appear once in the componentConfigs list. + enum: + - ExternalSecretsCoreController + - Webhook + - CertController + - BitwardenSDKServer + type: string + deploymentConfig: + description: |- + deploymentConfig allows specifying deployment-level configuration overrides + for the specified component. + properties: + revisionHistoryLimit: + description: |- + revisionHistoryLimit specifies the number of old ReplicaSets to retain for rollback. + Minimum value of 1 is enforced to ensure rollback capability. + format: int32 + minimum: 1 + type: integer + type: object + overrideEnv: + description: |- + overrideEnv allows setting custom environment variables for the component's container. + These environment variables are merged with the default environment variables set by + the operator. User-specified variables take precedence in case of conflicts. + Environment variables with names starting with HOSTNAME, KUBERNETES_, or EXTERNAL_SECRETS_ are reserved + and cannot be overridden. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + maxItems: 50 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: environment variable names with reserved prefixes + 'HOSTNAME', 'KUBERNETES_', 'EXTERNAL_SECRETS_' are not + allowed + rule: self.all(e, !['HOSTNAME', 'KUBERNETES_', 'EXTERNAL_SECRETS_'].exists(p, + e.name.startsWith(p))) + required: + - componentName + type: object + maxItems: 4 + type: array + x-kubernetes-list-map-keys: + - componentName + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: componentName must be unique across all componentConfig + entries + rule: self.all(x, self.exists_one(y, x.componentName == y.componentName)) labels: additionalProperties: type: string @@ -1293,6 +1514,8 @@ spec: component this network policy applies to. enum: - ExternalSecretsCoreController + - Webhook + - CertController - BitwardenSDKServer type: string egress: From 51fc3577e092a17f2955ad2ca89f5e160b89051a Mon Sep 17 00:00:00 2001 From: Swarup Ghosh Date: Mon, 6 Apr 2026 14:02:25 +0530 Subject: [PATCH 2/2] feat: implement controller logic for component overrides (EP-1898) Add controller implementation for the new ExternalSecretsConfig API fields: - applyAnnotationsToDeployment: merges global annotations from ControllerConfig.Annotations into Deployment metadata and Pod template metadata for all operand components - applyComponentConfig: applies per-component revisionHistoryLimit and overrideEnv from ComponentConfig to each component's Deployment - mergeEnvVars: merges override environment variables into container env lists with precedence for user-specified values - Helper functions: deploymentAssetNameForComponent, getComponentConfig, getComponentNameForDeploymentAsset for mapping between ComponentName enums and deployment asset names The new logic integrates into the existing getDeploymentObject flow, running after proxy configuration and before deployment creation/update. Ref: https://github.com/openshift/enhancements/pull/1898 Co-Authored-By: Claude Opus 4.6 --- .../external_secrets/deployments.go | 85 +++++++++++++++++++ pkg/controller/external_secrets/utils.go | 44 ++++++++++ 2 files changed, 129 insertions(+) diff --git a/pkg/controller/external_secrets/deployments.go b/pkg/controller/external_secrets/deployments.go index 47ab49d9c..a9996aaf9 100644 --- a/pkg/controller/external_secrets/deployments.go +++ b/pkg/controller/external_secrets/deployments.go @@ -148,6 +148,15 @@ func (r *Reconciler) getDeploymentObject(assetName string, esc *operatorv1alpha1 return nil, fmt.Errorf("failed to update proxy configuration: %w", err) } + // Apply global annotations from ControllerConfig to Deployment and Pod template. + applyAnnotationsToDeployment(deployment, esc) + + // Apply per-component configuration overrides (revisionHistoryLimit, overrideEnv). + componentName := getComponentNameForDeploymentAsset(assetName) + if componentName != "" { + applyComponentConfig(deployment, esc, componentName) + } + return deployment, nil } @@ -660,3 +669,79 @@ func (r *Reconciler) removeTrustedCAVolumeMount(container *corev1.Container) { } container.VolumeMounts = filteredVolumeMounts } + +// applyAnnotationsToDeployment merges annotations from ControllerConfig.Annotations into +// the Deployment's metadata and Pod template's metadata. User-specified annotations take +// precedence over defaults. Annotations with reserved prefixes are already blocked by +// CRD-level CEL validation, so they are not checked here. +func applyAnnotationsToDeployment(deployment *appsv1.Deployment, esc *operatorv1alpha1.ExternalSecretsConfig) { + if len(esc.Spec.ControllerConfig.Annotations) == 0 { + return + } + + // Apply to Deployment metadata annotations + deploymentAnnotations := deployment.GetAnnotations() + if deploymentAnnotations == nil { + deploymentAnnotations = make(map[string]string, len(esc.Spec.ControllerConfig.Annotations)) + } + for _, annotation := range esc.Spec.ControllerConfig.Annotations { + deploymentAnnotations[annotation.Key] = annotation.Value + } + deployment.SetAnnotations(deploymentAnnotations) + + // Apply to Pod template annotations + podAnnotations := deployment.Spec.Template.GetAnnotations() + if podAnnotations == nil { + podAnnotations = make(map[string]string, len(esc.Spec.ControllerConfig.Annotations)) + } + for _, annotation := range esc.Spec.ControllerConfig.Annotations { + podAnnotations[annotation.Key] = annotation.Value + } + deployment.Spec.Template.SetAnnotations(podAnnotations) +} + +// applyComponentConfig applies per-component configuration overrides from +// ComponentConfig to the corresponding Deployment. This includes: +// - revisionHistoryLimit: sets spec.revisionHistoryLimit on the Deployment +// - overrideEnv: merges custom environment variables into all containers +func applyComponentConfig(deployment *appsv1.Deployment, esc *operatorv1alpha1.ExternalSecretsConfig, componentName operatorv1alpha1.ComponentName) { + cc := getComponentConfig(esc, componentName) + if cc == nil { + return + } + + // Apply revisionHistoryLimit if specified + if cc.DeploymentConfig.RevisionHistoryLimit != nil { + deployment.Spec.RevisionHistoryLimit = cc.DeploymentConfig.RevisionHistoryLimit + } + + // Merge overrideEnv into all containers in the Pod template + if len(cc.OverrideEnv) > 0 { + for i := range deployment.Spec.Template.Spec.Containers { + mergeEnvVars(&deployment.Spec.Template.Spec.Containers[i], cc.OverrideEnv) + } + } +} + +// mergeEnvVars merges override environment variables into a container's existing env list. +// User-specified variables take precedence over defaults in case of conflicts. +// Reserved prefixes are already blocked by CRD-level CEL validation. +func mergeEnvVars(container *corev1.Container, overrideEnv []corev1.EnvVar) { + if container.Env == nil { + container.Env = make([]corev1.EnvVar, 0, len(overrideEnv)) + } + + for _, override := range overrideEnv { + found := false + for i, existing := range container.Env { + if existing.Name == override.Name { + container.Env[i] = override + found = true + break + } + } + if !found { + container.Env = append(container.Env, override) + } + } +} diff --git a/pkg/controller/external_secrets/utils.go b/pkg/controller/external_secrets/utils.go index cb96719f0..cb1e58571 100644 --- a/pkg/controller/external_secrets/utils.go +++ b/pkg/controller/external_secrets/utils.go @@ -124,6 +124,50 @@ func (r *Reconciler) IsCertManagerInstalled() bool { return ok } +// deploymentAssetNameForComponent maps a ComponentName enum to its corresponding deployment asset name. +// Returns empty string if no deployment asset is associated with the given component name. +func deploymentAssetNameForComponent(name operatorv1alpha1.ComponentName) string { + switch name { + case operatorv1alpha1.CoreController: + return controllerDeploymentAssetName + case operatorv1alpha1.Webhook: + return webhookDeploymentAssetName + case operatorv1alpha1.CertController: + return certControllerDeploymentAssetName + case operatorv1alpha1.BitwardenSDKServer: + return bitwardenDeploymentAssetName + default: + return "" + } +} + +// getComponentConfig returns the ComponentConfig for the given component name from the ControllerConfig. +// Returns nil if no ComponentConfig is found for the given component. +func getComponentConfig(esc *operatorv1alpha1.ExternalSecretsConfig, componentName operatorv1alpha1.ComponentName) *operatorv1alpha1.ComponentConfig { + for i := range esc.Spec.ControllerConfig.ComponentConfigs { + if esc.Spec.ControllerConfig.ComponentConfigs[i].ComponentName == componentName { + return &esc.Spec.ControllerConfig.ComponentConfigs[i] + } + } + return nil +} + +// getComponentNameForDeploymentAsset maps a deployment asset name back to a ComponentName. +func getComponentNameForDeploymentAsset(assetName string) operatorv1alpha1.ComponentName { + switch assetName { + case controllerDeploymentAssetName: + return operatorv1alpha1.CoreController + case webhookDeploymentAssetName: + return operatorv1alpha1.Webhook + case certControllerDeploymentAssetName: + return operatorv1alpha1.CertController + case bitwardenDeploymentAssetName: + return operatorv1alpha1.BitwardenSDKServer + default: + return "" + } +} + // getProxyConfiguration returns the proxy configuration based on precedence. // The precedence order is: ExternalSecretsConfig > ExternalSecretsManager > OLM environment variables. func (r *Reconciler) getProxyConfiguration(esc *operatorv1alpha1.ExternalSecretsConfig) *operatorv1alpha1.ProxyConfig {