From e3b65ee234b1d4106bee96ea248a61175ae3c917 Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Mon, 11 Nov 2024 14:43:44 +0900 Subject: [PATCH 1/5] feat: remove default config file Signed-off-by: Youngjin Jo --- cmd/config.go | 166 ++++++++++++++++++++++++++++---------------------- go.mod | 2 +- 2 files changed, 94 insertions(+), 74 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index db01684..ce65b79 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -10,7 +10,6 @@ import ( "os" "path/filepath" "strings" - "sync" "github.com/pterm/pterm" "github.com/spf13/cobra" @@ -25,7 +24,7 @@ var configCmd = &cobra.Command{ Use: "config", Short: "Manage cfctl configuration files", Long: `Manage configuration files for cfctl. You can initialize, - switch environments, and display the current configuration.`, +switch environments, and display the current configuration.`, } // initCmd initializes a new environment configuration @@ -33,11 +32,17 @@ var initCmd = &cobra.Command{ Use: "init", Short: "Initialize a new environment configuration", Run: func(cmd *cobra.Command, args []string) { + // Retrieve environment name from the flag environment, _ := cmd.Flags().GetString("environment") importFile, _ := cmd.Flags().GetString("import-file") + // Prompt for environment if not provided if environment == "" { - log.Fatalf("Environment name must be provided") + environment, _ = pterm.DefaultInteractiveTextInput.WithDefaultText("default").Show("Environment") + if environment == "" { + pterm.Error.Println("Environment name must be provided") + return + } } // Ensure environments directory exists @@ -128,7 +133,8 @@ var initCmd = &cobra.Command{ // envCmd manages environment switching and listing var envCmd = &cobra.Command{ Use: "environment", - Short: "Manage and switch environments", + Short: "List and manage environments", + Long: "List and manage environments", Run: func(cmd *cobra.Command, args []string) { // Update the global config file with the current list of environments updateGlobalConfig() @@ -155,27 +161,38 @@ var envCmd = &cobra.Command{ log.Fatalf("Failed to update environment.yml file: %v", err) } + // Display only the success message without additional text pterm.Success.Printf("Switched to '%s' environment.\n", switchEnv) return } - currentEnv := getCurrentEnvironment() - envDir := filepath.Join(getConfigDir(), "environments") - entries, err := os.ReadDir(envDir) - if err != nil { - log.Fatalf("Unable to list environments: %v", err) - } + // Check if the -l flag is provided + listOnly, _ := cmd.Flags().GetBool("list") + + // List environments if the -l flag is set + if listOnly { + currentEnv := getCurrentEnvironment() + envDir := filepath.Join(getConfigDir(), "environments") + entries, err := os.ReadDir(envDir) + if err != nil { + log.Fatalf("Unable to list environments: %v", err) + } - pterm.Println("Available Environments:\n") - for _, entry := range entries { - name := entry.Name() - name = name[:len(name)-len(filepath.Ext(name))] // Remove ".yml" extension - if name == currentEnv { - pterm.FgGreen.Printf(" > %s (current)\n", name) - } else { - pterm.Printf(" %s\n", name) + pterm.Println("Available Environments:") + for _, entry := range entries { + name := entry.Name() + name = name[:len(name)-len(filepath.Ext(name))] // Remove ".yml" extension + if name == currentEnv { + pterm.FgGreen.Printf(" > %s (current)\n", name) + } else { + pterm.Printf(" %s\n", name) + } } + return } + + // If -l is not set, show help by default + cmd.Help() }, } @@ -184,9 +201,33 @@ var showCmd = &cobra.Command{ Use: "show", Short: "Display the current cfctl configuration", Run: func(cmd *cobra.Command, args []string) { + // Load the current environment from ~/.spaceone/environment.yml + currentEnv := getCurrentEnvironment() + if currentEnv == "" { + log.Fatal("No environment set in ~/.spaceone/environment.yml") + } + + // Construct the path to the environment's YAML file + envDir := filepath.Join(getConfigDir(), "environments") + envFilePath := filepath.Join(envDir, currentEnv+".yml") + + // Check if the environment file exists + if _, err := os.Stat(envFilePath); os.IsNotExist(err) { + log.Fatalf("Environment file '%s.yml' does not exist in ~/.spaceone/environments", currentEnv) + } + + // Load and display the configuration from the environment YAML file + viper.SetConfigFile(envFilePath) + err := viper.ReadInConfig() + if err != nil { + log.Fatalf("Error reading environment file '%s': %v", envFilePath, err) + } + + // Get output format from the flag output, _ := cmd.Flags().GetString("output") configData := viper.AllSettings() + // Display the configuration in the requested format switch output { case "json": data, err := json.MarshalIndent(configData, "", " ") @@ -197,7 +238,7 @@ var showCmd = &cobra.Command{ case "yml": data, err := yaml.Marshal(configData) if err != nil { - log.Fatalf("Error formatting output as yml: %v", err) + log.Fatalf("Error formatting output as YAML: %v", err) } fmt.Println(string(data)) default: @@ -206,29 +247,6 @@ var showCmd = &cobra.Command{ }, } -func init() { - rootCmd.AddCommand(configCmd) - - // Adding subcommands to configCmd - configCmd.AddCommand(initCmd) - configCmd.AddCommand(envCmd) - configCmd.AddCommand(showCmd) - - // Defining flags for initCmd - initCmd.Flags().StringP("environment", "e", "", "Name of the environment (required)") - initCmd.Flags().StringP("import-file", "f", "", "Path to an import configuration file") - initCmd.MarkFlagRequired("environment") - - // Defining flags for envCmd - envCmd.Flags().StringP("switch", "s", "", "Switch to a different environment") - envCmd.Flags().StringP("remove", "r", "", "Remove an environment") - - // Defining flags for showCmd - showCmd.Flags().StringP("output", "o", "yml", "Output format (yml/json)") - - viper.SetConfigType("yml") -} - func getConfigDir() string { home, err := os.UserHomeDir() if err != nil { @@ -241,10 +259,8 @@ func getCurrentEnvironment() string { envConfigPath := filepath.Join(getConfigDir(), "environment.yml") viper.SetConfigFile(envConfigPath) - err := viper.ReadInConfig() - if err != nil { - log.Fatalf("Failed to read environment config file: %v", err) - } + // Prevent errors if the config file is missing + _ = viper.ReadInConfig() return viper.GetString("environment") } @@ -256,6 +272,7 @@ func updateGlobalConfig() { log.Fatalf("Unable to list environments: %v", err) } + // Open ~/.spaceone/config for writing (will overwrite existing contents) configPath := filepath.Join(getConfigDir(), "config") file, err := os.Create(configPath) if err != nil { @@ -266,24 +283,16 @@ func updateGlobalConfig() { writer := bufio.NewWriter(file) defer writer.Flush() - var wg sync.WaitGroup - existingEnvironments := make(map[string]bool) - + // Write each environment that currently exists in ~/.spaceone/environments for _, entry := range entries { - wg.Add(1) - go func(entry os.DirEntry) { - defer wg.Done() - name := entry.Name() - name = name[:len(name)-len(filepath.Ext(name))] // Remove ".yml" extension - existingEnvironments[name] = true - writer.WriteString(fmt.Sprintf("[%s]\n", name)) - writer.WriteString(fmt.Sprintf("cfctl environments -s %s\n\n", name)) - }(entry) + if !entry.IsDir() && filepath.Ext(entry.Name()) == ".yml" { + name := strings.TrimSuffix(entry.Name(), filepath.Ext(entry.Name())) + _, err := writer.WriteString(fmt.Sprintf("[%s]\ncfctl environments -s %s\n\n", name, name)) + if err != nil { + log.Fatalf("Failed to write to config file: %v", err) + } + } } - - wg.Wait() - - pterm.Info.Println("Updated global config file with available environments.") } // updateGlobalConfigWithEnvironment adds the new environment command to the global config file @@ -292,8 +301,6 @@ func updateGlobalConfigWithEnvironment(environment string) { var file *os.File var err error - var message string - var isFileCreated bool // Check if the config file already exists if _, err := os.Stat(configPath); os.IsNotExist(err) { @@ -302,8 +309,7 @@ func updateGlobalConfigWithEnvironment(environment string) { if err != nil { log.Fatalf("Failed to create config file: %v", err) } - message = fmt.Sprintf("Created global config file with environment '%s'.\n", environment) - isFileCreated = true + pterm.Info.Printf("Created global config file with environment '%s'.\n", environment) } else { // Read the existing config file to check for duplicates content, err := os.ReadFile(configPath) @@ -314,6 +320,7 @@ func updateGlobalConfigWithEnvironment(environment string) { pterm.Info.Printf("Environment '%s' already exists in the config file.\n", environment) return } + // Open the existing config file for appending file, err = os.OpenFile(configPath, os.O_APPEND|os.O_WRONLY, 0600) if err != nil { @@ -327,8 +334,6 @@ func updateGlobalConfigWithEnvironment(environment string) { log.Fatalf("Failed to write newline to config file: %v", err) } } - message = fmt.Sprintf("Added environment '%s' to global config file.\n", environment) - isFileCreated = false } defer file.Close() @@ -341,9 +346,24 @@ func updateGlobalConfigWithEnvironment(environment string) { log.Fatalf("Failed to write to config file: %v", err) } - if isFileCreated { - pterm.Info.Print(message) - } else { - pterm.Success.Print(message) - } + //pterm.Success.Printf("Added environment '%s' to global config file.\n", environment) +} + +func init() { + rootCmd.AddCommand(configCmd) + + // Adding subcommands to configCmd + configCmd.AddCommand(initCmd) + configCmd.AddCommand(envCmd) + configCmd.AddCommand(showCmd) + + // Defining flags for envCmd + envCmd.Flags().StringP("switch", "s", "", "Switch to a different environment") + envCmd.Flags().StringP("remove", "r", "", "Remove an environment") + envCmd.Flags().BoolP("list", "l", false, "List available environments") + + // Defining flags for showCmd + showCmd.Flags().StringP("output", "o", "yml", "Output format (yml/json)") + + viper.SetConfigType("yml") } diff --git a/go.mod b/go.mod index fc21a4f..d588562 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( google.golang.org/grpc v1.62.1 google.golang.org/protobuf v1.33.0 gopkg.in/yaml.v2 v2.2.4 - gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -45,4 +44,5 @@ require ( golang.org/x/text v0.14.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 ) From e301de0308be72fe4df14eca39804d78e008d9db Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Mon, 11 Nov 2024 14:44:23 +0900 Subject: [PATCH 2/5] feat: remove default config file by root command Signed-off-by: Youngjin Jo --- cmd/root.go | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 489e0fb..3e1da12 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,12 +4,9 @@ Copyright © 2024 NAME HERE package cmd import ( - "fmt" "os" - "path/filepath" "github.com/spf13/cobra" - "github.com/spf13/viper" ) var cfgFile string @@ -36,39 +33,11 @@ func Execute() { } func init() { - cobra.OnInitialize(initConfig) - // Here you will define your flags and configuration settings. // Cobra supports persistent flags, which, if defined here, // will be global for your application. - rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.spaceone/cfctl.yaml)") - // Cobra also supports local flags, which will only run // when this action is called directly. //rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } - -// initConfig reads in config file and ENV variables if set. -func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := os.UserHomeDir() - cobra.CheckErr(err) - - viper.AddConfigPath(home) - viper.AddConfigPath(filepath.Join(home, ".spaceone")) - viper.SetConfigName("cfctl") - viper.SetConfigType("yaml") - } - - viper.AutomaticEnv() // read in environment variables that match - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) - } -} From 068e5180cb7ad9e4d9127847399dde589075a263 Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Mon, 11 Nov 2024 16:08:40 +0900 Subject: [PATCH 3/5] chore: remove finding default config file Signed-off-by: Youngjin Jo --- cmd/login.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmd/login.go b/cmd/login.go index 246d4c6..d5a1975 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -512,7 +512,4 @@ func init() { viper.SetConfigName("cfctl") viper.SetConfigType("yaml") viper.AddConfigPath("$HOME/.spaceone/") - if err := viper.ReadInConfig(); err != nil { - pterm.Warning.Println("No configuration file found.") - } } From bf255a84410b7b30d721d105e830352b7d854962 Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Mon, 11 Nov 2024 16:34:32 +0900 Subject: [PATCH 4/5] feat: update api-resources to get endpoints by current environment Signed-off-by: Youngjin Jo --- cmd/apiResources.go | 246 ++++++++++++++++++++++++-------------------- 1 file changed, 133 insertions(+), 113 deletions(-) diff --git a/cmd/apiResources.go b/cmd/apiResources.go index 08c7897..77704ae 100644 --- a/cmd/apiResources.go +++ b/cmd/apiResources.go @@ -13,20 +13,151 @@ import ( "strings" "sync" - "github.com/pterm/pterm" "github.com/spf13/cobra" "github.com/spf13/viper" + "gopkg.in/yaml.v2" + + "github.com/pterm/pterm" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/reflection/grpc_reflection_v1alpha" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/descriptorpb" - "gopkg.in/yaml.v2" ) var endpoints string +var apiResourcesCmd = &cobra.Command{ + Use: "api-resources", + Short: "Displays supported API resources", + Run: func(cmd *cobra.Command, args []string) { + // Load the active environment configuration file + cfgFile, err := getEnvironmentConfig() + if err != nil { + log.Fatalf("Failed to load active environment configuration: %v", err) + } + + viper.SetConfigFile(cfgFile) + if err := viper.ReadInConfig(); err != nil { + log.Fatalf("Error reading config file: %v", err) + } + + endpointsMap := viper.GetStringMapString("endpoints") + + // Load short names configuration + shortNamesFile := filepath.Join(getConfigDirectory(), "short_names.yml") + shortNamesMap := make(map[string]string) + if _, err := os.Stat(shortNamesFile); err == nil { + file, err := os.Open(shortNamesFile) + if err != nil { + log.Fatalf("Failed to open short_names.yml file: %v", err) + } + defer file.Close() + + err = yaml.NewDecoder(file).Decode(&shortNamesMap) + if err != nil { + log.Fatalf("Failed to decode short_names.yml: %v", err) + } + } + + // Process endpoints provided via flag + if endpoints != "" { + selectedEndpoints := strings.Split(endpoints, ",") + for i := range selectedEndpoints { + selectedEndpoints[i] = strings.TrimSpace(selectedEndpoints[i]) + } + var allData [][]string + + for _, endpointName := range selectedEndpoints { + endpointName = strings.TrimSpace(endpointName) + serviceEndpoint, ok := endpointsMap[endpointName] + if !ok { + log.Printf("No endpoint found for %s", endpointName) + continue + } + + result, err := fetchServiceResources(endpointName, serviceEndpoint, shortNamesMap) + if err != nil { + log.Printf("Error processing service %s: %v", endpointName, err) + continue + } + + allData = append(allData, result...) + } + + sort.Slice(allData, func(i, j int) bool { + return allData[i][0] < allData[j][0] + }) + + renderTable(allData) + return + } + + // If -e flag is not provided, list all services as before + var wg sync.WaitGroup + dataChan := make(chan [][]string, len(endpointsMap)) + errorChan := make(chan error, len(endpointsMap)) + + for service, endpoint := range endpointsMap { + wg.Add(1) + go func(service, endpoint string) { + defer wg.Done() + result, err := fetchServiceResources(service, endpoint, shortNamesMap) + if err != nil { + errorChan <- fmt.Errorf("Error processing service %s: %v", service, err) + return + } + dataChan <- result + }(service, endpoint) + } + + wg.Wait() + close(dataChan) + close(errorChan) + + if len(errorChan) > 0 { + for err := range errorChan { + log.Println(err) + } + } + + var allData [][]string + for data := range dataChan { + allData = append(allData, data...) + } + + sort.Slice(allData, func(i, j int) bool { + return allData[i][0] < allData[j][0] + }) + + renderTable(allData) + }, +} + +func getEnvironmentConfig() (string, error) { + home, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("unable to find home directory: %v", err) + } + + // Load the main environment file + envConfigFile := filepath.Join(home, ".spaceone", "environment.yml") + viper.SetConfigFile(envConfigFile) + if err := viper.ReadInConfig(); err != nil { + return "", fmt.Errorf("error reading main environment file: %v", err) + } + + // Get the current environment name (e.g., 'dev') + currentEnv := viper.GetString("environment") + if currentEnv == "" { + return "", fmt.Errorf("no active environment specified in %s", envConfigFile) + } + + // Path to the specific environment file (e.g., ~/.spaceone/environments/dev.yml) + return filepath.Join(home, ".spaceone", "environments", currentEnv+".yml"), nil +} + func fetchServiceResources(service, endpoint string, shortNamesMap map[string]string) ([][]string, error) { // Configure gRPC connection based on TLS usage parts := strings.Split(endpoint, "://") @@ -131,117 +262,6 @@ func getServiceMethods(client grpc_reflection_v1alpha.ServerReflectionClient, se return methods } -var apiResourcesCmd = &cobra.Command{ - Use: "api-resources", - Short: "Displays supported API resources", - Run: func(cmd *cobra.Command, args []string) { - // Load configuration file - cfgFile := viper.GetString("config") - if cfgFile == "" { - home, err := os.UserHomeDir() - if err != nil { - log.Fatalf("Failed to get user home directory: %v", err) - } - cfgFile = filepath.Join(home, ".spaceone", "cfctl.yaml") - } - - viper.SetConfigFile(cfgFile) - if err := viper.ReadInConfig(); err != nil { - log.Fatalf("Error reading config file: %v", err) - } - - endpointsMap := viper.GetStringMapString("endpoints") - - // Load short names configuration - shortNamesFile := filepath.Join(getConfigDirectory(), "short_names.yml") - shortNamesMap := make(map[string]string) - if _, err := os.Stat(shortNamesFile); err == nil { - file, err := os.Open(shortNamesFile) - if err != nil { - log.Fatalf("Failed to open short_names.yml file: %v", err) - } - defer file.Close() - - err = yaml.NewDecoder(file).Decode(&shortNamesMap) - if err != nil { - log.Fatalf("Failed to decode short_names.yml: %v", err) - } - } - - // Process endpoints provided via flag - if endpoints != "" { - selectedEndpoints := strings.Split(endpoints, ",") - for i := range selectedEndpoints { - selectedEndpoints[i] = strings.TrimSpace(selectedEndpoints[i]) - } - var allData [][]string - - for _, endpointName := range selectedEndpoints { - endpointName = strings.TrimSpace(endpointName) - serviceEndpoint, ok := endpointsMap[endpointName] - if !ok { - log.Printf("No endpoint found for %s", endpointName) - continue - } - - result, err := fetchServiceResources(endpointName, serviceEndpoint, shortNamesMap) - if err != nil { - log.Printf("Error processing service %s: %v", endpointName, err) - continue - } - - allData = append(allData, result...) - } - - sort.Slice(allData, func(i, j int) bool { - return allData[i][0] < allData[j][0] - }) - - renderTable(allData) - return - } - - // If -e flag is not provided, list all services as before - var wg sync.WaitGroup - dataChan := make(chan [][]string, len(endpointsMap)) - errorChan := make(chan error, len(endpointsMap)) - - for service, endpoint := range endpointsMap { - wg.Add(1) - go func(service, endpoint string) { - defer wg.Done() - result, err := fetchServiceResources(service, endpoint, shortNamesMap) - if err != nil { - errorChan <- fmt.Errorf("Error processing service %s: %v", service, err) - return - } - dataChan <- result - }(service, endpoint) - } - - wg.Wait() - close(dataChan) - close(errorChan) - - if len(errorChan) > 0 { - for err := range errorChan { - log.Println(err) - } - } - - var allData [][]string - for data := range dataChan { - allData = append(allData, data...) - } - - sort.Slice(allData, func(i, j int) bool { - return allData[i][0] < allData[j][0] - }) - - renderTable(allData) - }, -} - func getConfigDirectory() string { home, err := os.UserHomeDir() if err != nil { From f4e0590ee1dcef00fb934033c567b54dab09dec5 Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Mon, 11 Nov 2024 18:50:56 +0900 Subject: [PATCH 5/5] 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=