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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy