From f4e0590ee1dcef00fb934033c567b54dab09dec5 Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Mon, 11 Nov 2024 18:50:56 +0900 Subject: [PATCH] feat: add exec subcommand except decode Signed-off-by: Youngjin Jo --- cmd/exec.go | 342 ++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 14 ++- go.sum | 35 +++--- 3 files changed, 368 insertions(+), 23 deletions(-) create mode 100644 cmd/exec.go diff --git a/cmd/exec.go b/cmd/exec.go new file mode 100644 index 0000000..c9cdbad --- /dev/null +++ b/cmd/exec.go @@ -0,0 +1,342 @@ +package cmd + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "io/ioutil" + "strings" + "time" + + "google.golang.org/grpc/credentials/insecure" + + "google.golang.org/grpc/credentials" + + "github.com/golang/protobuf/jsonpb" + "github.com/jhump/protoreflect/desc" + "github.com/jhump/protoreflect/dynamic" + "github.com/jhump/protoreflect/dynamic/grpcdynamic" + "github.com/jhump/protoreflect/grpcreflect" + "github.com/pterm/pterm" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/reflection/grpc_reflection_v1alpha" + reflectpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha" + "gopkg.in/yaml.v2" +) + +var execCmd = &cobra.Command{ + Use: "exec [verb] [service.resource]", + Short: "Execute an operation on a resource", + Long: `Execute an operation on a specified service and resource. + +Command format: + cfctl exec [Verb] [Service].[Resource] + +Examples: + cfctl exec create identity.Role + cfctl exec list identity.User + cfctl exec get identity.User -p user_id=user-123 + cfctl exec update identity.Project -f params.yaml`, + Args: cobra.ExactArgs(2), + RunE: runExec, +} + +func init() { + rootCmd.AddCommand(execCmd) + + execCmd.Flags().StringArrayP("parameter", "p", []string{}, "Input parameter (-p key=value)") + execCmd.Flags().StringP("json-parameter", "j", "", "JSON parameter") + execCmd.Flags().StringP("file-parameter", "f", "", "YAML file parameter") + execCmd.Flags().StringP("api-version", "v", "v1", "API version") + execCmd.Flags().StringP("output", "o", "yaml", "Output format (yaml/json)") +} + +func runExec(cmd *cobra.Command, args []string) error { + verb := args[0] + serviceResource := args[1] + + service, resource, err := parseServiceResource(serviceResource) + if err != nil { + return fmt.Errorf("failed to parse service.resource: %w", err) + } + + parameters, _ := cmd.Flags().GetStringArray("parameter") + jsonParameter, _ := cmd.Flags().GetString("json-parameter") + fileParameter, _ := cmd.Flags().GetString("file-parameter") + apiVersion, _ := cmd.Flags().GetString("api-version") + output, _ := cmd.Flags().GetString("output") + + params := parseParameters(parameters, jsonParameter, fileParameter) + return executeAPI(service, resource, verb, params, apiVersion, output) +} + +func parseServiceResource(serviceResource string) (string, string, error) { + parts := strings.Split(serviceResource, ".") + if len(parts) != 2 { + return "", "", fmt.Errorf("invalid resource format. It should be [service].[resource]") + } + return parts[0], parts[1], nil +} + +func parseParameters(parameters []string, jsonParameter string, fileParameter string) map[string]interface{} { + params := make(map[string]interface{}) + + // Handle key=value parameters + fmt.Println("Command line parameters:", parameters) + for _, p := range parameters { + parts := strings.SplitN(p, "=", 2) + if len(parts) == 2 { + params[parts[0]] = parts[1] + fmt.Printf("Added parameter: %s = %v\n", parts[0], parts[1]) + } + } + + // Handle JSON parameter + if jsonParameter != "" { + fmt.Println("JSON parameter:", jsonParameter) + var jsonParams map[string]interface{} + if err := json.Unmarshal([]byte(jsonParameter), &jsonParams); err == nil { + for k, v := range jsonParams { + params[k] = v + fmt.Printf("Added JSON parameter: %s = %v\n", k, v) + } + } else { + fmt.Printf("JSON parsing error: %v\n", err) + } + } + + // Handle file parameter + if fileParameter != "" { + fmt.Printf("Reading file: %s\n", fileParameter) + fileContent, err := ioutil.ReadFile(fileParameter) + if err == nil { + fmt.Printf("File content: %s\n", string(fileContent)) + var fileParams map[string]interface{} + if err := yaml.Unmarshal(fileContent, &fileParams); err == nil { + for k, v := range fileParams { + params[k] = v + fmt.Printf("Added file parameter: %s = %v\n", k, v) + } + } else { + fmt.Printf("YAML parsing error: %v\n", err) + } + } else { + fmt.Printf("File reading error: %v\n", err) + } + } + + fmt.Printf("Final parameters: %+v\n", params) + return params +} + +func getMethodDescriptor(ctx context.Context, conn *grpc.ClientConn, service, resource, method string) (*desc.MethodDescriptor, error) { + reflectClient := grpcreflect.NewClientV1Alpha(ctx, reflectpb.NewServerReflectionClient(conn)) + + // Convert to SpaceONE service namespace format + // Example: spaceone.api.identity.v1.User + fullServiceName := fmt.Sprintf("%s.api.dev.spaceone.dev", service) + + fmt.Printf("Looking for service: %s\n", fullServiceName) + + svc, err := reflectClient.ResolveService(fullServiceName) + if err != nil { + return nil, fmt.Errorf("service not found %s: %v", fullServiceName, err) + } + + // Capitalize the first letter of the method name + methodName := strings.Title(method) + methodDesc := svc.FindMethodByName(methodName) + if methodDesc == nil { + return nil, fmt.Errorf("method not found %s in %s", methodName, fullServiceName) + } + + return methodDesc, nil +} + +func createDynamicMessage(methodDesc *desc.MethodDescriptor, params map[string]interface{}) (*dynamic.Message, error) { + msgDesc := methodDesc.GetInputType() + msg := dynamic.NewMessage(msgDesc) + + jsonData, err := json.Marshal(params) + if err != nil { + return nil, fmt.Errorf("failed to convert parameters: %v", err) + } + + if err := msg.UnmarshalJSON(jsonData); err != nil { + return nil, fmt.Errorf("failed to unmarshal message: %v", err) + } + + return msg, nil +} + +func callAPI(conn *grpc.ClientConn, service, resource, verb string, params map[string]interface{}) (interface{}, error) { + ctx := context.Background() + + // Set timeout + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + + // Read token from SpaceONE config file + cfgFile, err := getEnvironmentConfig() + if err != nil { + return nil, fmt.Errorf("failed to find environment config file: %v", err) + } + + viper.SetConfigFile(cfgFile) + if err := viper.ReadInConfig(); err != nil { + return nil, fmt.Errorf("failed to read config file: %v", err) + } + + // Get token value + if token := viper.GetString("token"); token != "" { + md := metadata.New(map[string]string{ + "authorization": "Bearer " + token, + }) + ctx = metadata.NewOutgoingContext(ctx, md) + } else { + return nil, fmt.Errorf("token is not set") + } + + // Get method descriptor using reflection + methodDesc, err := getMethodDescriptor(ctx, conn, service, resource, verb) + if err != nil { + return nil, err + } + + // Create dynamic message + msg, err := createDynamicMessage(methodDesc, params) + if err != nil { + return nil, err + } + + // Create dynamic gRPC client + stub := grpcdynamic.NewStub(conn) + + // Invoke API + resp, err := stub.InvokeRpc(ctx, methodDesc, msg) + if err != nil { + return nil, fmt.Errorf("API call failed: %v", err) + } + + // Handle response + if dynamicMsg, ok := resp.(*dynamic.Message); ok { + jsonMarshaler := &jsonpb.Marshaler{ + EmitDefaults: true, + OrigName: true, + Indent: " ", + } + jsonStr, err := jsonMarshaler.MarshalToString(dynamicMsg) + if err != nil { + return nil, fmt.Errorf("failed to convert response: %v", err) + } + + var result map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &result); err != nil { + return nil, fmt.Errorf("failed to parse JSON: %v", err) + } + return result, nil + } + + return resp, nil +} + +func executeAPI(service, resource, verb string, params map[string]interface{}, apiVersion, output string) error { + spinner, _ := pterm.DefaultSpinner.Start("Executing API call...") + + // Read SpaceONE config file + cfgFile, err := getEnvironmentConfig() + if err != nil { + spinner.Fail(fmt.Sprintf("failed to find environment config file: %v", err)) + return err + } + + viper.SetConfigFile(cfgFile) + if err := viper.ReadInConfig(); err != nil { + spinner.Fail(fmt.Sprintf("failed to read config file: %v", err)) + return err + } + + endpointsMap := viper.GetStringMapString("endpoints") + endpoint, ok := endpointsMap[service] + if !ok { + spinner.Fail(fmt.Sprintf("failed to find endpoint for service %s", service)) + return fmt.Errorf("endpoint not found for service: %s", service) + } + + // Parse endpoint + parts := strings.Split(endpoint, "://") + if len(parts) != 2 { + return fmt.Errorf("invalid endpoint format: %s", endpoint) + } + + scheme := parts[0] + hostPort := strings.SplitN(parts[1], "/", 2)[0] + + var opts []grpc.DialOption + if scheme == "grpc+ssl" { + tlsConfig := &tls.Config{ + InsecureSkipVerify: false, + } + creds := credentials.NewTLS(tlsConfig) + opts = append(opts, grpc.WithTransportCredentials(creds)) + } else { + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + } + + fmt.Printf("Connecting to endpoint: %s\n", hostPort) + + conn, err := grpc.Dial(hostPort, opts...) + if err != nil { + spinner.Fail(fmt.Sprintf("failed to connect to server: %v", err)) + return err + } + defer conn.Close() + + client := grpc_reflection_v1alpha.NewServerReflectionClient(conn) + stream, err := client.ServerReflectionInfo(context.Background()) + if err != nil { + return fmt.Errorf("failed to create reflection client: %v", err) + } + + // Construct service name + fullServiceName := fmt.Sprintf("spaceone.api.%s.v1.%s", service, strings.Title(resource)) + + // Request method information + req := &grpc_reflection_v1alpha.ServerReflectionRequest{ + MessageRequest: &grpc_reflection_v1alpha.ServerReflectionRequest_FileContainingSymbol{ + FileContainingSymbol: fullServiceName, + }, + } + + if err := stream.Send(req); err != nil { + return fmt.Errorf("failed to send reflection request: %v", err) + } + + response, err := stream.Recv() + if err != nil { + return fmt.Errorf("failed to receive reflection response: %v", err) + } + + spinner.Success("API call complete") + + // Handle output format + var outputData []byte + if output == "yaml" { + outputData, err = yaml.Marshal(response) + } else if output == "json" { + outputData, err = json.MarshalIndent(response, "", " ") + } else { + return fmt.Errorf("unsupported output format: %s", output) + } + + if err != nil { + return fmt.Errorf("failed to format output: %v", err) + } + + fmt.Println(string(outputData)) + return nil +} diff --git a/go.mod b/go.mod index d588562..5e3d244 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,13 @@ module github.com/cloudforet-io/cfctl go 1.23.1 require ( + github.com/golang/protobuf v1.5.4 + github.com/jhump/protoreflect v1.17.0 github.com/pterm/pterm v0.12.79 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 google.golang.org/grpc v1.62.1 - google.golang.org/protobuf v1.33.0 + google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v2 v2.2.4 ) @@ -15,9 +17,9 @@ require ( atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect atomicgo.dev/schedule v0.1.0 // indirect + github.com/bufbuild/protocompile v0.14.1 // indirect github.com/containerd/console v1.0.3 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect github.com/gookit/color v1.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -38,10 +40,10 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index ae5f290..70bcfd8 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYew github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -27,10 +29,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= @@ -41,6 +41,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= @@ -133,11 +135,13 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -148,36 +152,33 @@ golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI= google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=