diff --git a/cmd/clusterctl/client/cluster/proxy.go b/cmd/clusterctl/client/cluster/proxy.go index 37339e5fdf92..d456b0c1b2f6 100644 --- a/cmd/clusterctl/client/cluster/proxy.go +++ b/cmd/clusterctl/client/cluster/proxy.go @@ -22,14 +22,19 @@ import ( "time" "github.com/pkg/errors" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" utilversion "k8s.io/apimachinery/pkg/util/version" "k8s.io/client-go/discovery" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha4" + clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/scheme" "sigs.k8s.io/cluster-api/version" "sigs.k8s.io/controller-runtime/pkg/client" @@ -150,6 +155,12 @@ func (k *proxy) NewClient() (client.Client, error) { return c, nil } +// ListResources lists namespaced and cluster-wide resources for a component matching the labels. Namespaced resources are only listed +// in the given namespaces. +// Please note that we are not returning resources for the component's CRD (e.g. we are not returning +// Certificates for cert-manager, Clusters for CAPI, AWSCluster for CAPA and so on). +// This is done to avoid errors when listing resources of providers which have already been deleted/scaled down to 0 replicas/with +// malfunctioning webhooks. func (k *proxy) ListResources(labels map[string]string, namespaces ...string) ([]unstructured.Unstructured, error) { cs, err := k.newClientSet() if err != nil { @@ -171,6 +182,30 @@ func (k *proxy) ListResources(labels map[string]string, namespaces ...string) ([ return nil, errors.Wrap(err, "failed to list api resources") } + // Exclude from discovery the objects from the cert-manager/provider's CRDs. + // Those objects are not part of the components, and they will eventually be removed when removing the CRD definition. + crdsToExclude := sets.String{} + + crdList := &apiextensionsv1.CustomResourceDefinitionList{} + if err := retryWithExponentialBackoff(newReadBackoff(), func() error { + return c.List(ctx, crdList) + }); err != nil { + return nil, errors.Wrap(err, "failed to list CRDs") + } + for _, crd := range crdList.Items { + component, isCoreComponent := labels[clusterctlv1.ClusterctlCoreLabelName] + _, isProviderResource := crd.Labels[clusterv1.ProviderLabelName] + if (isCoreComponent && component == clusterctlv1.ClusterctlCoreLabelCertManagerValue) || isProviderResource { + for _, version := range crd.Spec.Versions { + crdsToExclude.Insert(metav1.GroupVersionKind{ + Group: crd.Spec.Group, + Version: version.Name, + Kind: crd.Spec.Names.Kind, + }.String()) + } + } + } + // Select resources with list and delete methods (list is required by this method, delete by the callers of this method) resourceList = discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"list", "delete"}}, resourceList) @@ -183,6 +218,19 @@ func (k *proxy) ListResources(labels map[string]string, namespaces ...string) ([ continue } + // Continue if the resource is an excluded CRD. + gv, err := schema.ParseGroupVersion(resourceGroup.GroupVersion) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse GroupVersion") + } + if crdsToExclude.Has(metav1.GroupVersionKind{ + Group: gv.Group, + Version: gv.Version, + Kind: resourceKind.Kind, + }.String()) { + continue + } + // List all the object instances of this resourceKind with the given labels if resourceKind.Namespaced { for _, namespace := range namespaces {