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

Add list users command #77

Merged
merged 2 commits into from
Jul 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions ci/integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package integration

import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
Expand All @@ -11,6 +12,8 @@ import (
"time"

"cdr.dev/coder-cli/ci/tcli"
"cdr.dev/coder-cli/internal/entclient"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest/assert"
)

Expand Down Expand Up @@ -110,6 +113,19 @@ func TestCoderCLI(t *testing.T) {
tcli.Error(),
)

var user entclient.User
c.Run(ctx, `coder users ls -o json | jq -c '.[] | select( .username == "charlie")'`).Assert(t,
tcli.Success(),
jsonUnmarshals(&user),
)
assert.Equal(t, "user email is as expected", "charlie@coder.com", user.Email)
assert.Equal(t, "username is as expected", "Charlie", user.Name)

c.Run(ctx, "coder users ls -o human | grep charlie").Assert(t,
tcli.Success(),
tcli.StdoutMatches("charlie"),
)

c.Run(ctx, "coder logout").Assert(t,
tcli.Success(),
)
Expand All @@ -118,3 +134,11 @@ func TestCoderCLI(t *testing.T) {
tcli.Error(),
)
}

func jsonUnmarshals(target interface{}) tcli.Assertion {
return func(t *testing.T, r *tcli.CommandResult) {
slog.Helper()
err := json.Unmarshal(r.Stdout, target)
assert.Success(t, "json unmarshals", err)
}
}
2 changes: 1 addition & 1 deletion cmd/coder/envs.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type envsCmd struct {
func (cmd envsCmd) Spec() cli.CommandSpec {
return cli.CommandSpec{
Name: "envs",
Desc: "get a list of active environment",
Desc: "get a list of environments owned by the authenticated user",
}
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/coder/logout.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type logoutCmd struct {
func (cmd logoutCmd) Spec() cli.CommandSpec {
return cli.CommandSpec{
Name: "logout",
Desc: "remote local authentication credentials (if any)",
Desc: "remove local authentication credentials (if any)",
}
}

Expand Down
1 change: 1 addition & 0 deletions cmd/coder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func (r *rootCmd) Subcommands() []cli.Command {
&urlsCmd{},
&versionCmd{},
&configSSHCmd{},
&usersCmd{},
}
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/coder/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func (cmd *shellCmd) Spec() cli.CommandSpec {
return cli.CommandSpec{
Name: "sh",
Usage: "<env name> [<command [args...]>]",
Desc: "executes a remote command on the environment\nIf no command is specified, the default shell is opened.",
Desc: "execute a remote command on the environment\nIf no command is specified, the default shell is opened.",
RawArgs: true,
}
}
Expand Down
93 changes: 93 additions & 0 deletions cmd/coder/users.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package main

import (
"encoding/json"
"fmt"
"os"
"reflect"
"strings"
"text/tabwriter"

"github.com/spf13/pflag"

"go.coder.com/cli"
"go.coder.com/flog"
)

type usersCmd struct {
}

func (cmd usersCmd) Spec() cli.CommandSpec {
return cli.CommandSpec{
Name: "users",
Usage: "[subcommand] <flags>",
Desc: "interact with user accounts",
}
}

func (cmd usersCmd) Run(fl *pflag.FlagSet) {
exitUsage(fl)
}

func (cmd *usersCmd) Subcommands() []cli.Command {
return []cli.Command{
&listCmd{},
}
}

type listCmd struct {
outputFmt string
}

func tabDelimited(data interface{}) string {
v := reflect.ValueOf(data)
s := &strings.Builder{}
for i := 0; i < v.NumField(); i++ {
s.WriteString(fmt.Sprintf("%s\t", v.Field(i).Interface()))
}
return s.String()
}

func (cmd *listCmd) Run(fl *pflag.FlagSet) {
entClient := requireAuth()

users, err := entClient.Users()
if err != nil {
flog.Fatal("failed to get users: %v", err)
}

switch cmd.outputFmt {
case "human":
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
for _, u := range users {
_, err = fmt.Fprintln(w, tabDelimited(u))
if err != nil {
flog.Fatal("failed to write: %v", err)
}
}
err = w.Flush()
if err != nil {
flog.Fatal("failed to flush writer: %v", err)
}
case "json":
err = json.NewEncoder(os.Stdout).Encode(users)
if err != nil {
flog.Fatal("failed to encode users to json: %v", err)
}
default:
exitUsage(fl)
}

}

func (cmd *listCmd) RegisterFlags(fl *pflag.FlagSet) {
fl.StringVarP(&cmd.outputFmt, "output", "o", "human", "output format (human | json)")
}

func (cmd *listCmd) Spec() cli.CommandSpec {
return cli.CommandSpec{
Name: "ls",
Usage: "<flags>",
Desc: "list all users",
}
}
2 changes: 1 addition & 1 deletion cmd/coder/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func (versionCmd) Spec() cli.CommandSpec {
return cli.CommandSpec{
Name: "version",
Usage: "",
Desc: "Print the currently installed CLI version",
Desc: "print the currently installed CLI version",
}
}

Expand Down
12 changes: 9 additions & 3 deletions internal/entclient/me.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package entclient

import (
"time"
)

// User describes a Coder user account
type User struct {
ID string `json:"id"`
Email string `json:"email"`
Username string `json:"username"`
ID string `json:"id"`
Email string `json:"email"`
Username string `json:"username"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
}

// Me gets the details of the authenticated user
Expand Down
11 changes: 11 additions & 0 deletions internal/entclient/users.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package entclient

// Users gets the list of user accounts
func (c Client) Users() ([]User, error) {
var u []User
err := c.requestBody("GET", "/api/users", nil, &u)
if err != nil {
return nil, err
}
return u, nil
}