github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/fault/list_and_delete.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package fault
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"sort"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/jedib0t/go-pretty/v6/table"
    30  	"github.com/pkg/errors"
    31  	"github.com/spf13/cobra"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/cli-runtime/pkg/genericiooptions"
    34  	"k8s.io/client-go/dynamic"
    35  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    36  	"k8s.io/kubectl/pkg/util/templates"
    37  
    38  	"github.com/1aal/kubeblocks/pkg/cli/printer"
    39  	"github.com/1aal/kubeblocks/pkg/cli/util"
    40  )
    41  
    42  var listExample = templates.Examples(`
    43  	# List all chaos resources
    44  	kbcli fault list
    45  	
    46  	# List all chaos kind
    47  	kbcli fault list --kind
    48  
    49  	# List specific chaos resources. Use 'kbcli fault list --kind' to get chaos kind. 
    50  	kbcli fault list podchaos
    51  `)
    52  
    53  var deleteExample = templates.Examples(`
    54  	# Delete all chaos resources
    55  	kbcli fault delete
    56  	
    57  	# Delete specific chaos resources
    58  	kbcli fault delete podchaos
    59  `)
    60  
    61  type ListAndDeleteOptions struct {
    62  	Factory cmdutil.Factory
    63  	Dynamic dynamic.Interface
    64  
    65  	ResourceKinds    []string
    66  	AllResourceKinds []string
    67  	Kind             bool
    68  
    69  	genericiooptions.IOStreams
    70  }
    71  
    72  func NewListCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
    73  	o := &ListAndDeleteOptions{Factory: f, IOStreams: streams}
    74  	cmd := cobra.Command{
    75  		Use:     "list",
    76  		Short:   "List chaos resources.",
    77  		Example: listExample,
    78  		Run: func(cmd *cobra.Command, args []string) {
    79  			util.CheckErr(o.Validate(args))
    80  			util.CheckErr(o.Complete(args))
    81  			util.CheckErr(o.RunList())
    82  		},
    83  	}
    84  	cmd.Flags().BoolVar(&o.Kind, "kind", false, "Print chaos resource kind.")
    85  	return &cmd
    86  }
    87  
    88  func NewDeleteCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
    89  	o := &ListAndDeleteOptions{Factory: f, IOStreams: streams}
    90  	return &cobra.Command{
    91  		Use:     "delete",
    92  		Short:   "Delete chaos resources.",
    93  		Example: deleteExample,
    94  		Run: func(cmd *cobra.Command, args []string) {
    95  			util.CheckErr(o.Validate(args))
    96  			util.CheckErr(o.Complete(args))
    97  			util.CheckErr(o.RunDelete())
    98  		},
    99  	}
   100  }
   101  
   102  func (o *ListAndDeleteOptions) Validate(args []string) error {
   103  	var err error
   104  	o.AllResourceKinds, err = getAllChaosResourceKinds(o.Factory, GroupVersion)
   105  	if err != nil {
   106  		return fmt.Errorf("failed to get all chaos resource kinds: %v", err)
   107  	}
   108  	kindMap := make(map[string]bool)
   109  	for _, kind := range o.AllResourceKinds {
   110  		kindMap[kind] = true
   111  	}
   112  	for _, kind := range args {
   113  		if _, ok := kindMap[kind]; !ok {
   114  			return fmt.Errorf("invalid chaos resource kind: %s\nUse 'kbcli fault list --kind' to list all chaos resource kinds", kind)
   115  		}
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  func (o *ListAndDeleteOptions) Complete(args []string) error {
   122  	if o.Kind {
   123  		for _, resourceKind := range o.AllResourceKinds {
   124  			fmt.Fprintf(o.Out, "%s\n", resourceKind)
   125  		}
   126  		return nil
   127  	}
   128  
   129  	if len(args) > 0 {
   130  		o.ResourceKinds = args
   131  	} else {
   132  		o.ResourceKinds = o.AllResourceKinds
   133  	}
   134  
   135  	var err error
   136  	o.Dynamic, err = o.Factory.DynamicClient()
   137  	if err != nil {
   138  		return fmt.Errorf("failed to create dynamic client: %v", err)
   139  	}
   140  
   141  	return nil
   142  }
   143  
   144  func (o *ListAndDeleteOptions) RunList() error {
   145  	if o.Kind {
   146  		return nil
   147  	}
   148  
   149  	tbl := printer.NewTablePrinter(o.Out)
   150  	tbl.Tbl.SetColumnConfigs([]table.ColumnConfig{
   151  		{Number: 2, WidthMax: 120},
   152  	})
   153  	tbl.SetHeader("NAME", "AGE")
   154  
   155  	for _, resourceKind := range o.ResourceKinds {
   156  		if err := o.listResources(resourceKind, tbl); err != nil {
   157  			return err
   158  		}
   159  	}
   160  
   161  	tbl.Print()
   162  	return nil
   163  }
   164  
   165  func (o *ListAndDeleteOptions) RunDelete() error {
   166  	for _, resourceKind := range o.ResourceKinds {
   167  		if err := o.deleteResources(resourceKind); err != nil {
   168  			return err
   169  		}
   170  	}
   171  	return nil
   172  }
   173  
   174  func (o *ListAndDeleteOptions) listResources(resourceKind string, tbl *printer.TablePrinter) error {
   175  	gvr := GetGVR(Group, Version, resourceKind)
   176  	resourceList, err := o.Dynamic.Resource(gvr).List(context.TODO(), metav1.ListOptions{})
   177  	if err != nil {
   178  		return errors.Wrapf(err, "failed to list %s", gvr)
   179  	}
   180  
   181  	if len(resourceList.Items) == 0 {
   182  		return nil
   183  	}
   184  
   185  	// sort by creation time from old to new
   186  	sort.Slice(resourceList.Items, func(i, j int) bool {
   187  		t1, _ := time.Parse(time.RFC3339, resourceList.Items[i].GetCreationTimestamp().String())
   188  		t2, _ := time.Parse(time.RFC3339, resourceList.Items[j].GetCreationTimestamp().String())
   189  		return t1.Before(t2)
   190  	})
   191  
   192  	for _, obj := range resourceList.Items {
   193  		creationTime := obj.GetCreationTimestamp().Time
   194  		age := time.Since(creationTime).Round(time.Second).String()
   195  		tbl.AddRow(obj.GetName(), age)
   196  	}
   197  	return nil
   198  }
   199  
   200  func (o *ListAndDeleteOptions) deleteResources(resourceKind string) error {
   201  	gvr := GetGVR(Group, Version, resourceKind)
   202  	resourceList, err := o.Dynamic.Resource(gvr).List(context.TODO(), metav1.ListOptions{})
   203  	if err != nil {
   204  		return errors.Wrapf(err, "failed to list %s", gvr)
   205  	}
   206  
   207  	if len(resourceList.Items) == 0 {
   208  		return nil
   209  	}
   210  
   211  	for _, obj := range resourceList.Items {
   212  		err = o.Dynamic.Resource(gvr).Namespace(obj.GetNamespace()).Delete(context.TODO(), obj.GetName(), metav1.DeleteOptions{})
   213  		if err != nil {
   214  			return errors.Wrapf(err, "failed to delete %s", gvr)
   215  		}
   216  		fmt.Fprintf(o.Out, "delete resource %s/%s\n", obj.GetNamespace(), obj.GetName())
   217  	}
   218  	return nil
   219  }
   220  
   221  func getAllChaosResourceKinds(f cmdutil.Factory, groupVersion string) ([]string, error) {
   222  	discoveryClient, err := f.ToDiscoveryClient()
   223  	if err != nil {
   224  		return nil, errors.Wrap(err, "failed to create discovery client")
   225  	}
   226  	chaosResources, err := discoveryClient.ServerResourcesForGroupVersion(groupVersion)
   227  	if err != nil {
   228  		return nil, errors.Wrapf(err, "failed to get server resources for %s", groupVersion)
   229  	}
   230  
   231  	resourceKinds := make([]string, 0)
   232  	for _, resourceKind := range chaosResources.APIResources {
   233  		// skip subresources
   234  		if len(strings.Split(resourceKind.Name, "/")) > 1 {
   235  			continue
   236  		}
   237  		// skip podhttpchaos and podnetworkchaos etc.
   238  		if resourceKind.Name != "podchaos" && strings.HasPrefix(resourceKind.Name, "pod") {
   239  			continue
   240  		}
   241  		resourceKinds = append(resourceKinds, resourceKind.Name)
   242  	}
   243  	return resourceKinds, nil
   244  }