@@ -20,6 +20,7 @@ import (
2020 "context"
2121 "fmt"
2222 "reflect"
23+ "strings"
2324
2425 "sigs.k8s.io/controller-runtime/pkg/log"
2526
@@ -30,6 +31,11 @@ import (
3031 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3132)
3233
34+ const (
35+ // SecretFinalizer is added to critical secrets to prevent premature deletion during namespace teardown.
36+ SecretFinalizer = "secret.s3.bedag.ch/finalizer"
37+ )
38+
3339// FetchCredentialsFromSecret fetches a Secret and returns the credentials as strings.
3440// assumes that the keys are always "username" and "password".
3541func FetchCredentialsFromSecret (ctx context.Context , k8sClient client.Client , namespace string , secretName string ) (username string , password string , err error ) {
@@ -57,33 +63,36 @@ func FetchKeyPairFromSecret(ctx context.Context, k8sClient client.Client, namesp
5763}
5864
5965// creates a secret with keys "username" and "password" in the specified namespace.
60- func CreateCredentialSecret (ctx context.Context , k8sClient client.Client , namespace string , secretName string , username string , password string , owner metav1.Object ) error {
66+ func CreateCredentialSecret (ctx context.Context , k8sClient client.Client , namespace string , secretName string , username string , password string , owner metav1.Object , ownerKind string ) error {
6167 data := map [string ][]byte {
6268 "username" : []byte (username ),
6369 "password" : []byte (password ),
6470 }
6571
66- return createSecret (ctx , k8sClient , secretName , namespace , data , owner )
72+ return createSecret (ctx , k8sClient , secretName , namespace , data , owner , ownerKind )
6773}
6874
6975// creates a secret with keys "accessKeyId" and "secretAccessKey" in the specified namespace.
70- func CreateKeyPairSecret (ctx context.Context , k8sClient client.Client , namespace string , secretName string , accessKeyId string , secretAccessKey string , owner metav1.Object ) error {
76+ func CreateKeyPairSecret (ctx context.Context , k8sClient client.Client , namespace string , secretName string , accessKeyId string , secretAccessKey string , owner metav1.Object , ownerKind string ) error {
7177 data := map [string ][]byte {
7278 "accessKey" : []byte (accessKeyId ),
7379 "secretKey" : []byte (secretAccessKey ),
7480 }
7581
76- return createSecret (ctx , k8sClient , secretName , namespace , data , owner )
82+ return createSecret (ctx , k8sClient , secretName , namespace , data , owner , ownerKind )
7783}
7884
79- func createSecret (ctx context.Context , k8sClient client.Client , secretName string , secretNamespace string , data map [string ][]byte , owner metav1.Object ) error {
85+ func createSecret (ctx context.Context , k8sClient client.Client , secretName string , secretNamespace string , data map [string ][]byte , owner metav1.Object , ownerKind string ) error {
8086 log := log .FromContext (ctx ).WithValues ("func" , "createSecret" )
8187 log .V (1 ).Info ("Creating or updating secret" , "name" , secretName , "namespace" , secretNamespace )
8288
89+ labels := secretLabels (ownerKind , owner .GetName ())
90+
8391 desired := & corev1.Secret {
8492 ObjectMeta : metav1.ObjectMeta {
8593 Name : secretName ,
8694 Namespace : secretNamespace ,
95+ Labels : labels ,
8796 },
8897 Data : data ,
8998 }
@@ -116,9 +125,15 @@ func createSecret(ctx context.Context, k8sClient client.Client, secretName strin
116125 )
117126 }
118127
119- // Update existing if necessary.
120- if ! reflect .DeepEqual (existing .Data , desired .Data ) {
128+ // Reconcile labels and data on existing secret.
129+ labelsChanged := mergeLabels (existing , labels )
130+ dataChanged := ! reflect .DeepEqual (existing .Data , desired .Data )
131+
132+ if dataChanged {
121133 existing .Data = desired .Data
134+ }
135+
136+ if labelsChanged || dataChanged {
122137 if err := k8sClient .Update (ctx , existing ); err != nil {
123138 return err
124139 }
@@ -129,6 +144,32 @@ func createSecret(ctx context.Context, k8sClient client.Client, secretName strin
129144 return nil
130145}
131146
147+ // secretLabels returns the standard labels for operator-managed secrets.
148+ func secretLabels (ownerKind string , ownerName string ) map [string ]string {
149+ return map [string ]string {
150+ "app.kubernetes.io/managed-by" : "storagegrid-operator" ,
151+ "app.kubernetes.io/part-of" : strings .ToLower (ownerKind ),
152+ "app.kubernetes.io/instance" : ownerName ,
153+ }
154+ }
155+
156+ // mergeLabels adds missing labels to the existing object. Returns true if any labels were added or changed.
157+ func mergeLabels (obj metav1.Object , desired map [string ]string ) bool {
158+ existing := obj .GetLabels ()
159+ if existing == nil {
160+ obj .SetLabels (desired )
161+ return true
162+ }
163+ changed := false
164+ for k , v := range desired {
165+ if existing [k ] != v {
166+ existing [k ] = v
167+ changed = true
168+ }
169+ }
170+ return changed
171+ }
172+
132173// DeleteSecret deletes a secret from the specified namespace.
133174func DeleteSecret (ctx context.Context , k8sClient client.Client , namespace string , secretName string ) error {
134175 log := log .FromContext (ctx ).WithValues ("func" , "DeleteSecret" )
@@ -153,3 +194,51 @@ func DeleteSecret(ctx context.Context, k8sClient client.Client, namespace string
153194 log .V (1 ).Info ("Secret deleted successfully" , "name" , secretName , "namespace" , namespace )
154195 return nil
155196}
197+
198+ // AddSecretFinalizer adds the protective finalizer to a secret to prevent premature deletion during namespace teardown.
199+ func AddSecretFinalizer (ctx context.Context , k8sClient client.Client , namespace string , secretName string ) error {
200+ log := log .FromContext (ctx ).WithValues ("func" , "AddSecretFinalizer" )
201+
202+ secret := & corev1.Secret {}
203+ if err := k8sClient .Get (ctx , client.ObjectKey {Namespace : namespace , Name : secretName }, secret ); err != nil {
204+ return fmt .Errorf ("failed to get secret %s/%s for adding finalizer: %w" , namespace , secretName , err )
205+ }
206+
207+ if controllerutil .ContainsFinalizer (secret , SecretFinalizer ) {
208+ return nil
209+ }
210+
211+ controllerutil .AddFinalizer (secret , SecretFinalizer )
212+ if err := k8sClient .Update (ctx , secret ); err != nil {
213+ return fmt .Errorf ("failed to add finalizer to secret %s/%s: %w" , namespace , secretName , err )
214+ }
215+
216+ log .V (1 ).Info ("Added finalizer to secret" , "name" , secretName , "namespace" , namespace )
217+ return nil
218+ }
219+
220+ // RemoveSecretFinalizer removes the protective finalizer from a secret to allow deletion.
221+ func RemoveSecretFinalizer (ctx context.Context , k8sClient client.Client , namespace string , secretName string ) error {
222+ log := log .FromContext (ctx ).WithValues ("func" , "RemoveSecretFinalizer" )
223+
224+ secret := & corev1.Secret {}
225+ if err := k8sClient .Get (ctx , client.ObjectKey {Namespace : namespace , Name : secretName }, secret ); err != nil {
226+ if apierrors .IsNotFound (err ) {
227+ log .V (1 ).Info ("Secret already deleted, no finalizer to remove" , "name" , secretName , "namespace" , namespace )
228+ return nil
229+ }
230+ return fmt .Errorf ("failed to get secret %s/%s for removing finalizer: %w" , namespace , secretName , err )
231+ }
232+
233+ if ! controllerutil .ContainsFinalizer (secret , SecretFinalizer ) {
234+ return nil
235+ }
236+
237+ controllerutil .RemoveFinalizer (secret , SecretFinalizer )
238+ if err := k8sClient .Update (ctx , secret ); err != nil {
239+ return fmt .Errorf ("failed to remove finalizer from secret %s/%s: %w" , namespace , secretName , err )
240+ }
241+
242+ log .V (1 ).Info ("Removed finalizer from secret" , "name" , secretName , "namespace" , namespace )
243+ return nil
244+ }
0 commit comments