Skip to content

Commit a60d781

Browse files
committed
feat: add command to list cluster access
1 parent 5a2b67a commit a60d781

File tree

2 files changed

+175
-0
lines changed

2 files changed

+175
-0
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package access
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"io"
7+
"strings"
8+
9+
"github.com/spf13/cobra"
10+
"gopkg.in/yaml.v3"
11+
12+
"github.com/intility/indev/internal/redact"
13+
"github.com/intility/indev/internal/telemetry"
14+
"github.com/intility/indev/internal/ux"
15+
"github.com/intility/indev/pkg/client"
16+
"github.com/intility/indev/pkg/clientset"
17+
"github.com/intility/indev/pkg/outputformat"
18+
)
19+
20+
func NewListCommand(set clientset.ClientSet) *cobra.Command {
21+
var (
22+
clusterName string
23+
clusterID string
24+
output = outputformat.Format("")
25+
)
26+
27+
cmd := &cobra.Command{
28+
Use: "list [cluster-name]",
29+
Short: "List cluster members",
30+
Long: `List all members who have access to a cluster and their roles.`,
31+
Args: cobra.MaximumNArgs(1),
32+
PreRunE: set.EnsureSignedInPreHook,
33+
RunE: func(cmd *cobra.Command, args []string) error {
34+
ctx, span := telemetry.StartSpan(cmd.Context(), "cluster.access.list")
35+
defer span.End()
36+
37+
cmd.SilenceUsage = true
38+
39+
// If positional argument is provided, use it (takes precedence)
40+
if len(args) > 0 {
41+
clusterName = args[0]
42+
}
43+
44+
// Resolve cluster ID
45+
resolvedClusterID, err := resolveClusterID(ctx, set, clusterName, clusterID)
46+
if err != nil {
47+
return err
48+
}
49+
50+
members, err := set.PlatformClient.GetClusterMembers(ctx, resolvedClusterID)
51+
if err != nil {
52+
return redact.Errorf("could not get cluster members: %w", redact.Safe(err))
53+
}
54+
55+
if len(members) == 0 {
56+
ux.Fprint(cmd.OutOrStdout(), "No members found\n")
57+
return nil
58+
}
59+
60+
if err = printMemberList(cmd.OutOrStdout(), output, members); err != nil {
61+
return redact.Errorf("could not print member list: %w", redact.Safe(err))
62+
}
63+
64+
return nil
65+
},
66+
}
67+
68+
cmd.Flags().StringVarP(&clusterName, "cluster", "c", "", "Name of the cluster")
69+
cmd.Flags().StringVar(&clusterID, "cluster-id", "", "ID of the cluster")
70+
cmd.Flags().VarP(&output, "output", "o", "Output format (wide, json, yaml)")
71+
72+
return cmd
73+
}
74+
75+
func resolveClusterID(ctx context.Context, set clientset.ClientSet, clusterName, clusterID string) (string, error) {
76+
// If cluster ID is provided directly, use it
77+
if clusterID != "" {
78+
return clusterID, nil
79+
}
80+
81+
// If no cluster name provided, return error
82+
if clusterName == "" {
83+
return "", redact.Errorf("cluster name or ID is required")
84+
}
85+
86+
// List clusters to find the one by name
87+
clusters, err := set.PlatformClient.ListClusters(ctx)
88+
if err != nil {
89+
return "", redact.Errorf("could not list clusters: %w", redact.Safe(err))
90+
}
91+
92+
// Find the cluster with the matching name
93+
for _, c := range clusters {
94+
if strings.EqualFold(c.Name, clusterName) {
95+
return c.ID, nil
96+
}
97+
}
98+
99+
return "", redact.Errorf("cluster not found: %s", clusterName)
100+
}
101+
102+
func printMemberList(writer io.Writer, format outputformat.Format, members []client.ClusterMember) error {
103+
var err error
104+
105+
switch format {
106+
case "wide":
107+
table := ux.TableFromObjects(members, func(member client.ClusterMember) []ux.Row {
108+
return []ux.Row{
109+
ux.NewRow("Name", member.Subject.Name),
110+
ux.NewRow("Type", member.Subject.Type),
111+
ux.NewRow("Roles", formatRoles(member.Roles)),
112+
ux.NewRow("Details", member.Subject.Details),
113+
}
114+
})
115+
116+
ux.Fprint(writer, "%s", table.String())
117+
118+
return nil
119+
case "json":
120+
enc := json.NewEncoder(writer)
121+
enc.SetIndent("", " ")
122+
err = enc.Encode(members)
123+
case "yaml":
124+
indent := 2
125+
enc := yaml.NewEncoder(writer)
126+
enc.SetIndent(indent)
127+
err = enc.Encode(members)
128+
default:
129+
table := ux.TableFromObjects(members, func(member client.ClusterMember) []ux.Row {
130+
return []ux.Row{
131+
ux.NewRow("Name", member.Subject.Name),
132+
ux.NewRow("Type", member.Subject.Type),
133+
ux.NewRow("Roles", formatRoles(member.Roles)),
134+
}
135+
})
136+
137+
ux.Fprint(writer, "%s", table.String())
138+
139+
return nil
140+
}
141+
142+
if err != nil {
143+
return redact.Errorf("output encoder failed: %w", redact.Safe(err))
144+
}
145+
146+
return nil
147+
}
148+
149+
func formatRoles(roles []client.ClusterMemberRole) string {
150+
if len(roles) == 0 {
151+
return ""
152+
}
153+
154+
roleStrings := make([]string, len(roles))
155+
for i, role := range roles {
156+
roleStrings[i] = string(role)
157+
}
158+
159+
return strings.Join(roleStrings, ", ")
160+
}

pkg/rootcommand/rootcommand.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/intility/indev/pkg/clientset"
1212
"github.com/intility/indev/pkg/commands/account"
1313
"github.com/intility/indev/pkg/commands/cluster"
14+
"github.com/intility/indev/pkg/commands/cluster/access"
1415
"github.com/intility/indev/pkg/commands/teams"
1516
"github.com/intility/indev/pkg/commands/teams/member"
1617
"github.com/intility/indev/pkg/commands/user"
@@ -70,6 +71,20 @@ func getClusterCommand(set clientset.ClientSet) *cobra.Command {
7071
cmd.AddCommand(cluster.NewLoginCommand(set))
7172
cmd.AddCommand(cluster.NewOpenCommand(set))
7273
cmd.AddCommand(cluster.NewStatusCommand(set))
74+
cmd.AddCommand(getAccessCommand(set))
75+
76+
return cmd
77+
}
78+
79+
func getAccessCommand(set clientset.ClientSet) *cobra.Command {
80+
cmd := &cobra.Command{
81+
Use: "access",
82+
Short: "Manage cluster access",
83+
Long: "Manage cluster access",
84+
Run: showHelp,
85+
}
86+
87+
cmd.AddCommand(access.NewListCommand(set))
7388

7489
return cmd
7590
}

0 commit comments

Comments
 (0)