Files
coder/cli/userlist.go
Edward Angert f9817af11f docs: add section on how to retrieve user list (#17798)
previews
- [admin/users](https://coder.com/docs/@export-coder-users/admin/users)
-
[reference/cli/users](https://coder.com/docs/@export-coder-users/reference/cli/users)

followup to slack thread:

> Tim
> what's the best way for customers to export a list of Coder users?
>
> @ericpaulsen
> the `/api/v2/users` API route returns all users in the deployment
(along with other information - email, status, username, etc.). from
<https://coder.com/docs/reference/api/users#get-users>


- adds an easy-to-find section to the admin/users doc
- updates the cli commands with short descriptions

---------

Co-authored-by: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com>
Co-authored-by: M Atif Ali <atif@coder.com>
2025-05-13 20:48:16 +00:00

198 lines
4.2 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(),
)
client := new(codersdk.Client)
var githubUserID int64
cmd := &serpent.Command{
Use: "list",
Short: "Prints the list of users.",
Aliases: []string{"ls"},
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
r.InitClient(client),
),
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 {
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
}
_, 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(),
)
client := new(codersdk.Client)
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),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
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
}