Skip to content

Commit 1661bfa

Browse files
kaovilaiclaude
andauthored
OADP-6652: Fix unnecessary secret updates and logging in STS flow (#1936)
* Fix unnecessary secret updates and logging in STS flow The operator was repeatedly logging "Secret already exists, updating" and "Following standardized STS workflow, secret created successfully" even when the secret content hadn't changed. This was happening because the CloudStorage controller calls STSStandardizedFlow() on every reconciliation, which always attempted to create the secret first, then caught the AlreadyExists error and performed an update. Changed the approach to: - First check if the secret exists - Compare existing data with desired data - Only update when there are actual differences - Skip updates and avoid logging when content is identical - Changed CloudStorage controller to use Debug level and more accurate message when STS secret is available (not necessarily created) This eliminates unnecessary API calls to the Kubernetes cluster and reduces noise in the operator logs. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * refactor: Use constants for STS secret labels and error messages Replace hardcoded strings with constants from stsflow package: - Add constants for secret operation verbs (created, updated, unchanged) - Add constants for STS secret label key/value - Add constants for error messages - Update all files using "oadp.openshift.io/secret-type" to use STSSecretLabelKey - Update test files to use the new constants This improves maintainability and reduces risk of typos in label names and error messages across the codebase. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
1 parent 07d3569 commit 1661bfa

File tree

6 files changed

+151
-56
lines changed

6 files changed

+151
-56
lines changed

internal/controller/bsl_test.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
oadpv1alpha1 "github.com/openshift/oadp-operator/api/v1alpha1"
2525
"github.com/openshift/oadp-operator/pkg/common"
26+
"github.com/openshift/oadp-operator/pkg/credentials/stsflow"
2627
)
2728

2829
// A bucket that region can be automatically discovered
@@ -3457,7 +3458,7 @@ func TestPatchSecretsForBSL(t *testing.T) {
34573458
Name: "aws-secret",
34583459
Namespace: "test-ns",
34593460
Labels: map[string]string{
3460-
"oadp.openshift.io/secret-type": "sts-credentials",
3461+
stsflow.STSSecretLabelKey: stsflow.STSSecretLabelValue,
34613462
},
34623463
},
34633464
Data: map[string][]byte{
@@ -3498,7 +3499,7 @@ web_identity_token_file = /var/run/secrets/openshift/serviceaccount/token`),
34983499
Name: "aws-secret",
34993500
Namespace: "test-ns",
35003501
Labels: map[string]string{
3501-
"oadp.openshift.io/secret-type": "sts-credentials",
3502+
stsflow.STSSecretLabelKey: stsflow.STSSecretLabelValue,
35023503
},
35033504
},
35043505
Data: map[string][]byte{
@@ -3539,7 +3540,7 @@ web_identity_token_file = /var/run/secrets/openshift/serviceaccount/token`),
35393540
Name: "aws-secret",
35403541
Namespace: "test-ns",
35413542
Labels: map[string]string{
3542-
"oadp.openshift.io/secret-type": "sts-credentials",
3543+
stsflow.STSSecretLabelKey: stsflow.STSSecretLabelValue,
35433544
},
35443545
},
35453546
Data: map[string][]byte{
@@ -3579,7 +3580,7 @@ web_identity_token_file = /var/run/secrets/openshift/serviceaccount/token`),
35793580
Name: "azure-secret",
35803581
Namespace: "test-ns",
35813582
Labels: map[string]string{
3582-
"oadp.openshift.io/secret-type": "sts-credentials",
3583+
stsflow.STSSecretLabelKey: stsflow.STSSecretLabelValue,
35833584
},
35843585
},
35853586
Data: map[string][]byte{
@@ -3662,7 +3663,7 @@ AZURE_TENANT_ID=test-tenant`),
36623663
Name: "aws-secret",
36633664
Namespace: "test-ns",
36643665
Labels: map[string]string{
3665-
"oadp.openshift.io/secret-type": "sts-credentials",
3666+
stsflow.STSSecretLabelKey: stsflow.STSSecretLabelValue,
36663667
},
36673668
},
36683669
Data: map[string][]byte{
@@ -3777,7 +3778,7 @@ aws_secret_access_key=test-secret`),
37773778
Name: "aws-sts-secret",
37783779
Namespace: "test-ns",
37793780
Labels: map[string]string{
3780-
"oadp.openshift.io/secret-type": "sts-credentials",
3781+
stsflow.STSSecretLabelKey: stsflow.STSSecretLabelValue,
37813782
},
37823783
},
37833784
Data: map[string][]byte{
@@ -3860,7 +3861,7 @@ aws_secret_access_key=test-secret`),
38603861
Name: "azure-sts-secret",
38613862
Namespace: "test-ns",
38623863
Labels: map[string]string{
3863-
"oadp.openshift.io/secret-type": "sts-credentials",
3864+
stsflow.STSSecretLabelKey: stsflow.STSSecretLabelValue,
38643865
},
38653866
},
38663867
Data: map[string][]byte{

internal/controller/cloudstorage_controller.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ func (b CloudStorageReconciler) Reconcile(ctx context.Context, req ctrl.Request)
132132
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
133133
}
134134
if secretName != "" {
135-
// Secret was created successfully by STSStandardizedFlow
136-
logger.Info(fmt.Sprintf("Following standardized STS workflow, secret %s created successfully", secretName))
135+
// Secret exists after STSStandardizedFlow (may have been created, updated, or unchanged)
136+
logger.V(1).Info(fmt.Sprintf("Following standardized STS workflow, secret %s is available", secretName))
137137
}
138138
// Now continue with bucket creation as secret exists and we are good to go !!!
139139
if ok, err = clnt.Exists(); !ok && err == nil {

pkg/bucket/azure.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"sigs.k8s.io/controller-runtime/pkg/client"
2121

2222
"github.com/openshift/oadp-operator/api/v1alpha1"
23+
"github.com/openshift/oadp-operator/pkg/credentials/stsflow"
2324
)
2425

2526
// azureServiceClient abstracts the Azure blob service client for testing
@@ -352,7 +353,7 @@ func (a *azureBucketClient) createAzureClient() (azureServiceClient, error) {
352353
// hasWorkloadIdentityCredentials checks if the secret contains workload identity credentials
353354
func (a *azureBucketClient) hasWorkloadIdentityCredentials(secret *corev1.Secret) bool {
354355
// Check if this is an STS-type secret created by OADP operator
355-
if labels, ok := secret.Labels["oadp.openshift.io/secret-type"]; ok && labels == "sts-credentials" {
356+
if labels, ok := secret.Labels[stsflow.STSSecretLabelKey]; ok && labels == stsflow.STSSecretLabelValue {
356357
// For Azure STS secrets, check if it has the azurekey field
357358
if azureKey, ok := secret.Data["azurekey"]; ok && len(azureKey) > 0 {
358359
// Parse the azurekey to ensure it has the required fields

pkg/bucket/azure_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"sigs.k8s.io/controller-runtime/pkg/client"
1616

1717
"github.com/openshift/oadp-operator/api/v1alpha1"
18+
"github.com/openshift/oadp-operator/pkg/credentials/stsflow"
1819
)
1920

2021
func TestValidateContainerName(t *testing.T) {
@@ -295,7 +296,7 @@ AZURE_CLOUD_NAME=AzurePublicCloud
295296
`),
296297
},
297298
secretLabels: map[string]string{
298-
"oadp.openshift.io/secret-type": "sts-credentials",
299+
stsflow.STSSecretLabelKey: stsflow.STSSecretLabelValue,
299300
},
300301
expected: true,
301302
},
@@ -308,7 +309,7 @@ AZURE_CLOUD_NAME=AzurePublicCloud
308309
`),
309310
},
310311
secretLabels: map[string]string{
311-
"oadp.openshift.io/secret-type": "sts-credentials",
312+
stsflow.STSSecretLabelKey: stsflow.STSSecretLabelValue,
312313
},
313314
expected: false,
314315
},

pkg/credentials/stsflow/stsflow.go

Lines changed: 84 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,20 @@ const (
5959
VeleroAWSSecretName = "cloud-credentials"
6060
VeleroAzureSecretName = "cloud-credentials-azure"
6161
VeleroGCPSecretName = "cloud-credentials-gcp"
62+
63+
// Secret operation verbs
64+
SecretVerbCreated = "created"
65+
SecretVerbUpdated = "updated"
66+
SecretVerbUnchanged = "unchanged"
67+
68+
// Label keys and values
69+
STSSecretLabelKey = "oadp.openshift.io/secret-type"
70+
STSSecretLabelValue = "sts-credentials"
71+
72+
// Error messages
73+
ErrMsgCreateSecret = "unable to create secret resource"
74+
ErrMsgGetSecret = "unable to get secret resource"
75+
ErrMsgUpdateSecret = "unable to update secret resource: %v"
6276
)
6377

6478
// STSStandardizedFlow creates secrets for Short Term Service Account Tokens from environment variables for
@@ -216,58 +230,98 @@ func CreateOrUpdateSTSSecretWithClients(setupLog logr.Logger, secretName string,
216230
func CreateOrUpdateSTSSecretWithClientsAndWait(setupLog logr.Logger, secretName string, credStringData map[string]string, secretNS string, clientInstance client.Client, clientset kubernetes.Interface, waitForSecret bool) error {
217231
// Create a secret with the appropriate credentials format for STS/WIF authentication
218232
// Secret format follows standard patterns used by cloud providers
219-
secret := corev1.Secret{
233+
desiredSecret := corev1.Secret{
220234
ObjectMeta: metav1.ObjectMeta{
221235
Name: secretName,
222236
Namespace: secretNS,
223237
Labels: map[string]string{
224-
"oadp.openshift.io/secret-type": "sts-credentials",
238+
STSSecretLabelKey: STSSecretLabelValue,
225239
},
226240
},
227241
StringData: credStringData,
228242
}
229-
verb := "created"
230-
if err := clientInstance.Create(context.Background(), &secret); err != nil {
231-
if errors.IsAlreadyExists(err) {
232-
verb = "updated"
233-
setupLog.Info("Secret already exists, updating")
234-
fromCluster := corev1.Secret{}
235-
err = clientInstance.Get(context.Background(), types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, &fromCluster)
236-
if err != nil {
237-
setupLog.Error(err, "unable to get existing secret resource")
243+
244+
// First, try to get the existing secret
245+
existingSecret := corev1.Secret{}
246+
err := clientInstance.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: secretNS}, &existingSecret)
247+
248+
verb := SecretVerbCreated
249+
if err != nil {
250+
if errors.IsNotFound(err) {
251+
// Secret doesn't exist, create it
252+
if err := clientInstance.Create(context.Background(), &desiredSecret); err != nil {
253+
setupLog.Error(err, ErrMsgCreateSecret)
238254
return err
239255
}
240-
// update StringData - preserve existing Data that's not being replaced
241-
// This is safe because STS credentials are only updated during install/reconfiguration,
242-
// and any BSL-specific patches (like region) should be preserved
243-
updatedFromCluster := fromCluster.DeepCopy()
256+
} else {
257+
// Some other error occurred while getting the secret
258+
setupLog.Error(err, ErrMsgGetSecret)
259+
return err
260+
}
261+
} else {
262+
// Secret exists, check if update is needed
263+
needsUpdate := false
264+
265+
// Check if labels need updating
266+
if existingSecret.Labels == nil || existingSecret.Labels[STSSecretLabelKey] != STSSecretLabelValue {
267+
needsUpdate = true
268+
}
269+
270+
// Check if data needs updating
271+
// Convert existing Data to string for comparison
272+
existingData := make(map[string]string)
273+
for key, value := range existingSecret.Data {
274+
existingData[key] = string(value)
275+
}
276+
277+
// Compare each key in credStringData
278+
for key, desiredValue := range credStringData {
279+
if existingValue, exists := existingData[key]; !exists || existingValue != desiredValue {
280+
needsUpdate = true
281+
break
282+
}
283+
}
284+
285+
if needsUpdate {
286+
verb = SecretVerbUpdated
287+
setupLog.Info("Secret content differs, updating")
288+
289+
// Update the secret
290+
updatedSecret := existingSecret.DeepCopy()
291+
244292
// Initialize StringData if not present
245-
if updatedFromCluster.StringData == nil {
246-
updatedFromCluster.StringData = make(map[string]string)
293+
if updatedSecret.StringData == nil {
294+
updatedSecret.StringData = make(map[string]string)
247295
}
296+
248297
// Update only the new StringData fields, preserving existing Data
249-
for key, value := range secret.StringData {
250-
updatedFromCluster.StringData[key] = value
298+
for key, value := range credStringData {
299+
updatedSecret.StringData[key] = value
251300
}
301+
252302
// Ensure labels are set
253-
if updatedFromCluster.Labels == nil {
254-
updatedFromCluster.Labels = make(map[string]string)
303+
if updatedSecret.Labels == nil {
304+
updatedSecret.Labels = make(map[string]string)
255305
}
256-
updatedFromCluster.Labels["oadp.openshift.io/secret-type"] = "sts-credentials"
257-
if err := clientInstance.Patch(context.Background(), updatedFromCluster, client.MergeFrom(&fromCluster)); err != nil {
258-
setupLog.Error(err, fmt.Sprintf("unable to update secret resource: %v", err))
306+
updatedSecret.Labels[STSSecretLabelKey] = STSSecretLabelValue
307+
308+
if err := clientInstance.Patch(context.Background(), updatedSecret, client.MergeFrom(&existingSecret)); err != nil {
309+
setupLog.Error(err, fmt.Sprintf(ErrMsgUpdateSecret, err))
259310
return err
260311
}
261312
} else {
262-
setupLog.Error(err, "unable to create secret resource")
263-
return err
313+
// No update needed
314+
verb = SecretVerbUnchanged
264315
}
265316
}
266-
setupLog.Info("Secret " + secret.Name + " " + verb + " successfully")
267317

268-
if waitForSecret {
269-
// Wait for the Secret to be available
270-
setupLog.Info(fmt.Sprintf("Waiting for %s Secret to be available", secret.Name))
318+
if verb != SecretVerbUnchanged {
319+
setupLog.Info("Secret " + desiredSecret.Name + " " + verb + " successfully")
320+
}
321+
322+
if waitForSecret && verb == SecretVerbCreated {
323+
// Wait for the Secret to be available (only needed for newly created secrets)
324+
setupLog.Info(fmt.Sprintf("Waiting for %s Secret to be available", desiredSecret.Name))
271325
_, err := WaitForSecret(clientset, secretNS, secretName)
272326
if err != nil {
273327
setupLog.Error(err, "error waiting for credentials Secret")

pkg/credentials/stsflow/stsflow_test.go

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ func TestCreateOrUpdateSTSSecret(t *testing.T) {
158158

159159
// Verify the label is set
160160
assert.NotNil(t, secret.Labels)
161-
assert.Equal(t, "sts-credentials", secret.Labels["oadp.openshift.io/secret-type"])
161+
assert.Equal(t, STSSecretLabelValue, secret.Labels[STSSecretLabelKey])
162162
}
163163
})
164164
}
@@ -284,18 +284,11 @@ func TestCreateOrUpdateSTSSecret_ErrorScenarios(t *testing.T) {
284284
testSecretName := "test-secret"
285285
testLogger := zap.New(zap.UseDevMode(true))
286286

287-
t.Run("Get error during update", func(t *testing.T) {
288-
// Create a client that returns an error on Get
289-
fakeClient := &mockErrorClient{
290-
Client: fake.NewClientBuilder().
291-
WithRuntimeObjects(&corev1.Secret{
292-
ObjectMeta: metav1.ObjectMeta{
293-
Name: testSecretName,
294-
Namespace: testNamespace,
295-
},
296-
}).
297-
Build(),
298-
getError: true,
287+
t.Run("Get error (non-NotFound) during initial check", func(t *testing.T) {
288+
// Create a client that returns a non-NotFound error on Get
289+
// This simulates a real error (not just secret not existing)
290+
fakeClient := &mockErrorClientGenericGetError{
291+
Client: fake.NewClientBuilder().Build(),
299292
}
300293
fakeClientset := k8sfake.NewSimpleClientset()
301294

@@ -304,7 +297,7 @@ func TestCreateOrUpdateSTSSecret_ErrorScenarios(t *testing.T) {
304297
}, testNamespace, fakeClient, fakeClientset, false)
305298

306299
assert.Error(t, err)
307-
assert.Contains(t, err.Error(), "not found")
300+
assert.Contains(t, err.Error(), "unable to get secret resource")
308301
})
309302

310303
t.Run("Patch error during update", func(t *testing.T) {
@@ -329,6 +322,42 @@ func TestCreateOrUpdateSTSSecret_ErrorScenarios(t *testing.T) {
329322
assert.Error(t, err)
330323
assert.Contains(t, err.Error(), "patch error")
331324
})
325+
326+
t.Run("No update when content is identical", func(t *testing.T) {
327+
// Create a secret with the same data we'll try to update with
328+
existingSecret := &corev1.Secret{
329+
ObjectMeta: metav1.ObjectMeta{
330+
Name: testSecretName,
331+
Namespace: testNamespace,
332+
Labels: map[string]string{
333+
STSSecretLabelKey: STSSecretLabelValue,
334+
},
335+
},
336+
Data: map[string][]byte{
337+
"key": []byte("value"),
338+
},
339+
}
340+
fakeClient := fake.NewClientBuilder().
341+
WithRuntimeObjects(existingSecret).
342+
Build()
343+
fakeClientset := k8sfake.NewSimpleClientset()
344+
345+
// Try to update with the same content
346+
err := CreateOrUpdateSTSSecretWithClientsAndWait(testLogger, testSecretName, map[string]string{
347+
"key": "value",
348+
}, testNamespace, fakeClient, fakeClientset, false)
349+
350+
assert.NoError(t, err)
351+
// Verify the secret wasn't modified
352+
secretResult := &corev1.Secret{}
353+
err = fakeClient.Get(context.Background(), client.ObjectKey{
354+
Name: testSecretName,
355+
Namespace: testNamespace,
356+
}, secretResult)
357+
assert.NoError(t, err)
358+
// The Data should remain unchanged (no StringData should be set)
359+
assert.Equal(t, []byte("value"), secretResult.Data["key"])
360+
})
332361
}
333362

334363
// Mock client that can simulate errors
@@ -357,6 +386,15 @@ func (m *mockErrorClient) Patch(ctx context.Context, obj client.Object, patch cl
357386
return m.Client.Patch(ctx, obj, patch, opts...)
358387
}
359388

389+
// New mock client that returns a generic error on Get (not NotFound)
390+
type mockErrorClientGenericGetError struct {
391+
client.Client
392+
}
393+
394+
func (m *mockErrorClientGenericGetError) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
395+
return errors.NewServiceUnavailable("unable to get secret resource")
396+
}
397+
360398
func TestSTSStandardizedFlow(t *testing.T) {
361399
// Save original env values
362400
originalWatchNS := os.Getenv("WATCH_NAMESPACE")

0 commit comments

Comments
 (0)