diff --git a/cmd/clusterctl/client/cluster/mover.go b/cmd/clusterctl/client/cluster/mover.go index f1d3b199c215..f7cf156cd161 100644 --- a/cmd/clusterctl/client/cluster/mover.go +++ b/cmd/clusterctl/client/cluster/mover.go @@ -275,7 +275,7 @@ func getMoveSequence(graph *objectGraph) *moveSequence { // NB. it is necessary to filter out nodes not belonging to a cluster because e.g. discovery reads all the secrets, // but only few of them are related to Clusters/Machines etc. moveGroup := moveGroup{} - for _, n := range graph.getNodesWithClusterTenants() { + for _, n := range graph.getNodesWithTenants() { // If the node was already included in the moveSequence, skip it. if moveSequence.hasNode(n) { continue @@ -360,7 +360,7 @@ func patchCluster(proxy Proxy, cluster *node, patch client.Patch) error { func (o *objectMover) ensureNamespaces(graph *objectGraph, toProxy Proxy) error { ensureNamespaceBackoff := newWriteBackoff() namespaces := sets.NewString() - for _, node := range graph.getNodesWithClusterTenants() { + for _, node := range graph.getNodesWithTenants() { namespace := node.identity.Namespace // If the namespace was already processed, skip it. diff --git a/cmd/clusterctl/client/cluster/mover_test.go b/cmd/clusterctl/client/cluster/mover_test.go index 44829056d7d6..510b16e9d4c5 100644 --- a/cmd/clusterctl/client/cluster/mover_test.go +++ b/cmd/clusterctl/client/cluster/mover_test.go @@ -356,6 +356,42 @@ var moveTests = []struct { }, }, }, + { + name: "A ClusterResourceSet applied to a cluster", + fields: moveTestsFields{ + objs: func() []runtime.Object { + objs := []runtime.Object{} + objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) + + objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1"). + WithSecret("resource-s1"). + WithConfigMap("resource-c1"). + ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")). + Objs()...) + + return objs + }(), + }, + wantMoveGroups: [][]string{ + { //group 1 + // Cluster + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1", + // ClusterResourceSet + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSet, ns1/crs1", + }, + { //group 2 (objects with ownerReferences in group 1) + // owned by Clusters + "/v1, Kind=Secret, ns1/cluster1-ca", + "/v1, Kind=Secret, ns1/cluster1-kubeconfig", + "infrastructure.cluster.x-k8s.io/v1alpha3, Kind=GenericInfrastructureCluster, ns1/cluster1", + // owned by ClusterResourceSet + "/v1, Kind=Secret, ns1/resource-s1", + "/v1, Kind=ConfigMap, ns1/resource-c1", + // owned by ClusterResourceSet & Cluster + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSetBinding, ns1/crs1", + }, + }, + }, } func Test_getMoveSequence(t *testing.T) { diff --git a/cmd/clusterctl/client/cluster/objectgraph.go b/cmd/clusterctl/client/cluster/objectgraph.go index 8ed2ab409eb4..71054f2ef6ac 100644 --- a/cmd/clusterctl/client/cluster/objectgraph.go +++ b/cmd/clusterctl/client/cluster/objectgraph.go @@ -27,6 +27,7 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log" + addonsv1alpha3 "sigs.k8s.io/cluster-api/exp/addons/api/v1alpha3" secretutil "sigs.k8s.io/cluster-api/util/secret" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -58,6 +59,10 @@ type node struct { // tenantClusters define the list of Clusters which are tenant for the node, no matter if the node has a direct OwnerReference to the Cluster or if // the node is linked to a Cluster indirectly in the OwnerReference chain. tenantClusters map[*node]empty + + // tenantCRSs define the list of ClusterResourceSet which are tenant for the node, no matter if the node has a direct OwnerReference to the Cluster or if + // the node is linked to a ClusterResourceSet indirectly in the OwnerReference chain. + tenantCRSs map[*node]empty } // markObserved marks the fact that a node was observed as a concrete object. @@ -130,6 +135,7 @@ func (o *objectGraph) ownerToVirtualNode(owner metav1.OwnerReference, namespace owners: make(map[*node]ownerReferenceAttributes), softOwners: make(map[*node]empty), tenantClusters: make(map[*node]empty), + tenantCRSs: make(map[*node]empty), virtual: true, } @@ -158,6 +164,7 @@ func (o *objectGraph) objToNode(obj *unstructured.Unstructured) *node { owners: make(map[*node]ownerReferenceAttributes), softOwners: make(map[*node]empty), tenantClusters: make(map[*node]empty), + tenantCRSs: make(map[*node]empty), virtual: false, } @@ -254,6 +261,9 @@ func (o *objectGraph) Discovery(namespace string, types []metav1.TypeMeta) error // Completes the graph by setting for each node the list of Clusters the node belong to. o.setClusterTenants() + // Completes the graph by setting for each node the list of ClusterResourceSet the node belong to. + o.setCRSTenants() + return nil } @@ -306,11 +316,22 @@ func (o *objectGraph) getNodes() []*node { return nodes } -// getNodesWithClusterTenants returns the list of nodes existing in the object graph that belong at least to one Cluster. -func (o *objectGraph) getNodesWithClusterTenants() []*node { +// getCRSs returns the list of ClusterResourceSet existing in the object graph. +func (o *objectGraph) getCRSs() []*node { + clusters := []*node{} + for _, node := range o.uidToNode { + if node.identity.GroupVersionKind().GroupKind() == addonsv1alpha3.GroupVersion.WithKind("ClusterResourceSet").GroupKind() { + clusters = append(clusters, node) + } + } + return clusters +} + +// getNodesWithTenants returns the list of nodes existing in the object graph that belong at least to one Cluster or to a ClusterResourceSet. +func (o *objectGraph) getNodesWithTenants() []*node { nodes := []*node{} for _, node := range o.uidToNode { - if len(node.tenantClusters) > 0 { + if len(node.tenantClusters) > 0 || len(node.tenantCRSs) > 0 { nodes = append(nodes, node) } } @@ -360,7 +381,7 @@ func (o *objectGraph) setClusterTenants() { } } -// setNodeTenant sets a tenant for a node and for its own dependents/sofDependents. +// setNodeTenant sets a cluster tenant for a node and for its own dependents/sofDependents. func (o *objectGraph) setClusterTenant(node, tenant *node) { node.tenantClusters[tenant] = empty{} for _, other := range o.getNodes() { @@ -369,3 +390,20 @@ func (o *objectGraph) setClusterTenant(node, tenant *node) { } } } + +// setClusterTenants sets the cluster tenants for the clusters itself and all their dependent object tree. +func (o *objectGraph) setCRSTenants() { + for _, crs := range o.getCRSs() { + o.setCRSTenant(crs, crs) + } +} + +// setCRSTenant sets a ClusterResourceSet tenant for a node and for its own dependents/sofDependents. +func (o *objectGraph) setCRSTenant(node, tenant *node) { + node.tenantCRSs[tenant] = empty{} + for _, other := range o.getNodes() { + if other.isOwnedBy(node) { + o.setCRSTenant(other, tenant) + } + } +} diff --git a/cmd/clusterctl/client/cluster/objectgraph_test.go b/cmd/clusterctl/client/cluster/objectgraph_test.go index 4e96cead55f7..0408d99fae4b 100644 --- a/cmd/clusterctl/client/cluster/objectgraph_test.go +++ b/cmd/clusterctl/client/cluster/objectgraph_test.go @@ -100,7 +100,7 @@ func assertGraph(t *testing.T, got *objectGraph, want wantGraph) { for uid, wantNode := range want.nodes { gotNode, ok := got.uidToNode[types.UID(uid)] - g.Expect(ok).To(BeTrue()) + g.Expect(ok).To(BeTrue(), "node ", uid, " not found") g.Expect(gotNode.virtual).To(Equal(wantNode.virtual)) g.Expect(gotNode.owners).To(HaveLen(len(wantNode.owners))) @@ -852,6 +852,133 @@ var objectGraphsTests = []struct { }, }, }, + { + name: "A ClusterResourceSet applied to a cluster", + args: objectGraphTestArgs{ + objs: func() []runtime.Object { + objs := []runtime.Object{} + objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) + + objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1"). + WithSecret("resource-s1"). + WithConfigMap("resource-c1"). + ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")). + Objs()...) + + return objs + }(), + }, + want: wantGraph{ + nodes: map[string]wantGraphItem{ + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1": {}, + "infrastructure.cluster.x-k8s.io/v1alpha3, Kind=GenericInfrastructureCluster, ns1/cluster1": { + owners: []string{ + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1", + }, + }, + "/v1, Kind=Secret, ns1/cluster1-ca": { + softOwners: []string{ + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1", //NB. this secret is not linked to the cluster through owner ref + }, + }, + "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { + owners: []string{ + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1", + }, + }, + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSet, ns1/crs1": {}, + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSetBinding, ns1/crs1": { + owners: []string{ + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSet, ns1/crs1", + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1", + }, + }, + "/v1, Kind=Secret, ns1/resource-s1": { + owners: []string{ + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSet, ns1/crs1", + }, + }, + "/v1, Kind=ConfigMap, ns1/resource-c1": { + owners: []string{ + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSet, ns1/crs1", + }, + }, + }, + }, + }, + { + name: "A ClusterResourceSet applied to two clusters", + args: objectGraphTestArgs{ + objs: func() []runtime.Object { + objs := []runtime.Object{} + objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) + objs = append(objs, test.NewFakeCluster("ns1", "cluster2").Objs()...) + + objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1"). + WithSecret("resource-s1"). + WithConfigMap("resource-c1"). + ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")). + ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster2")). + Objs()...) + + return objs + }(), + }, + want: wantGraph{ + nodes: map[string]wantGraphItem{ + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1": {}, + "infrastructure.cluster.x-k8s.io/v1alpha3, Kind=GenericInfrastructureCluster, ns1/cluster1": { + owners: []string{ + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1", + }, + }, + "/v1, Kind=Secret, ns1/cluster1-ca": { + softOwners: []string{ + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1", //NB. this secret is not linked to the cluster through owner ref + }, + }, + "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { + owners: []string{ + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1", + }, + }, + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster2": {}, + "infrastructure.cluster.x-k8s.io/v1alpha3, Kind=GenericInfrastructureCluster, ns1/cluster2": { + owners: []string{ + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster2", + }, + }, + "/v1, Kind=Secret, ns1/cluster2-ca": { + softOwners: []string{ + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster2", //NB. this secret is not linked to the cluster through owner ref + }, + }, + "/v1, Kind=Secret, ns1/cluster2-kubeconfig": { + owners: []string{ + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster2", + }, + }, + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSet, ns1/crs1": {}, + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSetBinding, ns1/crs1": { + owners: []string{ + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSet, ns1/crs1", + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1", + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster2", + }, + }, + "/v1, Kind=Secret, ns1/resource-s1": { + owners: []string{ + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSet, ns1/crs1", + }, + }, + "/v1, Kind=ConfigMap, ns1/resource-c1": { + owners: []string{ + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSet, ns1/crs1", + }, + }, + }, + }, + }, } func getDetachedObjectGraphWihObjs(objs []runtime.Object) (*objectGraph, error) { @@ -1245,6 +1372,67 @@ func Test_objectGraph_setClusterTenants(t *testing.T) { }, }, }, + { + name: "A ClusterResourceSet applied to a cluster", + fields: fields{ + objs: func() []runtime.Object { + objs := []runtime.Object{} + objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) + + objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1"). + WithSecret("resource-s1"). + WithConfigMap("resource-c1"). + ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")). + Objs()...) + + return objs + }(), + }, + wantClusters: map[string][]string{ // wantClusters is a map[Cluster.UID] --> list of UIDs + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1": { + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1", // the cluster should be tenant of itself + "infrastructure.cluster.x-k8s.io/v1alpha3, Kind=GenericInfrastructureCluster, ns1/cluster1", + "/v1, Kind=Secret, ns1/cluster1-ca", // the ca secret is a soft owned + "/v1, Kind=Secret, ns1/cluster1-kubeconfig", + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSetBinding, ns1/crs1", // ClusterResourceSetBinding are owned by the cluster + }, + }, + }, + { + name: "A ClusterResourceSet applied to two clusters", + fields: fields{ + objs: func() []runtime.Object { + objs := []runtime.Object{} + objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) + objs = append(objs, test.NewFakeCluster("ns1", "cluster2").Objs()...) + + objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1"). + WithSecret("resource-s1"). + WithConfigMap("resource-c1"). + ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")). + ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster2")). + Objs()...) + + return objs + }(), + }, + wantClusters: map[string][]string{ // wantClusters is a map[Cluster.UID] --> list of UIDs + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1": { + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1", // the cluster should be tenant of itself + "infrastructure.cluster.x-k8s.io/v1alpha3, Kind=GenericInfrastructureCluster, ns1/cluster1", + "/v1, Kind=Secret, ns1/cluster1-ca", // the ca secret is a soft owned + "/v1, Kind=Secret, ns1/cluster1-kubeconfig", + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSetBinding, ns1/crs1", // ClusterResourceSetBinding are owned by the cluster + }, + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster2": { + "cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster2", // the cluster should be tenant of itself + "infrastructure.cluster.x-k8s.io/v1alpha3, Kind=GenericInfrastructureCluster, ns1/cluster2", + "/v1, Kind=Secret, ns1/cluster2-ca", // the ca secret is a soft owned + "/v1, Kind=Secret, ns1/cluster2-kubeconfig", + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSetBinding, ns1/crs1", // ClusterResourceSetBinding are owned by the cluster + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1284,3 +1472,100 @@ func Test_objectGraph_setClusterTenants(t *testing.T) { }) } } + +func Test_objectGraph_setCRSTenants(t *testing.T) { + type fields struct { + objs []runtime.Object + } + tests := []struct { + name string + fields fields + wantCRSs map[string][]string + }{ + { + name: "A ClusterResourceSet applied to a cluster", + fields: fields{ + objs: func() []runtime.Object { + objs := []runtime.Object{} + objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) + + objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1"). + WithSecret("resource-s1"). + WithConfigMap("resource-c1"). + ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")). + Objs()...) + + return objs + }(), + }, + wantCRSs: map[string][]string{ // wantCRDs is a map[ClusterResourceSet.UID] --> list of UIDs + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSet, ns1/crs1": { + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSet, ns1/crs1", // the ClusterResourceSet should be tenant of itself + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSetBinding, ns1/crs1", // ClusterResourceSetBinding are owned by ClusterResourceSet + "/v1, Kind=Secret, ns1/resource-s1", // resource are owned by ClusterResourceSet + "/v1, Kind=ConfigMap, ns1/resource-c1", // resource are owned by ClusterResourceSet + }, + }, + }, + { + name: "A ClusterResourceSet applied to two clusters", + fields: fields{ + objs: func() []runtime.Object { + objs := []runtime.Object{} + objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) + objs = append(objs, test.NewFakeCluster("ns1", "cluster2").Objs()...) + + objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1"). + WithSecret("resource-s1"). + WithConfigMap("resource-c1"). + ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")). + ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster2")). + Objs()...) + + return objs + }(), + }, + wantCRSs: map[string][]string{ // wantCRDs is a map[ClusterResourceSet.UID] --> list of UIDs + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSet, ns1/crs1": { + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSet, ns1/crs1", // the ClusterResourceSet should be tenant of itself + "addons.cluster.x-k8s.io/v1alpha3, Kind=ClusterResourceSetBinding, ns1/crs1", // ClusterResourceSetBinding are owned by ClusterResourceSet + "/v1, Kind=Secret, ns1/resource-s1", // resource are owned by ClusterResourceSet + "/v1, Kind=ConfigMap, ns1/resource-c1", // resource are owned by ClusterResourceSet + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + gb, err := getDetachedObjectGraphWihObjs(tt.fields.objs) + g.Expect(err).NotTo(HaveOccurred()) + + gb.setCRSTenants() + + gotCRSs := gb.getCRSs() + sort.Slice(gotCRSs, func(i, j int) bool { + return gotCRSs[i].identity.UID < gotCRSs[j].identity.UID + }) + + g.Expect(gotCRSs).To(HaveLen(len(tt.wantCRSs))) + + for _, crs := range gotCRSs { + wantTenants, ok := tt.wantCRSs[string(crs.identity.UID)] + g.Expect(ok).To(BeTrue()) + + gotTenants := []string{} + for _, node := range gb.uidToNode { + for c := range node.tenantCRSs { + if c.identity.UID == crs.identity.UID { + gotTenants = append(gotTenants, string(node.identity.UID)) + } + } + } + + g.Expect(gotTenants).To(ConsistOf(wantTenants)) + } + }) + } +} diff --git a/cmd/clusterctl/internal/test/fake_objects.go b/cmd/clusterctl/internal/test/fake_objects.go index ac60c4db5394..4711e0c53fc5 100644 --- a/cmd/clusterctl/internal/test/fake_objects.go +++ b/cmd/clusterctl/internal/test/fake_objects.go @@ -31,6 +31,7 @@ import ( fakebootstrap "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test/providers/bootstrap" fakecontrolplane "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test/providers/controlplane" fakeinfrastructure "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test/providers/infrastructure" + addonsv1alpha3 "sigs.k8s.io/cluster-api/exp/addons/api/v1alpha3" expv1 "sigs.k8s.io/cluster-api/exp/api/v1alpha3" ) @@ -903,6 +904,209 @@ func (f *FakeMachine) Objs(cluster *clusterv1.Cluster, generateCerts bool, machi return objs } +type FakeClusterResourceSet struct { + name string + namespace string + secrets []*corev1.Secret + configMaps []*corev1.ConfigMap + clusters []*clusterv1.Cluster +} + +// NewFakeClusterResourceSet return a FakeClusterResourceSet that can generate a ClusterResourceSet object, all its own ancillary objects: +// - the Secret/ConfigMap defining resources +// - the bindings that are created when a ClusterResourceSet is applied to a cluster +func NewFakeClusterResourceSet(namespace, name string) *FakeClusterResourceSet { + return &FakeClusterResourceSet{ + name: name, + namespace: namespace, + } +} + +func (f *FakeClusterResourceSet) WithSecret(name string) *FakeClusterResourceSet { + f.secrets = append(f.secrets, &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: f.namespace, + }, + // No data are required for the sake of move tests + }) + return f +} + +func (f *FakeClusterResourceSet) WithConfigMap(name string) *FakeClusterResourceSet { + f.configMaps = append(f.configMaps, &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: f.namespace, + }, + // No data are required for the sake of move tests + }) + return f +} + +func (f *FakeClusterResourceSet) ApplyToCluster(cluster *clusterv1.Cluster) *FakeClusterResourceSet { + if f.namespace != cluster.Namespace { + panic("A ClusterResourceSet can only be applied to a cluster in the same namespace") + } + f.clusters = append(f.clusters, cluster) + return f +} + +func (f *FakeClusterResourceSet) Objs() []runtime.Object { + crs := &addonsv1alpha3.ClusterResourceSet{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterResourceSet", + APIVersion: addonsv1alpha3.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: f.name, + Namespace: f.namespace, + }, + Spec: addonsv1alpha3.ClusterResourceSetSpec{ + Resources: []addonsv1alpha3.ResourceRef{}, + }, + } + + // Ensure the ClusterResourceSet gets a UID to be used by dependant objects for creating OwnerReferences. + setUID(crs) + + objs := []runtime.Object{crs} + + // Ensures all the resources of type Secret are created and listed as a ClusterResourceSet resources + for i := range f.secrets { + secret := f.secrets[i] + + // secrets are owned by the ClusterResourceSet / ownership set by the ClusterResourceSet controller + secret.SetOwnerReferences([]metav1.OwnerReference{{ + APIVersion: crs.APIVersion, + Kind: crs.Kind, + Name: crs.Name, + UID: crs.UID, + }}) + + crs.Spec.Resources = append(crs.Spec.Resources, addonsv1alpha3.ResourceRef{ + Name: secret.Name, + Kind: secret.Kind, + }) + + objs = append(objs, secret) + } + + // Ensures all the resources of type ConfigMap are created and listed as a ClusterResourceSet resources + for i := range f.configMaps { + configMap := f.configMaps[i] + + // configMap are owned by the ClusterResourceSet / ownership set by the ClusterResourceSet controller + configMap.SetOwnerReferences([]metav1.OwnerReference{{ + APIVersion: crs.APIVersion, + Kind: crs.Kind, + Name: crs.Name, + UID: crs.UID, + }}) + + crs.Spec.Resources = append(crs.Spec.Resources, addonsv1alpha3.ResourceRef{ + Name: configMap.Name, + Kind: configMap.Kind, + }) + + objs = append(objs, configMap) + } + + // Ensures all the binding with the clusters where resources are applied. + if len(f.clusters) > 0 { + binding := &addonsv1alpha3.ClusterResourceSetBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterResourceSetBinding", + APIVersion: addonsv1alpha3.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: f.name, + Namespace: f.namespace, + }, + Spec: addonsv1alpha3.ClusterResourceSetBindingSpec{ + Bindings: map[string]addonsv1alpha3.ResourcesSetBinding{}, + }, + } + + binding.SetOwnerReferences([]metav1.OwnerReference{ + // binding are owned by the ClusterResourceSet / ownership set by the ClusterResourceSet controller + { + APIVersion: crs.APIVersion, + Kind: crs.Kind, + Name: crs.Name, + UID: crs.UID, + }, + }) + + objs = append(objs, binding) + + // creates map entries for each cluster + for _, cluster := range f.clusters { + // binding are owned by the Cluster / ownership set by the ClusterResourceSet controller + binding.SetOwnerReferences(append(binding.OwnerReferences, metav1.OwnerReference{ + APIVersion: cluster.APIVersion, + Kind: cluster.Kind, + Name: cluster.Name, + UID: cluster.UID, + })) + + binding.Spec.Bindings[cluster.Name] = addonsv1alpha3.ResourcesSetBinding{ + Resources: map[string]addonsv1alpha3.ResourceBinding{}, + } + + // creates map entries for each cluster/resource of type Secret + for _, secret := range f.secrets { + key := fmt.Sprintf("%s/%s", secret.Kind, secret.Name) + binding.Spec.Bindings[cluster.Name].Resources[key] = addonsv1alpha3.ResourceBinding{} + } + + // creates map entries for each cluster/resource of type ConfigMap + for _, configMap := range f.configMaps { + key := fmt.Sprintf("%s/%s", configMap.Kind, configMap.Name) + binding.Spec.Bindings[cluster.Name].Resources[key] = addonsv1alpha3.ResourceBinding{} + } + } + } + + // Ensure all the objects gets UID. + // Nb. This adds UID to all the objects; it does not change the UID explicitly sets in advance for the objects involved in the object graphs. + for _, o := range objs { + setUID(o) + } + + return objs +} + +func SelectClusterObj(objs []runtime.Object, namespace, name string) *clusterv1.Cluster { + for _, o := range objs { + if o.GetObjectKind().GroupVersionKind().GroupKind() != clusterv1.GroupVersion.WithKind("Cluster").GroupKind() { + continue + } + + accessor, err := meta.Accessor(o) + if err != nil { + panic(fmt.Sprintf("failed to get accessor for %s: %v", o.GetObjectKind(), err)) + } + + if accessor.GetName() == name && accessor.GetNamespace() == namespace { + cluster := &clusterv1.Cluster{} + if err := FakeScheme.Convert(o, cluster, nil); err != nil { + panic(fmt.Sprintf("failed to convert %s to cluster: %v", o.GetObjectKind(), err)) + } + return cluster + } + } + return nil +} + // setUID assigns a UID to the object, so test objects are uniquely identified. // NB. In order to make debugging easier we are using a human readable, deterministic string (instead of a random UID). func setUID(obj runtime.Object) { @@ -957,6 +1161,8 @@ func FakeCRDList() []*apiextensionslv1.CustomResourceDefinition { FakeCustomResourceDefinition(clusterv1.GroupVersion.Group, "MachineDeployment", version), FakeCustomResourceDefinition(clusterv1.GroupVersion.Group, "MachineSet", version), FakeCustomResourceDefinition(expv1.GroupVersion.Group, "MachinePool", version), + FakeCustomResourceDefinition(addonsv1alpha3.GroupVersion.Group, "ClusterResourceSet", version), + FakeCustomResourceDefinition(addonsv1alpha3.GroupVersion.Group, "ClusterResourceSetBinding", version), FakeCustomResourceDefinition(fakecontrolplane.GroupVersion.Group, "GenericControlPlane", version), FakeCustomResourceDefinition(fakeinfrastructure.GroupVersion.Group, "GenericInfrastructureCluster", version), FakeCustomResourceDefinition(fakeinfrastructure.GroupVersion.Group, "GenericInfrastructureMachine", version), diff --git a/cmd/clusterctl/internal/test/fake_proxy.go b/cmd/clusterctl/internal/test/fake_proxy.go index 18b6c31776bc..c59ae91934cc 100644 --- a/cmd/clusterctl/internal/test/fake_proxy.go +++ b/cmd/clusterctl/internal/test/fake_proxy.go @@ -28,6 +28,7 @@ import ( fakebootstrap "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test/providers/bootstrap" fakecontrolplane "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test/providers/controlplane" fakeinfrastructure "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test/providers/infrastructure" + addonsv1alpha3 "sigs.k8s.io/cluster-api/exp/addons/api/v1alpha3" expv1 "sigs.k8s.io/cluster-api/exp/api/v1alpha3" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -47,6 +48,7 @@ func init() { _ = clusterctlv1.AddToScheme(FakeScheme) _ = clusterv1.AddToScheme(FakeScheme) _ = expv1.AddToScheme(FakeScheme) + _ = addonsv1alpha3.AddToScheme(FakeScheme) _ = apiextensionslv1.AddToScheme(FakeScheme) _ = fakebootstrap.AddToScheme(FakeScheme)