github.com/oam-dev/kubevela@v1.9.11/references/cli/utils.go (about) 1 /* 2 Copyright 2021 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cli 18 19 import ( 20 "bufio" 21 "bytes" 22 "encoding/json" 23 "fmt" 24 "io" 25 "log" 26 "os" 27 "strings" 28 29 "github.com/AlecAivazis/survey/v2" 30 "github.com/pkg/errors" 31 corev1 "k8s.io/api/core/v1" 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/client-go/util/jsonpath" 34 "k8s.io/kubectl/pkg/cmd/get" 35 "sigs.k8s.io/yaml" 36 37 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 38 "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types" 39 ) 40 41 // UserInput user input in command 42 type UserInput struct { 43 Writer io.Writer 44 Reader *bufio.Reader 45 } 46 47 // UserInputOptions user input options 48 type UserInputOptions struct { 49 AssumeYes bool 50 } 51 52 // NewUserInput new user input util 53 func NewUserInput() *UserInput { 54 return &UserInput{ 55 Writer: os.Stdout, 56 Reader: bufio.NewReader(os.Stdin), 57 } 58 } 59 60 // AskBool format the answer to bool type 61 func (ui *UserInput) AskBool(question string, opts *UserInputOptions) bool { 62 fmt.Fprintf(ui.Writer, "%s (y/n)", question) 63 if opts.AssumeYes { 64 return true 65 } 66 line, err := ui.read() 67 if err != nil { 68 log.Fatal(err.Error()) 69 } 70 if input := strings.TrimSpace(strings.ToLower(line)); input == "y" || input == "yes" { 71 return true 72 } 73 return false 74 } 75 76 func (ui *UserInput) read() (string, error) { 77 line, err := ui.Reader.ReadString('\n') 78 if err != nil && !errors.Is(err, io.EOF) { 79 return "", err 80 } 81 resultStr := strings.TrimSuffix(line, "\n") 82 return resultStr, err 83 } 84 85 // formatApplicationString formats an Application to string in yaml/json/jsonpath for printing (without managedFields). 86 // 87 // format = "yaml" / "json" / "jsonpath={.field}" 88 func formatApplicationString(format string, app *v1beta1.Application) (string, error) { 89 // No, we don't want managedFields, get rid of it. 90 app.ManagedFields = nil 91 92 return printObj(format, app) 93 } 94 95 // AskToChooseOnePod will ask user to select one pod 96 func AskToChooseOnePod(pods []types.PodBase) (*types.PodBase, error) { 97 if len(pods) == 0 { 98 return nil, errors.New("no pod found in your application") 99 } 100 if len(pods) == 1 { 101 return &pods[0], nil 102 } 103 var ops []string 104 for i := 0; i < len(pods); i++ { 105 pod := pods[i] 106 ops = append(ops, fmt.Sprintf("%s | %s | %s", pod.Cluster, pod.Component, pod.Metadata.Name)) 107 } 108 prompt := &survey.Select{ 109 Message: fmt.Sprintf("There are %d pods match your filter conditions. Please choose one:\nCluster | Component | Pod", len(ops)), 110 Options: ops, 111 } 112 var selectedRsc string 113 err := survey.AskOne(prompt, &selectedRsc) 114 if err != nil { 115 return nil, fmt.Errorf("choosing pod err %w", err) 116 } 117 for k, resource := range ops { 118 if selectedRsc == resource { 119 return &pods[k], nil 120 } 121 } 122 // it should never happen. 123 return nil, errors.New("no pod match for your choice") 124 } 125 126 // AskToChooseOneService will ask user to select one service and/or port 127 func AskToChooseOneService(services []types.ResourceItem, selectPort bool) (*types.ResourceItem, int, error) { 128 if len(services) == 0 { 129 return nil, 0, errors.New("no service found in your application") 130 } 131 var ops []string 132 var res []struct { 133 item types.ResourceItem 134 port int 135 } 136 for i := 0; i < len(services); i++ { 137 obj := services[i] 138 service := &corev1.Service{} 139 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object.Object, service); err == nil { 140 if selectPort { 141 for _, port := range service.Spec.Ports { 142 ops = append(ops, fmt.Sprintf("%s | %s | %s:%d", obj.Cluster, obj.Component, obj.Object.GetName(), port.Port)) 143 res = append(res, struct { 144 item types.ResourceItem 145 port int 146 }{ 147 item: obj, 148 port: int(port.Port), 149 }) 150 } 151 } else { 152 ops = append(ops, fmt.Sprintf("%s | %s | %s", obj.Cluster, obj.Component, obj.Object.GetName())) 153 res = append(res, struct { 154 item types.ResourceItem 155 port int 156 }{ 157 item: obj, 158 }) 159 } 160 161 } 162 } 163 if len(ops) == 1 { 164 return &res[0].item, res[0].port, nil 165 } 166 prompt := &survey.Select{ 167 Message: fmt.Sprintf("There are %d services match your filter conditions. Please choose one:\nCluster | Component | Service", len(ops)), 168 Options: ops, 169 } 170 var selectedRsc string 171 err := survey.AskOne(prompt, &selectedRsc) 172 if err != nil { 173 return nil, 0, fmt.Errorf("choosing service err %w", err) 174 } 175 for k, resource := range ops { 176 if selectedRsc == resource { 177 return &res[k].item, res[k].port, nil 178 } 179 } 180 // it should never happen. 181 return nil, 0, errors.New("no service match for your choice") 182 } 183 184 func convertApplicationRevisionTo(format string, apprev *v1beta1.ApplicationRevision) (string, error) { 185 // No, we don't want managedFields, get rid of it. 186 apprev.ManagedFields = nil 187 188 return printObj(format, apprev) 189 } 190 191 func printObj(format string, obj interface{}) (string, error) { 192 var ret string 193 194 if format == "" { 195 return "", fmt.Errorf("no format provided") 196 } 197 198 switch format { 199 case "yaml": 200 b, err := yaml.Marshal(obj) 201 if err != nil { 202 return "", err 203 } 204 ret = string(b) 205 case "json": 206 b, err := json.MarshalIndent(obj, "", " ") 207 if err != nil { 208 return "", err 209 } 210 ret = string(b) 211 default: 212 // format is not any of json/yaml/jsonpath, not supported 213 if !strings.HasPrefix(format, "jsonpath") { 214 return "", fmt.Errorf("%s is not supported", format) 215 } 216 217 // format = jsonpath 218 s := strings.SplitN(format, "=", 2) 219 if len(s) < 2 { 220 return "", fmt.Errorf("jsonpath template format specified but no template given") 221 } 222 path, err := get.RelaxedJSONPathExpression(s[1]) 223 if err != nil { 224 return "", err 225 } 226 227 jp := jsonpath.New("").AllowMissingKeys(true) 228 err = jp.Parse(path) 229 if err != nil { 230 return "", err 231 } 232 233 buf := &bytes.Buffer{} 234 err = jp.Execute(buf, obj) 235 if err != nil { 236 return "", err 237 } 238 ret = buf.String() 239 } 240 241 return ret, nil 242 }