Skip to content

Commit

Permalink
Implement list command for Cluster (#355)
Browse files Browse the repository at this point in the history
  • Loading branch information
mcncl authored Oct 10, 2024
1 parent a7904e6 commit 93d4825
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 78 deletions.
5 changes: 5 additions & 0 deletions internal/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package api_types

type RESTResponse[T any] struct {
Data []T `json:"data"`
}
15 changes: 0 additions & 15 deletions internal/cluster/cluster.go

This file was deleted.

78 changes: 45 additions & 33 deletions internal/cluster/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,65 @@ package cluster

import (
"context"
"fmt"
"sync"

"github.com/buildkite/cli/v3/internal/graphql"
"github.com/buildkite/cli/v3/pkg/cmd/factory"
"golang.org/x/sync/errgroup"
"github.com/buildkite/go-buildkite/v3/buildkite"
)

func QueryCluster(ctx context.Context, OrganizationSlug string, ClusterID string, f *factory.Factory) (*Cluster, error) {
q, err := graphql.GetClusterQueues(ctx, f.GraphQLClient, OrganizationSlug, ClusterID)
func GetQueues(ctx context.Context, f *factory.Factory, orgSlug string, clusterID string, lo *buildkite.ClusterQueuesListOptions) ([]buildkite.ClusterQueue, error) {
queues, _, err := f.RestAPIClient.ClusterQueues.List(orgSlug, clusterID, lo)
if err != nil {
return nil, fmt.Errorf("unable to read Cluster Queues: %s", err.Error())
return nil, err
}

ClusterDescription := q.Organization.Cluster.Description
cluster := Cluster{
OrganizationSlug: OrganizationSlug,
ClusterID: ClusterID,
Name: q.Organization.Cluster.Name,
Description: string(*ClusterDescription),
Queues: make([]Queue, len(q.Organization.Cluster.Queues.Edges)),
queuesResponse := make([]buildkite.ClusterQueue, len(queues))
var wg sync.WaitGroup
errChan := make(chan error, len(queues))
for i, q := range queues {
wg.Add(1)
go func(i int, q buildkite.ClusterQueue) {
defer wg.Done()
queuesResponse[i] = buildkite.ClusterQueue{
CreatedAt: q.CreatedAt,
CreatedBy: q.CreatedBy,
Description: q.Description,
DispatchPaused: q.DispatchPaused,
DispatchPausedAt: q.DispatchPausedAt,
DispatchPausedBy: q.DispatchPausedBy,
DispatchPausedNote: q.DispatchPausedNote,
ID: q.ID,
Key: q.Key,
URL: q.URL,
WebURL: q.WebURL,
}
}(i, q)
}

eg, ctx := errgroup.WithContext(ctx)

for i, edge := range q.Organization.Cluster.Queues.Edges {
go func() {
wg.Wait()
close(errChan)
}()

i, edge := i, edge
eg.Go(func() error {
agent, err := graphql.GetClusterQueueAgent(ctx, f.GraphQLClient, OrganizationSlug, []string{edge.Node.Id})
if err != nil {
return fmt.Errorf("unable to read Cluster Queue Agents %s: %s", edge.Node.Id, err.Error())
}

cluster.Queues[i] = Queue{
Id: edge.Node.Id,
Name: edge.Node.Key,
ActiveAgents: len(agent.Organization.Agents.Edges),
}
for err := range errChan {
if err != nil {
return nil, err
}
}

return nil
})
return queuesResponse, nil
}

func GetQueueAgentCount(ctx context.Context, f *factory.Factory, orgSlug string, queues ...buildkite.ClusterQueue) (int, error) {
queueIDs := []string{}
for _, q := range queues {
queueIDs = append(queueIDs, *q.ID)
}

if err := eg.Wait(); err != nil {
return nil, err
agent, err := graphql.GetClusterQueueAgent(ctx, f.GraphQLClient, orgSlug, queueIDs)
if err != nil {
return 0, err
}

return &cluster, nil
return len(agent.Organization.Agents.Edges), nil
}
42 changes: 18 additions & 24 deletions internal/cluster/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,36 @@ package cluster

import (
"bytes"
"context"
"fmt"
"strconv"

"github.com/buildkite/cli/v3/pkg/cmd/factory"
"github.com/buildkite/go-buildkite/v3/buildkite"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/table"
)

func ClusterSummary(ctx context.Context, OrganizationSlug string, ClusterID string, f *factory.Factory) (string, error) {
clusterSummary, err := QueryCluster(ctx, OrganizationSlug, ClusterID, f)
if err != nil {
return err.Error(), err
}
func ClusterViewTable(c ...buildkite.Cluster) string {
tableOut := &bytes.Buffer{}

bold := lipgloss.NewStyle().Bold(true)

t := table.New().Border(lipgloss.HiddenBorder()).StyleFunc(func(row, col int) lipgloss.Style {
t := table.New().Headers("Name", "ID", "Default Queue ID").Border(lipgloss.HiddenBorder()).StyleFunc(func(row, col int) lipgloss.Style {
return lipgloss.NewStyle().PaddingRight(2)
}).Headers("Queues", "No of agents")

if len(clusterSummary.ClusterID) > 0 {
fmt.Fprint(tableOut, bold.Render("Cluster name: "+clusterSummary.Name, "\n"))
fmt.Fprint(tableOut, bold.Render("\nCluster Description: "+clusterSummary.Description, "\n"))
})

if len(clusterSummary.Queues) == 0 {
fmt.Fprint(tableOut, "\n No Queues found for this cluster \n")
return tableOut.String(), nil
}
for _, queue := range clusterSummary.Queues {
t.Row(queue.Name, strconv.Itoa(queue.ActiveAgents))
if len(c) == 1 {
t.Row(*c[0].Name, *c[0].ID, *c[0].DefaultQueueID)
} else {
for _, cl := range c {
t.Row(*cl.Name, *cl.ID, *cl.DefaultQueueID)
}
}

fmt.Fprint(tableOut, t.Render())

return tableOut.String(), nil
return tableOut.String()
}

// func parseQueuesAsString(queues ...buildkite.ClusterQueue) string {
// queuesString := ""
// for _, q := range queues {
// queuesString = queuesString + *q.Key + ", "
// }
// return queuesString[:len(queuesString)-2]
// }
1 change: 1 addition & 0 deletions pkg/cmd/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func NewCmdCluster(f *factory.Factory) *cobra.Command {
PersistentPreRunE: validation.CheckValidConfiguration(f.Config),
}
cmd.AddCommand(NewCmdClusterView(f))
cmd.AddCommand(NewCmdClusterList(f))

return &cmd
}
89 changes: 89 additions & 0 deletions pkg/cmd/cluster/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package cluster

import (
"errors"
"fmt"
"os"
"sync"

"github.com/MakeNowJust/heredoc"
"github.com/buildkite/cli/v3/internal/cluster"
"github.com/buildkite/cli/v3/pkg/cmd/factory"
"github.com/buildkite/go-buildkite/v3/buildkite"
"github.com/spf13/cobra"
)

func NewCmdClusterList(f *factory.Factory) *cobra.Command {
cmd := cobra.Command{
DisableFlagsInUseLine: true,
Use: "list",
Short: "List all clusters.",
Long: heredoc.Doc(`
List the clusters for an organization.
`),
RunE: func(cmd *cobra.Command, args []string) error {
var listOptions buildkite.ClustersListOptions

clusters, err := listClusters(&listOptions, f)
if err != nil {
return err
}

summary := cluster.ClusterViewTable(clusters...)
fmt.Fprintf(os.Stdout, "%v\n", summary)

return nil
},
}

return &cmd
}

func listClusters(lo *buildkite.ClustersListOptions, f *factory.Factory) ([]buildkite.Cluster, error) {
clusters, _, err := f.RestAPIClient.Clusters.List(f.Config.OrganizationSlug(), lo)
if err != nil {
return nil, fmt.Errorf("error fetching cluster list: %v", err)
}

if len(clusters) < 1 {
return nil, errors.New("no clusters found in organization")
}

clusterList := make([]buildkite.Cluster, len(clusters))
var wg sync.WaitGroup
errChan := make(chan error, len(clusters))
for i, c := range clusters {
wg.Add(1)
go func(i int, c buildkite.Cluster) {
defer wg.Done()
clusterList[i] = buildkite.Cluster{
Color: c.Color,
CreatedAt: c.CreatedAt,
CreatedBy: c.CreatedBy,
DefaultQueueID: c.DefaultQueueID,
DefaultQueueURL: c.DefaultQueueURL,
Description: c.Description,
Emoji: c.Emoji,
GraphQLID: c.GraphQLID,
ID: c.ID,
Name: c.Name,
QueuesURL: c.QueuesURL,
URL: c.URL,
WebURL: c.WebURL,
}
}(i, c)
}

go func() {
wg.Wait()
close(errChan)
}()

for err := range errChan {
if err != nil {
return nil, err
}
}

return clusterList, nil
}
20 changes: 14 additions & 6 deletions pkg/cmd/cluster/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import (
"github.com/MakeNowJust/heredoc"
"github.com/buildkite/cli/v3/internal/cluster"
"github.com/buildkite/cli/v3/pkg/cmd/factory"
"github.com/buildkite/go-buildkite/v3/buildkite"
"github.com/charmbracelet/huh/spinner"
"github.com/spf13/cobra"
)

func NewCmdClusterView(f *factory.Factory) *cobra.Command {
cmd := cobra.Command{
DisableFlagsInUseLine: true,
Use: "view id ",
Use: "view <id>",
Args: cobra.MinimumNArgs(1),
Short: "View cluster information.",
Long: heredoc.Doc(`
Expand All @@ -22,23 +23,30 @@ func NewCmdClusterView(f *factory.Factory) *cobra.Command {
It accepts org slug and cluster id.
`),
RunE: func(cmd *cobra.Command, args []string) error {
var err error
orgSlug := f.Config.OrganizationSlug()
clusterID := args[0]
clusterRes, _, err := f.RestAPIClient.Clusters.Get(orgSlug, clusterID)

if err != nil {
return err
}

var err error
var output string
spinErr := spinner.New().
Title("Loading cluster information").
Action(func() {
output, err = cluster.ClusterSummary(cmd.Context(), orgSlug, clusterID, f)
output = cluster.ClusterViewTable(buildkite.Cluster{
Name: clusterRes.Name,
ID: clusterRes.ID,
DefaultQueueID: clusterRes.DefaultQueueID,
Color: clusterRes.Color,
})
}).
Run()
if spinErr != nil {
return spinErr
}
if err != nil {
return err
}

fmt.Fprintf(cmd.OutOrStdout(), "%s\n", output)

Expand Down
1 change: 1 addition & 0 deletions pkg/cmd/factory/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func init() {
}

func (a *gqlHTTPClient) Do(req *http.Request) (*http.Response, error) {

req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", a.token))
req.Header.Set("User-Agent", userAgent)
return a.client.Do(req)
Expand Down

0 comments on commit 93d4825

Please sign in to comment.