Skip to content

Commit 8c5fe48

Browse files
committed
Address review comments
Signed-off-by: chiragkyal <ckyal@redhat.com>
1 parent 6b694a6 commit 8c5fe48

13 files changed

Lines changed: 117 additions & 115 deletions

pkg/controller/external_secrets/certificate_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ func TestCreateOrApplyCertificates(t *testing.T) {
268268
esc.Spec.ControllerConfig.CertProvider.CertManager.IssuerRef.Name = testIssuerName
269269
},
270270
recon: false,
271-
wantErr: fmt.Sprintf("failed to create %s/%s: %s", commontest.TestExternalSecretsNamespace, testValidateCertificateResourceName, commontest.ErrTestClient),
271+
wantErr: fmt.Sprintf("failed to create Certificate %s/%s: %s", commontest.TestExternalSecretsNamespace, testValidateCertificateResourceName, commontest.ErrTestClient),
272272
},
273273
{
274274
name: "successful webhook certificate creation",

pkg/controller/external_secrets/configmap.go

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
package external_secrets
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"maps"
67

78
corev1 "k8s.io/api/core/v1"
89
apierrors "k8s.io/apimachinery/pkg/api/errors"
910
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/types"
1012
"sigs.k8s.io/controller-runtime/pkg/client"
1113

1214
operatorv1alpha1 "github.com/openshift/external-secrets-operator/api/v1alpha1"
15+
operatorclient "github.com/openshift/external-secrets-operator/pkg/controller/client"
1316
"github.com/openshift/external-secrets-operator/pkg/controller/common"
1417
)
1518

@@ -59,24 +62,17 @@ func (r *Reconciler) ensureTrustedCABundleConfigMap(esc *operatorv1alpha1.Extern
5962

6063
if !exist {
6164
// NOTE: This ConfigMap cannot use the generic createWithFallback helper because
62-
// the Data field is managed by CNO (not by this operator). A plain UpdateWithRetry
63-
// with our empty-Data desired object would wipe the CA certificates CNO injected.
64-
// Instead, on AlreadyExists we must fetch the real object first, preserve its Data,
65-
// then update only labels/annotations.
65+
// the Data/BinaryData fields are managed by CNO (not by this operator). On
66+
// AlreadyExists we use a MergePatch that touches only metadata, leaving
67+
// CNO-injected CA certificates untouched.
6668
if err := r.Create(r.ctx, desiredConfigMap); err != nil {
6769
if !apierrors.IsAlreadyExists(err) {
6870
return common.FromClientError(err, "failed to create %s trusted CA bundle ConfigMap resource", configMapName)
6971
}
70-
r.log.V(1).Info("trusted CA bundle ConfigMap exists on API server but absent from label-filtered cache, restoring desired state", "name", configMapName)
71-
actualConfigMap := &corev1.ConfigMap{}
72-
if _, fetchErr := r.UncachedClient.Exists(r.ctx, client.ObjectKeyFromObject(desiredConfigMap), actualConfigMap); fetchErr != nil {
73-
return common.FromClientError(fetchErr, "failed to fetch existing %s trusted CA bundle ConfigMap for restoration", configMapName)
74-
}
75-
desiredConfigMap.Data = actualConfigMap.Data
76-
desiredConfigMap.BinaryData = actualConfigMap.BinaryData
72+
r.log.V(1).Info("trusted CA bundle ConfigMap exists on API server but absent from label-filtered cache, patching metadata", "name", configMapName)
7773
common.RemoveObsoleteAnnotations(desiredConfigMap, resourceMetadata)
78-
if updateErr := r.UncachedClient.UpdateWithRetry(r.ctx, desiredConfigMap); updateErr != nil {
79-
return common.FromClientError(updateErr, "failed to restore %s trusted CA bundle ConfigMap to desired state", configMapName)
74+
if patchErr := r.patchConfigMapMetadata(desiredConfigMap, r.UncachedClient); patchErr != nil {
75+
return common.FromClientError(patchErr, "failed to patch %s trusted CA bundle ConfigMap metadata", configMapName)
8076
}
8177
r.eventRecorder.Eventf(esc, corev1.EventTypeNormal, "Reconciled", "trusted CA bundle ConfigMap resource %s restored to desired state", configMapName)
8278
return nil
@@ -85,17 +81,13 @@ func (r *Reconciler) ensureTrustedCABundleConfigMap(esc *operatorv1alpha1.Extern
8581
return nil
8682
}
8783

88-
// ConfigMap exists, ensure it has the correct labels and annotations.
89-
// Do not update the data of the ConfigMap since it is managed by CNO.
90-
// MergeFetchedAnnotations preserves external annotations (e.g., CNO's openshift.io/owning-component).
84+
// ConfigMap exists in cache — ensure it has the correct labels and annotations.
85+
// Use a metadata-only patch so CNO-managed Data/BinaryData are never touched.
9186
if exist && common.ObjectMetadataModified(desiredConfigMap, existingConfigMap, &resourceMetadata) {
92-
r.log.V(1).Info("trusted CA bundle ConfigMap has been modified, updating to desired state", "name", configMapName)
93-
// Preserve data from existing ConfigMap (managed by CNO)
94-
desiredConfigMap.Data = existingConfigMap.Data
95-
desiredConfigMap.BinaryData = existingConfigMap.BinaryData
87+
r.log.V(1).Info("trusted CA bundle ConfigMap has been modified, patching metadata to desired state", "name", configMapName)
9688
common.RemoveObsoleteAnnotations(desiredConfigMap, resourceMetadata)
97-
if err := r.UpdateWithRetry(r.ctx, desiredConfigMap); err != nil {
98-
return common.FromClientError(err, "failed to update %s trusted CA bundle ConfigMap resource", configMapName)
89+
if err := r.patchConfigMapMetadata(desiredConfigMap, r.CtrlClient); err != nil {
90+
return common.FromClientError(err, "failed to patch %s trusted CA bundle ConfigMap metadata", configMapName)
9991
}
10092
r.eventRecorder.Eventf(esc, corev1.EventTypeNormal, "Reconciled", "trusted CA bundle ConfigMap resource %s reconciled back to desired state", configMapName)
10193
} else {
@@ -105,6 +97,25 @@ func (r *Reconciler) ensureTrustedCABundleConfigMap(esc *operatorv1alpha1.Extern
10597
return nil
10698
}
10799

100+
// patchConfigMapMetadata sends a MergePatch that sets only labels and annotations
101+
// on the ConfigMap, leaving all other fields (especially Data/BinaryData managed
102+
// by CNO) untouched. The caller chooses the client: the cached client when the
103+
// object is visible in the informer cache, or the uncached client when it has
104+
// fallen out of the label-filtered cache.
105+
func (r *Reconciler) patchConfigMapMetadata(desired *corev1.ConfigMap, patchClient operatorclient.CtrlClient) error {
106+
patch := map[string]interface{}{
107+
"metadata": map[string]interface{}{
108+
"labels": desired.Labels,
109+
"annotations": desired.Annotations,
110+
},
111+
}
112+
patchBytes, err := json.Marshal(patch)
113+
if err != nil {
114+
return fmt.Errorf("failed to marshal metadata patch: %w", err)
115+
}
116+
return patchClient.Patch(r.ctx, desired, client.RawPatch(types.MergePatchType, patchBytes))
117+
}
118+
108119
// getTrustedCABundleLabels merges resource labels with the injection label.
109120
func getTrustedCABundleLabels(resourceLabels map[string]string) map[string]string {
110121
labels := make(map[string]string)

pkg/controller/external_secrets/configmap_test.go

Lines changed: 27 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func TestEnsureTrustedCABundleConfigMap(t *testing.T) {
3636
preReq func(r *Reconciler, cached *fakes.FakeCtrlClient, uncached *fakes.FakeCtrlClient)
3737
wantErr string
3838
wantCreate bool
39-
wantUpdate bool
39+
wantPatch bool
4040
noProxy bool
4141
}{
4242
{
@@ -79,10 +79,9 @@ func TestEnsureTrustedCABundleConfigMap(t *testing.T) {
7979
return true, nil
8080
})
8181
},
82-
wantUpdate: false,
8382
},
8483
{
85-
name: "ConfigMap exists with wrong labels, update triggered",
84+
name: "ConfigMap exists with wrong labels, patch triggered",
8685
resourceMetadata: testResourceMetadata(commontest.TestExternalSecretsConfig()),
8786
preReq: func(_ *Reconciler, cached *fakes.FakeCtrlClient, _ *fakes.FakeCtrlClient) {
8887
cached.ExistsCalls(func(_ context.Context, _ types.NamespacedName, obj client.Object) (bool, error) {
@@ -97,73 +96,58 @@ func TestEnsureTrustedCABundleConfigMap(t *testing.T) {
9796
existing.DeepCopyInto(obj.(*corev1.ConfigMap))
9897
return true, nil
9998
})
100-
cached.UpdateWithRetryCalls(func(_ context.Context, obj client.Object, _ ...client.UpdateOption) error {
99+
cached.PatchCalls(func(_ context.Context, obj client.Object, patch client.Patch, _ ...client.PatchOption) error {
101100
cm, ok := obj.(*corev1.ConfigMap)
102101
if !ok {
103102
t.Errorf("expected ConfigMap, got %T", obj)
104103
}
105104
if cm.Labels[trustedCABundleInjectLabel] != "true" {
106-
t.Errorf("expected inject label restored, got %q", cm.Labels[trustedCABundleInjectLabel])
105+
t.Errorf("expected inject label in patch target, got %q", cm.Labels[trustedCABundleInjectLabel])
107106
}
108-
if cm.Data["ca-bundle.crt"] != "cert-data" {
109-
t.Errorf("CNO-managed data should be preserved, got %v", cm.Data)
107+
if patch.Type() != types.MergePatchType {
108+
t.Errorf("expected MergePatch, got %s", patch.Type())
110109
}
111110
return nil
112111
})
113112
},
114-
wantUpdate: true,
113+
wantPatch: true,
115114
},
116115
{
117116
// Regression test for: labels updating with app: external-secrets of cm
118117
// external-secrets-trusted-ca-bundle will block the reconcile in the http_proxy env.
119118
//
120119
// When the managed label (app=external-secrets) is removed from the ConfigMap,
121120
// the object falls out of the label-filtered cache. The cached Exists() returns
122-
// false, Create() fails with AlreadyExists, and the controller must use the
123-
// uncached client to fetch and restore the correct labels instead of returning
124-
// an error that blocks reconciliation.
125-
name: "Create returns AlreadyExists (label-filtered cache miss): uncached client restores labels",
121+
// false, Create() fails with AlreadyExists, and the controller must patch only
122+
// the metadata (labels/annotations) via the uncached client, leaving
123+
// CNO-managed Data/BinaryData untouched.
124+
name: "Create returns AlreadyExists (label-filtered cache miss): uncached client patches metadata",
126125
resourceMetadata: testResourceMetadata(commontest.TestExternalSecretsConfig()),
127126
preReq: func(_ *Reconciler, cached *fakes.FakeCtrlClient, uncached *fakes.FakeCtrlClient) {
128-
// Cached client doesn't see the ConfigMap because its label was changed.
129127
cached.ExistsCalls(func(_ context.Context, _ types.NamespacedName, _ client.Object) (bool, error) {
130128
return false, nil
131129
})
132130
cached.CreateCalls(func(_ context.Context, _ client.Object, _ ...client.CreateOption) error {
133131
return apierrors.NewAlreadyExists(schema.GroupResource{Resource: "configmaps"}, trustedCABundleConfigMapName)
134132
})
135-
// Uncached client fetches the real object from the API server.
136-
uncached.ExistsCalls(func(_ context.Context, _ types.NamespacedName, obj client.Object) (bool, error) {
137-
existing := &corev1.ConfigMap{
138-
ObjectMeta: metav1.ObjectMeta{
139-
Name: trustedCABundleConfigMapName,
140-
Namespace: commontest.TestExternalSecretsNamespace,
141-
ResourceVersion: "1000",
142-
Labels: map[string]string{"app": "update-secrets"},
143-
},
144-
Data: cnoData,
145-
}
146-
existing.DeepCopyInto(obj.(*corev1.ConfigMap))
147-
return true, nil
148-
})
149-
uncached.UpdateWithRetryCalls(func(_ context.Context, obj client.Object, _ ...client.UpdateOption) error {
133+
uncached.PatchCalls(func(_ context.Context, obj client.Object, patch client.Patch, _ ...client.PatchOption) error {
150134
cm, ok := obj.(*corev1.ConfigMap)
151135
if !ok {
152136
t.Errorf("expected ConfigMap, got %T", obj)
153137
}
154138
if cm.Labels["app"] != "external-secrets" {
155-
t.Errorf("expected app=external-secrets label restored, got %q", cm.Labels["app"])
139+
t.Errorf("expected app=external-secrets label in patch target, got %q", cm.Labels["app"])
156140
}
157141
if cm.Labels[trustedCABundleInjectLabel] != "true" {
158-
t.Errorf("expected inject label restored, got %q", cm.Labels[trustedCABundleInjectLabel])
142+
t.Errorf("expected inject label in patch target, got %q", cm.Labels[trustedCABundleInjectLabel])
159143
}
160-
if cm.Data["ca-bundle.crt"] != "cert-data" {
161-
t.Errorf("CNO-managed data should be preserved, got %v", cm.Data)
144+
if patch.Type() != types.MergePatchType {
145+
t.Errorf("expected MergePatch, got %s", patch.Type())
162146
}
163147
return nil
164148
})
165149
},
166-
wantUpdate: true,
150+
wantPatch: true,
167151
},
168152
{
169153
name: "proxy not configured: ConfigMap reconcile skipped",
@@ -194,23 +178,7 @@ func TestEnsureTrustedCABundleConfigMap(t *testing.T) {
194178
wantErr: "failed to create external-secrets/external-secrets-trusted-ca-bundle trusted CA bundle ConfigMap resource: test client error",
195179
},
196180
{
197-
name: "uncached Exists fails after AlreadyExists from Create",
198-
resourceMetadata: testResourceMetadata(commontest.TestExternalSecretsConfig()),
199-
preReq: func(_ *Reconciler, cached *fakes.FakeCtrlClient, uncached *fakes.FakeCtrlClient) {
200-
cached.ExistsCalls(func(_ context.Context, _ types.NamespacedName, _ client.Object) (bool, error) {
201-
return false, nil
202-
})
203-
cached.CreateCalls(func(_ context.Context, _ client.Object, _ ...client.CreateOption) error {
204-
return apierrors.NewAlreadyExists(schema.GroupResource{Resource: "configmaps"}, trustedCABundleConfigMapName)
205-
})
206-
uncached.ExistsCalls(func(_ context.Context, _ types.NamespacedName, _ client.Object) (bool, error) {
207-
return false, commontest.ErrTestClient
208-
})
209-
},
210-
wantErr: "failed to fetch existing external-secrets/external-secrets-trusted-ca-bundle trusted CA bundle ConfigMap for restoration: test client error",
211-
},
212-
{
213-
name: "uncached UpdateWithRetry fails after AlreadyExists from Create",
181+
name: "uncached Patch fails after AlreadyExists from Create",
214182
resourceMetadata: testResourceMetadata(commontest.TestExternalSecretsConfig()),
215183
preReq: func(_ *Reconciler, cached *fakes.FakeCtrlClient, uncached *fakes.FakeCtrlClient) {
216184
cached.ExistsCalls(func(_ context.Context, _ types.NamespacedName, _ client.Object) (bool, error) {
@@ -219,26 +187,14 @@ func TestEnsureTrustedCABundleConfigMap(t *testing.T) {
219187
cached.CreateCalls(func(_ context.Context, _ client.Object, _ ...client.CreateOption) error {
220188
return apierrors.NewAlreadyExists(schema.GroupResource{Resource: "configmaps"}, trustedCABundleConfigMapName)
221189
})
222-
uncached.ExistsCalls(func(_ context.Context, _ types.NamespacedName, obj client.Object) (bool, error) {
223-
existing := &corev1.ConfigMap{
224-
ObjectMeta: metav1.ObjectMeta{
225-
Name: trustedCABundleConfigMapName,
226-
Namespace: commontest.TestExternalSecretsNamespace,
227-
ResourceVersion: "1000",
228-
Labels: map[string]string{"app": "update-secrets"},
229-
},
230-
}
231-
existing.DeepCopyInto(obj.(*corev1.ConfigMap))
232-
return true, nil
233-
})
234-
uncached.UpdateWithRetryCalls(func(_ context.Context, _ client.Object, _ ...client.UpdateOption) error {
190+
uncached.PatchCalls(func(_ context.Context, _ client.Object, _ client.Patch, _ ...client.PatchOption) error {
235191
return commontest.ErrTestClient
236192
})
237193
},
238-
wantErr: "failed to restore external-secrets/external-secrets-trusted-ca-bundle trusted CA bundle ConfigMap to desired state: test client error",
194+
wantErr: "failed to patch external-secrets/external-secrets-trusted-ca-bundle trusted CA bundle ConfigMap metadata: test client error",
239195
},
240196
{
241-
name: "cached UpdateWithRetry fails when ConfigMap has wrong labels",
197+
name: "cached Patch fails when ConfigMap has wrong labels",
242198
resourceMetadata: testResourceMetadata(commontest.TestExternalSecretsConfig()),
243199
preReq: func(_ *Reconciler, cached *fakes.FakeCtrlClient, _ *fakes.FakeCtrlClient) {
244200
cached.ExistsCalls(func(_ context.Context, _ types.NamespacedName, obj client.Object) (bool, error) {
@@ -252,11 +208,11 @@ func TestEnsureTrustedCABundleConfigMap(t *testing.T) {
252208
existing.DeepCopyInto(obj.(*corev1.ConfigMap))
253209
return true, nil
254210
})
255-
cached.UpdateWithRetryCalls(func(_ context.Context, _ client.Object, _ ...client.UpdateOption) error {
211+
cached.PatchCalls(func(_ context.Context, _ client.Object, _ client.Patch, _ ...client.PatchOption) error {
256212
return commontest.ErrTestClient
257213
})
258214
},
259-
wantErr: "failed to update external-secrets/external-secrets-trusted-ca-bundle trusted CA bundle ConfigMap resource: test client error",
215+
wantErr: "failed to patch external-secrets/external-secrets-trusted-ca-bundle trusted CA bundle ConfigMap metadata: test client error",
260216
},
261217
}
262218

@@ -293,11 +249,11 @@ func TestEnsureTrustedCABundleConfigMap(t *testing.T) {
293249
if tt.wantCreate && cached.CreateCallCount() == 0 {
294250
t.Error("expected Create to be called, but it wasn't")
295251
}
296-
if tt.wantUpdate && cached.UpdateWithRetryCallCount() == 0 && uncached.UpdateWithRetryCallCount() == 0 {
297-
t.Error("expected UpdateWithRetry to be called (on cached or uncached client), but it wasn't")
252+
if tt.wantPatch && cached.PatchCallCount() == 0 && uncached.PatchCallCount() == 0 {
253+
t.Error("expected Patch to be called (on cached or uncached client), but it wasn't")
298254
}
299-
if !tt.wantUpdate && (cached.UpdateWithRetryCallCount() > 0 || uncached.UpdateWithRetryCallCount() > 0) {
300-
t.Error("expected no UpdateWithRetry call, but one was made")
255+
if !tt.wantPatch && (cached.PatchCallCount() > 0 || uncached.PatchCallCount() > 0) {
256+
t.Error("expected no Patch call, but one was made")
301257
}
302258
})
303259
}

pkg/controller/external_secrets/controller.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,20 +270,40 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
270270
return object.GetLabels() != nil && object.GetLabels()[requestEnqueueLabelKey] == requestEnqueueLabelValue
271271
}
272272

273+
logIgnored := func(obj client.Object) {
274+
r.log.V(4).Info("object not of interest, ignoring reconcile event", "object", fmt.Sprintf("%T", obj), "name", obj.GetName(), "namespace", obj.GetNamespace())
275+
}
276+
273277
// On updates, check both old and new objects so that events where the managed
274278
// label is removed externally still trigger reconciliation and label restoration.
275279
managedResources := predicate.Funcs{
276280
CreateFunc: func(e event.CreateEvent) bool {
277-
return isManagedResource(e.Object)
281+
if !isManagedResource(e.Object) {
282+
logIgnored(e.Object)
283+
return false
284+
}
285+
return true
278286
},
279287
UpdateFunc: func(e event.UpdateEvent) bool {
280-
return isManagedResource(e.ObjectOld) || isManagedResource(e.ObjectNew)
288+
if !isManagedResource(e.ObjectOld) && !isManagedResource(e.ObjectNew) {
289+
logIgnored(e.ObjectNew)
290+
return false
291+
}
292+
return true
281293
},
282294
DeleteFunc: func(e event.DeleteEvent) bool {
283-
return isManagedResource(e.Object)
295+
if !isManagedResource(e.Object) {
296+
logIgnored(e.Object)
297+
return false
298+
}
299+
return true
284300
},
285301
GenericFunc: func(e event.GenericEvent) bool {
286-
return isManagedResource(e.Object)
302+
if !isManagedResource(e.Object) {
303+
logIgnored(e.Object)
304+
return false
305+
}
306+
return true
287307
},
288308
}
289309

pkg/controller/external_secrets/networkpolicy_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func TestCreateOrApplyStaticNetworkPolicies(t *testing.T) {
146146
return nil
147147
})
148148
},
149-
wantErr: "failed to create external-secrets/eso-sys-deny-all-traffic: test client error",
149+
wantErr: "failed to create NetworkPolicy external-secrets/eso-sys-deny-all-traffic: test client error",
150150
},
151151
{
152152
name: "network policy exists check fails",
@@ -344,7 +344,7 @@ func TestCreateOrApplyCustomNetworkPolicies(t *testing.T) {
344344
},
345345
}
346346
},
347-
wantErr: "failed to create external-secrets/eso-user-test-fail-policy: test client error",
347+
wantErr: "failed to create NetworkPolicy external-secrets/eso-user-test-fail-policy: test client error",
348348
},
349349
{
350350
name: "custom network policy updated successfully",

0 commit comments

Comments
 (0)