Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 1a5ec82

Browse files
committed
Add users ls command
1 parent 433da04 commit 1a5ec82

File tree

6 files changed

+139
-4
lines changed

6 files changed

+139
-4
lines changed

ci/integration/integration_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package integration
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"os"
78
"os/exec"
@@ -11,6 +12,8 @@ import (
1112
"time"
1213

1314
"cdr.dev/coder-cli/ci/tcli"
15+
"cdr.dev/coder-cli/internal/entclient"
16+
"cdr.dev/slog"
1417
"cdr.dev/slog/sloggers/slogtest/assert"
1518
)
1619

@@ -110,6 +113,19 @@ func TestCoderCLI(t *testing.T) {
110113
tcli.Error(),
111114
)
112115

116+
var user entclient.User
117+
c.Run(ctx, `coder users ls -o json | jq -c '.[] | select( .username == "charlie")'`).Assert(t,
118+
tcli.Success(),
119+
jsonUnmarshals(&user),
120+
)
121+
assert.Equal(t, "user email is as expected", "charlie@coder.com", user.Email)
122+
assert.Equal(t, "username is as expected", "Charlie", user.Name)
123+
124+
c.Run(ctx, "coder users ls -o human | grep charlie").Assert(t,
125+
tcli.Success(),
126+
tcli.StdoutMatches("charlie"),
127+
)
128+
113129
c.Run(ctx, "coder logout").Assert(t,
114130
tcli.Success(),
115131
)
@@ -118,3 +134,11 @@ func TestCoderCLI(t *testing.T) {
118134
tcli.Error(),
119135
)
120136
}
137+
138+
func jsonUnmarshals(target interface{}) tcli.Assertion {
139+
return func(t *testing.T, r *tcli.CommandResult) {
140+
slog.Helper()
141+
err := json.Unmarshal(r.Stdout, target)
142+
assert.Success(t, "json unmarshals", err)
143+
}
144+
}

cmd/coder/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func (r *rootCmd) Subcommands() []cli.Command {
4242
&urlsCmd{},
4343
&versionCmd{},
4444
&configSSHCmd{},
45+
&usersCmd{},
4546
}
4647
}
4748

cmd/coder/users.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"reflect"
8+
"strings"
9+
"text/tabwriter"
10+
11+
"github.com/spf13/pflag"
12+
13+
"go.coder.com/cli"
14+
"go.coder.com/flog"
15+
)
16+
17+
type usersCmd struct {
18+
}
19+
20+
func (cmd usersCmd) Spec() cli.CommandSpec {
21+
return cli.CommandSpec{
22+
Name: "users",
23+
Usage: "[subcommand] <flags>",
24+
Desc: "interact with user accounts",
25+
}
26+
}
27+
28+
func (cmd usersCmd) Run(fl *pflag.FlagSet) {
29+
exitUsage(fl)
30+
}
31+
32+
func (cmd *usersCmd) Subcommands() []cli.Command {
33+
return []cli.Command{
34+
&listCmd{},
35+
}
36+
}
37+
38+
type listCmd struct {
39+
outputFmt string
40+
}
41+
42+
func tabDelimited(data interface{}) string {
43+
v := reflect.ValueOf(data)
44+
s := &strings.Builder{}
45+
for i := 0; i < v.NumField(); i++ {
46+
s.WriteString(fmt.Sprintf("%s\t", v.Field(i).Interface()))
47+
}
48+
return s.String()
49+
}
50+
51+
func (cmd *listCmd) Run(fl *pflag.FlagSet) {
52+
entClient := requireAuth()
53+
54+
users, err := entClient.Users()
55+
if err != nil {
56+
flog.Fatal("failed to get users: %v", err)
57+
}
58+
59+
switch cmd.outputFmt {
60+
case "human":
61+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
62+
for _, u := range users {
63+
_, err = fmt.Fprintln(w, tabDelimited(u))
64+
if err != nil {
65+
flog.Fatal("failed to write: %v", err)
66+
}
67+
}
68+
err = w.Flush()
69+
if err != nil {
70+
flog.Fatal("failed to flush writer: %v", err)
71+
}
72+
case "json":
73+
err = json.NewEncoder(os.Stdout).Encode(users)
74+
if err != nil {
75+
flog.Fatal("failed to encode users to json: %v", err)
76+
}
77+
default:
78+
exitUsage(fl)
79+
}
80+
81+
}
82+
83+
func (cmd *listCmd) RegisterFlags(fl *pflag.FlagSet) {
84+
fl.StringVarP(&cmd.outputFmt, "output", "o", "human", "output format (human | json)")
85+
}
86+
87+
func (cmd *listCmd) Spec() cli.CommandSpec {
88+
return cli.CommandSpec{
89+
Name: "ls",
90+
Usage: "<flags>",
91+
Desc: "list all users",
92+
}
93+
}

cmd/coder/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func (versionCmd) Spec() cli.CommandSpec {
1515
return cli.CommandSpec{
1616
Name: "version",
1717
Usage: "",
18-
Desc: "Print the currently installed CLI version",
18+
Desc: "print the currently installed CLI version",
1919
}
2020
}
2121

internal/entclient/me.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package entclient
22

3+
import (
4+
"time"
5+
)
6+
37
// User describes a Coder user account
48
type User struct {
5-
ID string `json:"id"`
6-
Email string `json:"email"`
7-
Username string `json:"username"`
9+
ID string `json:"id"`
10+
Email string `json:"email"`
11+
Username string `json:"username"`
12+
Name string `json:"name"`
13+
CreatedAt time.Time `json:"created_at"`
814
}
915

1016
// Me gets the details of the authenticated user

internal/entclient/users.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package entclient
2+
3+
// Users gets the list of user accounts
4+
func (c Client) Users() ([]User, error) {
5+
var u []User
6+
err := c.requestBody("GET", "/api/users", nil, &u)
7+
if err != nil {
8+
return nil, err
9+
}
10+
return u, nil
11+
}

0 commit comments

Comments
 (0)