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

Commit 87c708a

Browse files
authored
Merge pull request #77 from cdr/users
Add users ls command
2 parents 433da04 + 5e43b71 commit 87c708a

File tree

9 files changed

+142
-7
lines changed

9 files changed

+142
-7
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/envs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type envsCmd struct {
1414
func (cmd envsCmd) Spec() cli.CommandSpec {
1515
return cli.CommandSpec{
1616
Name: "envs",
17-
Desc: "get a list of active environment",
17+
Desc: "get a list of environments owned by the authenticated user",
1818
}
1919
}
2020

cmd/coder/logout.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type logoutCmd struct {
1717
func (cmd logoutCmd) Spec() cli.CommandSpec {
1818
return cli.CommandSpec{
1919
Name: "logout",
20-
Desc: "remote local authentication credentials (if any)",
20+
Desc: "remove local authentication credentials (if any)",
2121
}
2222
}
2323

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/shell.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func (cmd *shellCmd) Spec() cli.CommandSpec {
2727
return cli.CommandSpec{
2828
Name: "sh",
2929
Usage: "<env name> [<command [args...]>]",
30-
Desc: "executes a remote command on the environment\nIf no command is specified, the default shell is opened.",
30+
Desc: "execute a remote command on the environment\nIf no command is specified, the default shell is opened.",
3131
RawArgs: true,
3232
}
3333
}

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