Skip to content

Commit ef4b552

Browse files
authored
Merge pull request #43 from intility/feat/users
Add command to get users
2 parents 9d9291b + 6d1f3b0 commit ef4b552

File tree

5 files changed

+177
-9
lines changed

5 files changed

+177
-9
lines changed

internal/ux/table.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package ux
22

3-
import "strings"
3+
import (
4+
"strings"
5+
"unicode/utf8"
6+
)
47

58
type Table struct {
69
Header []string
@@ -55,19 +58,22 @@ func (t *Table) calculateColumnWidths() map[int]int {
5558
// calculate the longest string in each column
5659
for i, row := range t.Rows {
5760
for j, cell := range row {
58-
if len(cell) > colWidths[j] {
59-
colWidths[j] = len(cell)
61+
cellWidth := utf8.RuneCountInString(cell)
62+
if cellWidth > colWidths[j] {
63+
colWidths[j] = cellWidth
6064
}
6165

62-
if len(t.Header[j]) > colWidths[j] {
63-
colWidths[j] = len(t.Header[j])
66+
headerWidth := utf8.RuneCountInString(t.Header[j])
67+
if headerWidth > colWidths[j] {
68+
colWidths[j] = headerWidth
6469
}
6570
}
6671

6772
if i == 0 {
6873
for j, header := range t.Header {
69-
if len(header) > colWidths[j] {
70-
colWidths[j] = len(header)
74+
headerWidth := utf8.RuneCountInString(header)
75+
if headerWidth > colWidths[j] {
76+
colWidths[j] = headerWidth
7177
}
7278
}
7379
}
@@ -85,15 +91,17 @@ func (t *Table) String() string {
8591
// print padded cells
8692
for i, header := range t.Header {
8793
s += header + " "
88-
s += strings.Repeat(" ", longestInCol[i]-len(header)) + "\t"
94+
headerWidth := utf8.RuneCountInString(header)
95+
s += strings.Repeat(" ", longestInCol[i]-headerWidth) + "\t"
8996
}
9097

9198
s += "\n"
9299

93100
for _, row := range t.Rows {
94101
for i, cell := range row {
95102
s += cell + " "
96-
s += strings.Repeat(" ", longestInCol[i]-len(cell)) + "\t"
103+
cellWidth := utf8.RuneCountInString(cell)
104+
s += strings.Repeat(" ", longestInCol[i]-cellWidth) + "\t"
97105
}
98106

99107
s += "\n"

pkg/client/client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,16 @@ type TeamsClient interface {
4141
GetTeamMembers(ctx context.Context, teamId string) ([]TeamMember, error)
4242
}
4343

44+
type UserClient interface {
45+
ListUsers(ctx context.Context) ([]User, error)
46+
}
47+
4448
type Client interface {
4549
ClusterClient
4650
IntegrationClient
4751
MeClient
4852
TeamsClient
53+
UserClient
4954
}
5055

5156
type RestClientOption func(*RestClient)

pkg/client/user.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"fmt"
6+
)
7+
8+
type User struct {
9+
ID string `json:"id"`
10+
Name string `json:"name"`
11+
UPN string `json:"upn"`
12+
Roles []string `json:"roles"`
13+
}
14+
15+
func (c *RestClient) ListUsers(ctx context.Context) ([]User, error) {
16+
var users []User
17+
18+
req, err := c.createAuthenticatedRequest(ctx, "GET", c.baseURI+"/api/v1/users", nil)
19+
if err != nil {
20+
return users, err
21+
}
22+
23+
if err = doRequest(c.httpClient, req, &users); err != nil {
24+
return users, fmt.Errorf("request failed: %w", err)
25+
}
26+
27+
return users, nil
28+
}

pkg/commands/user/list.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package user
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
"slices"
7+
"sort"
8+
"strings"
9+
10+
"github.com/spf13/cobra"
11+
"gopkg.in/yaml.v3"
12+
13+
"github.com/intility/indev/internal/redact"
14+
"github.com/intility/indev/internal/telemetry"
15+
"github.com/intility/indev/internal/ux"
16+
"github.com/intility/indev/pkg/client"
17+
"github.com/intility/indev/pkg/clientset"
18+
"github.com/intility/indev/pkg/outputformat"
19+
)
20+
21+
func NewListCommand(set clientset.ClientSet) *cobra.Command {
22+
output := outputformat.Format("")
23+
cmd := &cobra.Command{
24+
Use: "list",
25+
Short: "List all users",
26+
Long: `List all users in the Intility Developer Platform`,
27+
PreRunE: set.EnsureSignedInPreHook,
28+
RunE: func(cmd *cobra.Command, args []string) error {
29+
ctx, span := telemetry.StartSpan(cmd.Context(), "users.list")
30+
defer span.End()
31+
32+
cmd.SilenceUsage = true
33+
34+
users, err := set.PlatformClient.ListUsers(ctx)
35+
if err != nil {
36+
return redact.Errorf("could not list users: %w", redact.Safe(err))
37+
}
38+
39+
if len(users) == 0 {
40+
ux.Fprint(cmd.OutOrStdout(), "No users found\n")
41+
return nil
42+
}
43+
44+
if err = printUsersList(cmd.OutOrStdout(), output, users); err != nil {
45+
return redact.Errorf("could not print users list: %w", redact.Safe(err))
46+
}
47+
48+
return nil
49+
},
50+
}
51+
52+
cmd.Flags().VarP(&output, "output", "o", "Output format (wide, json, yaml)")
53+
54+
return cmd
55+
}
56+
57+
func printUsersList(writer io.Writer, format outputformat.Format, users []client.User) error {
58+
var err error
59+
60+
sortUsersByOwnerThenName(users)
61+
62+
switch format {
63+
case "wide":
64+
table := ux.TableFromObjects(users, func(user client.User) []ux.Row {
65+
return []ux.Row{
66+
ux.NewRow("Id", user.ID),
67+
ux.NewRow("Name", user.Name),
68+
ux.NewRow("UPN", user.UPN),
69+
ux.NewRow("Role", strings.Join(user.Roles, ",")),
70+
}
71+
})
72+
73+
ux.Fprint(writer, "%s", table.String())
74+
case "json":
75+
enc := json.NewEncoder(writer)
76+
enc.SetIndent("", " ")
77+
err = enc.Encode(users)
78+
case "yaml":
79+
indent := 2
80+
enc := yaml.NewEncoder(writer)
81+
enc.SetIndent(indent)
82+
err = enc.Encode(users)
83+
default:
84+
table := ux.TableFromObjects(users, func(user client.User) []ux.Row {
85+
return []ux.Row{
86+
ux.NewRow("Name", user.Name),
87+
ux.NewRow("UPN", user.UPN),
88+
ux.NewRow("Role", strings.Join(user.Roles, ",")),
89+
}
90+
})
91+
92+
ux.Fprint(writer, "%s", table.String())
93+
}
94+
95+
if err != nil {
96+
return redact.Errorf("output encoder failed: %w", redact.Safe(err))
97+
}
98+
99+
return nil
100+
}
101+
102+
func sortUsersByOwnerThenName(users []client.User) {
103+
sort.Slice(users, func(i, j int) bool {
104+
hasOwnerI := slices.Contains(users[i].Roles, "owner")
105+
hasOwnerJ := slices.Contains(users[j].Roles, "owner")
106+
107+
if hasOwnerI != hasOwnerJ {
108+
return hasOwnerI // Owners first
109+
}
110+
return users[i].Name < users[j].Name // Then by name
111+
})
112+
}

pkg/rootcommand/rootcommand.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/intility/indev/pkg/commands/account"
1313
"github.com/intility/indev/pkg/commands/cluster"
1414
"github.com/intility/indev/pkg/commands/teams"
15+
"github.com/intility/indev/pkg/commands/user"
1516
)
1617

1718
func GetRootCommand() *cobra.Command {
@@ -34,6 +35,7 @@ func GetRootCommand() *cobra.Command {
3435
rootCmd.AddCommand(getClusterCommand(clients))
3536
rootCmd.AddCommand(getAccountCommand(clients))
3637
rootCmd.AddCommand(getTeamsCommand(clients))
38+
rootCmd.AddCommand(getUserCommand(clients))
3739

3840
return rootCmd
3941
}
@@ -96,6 +98,19 @@ func getTeamsCommand(set clientset.ClientSet) *cobra.Command {
9698
return cmd
9799
}
98100

101+
func getUserCommand(set clientset.ClientSet) *cobra.Command {
102+
cmd := &cobra.Command{
103+
Use: "user",
104+
Short: "Manage Intility Developer Platform users",
105+
Long: "Manage Intility Developer Platform users",
106+
Run: showHelp,
107+
}
108+
109+
cmd.AddCommand(user.NewListCommand(set))
110+
111+
return cmd
112+
}
113+
99114
func showHelp(cmd *cobra.Command, args []string) {
100115
_, span := telemetry.StartSpan(cmd.Context(), cmd.Use)
101116
defer span.End()

0 commit comments

Comments
 (0)