Skip to content

Commit b3fff36

Browse files
authored
Merge pull request #5420 from sbueringer/pr-fix-clusterctl-delete
🐛 Fix clusterctl delete when deleting providers with cluster-wide resources
2 parents bb4fa55 + f5a9d76 commit b3fff36

File tree

3 files changed

+90
-1
lines changed

3 files changed

+90
-1
lines changed

cmd/clusterctl/client/cluster/proxy.go

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,18 @@ import (
2222
"time"
2323

2424
"github.com/pkg/errors"
25+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2526
apierrors "k8s.io/apimachinery/pkg/api/errors"
2627
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2728
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
29+
"k8s.io/apimachinery/pkg/runtime/schema"
30+
"k8s.io/apimachinery/pkg/util/sets"
2831
utilversion "k8s.io/apimachinery/pkg/util/version"
2932
"k8s.io/client-go/discovery"
3033
"k8s.io/client-go/kubernetes"
3134
"k8s.io/client-go/rest"
3235
"k8s.io/client-go/tools/clientcmd"
36+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
3337
"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/scheme"
3438
"sigs.k8s.io/cluster-api/version"
3539
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -56,7 +60,10 @@ type Proxy interface {
5660
// CheckClusterAvailable checks if a a cluster is available and reachable.
5761
CheckClusterAvailable() error
5862

59-
// ListResources returns all the Kubernetes objects with the given labels existing the listed namespaces.
63+
// ListResources lists namespaced and cluster-wide resources matching the labels. Namespaced resources are only listed
64+
// in the given namespaces.
65+
// If labels contains the ProviderLabelName label, CRDs of other providers are excluded.
66+
// This is done to avoid errors when listing resources of providers which have already been deleted.
6067
ListResources(labels map[string]string, namespaces ...string) ([]unstructured.Unstructured, error)
6168

6269
// GetContexts returns the list of contexts in kubeconfig which begin with prefix.
@@ -198,6 +205,17 @@ func (k *proxy) CheckClusterAvailable() error {
198205
return nil
199206
}
200207

208+
// ListResources lists namespaced and cluster-wide resources matching the labels. Namespaced resources are only listed
209+
// in the given namespaces.
210+
// If labels contains the ProviderLabelName label, CRDs of other providers are excluded.
211+
// This is done to avoid errors when listing resources of providers which have already been deleted.
212+
// For example:
213+
// * The AWS provider has already been deleted, but there are still cluster-wide resources of AWSClusterControllerIdentity.
214+
// * The AWSClusterControllerIdentity resources are still stored in an older version (e.g. v1alpha4, when the preferred
215+
// version is v1beta1)
216+
// * If we now want to delete e.g. the kubeadm bootstrap provider, we cannot list AWSClusterControllerIdentity resources
217+
// as the conversion would fail, because the AWS controller hosting the conversion webhook has already been deleted.
218+
// * Thus we exclude resources of other providers if we detect that ListResources is called to list resources of a provider.
201219
func (k *proxy) ListResources(labels map[string]string, namespaces ...string) ([]unstructured.Unstructured, error) {
202220
cs, err := k.newClientSet()
203221
if err != nil {
@@ -219,6 +237,31 @@ func (k *proxy) ListResources(labels map[string]string, namespaces ...string) ([
219237
return nil, errors.Wrap(err, "failed to list api resources")
220238
}
221239

240+
// If labels indicates that resources of a specific provider should be listed, exclude CRDs of other providers.
241+
crdsToExclude := sets.String{}
242+
if providerName, ok := labels[clusterv1.ProviderLabelName]; ok {
243+
// List all CRDs in the cluster.
244+
crdList := &apiextensionsv1.CustomResourceDefinitionList{}
245+
if err := retryWithExponentialBackoff(newReadBackoff(), func() error {
246+
return c.List(ctx, crdList)
247+
}); err != nil {
248+
return nil, errors.Wrap(err, "failed to list CRDs")
249+
}
250+
251+
// Exclude CRDs of other providers.
252+
for _, crd := range crdList.Items {
253+
if v, ok := crd.Labels[clusterv1.ProviderLabelName]; ok && v != providerName {
254+
for _, version := range crd.Spec.Versions {
255+
crdsToExclude.Insert(metav1.GroupVersionKind{
256+
Group: crd.Spec.Group,
257+
Version: version.Name,
258+
Kind: crd.Spec.Names.Kind,
259+
}.String())
260+
}
261+
}
262+
}
263+
}
264+
222265
// Select resources with list and delete methods (list is required by this method, delete by the callers of this method)
223266
resourceList = discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"list", "delete"}}, resourceList)
224267

@@ -231,6 +274,19 @@ func (k *proxy) ListResources(labels map[string]string, namespaces ...string) ([
231274
continue
232275
}
233276

277+
// Continue if the resource is an excluded CRD.
278+
gv, err := schema.ParseGroupVersion(resourceGroup.GroupVersion)
279+
if err != nil {
280+
return nil, errors.Wrapf(err, "failed to parse GroupVersion")
281+
}
282+
if crdsToExclude.Has(metav1.GroupVersionKind{
283+
Group: gv.Group,
284+
Version: gv.Version,
285+
Kind: resourceKind.Kind,
286+
}.String()) {
287+
continue
288+
}
289+
234290
// List all the object instances of this resourceKind with the given labels
235291
if resourceKind.Namespaced {
236292
for _, namespace := range namespaces {

test/e2e/clusterctl_upgrade.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,13 @@ func ClusterctlUpgradeSpec(ctx context.Context, inputGetter func() ClusterctlUpg
362362
Deleter: managementClusterProxy.GetClient(),
363363
Name: testNamespace.Name,
364364
})
365+
366+
Byf("Deleting providers")
367+
clusterctl.Delete(ctx, clusterctl.DeleteInput{
368+
LogFolder: filepath.Join(input.ArtifactFolder, "clusters", managementClusterResources.Cluster.Name),
369+
ClusterctlConfigPath: input.ClusterctlConfigPath,
370+
KubeconfigPath: managementClusterProxy.GetKubeconfigPath(),
371+
})
365372
}
366373
testCancelWatches()
367374
}

test/framework/clusterctl/client.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,32 @@ func Upgrade(ctx context.Context, input UpgradeInput) {
137137
Expect(err).ToNot(HaveOccurred(), "failed to run clusterctl upgrade")
138138
}
139139

140+
// DeleteInput is the input for Delete.
141+
type DeleteInput struct {
142+
LogFolder string
143+
ClusterctlConfigPath string
144+
KubeconfigPath string
145+
}
146+
147+
// Delete calls clusterctl delete --all.
148+
func Delete(_ context.Context, input DeleteInput) {
149+
log.Logf("clusterctl delete --all")
150+
151+
deleteOpts := clusterctlclient.DeleteOptions{
152+
Kubeconfig: clusterctlclient.Kubeconfig{
153+
Path: input.KubeconfigPath,
154+
Context: "",
155+
},
156+
DeleteAll: true,
157+
}
158+
159+
clusterctlClient, log := getClusterctlClientWithLogger(input.ClusterctlConfigPath, "clusterctl-delete.log", input.LogFolder)
160+
defer log.Close()
161+
162+
err := clusterctlClient.Delete(deleteOpts)
163+
Expect(err).ToNot(HaveOccurred(), "failed to run clusterctl upgrade")
164+
}
165+
140166
// ConfigClusterInput is the input for ConfigCluster.
141167
type ConfigClusterInput struct {
142168
LogFolder string

0 commit comments

Comments
 (0)