mirror of https://github.com/coder/coder.git
feat: Add suspend/active user to cli (#1422)
* feat: Add suspend/active user to cli * UserID is now a string and allows for username too
This commit is contained in:
parent
a77da8445e
commit
b55d83ca82
|
@ -2,12 +2,9 @@ package cli
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
|
@ -28,25 +25,11 @@ func userList() *cobra.Command {
|
|||
return err
|
||||
}
|
||||
|
||||
tableWriter := cliui.Table()
|
||||
header := table.Row{"Username", "Email", "Created At"}
|
||||
tableWriter.AppendHeader(header)
|
||||
tableWriter.SetColumnConfigs(cliui.FilterTableColumns(header, columns))
|
||||
tableWriter.SortBy([]table.SortBy{{
|
||||
Name: "Username",
|
||||
}})
|
||||
for _, user := range users {
|
||||
tableWriter.AppendRow(table.Row{
|
||||
user.Username,
|
||||
user.Email,
|
||||
user.CreatedAt.Format(time.Stamp),
|
||||
})
|
||||
}
|
||||
_, err = fmt.Fprintln(cmd.OutOrStdout(), tableWriter.Render())
|
||||
_, err = fmt.Fprintln(cmd.OutOrStdout(), displayUsers(columns, users...))
|
||||
return err
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringArrayVarP(&columns, "column", "c", nil,
|
||||
cmd.Flags().StringArrayVarP(&columns, "column", "c", []string{"username", "email", "created_at"},
|
||||
"Specify a column to filter in the table.")
|
||||
return cmd
|
||||
}
|
||||
|
|
40
cli/users.go
40
cli/users.go
|
@ -1,12 +1,48 @@
|
|||
package cli
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
func users() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Short: "Create, remove, and list users",
|
||||
Use: "users",
|
||||
}
|
||||
cmd.AddCommand(userCreate(), userList())
|
||||
cmd.AddCommand(
|
||||
userCreate(),
|
||||
userList(),
|
||||
createUserStatusCommand(codersdk.UserStatusActive),
|
||||
createUserStatusCommand(codersdk.UserStatusSuspended),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// displayUsers will return a table displaying all users passed in.
|
||||
// filterColumns must be a subset of the user fields and will determine which
|
||||
// columns to display
|
||||
func displayUsers(filterColumns []string, users ...codersdk.User) string {
|
||||
tableWriter := cliui.Table()
|
||||
header := table.Row{"id", "username", "email", "created_at", "status"}
|
||||
tableWriter.AppendHeader(header)
|
||||
tableWriter.SetColumnConfigs(cliui.FilterTableColumns(header, filterColumns))
|
||||
tableWriter.SortBy([]table.SortBy{{
|
||||
Name: "Username",
|
||||
}})
|
||||
for _, user := range users {
|
||||
tableWriter.AppendRow(table.Row{
|
||||
user.ID.String(),
|
||||
user.Username,
|
||||
user.Email,
|
||||
user.CreatedAt.Format(time.Stamp),
|
||||
user.Status,
|
||||
})
|
||||
}
|
||||
return tableWriter.Render()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
// createUserStatusCommand sets a user status.
|
||||
func createUserStatusCommand(sdkStatus codersdk.UserStatus) *cobra.Command {
|
||||
var verb string
|
||||
var aliases []string
|
||||
var short string
|
||||
switch sdkStatus {
|
||||
case codersdk.UserStatusActive:
|
||||
verb = "activate"
|
||||
aliases = []string{"active"}
|
||||
short = "Update a user's status to 'active'. Active users can fully interact with the platform"
|
||||
case codersdk.UserStatusSuspended:
|
||||
verb = "suspend"
|
||||
aliases = []string{"rm", "delete"}
|
||||
short = "Update a user's status to 'suspended'. A suspended user cannot log into the platform"
|
||||
default:
|
||||
panic(fmt.Sprintf("%s is not supported", sdkStatus))
|
||||
}
|
||||
|
||||
var (
|
||||
columns []string
|
||||
)
|
||||
cmd := &cobra.Command{
|
||||
Use: fmt.Sprintf("%s <username|user_id>", verb),
|
||||
Short: short,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Aliases: aliases,
|
||||
Example: fmt.Sprintf("coder users %s example_user", verb),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
client, err := createClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
identifier := args[0]
|
||||
if identifier == "" {
|
||||
return xerrors.Errorf("user identifier cannot be an empty string")
|
||||
}
|
||||
|
||||
user, err := client.User(cmd.Context(), identifier)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch user: %w", err)
|
||||
}
|
||||
|
||||
// Display the user
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), displayUsers(columns, user))
|
||||
|
||||
// User status is already set to this
|
||||
if user.Status == sdkStatus {
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "User status is already %q\n", sdkStatus)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prompt to confirm the action
|
||||
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
|
||||
Text: fmt.Sprintf("Are you sure you want to %s this user?", verb),
|
||||
IsConfirm: true,
|
||||
Default: "yes",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.UpdateUserStatus(cmd.Context(), user.ID.String(), sdkStatus)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("%s user: %w", verb, err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringArrayVarP(&columns, "column", "c", []string{"username", "email", "created_at", "status"},
|
||||
"Specify a column to filter in the table.")
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package cli_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/cli/clitest"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
func TestUserStatus(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
admin := coderdtest.CreateFirstUser(t, client)
|
||||
other := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
|
||||
otherUser, err := other.User(context.Background(), codersdk.Me)
|
||||
require.NoError(t, err, "fetch user")
|
||||
|
||||
//nolint:paralleltest
|
||||
t.Run("StatusSelf", func(t *testing.T) {
|
||||
cmd, root := clitest.New(t, "users", "suspend", "me")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
// Yes to the prompt
|
||||
cmd.SetIn(bytes.NewReader([]byte("yes\n")))
|
||||
err := cmd.Execute()
|
||||
// Expect an error, as you cannot suspend yourself
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "cannot suspend yourself")
|
||||
})
|
||||
|
||||
//nolint:paralleltest
|
||||
t.Run("StatusOther", func(t *testing.T) {
|
||||
require.Equal(t, otherUser.Status, codersdk.UserStatusActive, "start as active")
|
||||
|
||||
cmd, root := clitest.New(t, "users", "suspend", otherUser.Username)
|
||||
clitest.SetupConfig(t, client, root)
|
||||
// Yes to the prompt
|
||||
cmd.SetIn(bytes.NewReader([]byte("yes\n")))
|
||||
err := cmd.Execute()
|
||||
require.NoError(t, err, "suspend user")
|
||||
|
||||
// Check the user status
|
||||
otherUser, err = client.User(context.Background(), otherUser.Username)
|
||||
require.NoError(t, err, "fetch suspended user")
|
||||
require.Equal(t, otherUser.Status, codersdk.UserStatusSuspended, "suspended user")
|
||||
|
||||
// Set back to active. Try using a uuid as well
|
||||
cmd, root = clitest.New(t, "users", "activate", otherUser.ID.String())
|
||||
clitest.SetupConfig(t, client, root)
|
||||
// Yes to the prompt
|
||||
cmd.SetIn(bytes.NewReader([]byte("yes\n")))
|
||||
err = cmd.Execute()
|
||||
require.NoError(t, err, "suspend user")
|
||||
|
||||
// Check the user status
|
||||
otherUser, err = client.User(context.Background(), otherUser.ID.String())
|
||||
require.NoError(t, err, "fetch active user")
|
||||
require.Equal(t, otherUser.Status, codersdk.UserStatusActive, "active user")
|
||||
})
|
||||
}
|
|
@ -78,7 +78,7 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
|
|||
require.Empty(t, workspace.AutostartSchedule)
|
||||
|
||||
// Given: the workspace template has been updated
|
||||
orgs, err := client.OrganizationsByUser(ctx, workspace.OwnerID)
|
||||
orgs, err := client.OrganizationsByUser(ctx, workspace.OwnerID.String())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, orgs, 1)
|
||||
|
||||
|
|
|
@ -239,7 +239,10 @@ func New(options *Options) (http.Handler, func()) {
|
|||
r.Use(httpmw.ExtractUserParam(options.Database))
|
||||
r.Get("/", api.userByName)
|
||||
r.Put("/profile", api.putUserProfile)
|
||||
r.Put("/suspend", api.putUserSuspend)
|
||||
r.Route("/status", func(r chi.Router) {
|
||||
r.Put("/suspend", api.putUserStatus(database.UserStatusSuspended))
|
||||
r.Put("/active", api.putUserStatus(database.UserStatusActive))
|
||||
})
|
||||
r.Route("/password", func(r chi.Router) {
|
||||
r.Use(httpmw.WithRBACObject(rbac.ResourceUserPasswordRole))
|
||||
r.Put("/", authorize(api.putUserPassword, rbac.ActionUpdate))
|
||||
|
|
|
@ -254,7 +254,7 @@ func CreateAnotherUser(t *testing.T, client *codersdk.Client, organizationID uui
|
|||
}
|
||||
// TODO: @emyrk switch "other" to "client" when we support updating other
|
||||
// users.
|
||||
_, err := other.UpdateUserRoles(context.Background(), user.ID, codersdk.UpdateRoles{Roles: siteRoles})
|
||||
_, err := other.UpdateUserRoles(context.Background(), user.ID.String(), codersdk.UpdateRoles{Roles: siteRoles})
|
||||
require.NoError(t, err, "update site roles")
|
||||
|
||||
// Update org roles
|
||||
|
@ -262,7 +262,7 @@ func CreateAnotherUser(t *testing.T, client *codersdk.Client, organizationID uui
|
|||
organizationID, err := uuid.Parse(orgID)
|
||||
require.NoError(t, err, fmt.Sprintf("parse org id %q", orgID))
|
||||
// TODO: @Emyrk add the member to the organization if they do not already belong.
|
||||
_, err = other.UpdateOrganizationMemberRoles(context.Background(), organizationID, user.ID,
|
||||
_, err = other.UpdateOrganizationMemberRoles(context.Background(), organizationID, user.ID.String(),
|
||||
codersdk.UpdateRoles{Roles: append(roles, rbac.RoleOrgMember(organizationID))})
|
||||
require.NoError(t, err, "update org membership roles")
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ func TestGitSSHKey(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
client := coderdtest.New(t, nil)
|
||||
res := coderdtest.CreateFirstUser(t, client)
|
||||
key, err := client.GitSSHKey(ctx, res.UserID)
|
||||
key, err := client.GitSSHKey(ctx, res.UserID.String())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, key.PublicKey)
|
||||
})
|
||||
|
@ -32,7 +32,7 @@ func TestGitSSHKey(t *testing.T) {
|
|||
SSHKeygenAlgorithm: gitsshkey.AlgorithmEd25519,
|
||||
})
|
||||
res := coderdtest.CreateFirstUser(t, client)
|
||||
key, err := client.GitSSHKey(ctx, res.UserID)
|
||||
key, err := client.GitSSHKey(ctx, res.UserID.String())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, key.PublicKey)
|
||||
})
|
||||
|
@ -43,7 +43,7 @@ func TestGitSSHKey(t *testing.T) {
|
|||
SSHKeygenAlgorithm: gitsshkey.AlgorithmECDSA,
|
||||
})
|
||||
res := coderdtest.CreateFirstUser(t, client)
|
||||
key, err := client.GitSSHKey(ctx, res.UserID)
|
||||
key, err := client.GitSSHKey(ctx, res.UserID.String())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, key.PublicKey)
|
||||
})
|
||||
|
@ -54,7 +54,7 @@ func TestGitSSHKey(t *testing.T) {
|
|||
SSHKeygenAlgorithm: gitsshkey.AlgorithmRSA4096,
|
||||
})
|
||||
res := coderdtest.CreateFirstUser(t, client)
|
||||
key, err := client.GitSSHKey(ctx, res.UserID)
|
||||
key, err := client.GitSSHKey(ctx, res.UserID.String())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, key.PublicKey)
|
||||
})
|
||||
|
@ -65,10 +65,10 @@ func TestGitSSHKey(t *testing.T) {
|
|||
SSHKeygenAlgorithm: gitsshkey.AlgorithmEd25519,
|
||||
})
|
||||
res := coderdtest.CreateFirstUser(t, client)
|
||||
key1, err := client.GitSSHKey(ctx, res.UserID)
|
||||
key1, err := client.GitSSHKey(ctx, res.UserID.String())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, key1.PublicKey)
|
||||
key2, err := client.RegenerateGitSSHKey(ctx, res.UserID)
|
||||
key2, err := client.RegenerateGitSSHKey(ctx, res.UserID.String())
|
||||
require.NoError(t, err)
|
||||
require.GreaterOrEqual(t, key2.UpdatedAt, key1.UpdatedAt)
|
||||
require.NotEmpty(t, key2.PublicKey)
|
||||
|
|
|
@ -107,7 +107,7 @@ func TestListRoles(t *testing.T) {
|
|||
member := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
|
||||
orgAdmin := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID, rbac.RoleOrgAdmin(admin.OrganizationID))
|
||||
|
||||
otherOrg, err := client.CreateOrganization(ctx, admin.UserID, codersdk.CreateOrganizationRequest{
|
||||
otherOrg, err := client.CreateOrganization(ctx, admin.UserID.String(), codersdk.CreateOrganizationRequest{
|
||||
Name: "other",
|
||||
})
|
||||
require.NoError(t, err, "create org")
|
||||
|
|
|
@ -303,31 +303,40 @@ func (api *api) putUserProfile(rw http.ResponseWriter, r *http.Request) {
|
|||
httpapi.Write(rw, http.StatusOK, convertUser(updatedUserProfile, organizationIDs))
|
||||
}
|
||||
|
||||
func (api *api) putUserSuspend(rw http.ResponseWriter, r *http.Request) {
|
||||
user := httpmw.UserParam(r)
|
||||
func (api *api) putUserStatus(status database.UserStatus) func(rw http.ResponseWriter, r *http.Request) {
|
||||
return func(rw http.ResponseWriter, r *http.Request) {
|
||||
user := httpmw.UserParam(r)
|
||||
apiKey := httpmw.APIKey(r)
|
||||
if status == database.UserStatusSuspended && user.ID == apiKey.UserID {
|
||||
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
|
||||
Message: "You cannot suspend yourself",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
suspendedUser, err := api.Database.UpdateUserStatus(r.Context(), database.UpdateUserStatusParams{
|
||||
ID: user.ID,
|
||||
Status: database.UserStatusSuspended,
|
||||
UpdatedAt: database.Now(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("put user suspended: %s", err.Error()),
|
||||
suspendedUser, err := api.Database.UpdateUserStatus(r.Context(), database.UpdateUserStatusParams{
|
||||
ID: user.ID,
|
||||
Status: status,
|
||||
UpdatedAt: database.Now(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
organizations, err := userOrganizationIDs(r.Context(), api, user)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("get organization IDs: %s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("put user suspended: %s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, convertUser(suspendedUser, organizations))
|
||||
organizations, err := userOrganizationIDs(r.Context(), api, user)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("get organization IDs: %s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, convertUser(suspendedUser, organizations))
|
||||
}
|
||||
}
|
||||
|
||||
func (api *api) putUserPassword(rw http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -209,7 +209,7 @@ func TestUpdateUserProfile(t *testing.T) {
|
|||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
_, err := client.UpdateUserProfile(context.Background(), uuid.New(), codersdk.UpdateUserProfileRequest{
|
||||
_, err := client.UpdateUserProfile(context.Background(), uuid.New().String(), codersdk.UpdateUserProfileRequest{
|
||||
Username: "newusername",
|
||||
Email: "newemail@coder.com",
|
||||
})
|
||||
|
@ -295,7 +295,7 @@ func TestUpdateUserPassword(t *testing.T) {
|
|||
client := coderdtest.New(t, nil)
|
||||
admin := coderdtest.CreateFirstUser(t, client)
|
||||
member := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
|
||||
err := member.UpdateUserPassword(context.Background(), admin.UserID, codersdk.UpdateUserPasswordRequest{
|
||||
err := member.UpdateUserPassword(context.Background(), admin.UserID.String(), codersdk.UpdateUserPasswordRequest{
|
||||
Password: "newpassword",
|
||||
})
|
||||
require.Error(t, err, "member should not be able to update admin password")
|
||||
|
@ -312,7 +312,7 @@ func TestUpdateUserPassword(t *testing.T) {
|
|||
OrganizationID: admin.OrganizationID,
|
||||
})
|
||||
require.NoError(t, err, "create member")
|
||||
err = client.UpdateUserPassword(context.Background(), member.ID, codersdk.UpdateUserPasswordRequest{
|
||||
err = client.UpdateUserPassword(context.Background(), member.ID.String(), codersdk.UpdateUserPasswordRequest{
|
||||
Password: "newpassword",
|
||||
})
|
||||
require.NoError(t, err, "admin should be able to update member password")
|
||||
|
@ -339,7 +339,7 @@ func TestGrantRoles(t *testing.T) {
|
|||
})
|
||||
require.Error(t, err, "org role in site")
|
||||
|
||||
_, err = admin.UpdateUserRoles(ctx, uuid.New(), codersdk.UpdateRoles{
|
||||
_, err = admin.UpdateUserRoles(ctx, uuid.New().String(), codersdk.UpdateRoles{
|
||||
Roles: []string{rbac.RoleOrgMember(first.OrganizationID)},
|
||||
})
|
||||
require.Error(t, err, "user does not exist")
|
||||
|
@ -354,12 +354,12 @@ func TestGrantRoles(t *testing.T) {
|
|||
})
|
||||
require.Error(t, err, "role in org without membership")
|
||||
|
||||
_, err = member.UpdateUserRoles(ctx, first.UserID, codersdk.UpdateRoles{
|
||||
_, err = member.UpdateUserRoles(ctx, first.UserID.String(), codersdk.UpdateRoles{
|
||||
Roles: []string{rbac.RoleMember()},
|
||||
})
|
||||
require.Error(t, err, "member cannot change other's roles")
|
||||
|
||||
_, err = member.UpdateOrganizationMemberRoles(ctx, first.OrganizationID, first.UserID, codersdk.UpdateRoles{
|
||||
_, err = member.UpdateOrganizationMemberRoles(ctx, first.OrganizationID, first.UserID.String(), codersdk.UpdateRoles{
|
||||
Roles: []string{rbac.RoleMember()},
|
||||
})
|
||||
require.Error(t, err, "member cannot change other's org roles")
|
||||
|
@ -452,7 +452,7 @@ func TestPutUserSuspend(t *testing.T) {
|
|||
Password: "password",
|
||||
OrganizationID: me.OrganizationID,
|
||||
})
|
||||
user, err := client.SuspendUser(context.Background(), user.ID)
|
||||
user, err := client.UpdateUserStatus(context.Background(), user.Username, codersdk.UserStatusSuspended)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, user.Status, codersdk.UserStatusSuspended)
|
||||
})
|
||||
|
@ -462,10 +462,9 @@ func TestPutUserSuspend(t *testing.T) {
|
|||
client := coderdtest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
client.User(context.Background(), codersdk.Me)
|
||||
suspendedUser, err := client.SuspendUser(context.Background(), codersdk.Me)
|
||||
_, err := client.UpdateUserStatus(context.Background(), codersdk.Me, codersdk.UserStatusSuspended)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, suspendedUser.Status, codersdk.UserStatusSuspended)
|
||||
require.ErrorContains(t, err, "suspend yourself", "cannot suspend yourself")
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -490,7 +489,7 @@ func TestGetUser(t *testing.T) {
|
|||
client := coderdtest.New(t, nil)
|
||||
firstUser := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
user, err := client.User(context.Background(), firstUser.UserID)
|
||||
user, err := client.User(context.Background(), firstUser.UserID.String())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, firstUser.UserID, user.ID)
|
||||
require.Equal(t, firstUser.OrganizationID, user.OrganizationIDs[0])
|
||||
|
@ -501,10 +500,10 @@ func TestGetUser(t *testing.T) {
|
|||
|
||||
client := coderdtest.New(t, nil)
|
||||
firstUser := coderdtest.CreateFirstUser(t, client)
|
||||
exp, err := client.User(context.Background(), firstUser.UserID)
|
||||
exp, err := client.User(context.Background(), firstUser.UserID.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
user, err := client.UserByUsername(context.Background(), exp.Username)
|
||||
user, err := client.User(context.Background(), exp.Username)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, exp, user)
|
||||
})
|
||||
|
@ -530,9 +529,15 @@ func TestGetUsers(t *testing.T) {
|
|||
})
|
||||
t.Run("ActiveUsers", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
active := make([]codersdk.User, 0)
|
||||
client := coderdtest.New(t, nil)
|
||||
first := coderdtest.CreateFirstUser(t, client)
|
||||
active := make([]codersdk.User, 0)
|
||||
|
||||
firstUser, err := client.User(context.Background(), first.UserID.String())
|
||||
require.NoError(t, err, "")
|
||||
active = append(active, firstUser)
|
||||
|
||||
// Alice will be suspended
|
||||
alice, err := client.CreateUser(context.Background(), codersdk.CreateUserRequest{
|
||||
Email: "alice@email.com",
|
||||
Username: "alice",
|
||||
|
@ -540,7 +545,6 @@ func TestGetUsers(t *testing.T) {
|
|||
OrganizationID: first.OrganizationID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
active = append(active, alice)
|
||||
|
||||
bruno, err := client.CreateUser(context.Background(), codersdk.CreateUserRequest{
|
||||
Email: "bruno@email.com",
|
||||
|
@ -551,7 +555,7 @@ func TestGetUsers(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
active = append(active, bruno)
|
||||
|
||||
_, err = client.SuspendUser(context.Background(), first.UserID)
|
||||
_, err = client.UpdateUserStatus(context.Background(), alice.Username, codersdk.UserStatusSuspended)
|
||||
require.NoError(t, err)
|
||||
|
||||
users, err := client.Users(context.Background(), codersdk.UsersRequest{
|
||||
|
|
|
@ -297,7 +297,7 @@ func TestPostWorkspaceBuild(t *testing.T) {
|
|||
require.Equal(t, workspace.LatestBuild.ID.String(), build.BeforeID.String())
|
||||
coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID)
|
||||
|
||||
workspaces, err := client.WorkspacesByOwner(context.Background(), user.OrganizationID, user.UserID)
|
||||
workspaces, err := client.WorkspacesByOwner(context.Background(), user.OrganizationID, user.UserID.String())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, workspaces, 0)
|
||||
})
|
||||
|
|
|
@ -24,8 +24,8 @@ type AgentGitSSHKey struct {
|
|||
}
|
||||
|
||||
// GitSSHKey returns the user's git SSH public key.
|
||||
func (c *Client) GitSSHKey(ctx context.Context, userID uuid.UUID) (GitSSHKey, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/gitsshkey", uuidOrMe(userID)), nil)
|
||||
func (c *Client) GitSSHKey(ctx context.Context, user string) (GitSSHKey, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/gitsshkey", user), nil)
|
||||
if err != nil {
|
||||
return GitSSHKey{}, xerrors.Errorf("execute request: %w", err)
|
||||
}
|
||||
|
@ -40,8 +40,8 @@ func (c *Client) GitSSHKey(ctx context.Context, userID uuid.UUID) (GitSSHKey, er
|
|||
}
|
||||
|
||||
// RegenerateGitSSHKey will create a new SSH key pair for the user and return it.
|
||||
func (c *Client) RegenerateGitSSHKey(ctx context.Context, userID uuid.UUID) (GitSSHKey, error) {
|
||||
res, err := c.request(ctx, http.MethodPut, fmt.Sprintf("/api/v2/users/%s/gitsshkey", uuidOrMe(userID)), nil)
|
||||
func (c *Client) RegenerateGitSSHKey(ctx context.Context, user string) (GitSSHKey, error) {
|
||||
res, err := c.request(ctx, http.MethodPut, fmt.Sprintf("/api/v2/users/%s/gitsshkey", user), nil)
|
||||
if err != nil {
|
||||
return GitSSHKey{}, xerrors.Errorf("execute request: %w", err)
|
||||
}
|
||||
|
|
|
@ -201,8 +201,8 @@ func (c *Client) WorkspacesByOrganization(ctx context.Context, organizationID uu
|
|||
}
|
||||
|
||||
// WorkspacesByOwner returns all workspaces contained in the organization owned by the user.
|
||||
func (c *Client) WorkspacesByOwner(ctx context.Context, organizationID, userID uuid.UUID) ([]Workspace, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/workspaces/%s", organizationID, uuidOrMe(userID)), nil)
|
||||
func (c *Client) WorkspacesByOwner(ctx context.Context, organizationID uuid.UUID, user string) ([]Workspace, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/workspaces/%s", organizationID, user), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -217,8 +217,8 @@ func (c *Client) WorkspacesByOwner(ctx context.Context, organizationID, userID u
|
|||
}
|
||||
|
||||
// WorkspaceByOwnerAndName returns a workspace by the owner's UUID and the workspace's name.
|
||||
func (c *Client) WorkspaceByOwnerAndName(ctx context.Context, organization, owner uuid.UUID, name string) (Workspace, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/workspaces/%s/%s", organization, uuidOrMe(owner), name), nil)
|
||||
func (c *Client) WorkspaceByOwnerAndName(ctx context.Context, organization uuid.UUID, owner string, name string) (Workspace, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/workspaces/%s/%s", organization, owner, name), nil)
|
||||
if err != nil {
|
||||
return Workspace{}, err
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ func (c *Client) ListOrganizationRoles(ctx context.Context, org uuid.UUID) ([]Ro
|
|||
}
|
||||
|
||||
func (c *Client) CheckPermissions(ctx context.Context, checks UserAuthorizationRequest) (UserAuthorizationResponse, error) {
|
||||
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/authorization", uuidOrMe(Me)), checks)
|
||||
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/authorization", Me), checks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -8,10 +8,11 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// Me is used as a replacement for your own ID.
|
||||
var Me = uuid.Nil
|
||||
var Me = "me"
|
||||
|
||||
type UserStatus string
|
||||
|
||||
|
@ -198,8 +199,8 @@ func (c *Client) CreateUser(ctx context.Context, req CreateUserRequest) (User, e
|
|||
}
|
||||
|
||||
// UpdateUserProfile enables callers to update profile information
|
||||
func (c *Client) UpdateUserProfile(ctx context.Context, userID uuid.UUID, req UpdateUserProfileRequest) (User, error) {
|
||||
res, err := c.request(ctx, http.MethodPut, fmt.Sprintf("/api/v2/users/%s/profile", uuidOrMe(userID)), req)
|
||||
func (c *Client) UpdateUserProfile(ctx context.Context, user string, req UpdateUserProfileRequest) (User, error) {
|
||||
res, err := c.request(ctx, http.MethodPut, fmt.Sprintf("/api/v2/users/%s/profile", user), req)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
@ -207,13 +208,23 @@ func (c *Client) UpdateUserProfile(ctx context.Context, userID uuid.UUID, req Up
|
|||
if res.StatusCode != http.StatusOK {
|
||||
return User{}, readBodyAsError(res)
|
||||
}
|
||||
var user User
|
||||
return user, json.NewDecoder(res.Body).Decode(&user)
|
||||
var resp User
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
// SuspendUser enables callers to suspend a user
|
||||
func (c *Client) SuspendUser(ctx context.Context, userID uuid.UUID) (User, error) {
|
||||
res, err := c.request(ctx, http.MethodPut, fmt.Sprintf("/api/v2/users/%s/suspend", uuidOrMe(userID)), nil)
|
||||
// UpdateUserStatus sets the user status to the given status
|
||||
func (c *Client) UpdateUserStatus(ctx context.Context, user string, status UserStatus) (User, error) {
|
||||
path := fmt.Sprintf("/api/v2/users/%s/status/", user)
|
||||
switch status {
|
||||
case UserStatusActive:
|
||||
path += "active"
|
||||
case UserStatusSuspended:
|
||||
path += "suspend"
|
||||
default:
|
||||
return User{}, xerrors.Errorf("status %q is not supported", status)
|
||||
}
|
||||
|
||||
res, err := c.request(ctx, http.MethodPut, path, nil)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
@ -222,14 +233,14 @@ func (c *Client) SuspendUser(ctx context.Context, userID uuid.UUID) (User, error
|
|||
return User{}, readBodyAsError(res)
|
||||
}
|
||||
|
||||
var user User
|
||||
return user, json.NewDecoder(res.Body).Decode(&user)
|
||||
var resp User
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
// UpdateUserPassword updates a user password.
|
||||
// It calls PUT /users/{user}/password
|
||||
func (c *Client) UpdateUserPassword(ctx context.Context, userID uuid.UUID, req UpdateUserPasswordRequest) error {
|
||||
res, err := c.request(ctx, http.MethodPut, fmt.Sprintf("/api/v2/users/%s/password", uuidOrMe(userID)), req)
|
||||
func (c *Client) UpdateUserPassword(ctx context.Context, user string, req UpdateUserPasswordRequest) error {
|
||||
res, err := c.request(ctx, http.MethodPut, fmt.Sprintf("/api/v2/users/%s/password", user), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -242,8 +253,8 @@ func (c *Client) UpdateUserPassword(ctx context.Context, userID uuid.UUID, req U
|
|||
|
||||
// UpdateUserRoles grants the userID the specified roles.
|
||||
// Include ALL roles the user has.
|
||||
func (c *Client) UpdateUserRoles(ctx context.Context, userID uuid.UUID, req UpdateRoles) (User, error) {
|
||||
res, err := c.request(ctx, http.MethodPut, fmt.Sprintf("/api/v2/users/%s/roles", uuidOrMe(userID)), req)
|
||||
func (c *Client) UpdateUserRoles(ctx context.Context, user string, req UpdateRoles) (User, error) {
|
||||
res, err := c.request(ctx, http.MethodPut, fmt.Sprintf("/api/v2/users/%s/roles", user), req)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
@ -251,14 +262,14 @@ func (c *Client) UpdateUserRoles(ctx context.Context, userID uuid.UUID, req Upda
|
|||
if res.StatusCode != http.StatusOK {
|
||||
return User{}, readBodyAsError(res)
|
||||
}
|
||||
var user User
|
||||
return user, json.NewDecoder(res.Body).Decode(&user)
|
||||
var resp User
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
// UpdateOrganizationMemberRoles grants the userID the specified roles in an org.
|
||||
// Include ALL roles the user has.
|
||||
func (c *Client) UpdateOrganizationMemberRoles(ctx context.Context, organizationID, userID uuid.UUID, req UpdateRoles) (OrganizationMember, error) {
|
||||
res, err := c.request(ctx, http.MethodPut, fmt.Sprintf("/api/v2/organizations/%s/members/%s/roles", organizationID, uuidOrMe(userID)), req)
|
||||
func (c *Client) UpdateOrganizationMemberRoles(ctx context.Context, organizationID uuid.UUID, user string, req UpdateRoles) (OrganizationMember, error) {
|
||||
res, err := c.request(ctx, http.MethodPut, fmt.Sprintf("/api/v2/organizations/%s/members/%s/roles", organizationID, user), req)
|
||||
if err != nil {
|
||||
return OrganizationMember{}, err
|
||||
}
|
||||
|
@ -271,8 +282,8 @@ func (c *Client) UpdateOrganizationMemberRoles(ctx context.Context, organization
|
|||
}
|
||||
|
||||
// GetUserRoles returns all roles the user has
|
||||
func (c *Client) GetUserRoles(ctx context.Context, userID uuid.UUID) (UserRoles, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/roles", uuidOrMe(userID)), nil)
|
||||
func (c *Client) GetUserRoles(ctx context.Context, user string) (UserRoles, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/roles", user), nil)
|
||||
if err != nil {
|
||||
return UserRoles{}, err
|
||||
}
|
||||
|
@ -285,8 +296,8 @@ func (c *Client) GetUserRoles(ctx context.Context, userID uuid.UUID) (UserRoles,
|
|||
}
|
||||
|
||||
// CreateAPIKey generates an API key for the user ID provided.
|
||||
func (c *Client) CreateAPIKey(ctx context.Context, userID uuid.UUID) (*GenerateAPIKeyResponse, error) {
|
||||
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/keys", uuidOrMe(userID)), nil)
|
||||
func (c *Client) CreateAPIKey(ctx context.Context, user string) (*GenerateAPIKeyResponse, error) {
|
||||
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/keys", user), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -330,24 +341,14 @@ func (c *Client) Logout(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// User returns a user for the ID provided.
|
||||
// If the uuid is nil, the current user will be returned.
|
||||
func (c *Client) User(ctx context.Context, id uuid.UUID) (User, error) {
|
||||
return c.userByIdentifier(ctx, uuidOrMe(id))
|
||||
}
|
||||
|
||||
// UserByUsername returns a user for the username provided.
|
||||
func (c *Client) UserByUsername(ctx context.Context, username string) (User, error) {
|
||||
return c.userByIdentifier(ctx, username)
|
||||
}
|
||||
|
||||
func (c *Client) userByIdentifier(ctx context.Context, ident string) (User, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s", ident), nil)
|
||||
// User returns a user for the ID/username provided.
|
||||
func (c *Client) User(ctx context.Context, userIdent string) (User, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s", userIdent), nil)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode > http.StatusOK {
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return User{}, readBodyAsError(res)
|
||||
}
|
||||
var user User
|
||||
|
@ -380,8 +381,8 @@ func (c *Client) Users(ctx context.Context, req UsersRequest) ([]User, error) {
|
|||
}
|
||||
|
||||
// OrganizationsByUser returns all organizations the user is a member of.
|
||||
func (c *Client) OrganizationsByUser(ctx context.Context, userID uuid.UUID) ([]Organization, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/organizations", uuidOrMe(userID)), nil)
|
||||
func (c *Client) OrganizationsByUser(ctx context.Context, user string) ([]Organization, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/organizations", user), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -393,8 +394,8 @@ func (c *Client) OrganizationsByUser(ctx context.Context, userID uuid.UUID) ([]O
|
|||
return orgs, json.NewDecoder(res.Body).Decode(&orgs)
|
||||
}
|
||||
|
||||
func (c *Client) OrganizationByName(ctx context.Context, userID uuid.UUID, name string) (Organization, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/organizations/%s", uuidOrMe(userID), name), nil)
|
||||
func (c *Client) OrganizationByName(ctx context.Context, user string, name string) (Organization, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/organizations/%s", user, name), nil)
|
||||
if err != nil {
|
||||
return Organization{}, err
|
||||
}
|
||||
|
@ -407,8 +408,8 @@ func (c *Client) OrganizationByName(ctx context.Context, userID uuid.UUID, name
|
|||
}
|
||||
|
||||
// CreateOrganization creates an organization and adds the provided user as an admin.
|
||||
func (c *Client) CreateOrganization(ctx context.Context, userID uuid.UUID, req CreateOrganizationRequest) (Organization, error) {
|
||||
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/organizations", uuidOrMe(userID)), req)
|
||||
func (c *Client) CreateOrganization(ctx context.Context, user string, req CreateOrganizationRequest) (Organization, error) {
|
||||
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/organizations", user), req)
|
||||
if err != nil {
|
||||
return Organization{}, err
|
||||
}
|
||||
|
@ -439,8 +440,8 @@ func (c *Client) AuthMethods(ctx context.Context) (AuthMethods, error) {
|
|||
}
|
||||
|
||||
// WorkspacesByUser returns all workspaces a user has access to.
|
||||
func (c *Client) WorkspacesByUser(ctx context.Context, userID uuid.UUID) ([]Workspace, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/workspaces", uuidOrMe(userID)), nil)
|
||||
func (c *Client) WorkspacesByUser(ctx context.Context, user string) ([]Workspace, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/workspaces", user), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -453,13 +454,3 @@ func (c *Client) WorkspacesByUser(ctx context.Context, userID uuid.UUID) ([]Work
|
|||
var workspaces []Workspace
|
||||
return workspaces, json.NewDecoder(res.Body).Decode(&workspaces)
|
||||
}
|
||||
|
||||
// uuidOrMe returns the provided uuid as a string if it's valid, ortherwise
|
||||
// `me`.
|
||||
func uuidOrMe(id uuid.UUID) string {
|
||||
if id == Me {
|
||||
return "me"
|
||||
}
|
||||
|
||||
return id.String()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue