Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions internal/controllers/cluster/cluster_controller_phases.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,14 @@ func ensureOwnerRefAndLabel(ctx context.Context, c client.Client, obj *unstructu
return err
}

if err := controllerutil.SetControllerReference(cluster, obj, c.Scheme()); err != nil {
return err
// Only set ownerReference when Topology is defined.
// When Topology is not defined, the InfraCluster/ControlPlane is managed by
// the user or external tools (e.g., metacontroller), so CAPI should not
// set ownerReference to avoid interfering with their lifecycle management.
if cluster.Spec.Topology.IsDefined() {
if err := controllerutil.SetControllerReference(cluster, obj, c.Scheme()); err != nil {
return err
}
}

labels := obj.GetLabels()
Expand Down
100 changes: 100 additions & 0 deletions internal/controllers/cluster/cluster_controller_phases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -906,3 +906,103 @@ func generateInfraClusterV1Beta1(withFailureDomain bool) map[string]interface{}

return infraRef
}

func TestEnsureOwnerRefAndLabel(t *testing.T) {
tests := []struct {
name string
cluster *clusterv1.Cluster
obj *unstructured.Unstructured
expectOwnerRef bool
}{
{
name: "Topology defined - should set ownerReference with controller: true",
cluster: &clusterv1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
Namespace: "test-namespace",
UID: "test-uid",
},
Spec: clusterv1.ClusterSpec{
Topology: clusterv1.Topology{
ClassRef: clusterv1.ClusterClassRef{
Name: "test-class",
},
Version: "v1.25.0",
},
},
},
obj: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": clusterv1.GroupVersionInfrastructure.String(),
"kind": "GenericInfrastructureCluster",
"metadata": map[string]interface{}{
"name": "test-infra",
"namespace": "test-namespace",
},
},
},
expectOwnerRef: true,
},
{
name: "Topology not defined - should not set ownerReference",
cluster: &clusterv1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
Namespace: "test-namespace",
UID: "test-uid",
},
Spec: clusterv1.ClusterSpec{},
},
obj: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": clusterv1.GroupVersionInfrastructure.String(),
"kind": "GenericInfrastructureCluster",
"metadata": map[string]interface{}{
"name": "test-infra",
"namespace": "test-namespace",
},
},
},
expectOwnerRef: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)

c := fake.NewClientBuilder().
WithObjects(builder.GenericInfrastructureClusterCRD.DeepCopy(), tt.cluster, tt.obj).
Build()

err := ensureOwnerRefAndLabel(ctx, c, tt.obj, tt.cluster)
g.Expect(err).ToNot(HaveOccurred())

// Verify the object was updated
updatedObj := &unstructured.Unstructured{}
updatedObj.SetGroupVersionKind(tt.obj.GroupVersionKind())
err = c.Get(ctx, client.ObjectKey{Name: tt.obj.GetName(), Namespace: tt.obj.GetNamespace()}, updatedObj)
g.Expect(err).ToNot(HaveOccurred())

// Check owner references
ownerRefs := updatedObj.GetOwnerReferences()
var clusterOwnerRef *metav1.OwnerReference
for i := range ownerRefs {
if ownerRefs[i].Kind == "Cluster" && ownerRefs[i].Name == tt.cluster.Name {
clusterOwnerRef = &ownerRefs[i]
break
}
}

if tt.expectOwnerRef {
g.Expect(clusterOwnerRef).ToNot(BeNil(), "expected ownerReference to Cluster")
g.Expect(ptr.Deref(clusterOwnerRef.Controller, false)).To(BeTrue(), "expected controller: true")
} else {
g.Expect(clusterOwnerRef).To(BeNil(), "expected no ownerReference to Cluster")
}

// Verify label is always set
g.Expect(updatedObj.GetLabels()[clusterv1.ClusterNameLabel]).To(Equal(tt.cluster.Name))
})
}
}
Loading