diff --git a/api/v1alpha1/external_secrets_config_types.go b/api/v1alpha1/external_secrets_config_types.go index ccac0512f..321c61684 100644 --- a/api/v1alpha1/external_secrets_config_types.go +++ b/api/v1alpha1/external_secrets_config_types.go @@ -4,6 +4,7 @@ import ( corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) func init() { @@ -151,6 +152,20 @@ type ControllerConfig struct { // +listMapKey=componentName NetworkPolicies []NetworkPolicy `json:"networkPolicies,omitempty"` + // concurrent sets the core controller --concurrent flag (max concurrent reconciles). + // When omitted, defaults to 1 for backward compatibility. + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=100 + // +optional + Concurrent *int32 `json:"concurrent,omitempty"` + + // replicas sets the desired replica count for the external-secrets core controller Deployment. + // When omitted, defaults to 1. When greater than 1, leader election keeps a single active reconciler. + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=10 + // +optional + Replicas *int32 `json:"replicas,omitempty"` + // componentConfigs allows specifying deployment-level configuration overrides for individual external-secrets components. This field enables fine-grained control over deployment settings for each component independently. // Each component can only have one configuration entry. // +kubebuilder:validation:MinItems:=0 @@ -191,6 +206,16 @@ type ComponentConfig struct { // +listMapKey=name // +optional OverrideEnv []corev1.EnvVar `json:"overrideEnv,omitempty"` + + // experimentalOverrides is an escape hatch: a strategic merge patch applied to this + // component's Deployment.spec after first-class fields are rendered. Intended for scheduling + // fields (affinity, tolerations, nodeSelector, topologySpreadConstraints). Patches that touch + // operator-owned nested lists — containers, initContainers, ephemeralContainers, volumes, or + // volumeMounts/volumeDevices — are rejected and cause Degraded. Invalid or unusable patch data + // also causes Degraded. Prefer first-class fields (concurrent, replicas, revisionHistoryLimit, + // overrideEnv, trustedCABundle) when available. + // +optional + ExperimentalOverrides *runtime.RawExtension `json:"experimentalOverrides,omitempty"` } // DeploymentConfig defines configuration overrides for a Kubernetes Deployment resource. 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 b922d6a07..3408fe8dd 100644 --- a/api/v1alpha1/tests/externalsecretsconfig.operator.openshift.io/externalsecretsconfig.testsuite.yaml +++ b/api/v1alpha1/tests/externalsecretsconfig.operator.openshift.io/externalsecretsconfig.testsuite.yaml @@ -1385,6 +1385,126 @@ tests: overrideEnv: - name: SHARED_VAR value: "webhook-value" + - name: Should allow concurrent and replicas within valid bounds + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + concurrent: 20 + replicas: 2 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + concurrent: 20 + replicas: 2 + - name: Should allow concurrent at maximum of 100 + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + concurrent: 100 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + concurrent: 100 + - name: Should allow replicas at maximum of 10 + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + replicas: 10 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + replicas: 10 + - name: Should fail with concurrent less than 1 + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + concurrent: 0 + expectedError: "ExternalSecretsConfig.operator.openshift.io \"cluster\" is invalid: spec.controllerConfig.concurrent: Invalid value: 0: spec.controllerConfig.concurrent in body should be greater than or equal to 1" + - name: Should fail with concurrent exceeding maximum of 100 + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + concurrent: 101 + expectedError: "ExternalSecretsConfig.operator.openshift.io \"cluster\" is invalid: spec.controllerConfig.concurrent: Invalid value: 101: spec.controllerConfig.concurrent in body should be less than or equal to 100" + - name: Should fail with replicas less than 1 + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + replicas: 0 + expectedError: "ExternalSecretsConfig.operator.openshift.io \"cluster\" is invalid: spec.controllerConfig.replicas: Invalid value: 0: spec.controllerConfig.replicas in body should be greater than or equal to 1" + - name: Should fail with replicas exceeding maximum of 10 + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + replicas: 11 + expectedError: "ExternalSecretsConfig.operator.openshift.io \"cluster\" is invalid: spec.controllerConfig.replicas: Invalid value: 11: spec.controllerConfig.replicas in body should be less than or equal to 10" + - name: Should allow experimentalOverrides on a componentConfig + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + concurrent: 20 + replicas: 2 + componentConfigs: + - componentName: ExternalSecretsCoreController + experimentalOverrides: + template: + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/name: external-secrets + topologyKey: kubernetes.io/hostname + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsConfig + spec: + controllerConfig: + concurrent: 20 + replicas: 2 + componentConfigs: + - componentName: ExternalSecretsCoreController + experimentalOverrides: + template: + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/name: external-secrets + topologyKey: kubernetes.io/hostname - name: Should allow networkPolicy with valid componentName ExternalSecretsCoreController resourceName: cluster initial: | diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 3743fc256..2e39f62d0 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -24,7 +24,7 @@ import ( "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -177,6 +177,11 @@ func (in *ComponentConfig) DeepCopyInto(out *ComponentConfig) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.ExperimentalOverrides != nil { + in, out := &in.ExperimentalOverrides, &out.ExperimentalOverrides + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentConfig. @@ -270,6 +275,16 @@ func (in *ControllerConfig) DeepCopyInto(out *ControllerConfig) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Concurrent != nil { + in, out := &in.Concurrent, &out.Concurrent + *out = new(int32) + **out = **in + } + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } if in.ComponentConfigs != nil { in, out := &in.ComponentConfigs, &out.ComponentConfigs *out = make([]ComponentConfig, len(*in)) diff --git a/bundle/manifests/operator.openshift.io_externalsecretsconfigs.yaml b/bundle/manifests/operator.openshift.io_externalsecretsconfigs.yaml index 699f85bfc..666d16989 100644 --- a/bundle/manifests/operator.openshift.io_externalsecretsconfigs.yaml +++ b/bundle/manifests/operator.openshift.io_externalsecretsconfigs.yaml @@ -1330,6 +1330,17 @@ spec: minimum: 1 type: integer type: object + experimentalOverrides: + description: |- + experimentalOverrides is an escape hatch: a strategic merge patch applied to this + component's Deployment.spec after first-class fields are rendered. Intended for scheduling + fields (affinity, tolerations, nodeSelector, topologySpreadConstraints). Patches that touch + operator-owned nested lists — containers, initContainers, ephemeralContainers, volumes, or + volumeMounts/volumeDevices — are rejected and cause Degraded. Invalid or unusable patch data + also causes Degraded. Prefer first-class fields (concurrent, replicas, revisionHistoryLimit, + overrideEnv, trustedCABundle) when available. + type: object + x-kubernetes-preserve-unknown-fields: true overrideEnv: description: |- overrideEnv specifies custom environment variables for this component's container. These are merged with operator-managed environment variables, with user-defined values taking precedence. @@ -1511,6 +1522,14 @@ spec: x-kubernetes-list-map-keys: - componentName x-kubernetes-list-type: map + concurrent: + description: |- + concurrent sets the core controller --concurrent flag (max concurrent reconciles). + When omitted, defaults to 1 for backward compatibility. + format: int32 + maximum: 100 + minimum: 1 + type: integer labels: additionalProperties: type: string @@ -1768,6 +1787,14 @@ spec: immutable rule: oldSelf.all(op, self.exists(p, p.name == op.name && p.componentName == op.componentName)) + replicas: + description: |- + replicas sets the desired replica count for the external-secrets core controller Deployment. + When omitted, defaults to 1. When greater than 1, leader election keeps a single active reconciler. + format: int32 + maximum: 10 + minimum: 1 + type: integer trustedCABundle: description: |- trustedCABundle references a ConfigMap containing PEM-encoded CA certificates for the external-secrets core controller to trust when making outbound TLS connections. diff --git a/config/crd/bases/operator.openshift.io_externalsecretsconfigs.yaml b/config/crd/bases/operator.openshift.io_externalsecretsconfigs.yaml index 4dcfb2135..6414ece00 100644 --- a/config/crd/bases/operator.openshift.io_externalsecretsconfigs.yaml +++ b/config/crd/bases/operator.openshift.io_externalsecretsconfigs.yaml @@ -1330,6 +1330,17 @@ spec: minimum: 1 type: integer type: object + experimentalOverrides: + description: |- + experimentalOverrides is an escape hatch: a strategic merge patch applied to this + component's Deployment.spec after first-class fields are rendered. Intended for scheduling + fields (affinity, tolerations, nodeSelector, topologySpreadConstraints). Patches that touch + operator-owned nested lists — containers, initContainers, ephemeralContainers, volumes, or + volumeMounts/volumeDevices — are rejected and cause Degraded. Invalid or unusable patch data + also causes Degraded. Prefer first-class fields (concurrent, replicas, revisionHistoryLimit, + overrideEnv, trustedCABundle) when available. + type: object + x-kubernetes-preserve-unknown-fields: true overrideEnv: description: |- overrideEnv specifies custom environment variables for this component's container. These are merged with operator-managed environment variables, with user-defined values taking precedence. @@ -1511,6 +1522,14 @@ spec: x-kubernetes-list-map-keys: - componentName x-kubernetes-list-type: map + concurrent: + description: |- + concurrent sets the core controller --concurrent flag (max concurrent reconciles). + When omitted, defaults to 1 for backward compatibility. + format: int32 + maximum: 100 + minimum: 1 + type: integer labels: additionalProperties: type: string @@ -1768,6 +1787,14 @@ spec: immutable rule: oldSelf.all(op, self.exists(p, p.name == op.name && p.componentName == op.componentName)) + replicas: + description: |- + replicas sets the desired replica count for the external-secrets core controller Deployment. + When omitted, defaults to 1. When greater than 1, leader election keeps a single active reconciler. + format: int32 + maximum: 10 + minimum: 1 + type: integer trustedCABundle: description: |- trustedCABundle references a ConfigMap containing PEM-encoded CA certificates for the external-secrets core controller to trust when making outbound TLS connections. diff --git a/docs/api_reference.md b/docs/api_reference.md index a9851a1b6..b7233fc00 100644 --- a/docs/api_reference.md +++ b/docs/api_reference.md @@ -130,6 +130,7 @@ _Appears in:_ | `componentName` _[ComponentName](#componentname)_ | componentName identifies which external-secrets component this configuration applies to.
Valid component names: ExternalSecretsCoreController, Webhook, CertController, BitwardenSDKServer. | | Enum: [ExternalSecretsCoreController Webhook CertController BitwardenSDKServer]
| | `deploymentConfigs` _[DeploymentConfig](#deploymentconfig)_ | deploymentConfigs specifies overrides for the Kubernetes Deployment resource of this component. | | | | `overrideEnv` _[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#envvar-v1-core) array_ | overrideEnv specifies custom environment variables for this component's container. These are merged with operator-managed environment variables, with user-defined values taking precedence.
Names starting with 'KUBERNETES_' or 'EXTERNAL_SECRETS_' are reserved prefixes and will be rejected.
The exact names 'HOSTNAME', 'SSL_CERT_DIR', and 'SSL_CERT_FILE' are also reserved. | | MaxItems: 50
| +| `experimentalOverrides` _[RawExtension](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#rawextension-runtime-pkg)_ | experimentalOverrides is an escape hatch: a strategic merge patch applied to this
component's Deployment.spec after first-class fields are rendered. Intended for scheduling
fields (affinity, tolerations, nodeSelector, topologySpreadConstraints). Patches that touch
operator-owned nested lists — containers, initContainers, ephemeralContainers, volumes, or
volumeMounts/volumeDevices — are rejected and cause Degraded. Invalid or unusable patch data
also causes Degraded. Prefer first-class fields (concurrent, replicas, revisionHistoryLimit,
overrideEnv, trustedCABundle) when available. | | | #### ComponentName @@ -220,6 +221,8 @@ _Appears in:_ | `labels` _object (keys:string, values:string)_ | labels to apply to all resources created for the external-secrets operand deployment.
This field can have a maximum of 20 entries. | | MaxProperties: 20
MinProperties: 0
| | `annotations` _object (keys:string, values:string)_ | annotations are for adding custom annotations to all the resources created for external-secrets deployment.
The annotations are merged with any default annotations set by the operator. User-specified annotations take precedence over defaults in case of conflicts.
Annotation keys containing domains `kubernetes.io/`, `openshift.io/`, `cert-manager.io/` or `k8s.io/` (including subdomains like `*.kubernetes.io/`) are not allowed. | | MaxProperties: 20
MinProperties: 0
| | `networkPolicies` _[NetworkPolicy](#networkpolicy) array_ | networkPolicies specifies the list of network policy configurations
to be applied to external-secrets pods.
Each entry allows specifying a name for the generated NetworkPolicy object,
along with its full Kubernetes NetworkPolicy definition.
The operator prepends "eso-user-" to the provided name when creating the Kubernetes object.
If this field is not provided, external-secrets components will be isolated
with deny-all network policies, which will prevent proper operation. | | MaxItems: 50
MinItems: 0
| +| `concurrent` _integer_ | concurrent sets the core controller --concurrent flag (max concurrent reconciles).
When omitted, defaults to 1 for backward compatibility. | | Maximum: 100
Minimum: 1
| +| `replicas` _integer_ | replicas sets the desired replica count for the external-secrets core controller Deployment.
When omitted, defaults to 1. When greater than 1, leader election keeps a single active reconciler. | | Maximum: 10
Minimum: 1
| | `componentConfigs` _[ComponentConfig](#componentconfig) array_ | componentConfigs allows specifying deployment-level configuration overrides for individual external-secrets components. This field enables fine-grained control over deployment settings for each component independently.
Each component can only have one configuration entry. | | MaxItems: 4
MinItems: 0
| | `trustedCABundle` _[ConfigMapKeyReference](#configmapkeyreference)_ | trustedCABundle references a ConfigMap containing PEM-encoded CA certificates for the external-secrets core controller to trust when making outbound TLS connections.
If specified, this bundle is used for all outbound TLS traffic, including connections to external secret management systems and configured proxies.
The ConfigMap must exist in the external-secrets operand namespace and must not carry the CNO inject-trusted-cabundle label when proxy is configured.
When omitted, external providers use standard system certificates. When proxy is configured, proxy TLS connections use the operator-managed
OpenShift trusted CA bundle injected by the Cluster Network Operator. | | |