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  }