mirror of
https://github.com/coder/coder.git
synced 2026-06-03 04:58:23 +00:00
c750695d83
This changes makes it so that we output the empty string for Format when there is no data. It turns out there are many places in the code where we have such handling, but in a way that would break the JSON formatter (since we'd output nothing on stdout or text rather than `[]`/`null`).
209 lines
4.3 KiB
Go
209 lines
4.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/jedib0t/go-pretty/v6/table"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/v2/cli/cliui"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/serpent"
|
|
)
|
|
|
|
func (r *RootCmd) userList() *serpent.Command {
|
|
formatter := cliui.NewOutputFormatter(
|
|
cliui.TableFormat([]codersdk.User{}, []string{"username", "email", "created at", "status"}),
|
|
cliui.JSONFormat(),
|
|
)
|
|
var githubUserID int64
|
|
|
|
cmd := &serpent.Command{
|
|
Use: "list",
|
|
Short: "Prints the list of users.",
|
|
Aliases: []string{"ls"},
|
|
Middleware: serpent.Chain(
|
|
serpent.RequireNArgs(0),
|
|
),
|
|
Options: serpent.OptionSet{
|
|
{
|
|
Name: "github-user-id",
|
|
Description: "Filter users by their GitHub user ID.",
|
|
Default: "",
|
|
Flag: "github-user-id",
|
|
Required: false,
|
|
Value: serpent.Int64Of(&githubUserID),
|
|
},
|
|
},
|
|
Handler: func(inv *serpent.Invocation) error {
|
|
client, err := r.InitClient(inv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req := codersdk.UsersRequest{}
|
|
if githubUserID != 0 {
|
|
req.Search = fmt.Sprintf("github_com_user_id:%d", githubUserID)
|
|
}
|
|
|
|
res, err := client.Users(inv.Context(), req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
out, err := formatter.Format(inv.Context(), res.Users)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if out == "" {
|
|
cliui.Infof(inv.Stderr, "No users found.")
|
|
return nil
|
|
}
|
|
|
|
_, err = fmt.Fprintln(inv.Stdout, out)
|
|
return err
|
|
},
|
|
}
|
|
|
|
formatter.AttachOptions(&cmd.Options)
|
|
return cmd
|
|
}
|
|
|
|
func (r *RootCmd) userSingle() *serpent.Command {
|
|
formatter := cliui.NewOutputFormatter(
|
|
&userShowFormat{},
|
|
cliui.JSONFormat(),
|
|
)
|
|
|
|
cmd := &serpent.Command{
|
|
Use: "show <username|user_id|'me'>",
|
|
Short: "Show a single user. Use 'me' to indicate the currently authenticated user.",
|
|
Long: FormatExamples(
|
|
Example{
|
|
Command: "coder users show me",
|
|
},
|
|
),
|
|
Middleware: serpent.Chain(
|
|
serpent.RequireNArgs(1),
|
|
),
|
|
Handler: func(inv *serpent.Invocation) error {
|
|
client, err := r.InitClient(inv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
user, err := client.User(inv.Context(), inv.Args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
orgNames := make([]string, len(user.OrganizationIDs))
|
|
for i, orgID := range user.OrganizationIDs {
|
|
org, err := client.Organization(inv.Context(), orgID)
|
|
if err != nil {
|
|
return xerrors.Errorf("get organization %q: %w", orgID.String(), err)
|
|
}
|
|
|
|
orgNames[i] = org.Name
|
|
}
|
|
|
|
out, err := formatter.Format(inv.Context(), userWithOrgNames{
|
|
User: user,
|
|
OrganizationNames: orgNames,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = fmt.Fprintln(inv.Stdout, out)
|
|
return err
|
|
},
|
|
}
|
|
|
|
formatter.AttachOptions(&cmd.Options)
|
|
return cmd
|
|
}
|
|
|
|
type userWithOrgNames struct {
|
|
codersdk.User
|
|
OrganizationNames []string `json:"organization_names"`
|
|
}
|
|
|
|
type userShowFormat struct{}
|
|
|
|
var _ cliui.OutputFormat = &userShowFormat{}
|
|
|
|
// ID implements OutputFormat.
|
|
func (*userShowFormat) ID() string {
|
|
return "table"
|
|
}
|
|
|
|
// AttachOptions implements OutputFormat.
|
|
func (*userShowFormat) AttachOptions(_ *serpent.OptionSet) {}
|
|
|
|
// Format implements OutputFormat.
|
|
func (*userShowFormat) Format(_ context.Context, out interface{}) (string, error) {
|
|
user, ok := out.(userWithOrgNames)
|
|
if !ok {
|
|
return "", xerrors.Errorf("expected type %T, got %T", user, out)
|
|
}
|
|
|
|
tw := cliui.Table()
|
|
addRow := func(name string, value interface{}) {
|
|
key := ""
|
|
if name != "" {
|
|
key = name + ":"
|
|
}
|
|
tw.AppendRow(table.Row{
|
|
key, value,
|
|
})
|
|
}
|
|
|
|
// Add rows for each of the user's fields.
|
|
addRow("ID", user.ID.String())
|
|
addRow("Username", user.Username)
|
|
addRow("Full name", user.Name)
|
|
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 _, orgName := range user.OrganizationNames {
|
|
key := ""
|
|
if firstOrg {
|
|
key = "Organizations"
|
|
firstOrg = false
|
|
}
|
|
|
|
addRow(key, orgName)
|
|
}
|
|
if firstOrg {
|
|
addRow("Organizations", "(none)")
|
|
}
|
|
|
|
return tw.Render(), nil
|
|
}
|