diff --git a/cmd/apiResources.go b/cmd/apiResources.go index c042f3b..ed15dac 100644 --- a/cmd/apiResources.go +++ b/cmd/apiResources.go @@ -22,81 +22,6 @@ import ( "google.golang.org/protobuf/types/descriptorpb" ) -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) - } - - endpoints := viper.GetStringMapString("endpoints") - - var wg sync.WaitGroup - dataChan := make(chan [][]string, len(endpoints)) - errorChan := make(chan error, len(endpoints)) - - for service, endpoint := range endpoints { - wg.Add(1) - go func(service, endpoint string) { - defer wg.Done() - result, err := fetchServiceResources(service, endpoint) - if err != nil { - errorChan <- fmt.Errorf("Error processing service %s: %v", service, err) - return - } - dataChan <- result - }(service, endpoint) - } - - // Wait for all goroutines to finish - wg.Wait() - close(dataChan) - close(errorChan) - - // Handle errors - if len(errorChan) > 0 { - for err := range errorChan { - log.Println(err) - } - // Optionally exit the program - // log.Fatal("Failed to process one or more endpoints.") - } - - // Collect data - var allData [][]string - for data := range dataChan { - allData = append(allData, data...) - } - - // Sort the data by Service name - sort.Slice(allData, func(i, j int) bool { - return allData[i][0] < allData[j][0] - }) - - // Render table - table := pterm.TableData{{"Service", "Resource", "Short Names", "Verb"}} - table = append(table, allData...) - - pterm.DefaultTable.WithHasHeader().WithData(table).Render() - }, -} - -func init() { - rootCmd.AddCommand(apiResourcesCmd) -} - func fetchServiceResources(service, endpoint string) ([][]string, error) { // Configure gRPC connection based on TLS usage parts := strings.Split(endpoint, "://") @@ -182,7 +107,6 @@ func getServiceMethods(client grpc_reflection_v1alpha.ServerReflectionClient, se return []string{} } - // Extract method names from file descriptor methods := []string{} for _, fdBytes := range fileDescriptor.FileDescriptorProto { fd := &descriptorpb.FileDescriptorProto{} @@ -200,3 +124,138 @@ 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) + } + + endpoints := viper.GetStringMapString("endpoints") + + var wg sync.WaitGroup + dataChan := make(chan [][]string, len(endpoints)) + errorChan := make(chan error, len(endpoints)) + + for service, endpoint := range endpoints { + wg.Add(1) + go func(service, endpoint string) { + defer wg.Done() + result, err := fetchServiceResources(service, endpoint) + 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] + }) + + // Calculate the dynamic width for the "Verb" column + terminalWidth := pterm.GetTerminalWidth() + usedWidth := 30 + 20 + 15 // Estimated widths for Service, Resource, and Short Names + verbColumnWidth := terminalWidth - usedWidth + if verbColumnWidth < 20 { + verbColumnWidth = 20 // Minimum width for Verb column + } + + // Use unique colors for each service and its associated data + serviceColors := []pterm.Color{ + pterm.FgLightGreen, pterm.FgLightYellow, pterm.FgLightBlue, + pterm.FgLightMagenta, pterm.FgLightCyan, pterm.FgWhite, + } + + serviceColorMap := make(map[string]pterm.Color) + colorIndex := 0 + + table := pterm.TableData{{"Service", "Resource", "Short Names", "Verb"}} + + for _, row := range allData { + service := row[0] + // Assign a unique color to each service if not already assigned + if _, exists := serviceColorMap[service]; !exists { + serviceColorMap[service] = serviceColors[colorIndex] + colorIndex = (colorIndex + 1) % len(serviceColors) + } + + // Get the color for this service + color := serviceColorMap[service] + coloredStyle := pterm.NewStyle(color) + + // Color the entire row (Service, Resource, Short Names, Verb) + serviceColored := coloredStyle.Sprint(service) + resourceColored := coloredStyle.Sprint(row[1]) + shortNamesColored := coloredStyle.Sprint(row[2]) + + verbs := splitIntoLinesWithComma(row[3], verbColumnWidth) + for i, line := range verbs { + if i == 0 { + table = append(table, []string{serviceColored, resourceColored, shortNamesColored, coloredStyle.Sprint(line)}) + } else { + table = append(table, []string{"", "", "", coloredStyle.Sprint(line)}) + } + } + } + + // Render the table using pterm + pterm.DefaultTable.WithHasHeader().WithData(table).Render() + }, +} + +func splitIntoLinesWithComma(text string, maxWidth int) []string { + words := strings.Split(text, ", ") + var lines []string + var currentLine string + + for _, word := range words { + if len(currentLine)+len(word)+2 > maxWidth { // +2 accounts for the ", " separator + lines = append(lines, currentLine+",") + currentLine = word + } else { + if currentLine != "" { + currentLine += ", " + } + currentLine += word + } + } + if currentLine != "" { + lines = append(lines, currentLine) + } + + return lines +} + +func init() { + rootCmd.AddCommand(apiResourcesCmd) +}