Skip to content

Commit 5d16af9

Browse files
authored
Merge pull request #405 from Danil-Grigorev/plugin-delete
✨ Plugin delete subcommand implementation
2 parents c6c4684 + 06222f7 commit 5d16af9

11 files changed

+413
-28
lines changed

api/v1alpha2/addonprovider_types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,6 @@ type AddonProviderList struct {
5656
}
5757

5858
func init() {
59-
objectTypes = append(objectTypes, &AddonProvider{}, &AddonProviderList{})
59+
ProviderLists = append(ProviderLists, &AddonProviderList{})
60+
Providers = append(Providers, &AddonProvider{})
6061
}

api/v1alpha2/bootstrapprovider_types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,6 @@ type BootstrapProviderList struct {
5656
}
5757

5858
func init() {
59-
objectTypes = append(objectTypes, &BootstrapProvider{}, &BootstrapProviderList{})
59+
ProviderLists = append(ProviderLists, &BootstrapProviderList{})
60+
Providers = append(Providers, &BootstrapProvider{})
6061
}

api/v1alpha2/controlplaneprovider_types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,6 @@ type ControlPlaneProviderList struct {
5656
}
5757

5858
func init() {
59-
objectTypes = append(objectTypes, &ControlPlaneProvider{}, &ControlPlaneProviderList{})
59+
ProviderLists = append(ProviderLists, &ControlPlaneProviderList{})
60+
Providers = append(Providers, &ControlPlaneProvider{})
6061
}

api/v1alpha2/coreprovider_types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,6 @@ type CoreProviderList struct {
5656
}
5757

5858
func init() {
59-
objectTypes = append(objectTypes, &CoreProvider{}, &CoreProviderList{})
59+
ProviderLists = append(ProviderLists, &CoreProviderList{})
60+
Providers = append(Providers, &CoreProvider{})
6061
}

api/v1alpha2/groupversion_info.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,25 @@ var (
3535
// AddToScheme adds the types in this group-version to the given scheme.
3636
AddToScheme = SchemeBuilder.AddToScheme
3737

38-
objectTypes = []runtime.Object{}
38+
Providers = []GenericProvider{}
39+
ProviderLists = []GenericProviderList{}
3940
)
4041

4142
// Adds the list of known types to api.Scheme.
4243
func addKnownTypes(scheme *runtime.Scheme) error {
43-
scheme.AddKnownTypes(GroupVersion, objectTypes...)
4444
metav1.AddToGroupVersion(scheme, GroupVersion)
4545

46+
for _, p := range Providers {
47+
if obj, ok := p.(runtime.Object); ok {
48+
scheme.AddKnownTypes(GroupVersion, obj)
49+
}
50+
}
51+
52+
for _, p := range ProviderLists {
53+
if obj, ok := p.(runtime.Object); ok {
54+
scheme.AddKnownTypes(GroupVersion, obj)
55+
}
56+
}
57+
4658
return nil
4759
}

api/v1alpha2/infrastructureprovider_types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,6 @@ type InfrastructureProviderList struct {
5656
}
5757

5858
func init() {
59-
objectTypes = append(objectTypes, &InfrastructureProvider{}, &InfrastructureProviderList{})
59+
ProviderLists = append(ProviderLists, &InfrastructureProviderList{})
60+
Providers = append(Providers, &InfrastructureProvider{})
6061
}

api/v1alpha2/ipamprovider_types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,6 @@ type IPAMProviderList struct {
5656
}
5757

5858
func init() {
59-
objectTypes = append(objectTypes, &IPAMProvider{}, &IPAMProviderList{})
59+
ProviderLists = append(ProviderLists, &IPAMProviderList{})
60+
Providers = append(Providers, &IPAMProvider{})
6061
}

cmd/plugin/cmd/delete.go

Lines changed: 221 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,40 @@ package cmd
1818

1919
import (
2020
"context"
21+
"fmt"
22+
"strings"
23+
"time"
2124

2225
"github.com/go-errors/errors"
2326
"github.com/spf13/cobra"
27+
corev1 "k8s.io/api/core/v1"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/fields"
30+
"k8s.io/apimachinery/pkg/util/wait"
31+
"k8s.io/klog/v2/klogr"
32+
33+
kerrors "k8s.io/apimachinery/pkg/util/errors"
34+
35+
"k8s.io/apimachinery/pkg/api/meta"
36+
ctrl "sigs.k8s.io/controller-runtime"
37+
38+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
39+
apierrors "k8s.io/apimachinery/pkg/api/errors"
40+
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
41+
42+
operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"
2443
)
2544

2645
type deleteOptions struct {
2746
kubeconfig string
2847
kubeconfigContext string
29-
coreProvider string
48+
coreProvider bool
3049
bootstrapProviders []string
3150
controlPlaneProviders []string
3251
infrastructureProviders []string
3352
ipamProviders []string
53+
addonProviders []string
3454
// runtimeExtensionProviders []string
35-
addonProviders []string
3655
includeNamespace bool
3756
includeCRDs bool
3857
deleteAll bool
@@ -63,7 +82,7 @@ var deleteCmd = &cobra.Command{
6382
# Important! As a consequence of this operation, all the corresponding resources managed by
6483
# the AWS infrastructure provider and Cluster API Providers are orphaned and there might be
6584
# ongoing costs incurred as a result of this.
66-
capioperator delete --core cluster-api --infrastructure aws
85+
capioperator delete --core --infrastructure aws
6786
6887
# Delete the AWS infrastructure provider and related CRDs. Please note that this forces deletion of
6988
# all the related objects (e.g. AWSClusters, AWSMachines etc.).
@@ -98,20 +117,20 @@ func init() {
98117
deleteCmd.Flags().BoolVar(&deleteOpts.includeCRDs, "include-crd", false,
99118
"Forces the deletion of the provider's CRDs (and of all the related objects)")
100119

101-
deleteCmd.Flags().StringVar(&deleteOpts.coreProvider, "core", "",
102-
"Core provider version (e.g. cluster-api:v1.1.5) to delete from the management cluster")
120+
deleteCmd.Flags().BoolVar(&deleteOpts.coreProvider, "core", false,
121+
"Core provider to delete from the management cluster. If not set, core provider is not removed. Cluster cannot have more then 1 core provider in total.")
103122
deleteCmd.Flags().StringSliceVarP(&deleteOpts.infrastructureProviders, "infrastructure", "i", nil,
104-
"Infrastructure providers and versions (e.g. aws:v0.5.0) to delete from the management cluster")
123+
"Infrastructure provider and namespace (e.g. aws:<namespace>) to delete from the management cluster")
105124
deleteCmd.Flags().StringSliceVarP(&deleteOpts.bootstrapProviders, "bootstrap", "b", nil,
106-
"Bootstrap providers and versions (e.g. kubeadm:v1.1.5) to delete from the management cluster")
125+
"Bootstrap provider and namespace (e.g. kubeadm:<namespace>) to delete from the management cluster")
107126
deleteCmd.Flags().StringSliceVarP(&deleteOpts.controlPlaneProviders, "control-plane", "c", nil,
108-
"ControlPlane providers and versions (e.g. kubeadm:v1.1.5) to delete from the management cluster")
127+
"ControlPlane provider and namespace (e.g. kubeadm:<namespace>) to delete from the management cluster")
109128
deleteCmd.Flags().StringSliceVar(&deleteOpts.ipamProviders, "ipam", nil,
110-
"IPAM providers and versions (e.g. infoblox:v0.0.1) to delete from the management cluster")
129+
"IPAM provider and namespace (e.g. infoblox:<namespace>) to delete from the management cluster")
111130
// deleteCmd.Flags().StringSliceVar(&deleteOpts.runtimeExtensionProviders, "runtime-extension", nil,
112131
// "Runtime extension providers and versions (e.g. test:v0.0.1) to delete from the management cluster")
113132
deleteCmd.Flags().StringSliceVar(&deleteOpts.addonProviders, "addon", nil,
114-
"Add-on providers and versions (e.g. helm:v0.1.0) to delete from the management cluster")
133+
"Add-on providers and versions (e.g. helm:<namespace>) to delete from the management cluster")
115134

116135
deleteCmd.Flags().BoolVar(&deleteOpts.deleteAll, "all", false,
117136
"Force deletion of all the providers")
@@ -122,11 +141,13 @@ func init() {
122141
func runDelete() error {
123142
ctx := context.Background()
124143

125-
hasProviderNames := (deleteOpts.coreProvider != "") ||
144+
ctrl.SetLogger(klogr.New())
145+
146+
hasProviderNames := deleteOpts.coreProvider ||
126147
(len(deleteOpts.bootstrapProviders) > 0) ||
127148
(len(deleteOpts.controlPlaneProviders) > 0) ||
128149
(len(deleteOpts.infrastructureProviders) > 0) ||
129-
// (len(deleteOpts.ipamProviders) > 0) ||
150+
(len(deleteOpts.ipamProviders) > 0) ||
130151
// (len(deleteOpts.runtimeExtensionProviders) > 0) ||
131152
(len(deleteOpts.addonProviders) > 0)
132153

@@ -138,9 +159,194 @@ func runDelete() error {
138159
return errors.New("At least one of --core, --bootstrap, --control-plane, --infrastructure, --ipam, --extension, --addon should be specified or the --all flag should be set")
139160
}
140161

141-
return deleteProvider(ctx, deleteOpts)
162+
if deleteOpts.kubeconfig == "" {
163+
deleteOpts.kubeconfig = GetKubeconfigLocation()
164+
}
165+
166+
cl, err := CreateKubeClient(deleteOpts.kubeconfig, deleteOpts.kubeconfigContext)
167+
if err != nil {
168+
return fmt.Errorf("unable to create client from kubeconfig flag %s with context %s: %w", deleteOpts.kubeconfig, deleteOpts.kubeconfigContext, err)
169+
}
170+
171+
group := &DeleteGroup{
172+
selectors: []fields.Set{},
173+
providers: []genericProviderList{},
174+
}
175+
errors := append([]error{},
176+
group.delete(&operatorv1.BootstrapProviderList{}, deleteOpts.bootstrapProviders...),
177+
group.delete(&operatorv1.ControlPlaneProviderList{}, deleteOpts.controlPlaneProviders...),
178+
group.delete(&operatorv1.InfrastructureProviderList{}, deleteOpts.infrastructureProviders...),
179+
group.delete(&operatorv1.IPAMProviderList{}, deleteOpts.ipamProviders...),
180+
group.delete(&operatorv1.AddonProviderList{}, deleteOpts.addonProviders...))
181+
182+
if deleteOpts.coreProvider {
183+
errors = append(errors, group.delete(&operatorv1.CoreProviderList{}, []string{""}...))
184+
}
185+
186+
if err := kerrors.NewAggregate(errors); err != nil {
187+
return err
188+
}
189+
190+
if deleteOpts.deleteAll {
191+
group.deleteAll()
192+
}
193+
194+
return group.execute(ctx, cl)
142195
}
143196

144-
func deleteProvider(ctx context.Context, opts *deleteOptions) error {
145-
return errors.New("Not implemented")
197+
type DeleteGroup struct {
198+
selectors []fields.Set
199+
providers []genericProviderList
200+
}
201+
202+
func (d *DeleteGroup) delete(providerType genericProviderList, names ...string) error {
203+
for _, provider := range names {
204+
selector, err := selectorFromProvider(provider)
205+
if err != nil {
206+
return fmt.Errorf("invalid provider format: %w", err)
207+
}
208+
209+
d.providers = append(d.providers, providerType)
210+
d.selectors = append(d.selectors, selector)
211+
}
212+
213+
return nil
214+
}
215+
216+
func (d *DeleteGroup) deleteAll() {
217+
for _, list := range operatorv1.ProviderLists {
218+
providerList, ok := list.(genericProviderList)
219+
if !ok {
220+
log.V(5).Info("Expected to get GenericProviderList")
221+
continue
222+
}
223+
224+
d.providers = append(d.providers, providerList)
225+
d.selectors = append(d.selectors, fields.Set{})
226+
}
227+
}
228+
229+
func (d *DeleteGroup) execute(ctx context.Context, cl ctrlclient.Client) error {
230+
opts := wait.Backoff{
231+
Duration: 500 * time.Millisecond,
232+
Factor: 1.5,
233+
Steps: 10,
234+
Jitter: 0.4,
235+
}
236+
237+
log.Info("Waiting for CAPI Operator manifests to be removed...")
238+
239+
if err := wait.ExponentialBackoff(opts, func() (bool, error) {
240+
ready := true
241+
for i := range d.providers {
242+
if done, err := deleteProviders(ctx, cl, d.providers[i], ctrlclient.MatchingFieldsSelector{
243+
Selector: fields.SelectorFromSet(d.selectors[i]),
244+
}); err != nil {
245+
return false, err
246+
} else {
247+
ready = ready && done
248+
}
249+
}
250+
251+
return ready, nil
252+
}); err != nil {
253+
return fmt.Errorf("cannot remove provider: %w", err)
254+
}
255+
256+
return nil
257+
}
258+
259+
func selectorFromProvider(provider string) (fields.Set, error) {
260+
var name, namespace string
261+
262+
parts := strings.Split(provider, ":")
263+
switch len(parts) {
264+
case 0 | 3:
265+
case 1:
266+
name = parts[0]
267+
case 2:
268+
name, namespace = parts[0], parts[1]
269+
default:
270+
return nil, fmt.Errorf("invalid provider format: %s", provider)
271+
}
272+
273+
selector := fields.Set{}
274+
275+
if name != "" {
276+
selector["metadata.name"] = name
277+
}
278+
279+
if namespace != "" {
280+
selector["metadata.namespace"] = namespace
281+
}
282+
283+
return selector, nil
284+
}
285+
286+
func deleteProviders(ctx context.Context, client ctrlclient.Client, providerList genericProviderList, selector ctrlclient.MatchingFieldsSelector) (bool, error) {
287+
//nolint:forcetypeassert
288+
providerList = providerList.DeepCopyObject().(genericProviderList)
289+
ready := true
290+
291+
gvks, _, err := scheme.ObjectKinds(providerList)
292+
if err != nil {
293+
log.Error(err, "Kind is not registered in provider list")
294+
return false, err
295+
}
296+
297+
gvk := gvks[0]
298+
299+
if err := client.List(ctx, providerList, selector); meta.IsNoMatchError(err) || apierrors.IsNotFound(err) {
300+
return true, nil
301+
} else if err != nil {
302+
log.Error(err, fmt.Sprintf("Unable to list providers to delete, %#v", err))
303+
return false, err
304+
}
305+
306+
for _, provider := range providerList.GetItems() {
307+
log.Info(fmt.Sprintf("Deleting %s %s/%s", provider.GetType(), provider.GetName(), provider.GetNamespace()))
308+
309+
provider, ok := provider.(genericProvider)
310+
if !ok {
311+
log.Info(fmt.Sprintf("Expected to get GenericProvider for %s", gvk))
312+
continue
313+
}
314+
315+
if err := client.DeleteAllOf(ctx, provider, ctrlclient.InNamespace(provider.GetNamespace())); err != nil {
316+
return false, fmt.Errorf("unable to issue delete for %s: %w", gvk, err)
317+
}
318+
319+
if deleteOpts.includeNamespace {
320+
if strings.HasPrefix(provider.GetNamespace(), "kube-") || provider.GetNamespace() == "default" {
321+
log.Info(fmt.Sprintf("Skipping system namespace %s", provider.GetNamespace()))
322+
continue
323+
}
324+
325+
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: provider.GetNamespace()}}
326+
if err := client.Delete(ctx, ns); ctrlclient.IgnoreNotFound(err) != nil {
327+
return false, fmt.Errorf("unable to issue delete for Namespace %s: %w", provider.GetNamespace(), err)
328+
}
329+
}
330+
}
331+
332+
if len(providerList.GetItems()) > 0 {
333+
log.Info(fmt.Sprintf("%d items remaning...", len(providerList.GetItems())))
334+
return false, nil
335+
}
336+
337+
if deleteOpts.includeCRDs && len(providerList.GetItems()) == 0 {
338+
log.Info("Removing CRDs")
339+
340+
group := gvk.GroupKind()
341+
group.Kind = strings.Replace(strings.ToLower(group.Kind), "list", "s", 1)
342+
crd := &apiextensionsv1.CustomResourceDefinition{ObjectMeta: metav1.ObjectMeta{Name: group.String()}}
343+
344+
if err := client.Delete(ctx, crd); ctrlclient.IgnoreNotFound(err) != nil {
345+
return false, fmt.Errorf("unable to issue delete for %s: %w", group, err)
346+
}
347+
}
348+
349+
log.Info("All requested providers are deleted")
350+
351+
return ready, nil
146352
}

0 commit comments

Comments
 (0)