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 1 commit
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
Next Next commit
Add users ls command
  • Loading branch information
cmoog committed Jul 29, 2020
commit 1a5ec825de5cfcba7d58b5fb72e24af1d82804cb
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)
}
}
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
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
}