44 "context"
55 "errors"
66 "fmt"
7- "maps"
87 "slices"
98 "strings"
109 "time"
@@ -154,7 +153,7 @@ func (h *handler) prepareHandler(registrationObj *v1.Registration, rancherURL st
154153 defaultLabels := map [string ]string {
155154 consts .LabelSccHash : registrationObj .Labels [consts .LabelSccHash ],
156155 consts .LabelNameSuffix : nameSuffixHash ,
157- consts .LabelSccManagedBy : controllerID ,
156+ consts .LabelSccManagedBy : consts . SccManagedByValue ( initializer . OperatorName . Get ()) ,
158157 consts .LabelK8sManagedBy : initializer .OperatorName .Get (),
159158 }
160159
@@ -195,6 +194,27 @@ func (h *handler) prepareHandler(registrationObj *v1.Registration, rancherURL st
195194 }
196195}
197196
197+ // mergeLabelsPreservingHelm merges params labels into the secret while preserving existing Helm ownership.
198+ func mergeLabelsPreservingHelm (secret * corev1.Secret , paramsLabels map [string ]string ) {
199+ // Initialize labels map if nil
200+ if secret .Labels == nil {
201+ secret .Labels = map [string ]string {}
202+ }
203+
204+ // Preserve existing app.kubernetes.io/managed-by (e.g., Helm)
205+ existingK8sManagedBy := secret .Labels [consts .LabelK8sManagedBy ]
206+
207+ // Merge params labels (which sets k8s managed-by to operator name)
208+ for k , v := range paramsLabels {
209+ secret .Labels [k ] = v
210+ }
211+
212+ // Restore the original k8s managed-by if it was set (preserve Helm ownership)
213+ if existingK8sManagedBy != "" {
214+ secret .Labels [consts .LabelK8sManagedBy ] = existingK8sManagedBy
215+ }
216+ }
217+
198218func (h * handler ) OnSecretChange (_ string , incomingObj * corev1.Secret ) (* corev1.Secret , error ) {
199219 if incomingObj == nil || incomingObj .DeletionTimestamp != nil {
200220 return incomingObj , nil
@@ -204,30 +224,34 @@ func (h *handler) OnSecretChange(_ string, incomingObj *corev1.Secret) (*corev1.
204224 return incomingObj , nil
205225 }
206226
207- // This only applies to the SCC Entrypoint secrets - currently only used for/by Rancher
208- // This will adopt "unowned" secrets and ignore any that are owned by other operators
209- if ! helpers .ShouldManage (incomingObj , h .options .OperatorName ) {
210- // When the secret has no managedBy label, we should assume ownership I guess?
211- if ! helpers .HasManagedByLabel (incomingObj ) {
212- h .log .Debugf ("taking ownership of the unowned entrypoint secret" )
213- prepared := incomingObj .DeepCopy ()
214- prepared = helpers .TakeOwnership (prepared , h .options .OperatorName )
215- _ , updateErr := h .secretRepo .RetryingPatchUpdate (incomingObj , prepared )
216- if updateErr != nil {
217- h .log .Errorf ("failed to take ownership of secret %s/%s: %v" , incomingObj .Namespace , incomingObj .Name , updateErr )
218- return incomingObj , updateErr
219- }
227+ // Phase 1: Adoption - take ownership of unmanaged or Helm-managed entrypoint Secrets
228+ if ! helpers .HasSccManagedByLabel (incomingObj ) && helpers .ShouldAdopt (incomingObj , h .options .OperatorName ) {
229+ h .log .Debugf ("adopting unmanaged or Helm-managed entrypoint secret" )
230+ prepared := incomingObj .DeepCopy ()
220231
221- h .log .Debugf ("Secret %s/%s is now managed by %s" , incomingObj .Namespace , incomingObj .Name , h .options .OperatorName )
232+ // Set both SCC and k8s managed-by labels (preserves Helm if present)
233+ prepared = helpers .TakeOwnership (prepared , h .options .OperatorName )
222234
223- return incomingObj , nil
235+ _ , updateErr := h .secretRepo .RetryingPatchUpdate (incomingObj , prepared )
236+ if updateErr != nil {
237+ h .log .Errorf ("failed to take ownership of secret %s/%s: %v" , incomingObj .Namespace , incomingObj .Name , updateErr )
238+ return incomingObj , updateErr
224239 }
225240
226- managedBy := helpers .GetManagedByValue (incomingObj )
227- h .log .Debugf ("Secret %s/%s is managed by %s not %s, skipping" , incomingObj .Namespace , incomingObj .Name , managedBy , h .options .OperatorName )
241+ h .log .Debugf ("Secret %s/%s is now managed by %s" , incomingObj .Namespace , incomingObj .Name , h .options .OperatorName )
228242 return incomingObj , nil
229243 }
230244
245+ // Phase 2: Validation - skip Secrets not managed by this operator
246+ if ! helpers .ShouldManage (incomingObj , h .options .OperatorName ) {
247+ sccManagedBy := helpers .GetSccManagedByValue (incomingObj )
248+ k8sManagedBy := helpers .GetManagedByValue (incomingObj )
249+ h .log .Debugf ("Secret %s/%s is managed by another tool (k8s: %s, scc: %s), skipping" , incomingObj .Namespace , incomingObj .Name , k8sManagedBy , sccManagedBy )
250+ return incomingObj , nil
251+ }
252+
253+ // Phase 3: Processing - handle managed entrypoint Secret
254+
231255 if _ , saltOk := incomingObj .GetLabels ()[consts .LabelObjectSalt ]; ! saltOk {
232256 return h .prepareSecretSalt (incomingObj )
233257 }
@@ -247,7 +271,9 @@ func (h *handler) OnSecretChange(_ string, incomingObj *corev1.Secret) (*corev1.
247271 newSecret .Annotations = map [string ]string {}
248272 }
249273 newSecret .Annotations [consts .LabelSccLastProcessed ] = time .Now ().Format (time .RFC3339 )
250- maps .Copy (newSecret .Labels , params .Labels ())
274+
275+ // Merge params labels while preserving Helm ownership
276+ mergeLabelsPreservingHelm (newSecret , params .Labels ())
251277
252278 _ , updateErr := h .secretRepo .RetryingPatchUpdate (incomingObj , newSecret )
253279 if updateErr != nil {
@@ -291,9 +317,8 @@ func (h *handler) OnSecretChange(_ string, incomingObj *corev1.Secret) (*corev1.
291317 }
292318 newSecret .Annotations [consts .LabelSccLastProcessed ] = time .Now ().Format (time .RFC3339 )
293319
294- labels := incomingObj .Labels
295- maps .Copy (labels , params .Labels ())
296- newSecret .Labels = labels
320+ // Merge params labels while preserving Helm ownership
321+ mergeLabelsPreservingHelm (newSecret , params .Labels ())
297322
298323 if _ , err := h .secretRepo .RetryingPatchUpdate (incomingObj , newSecret ); err != nil {
299324 return incomingObj , err
@@ -436,6 +461,9 @@ func (h *handler) OnSecretRemove(_ string, incomingObj *corev1.Secret) (*corev1.
436461 return incomingObj , nil
437462 }
438463
464+ // All Secrets (entrypoint, credentials, regcode, offline) must be managed by this operator
465+ // ShouldManage handles both Helm-managed entrypoint Secrets and operator-created Secrets
466+ // with dual labels (app.kubernetes.io/managed-by + scc.cattle.io/managed-by)
439467 if ! helpers .ShouldManage (incomingObj , h .options .OperatorName ) {
440468 h .log .Debugf ("Secret %s/%s is not managed by %s, skipping" , incomingObj .Namespace , incomingObj .Name , h .options .OperatorName )
441469 return incomingObj , nil
@@ -468,6 +496,10 @@ func (h *handler) OnSecretRemove(_ string, incomingObj *corev1.Secret) (*corev1.
468496 return incomingObj , nil
469497 }
470498
499+ // Non-entrypoint Secrets: ShouldManage already checked above
500+ // Operator-created Secrets (credentials, regcode, offline) always have both labels:
501+ // app.kubernetes.io/managed-by: rancher-scc-operator (never Helm)
502+ // scc.cattle.io/managed-by: rancher-scc-operator_secret-broker
471503 if lifecycle .SecretHasCredentialsFinalizer (incomingObj ) ||
472504 lifecycle .SecretHasRegCodeFinalizer (incomingObj ) {
473505 refs := incomingObj .GetOwnerReferences ()
0 commit comments