Skip to content

Commit df2ab2f

Browse files
authored
Merge pull request #4598 from fabriziopandini/clusterctl-move-infrastructure-secrets
🌱 clusterctl move should consider Secrets from provider's namespace
2 parents a74b6a6 + c4bf2e7 commit df2ab2f

File tree

5 files changed

+123
-51
lines changed

5 files changed

+123
-51
lines changed

cmd/clusterctl/client/cluster/mover.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func (o *objectMover) Move(namespace string, toCluster Client, dryRun bool) erro
6060
log.Info("********************************************************")
6161
}
6262

63-
objectGraph := newObjectGraph(o.fromProxy)
63+
objectGraph := newObjectGraph(o.fromProxy, o.fromProviderInventory)
6464

6565
// checks that all the required providers in place in the target cluster.
6666
if !o.dryRun {

cmd/clusterctl/client/cluster/objectgraph.go

+28-6
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,17 @@ func (n *node) isSoftOwnedBy(other *node) bool {
105105

106106
// objectGraph manages the Kubernetes object graph that is generated during the discovery phase for the move operation.
107107
type objectGraph struct {
108-
proxy Proxy
109-
uidToNode map[types.UID]*node
110-
types map[string]*discoveryTypeInfo
108+
proxy Proxy
109+
providerInventory InventoryClient
110+
uidToNode map[types.UID]*node
111+
types map[string]*discoveryTypeInfo
111112
}
112113

113-
func newObjectGraph(proxy Proxy) *objectGraph {
114+
func newObjectGraph(proxy Proxy, providerInventory InventoryClient) *objectGraph {
114115
return &objectGraph{
115-
proxy: proxy,
116-
uidToNode: map[types.UID]*node{},
116+
proxy: proxy,
117+
providerInventory: providerInventory,
118+
uidToNode: map[types.UID]*node{},
117119
}
118120
}
119121

@@ -309,6 +311,26 @@ func (o *objectGraph) Discovery(namespace string) error {
309311
return err
310312
}
311313

314+
// if we are discovering Secrets, also secrets from the providers namespace should be included.
315+
if discoveryType.typeMeta.GetObjectKind().GroupVersionKind().GroupKind() == corev1.SchemeGroupVersion.WithKind("SecretList").GroupKind() {
316+
providers, err := o.providerInventory.List()
317+
if err != nil {
318+
return err
319+
}
320+
for _, p := range providers.Items {
321+
if p.Type == string(clusterctlv1.InfrastructureProviderType) {
322+
providerNamespaceSelector := []client.ListOption{client.InNamespace(p.Namespace)}
323+
providerNamespaceSecretList := new(unstructured.UnstructuredList)
324+
if err := retryWithExponentialBackoff(discoveryBackoff, func() error {
325+
return getObjList(o.proxy, typeMeta, providerNamespaceSelector, providerNamespaceSecretList)
326+
}); err != nil {
327+
return err
328+
}
329+
objList.Items = append(objList.Items, providerNamespaceSecretList.Items...)
330+
}
331+
}
332+
}
333+
312334
if len(objList.Items) == 0 {
313335
continue
314336
}

cmd/clusterctl/client/cluster/objectgraph_test.go

+65-32
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,11 @@ import (
2222
"testing"
2323

2424
. "github.com/onsi/gomega"
25-
2625
"github.com/pkg/errors"
27-
2826
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2927
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3028
"k8s.io/apimachinery/pkg/types"
29+
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
3130
"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
3231
"sigs.k8s.io/controller-runtime/pkg/client"
3332
)
@@ -64,7 +63,7 @@ func TestObjectGraph_getDiscoveryTypeMetaList(t *testing.T) {
6463
t.Run(tt.name, func(t *testing.T) {
6564
g := NewWithT(t)
6665

67-
graph := newObjectGraph(tt.fields.proxy)
66+
graph := newObjectGraph(tt.fields.proxy, nil)
6867
err := graph.getDiscoveryTypes()
6968
if tt.wantErr {
7069
g.Expect(err).To(HaveOccurred())
@@ -304,7 +303,7 @@ func TestObjectGraph_addObj(t *testing.T) {
304303
}
305304
for _, tt := range tests {
306305
t.Run(tt.name, func(t *testing.T) {
307-
graph := newObjectGraph(nil)
306+
graph := newObjectGraph(nil, nil)
308307
for _, o := range tt.args.objs {
309308
graph.addObj(o)
310309
}
@@ -1012,44 +1011,47 @@ var objectGraphsTests = []struct {
10121011
},
10131012
},
10141013
{
1015-
name: "Cluster and Global + Namespaced External Objects",
1014+
// NOTE: External objects are CRD types installed by clusterctl, but not directly related with the CAPI hierarchy of objects. e.g. IPAM claims.
1015+
name: "Namespaced External Objects with force move label",
10161016
args: objectGraphTestArgs{
1017-
func() []client.Object {
1018-
objs := []client.Object{}
1019-
objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...)
1020-
objs = append(objs, test.NewFakeExternalObject("ns1", "externalObject1").Objs()...)
1021-
objs = append(objs, test.NewFakeExternalObject("", "externalObject2").Objs()...)
1022-
1023-
return objs
1024-
}(),
1017+
objs: test.NewFakeExternalObject("ns1", "externalObject1").Objs(),
10251018
},
10261019
want: wantGraph{
10271020
nodes: map[string]wantGraphItem{
10281021
"external.cluster.x-k8s.io/v1alpha4, Kind=GenericExternalObject, ns1/externalObject1": {},
1029-
"external.cluster.x-k8s.io/v1alpha4, Kind=GenericExternalObject, /externalObject2": {},
1030-
"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {},
1031-
"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster1": {
1032-
owners: []string{
1033-
"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
1034-
},
1035-
},
1036-
"/v1, Kind=Secret, ns1/cluster1-ca": {
1037-
softOwners: []string{
1038-
"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref
1039-
},
1040-
},
1041-
"/v1, Kind=Secret, ns1/cluster1-kubeconfig": {
1042-
owners: []string{
1043-
"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
1044-
},
1045-
},
1022+
},
1023+
},
1024+
},
1025+
{
1026+
// NOTE: External objects are CRD types installed by clusterctl, but not directly related with the CAPI hierarchy of objects. e.g. IPAM claims.
1027+
name: "Global External Objects with force move label",
1028+
args: objectGraphTestArgs{
1029+
objs: test.NewFakeExternalObject("", "externalObject1").Objs(),
1030+
},
1031+
want: wantGraph{
1032+
nodes: map[string]wantGraphItem{
1033+
"external.cluster.x-k8s.io/v1alpha4, Kind=GenericExternalObject, /externalObject1": {},
1034+
},
1035+
},
1036+
},
1037+
{
1038+
// NOTE: Infrastructure providers global credentials are going to be stored in Secrets in the provider's namespaces.
1039+
name: "Secrets from provider's namespace",
1040+
args: objectGraphTestArgs{
1041+
objs: []client.Object{
1042+
test.NewSecret("infra1", "credentials"),
1043+
},
1044+
},
1045+
want: wantGraph{
1046+
nodes: map[string]wantGraphItem{
1047+
"/v1, Kind=Secret, infra1/credentials": {},
10461048
},
10471049
},
10481050
},
10491051
}
10501052

10511053
func getDetachedObjectGraphWihObjs(objs []client.Object) (*objectGraph, error) {
1052-
graph := newObjectGraph(nil) // detached from any cluster
1054+
graph := newObjectGraph(nil, nil) // detached from any cluster
10531055
for _, o := range objs {
10541056
u := &unstructured.Unstructured{}
10551057
if err := test.FakeScheme.Convert(o, u, nil); err != nil {
@@ -1084,7 +1086,10 @@ func getObjectGraphWithObjs(objs []client.Object) *objectGraph {
10841086
fromProxy.WithObjs(o)
10851087
}
10861088

1087-
return newObjectGraph(fromProxy)
1089+
fromProxy.WithProviderInventory("infra1", clusterctlv1.InfrastructureProviderType, "v1.2.3", "infra1")
1090+
inventory := newInventoryClient(fromProxy, fakePollImmediateWaiter)
1091+
1092+
return newObjectGraph(fromProxy, inventory)
10881093
}
10891094

10901095
func getFakeProxyWithCRDs() *test.FakeProxy {
@@ -1225,6 +1230,34 @@ func TestObjectGraph_DiscoveryByNamespace(t *testing.T) {
12251230
},
12261231
},
12271232
},
1233+
{
1234+
// NOTE: External objects are CRD types installed by clusterctl, but not directly related with the CAPI hierarchy of objects. e.g. IPAM claims.
1235+
name: "Namespaced External Objects with force move label",
1236+
args: args{
1237+
namespace: "ns1", // read only from ns1
1238+
objs: test.NewFakeExternalObject("ns1", "externalObject1").Objs(), // Fake external object with
1239+
},
1240+
want: wantGraph{
1241+
nodes: map[string]wantGraphItem{
1242+
"external.cluster.x-k8s.io/v1alpha4, Kind=GenericExternalObject, ns1/externalObject1": {},
1243+
},
1244+
},
1245+
},
1246+
{
1247+
// NOTE: Infrastructure providers global credentials are going to be stored in Secrets in the provider's namespaces.
1248+
name: "Secrets from provider's namespace (e.g. credentials) should always be read",
1249+
args: args{
1250+
namespace: "ns1", // read only from ns1
1251+
objs: []client.Object{
1252+
test.NewSecret("infra1", "credentials"), // a secret in infra1 namespace, where an infrastructure provider is installed
1253+
},
1254+
},
1255+
want: wantGraph{
1256+
nodes: map[string]wantGraphItem{
1257+
"/v1, Kind=Secret, infra1/credentials": {},
1258+
},
1259+
},
1260+
},
12281261
}
12291262

12301263
for _, tt := range tests {

cmd/clusterctl/internal/test/fake_objects.go

+16
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,22 @@ func (f *FakeExternalObject) Objs() []client.Object {
11431143
return []client.Object{externalObj}
11441144
}
11451145

1146+
// NewSecret generates a new secret with the given namespace and name.
1147+
func NewSecret(namespace, name string) *corev1.Secret {
1148+
s := &corev1.Secret{
1149+
TypeMeta: metav1.TypeMeta{
1150+
APIVersion: corev1.SchemeGroupVersion.String(),
1151+
Kind: "Secret",
1152+
},
1153+
ObjectMeta: metav1.ObjectMeta{
1154+
Name: name,
1155+
Namespace: namespace,
1156+
},
1157+
}
1158+
setUID(s)
1159+
return s
1160+
}
1161+
11461162
// SelectClusterObj finds and returns a Cluster with the given name and namespace, if any.
11471163
func SelectClusterObj(objs []client.Object, namespace, name string) *clusterv1.Cluster {
11481164
for _, o := range objs {

docs/book/src/clusterctl/provider-contract.md

+13-12
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,18 @@ functioning of `clusterctl` when using non-compliant component YAML or cluster t
311311

312312
Provider authors should be aware that `clusterctl move` command implements a discovery mechanism that considers:
313313

314-
* All the objects of Kind defined in one of the CRDs installed by clusterctl using `clusterctl init`.
315-
* `Secret` and `ConfigMap` objects.
316-
* The `OwnerReference` chain of the above objects.
317-
* Any object of Kind in which its CRD has the "move" label (`clusterctl.cluster.x-k8s.io/move`) attached to it.
314+
* All the objects of Kind defined in one of the CRDs installed by clusterctl using `clusterctl init` from the namespace being moved.
315+
* `ConfigMap` objects from the namespace being moved.
316+
* `Secret` objects from the namespace being moved and from the namespaces where infrastructure providers are installed.
317+
318+
`clusterctl move` does NOT consider any objects:
319+
320+
* Not included in the set of objects defined above.
321+
* Included in the set of objects defined above, but not:
322+
* Directly or indirectly linked to a `Cluster` object through the `OwnerReference` chain.
323+
* Directly or indirectly linked to a `ClusterResourceSet` object through the `OwnerReference` chain.
324+
* Explicitly required to move via the "move" label (`clusterctl.cluster.x-k8s.io/move`) attached to the object or to
325+
the CRD definition.
318326

319327
<aside class="note warning">
320328

@@ -324,15 +332,8 @@ When using the "move" label, if the CRD is a global resource, the object is copi
324332

325333
</aside>
326334

327-
`clusterctl move` does NOT consider any objects:
328-
329-
* Not included in the set of objects defined above.
330-
* Included in the set of objects defined above, but not:
331-
* Directly or indirectly linked to a `Cluster` object through the `OwnerReference` chain.
332-
* Directly or indirectly linked to a `ClusterResourceSet` object through the `OwnerReference` chain.
333-
334335
If moving some of excluded object is required, the provider authors should create documentation describing the
335-
the exact move sequence to be executed by the user.
336+
exact move sequence to be executed by the user.
336337

337338
Additionally, provider authors should be aware that `clusterctl move` assumes all the provider's Controllers respect the
338339
`Cluster.Spec.Paused` field introduced in the v1alpha3 Cluster API specification.

0 commit comments

Comments
 (0)