github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/cluster/list_ops.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 cluster 21 22 import ( 23 "context" 24 "fmt" 25 "sort" 26 "strings" 27 28 "github.com/spf13/cobra" 29 "golang.org/x/exp/slices" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/cli-runtime/pkg/genericiooptions" 34 cmdutil "k8s.io/kubectl/pkg/cmd/util" 35 "k8s.io/kubectl/pkg/util/templates" 36 37 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 38 "github.com/1aal/kubeblocks/pkg/cli/list" 39 "github.com/1aal/kubeblocks/pkg/cli/printer" 40 "github.com/1aal/kubeblocks/pkg/cli/types" 41 "github.com/1aal/kubeblocks/pkg/cli/util" 42 ) 43 44 var ( 45 listOpsExample = templates.Examples(` 46 # list all opsRequests 47 kbcli cluster list-ops 48 49 # list all opsRequests of specified cluster 50 kbcli cluster list-ops mycluster`) 51 52 defaultDisplayPhase = []string{"pending", "creating", "running", "canceling", "failed"} 53 ) 54 55 type opsListOptions struct { 56 *list.ListOptions 57 status []string 58 opsType []string 59 opsRequestName string 60 } 61 62 func NewListOpsCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { 63 o := &opsListOptions{ 64 ListOptions: list.NewListOptions(f, streams, types.OpsGVR()), 65 } 66 cmd := &cobra.Command{ 67 Use: "list-ops", 68 Short: "List all opsRequests.", 69 Aliases: []string{"ls-ops"}, 70 Example: listOpsExample, 71 ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.ClusterGVR()), 72 Run: func(cmd *cobra.Command, args []string) { 73 // build label selector for listing ops 74 o.LabelSelector = util.BuildLabelSelectorByNames(o.LabelSelector, args) 75 // args are the cluster names. we only use the label selector to get ops, so resources names 76 // are not needed. 77 o.Names = nil 78 util.CheckErr(o.Complete()) 79 util.CheckErr(o.printOpsList()) 80 }, 81 } 82 o.AddFlags(cmd) 83 cmd.Flags().StringSliceVar(&o.opsType, "type", nil, "The OpsRequest type") 84 cmd.Flags().StringSliceVar(&o.status, "status", defaultDisplayPhase, fmt.Sprintf("Options include all, %s. by default, outputs the %s OpsRequest.", 85 strings.Join(defaultDisplayPhase, ", "), strings.Join(defaultDisplayPhase, "/"))) 86 cmd.Flags().StringVar(&o.opsRequestName, "name", "", "The OpsRequest name to get the details.") 87 return cmd 88 } 89 90 func (o *opsListOptions) printOpsList() error { 91 // if format is JSON or YAML, use default printer to output the result. 92 if o.Format == printer.JSON || o.Format == printer.YAML { 93 if o.opsRequestName != "" { 94 o.Names = []string{o.opsRequestName} 95 } 96 _, err := o.Run() 97 return err 98 } 99 100 dynamic, err := o.Factory.DynamicClient() 101 if err != nil { 102 return err 103 } 104 listOptions := metav1.ListOptions{ 105 LabelSelector: o.LabelSelector, 106 FieldSelector: o.FieldSelector, 107 } 108 if o.AllNamespaces { 109 o.Namespace = "" 110 } 111 opsList, err := dynamic.Resource(types.OpsGVR()).Namespace(o.Namespace).List(context.TODO(), listOptions) 112 if err != nil { 113 return err 114 } 115 if len(opsList.Items) == 0 { 116 o.PrintNotFoundResources() 117 return nil 118 } 119 // sort the unstructured objects with the creationTimestamp in positive order 120 sort.Sort(unstructuredList(opsList.Items)) 121 122 // check if specified with "all" keyword for status. 123 isAllStatus := o.isAllStatus() 124 tblPrinter := printer.NewTablePrinter(o.Out) 125 tblPrinter.SetHeader("NAME", "NAMESPACE", "TYPE", "CLUSTER", "COMPONENT", "STATUS", "PROGRESS", "CREATED-TIME") 126 for _, obj := range opsList.Items { 127 ops := &appsv1alpha1.OpsRequest{} 128 if err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, ops); err != nil { 129 return err 130 } 131 phase := string(ops.Status.Phase) 132 opsType := string(ops.Spec.Type) 133 if len(o.opsRequestName) != 0 { 134 if ops.Name == o.opsRequestName { 135 tblPrinter.AddRow(ops.Name, ops.GetNamespace(), opsType, ops.Spec.ClusterRef, getComponentNameFromOps(ops), phase, ops.Status.Progress, util.TimeFormat(&ops.CreationTimestamp)) 136 } 137 continue 138 } 139 // if the OpsRequest phase is not expected, continue 140 if !isAllStatus && !o.containsIgnoreCase(o.status, phase) { 141 continue 142 } 143 144 if len(o.opsType) != 0 && !o.containsIgnoreCase(o.opsType, opsType) { 145 continue 146 } 147 tblPrinter.AddRow(ops.Name, ops.GetNamespace(), opsType, ops.Spec.ClusterRef, getComponentNameFromOps(ops), phase, ops.Status.Progress, util.TimeFormat(&ops.CreationTimestamp)) 148 } 149 if tblPrinter.Tbl.Length() != 0 { 150 tblPrinter.Print() 151 return nil 152 } 153 message := "No opsRequests found" 154 if len(o.opsRequestName) == 0 && !o.isAllStatus() { 155 message += ", you can try as follows:\n\tkbcli cluster list-ops --status all" 156 } 157 printer.PrintLine(message) 158 return nil 159 } 160 161 func getComponentNameFromOps(ops *appsv1alpha1.OpsRequest) string { 162 components := make([]string, 0) 163 opsSpec := ops.Spec 164 switch opsSpec.Type { 165 case appsv1alpha1.ReconfiguringType: 166 components = append(components, opsSpec.Reconfigure.ComponentName) 167 case appsv1alpha1.HorizontalScalingType: 168 for _, item := range opsSpec.HorizontalScalingList { 169 components = append(components, item.ComponentName) 170 } 171 case appsv1alpha1.VolumeExpansionType: 172 for _, item := range opsSpec.VolumeExpansionList { 173 components = append(components, item.ComponentName) 174 } 175 case appsv1alpha1.RestartType: 176 for _, item := range opsSpec.RestartList { 177 components = append(components, item.ComponentName) 178 } 179 case appsv1alpha1.VerticalScalingType: 180 for _, item := range opsSpec.VerticalScalingList { 181 components = append(components, item.ComponentName) 182 } 183 default: 184 for k := range ops.Status.Components { 185 components = append(components, k) 186 } 187 slices.Sort(components) 188 } 189 return strings.Join(components, ",") 190 } 191 192 func getTemplateNameFromOps(ops appsv1alpha1.OpsRequestSpec) string { 193 if ops.Type != appsv1alpha1.ReconfiguringType { 194 return "" 195 } 196 197 tpls := make([]string, 0) 198 for _, config := range ops.Reconfigure.Configurations { 199 tpls = append(tpls, config.Name) 200 } 201 return strings.Join(tpls, ",") 202 } 203 204 func getKeyNameFromOps(ops appsv1alpha1.OpsRequestSpec) string { 205 if ops.Type != appsv1alpha1.ReconfiguringType { 206 return "" 207 } 208 209 keys := make([]string, 0) 210 for _, config := range ops.Reconfigure.Configurations { 211 for _, key := range config.Keys { 212 keys = append(keys, key.Key) 213 } 214 } 215 return strings.Join(keys, ",") 216 } 217 218 func (o *opsListOptions) containsIgnoreCase(s []string, e string) bool { 219 for i := range s { 220 if strings.EqualFold(s[i], e) { 221 return true 222 } 223 } 224 return false 225 } 226 227 // isAllStatus checks if the status flag contains "all" keyword. 228 func (o *opsListOptions) isAllStatus() bool { 229 return slices.Contains(o.status, "all") 230 } 231 232 type unstructuredList []unstructured.Unstructured 233 234 func (us unstructuredList) Len() int { 235 return len(us) 236 } 237 func (us unstructuredList) Swap(i, j int) { 238 us[i], us[j] = us[j], us[i] 239 } 240 func (us unstructuredList) Less(i, j int) bool { 241 createTimeForJ := us[j].GetCreationTimestamp() 242 createTimeForI := us[i].GetCreationTimestamp() 243 return createTimeForI.Before(&createTimeForJ) 244 }