Skip to content

Commit

Permalink
Merge pull request #7 from yjinjo/v0
Browse files Browse the repository at this point in the history
Update api-resources subcommand using pterm and show verbs by dynamic tab size
  • Loading branch information
yjinjo authored Nov 6, 2024
2 parents 40cbb8a + 054fca1 commit 964f4b9
Showing 1 changed file with 135 additions and 76 deletions.
211 changes: 135 additions & 76 deletions cmd/apiResources.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, "://")
Expand Down Expand Up @@ -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{}
Expand All @@ -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)
}

0 comments on commit 964f4b9

Please sign in to comment.