Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update api-resources subcommand using pterm and show verbs by dynamic tab size #7

Merged
merged 1 commit into from
Nov 6, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
Loading