@@ -12,6 +12,7 @@ import (
1212 un "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1313 "k8s.io/apimachinery/pkg/runtime/schema"
1414 "k8s.io/apimachinery/pkg/util/uuid"
15+ "k8s.io/utils/ptr"
1516
1617 "github.com/crossplane/crossplane-runtime/v2/pkg/errors"
1718 "github.com/crossplane/crossplane-runtime/v2/pkg/logging"
@@ -24,7 +25,7 @@ type ResourceManager interface {
2425 FetchCurrentObject (ctx context.Context , composite * un.Unstructured , desired * un.Unstructured ) (* un.Unstructured , bool , error )
2526
2627 // UpdateOwnerRefs ensures all OwnerReferences have valid UIDs
27- UpdateOwnerRefs (parent * un.Unstructured , child * un.Unstructured )
28+ UpdateOwnerRefs (ctx context. Context , parent * un.Unstructured , child * un.Unstructured )
2829}
2930
3031// DefaultResourceManager implements ResourceManager interface.
@@ -289,6 +290,9 @@ func (m *DefaultResourceManager) findMatchingResource(
289290 continue
290291 }
291292
293+ // TODO: is this logic correct? we should definitely match composition-resource-name, even if the generateName
294+ // doesn't match. maybe as a fallback if we don't have the annotation?
295+
292296 // If we have a generateName, verify the match has the right prefix
293297 if generateName != "" {
294298 resName := res .GetName ()
@@ -333,17 +337,79 @@ func (m *DefaultResourceManager) hasMatchingResourceName(annotations map[string]
333337}
334338
335339// UpdateOwnerRefs ensures all OwnerReferences have valid UIDs.
336- func (m * DefaultResourceManager ) UpdateOwnerRefs (parent * un.Unstructured , child * un.Unstructured ) {
340+ // It handles Claims and XRs differently according to Crossplane's ownership model:
341+ // - Claims should never be controller owners of composed resources.
342+ // - XRs should be controller owners and use their UID for matching references.
343+ func (m * DefaultResourceManager ) UpdateOwnerRefs (ctx context.Context , parent * un.Unstructured , child * un.Unstructured ) {
337344 // if there's no parent, we are the parent.
338345 if parent == nil {
339346 m .logger .Debug ("No parent provided for owner references update" )
340347 return
341348 }
342349
343- uid := parent .GetUID ()
350+ // Check if the parent is a claim and dispatch to appropriate handler
351+ isParentAClaim := m .defClient .IsClaimResource (ctx , parent )
352+
353+ switch {
354+ case isParentAClaim :
355+ m .updateOwnerRefsForClaim (parent , child )
356+ default :
357+ m .updateOwnerRefsForXR (parent , child )
358+ }
359+
360+ // Update composite owner label (common for both Claims and XRs)
361+ m .updateCompositeOwnerLabel (ctx , parent , child )
362+ }
363+
364+ // updateOwnerRefsForClaim handles owner reference updates when the parent is a Claim.
365+ // Claims should not be controller owners of composed resources per Crossplane's model:
366+ // Claim -> owns -> XR -> owns -> Composed Resources.
367+ func (m * DefaultResourceManager ) updateOwnerRefsForClaim (claim * un.Unstructured , child * un.Unstructured ) {
368+ m .logger .Debug ("Processing owner references with claim parent" ,
369+ "parentKind" , claim .GetKind (),
370+ "parentName" , claim .GetName (),
371+ "childKind" , child .GetKind (),
372+ "childName" , child .GetName ())
373+
374+ // Get the current owner references
375+ refs := child .GetOwnerReferences ()
376+ updatedRefs := make ([]metav1.OwnerReference , 0 , len (refs ))
377+
378+ for _ , ref := range refs {
379+ // Generate UIDs for any owner references that don't have them
380+ // For Claims, we never use the Claim's UID even for matching refs
381+ if ref .UID == "" {
382+ ref .UID = uuid .NewUUID ()
383+ m .logger .Debug ("Generated UID for owner reference" ,
384+ "refKind" , ref .Kind ,
385+ "refName" , ref .Name ,
386+ "newUID" , ref .UID )
387+ }
388+
389+ // Ensure Claims are never controller owners
390+ if ref .Kind == claim .GetKind () && ref .Name == claim .GetName () {
391+ if ref .Controller != nil && * ref .Controller {
392+ ref .Controller = ptr .To (false )
393+ m .logger .Debug ("Set Controller to false for claim owner reference" ,
394+ "refKind" , ref .Kind ,
395+ "refName" , ref .Name )
396+ }
397+ }
398+
399+ updatedRefs = append (updatedRefs , ref )
400+ }
401+
402+ child .SetOwnerReferences (updatedRefs )
403+ }
404+
405+ // updateOwnerRefsForXR handles owner reference updates when the parent is an XR.
406+ // XRs should be controller owners of their composed resources and use their UID
407+ // for matching owner references.
408+ func (m * DefaultResourceManager ) updateOwnerRefsForXR (xr * un.Unstructured , child * un.Unstructured ) {
409+ uid := xr .GetUID ()
344410 m .logger .Debug ("Updating owner references" ,
345- "parentKind" , parent .GetKind (),
346- "parentName" , parent .GetName (),
411+ "parentKind" , xr .GetKind (),
412+ "parentName" , xr .GetName (),
347413 "parentUID" , uid ,
348414 "childKind" , child .GetKind (),
349415 "childName" , child .GetName ())
@@ -359,11 +425,11 @@ func (m *DefaultResourceManager) UpdateOwnerRefs(parent *un.Unstructured, child
359425 for _ , ref := range refs {
360426 originalUID := ref .UID
361427
362- // if there is an owner ref on the dependent that we are pretty sure comes from us ,
363- // point the UID to the parent.
364- if ref .Name == parent .GetName () &&
365- ref .APIVersion == parent .GetAPIVersion () &&
366- ref .Kind == parent .GetKind () &&
428+ // If there is an owner ref on the dependent that matches the parent XR ,
429+ // use the parent's UID
430+ if ref .Name == xr .GetName () &&
431+ ref .APIVersion == xr .GetAPIVersion () &&
432+ ref .Kind == xr .GetKind () &&
367433 ref .UID == "" {
368434 ref .UID = uid
369435 m .logger .Debug ("Updated matching owner reference with parent UID" ,
@@ -372,7 +438,7 @@ func (m *DefaultResourceManager) UpdateOwnerRefs(parent *un.Unstructured, child
372438 "newUID" , ref .UID )
373439 }
374440
375- // if we have a non-matching owner ref don't use the parent UID.
441+ // For non-matching owner refs, generate a random UID
376442 if ref .UID == "" {
377443 ref .UID = uuid .NewUUID ()
378444 m .logger .Debug ("Generated new random UID for owner reference" ,
@@ -387,17 +453,14 @@ func (m *DefaultResourceManager) UpdateOwnerRefs(parent *un.Unstructured, child
387453 // Update the object with the modified owner references
388454 child .SetOwnerReferences (updatedRefs )
389455
390- // Update composite owner label
391- m .updateCompositeOwnerLabel (parent , child )
392-
393456 m .logger .Debug ("Updated owner references and labels" ,
394457 "newCount" , len (updatedRefs ))
395458}
396459
397460// updateCompositeOwnerLabel updates the ownership labels on the child resource.
398461// For Claims, it sets claim-name and claim-namespace labels.
399462// For XRs, it sets the composite label.
400- func (m * DefaultResourceManager ) updateCompositeOwnerLabel (parent , child * un.Unstructured ) {
463+ func (m * DefaultResourceManager ) updateCompositeOwnerLabel (ctx context. Context , parent , child * un.Unstructured ) {
401464 if parent == nil {
402465 return
403466 }
@@ -419,18 +482,32 @@ func (m *DefaultResourceManager) updateCompositeOwnerLabel(parent, child *un.Uns
419482 }
420483
421484 // Check if the parent is a claim
422- ctx := context .Background ()
423485 isParentAClaim := m .defClient .IsClaimResource (ctx , parent )
424486
425487 switch {
426488 case isParentAClaim :
427489 // For claims, set claim-specific labels (all claims are namespaced)
428490 labels ["crossplane.io/claim-name" ] = parentName
429491 labels ["crossplane.io/claim-namespace" ] = parent .GetNamespace ()
430- m .logger .Debug ("Updated claim owner labels" ,
431- "claimName" , parentName ,
432- "claimNamespace" , parent .GetNamespace (),
433- "child" , child .GetName ())
492+
493+ // For claims, only set the composite label if it doesn't already exist
494+ // If it exists, it likely points to a generated XR name which we should preserve
495+ existingComposite , hasComposite := labels ["crossplane.io/composite" ]
496+ if ! hasComposite {
497+ // No existing composite label, set it to the claim name
498+ labels ["crossplane.io/composite" ] = parentName
499+ m .logger .Debug ("Set composite label for new claim resource" ,
500+ "claimName" , parentName ,
501+ "claimNamespace" , parent .GetNamespace (),
502+ "child" , child .GetName ())
503+ } else {
504+ // Preserve existing composite label (likely a generated XR name)
505+ m .logger .Debug ("Preserved existing composite label for claim resource" ,
506+ "claimName" , parentName ,
507+ "claimNamespace" , parent .GetNamespace (),
508+ "existingComposite" , existingComposite ,
509+ "child" , child .GetName ())
510+ }
434511 default :
435512 // For XRs, set the composite label
436513 labels ["crossplane.io/composite" ] = parentName
0 commit comments