feat: improve coder users show output, add json format (#3176)

This commit is contained in:
Dean Sheather
2022-07-26 15:47:12 -05:00
committed by GitHub
parent aaf0da27ef
commit df20dd7374
4 changed files with 200 additions and 31 deletions
+2 -2
View File
@@ -476,8 +476,8 @@ download the server version with: 'curl -L https://coder.com/install.sh | sh -s
if !buildinfo.VersionsMatch(clientVersion, info.Version) {
warn := cliui.Styles.Warn.Copy().Align(lipgloss.Left)
// Trim the leading 'v', our install.sh script does not handle this case well.
_, _ = fmt.Fprintf(cmd.OutOrStdout(), warn.Render(fmtWarningText), clientVersion, info.Version, strings.TrimPrefix(info.CanonicalVersion(), "v"))
_, _ = fmt.Fprintln(cmd.OutOrStdout())
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), warn.Render(fmtWarningText), clientVersion, info.Version, strings.TrimPrefix(info.CanonicalVersion(), "v"))
_, _ = fmt.Fprintln(cmd.ErrOrStderr())
}
return nil
+115 -8
View File
@@ -1,15 +1,27 @@
package cli
import (
"context"
"encoding/json"
"fmt"
"io"
"time"
"github.com/charmbracelet/lipgloss"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"golang.org/x/xerrors"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/codersdk"
)
func userList() *cobra.Command {
var columns []string
var (
columns []string
outputFormat string
)
cmd := &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
@@ -23,17 +35,34 @@ func userList() *cobra.Command {
return err
}
_, err = fmt.Fprintln(cmd.OutOrStdout(), displayUsers(columns, users...))
out := ""
switch outputFormat {
case "table", "":
out = displayUsers(columns, users...)
case "json":
outBytes, err := json.Marshal(users)
if err != nil {
return xerrors.Errorf("marshal users to JSON: %w", err)
}
out = string(outBytes)
default:
return xerrors.Errorf(`unknown output format %q, only "table" and "json" are supported`, outputFormat)
}
_, err = fmt.Fprintln(cmd.OutOrStdout(), out)
return err
},
}
cmd.Flags().StringArrayVarP(&columns, "column", "c", []string{"username", "email", "created_at"},
"Specify a column to filter in the table.")
cmd.Flags().StringArrayVarP(&columns, "column", "c", []string{"username", "email", "created_at", "status"},
"Specify a column to filter in the table. Available columns are: id, username, email, created_at, status.")
cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "Output format. Available formats are: table, json.")
return cmd
}
func userSingle() *cobra.Command {
var columns []string
var outputFormat string
cmd := &cobra.Command{
Use: "show <username|user_id|'me'>",
Short: "Show a single user. Use 'me' to indicate the currently authenticated user.",
@@ -54,11 +83,89 @@ func userSingle() *cobra.Command {
return err
}
_, err = fmt.Fprintln(cmd.OutOrStdout(), displayUsers(columns, user))
out := ""
switch outputFormat {
case "table", "":
out = displayUser(cmd.Context(), cmd.ErrOrStderr(), client, user)
case "json":
outBytes, err := json.Marshal(user)
if err != nil {
return xerrors.Errorf("marshal user to JSON: %w", err)
}
out = string(outBytes)
default:
return xerrors.Errorf(`unknown output format %q, only "table" and "json" are supported`, outputFormat)
}
_, err = fmt.Fprintln(cmd.OutOrStdout(), out)
return err
},
}
cmd.Flags().StringArrayVarP(&columns, "column", "c", []string{"username", "email", "created_at"},
"Specify a column to filter in the table.")
cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "Output format. Available formats are: table, json.")
return cmd
}
func displayUser(ctx context.Context, stderr io.Writer, client *codersdk.Client, user codersdk.User) string {
tableWriter := cliui.Table()
addRow := func(name string, value interface{}) {
key := ""
if name != "" {
key = name + ":"
}
tableWriter.AppendRow(table.Row{
key, value,
})
}
// Add rows for each of the user's fields.
addRow("ID", user.ID.String())
addRow("Username", user.Username)
addRow("Email", user.Email)
addRow("Status", user.Status)
addRow("Created At", user.CreatedAt.Format(time.Stamp))
addRow("", "")
firstRole := true
for _, role := range user.Roles {
if role.DisplayName == "" {
// Skip roles with no display name.
continue
}
key := ""
if firstRole {
key = "Roles"
firstRole = false
}
addRow(key, role.DisplayName)
}
if firstRole {
addRow("Roles", "(none)")
}
addRow("", "")
firstOrg := true
for _, orgID := range user.OrganizationIDs {
org, err := client.Organization(ctx, orgID)
if err != nil {
warn := cliui.Styles.Warn.Copy().Align(lipgloss.Left)
_, _ = fmt.Fprintf(stderr, warn.Render("Could not fetch organization %s: %+v"), orgID, err)
continue
}
key := ""
if firstOrg {
key = "Organizations"
firstOrg = false
}
addRow(key, org.Name)
}
if firstOrg {
addRow("Organizations", "(none)")
}
return tableWriter.Render()
}
+82 -20
View File
@@ -1,7 +1,9 @@
package cli_test
import (
"bytes"
"context"
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
@@ -15,7 +17,7 @@ import (
func TestUserList(t *testing.T) {
t.Parallel()
t.Run("List", func(t *testing.T) {
t.Run("Table", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
@@ -31,6 +33,31 @@ func TestUserList(t *testing.T) {
require.NoError(t, <-errC)
pty.ExpectMatch("coder.com")
})
t.Run("JSON", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
cmd, root := clitest.New(t, "users", "list", "-o", "json")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
buf := bytes.NewBuffer(nil)
cmd.SetOut(buf)
go func() {
defer close(doneChan)
err := cmd.Execute()
assert.NoError(t, err)
}()
<-doneChan
var users []codersdk.User
err := json.Unmarshal(buf.Bytes(), &users)
require.NoError(t, err, "unmarshal JSON output")
require.Len(t, users, 1)
require.Contains(t, users[0].Email, "coder.com")
})
t.Run("NoURLFileErrorHasHelperText", func(t *testing.T) {
t.Parallel()
@@ -57,23 +84,58 @@ func TestUserList(t *testing.T) {
func TestUserShow(t *testing.T) {
t.Parallel()
ctx := context.Background()
client := coderdtest.New(t, nil)
admin := coderdtest.CreateFirstUser(t, client)
other := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
otherUser, err := other.User(ctx, codersdk.Me)
require.NoError(t, err, "fetch other user")
cmd, root := clitest.New(t, "users", "show", otherUser.Username)
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := cmd.Execute()
assert.NoError(t, err)
}()
pty.ExpectMatch(otherUser.Email)
<-doneChan
t.Run("Table", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
client := coderdtest.New(t, nil)
admin := coderdtest.CreateFirstUser(t, client)
other := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
otherUser, err := other.User(ctx, codersdk.Me)
require.NoError(t, err, "fetch other user")
cmd, root := clitest.New(t, "users", "show", otherUser.Username)
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := cmd.Execute()
assert.NoError(t, err)
}()
pty.ExpectMatch(otherUser.Email)
<-doneChan
})
t.Run("JSON", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
client := coderdtest.New(t, nil)
admin := coderdtest.CreateFirstUser(t, client)
other := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
otherUser, err := other.User(ctx, codersdk.Me)
require.NoError(t, err, "fetch other user")
cmd, root := clitest.New(t, "users", "show", otherUser.Username, "-o", "json")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
buf := bytes.NewBuffer(nil)
cmd.SetOut(buf)
go func() {
defer close(doneChan)
err := cmd.Execute()
assert.NoError(t, err)
}()
<-doneChan
var newUser codersdk.User
err = json.Unmarshal(buf.Bytes(), &newUser)
require.NoError(t, err, "unmarshal JSON output")
require.Equal(t, otherUser.ID, newUser.ID)
require.Equal(t, otherUser.Username, newUser.Username)
require.Equal(t, otherUser.Email, newUser.Email)
})
}
+1 -1
View File
@@ -35,7 +35,7 @@ func displayUsers(filterColumns []string, users ...codersdk.User) string {
tableWriter.AppendHeader(header)
tableWriter.SetColumnConfigs(cliui.FilterTableColumns(header, filterColumns))
tableWriter.SortBy([]table.SortBy{{
Name: "Username",
Name: "username",
}})
for _, user := range users {
tableWriter.AppendRow(table.Row{