-
Notifications
You must be signed in to change notification settings - Fork 11
wip: Make operator HTTP proxy aware #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: chiragkyal The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
155cd28
to
cac8b3f
Compare
@CodeRabbit review |
✅ Actions performedReview triggered.
|
WalkthroughEnable proxy-awareness and add trusted CA bundle support: controller now computes proxy configuration, injects proxy environment variables, manages a trusted CA ConfigMap, mounts the bundle into deployments, adds ConfigMap watches, updates CSVs/manifests, and adds unit tests and metrics cert mounts. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 19
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (10)
pkg/controller/external_secrets/rbacs_test.go (1)
172-183
: Wrong asset helper used for ClusterRole name check.Comparing against a ClusterRoleBinding name will mask failures.
- case *rbacv1.ClusterRole: - if obj.GetName() == testClusterRoleBinding(certControllerClusterRoleBindingAssetName).GetName() { + case *rbacv1.ClusterRole: + if obj.GetName() == testClusterRole(certControllerClusterRoleAssetName).GetName() {pkg/controller/common/utils.go (4)
62-68
: Potential nil-map panic in UpdateResourceLabels.
obj.GetLabels()
can be nil; assigning into it panics.func UpdateResourceLabels(obj client.Object, labels map[string]string) { - l := obj.GetLabels() + l := obj.GetLabels() + if l == nil { + l = map[string]string{} + } for k, v := range labels { l[k] = v } obj.SetLabels(l) }
284-286
: Possible nil dereference on SecurityContext compare.Dereferences
*fetchedContainer.SecurityContext
without nil check when desired is non‑nil and fetched is nil.- // SecurityContext nil check - if desiredContainer.SecurityContext != nil && !reflect.DeepEqual(*desiredContainer.SecurityContext, *fetchedContainer.SecurityContext) { - return true - } + // SecurityContext nil checks + if (desiredContainer.SecurityContext == nil) != (fetchedContainer.SecurityContext == nil) { + return true + } + if desiredContainer.SecurityContext != nil && fetchedContainer.SecurityContext != nil && + !reflect.DeepEqual(*desiredContainer.SecurityContext, *fetchedContainer.SecurityContext) { + return true + }
292-295
: Resource comparison logic likely inverted.Currently flags a change when desired resources are empty and fetched aren’t. Usually we want to update only when desired explicitly differs.
- if reflect.DeepEqual(desiredContainer.Resources, corev1.ResourceRequirements{}) && - !reflect.DeepEqual(desiredContainer.Resources, fetchedContainer.Resources) { - return true - } + if !reflect.DeepEqual(desiredContainer.Resources, fetchedContainer.Resources) && + !reflect.DeepEqual(desiredContainer.Resources, corev1.ResourceRequirements{}) { + return true + }If the intended behavior was to clear resources when desired is empty, please confirm and we can gate that behind an explicit flag.
385-387
: Function name vs behavior mismatch: IsESMSpecEmpty returns true when spec is non‑empty.This can propagate logic errors.
-func IsESMSpecEmpty(esm *operatorv1alpha1.ExternalSecretsManager) bool { - return esm != nil && !reflect.DeepEqual(esm.Spec, operatorv1alpha1.ExternalSecretsManagerSpec{}) -} +func IsESMSpecEmpty(esm *operatorv1alpha1.ExternalSecretsManager) bool { + if esm == nil { + return true + } + return reflect.DeepEqual(esm.Spec, operatorv1alpha1.ExternalSecretsManagerSpec{}) +}If callers rely on the old (inverted) semantics, consider introducing
IsESMSpecNonEmpty
and migrating call sites.pkg/controller/external_secrets/controller.go (1)
412-414
: Wrong Reason for Ready=false fatal case.ReasonReady on a non‑ready condition is misleading. Use ReasonFailed.
- readyCond.Status = metav1.ConditionFalse - readyCond.Reason = operatorv1alpha1.ReasonReady + readyCond.Status = metav1.ConditionFalse + readyCond.Reason = operatorv1alpha1.ReasonFailedpkg/controller/external_secrets/install_external_secrets.go (2)
30-46
: Guard against nil ControllerConfig before accessing LabelsPotential NPE: esc.Spec.ControllerConfig may be nil.
Apply:
- if len(esc.Spec.ControllerConfig.Labels) != 0 { - for k, v := range esc.Spec.ControllerConfig.Labels { + if esc.Spec.ControllerConfig != nil && len(esc.Spec.ControllerConfig.Labels) != 0 { + for k, v := range esc.Spec.ControllerConfig.Labels {
106-121
: createOrApplyNamespace only creates; it never applies labels on existing namespacesThis regresses "apply" semantics; label drift won’t be corrected.
Apply:
if err := r.Create(r.ctx, obj); err != nil { if errors.IsAlreadyExists(err) { - r.log.V(4).Info("namespace already exists", "namespace", namespace) - return nil + // Ensure labels are up to date on existing namespace + existing := &corev1.Namespace{} + if getErr := r.Get(r.ctx, types.NamespacedName{Name: namespace}, existing); getErr != nil { + return getErr + } + common.UpdateResourceLabels(existing, resourceLabels) + if updateErr := r.UpdateWithRetry(r.ctx, existing); updateErr != nil { + return updateErr + } + r.log.V(4).Info("namespace already exists; labels ensured", "namespace", namespace) + return nil } return err }Add missing import:
k8s.io/apimachinery/pkg/typespkg/controller/external_secrets/certificate.go (1)
164-183
: Handle unsupported Issuer kind to avoid passing a nil object to Exists()Currently, an unknown kind yields a nil client.Object and risks a panic/pathological behavior.
var object client.Object switch issuerRef.Kind { case clusterIssuerKind: object = &certmanagerv1.ClusterIssuer{} case issuerKind: object = &certmanagerv1.Issuer{} + default: + return false, fmt.Errorf("unsupported issuer kind %q", issuerRef.Kind) } - ifExists, err := r.UncachedClient.Exists(r.ctx, namespacedName, object) + ifExists, err := r.UncachedClient.Exists(r.ctx, namespacedName, object)pkg/controller/external_secrets_manager/controller.go (1)
67-70
: Missing RBAC for ExternalSecretsConfigThe controller reads and watches externalsecretsconfigs; add permissions or manager will fail with RBAC errors.
Add:
// +kubebuilder:rbac:groups=operator.openshift.io,resources=externalsecretsconfigs,verbs=get;list;watch
🧹 Nitpick comments (56)
test/apis/README.md (1)
1-1
: Fix bare URL and stabilize branch reference.Use a Markdown link and avoid hardcoding the branch name to reduce link rot.
-Refer to https://github.com/openshift/api/tree/master/tests for more details. +Refer to the OpenShift API tests for more details: https://github.com/openshift/api/tree/main/testspkg/controller/external_secrets/constants.go (1)
48-51
: Correct comment to reflect actual field path.Spec.Namespace doesn’t exist; Namespace lives under ControllerConfig.
- // will be created, when ExternalSecretsConfig.Spec.Namespace is not set. + // will be created when ExternalSecretsConfig.Spec.ControllerConfig.Namespace is not set.api/v1alpha1/tests/externalsecretsmanager.operator.openshift.io/externalsecretsmanager.testsuite.yaml (2)
210-219
: Add acceptance test for noProxy at boundary length.You test “too long”; add a case that accepts exactly 4096 bytes to lock the boundary.
+ - name: Should accept no proxy list at maximum length boundary + resourceName: cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsManager + spec: + globalConfig: + logLevel: 1 + proxy: + noProxy: "<string of exactly 4096 bytes>" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ExternalSecretsManager + spec: + globalConfig: + logLevel: 1 + proxy: + noProxy: "<string of exactly 4096 bytes>"
92-99
: Consider invalid-scheme tests for proxy URLs.Add negative cases for schemes other than http/https (e.g., socks5://) if the CRD enforces scheme.
Also applies to: 244-253
pkg/controller/external_secrets/serviceaccounts_test.go (1)
124-124
: Duplicate test name; rename for clarity.Two tests share the same title. Rename this one to reflect the deletion path.
- name: "cert-controller serviceaccount skipped when cert-manager enabled", + name: "cert-controller serviceaccount deletion fails when cert-manager enabled",config/samples/operator_v1alpha1_externalsecrets.yaml (1)
2-8
: Align sample with rename and proxy feature
- Consider renaming the file to operator_v1alpha1_externalsecretsconfig.yaml for consistency.
- Add a minimal Proxy configuration example in spec to showcase the PR’s HTTP proxy support.
test/e2e/testdata/external_secret.yaml (1)
2-8
: E2E testdata naming and coverage
- Consider renaming to external_secrets_config.yaml for clarity.
- Add a case exercising Proxy settings to validate the new HTTP proxy awareness end‑to‑end.
README.md (1)
28-31
: Fix typo in controller bullet“external-scerets” → “external-secrets”.
- * reconciling the `externalsecretsmanagers.openshift.operator.io` resource for the global configurations and updates the `external-scerets` deployment accordingly. + * reconciling the `externalsecretsmanagers.openshift.operator.io` resource for the global configurations and updates the `external-secrets` deployment accordingly.hack/test-apis.sh (1)
16-19
: Shell robustness: quote HOME and comparisonAvoid unbound/word-splitting issues; quote HOME and use
:
default.-export HOME=${HOME:=/tmp/kubebuilder-testing} -if [ $HOME == "/" ]; then +export HOME="${HOME:-/tmp/kubebuilder-testing}" +if [ "$HOME" = "/" ]; then export HOME=/tmp/kubebuilder-testing fitest/apis/suite_test.go (1)
46-50
: Register project APIs with the scheme.Typed client encodes/decodes your CRs only if the scheme knows them.
- testScheme = scheme.Scheme + testScheme = scheme.Scheme + // register operator APIs + Expect(operatorv1alpha1.AddToScheme(testScheme)).To(Succeed())And add imports:
@@ - "testing" + "testing" + "context" @@ - "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery" + operatorv1alpha1 "github.com/openshift/external-secrets-operator/api/v1alpha1"If these vars aren’t declared elsewhere, also declare at file scope:
+var ( + cfg *rest.Config + k8sClient client.Client + testEnv *envtest.Environment + testScheme = scheme.Scheme +)With import:
+ "k8s.io/client-go/rest"
pkg/controller/external_secrets/serviceaccounts.go (2)
40-42
: Fix misleading delete error message.Always mentions “cert-controller” even when deleting another SA.
- return fmt.Errorf("failed to delete cert-controller serviceaccount: %w", err) + return fmt.Errorf("failed to delete serviceaccount %q: %w", serviceAccount.assetName, err)
15-15
: Optional: rename boolean param for clarity.
externalsecretsCreateRecon
now mismatches type naming. ConsidercreateReconcile
orescCreateRecon
.api/v1alpha1/meta.go (3)
58-63
: Enforce immutability of Resources via CEL (comment says “Cannot be updated”).Add an XValidation rule so the API enforces it.
- // Cannot be updated. + // Cannot be updated. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="resources is immutable"
91-111
: Proxy config shape is fine; consider follow-ups.
- NoProxy as a string is acceptable, but a string list (slice) avoids comma-splitting issues later.
- Optionally add URL pattern checks for http(s)Proxy.
Would you like a follow-up PR to add patterns and a helper to translate ProxyConfig to env vars (HTTP_PROXY/HTTPS_PROXY/NO_PROXY and lowercase variants)?
72-84
: Hard caps on Tolerations/NodeSelector may be too restrictive.MaxItems/MaxProperties of 10 can block legitimate configurations. Consider removing or raising.
pkg/controller/common/utils.go (1)
432-437
: Generic RemoveFinalizer error messages hardcode resource kind.Errors always mention
externalsecretsconfig...
even when removing from other types.- return fmt.Errorf("failed to create %q externalsecretsconfig.openshift.operator.io object with finalizers removed", namespacedName) + return fmt.Errorf("failed to update %q with finalizers removed", namespacedName) @@ - return fmt.Errorf("failed to remove finalizers on %q externalsecretsconfig.openshift.operator.io with %w", namespacedName, err) + return fmt.Errorf("failed to remove finalizers on %q: %w", namespacedName, err)Optionally specialize by inspecting the object’s GVK.
Makefile (1)
266-269
: Optional: add a simple clean target (static checkmake warning).Helps CI hygiene.
.PHONY: ginkgo ginkgo: $(LOCALBIN) ## Download ginkgo locally if necessary. $(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/v2/ginkgo) +.PHONY: clean +clean: ## Remove local build artifacts. + rm -rf $(LOCALBIN) cover.out distpkg/controller/crd_annotator/controller_test.go (2)
171-177
: Use updated resource name in NotFound error.Reflect the CRD rename for accuracy.
- return errors.NewNotFound(schema.GroupResource{ - Group: operatorv1alpha1.GroupVersion.Group, - Resource: "externalsecrets", - }, commontest.TestExternalSecretsConfigResourceName) + return errors.NewNotFound(schema.GroupResource{ + Group: operatorv1alpha1.GroupVersion.Group, + Resource: "externalsecretsconfigs", + }, commontest.TestExternalSecretsConfigResourceName)
418-423
: Consider asserting Get() error in status checks.Ignoring the error can hide regressions when the resource isn’t created.
- r.CtrlClient.Get(r.ctx, key, esc) + if err := r.CtrlClient.Get(r.ctx, key, esc); err != nil && tt.wantErr == "" { + t.Fatalf("failed to fetch ESC for status assertions: %v", err) + }pkg/controller/external_secrets/services.go (1)
42-79
: Param name nit: keep naming consistent with ExternalSecretsConfig.Rename externalsecretsCreateRecon → externalSecretsConfigCreateRecon for clarity.
-func (r *Reconciler) createOrApplyServiceFromAsset(esc *operatorv1alpha1.ExternalSecretsConfig, assetName string, resourceLabels map[string]string, externalsecretsCreateRecon bool) error { +func (r *Reconciler) createOrApplyServiceFromAsset(esc *operatorv1alpha1.ExternalSecretsConfig, assetName string, resourceLabels map[string]string, externalSecretsConfigCreateRecon bool) error { @@ - if exists { - if externalsecretsCreateRecon { + if exists { + if externalSecretsConfigCreateRecon {api/v1alpha1/tests/externalsecretsconfig.operator.openshift.io/externalsecretsconfig.testsuite.yaml (1)
104-130
: Duplicate test case—consolidate to one.“addInjectorAnnotations true when enabled is false” appears twice with the same expectation. Please drop one to reduce noise.
pkg/controller/external_secrets/secret_test.go (2)
181-185
: Test doesn’t enable cert‑manager but claims creation is skipped.This case never toggles esc.Spec.ApplicationConfig.CertManagerConfig.Enabled to "true", and no client behavior is stubbed. Make intent explicit and stub a successful delete.
- { - name: "secret creation skipped when cert-manager config is enabled", - preReq: func(r *Reconciler, m *fakes.FakeCtrlClient) { - }, - }, + { + name: "secret creation skipped when cert-manager config is enabled", + preReq: func(r *Reconciler, m *fakes.FakeCtrlClient) { + m.DeleteCalls(func(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + return nil + }) + }, + esc: func(esc *v1alpha1.ExternalSecretsConfig) { + esc.Spec.ApplicationConfig = v1alpha1.ApplicationConfig{ + CertManagerConfig: &v1alpha1.CertManagerConfig{ Enabled: "true" }, + } + }, + },
224-227
: Consider adding a drift test if Secret comparison switches to full object.If you adopt HasObjectChanged in production, add a test where Secret data differs and ensure update path triggers.
pkg/controller/external_secrets/controller.go (3)
327-365
: Resource strings use the wrong API group order.Logs/comments reference externalsecretsconfig.openshift.operator.io; the group is operator.openshift.io (plural form for resources).
- // deployment to reflect desired state configured in `externalsecretsconfig.openshift.operator.io`. + // deployment to reflect desired state configured in `externalsecretsconfigs.operator.openshift.io`. @@ - // Fetch the externalsecretsconfig.openshift.operator.io CR + // Fetch the externalsecretsconfigs.operator.openshift.io CR @@ - r.log.V(1).Info("externalsecretsconfig.openshift.operator.io object not found, skipping reconciliation", "request", req) + r.log.V(1).Info("externalsecretsconfigs.operator.openshift.io object not found, skipping reconciliation", "request", req) @@ - return ctrl.Result{}, fmt.Errorf("failed to fetch externalsecretsconfig.openshift.operator.io %q during reconciliation: %w", req.NamespacedName, err) + return ctrl.Result{}, fmt.Errorf("failed to fetch externalsecretsconfigs.operator.openshift.io %q during reconciliation: %w", req.NamespacedName, err) @@ - r.log.V(1).Info("externalsecretsconfig.openshift.operator.io is marked for deletion", "namespace", req.NamespacedName) + r.log.V(1).Info("externalsecretsconfigs.operator.openshift.io is marked for deletion", "resource", req.NamespacedName) @@ - return ctrl.Result{}, fmt.Errorf("clean up failed for %q externalsecretsconfig.openshift.operator.io instance deletion: %w", req.NamespacedName, err) + return ctrl.Result{}, fmt.Errorf("clean up failed for %q externalsecretsconfigs.operator.openshift.io instance deletion: %w", req.NamespacedName, err) @@ - return ctrl.Result{}, fmt.Errorf("failed to update %q externalsecretsconfig.openshift.operator.io with finalizers: %w", req.NamespacedName, err) + return ctrl.Result{}, fmt.Errorf("failed to update %q externalsecretsconfigs.operator.openshift.io with finalizers: %w", req.NamespacedName, err)
367-379
: Same group order issue for ExternalSecretsManager.Use externalsecretsmanagers.operator.openshift.io.
- // Fetch the externalsecretsmanager.openshift.operator.io CR + // Fetch the externalsecretsmanagers.operator.openshift.io CR @@ - r.log.V(1).Info("externalsecretsmanager.openshift.operator.io object not found, continuing without it") + r.log.V(1).Info("externalsecretsmanagers.operator.openshift.io object not found, continuing without it") @@ - return ctrl.Result{}, fmt.Errorf("failed to fetch externalsecretsmanager.openshift.operator.io %q during reconciliation: %w", esmNamespacedName, err) + return ctrl.Result{}, fmt.Errorf("failed to fetch externalsecretsmanagers.operator.openshift.io %q during reconciliation: %w", esmNamespacedName, err)
458-463
: Event message uses wrong group order.Minor copy fix.
- r.eventRecorder.Eventf(esc, corev1.EventTypeWarning, "RemoveDeployment", "%s/%s externalsecretsconfig.openshift.operator.io marked for deletion, remove reference in deployment and remove all resources created for deployment", esc.GetNamespace(), esc.GetName()) + r.eventRecorder.Eventf(esc, corev1.EventTypeWarning, "RemoveDeployment", "%s/%s externalsecretsconfigs.operator.openshift.io marked for deletion, remove reference in deployment and remove all resources created for deployment", esc.GetNamespace(), esc.GetName())config/manifests/bases/external-secrets-operator.clusterserviceversion.yaml (2)
133-138
: Tighten CSV description grammar.Minor copy fixes for clarity.
- description: External Secrets Operator for Red Hat OpenShift deploys and manages - `external-secrets` application in OpenShift clusters. `external-secrets` provides - an uniformed interface to fetch secrets stored in external providers like AWS - Secrets Manager, HashiCorp Vault, Google Secrets Manager, Azure Key Vault, IBM - Cloud Secrets Manager to name a few, stores them as secrets in OpenShift. It provides - APIs to define authentication and the details of the secret to fetch. + description: External Secrets Operator for Red Hat OpenShift deploys and manages + the `external-secrets` application in OpenShift clusters. `external-secrets` provides + a uniform interface to fetch secrets stored in external providers such as AWS + Secrets Manager, HashiCorp Vault, Google Secret Manager, Azure Key Vault, and IBM + Cloud Secrets Manager, and stores them as Secrets in OpenShift. It provides + APIs to define authentication and the details of the secret to fetch.
10-10
: Align createdAt with bundle CSV.Base CSV has createdAt: 2023-03-03; bundle CSV uses 2025-09-18. Consider syncing for consistency.
pkg/controller/external_secrets/validatingwebhook.go (1)
57-73
: Error message phrasing and potential nil-guard.Message says “for %s external secrets”; suggest neutral wording and avoid esc.GetName() in the error path in case esc is nil in future edits.
- if err := updateValidatingWebhookAnnotation(esc, validatingWebhook); err != nil { - return nil, fmt.Errorf("failed to update validatingWebhook resource for %s external secrets: %s", esc.GetName(), err.Error()) - } + if err := updateValidatingWebhookAnnotation(esc, validatingWebhook); err != nil { + return nil, fmt.Errorf("failed to update validatingWebhook annotations: %w", err) + }test/apis/generator.go (5)
47-58
: Deterministic suite order.Map iteration randomizes test order. Sort file paths before loading to stabilize runs.
var out []SuiteSpec - for path := range suiteFiles { + paths := make([]string, 0, len(suiteFiles)) + for p := range suiteFiles { + paths = append(paths, p) + } + sort.Strings(paths) + for _, path := range paths { suite, err := loadSuiteFile(path) if err != nil { return nil, fmt.Errorf("could not set up test suite: %w", err) } out = append(out, suite) }Add import:
import ( "bytes" "fmt" + "sort" "os" "path/filepath" "strings"
396-404
: GenerateName ergonomics.Kubernetes commonly expects generateName prefixes to end with “-”. Add it if missing for readability.
- if useGenerateName { - u.SetGenerateName(resourceName) + if useGenerateName { + gen := resourceName + if !strings.HasSuffix(gen, "-") { + gen += "-" + } + u.SetGenerateName(gen) } else { u.SetName(resourceName) }
256-260
: Comment typo.“sential” → “sentinel”.
- // Use an eventually here, so that we retry until the sential correctly applies. + // Use Eventually here so that we retry until the sentinel correctly applies.
555-585
: Redundant continue.The continue after the if/break is dead code; remove for clarity.
if currCRD.Name == crdName { crdFilesToCheck = append(crdFilesToCheck, filename) break } - continue
14-15
: Prefer maintained YAML lib.Switch from github.com/ghodss/yaml (archived) to sigs.k8s.io/yaml.
- "github.com/ghodss/yaml" + sigsYaml "sigs.k8s.io/yaml"- if err := yaml.Unmarshal(raw, &s); err != nil { + if err := sigsYaml.Unmarshal(raw, &s); err != nil {- if err := yaml.Unmarshal(raw, crd); err != nil { + if err := sigsYaml.Unmarshal(raw, crd); err != nil {- if err := yaml.Unmarshal(patchedData.Bytes(), crd); err != nil { + if err := sigsYaml.Unmarshal(patchedData.Bytes(), crd); err != nil {Also applies to: 431-434, 548-550
pkg/controller/external_secrets/certificate_test.go (1)
467-481
: Helper reuse suggestion.Small DRY win: factor a helper to set Enabled/IssuerRef to cut repetition in table entries.
bundle/manifests/external-secrets-operator.clusterserviceversion.yaml (2)
331-336
: Fix bundle CSV description grammar.Same copy edits as base CSV.
- description: External Secrets Operator for Red Hat OpenShift deploys and manages - `external-secrets` application in OpenShift clusters. `external-secrets` provides - an uniformed interface to fetch secrets stored in external providers like AWS - Secrets Manager, HashiCorp Vault, Google Secrets Manager, Azure Key Vault, IBM - Cloud Secrets Manager to name a few, stores them as secrets in OpenShift. It provides - APIs to define authentication and the details of the secret to fetch. + description: External Secrets Operator for Red Hat OpenShift deploys and manages + the `external-secrets` application in OpenShift clusters. `external-secrets` provides + a uniform interface to fetch secrets stored in external providers such as AWS + Secrets Manager, HashiCorp Vault, Google Secret Manager, Azure Key Vault, and IBM + Cloud Secrets Manager, and stores them as Secrets in OpenShift. It provides + APIs to define authentication and the details of the secret to fetch.
500-523
: RBAC for Proxy object (if read at runtime).If the operator reads cluster proxy config (config.openshift.io/v1, Resource=proxies), add get/list/watch to clusterPermissions. If not, ignore.
I can propose the RBAC stanza once confirmed.
pkg/controller/external_secrets/install_external_secrets.go (1)
100-100
: Align log message with new API nameUse "externalsecretsconfig" for clarity.
- r.log.V(4).Info("finished reconciliation of external-secrets", "namespace", esc.GetNamespace(), "name", esc.GetName()) + r.log.V(4).Info("finished reconciliation of externalsecretsconfig", "namespace", esc.GetNamespace(), "name", esc.GetName())pkg/controller/external_secrets/certificate.go (1)
5-5
: Avoid reflect; use explicit nil/field checksReflect here adds complexity and a false‑positive path. Prefer direct checks.
-import "reflect"
pkg/controller/external_secrets_manager/controller.go (3)
91-96
: Defensive type assertions in predicateHard type assertions can panic; guard them.
- oldObj := e.ObjectOld.(*operatorv1alpha1.ExternalSecretsConfig) - newObj := e.ObjectNew.(*operatorv1alpha1.ExternalSecretsConfig) + oldObj, ok1 := e.ObjectOld.(*operatorv1alpha1.ExternalSecretsConfig) + newObj, ok2 := e.ObjectNew.(*operatorv1alpha1.ExternalSecretsConfig) + if !ok1 || !ok2 { + return false + }
49-50
: Fix finalizer comment to match resourceComment references the wrong group/resource.
- // finalizer name for external-secrets.openshift.operator.io resource. + // finalizer name for externalsecretsmanager.openshift.operator.io resource.
102-103
: Optional: Map ESC updates directly to the singleton ESM requestReduces chance of accidental enqueueing with unexpected names.
Use EnqueueRequestsFromMapFunc to always enqueue NamespacedName{Name: common.ExternalSecretsManagerObjectName}.
pkg/controller/external_secrets/deployments.go (2)
247-249
: Fix duplicated error field paths (“spec.affinity.affinity…”, “spec.tolerations.tolerations…”)Call sites already include the field segment; the validators append it again, duplicating path components.
- if err := validateAffinityRules(affinity, field.NewPath("spec", "affinity")); err != nil { + if err := validateAffinityRules(affinity, field.NewPath("spec")); err != nil { return err }- if err := validateTolerationsConfig(tolerations, field.NewPath("spec", "tolerations")); err != nil { + if err := validateTolerationsConfig(tolerations, field.NewPath("spec")); err != nil { return err }Then update the unit test expectations to reflect correct paths (see related comment).
Also applies to: 269-271
290-294
: Align tolerations validator with caller to avoid double “tolerations” in pathIf you keep the callers as-is, remove the extra Child here. Prefer aligning with the pattern used by nodeSelector.
func validateTolerationsConfig(tolerations []corev1.Toleration, fldPath *field.Path) error { // convert corev1.Tolerations to core.Tolerations, required for validation. convTolerations := *(*[]core.Toleration)(unsafe.Pointer(&tolerations)) - return corevalidation.ValidateTolerations(convTolerations, fldPath.Child("tolerations")).ToAggregate() + return corevalidation.ValidateTolerations(convTolerations, fldPath.Child("tolerations")).ToAggregate() }If you applied the call-site fix, change this to
fldPath.Child("tolerations")
->fldPath
instead.test/apis/vars.go (1)
68-71
: Fix typo in comment (“resourec” → “resource”)Small doc nit.
- // ExpectedError defines the error string that should be returned when the initial resourec is invalid. + // ExpectedError defines the error string that should be returned when the initial resource is invalid.pkg/controller/external_secrets/deployments_test.go (3)
414-418
: Remove duplicated conditional in status image assertionNested
if tt.wantErr == ""
is redundant.- if tt.wantErr == "" { - if tt.wantErr == "" { - if externalsecrets.Status.ExternalSecretsImage != commontest.TestExternalSecretsImageName { - t.Errorf(...) - } - } - } + if tt.wantErr == "" && externalsecrets.Status.ExternalSecretsImage != commontest.TestExternalSecretsImageName { + t.Errorf("createOrApplyDeployments() got image in status: %v, want: %v", externalsecrets.Status.ExternalSecretsImage, "test-image") + }
424-722
: Add tests: proxy removal and initContainers coverage
- Add a case where proxies were previously present, then ESC/ESM unset and OLM envs are absent; expect env vars removed from all containers.
- Add a case with initContainers to assert proxy env propagation/removal there too.
I can draft these test cases to align with the proposed code changes—want me to push them?
231-231
: Error path field duplication is codified in expectationsTests expect
spec.tolerations.tolerations[...]
andspec.affinity.affinity...
. If you adopt the field-path fix in code, update these expected strings accordingly.Also applies to: 312-313
bundle/manifests/operator.openshift.io_externalsecretsconfigs.yaml (2)
1018-1021
: Validation inconsistency for TLS secret reference fieldThe comment mentions that the TLS key pair secret is required, but the field is marked as optional without any validation to ensure it's provided when Bitwarden is enabled.
Consider adding a validation rule to ensure the secret reference is provided when Bitwarden is enabled:
x-kubernetes-validations: - message: secretRef must be provided when Bitwarden is enabled rule: self.enabled != 'true' || has(self.secretRef)
1108-1113
: Consider simplifying the validation expressionThe validation expression is complex and could be simplified for better readability.
- - message: addInjectorAnnotations can only be set when enabled - is set to 'true'. - rule: 'has(self.addInjectorAnnotations) && self.addInjectorAnnotations - != ''false'' ? self.enabled != ''false'' : true' + - message: addInjectorAnnotations can only be set when enabled is set to 'true'. + rule: '!has(self.addInjectorAnnotations) || self.addInjectorAnnotations == ''false'' || self.enabled == ''true'''pkg/controller/external_secrets/utils.go (1)
102-110
: Consider handling additional log levels explicitlyThe log level mapping only handles specific cases (0-2, 4-5) but doesn't handle level 3. This might cause unexpected behavior.
func getLogLevel(config operatorv1alpha1.ExternalSecretsConfigSpec) string { switch config.ApplicationConfig.LogLevel { case 0, 1, 2: return zapcore.Level(config.ApplicationConfig.LogLevel).String() + case 3: + return zapcore.WarnLevel.String() case 4, 5: return zapcore.DebugLevel.String() } return zapcore.InfoLevel.String() }docs/api_reference.md (1)
37-40
: Fix bare URLs in markdown documentationThe documentation contains bare URLs that should be properly formatted as markdown links for better readability.
-| `resources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#resourcerequirements-v1-core)_ | resources is for defining the resource requirements.<br />Cannot be updated.<br />ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ | | Optional: \{\} <br /> | -| `affinity` _[Affinity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#affinity-v1-core)_ | affinity is for setting scheduling affinity rules.<br />ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/ | | Optional: \{\} <br /> | -| `tolerations` _[Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#toleration-v1-core) array_ | tolerations is for setting the pod tolerations.<br />ref: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ | | MaxItems: 10 <br />MinItems: 0 <br />Optional: \{\} <br /> | -| `nodeSelector` _object (keys:string, values:string)_ | nodeSelector is for defining the scheduling criteria using node labels.<br />ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ | | MaxProperties: 10 <br />MinProperties: 0 <br />Optional: \{\} <br /> | +| `resources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#resourcerequirements-v1-core)_ | resources is for defining the resource requirements.<br />Cannot be updated.<br />ref: [Managing Resources for Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) | | Optional: \{\} <br /> | +| `affinity` _[Affinity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#affinity-v1-core)_ | affinity is for setting scheduling affinity rules.<br />ref: [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/) | | Optional: \{\} <br /> | +| `tolerations` _[Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#toleration-v1-core) array_ | tolerations is for setting the pod tolerations.<br />ref: [Taints and Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) | | MaxItems: 10 <br />MinItems: 0 <br />Optional: \{\} <br /> | +| `nodeSelector` _object (keys:string, values:string)_ | nodeSelector is for defining the scheduling criteria using node labels.<br />ref: [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/) | | MaxProperties: 10 <br />MinProperties: 0 <br />Optional: \{\} <br /> |api/v1alpha1/external_secrets_config_types.go (1)
182-183
: Consider using constants for cert-manager validationThe validation rules use hardcoded strings for 'issuer', 'clusterissuer', and 'cert-manager.io'. Consider defining these as constants for maintainability.
While the current validation works, you might want to define constants in the code for these values to ensure consistency across the codebase and make future updates easier.
pkg/controller/crd_annotator/controller.go (3)
215-235
: Thread the request context; avoid r.ctx inside reconciliation pathUsing a background context bypasses cancellation/shutdown and deadlines. Pass ctx through to all client calls.
Apply this diff in this function:
-func (r *Reconciler) processReconcileRequest(esc *operatorv1alpha1.ExternalSecretsConfig, req types.NamespacedName) (ctrl.Result, error) { +func (r *Reconciler) processReconcileRequest(ctx context.Context, esc *operatorv1alpha1.ExternalSecretsConfig, req types.NamespacedName) (ctrl.Result, error) { @@ - if err := r.Get(r.ctx, req, crd); err != nil { + if err := r.Get(ctx, req, crd); err != nil {And update the call site in Reconcile:
return r.processReconcileRequest(ctx, esc, req.NamespacedName)
223-231
: Preserve status updates on NotFound by avoiding early returnCurrently, a NotFound CRD short-circuits before updateCondition, so Status isn’t updated. Update the condition before returning.
Apply this diff:
- if errors.IsNotFound(err) { - r.log.V(1).Info("crd managed by OLM is not found, skipping reconciliation", "crd", req) - return ctrl.Result{}, nil - } + if errors.IsNotFound(err) { + r.log.V(1).Info("crd managed by OLM is not found, skipping reconciliation", "crd", req) + if uerr := r.updateCondition(esc, nil); uerr != nil { + return ctrl.Result{}, uerr + } + return ctrl.Result{}, nil + }
272-276
: Avoid taking the address of the range loop variablePassing &crd uses the address of the loop variable; prefer indexing to take the address of the slice element.
- for _, crd := range managedCRDList.Items { - if err := r.updateAnnotations(&crd); err != nil { + for i := range managedCRDList.Items { + if err := r.updateAnnotations(&managedCRDList.Items[i]); err != nil { return fmt.Errorf("failed to update annotations in %q: %w", crd.GetName(), err) } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base
setting
⛔ Files ignored due to path filters (125)
go.sum
is excluded by!**/*.sum
vendor/github.com/ghodss/yaml/.gitignore
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/ghodss/yaml/.travis.yml
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/ghodss/yaml/LICENSE
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/ghodss/yaml/README.md
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/ghodss/yaml/fields.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/ghodss/yaml/yaml.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/core_dsl.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/decorator_dsl.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/ginkgo/build/build_command.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/dependencies.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/ginkgo_t_dsl.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/internal/around_node.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/internal/focus.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/internal/group.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/internal/node.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/internal/progress_report.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/internal/spec_context.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/internal/suite.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/internal/testingtproxy/testing_t_proxy.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/reporters/default_reporter.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/reporters/junit_report.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/reporters/teamcity_report.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/types/around_node.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/types/config.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/types/errors.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/types/semver_filter.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/types/types.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/ginkgo/v2/types/version.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/gomega/CHANGELOG.md
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/gomega/gomega_dsl.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/gomega/internal/async_assertion.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/gomega/matchers/be_comparable_to_matcher.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/onsi/gomega/matchers/match_yaml_matcher.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/vmware-archive/yaml-patch/LICENSE
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/vmware-archive/yaml-patch/Makefile
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/vmware-archive/yaml-patch/README.md
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/vmware-archive/yaml-patch/container.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/vmware-archive/yaml-patch/node.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/vmware-archive/yaml-patch/operation.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/vmware-archive/yaml-patch/patch.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/vmware-archive/yaml-patch/pathfinder.go
is excluded by!vendor/**
,!**/vendor/**
vendor/github.com/vmware-archive/yaml-patch/placeholder_wrapper.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/net/http2/http2.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/mkerrors.sh
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/syscall_darwin.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux_386.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux_arm.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux_mips.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zsysnum_linux_386.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux_386.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux_arm.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux_mips.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/term/term_windows.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/term/terminal.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/tools/go/ast/astutil/enclosing.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/tools/go/ast/inspector/inspector.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/tools/go/ast/inspector/typeof.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/tools/go/packages/doc.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/tools/go/ssa/builder.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/tools/internal/imports/source_modindex.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/tools/internal/modindex/directories.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/tools/internal/modindex/index.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/tools/internal/modindex/modindex.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/tools/internal/modindex/symbols.go
is excluded by!vendor/**
,!**/vendor/**
vendor/golang.org/x/tools/internal/modindex/types.go
is excluded by!vendor/**
,!**/vendor/**
vendor/google.golang.org/protobuf/encoding/protowire/wire.go
is excluded by!vendor/**
,!**/vendor/**
vendor/google.golang.org/protobuf/internal/editiondefaults/editions_defaults.binpb
is excluded by!vendor/**
,!**/vendor/**
vendor/google.golang.org/protobuf/internal/filedesc/editions.go
is excluded by!vendor/**
,!**/vendor/**
vendor/google.golang.org/protobuf/internal/filedesc/presence.go
is excluded by!vendor/**
,!**/vendor/**
vendor/google.golang.org/protobuf/internal/genid/descriptor_gen.go
is excluded by!vendor/**
,!**/vendor/**
vendor/google.golang.org/protobuf/internal/impl/codec_message_opaque.go
is excluded by!vendor/**
,!**/vendor/**
vendor/google.golang.org/protobuf/internal/impl/message_opaque.go
is excluded by!vendor/**
,!**/vendor/**
vendor/google.golang.org/protobuf/internal/impl/presence.go
is excluded by!vendor/**
,!**/vendor/**
vendor/google.golang.org/protobuf/internal/version/version.go
is excluded by!vendor/**
,!**/vendor/**
vendor/google.golang.org/protobuf/reflect/protoreflect/source_gen.go
is excluded by!vendor/**
,!**/vendor/**
vendor/google.golang.org/protobuf/types/descriptorpb/descriptor.pb.go
is excluded by!**/*.pb.go
,!vendor/**
,!**/vendor/**
vendor/modules.txt
is excluded by!vendor/**
,!**/vendor/**
vendor/sigs.k8s.io/controller-runtime/pkg/envtest/komega/OWNERS
is excluded by!vendor/**
,!**/vendor/**
vendor/sigs.k8s.io/controller-runtime/pkg/envtest/komega/default.go
is excluded by!vendor/**
,!**/vendor/**
vendor/sigs.k8s.io/controller-runtime/pkg/envtest/komega/equalobject.go
is excluded by!vendor/**
,!**/vendor/**
vendor/sigs.k8s.io/controller-runtime/pkg/envtest/komega/interfaces.go
is excluded by!vendor/**
,!**/vendor/**
vendor/sigs.k8s.io/controller-runtime/pkg/envtest/komega/komega.go
is excluded by!vendor/**
,!**/vendor/**
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/multi.go
is excluded by!vendor/**
,!**/vendor/**
📒 Files selected for processing (53)
Makefile
(5 hunks)PROJECT
(1 hunks)README.md
(1 hunks)api/v1alpha1/external_secrets_config_types.go
(4 hunks)api/v1alpha1/external_secrets_manager_types.go
(4 hunks)api/v1alpha1/meta.go
(3 hunks)api/v1alpha1/tests/externalsecretsconfig.operator.openshift.io/externalsecretsconfig.testsuite.yaml
(1 hunks)api/v1alpha1/tests/externalsecretsmanager.operator.openshift.io/externalsecretsmanager.testsuite.yaml
(1 hunks)api/v1alpha1/zz_generated.deepcopy.go
(8 hunks)bundle/manifests/external-secrets-operator.clusterserviceversion.yaml
(6 hunks)bundle/manifests/operator.openshift.io_externalsecretsconfigs.yaml
(9 hunks)bundle/manifests/operator.openshift.io_externalsecretsmanagers.yaml
(5 hunks)config/crd/bases/operator.openshift.io_externalsecretsconfigs.yaml
(9 hunks)config/crd/bases/operator.openshift.io_externalsecretsmanagers.yaml
(5 hunks)config/crd/kustomization.yaml
(1 hunks)config/manifests/bases/external-secrets-operator.clusterserviceversion.yaml
(2 hunks)config/rbac/role.yaml
(2 hunks)config/samples/operator_v1alpha1_externalsecrets.yaml
(1 hunks)docs/api_reference.md
(10 hunks)go.mod
(4 hunks)hack/test-apis.sh
(1 hunks)pkg/controller/common/constants.go
(1 hunks)pkg/controller/common/utils.go
(4 hunks)pkg/controller/commontest/utils.go
(3 hunks)pkg/controller/crd_annotator/controller.go
(8 hunks)pkg/controller/crd_annotator/controller_test.go
(16 hunks)pkg/controller/external_secrets/certificate.go
(5 hunks)pkg/controller/external_secrets/certificate_test.go
(13 hunks)pkg/controller/external_secrets/constants.go
(2 hunks)pkg/controller/external_secrets/controller.go
(9 hunks)pkg/controller/external_secrets/deployments.go
(15 hunks)pkg/controller/external_secrets/deployments_test.go
(14 hunks)pkg/controller/external_secrets/install_external_secrets.go
(3 hunks)pkg/controller/external_secrets/rbacs.go
(11 hunks)pkg/controller/external_secrets/rbacs_test.go
(6 hunks)pkg/controller/external_secrets/secret.go
(3 hunks)pkg/controller/external_secrets/secret_test.go
(3 hunks)pkg/controller/external_secrets/service_test.go
(3 hunks)pkg/controller/external_secrets/serviceaccounts.go
(4 hunks)pkg/controller/external_secrets/serviceaccounts_test.go
(5 hunks)pkg/controller/external_secrets/services.go
(4 hunks)pkg/controller/external_secrets/utils.go
(3 hunks)pkg/controller/external_secrets/validatingwebhook.go
(4 hunks)pkg/controller/external_secrets/validatingwebhook_test.go
(1 hunks)pkg/controller/external_secrets_manager/controller.go
(4 hunks)pkg/controller/external_secrets_manager/controller_test.go
(7 hunks)test/apis/README.md
(1 hunks)test/apis/generator.go
(1 hunks)test/apis/suite_test.go
(1 hunks)test/apis/vars.go
(1 hunks)test/e2e/e2e_test.go
(1 hunks)test/e2e/testdata/external_secret.yaml
(1 hunks)tools/tools.go
(1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
test/apis/README.md
1-1: Bare URL used
(MD034, no-bare-urls)
docs/api_reference.md
37-37: Bare URL used
(MD034, no-bare-urls)
38-38: Bare URL used
(MD034, no-bare-urls)
39-39: Bare URL used
(MD034, no-bare-urls)
40-40: Bare URL used
(MD034, no-bare-urls)
98-98: Bare URL used
(MD034, no-bare-urls)
99-99: Bare URL used
(MD034, no-bare-urls)
100-100: Bare URL used
(MD034, no-bare-urls)
101-101: Bare URL used
(MD034, no-bare-urls)
364-364: Bare URL used
(MD034, no-bare-urls)
365-365: Bare URL used
(MD034, no-bare-urls)
366-366: Bare URL used
(MD034, no-bare-urls)
367-367: Bare URL used
(MD034, no-bare-urls)
🪛 checkmake (0.2.2)
Makefile
[warning] 395-395: Missing required phony target "clean"
(minphony)
bundle/manifests/operator.openshift.io_externalsecretsconfigs.yaml
Outdated
Show resolved
Hide resolved
config/crd/bases/operator.openshift.io_externalsecretsconfigs.yaml
Outdated
Show resolved
Hide resolved
config/crd/bases/operator.openshift.io_externalsecretsmanagers.yaml
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pkg/controller/external_secrets/deployments.go (1)
117-121
: Do not require Bitwarden image unless reconciling Bitwarden deployment.Today, missing RELATED_IMAGE_BITWARDEN_SDK_SERVER fails all deployment reconciliations even if Bitwarden is disabled.
- bitwardenImage := os.Getenv(bitwardenImageEnvVarName) - if bitwardenImage == "" { - return nil, common.NewIrrecoverableError(fmt.Errorf("%s environment variable with bitwarden-sdk-server image not set", bitwardenImageEnvVarName), "failed to update image in %s deployment object", deployment.GetName()) - } @@ case bitwardenDeploymentAssetName: - deployment.Labels["app.kubernetes.io/version"] = bitwardenImageVersionEnvVarName - updateBitwardenServerContainerSpec(deployment, bitwardenImage) + bitwardenImage := os.Getenv(bitwardenImageEnvVarName) + if bitwardenImage == "" { + return nil, common.NewIrrecoverableError(fmt.Errorf("%s environment variable with bitwarden-sdk-server image not set", bitwardenImageEnvVarName), "failed to update image in %s deployment object", deployment.GetName()) + } + deployment.Labels["app.kubernetes.io/version"] = bitwardenImageVersionEnvVarName + updateBitwardenServerContainerSpec(deployment, bitwardenImage)Also applies to: 129-133
🧹 Nitpick comments (11)
pkg/controller/external_secrets/configmap.go (2)
46-47
: Use reconciler context instead of context.TODO().Leverage r.ctx for cancellation, tracing, and consistency with the rest of the controller.
- err := r.Get(context.TODO(), types.NamespacedName{Name: trustedCABundleConfigMapName, Namespace: namespace}, existingConfigMap) + err := r.Get(r.ctx, types.NamespacedName{Name: trustedCABundleConfigMapName, Namespace: namespace}, existingConfigMap) @@ - if err := r.Create(context.TODO(), configMap); err != nil { + if err := r.Create(r.ctx, configMap); err != nil { @@ - if err := r.Update(context.TODO(), existingConfigMap); err != nil { + if err := r.Update(r.ctx, existingConfigMap); err != nil {Also applies to: 51-52, 76-77
65-72
: Minor: avoid recomputing expectedLabels.You already compute expectedLabels at Line 30. Reuse it to reduce noise.
- expectedLabels = getTrustedCABundleLabels(resourceLabels) needsUpdate := false for k, expectedValue := range expectedLabels {
pkg/controller/external_secrets/install_external_secrets.go (1)
111-127
: createOrApplyNamespace doesn't “apply” when Namespace exists.If the Namespace pre-exists, labels are never reconciled. Apply label changes when AlreadyExists.
func (r *Reconciler) createOrApplyNamespace(esc *operatorv1alpha1.ExternalSecretsConfig, resourceLabels map[string]string) error { namespace := getNamespace(esc) obj := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: namespace, Labels: resourceLabels, }, } if err := r.Create(r.ctx, obj); err != nil { if errors.IsAlreadyExists(err) { - r.log.V(4).Info("namespace already exists", "namespace", namespace) - return nil + // Fetch and patch labels if they differ + existing := &corev1.Namespace{} + if getErr := r.Get(r.ctx, types.NamespacedName{Name: namespace}, existing); getErr != nil { + return getErr + } + if !reflect.DeepEqual(existing.Labels, resourceLabels) { + if existing.Labels == nil { + existing.Labels = map[string]string{} + } + for k, v := range resourceLabels { + existing.Labels[k] = v + } + if updErr := r.Update(r.ctx, existing); updErr != nil { + return updErr + } + } + return nil } return err } return nil }Note: add imports if missing:
- "reflect"
- "k8s.io/apimachinery/pkg/types"
pkg/controller/external_secrets/controller.go (2)
225-228
: Fix wrong object in error message.Uses ownObject in the esm informer error; should log esmObject.
- if err != nil { - return nil, fmt.Errorf("failed to add informer for %s resource: %w", ownObject.GetObjectKind().GroupVersionKind().String(), err) - } + if err != nil { + return nil, fmt.Errorf("failed to add informer for %s resource: %w", esmObject.GetObjectKind().GroupVersionKind().String(), err) + }
270-277
: Tighten ConfigMap predicate to avoid cross‑namespace noise.Name-only match can enqueue on similarly named CMs in other namespaces. Prefer filtering by the injection label you control.
- // predicate function for ConfigMaps to include both managed resources and trusted CA bundle ConfigMap - configMapPredicate := predicate.NewPredicateFuncs(func(object client.Object) bool { - if object.GetLabels() != nil && object.GetLabels()[requestEnqueueLabelKey] == requestEnqueueLabelValue { - return true - } - return object.GetName() == trustedCABundleConfigMapName - }) + // Predicate for ConfigMaps: include controller-managed resources and any CM with the injection label + configMapPredicate := predicate.NewPredicateFuncs(func(object client.Object) bool { + labels := object.GetLabels() + if labels != nil && labels[requestEnqueueLabelKey] == requestEnqueueLabelValue { + return true + } + return labels != nil && labels[trustedCABundleInjectLabel] == "true" + })pkg/controller/external_secrets/deployments_test.go (2)
425-759
: Add coverage: initContainers, lowercase envs, and removal of stale proxy vars.Current tests validate only containers and uppercase envs. Please add cases to assert:
- initContainers receive the same proxy env and mounts
- lowercase http_proxy/https_proxy/no_proxy are set
- when no proxy is configured from ESC/ESM/OLM, any existing proxy envs are removed
761-774
: Make env var comparison order-insensitive.Container.Env ordering is not guaranteed. Sort by Name before comparing to reduce test flakiness.
func validateEnvironmentVariables(t *testing.T, deployment *appsv1.Deployment, expectedContainerEnvVars map[string][]corev1.EnvVar) { for containerName, expectedEnvVars := range expectedContainerEnvVars { container := findContainer(deployment, containerName) if container == nil { t.Errorf("Container %s not found in deployment", containerName) return } - if !reflect.DeepEqual(container.Env, expectedEnvVars) { + sort.Slice(container.Env, func(i, j int) bool { return container.Env[i].Name < container.Env[j].Name }) + sort.Slice(expectedEnvVars, func(i, j int) bool { return expectedEnvVars[i].Name < expectedEnvVars[j].Name }) + if !reflect.DeepEqual(container.Env, expectedEnvVars) { t.Errorf("Container %s environment variables mismatch.\nExpected: %+v\nActual: %+v", containerName, expectedEnvVars, container.Env) } } }Note: add
import "sort"
at top.pkg/controller/external_secrets/deployments.go (3)
185-187
: Guard against nil r.esm in fallbacks.Avoid potential NPEs by checking r.esm != nil before dereferencing in resource/affinity/toleration/nodeSelector fallbacks.
- } else if r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.Resources != nil { + } else if r.esm != nil && r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.Resources != nil { @@ - } else if r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.NodeSelector != nil { + } else if r.esm != nil && r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.NodeSelector != nil { @@ - } else if r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.Affinity != nil { + } else if r.esm != nil && r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.Affinity != nil { @@ - } else if r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.Tolerations != nil { + } else if r.esm != nil && r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.Tolerations != nil {Also applies to: 217-219, 239-241, 262-263
411-424
: Proxies: support removal, initContainers, and lowercase envs.
- Remove stale proxy envs when no proxy config resolves from ESC/ESM/OLM
- Apply to initContainers as well
- Set lowercase variants for broader image compatibility
func (r *Reconciler) updateProxyEnvironmentVariables(deployment *appsv1.Deployment, esc *operatorv1alpha1.ExternalSecretsConfig) error { proxyConfig := r.getProxyConfiguration(esc) - if proxyConfig == nil { - return nil - } - - // Apply proxy environment variables to all containers - for i := range deployment.Spec.Template.Spec.Containers { - container := &deployment.Spec.Template.Spec.Containers[i] - r.setProxyEnvVars(container, proxyConfig) - } + // Helper to process both containers and initContainers + process := func(envSetter func(c *corev1.Container)) { + for i := range deployment.Spec.Template.Spec.Containers { + envSetter(&deployment.Spec.Template.Spec.Containers[i]) + } + for i := range deployment.Spec.Template.Spec.InitContainers { + envSetter(&deployment.Spec.Template.Spec.InitContainers[i]) + } + } + if proxyConfig == nil { + process(func(c *corev1.Container) { r.removeProxyEnvVars(c) }) + return nil + } + process(func(c *corev1.Container) { r.setProxyEnvVars(c, proxyConfig) }) return nil } @@ // Set proxy environment variables setEnvVar("HTTP_PROXY", proxyConfig.HTTPProxy) setEnvVar("HTTPS_PROXY", proxyConfig.HTTPSProxy) setEnvVar("NO_PROXY", proxyConfig.NoProxy) + // Lowercase variants + setEnvVar("http_proxy", proxyConfig.HTTPProxy) + setEnvVar("https_proxy", proxyConfig.HTTPSProxy) + setEnvVar("no_proxy", proxyConfig.NoProxy)Add helper outside the shown ranges:
// removeProxyEnvVars deletes all proxy-related env vars from a container. func (r *Reconciler) removeProxyEnvVars(container *corev1.Container) { if len(container.Env) == 0 { return } toDrop := map[string]struct{}{ "HTTP_PROXY": {}, "HTTPS_PROXY": {}, "NO_PROXY": {}, "http_proxy": {}, "https_proxy": {}, "no_proxy": {}, } dst := container.Env[:0] for _, e := range container.Env { if _, drop := toDrop[e.Name]; !drop { dst = append(dst, e) } } container.Env = dst }Also applies to: 426-459
509-524
: Mount trusted CA bundle in initContainers too.Parity with main containers ensures any init work honors the proxy CA.
- for i := range deployment.Spec.Template.Spec.Containers { + for i := range deployment.Spec.Template.Spec.Containers { container := &deployment.Spec.Template.Spec.Containers[i] @@ } + + // Add volume mounts to all init containers + for i := range deployment.Spec.Template.Spec.InitContainers { + container := &deployment.Spec.Template.Spec.InitContainers[i] + volumeMountExists := false + for j, volumeMount := range container.VolumeMounts { + if volumeMount.Name == trustedCABundleVolumeName { + container.VolumeMounts[j] = trustedCAVolumeMount + volumeMountExists = true + break + } + } + if !volumeMountExists { + container.VolumeMounts = append(container.VolumeMounts, trustedCAVolumeMount) + } + }pkg/controller/external_secrets/constants.go (1)
52-66
: Add derived file path constant for the injected CA bundleConfirmed usage of ConfigMapName, InjectLabel, VolumeName, MountPath and KeyName; no direct full-path references exist—derive once instead of recomputing.
// trustedCABundleKeyName is the key in the ConfigMap that contains the CA bundle trustedCABundleKeyName = "ca-bundle.crt" + // trustedCABundleFilePath is the full path to the injected CA bundle file + trustedCABundleFilePath = trustedCABundleMountPath + "/" + trustedCABundleKeyName
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base
setting
📒 Files selected for processing (7)
pkg/controller/external_secrets/configmap.go
(1 hunks)pkg/controller/external_secrets/constants.go
(2 hunks)pkg/controller/external_secrets/controller.go
(9 hunks)pkg/controller/external_secrets/deployments.go
(15 hunks)pkg/controller/external_secrets/deployments_test.go
(15 hunks)pkg/controller/external_secrets/install_external_secrets.go
(3 hunks)pkg/controller/external_secrets/utils.go
(4 hunks)
🔇 Additional comments (6)
pkg/controller/external_secrets/deployments.go (1)
410-415
: Guard getProxyConfiguration against nil r.esm.If not already fixed, ensure r.esm is checked inside getProxyConfiguration to avoid panics. This was flagged earlier.
Additional snippet (outside range):
func (r *Reconciler) getProxyConfiguration(esc *operatorv1alpha1.ExternalSecretsConfig) *operatorv1alpha1.ProxyConfig { // ESC overrides if esc != nil && esc.Spec.ApplicationConfig.CommonConfigs.Proxy != nil { return esc.Spec.ApplicationConfig.CommonConfigs.Proxy } // ESM fallback if r != nil && r.esm != nil && r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.CommonConfigs.Proxy != nil { return r.esm.Spec.GlobalConfig.CommonConfigs.Proxy } // OLM env fallback... // ... return nil }pkg/controller/external_secrets/install_external_secrets.go (1)
55-57
: Don't swallow namespace reconcile errors.Propagate the error so the loop can requeue and status can reflect failure. This was flagged earlier.
if err := r.createOrApplyNamespace(esc, resourceLabels); err != nil { - r.log.Error(err, "failed to create namespace") + r.log.Error(err, "failed to reconcile namespace resource") + return err }pkg/controller/external_secrets/controller.go (2)
429-433
: Status updates short‑circuited; Ready may not update.Evaluate both SetStatusCondition calls, then update once. Same issue was previously noted.
- if apimeta.SetStatusCondition(&esc.Status.Conditions, degradedCond) || - apimeta.SetStatusCondition(&esc.Status.Conditions, readyCond) { + changed := apimeta.SetStatusCondition(&esc.Status.Conditions, degradedCond) + changed = apimeta.SetStatusCondition(&esc.Status.Conditions, readyCond) || changed + if changed { errUpdate = r.updateCondition(esc, err) err = utilerrors.NewAggregate([]error{err, errUpdate}) }
456-459
: Same short‑circuit bug on success path.- if apimeta.SetStatusCondition(&esc.Status.Conditions, degradedCond) || - apimeta.SetStatusCondition(&esc.Status.Conditions, readyCond) { - errUpdate = r.updateCondition(esc, nil) - } + changed := apimeta.SetStatusCondition(&esc.Status.Conditions, degradedCond) + changed = apimeta.SetStatusCondition(&esc.Status.Conditions, readyCond) || changed + if changed { + errUpdate = r.updateCondition(esc, nil) + }pkg/controller/external_secrets/constants.go (2)
48-51
: LGTM: default namespace comment clarified.Comment correctly reflects ExternalSecretsConfig.Spec.Namespace fallback.
19-21
: Fix finalizer domain and keep a legacy finalizer for upgrade paths.The string uses openshift.operator.io (reversed). Use operator.openshift.io and add a legacy constant so existing CRs don’t get stranded on upgrade. Also update the stale comment.
Apply:
- // finalizer name for external-secrets.openshift.operator.io resource. - finalizer = "externalsecretsconfig.openshift.operator.io/" + ControllerName + // finalizer name for ExternalSecretsConfig resources. + // NOTE: keep legacy finalizer for upgrade cleanup of existing CRs. + finalizer = "externalsecretsconfig.operator.openshift.io/" + ControllerName + legacyFinalizer = "externalsecrets.openshift.operator.io/" + ControllerNameRun to find and update add/remove checks to handle both finalizers (prefer new; remove both):
#!/bin/bash # Find finalizer string literals and controller-runtime helpers usage rg -nC2 -P 'externalsecrets.*openshift\.operator\.io/|-finalizer|-Finalizer|controllerutil\.(AddFinalizer|RemoveFinalizer|ContainsFinalizer)\('
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
bundle/manifests/external-secrets-operator.clusterserviceversion.yaml (2)
331-337
: Fix user‑facing grammar and clarity in description
- “an uniformed interface” → “a unified interface”
- Remove double space before “AWS”
- Clarify that secrets are stored as Kubernetes Secrets.
Apply:
- description: External Secrets Operator for Red Hat OpenShift deploys and manages - `external-secrets` application in OpenShift clusters. `external-secrets` provides - an uniformed interface to fetch secrets stored in external providers like AWS - Secrets Manager, HashiCorp Vault, Google Secrets Manager, Azure Key Vault, IBM - Cloud Secrets Manager to name a few, stores them as secrets in OpenShift. It provides - APIs to define authentication and the details of the secret to fetch. + description: External Secrets Operator for Red Hat OpenShift deploys and manages + the `external-secrets` application in OpenShift clusters. `external-secrets` provides + a unified interface to fetch secrets stored in external providers like AWS + Secrets Manager, HashiCorp Vault, Google Secrets Manager, Azure Key Vault, and IBM + Cloud Secrets Manager, and stores them as Kubernetes Secrets in OpenShift. It provides + APIs to define authentication and the details of the secret to fetch.
500-523
: Add patch verb to externalsecretsconfigs ClusterRole
Controller patches ExternalSecretsConfig (finalizers); addpatch
to the main resource’s verbs. No patch needed on the status subresource.--- bundle/manifests/external-secrets-operator.clusterserviceversion.yaml @@ -502,7 +502,8 @@ - operator.openshift.io resources: - externalsecretsconfigs - verbs: - - create - - get - - list - - update - - watch + verbs: + - create + - get + - list + - patch + - update + - watch
🧹 Nitpick comments (4)
config/manager/trusted-ca-patch.yaml (2)
11-15
: Explicitly set SSL_CERT_FILE for portability.To ensure all base images/apps pick up the injected CA, set SSL_CERT_FILE to the mounted bundle.
Apply this diff:
- name: manager + env: + - name: SSL_CERT_FILE + value: /etc/pki/tls/certs/ca-bundle.crt volumeMounts: - name: trusted-ca-bundle mountPath: /etc/pki/tls/certs readOnly: truePlease confirm the controller image base (UBI/distroless/etc.). If it’s not UBI/RHEL‑like, we may need SSL_CERT_DIR instead or a different path.
10-14
: Harden container security context (addresses Checkov hints).You can set restrictive defaults here without affecting functionality.
Apply this diff:
- name: manager + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + seccompProfile: + type: RuntimeDefaultbundle/manifests/external-secrets-operator.clusterserviceversion.yaml (2)
179-189
: alm-example: ExternalSecretsConfig entry looks correctKind/name align with singleton pattern; empty spec is fine for a minimal example.
Consider showcasing a minimal ProxyConfig in the example to demonstrate HTTP(S) proxy usage.
668-685
: Mount CA bundle via subPath to avoid shadowing system certs
Use subPath to mount only ca-bundle.crt instead of the entire certs directory. The ConfigMap external-secrets-operator-trusted-ca-bundle (with config.openshift.io/inject-trusted-cabundle: "true") is present.- volumeMounts: - - mountPath: /etc/pki/tls/certs - name: trusted-ca-bundle - readOnly: true + volumeMounts: + - mountPath: /etc/pki/tls/certs/ca-bundle.crt + name: trusted-ca-bundle + subPath: ca-bundle.crt + readOnly: true
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base
setting
📒 Files selected for processing (4)
bundle/manifests/external-secrets-operator-trusted-ca-bundle_v1_configmap.yaml
(1 hunks)bundle/manifests/external-secrets-operator.clusterserviceversion.yaml
(7 hunks)config/manager/kustomization.yaml
(1 hunks)config/manager/trusted-ca-patch.yaml
(1 hunks)
✅ Files skipped from review due to trivial changes (2)
- config/manager/kustomization.yaml
- bundle/manifests/external-secrets-operator-trusted-ca-bundle_v1_configmap.yaml
🧰 Additional context used
🪛 Checkov (3.2.334)
config/manager/trusted-ca-patch.yaml
[medium] 1-21: Containers should not run with allowPrivilegeEscalation
(CKV_K8S_20)
[medium] 1-21: Minimize the admission of root containers
(CKV_K8S_23)
🔇 Additional comments (2)
config/manager/trusted-ca-patch.yaml (1)
16-21
: LGTM on volume wiring and key mapping.Mounting ca-bundle.crt read-only at /etc/pki/tls/certs is correct for OpenShift’s injected bundle.
If a kustomize ConfigMapGenerator is also used for this bundle, ensure there is no duplication and either:
- use the static, annotated CM (no nameSuffixHash), or
- rely solely on the generator and verify kustomize rewrites the name reference in volumes.configMap.name.
bundle/manifests/external-secrets-operator.clusterserviceversion.yaml (1)
267-276
: CRD ownership/description rename to ExternalSecretsConfig is consistentKind, name, and singleton wording align with the API rebrand.
bundle/manifests/external-secrets-operator.clusterserviceversion.yaml
Outdated
Show resolved
Hide resolved
volumes: | ||
- name: trusted-ca-bundle | ||
configMap: | ||
name: trusted-ca-bundle |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
ConfigMap name likely mismatched with the injected bundle manifest.
This references ConfigMap "trusted-ca-bundle", while the PR context mentions creating "external-secrets-operator-trusted-ca-bundle". If the names differ, the volume will mount an empty/non‑existent CM at runtime.
Apply this diff to align with the injected bundle name:
configMap:
- name: trusted-ca-bundle
+ name: external-secrets-operator-trusted-ca-bundle
Run this script to verify CM names across the repo and ensure consistency:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== kustomization in config/manager =="
fd -t f kustomization.yaml | rg -n '^config/manager/.*' -n
rg -n -C2 'configMapGenerator|name:\s*trusted-ca-bundle|external-secrets-operator-trusted-ca-bundle' config/manager/kustomization.yaml || true
echo "== CM manifests mentioning trusted CA bundle =="
fd -H -t f '.*(trusted|ca-bundle).*\.ya?ml' | rg -n -C2 'external-secrets-operator-trusted-ca-bundle|trusted-ca-bundle' || true
Length of output: 375
Align ConfigMap name to the injected bundle
Update both the volume patch and the configMapGenerator in the manager overlay to use the actual ConfigMap name:
- In config/manager/trusted-ca-patch.yaml, change
to
name: trusted-ca-bundle
name: external-secrets-operator-trusted-ca-bundle
- In config/manager/kustomization.yaml’s configMapGenerator, change
to
- name: trusted-ca-bundle
- name: external-secrets-operator-trusted-ca-bundle
🤖 Prompt for AI Agents
In config/manager/trusted-ca-patch.yaml around line 18 change the volume's name
from "trusted-ca-bundle" to "external-secrets-operator-trusted-ca-bundle"; also
update config/manager/kustomization.yaml (configMapGenerator) to rename the
generated ConfigMap from "trusted-ca-bundle" to
"external-secrets-operator-trusted-ca-bundle" so both the volume patch and the
generated ConfigMap names match the injected bundle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
pkg/controller/external_secrets/deployments.go (5)
113-120
: Bitwarden image required unconditionally and version label bug
- getDeploymentObject fails for non-bitwarden assets when BITWARDEN image env is unset.
- The version label is set to the env var name constant instead of its value.
Scope the bitwarden image requirement to the bitwarden asset and set the label to the actual version value.
Apply this diff:
image := os.Getenv(externalsecretsImageEnvVarName) if image == "" { return nil, common.NewIrrecoverableError(fmt.Errorf("%s environment variable with externalsecrets image not set", externalsecretsImageEnvVarName), "failed to update image in %s deployment object", deployment.GetName()) } - bitwardenImage := os.Getenv(bitwardenImageEnvVarName) - if bitwardenImage == "" { - return nil, common.NewIrrecoverableError(fmt.Errorf("%s environment variable with bitwarden-sdk-server image not set", bitwardenImageEnvVarName), "failed to update image in %s deployment object", deployment.GetName()) - } logLevel := getLogLevel(esc.Spec) switch assetName { case controllerDeploymentAssetName: updateContainerSpec(deployment, esc, image, logLevel) case webhookDeploymentAssetName: updateWebhookContainerSpec(deployment, image, logLevel) case certControllerDeploymentAssetName: updateCertControllerContainerSpec(deployment, image, logLevel) case bitwardenDeploymentAssetName: - deployment.Labels["app.kubernetes.io/version"] = bitwardenImageVersionEnvVarName - updateBitwardenServerContainerSpec(deployment, bitwardenImage) + // Bitwarden image/env are only required for this asset. + bitwardenImage := os.Getenv(bitwardenImageEnvVarName) + if bitwardenImage == "" { + return nil, common.NewIrrecoverableError(fmt.Errorf("%s environment variable with bitwarden-sdk-server image not set", bitwardenImageEnvVarName), "failed to update image in %s deployment object", deployment.GetName()) + } + if deployment.Labels == nil { + deployment.Labels = map[string]string{} + } + if v := os.Getenv(bitwardenImageVersionEnvVarName); v != "" { + deployment.Labels["app.kubernetes.io/version"] = v + } + updateBitwardenServerContainerSpec(deployment, bitwardenImage) }Also applies to: 124-133
180-202
: Nil deref risk: r.esm checks missingAccesses r.esm.Spec.GlobalConfig without guarding r.esm != nil; can panic.
Apply this diff:
func (r *Reconciler) updateResourceRequirement(deployment *appsv1.Deployment, esc *operatorv1alpha1.ExternalSecretsConfig) error { rscReqs := corev1.ResourceRequirements{} if esc.Spec.ApplicationConfig.Resources != nil { esc.Spec.ApplicationConfig.Resources.DeepCopyInto(&rscReqs) - } else if r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.Resources != nil { + } else if r.esm != nil && r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.Resources != nil { r.esm.Spec.GlobalConfig.Resources.DeepCopyInto(&rscReqs) } else { return nil }
211-231
: Nil deref risk: r.esm checks missing (node selector)Same issue here.
Apply this diff:
- } else if r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.NodeSelector != nil { + } else if r.esm != nil && r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.NodeSelector != nil { nodeSelector = r.esm.Spec.GlobalConfig.NodeSelector }
233-253
: Nil deref risk: r.esm checks missing (affinity)Same issue here.
Apply this diff:
- } else if r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.Affinity != nil { + } else if r.esm != nil && r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.Affinity != nil { affinity = r.esm.Spec.GlobalConfig.Affinity }
255-275
: Nil deref risk: r.esm checks missing (tolerations)Same issue here.
Apply this diff:
- } else if r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.Tolerations != nil { + } else if r.esm != nil && r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.Tolerations != nil { tolerations = r.esm.Spec.GlobalConfig.Tolerations }
🧹 Nitpick comments (5)
bundle/manifests/external-secrets-operator.clusterserviceversion.yaml (1)
331-336
: Minor wording nit in description“an uniformed interface” → “a unified interface”.
Apply this diff:
- an uniformed interface to fetch secrets stored in external providers like AWS + a unified interface to fetch secrets stored in external providers like AWSpkg/controller/external_secrets/deployments_test.go (3)
755-768
: Tests compare env vars by order; consider order-insensitive comparisonDeepEqual requires exact ordering. Consider comparing by map keyed by Name to reduce brittleness.
770-787
: Volumes check is strict; consider contains-based assertionDeepEqual on full slice fails if unrelated volumes are present. Prefer asserting the presence and equality of expected volumes.
789-817
: VolumeMounts check is strict; consider contains-based assertion per containerFilter is good; still DeepEqual enforces order. Consider sort-by-name or contains check.
pkg/controller/external_secrets/deployments.go (1)
461-522
: Trusted CA bundle: make optional and mount for initContainers tooAvoid startup failures if the ConfigMap is not yet present; ensure initContainers also trust the CA.
Apply this diff:
trustedCAVolume := corev1.Volume{ Name: trustedCABundleVolumeName, VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ LocalObjectReference: corev1.LocalObjectReference{ Name: trustedCABundleConfigMapName, }, + Optional: ptr.To(true), }, }, } @@ for i := range deployment.Spec.Template.Spec.Containers { container := &deployment.Spec.Template.Spec.Containers[i] @@ if !volumeMountExists { container.VolumeMounts = append(container.VolumeMounts, trustedCAVolumeMount) } } + + // Also mount in initContainers + for i := range deployment.Spec.Template.Spec.InitContainers { + c := &deployment.Spec.Template.Spec.InitContainers[i] + volumeMountExists := false + for j, vm := range c.VolumeMounts { + if vm.Name == trustedCABundleVolumeName { + c.VolumeMounts[j] = trustedCAVolumeMount + volumeMountExists = true + break + } + } + if !volumeMountExists { + c.VolumeMounts = append(c.VolumeMounts, trustedCAVolumeMount) + } + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base
setting
📒 Files selected for processing (4)
bundle/manifests/external-secrets-operator.clusterserviceversion.yaml
(7 hunks)config/manager/trusted-ca-patch.yaml
(1 hunks)pkg/controller/external_secrets/deployments.go
(15 hunks)pkg/controller/external_secrets/deployments_test.go
(15 hunks)
🧰 Additional context used
🪛 Checkov (3.2.334)
config/manager/trusted-ca-patch.yaml
[medium] 1-18: Containers should not run with allowPrivilegeEscalation
(CKV_K8S_20)
[medium] 1-18: Minimize the admission of root containers
(CKV_K8S_23)
🔇 Additional comments (7)
config/manager/trusted-ca-patch.yaml (3)
5-5
: Remove hardcoded namespace from the patch.Delete Line 5 to keep the patch namespace-agnostic for overlays.
Apply this diff:
metadata: name: controller-manager - namespace: system
16-18
: Align ConfigMap name with injected bundle and make it optional.Use the actual injected CM name and avoid startup failures if it’s not present yet.
Apply this diff:
volumes: - name: trusted-ca-bundle configMap: - name: trusted-ca-bundle + name: external-secrets-operator-trusted-ca-bundle + optional: true + items: + - key: ca-bundle.crt + path: ca-bundle.crt
1-18
: Verify base image CA path assumptions. No occurrences of SSL_CERT_FILE, REQUESTS_CA_BUNDLE, or alternate CA bundle filenames were found; confirm the controller image reads CAs from /etc/pki/tls/certs/ca-bundle.crt or adjust the mountPath or set SSL_CERT_FILE/REQUESTS_CA_BUNDLE to match its CA path.bundle/manifests/external-secrets-operator.clusterserviceversion.yaml (3)
213-213
: Proxy-aware annotation: goodEnables OLM to inject HTTP(S)_PROXY/NO_PROXY; aligns with the PR goal.
637-637
: Pin operator image (avoid :latest) in DeploymentThe install spec still uses :latest; pin to a digest to match supply‑chain best practices.
Apply this diff (replace with the real digest/tag):
- image: openshift.io/external-secrets-operator:latest + image: openshift.io/external-secrets-operator@sha256:<digest>
206-206
: Pin operator image (avoid :latest) in CSV metadataUsing a mutable tag harms reproducibility and compliance. Pin to an immutable digest.
Apply this diff (replace with the real digest/tag):
- containerImage: openshift.io/external-secrets-operator:latest + containerImage: openshift.io/external-secrets-operator@sha256:<digest>pkg/controller/external_secrets/deployments.go (1)
410-424
: Proxy envs: add lowercase variants and cover initContainersMany images honor lowercase envs; also initContainers need the same proxy envs. Keep “no change” behavior when no proxy is resolved to avoid breaking existing tests.
Apply this diff:
func (r *Reconciler) updateProxyEnvironmentVariables(deployment *appsv1.Deployment, esc *operatorv1alpha1.ExternalSecretsConfig) error { proxyConfig := r.getProxyConfiguration(esc) if proxyConfig == nil { return nil } - // Apply proxy environment variables to all containers - for i := range deployment.Spec.Template.Spec.Containers { - container := &deployment.Spec.Template.Spec.Containers[i] - r.setProxyEnvVars(container, proxyConfig) - } + // Apply proxy environment variables to all containers and initContainers + for i := range deployment.Spec.Template.Spec.Containers { + r.setProxyEnvVars(&deployment.Spec.Template.Spec.Containers[i], proxyConfig) + } + for i := range deployment.Spec.Template.Spec.InitContainers { + r.setProxyEnvVars(&deployment.Spec.Template.Spec.InitContainers[i], proxyConfig) + } return nil } @@ // Set proxy environment variables setEnvVar("HTTP_PROXY", proxyConfig.HTTPProxy) setEnvVar("HTTPS_PROXY", proxyConfig.HTTPSProxy) setEnvVar("NO_PROXY", proxyConfig.NoProxy) + // Lowercase variants for broader compatibility + setEnvVar("http_proxy", proxyConfig.HTTPProxy) + setEnvVar("https_proxy", proxyConfig.HTTPSProxy) + setEnvVar("no_proxy", proxyConfig.NoProxy) }Also applies to: 426-459
- name: trusted-ca-bundle | ||
mountPath: /etc/pki/tls/certs | ||
readOnly: true | ||
volumes: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not mount over the entire /etc/pki/tls/certs (masks system certs). Mount only ca-bundle.crt via subPath.
Mounting the whole directory can break TLS trust by hiding system CA certs.
Apply this diff:
- name: manager
volumeMounts:
- - name: trusted-ca-bundle
- mountPath: /etc/pki/tls/certs
- readOnly: true
+ - name: trusted-ca-bundle
+ mountPath: /etc/pki/tls/certs/ca-bundle.crt
+ subPath: ca-bundle.crt
+ readOnly: true
🤖 Prompt for AI Agents
In config/manager/trusted-ca-patch.yaml around lines 12-15, the pod currently
mounts the entire /etc/pki/tls/certs directory which masks system CA certs;
change the volumeMount to only mount the ca-bundle.crt file using subPath (e.g.
set mountPath to /etc/pki/tls/certs/ca-bundle.crt and add subPath:
ca-bundle.crt) and keep readOnly: true so the system certs remain available
while supplying the custom bundle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mounting as subPath
will not automatically update the mounted value during configmap modification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
OpenShift trusted CA bundle injection mounting best practices subPath vs directory mount automatic updates
💡 Result:
Short answer
- Don’t use subPath when mounting an OpenShift “trusted CA” ConfigMap if you want automatic in‑place updates; subPath-mounted files are not updated in running pods. Mount the ConfigMap as a directory (or project the file into the directory) so kubelet’s ConfigMap/Secret update mechanism can refresh the bundle. [1][2][3]
Why
- Kubernetes mounts files from ConfigMaps/Secrets using symlink/atomic‑replace techniques; subPath creates a separate filesystem mount that breaks that update mechanism so the file in the container won’t be replaced while the pod is running. This is a long‑standing limitation/behavior documented in issues and answers. [1][3]
OpenShift specifics / recommended approaches
- Use OpenShift’s trusted CA injection pattern: create the empty ConfigMap with label config.openshift.io/inject-trusted-cabundle="true" and mount the resulting ConfigMap (with key ca-bundle.crt) into the container trust directory (for example /etc/pki/ca-trust/extracted/pem). Mount the ConfigMap as a directory (or with items that place ca-bundle.crt into the directory), not via subPath. That allows the cluster operators and kubelet to merge/inject and update the bundle correctly. [2][5]
If you must use a single-file mount or need immediate in‑pod refresh
- Workarounds:
- Use a directory mount and name the file inside the volume (items: key: ca-bundle.crt path: tls-ca-bundle.pem) instead of subPath. [2][5]
- Have an initContainer or sidecar that copies the file from the mounted directory to the exact path your process expects, and implement a file-watcher/reload in the container to respond to changes (or restart the process when the file changes).
- If you need an atomic reload across many pods, consider updating the Deployment/StatefulSet (rolling restart) after changing the ConfigMap, or use an operator that supports certificate/CA updates. [2][4]
Practical checklist
- Put your CA in a ConfigMap with key ca-bundle.crt. [5]
- Mount the ConfigMap as a directory under the process trust dir (avoid subPath). [2][5]
- Confirm the app can pick up changed bundle (either via automatic file watching, restart, or sidecar copy+reload).
- For cluster‑wide injection, use the OpenShift inject-trusted-cabundle label so cluster operators handle merging/injection. [2][5]
Sources
- Kubernetes issue and behavior discussion (subPath prevents ConfigMap/Secret updates). [GitHub kubernetes/kubernetes issue #50345 and related threads]. [1]
- OpenShift docs: Configuring a custom PKI / trusted CA bundle injection and mount examples. [2][5]
- Community Q&A noting subPath update behavior (StackOverflow). [3]
@chiragkyal You're absolutely correct! Using subPath
would indeed prevent automatic ConfigMap updates—that's a known Kubernetes limitation where subPath creates a static filesystem mount that doesn't get refreshed when the ConfigMap changes.
Given this trade-off, here are better alternatives that preserve both automatic updates and system certificates:
Option 1: Use items
to project only the CA bundle file (Recommended)
containers:
- name: manager
volumeMounts:
- name: trusted-ca-bundle
mountPath: /etc/pki/ca-trust/extracted/pem
readOnly: true
volumes:
- name: trusted-ca-bundle
configMap:
name: trusted-ca-bundle
items:
- key: ca-bundle.crt
path: tls-ca-bundle.pem
This mounts the ConfigMap as a directory but projects only the specific key, allowing updates while not masking the entire system cert directory.
Option 2: Mount to a custom CA trust path
containers:
- name: manager
volumeMounts:
- name: trusted-ca-bundle
mountPath: /etc/ssl/certs/openshift-injected
readOnly: true
This avoids masking system certs by using a separate directory, though you'd need to ensure the application can read CAs from this path.
Which approach works best depends on how the external-secrets-operator components consume CA certificates. Do they use the system CA bundle automatically, or would they need explicit configuration to pick up certificates from a custom path?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Option 1: Use items to project only the CA bundle file (Recommended)
We are creating a blank configmap through OLM which won't have any items configured with it. Later CNO will inject the data field in the configmap. Now, without having the target items in the ConfigMap, the operator pods will never come up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chiragkyal Excellent point! You're right that using items
would cause pod startup failures when the ConfigMap key doesn't exist yet.
Here's the best solution for your scenario where the ConfigMap starts empty and gets populated later by CNO:
Recommended approach: Use optional: true
and mount to the proper CA trust path
containers:
- name: manager
volumeMounts:
- name: trusted-ca-bundle
mountPath: /etc/pki/ca-trust/extracted/pem
readOnly: true
volumes:
- name: trusted-ca-bundle
configMap:
name: trusted-ca-bundle
optional: true # Allows pod to start even if ConfigMap is empty
This approach:
- ✅ Allows pod startup even with an empty ConfigMap (due to
optional: true
) - ✅ Enables automatic updates when CNO injects the CA bundle (no subPath)
- ✅ Doesn't mask system certs (uses
/etc/pki/ca-trust/extracted/pem
instead of/etc/pki/tls/certs
) - ✅ Follows OpenShift patterns for custom CA injection
The /etc/pki/ca-trust/extracted/pem
directory is the standard OpenShift location for PEM-formatted CA bundles, and most applications that use system CA trust will automatically pick up certificates from this location.
Alternative: If the application specifically needs certs under /etc/pki/tls/certs
, you could:
- Mount to a custom path like
/etc/pki/ca-trust/source/anchors
or/custom-ca
- Configure the application to read from both system certs and the custom path
- Use the
SSL_CERT_DIR
orSSL_CERT_FILE
environment variables to point to the merged bundle
Does /etc/pki/ca-trust/extracted/pem
work for the external-secrets-operator components, or do they require a specific cert path?
// ConfigMap exists, ensure it has the correct labels | ||
// Do not update the data of the ConfigMap since it is managed by CNO | ||
if existingConfigMap.Labels == nil { | ||
existingConfigMap.Labels = make(map[string]string) | ||
} | ||
|
||
expectedLabels = getTrustedCABundleLabels(resourceLabels) | ||
needsUpdate := false | ||
for k, expectedValue := range expectedLabels { | ||
if existingConfigMap.Labels[k] != expectedValue { | ||
existingConfigMap.Labels[k] = expectedValue | ||
needsUpdate = true | ||
} | ||
} | ||
|
||
// Update the ConfigMap if any labels changed | ||
if needsUpdate { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// ConfigMap exists, ensure it has the correct labels | |
// Do not update the data of the ConfigMap since it is managed by CNO | |
if existingConfigMap.Labels == nil { | |
existingConfigMap.Labels = make(map[string]string) | |
} | |
expectedLabels = getTrustedCABundleLabels(resourceLabels) | |
needsUpdate := false | |
for k, expectedValue := range expectedLabels { | |
if existingConfigMap.Labels[k] != expectedValue { | |
existingConfigMap.Labels[k] = expectedValue | |
needsUpdate = true | |
} | |
} | |
// Update the ConfigMap if any labels changed | |
if needsUpdate { | |
// ConfigMap exists, ensure it has the correct labels | |
// Do not update the data of the ConfigMap since it is managed by CNO | |
if exist && common.ObjectMetadataModified(configMap, existingConfigMap) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func ObjectMetadataModified(desired, fetched client.Object) bool {
return !reflect.DeepEqual(desired.GetLabels(), fetched.GetLabels())
}
I think ObjectMetadataModified
function name is a bit misleading. Metadata holds a lot of other fields as well: annotations
, resourceVersion
, uid
etc. which are prone to change.
And CNO is also adding it's own annotation openshift.io/owning-component: Networking / cluster-network-operator
If we can rename this function to something like ObjecLabelsModified
, then this can be used here.
|
||
// setProxyEnvVars sets proxy environment variables on a container. | ||
func (r *Reconciler) setProxyEnvVars(container *corev1.Container, proxyConfig *operatorv1alpha1.ProxyConfig) { | ||
if proxyConfig == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should for proxy ENV vars in operator pod set by OLM, when cluster-wide egress proxy is configured.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I could not understand it correctly.
We are doing the necessary checks in getProxyConfiguration
function and feeding the value in proxyConfig
before callingsetProxyEnvVars
.
If proxyConfig
is non-nil value here, then we will set the env vars in the operand pods.
In case we need any modification to this logic, let me know.
Signed-off-by: chiragkyal <[email protected]>
Signed-off-by: chiragkyal <[email protected]>
Signed-off-by: chiragkyal <[email protected]>
Signed-off-by: chiragkyal <[email protected]>
because initially we are creating blank configmap Signed-off-by: chiragkyal <[email protected]>
Signed-off-by: chiragkyal <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (5)
config/manager/trusted-ca-patch.yaml (3)
5-5
: Remove hardcoded namespace to support kustomize overlays.The hardcoded
namespace: system
will cause the patch to miss the target Deployment when overlays change the namespace. Remove this line to make the patch namespace-agnostic.Apply this diff:
metadata: name: controller-manager - namespace: system
12-14
: Mounting entire directory masks system CA certificates.Mounting the entire
/etc/pki/tls/certs
directory will hide system CA certificates, breaking TLS trust for connections that rely on system CAs. UsesubPath
to mount only theca-bundle.crt
file.Apply this diff:
volumeMounts: - name: trusted-ca-bundle - mountPath: /etc/pki/tls/certs + mountPath: /etc/pki/tls/certs/ca-bundle.crt + subPath: ca-bundle.crt readOnly: true
18-18
: ConfigMap name mismatch.The volume references ConfigMap
"trusted-ca-bundle"
, but the actual ConfigMap created is named"external-secrets-operator-trusted-ca-bundle"
(as seen in the manifest and constants). This will cause the volume to mount an empty/non-existent ConfigMap at runtime.Apply this diff:
configMap: - name: trusted-ca-bundle + name: external-secrets-operator-trusted-ca-bundlepkg/controller/external_secrets/utils.go (1)
127-155
: Guard against nil ApplicationConfig to prevent panic.Line 133 dereferences
esc.Spec.ApplicationConfig
without checking for nil, which will panic for CRs that omitApplicationConfig
and rely solely on ESM or OLM defaults.Apply this diff:
func (r *Reconciler) getProxyConfiguration(esc *operatorv1alpha1.ExternalSecretsConfig) *operatorv1alpha1.ProxyConfig { var proxyConfig *operatorv1alpha1.ProxyConfig // Check ExternalSecretsConfig first - if esc.Spec.ApplicationConfig.Proxy != nil { // TODO: check if esc.Spec.ApplicationConfig != nil is required + if esc.Spec.ApplicationConfig != nil && esc.Spec.ApplicationConfig.Proxy != nil { proxyConfig = esc.Spec.ApplicationConfig.Proxy } else if r.esm.Spec.GlobalConfig != nil && r.esm.Spec.GlobalConfig.Proxy != nil {pkg/controller/external_secrets/deployments.go (1)
413-460
: Proxy env vars still linger and miss init containersWe still can’t turn the proxy off once it was set: when
proxyConfig
becomes nil (or a field goes empty), the old environment variables stay on the pod. We also never touch initContainers and only export upper-case keys, so several common images keep using stale settings. Please fold in cleanup + init container coverage and set both casings.func (r *Reconciler) updateProxyEnvironmentVariables(deployment *appsv1.Deployment, esc *operatorv1alpha1.ExternalSecretsConfig) error { - proxyConfig := r.getProxyConfiguration(esc) - if proxyConfig == nil { - return nil - } - - // Apply proxy environment variables to all containers - for i := range deployment.Spec.Template.Spec.Containers { - container := &deployment.Spec.Template.Spec.Containers[i] - r.setProxyEnvVars(container, proxyConfig) - } + proxyConfig := r.getProxyConfiguration(esc) + + process := func(envSetter func(*corev1.Container)) { + for i := range deployment.Spec.Template.Spec.Containers { + envSetter(&deployment.Spec.Template.Spec.Containers[i]) + } + for i := range deployment.Spec.Template.Spec.InitContainers { + envSetter(&deployment.Spec.Template.Spec.InitContainers[i]) + } + } + if proxyConfig == nil { + process(r.removeProxyEnvVars) + return nil + } + + process(func(c *corev1.Container) { r.setProxyEnvVars(c, proxyConfig) }) return nil } func (r *Reconciler) setProxyEnvVars(container *corev1.Container, proxyConfig *operatorv1alpha1.ProxyConfig) { @@ if container.Env == nil { container.Env = []corev1.EnvVar{} } + removeEnvVar := func(name string) { + for i := 0; i < len(container.Env); i++ { + if container.Env[i].Name == name { + container.Env = append(container.Env[:i], container.Env[i+1:]...) + i-- + } + } + } setEnvVar := func(name, value string) { if value == "" { - return + removeEnvVar(name) + return } @@ // Set proxy environment variables setEnvVar("HTTP_PROXY", proxyConfig.HTTPProxy) setEnvVar("HTTPS_PROXY", proxyConfig.HTTPSProxy) setEnvVar("NO_PROXY", proxyConfig.NoProxy) + setEnvVar("http_proxy", proxyConfig.HTTPProxy) + setEnvVar("https_proxy", proxyConfig.HTTPSProxy) + setEnvVar("no_proxy", proxyConfig.NoProxy) } + +// removeProxyEnvVars deletes all proxy-related env vars from a container. +func (r *Reconciler) removeProxyEnvVars(container *corev1.Container) { + if len(container.Env) == 0 { + return + } + toDrop := map[string]struct{}{ + "HTTP_PROXY": {}, "HTTPS_PROXY": {}, "NO_PROXY": {}, + "http_proxy": {}, "https_proxy": {}, "no_proxy": {}, + } + dst := container.Env[:0] + for _, env := range container.Env { + if _, drop := toDrop[env.Name]; drop { + continue + } + dst = append(dst, env) + } + container.Env = dst +}Please refresh the accompanying tests as well.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base
setting
📒 Files selected for processing (13)
bundle/manifests/external-secrets-operator-trusted-ca-bundle_v1_configmap.yaml
(1 hunks)bundle/manifests/external-secrets-operator.clusterserviceversion.yaml
(3 hunks)config/default/manager_metrics_patch.yaml
(0 hunks)config/manager/kustomization.yaml
(1 hunks)config/manager/trusted-ca-patch.yaml
(1 hunks)config/manifests/bases/external-secrets-operator.clusterserviceversion.yaml
(1 hunks)pkg/controller/external_secrets/configmap.go
(1 hunks)pkg/controller/external_secrets/constants.go
(1 hunks)pkg/controller/external_secrets/controller.go
(4 hunks)pkg/controller/external_secrets/deployments.go
(2 hunks)pkg/controller/external_secrets/deployments_test.go
(2 hunks)pkg/controller/external_secrets/install_external_secrets.go
(1 hunks)pkg/controller/external_secrets/utils.go
(2 hunks)
💤 Files with no reviewable changes (1)
- config/default/manager_metrics_patch.yaml
🚧 Files skipped from review as they are similar to previous changes (3)
- pkg/controller/external_secrets/configmap.go
- config/manager/kustomization.yaml
- bundle/manifests/external-secrets-operator.clusterserviceversion.yaml
🧰 Additional context used
🪛 Checkov (3.2.334)
config/manager/trusted-ca-patch.yaml
[medium] 1-18: Containers should not run with allowPrivilegeEscalation
(CKV_K8S_20)
[medium] 1-18: Minimize the admission of root containers
(CKV_K8S_23)
🔇 Additional comments (11)
config/manifests/bases/external-secrets-operator.clusterserviceversion.yaml (1)
16-16
: LGTM! Proxy-aware mode enabled.The annotation change correctly enables proxy-aware behavior for the operator, aligning with the PR objectives.
pkg/controller/external_secrets/constants.go (1)
52-65
: LGTM! Constants are well-defined.The new trusted CA bundle constants follow naming conventions and use appropriate values for OpenShift CA injection.
bundle/manifests/external-secrets-operator-trusted-ca-bundle_v1_configmap.yaml (1)
1-9
: LGTM! ConfigMap correctly configured for CA injection.The ConfigMap manifest is properly structured with the
config.openshift.io/inject-trusted-cabundle: "true"
label that triggers OpenShift's CNO to inject cluster-wide CA certificates.pkg/controller/external_secrets/install_external_secrets.go (1)
85-88
: LGTM! CA bundle ConfigMap ensured before deployments.The call to
ensureTrustedCABundleConfigMap
is correctly placed beforecreateOrApplyDeployments
, ensuring the CA bundle ConfigMap exists when deployments need to mount it. Error handling is consistent with other reconciliation steps.pkg/controller/external_secrets/deployments_test.go (2)
424-752
: Test coverage looks comprehensive.The test function covers important scenarios including proxy precedence, partial configurations, updates to existing environment variables, and no-change cases. The test structure is clear and maintainable.
754-837
: Helper functions are well-designed.The validation helper functions (
validateEnvironmentVariables
,validateVolumes
,validateVolumeMounts
,findContainer
,filterTrustedCAMounts
) provide clear separation of concerns and make the tests more readable.pkg/controller/external_secrets/controller.go (5)
77-77
: LGTM! ConfigMap added to managed resources.Adding
&corev1.ConfigMap{}
tocontrollerManagedResources
enables the controller to watch ConfigMap resources, which is necessary for trusted CA bundle management.
255-262
: LGTM! Simplified mapFunc aligns with predicate-based filtering.The simplified
mapFunc
that always enqueues reconciliation is correct because the predicate now handles all filtering logic. Objects that reachmapFunc
have already been filtered by the appropriate predicates.
270-280
: LGTM! ConfigMap predicate correctly handles both managed and CA bundle ConfigMaps.The
configMapPredicate
properly filters ConfigMaps to include:
- Managed resources (with the standard label)
- The trusted CA bundle ConfigMap (by name)
This allows the controller to reconcile when the trusted CA bundle ConfigMap is created or updated, even if it doesn't have the managed resource label.
296-297
: LGTM! ConfigMap watch uses the correct predicate.The ConfigMap watch correctly uses
configMapResourcePredicate
instead ofmanagedResourcePredicate
, enabling reconciliation for both managed ConfigMaps and the trusted CA bundle ConfigMap.
467-467
: TODO: Consider implementing CA bundle ConfigMap cleanup.The TODO comment correctly identifies that cleanup logic for the trusted CA bundle ConfigMap should be added to the
cleanUp
function. When the ExternalSecretsConfig is deleted, the associated CA bundle ConfigMap should also be removed if no longer needed.Consider implementing the cleanup logic to remove the trusted CA bundle ConfigMap when appropriate, or open a follow-up issue to track this work.
proxyConfig := r.getProxyConfiguration(esc) | ||
|
||
// Only add trusted CA bundle if proxy is configured | ||
if proxyConfig == nil { | ||
return nil | ||
} | ||
|
||
// Add the trusted CA bundle volume to the pod spec | ||
trustedCAVolume := corev1.Volume{ | ||
Name: trustedCABundleVolumeName, | ||
VolumeSource: corev1.VolumeSource{ | ||
ConfigMap: &corev1.ConfigMapVolumeSource{ | ||
LocalObjectReference: corev1.LocalObjectReference{ | ||
Name: trustedCABundleConfigMapName, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
// Check if the volume already exists, if not add it | ||
volumeExists := false | ||
for i, volume := range deployment.Spec.Template.Spec.Volumes { | ||
if volume.Name == trustedCABundleVolumeName { | ||
deployment.Spec.Template.Spec.Volumes[i] = trustedCAVolume | ||
volumeExists = true | ||
break | ||
} | ||
} | ||
if !volumeExists { | ||
deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes, trustedCAVolume) | ||
} | ||
|
||
// Add volume mounts to all containers | ||
trustedCAVolumeMount := corev1.VolumeMount{ | ||
Name: trustedCABundleVolumeName, | ||
MountPath: trustedCABundleMountPath, | ||
ReadOnly: true, | ||
} | ||
|
||
for i := range deployment.Spec.Template.Spec.Containers { | ||
container := &deployment.Spec.Template.Spec.Containers[i] | ||
|
||
// Check if the volume mount already exists, if not add it | ||
volumeMountExists := false | ||
for j, volumeMount := range container.VolumeMounts { | ||
if volumeMount.Name == trustedCABundleVolumeName { | ||
container.VolumeMounts[j] = trustedCAVolumeMount | ||
volumeMountExists = true | ||
break | ||
} | ||
} | ||
if !volumeMountExists { | ||
container.VolumeMounts = append(container.VolumeMounts, trustedCAVolumeMount) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trusted CA volume needs symmetry and init-container coverage
Same problem on the CA bundle path: once added, the volume + mount never come off, and initContainers never see the mount. That keeps pods tied to a ConfigMap even after disabling the proxy, and starves any init container of the required CA. Let’s cleanly add/remove for every container type.
func (r *Reconciler) updateTrustedCABundleVolumes(deployment *appsv1.Deployment, esc *operatorv1alpha1.ExternalSecretsConfig) error {
proxyConfig := r.getProxyConfiguration(esc)
- // Only add trusted CA bundle if proxy is configured
- if proxyConfig == nil {
- return nil
- }
+ removeVolumeMount := func(container *corev1.Container) {
+ for i := 0; i < len(container.VolumeMounts); i++ {
+ if container.VolumeMounts[i].Name == trustedCABundleVolumeName {
+ container.VolumeMounts = append(container.VolumeMounts[:i], container.VolumeMounts[i+1:]...)
+ i--
+ }
+ }
+ }
+ if proxyConfig == nil {
+ for i := range deployment.Spec.Template.Spec.Containers {
+ removeVolumeMount(&deployment.Spec.Template.Spec.Containers[i])
+ }
+ for i := range deployment.Spec.Template.Spec.InitContainers {
+ removeVolumeMount(&deployment.Spec.Template.Spec.InitContainers[i])
+ }
+ for i := 0; i < len(deployment.Spec.Template.Spec.Volumes); i++ {
+ if deployment.Spec.Template.Spec.Volumes[i].Name == trustedCABundleVolumeName {
+ deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes[:i], deployment.Spec.Template.Spec.Volumes[i+1:]...)
+ i--
+ }
+ }
+ return nil
+ }
@@
- // Add volume mounts to all containers
- trustedCAVolumeMount := corev1.VolumeMount{
+ trustedCAVolumeMount := corev1.VolumeMount{
Name: trustedCABundleVolumeName,
MountPath: trustedCABundleMountPath,
ReadOnly: true,
}
- for i := range deployment.Spec.Template.Spec.Containers {
- container := &deployment.Spec.Template.Spec.Containers[i]
-
- // Check if the volume mount already exists, if not add it
- volumeMountExists := false
- for j, volumeMount := range container.VolumeMounts {
- if volumeMount.Name == trustedCABundleVolumeName {
- container.VolumeMounts[j] = trustedCAVolumeMount
- volumeMountExists = true
- break
- }
- }
- if !volumeMountExists {
- container.VolumeMounts = append(container.VolumeMounts, trustedCAVolumeMount)
- }
- }
+ addMount := func(container *corev1.Container) {
+ for j, volumeMount := range container.VolumeMounts {
+ if volumeMount.Name == trustedCABundleVolumeName {
+ container.VolumeMounts[j] = trustedCAVolumeMount
+ return
+ }
+ }
+ container.VolumeMounts = append(container.VolumeMounts, trustedCAVolumeMount)
+ }
+ for i := range deployment.Spec.Template.Spec.Containers {
+ addMount(&deployment.Spec.Template.Spec.Containers[i])
+ }
+ for i := range deployment.Spec.Template.Spec.InitContainers {
+ addMount(&deployment.Spec.Template.Spec.InitContainers[i])
+ }
return nil
}
Remember to mirror the updated behaviour in tests.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
proxyConfig := r.getProxyConfiguration(esc) | |
// Only add trusted CA bundle if proxy is configured | |
if proxyConfig == nil { | |
return nil | |
} | |
// Add the trusted CA bundle volume to the pod spec | |
trustedCAVolume := corev1.Volume{ | |
Name: trustedCABundleVolumeName, | |
VolumeSource: corev1.VolumeSource{ | |
ConfigMap: &corev1.ConfigMapVolumeSource{ | |
LocalObjectReference: corev1.LocalObjectReference{ | |
Name: trustedCABundleConfigMapName, | |
}, | |
}, | |
}, | |
} | |
// Check if the volume already exists, if not add it | |
volumeExists := false | |
for i, volume := range deployment.Spec.Template.Spec.Volumes { | |
if volume.Name == trustedCABundleVolumeName { | |
deployment.Spec.Template.Spec.Volumes[i] = trustedCAVolume | |
volumeExists = true | |
break | |
} | |
} | |
if !volumeExists { | |
deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes, trustedCAVolume) | |
} | |
// Add volume mounts to all containers | |
trustedCAVolumeMount := corev1.VolumeMount{ | |
Name: trustedCABundleVolumeName, | |
MountPath: trustedCABundleMountPath, | |
ReadOnly: true, | |
} | |
for i := range deployment.Spec.Template.Spec.Containers { | |
container := &deployment.Spec.Template.Spec.Containers[i] | |
// Check if the volume mount already exists, if not add it | |
volumeMountExists := false | |
for j, volumeMount := range container.VolumeMounts { | |
if volumeMount.Name == trustedCABundleVolumeName { | |
container.VolumeMounts[j] = trustedCAVolumeMount | |
volumeMountExists = true | |
break | |
} | |
} | |
if !volumeMountExists { | |
container.VolumeMounts = append(container.VolumeMounts, trustedCAVolumeMount) | |
} | |
} | |
proxyConfig := r.getProxyConfiguration(esc) | |
removeVolumeMount := func(container *corev1.Container) { | |
for i := 0; i < len(container.VolumeMounts); i++ { | |
if container.VolumeMounts[i].Name == trustedCABundleVolumeName { | |
container.VolumeMounts = append(container.VolumeMounts[:i], container.VolumeMounts[i+1:]...) | |
i-- | |
} | |
} | |
} | |
if proxyConfig == nil { | |
for i := range deployment.Spec.Template.Spec.Containers { | |
removeVolumeMount(&deployment.Spec.Template.Spec.Containers[i]) | |
} | |
for i := range deployment.Spec.Template.Spec.InitContainers { | |
removeVolumeMount(&deployment.Spec.Template.Spec.InitContainers[i]) | |
} | |
for i := 0; i < len(deployment.Spec.Template.Spec.Volumes); i++ { | |
if deployment.Spec.Template.Spec.Volumes[i].Name == trustedCABundleVolumeName { | |
deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes[:i], deployment.Spec.Template.Spec.Volumes[i+1:]...) | |
i-- | |
} | |
} | |
return nil | |
} | |
// Add the trusted CA bundle volume to the pod spec | |
trustedCAVolume := corev1.Volume{ | |
Name: trustedCABundleVolumeName, | |
VolumeSource: corev1.VolumeSource{ | |
ConfigMap: &corev1.ConfigMapVolumeSource{ | |
LocalObjectReference: corev1.LocalObjectReference{ | |
Name: trustedCABundleConfigMapName, | |
}, | |
}, | |
}, | |
} | |
// Check if the volume already exists, if not add it | |
volumeExists := false | |
for i, volume := range deployment.Spec.Template.Spec.Volumes { | |
if volume.Name == trustedCABundleVolumeName { | |
deployment.Spec.Template.Spec.Volumes[i] = trustedCAVolume | |
volumeExists = true | |
break | |
} | |
} | |
if !volumeExists { | |
deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes, trustedCAVolume) | |
} | |
trustedCAVolumeMount := corev1.VolumeMount{ | |
Name: trustedCABundleVolumeName, | |
MountPath: trustedCABundleMountPath, | |
ReadOnly: true, | |
} | |
addMount := func(container *corev1.Container) { | |
for j, volumeMount := range container.VolumeMounts { | |
if volumeMount.Name == trustedCABundleVolumeName { | |
container.VolumeMounts[j] = trustedCAVolumeMount | |
return | |
} | |
} | |
container.VolumeMounts = append(container.VolumeMounts, trustedCAVolumeMount) | |
} | |
for i := range deployment.Spec.Template.Spec.Containers { | |
addMount(&deployment.Spec.Template.Spec.Containers[i]) | |
} | |
for i := range deployment.Spec.Template.Spec.InitContainers { | |
addMount(&deployment.Spec.Template.Spec.InitContainers[i]) | |
} | |
return nil |
Signed-off-by: chiragkyal <[email protected]>
PR needs rebase. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (3)
pkg/controller/external_secrets/deployments_test.go (1)
431-431
: Verify ConfigMap name matches the manifest.Based on a previous review comment, the ConfigMap name should be
"external-secrets-operator-trusted-ca-bundle"
to match the OLM manifest atbundle/manifests/external-secrets-operator-trusted-ca-bundle_v1_configmap.yaml
. The current value"external-secrets-trusted-ca-bundle"
may cause runtime failures.Run the following script to verify the ConfigMap name across the codebase:
#!/bin/bash # Search for all references to the trusted CA bundle ConfigMap name rg -n 'external-secrets.*trusted-ca-bundle' --type yaml --type gopkg/controller/external_secrets/deployments.go (2)
412-432
: Missing cleanup when proxy configuration is disabled.When
proxyConfig
isnil
, the function returns early without removing previously set proxy environment variables. This means once proxy is configured, it cannot be disabled by removing the configuration. The same issue exists inupdateTrustedCABundleVolumes
for CA bundle volumes/mounts.To support proxy removal, the function should clean up when
proxyConfig
is nil:func (r *Reconciler) updateProxyEnvironmentVariables(deployment *appsv1.Deployment, esc *operatorv1alpha1.ExternalSecretsConfig) error { proxyConfig := r.getProxyConfiguration(esc) - if proxyConfig == nil { - return nil - } - // Apply proxy environment variables to all containers - for i := range deployment.Spec.Template.Spec.Containers { - container := &deployment.Spec.Template.Spec.Containers[i] - r.setProxyEnvVars(container, proxyConfig) - } - - // Apply proxy environment variables to all init containers - for i := range deployment.Spec.Template.Spec.InitContainers { - initContainer := &deployment.Spec.Template.Spec.InitContainers[i] - r.setProxyEnvVars(initContainer, proxyConfig) - } + process := func(c *corev1.Container) { + if proxyConfig == nil { + r.removeProxyEnvVars(c) + } else { + r.setProxyEnvVars(c, proxyConfig) + } + } + + for i := range deployment.Spec.Template.Spec.Containers { + process(&deployment.Spec.Template.Spec.Containers[i]) + } + for i := range deployment.Spec.Template.Spec.InitContainers { + process(&deployment.Spec.Template.Spec.InitContainers[i]) + } return nil }Add the removal helper:
// removeProxyEnvVars removes all proxy-related environment variables from a container. func (r *Reconciler) removeProxyEnvVars(container *corev1.Container) { if len(container.Env) == 0 { return } proxyEnvVars := map[string]struct{}{ "HTTP_PROXY": {}, "HTTPS_PROXY": {}, "NO_PROXY": {}, "http_proxy": {}, "https_proxy": {}, "no_proxy": {}, } filtered := container.Env[:0] for _, e := range container.Env { if _, isProxy := proxyEnvVars[e.Name]; !isProxy { filtered = append(filtered, e) } } container.Env = filtered }
473-528
: Missing cleanup of CA bundle volumes when proxy is disabled.Similar to the environment variables issue, when
proxyConfig
isnil
, the function returns early without removing the trusted CA bundle volume and its mounts. This leaves stale volumes mounted even after proxy configuration is removed.Apply symmetric cleanup logic:
func (r *Reconciler) updateTrustedCABundleVolumes(deployment *appsv1.Deployment, esc *operatorv1alpha1.ExternalSecretsConfig) error { proxyConfig := r.getProxyConfiguration(esc) - // Only add trusted CA bundle if proxy is configured - if proxyConfig == nil { - return nil - } + removeVolumeMount := func(container *corev1.Container) { + filtered := container.VolumeMounts[:0] + for _, vm := range container.VolumeMounts { + if vm.Name != trustedCABundleVolumeName { + filtered = append(filtered, vm) + } + } + container.VolumeMounts = filtered + } + + if proxyConfig == nil { + // Remove volume mounts from all containers and init containers + for i := range deployment.Spec.Template.Spec.Containers { + removeVolumeMount(&deployment.Spec.Template.Spec.Containers[i]) + } + for i := range deployment.Spec.Template.Spec.InitContainers { + removeVolumeMount(&deployment.Spec.Template.Spec.InitContainers[i]) + } + // Remove the volume itself + filtered := deployment.Spec.Template.Spec.Volumes[:0] + for _, v := range deployment.Spec.Template.Spec.Volumes { + if v.Name != trustedCABundleVolumeName { + filtered = append(filtered, v) + } + } + deployment.Spec.Template.Spec.Volumes = filtered + return nil + } // Add the trusted CA bundle volume to the pod spec trustedCAVolume := corev1.Volume{
🧹 Nitpick comments (1)
pkg/controller/external_secrets/deployments_test.go (1)
424-830
: Add test case for proxy removal scenario.The test suite comprehensively covers proxy configuration being added and updated, but lacks a test case for when proxy configuration is removed (i.e., when
proxyConfig
transitions from being set tonil
). This scenario should verify that proxy environment variables and CA bundle volumes/mounts are cleaned up.Add a test case similar to:
{ name: "Proxy removal cleans up environment variables and volumes", deployment: &appsv1.Deployment{ Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "external-secrets", Env: []corev1.EnvVar{ {Name: "HTTP_PROXY", Value: "http://old-proxy:8080"}, {Name: "EXISTING_VAR", Value: "existing-value"}, }, }, }, Volumes: []corev1.Volume{ { Name: "trusted-ca-bundle", VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ LocalObjectReference: corev1.LocalObjectReference{ Name: "external-secrets-trusted-ca-bundle", }, }, }, }, }, }, }, }, }, externalSecretsConfig: &v1alpha1.ExternalSecretsConfig{}, externalSecretsManager: &v1alpha1.ExternalSecretsManager{}, olmEnvVars: map[string]string{}, expectedContainerEnvVars: map[string][]corev1.EnvVar{ "external-secrets": { {Name: "EXISTING_VAR", Value: "existing-value"}, }, }, expectedVolumes: []corev1.Volume{}, expectedVolumeMounts: map[string][]corev1.VolumeMount{}, },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base
setting
📒 Files selected for processing (5)
pkg/controller/external_secrets/configmap.go
(1 hunks)pkg/controller/external_secrets/constants.go
(1 hunks)pkg/controller/external_secrets/deployments.go
(3 hunks)pkg/controller/external_secrets/deployments_test.go
(2 hunks)pkg/controller/external_secrets/utils.go
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- pkg/controller/external_secrets/constants.go
- pkg/controller/external_secrets/configmap.go
🔇 Additional comments (5)
pkg/controller/external_secrets/utils.go (1)
127-155
: LGTM! Proxy configuration precedence is correctly implemented.The three-tier precedence (ExternalSecretsConfig → ExternalSecretsManager → OLM environment variables) is clearly implemented and aligns with the design. The nil checks for nested fields are appropriate given that
ApplicationConfig
is a struct andr.esm
is always initialized.pkg/controller/external_secrets/deployments_test.go (1)
832-922
: LGTM! Well-structured test helpers.The validation helper functions are well-organized and provide clear, detailed error messages for test failures. The separation of concerns (environment variables, volumes, volume mounts) makes the tests easy to understand and maintain.
pkg/controller/external_secrets/deployments.go (3)
400-410
: LGTM! Clean orchestration of proxy configuration updates.The function correctly delegates to specialized handlers for environment variables and CA bundle volumes, with proper error handling and context.
434-471
: LGTM! Comprehensive proxy environment variable handling.The function correctly handles both uppercase and lowercase variants of proxy environment variables, which improves compatibility across different tools and libraries. The update-or-append logic avoids duplicates.
530-544
: LGTM! Clean helper for adding volume mounts.The function correctly checks for existing mounts and either updates or appends, avoiding duplicates.
@chiragkyal: The following tests failed, say
Full PR test history. Your PR dashboard. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
No description provided.