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 }