@@ -6,7 +6,9 @@ package reconciler
66
77import (
88 "context"
9+ "encoding/json"
910 "reflect"
11+ "strings"
1012
1113 corev1 "k8s.io/api/core/v1"
1214 apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -17,6 +19,7 @@ import (
1719 "sigs.k8s.io/controller-runtime/pkg/client"
1820
1921 policyv1alpha1 "github.com/elastic/cloud-on-k8s/v3/pkg/apis/stackconfigpolicy/v1alpha1"
22+ commonannotation "github.com/elastic/cloud-on-k8s/v3/pkg/controller/common/annotation"
2023 "github.com/elastic/cloud-on-k8s/v3/pkg/utils/k8s"
2124 ulog "github.com/elastic/cloud-on-k8s/v3/pkg/utils/log"
2225 "github.com/elastic/cloud-on-k8s/v3/pkg/utils/maps"
@@ -92,6 +95,49 @@ func SoftOwnerRefFromLabels(labels map[string]string) (SoftOwnerRef, bool) {
9295 return SoftOwnerRef {Namespace : namespace , Name : name , Kind : kind }, true
9396}
9497
98+ // SoftOwnerRefs returns the soft owner references of the given object.
99+ func SoftOwnerRefs (obj metav1.Object ) ([]SoftOwnerRef , error ) {
100+ // Check if this Secret has a soft-owner kind label set
101+ ownerKind , exists := obj .GetLabels ()[SoftOwnerKindLabel ]
102+ if ! exists {
103+ // Not a soft-owned secret
104+ return nil , nil
105+ }
106+
107+ // Check for multi-policy ownership (annotation-based)
108+ if ownerRefsBytes , exists := obj .GetAnnotations ()[commonannotation .SoftOwnerRefsAnnotation ]; exists {
109+ // Multi-policy soft owned secret - parse the JSON map of owners
110+ var ownerRefs map [string ]struct {}
111+ if err := json .Unmarshal ([]byte (ownerRefsBytes ), & ownerRefs ); err != nil {
112+ return nil , err
113+ }
114+
115+ // Convert the map keys (namespaced name strings) back to NamespacedName objects
116+ var ownerRefsNsn []SoftOwnerRef
117+ for nsnStr := range ownerRefs {
118+ // Split the string format "namespace/name" into components
119+ nsnComponents := strings .Split (nsnStr , string (types .Separator ))
120+ if len (nsnComponents ) != 2 {
121+ // Skip malformed entries
122+ continue
123+ }
124+ ownerRefsNsn = append (ownerRefsNsn , SoftOwnerRef {Namespace : nsnComponents [0 ], Name : nsnComponents [1 ], Kind : ownerKind })
125+ }
126+
127+ return ownerRefsNsn , nil
128+ }
129+
130+ // Fall back to single-policy ownership (label-based)
131+ currentOwner , referenced := SoftOwnerRefFromLabels (obj .GetLabels ())
132+ if ! referenced {
133+ // No soft owner found in labels
134+ return nil , nil
135+ }
136+
137+ // Return the single owner as a slice with one element
138+ return []SoftOwnerRef {currentOwner }, nil
139+ }
140+
95141// ReconcileSecretNoOwnerRef should be called to reconcile a Secret for which we explicitly don't want
96142// an owner reference to be set, and want existing ownerReferences from previous operator versions to be removed,
97143// because of this k8s bug: https://github.com/kubernetes/kubernetes/issues/65200 (fixed in k8s 1.20).
@@ -200,43 +246,54 @@ func GarbageCollectAllSoftOwnedOrphanSecrets(ctx context.Context, c k8s.Client,
200246 var secrets corev1.SecretList
201247 if err := c .List (ctx ,
202248 & secrets ,
203- client.HasLabels {SoftOwnerNamespaceLabel , SoftOwnerNameLabel , SoftOwnerKindLabel },
249+ client.HasLabels {SoftOwnerKindLabel },
204250 ); err != nil {
205251 return err
206252 }
207253 // remove any secret whose owner doesn't exist
208254 for i := range secrets .Items {
209255 secret := secrets .Items [i ]
210- softOwner , referenced := SoftOwnerRefFromLabels (secret .Labels )
211- if ! referenced {
212- continue
213- }
214- if restrictedToOwnerNamespace (softOwner .Kind ) && softOwner .Namespace != secret .Namespace {
215- // Secret references an owner in a different namespace: this likely results
216- // from a "manual" copy of the secret in another namespace, not handled by the operator.
217- // We don't want to touch that secret.
218- continue
256+ softOwners , err := SoftOwnerRefs (& secret )
257+ if err != nil {
258+ return err
219259 }
220- owner , managed := ownerKinds [softOwner .Kind ]
221- if ! managed {
260+ if len (softOwners ) == 0 {
222261 continue
223262 }
224- owner = k8s .DeepCopyObject (owner )
225- err := c .Get (ctx , types.NamespacedName {Namespace : softOwner .Namespace , Name : softOwner .Name }, owner )
226- if err != nil {
227- if apierrors .IsNotFound (err ) {
228- // owner doesn't exit anymore
229- ulog .FromContext (ctx ).Info ("Deleting secret as part of garbage collection" ,
230- "namespace" , secret .Namespace , "secret_name" , secret .Name ,
231- "owner_kind" , softOwner .Kind , "owner_namespace" , softOwner .Namespace , "owner_name" , softOwner .Name ,
232- )
233- options := client.DeleteOptions {Preconditions : & metav1.Preconditions {UID : & secret .UID }}
234- if err := c .Delete (ctx , & secret , & options ); err != nil && ! apierrors .IsNotFound (err ) {
235- return err
236- }
263+
264+ missingOwners := make (map [types.NamespacedName ]client.Object )
265+ for _ , softOwner := range softOwners {
266+ if restrictedToOwnerNamespace (softOwner .Kind ) && softOwner .Namespace != secret .Namespace {
267+ // Secret references an owner in a different namespace: this likely results
268+ // from a "manual" copy of the secret in another namespace, not handled by the operator.
269+ // We don't want to touch that secret.
237270 continue
238271 }
239- return err
272+ owner , managed := ownerKinds [softOwner .Kind ]
273+ if ! managed {
274+ continue
275+ }
276+ owner = k8s .DeepCopyObject (owner )
277+ err := c .Get (ctx , types.NamespacedName {Namespace : softOwner .Namespace , Name : softOwner .Name }, owner )
278+ if err != nil {
279+ if apierrors .IsNotFound (err ) {
280+ // owner doesn't exit anymore
281+ ulog .FromContext (ctx ).Info ("Deleting secret as part of garbage collection" ,
282+ "namespace" , secret .Namespace , "secret_name" , secret .Name ,
283+ "owner_kind" , softOwner .Kind , "owner_namespace" , softOwner .Namespace , "owner_name" , softOwner .Name ,
284+ )
285+ missingOwners [types.NamespacedName {Namespace : softOwner .Namespace , Name : softOwner .Name }] = owner
286+ continue
287+ }
288+ return err
289+ }
290+ }
291+
292+ if len (missingOwners ) == len (softOwners ) {
293+ options := client.DeleteOptions {Preconditions : & metav1.Preconditions {UID : & secret .UID }}
294+ if err := c .Delete (ctx , & secret , & options ); err != nil && ! apierrors .IsNotFound (err ) {
295+ return err
296+ }
240297 }
241298 // owner still exists, keep the secret
242299 }
0 commit comments