mirror of https://github.com/coder/coder.git
feat(cli): add `coder users delete` command (#10115)
This commit is contained in:
parent
24c80bf532
commit
9e622d00a6
|
@ -11,6 +11,7 @@ SUBCOMMANDS:
|
|||
activate Update a user's status to 'active'. Active users can fully
|
||||
interact with the platform
|
||||
create
|
||||
delete Delete a user by username or user_id.
|
||||
list
|
||||
show Show a single user. Use 'me' to indicate the currently
|
||||
authenticated user.
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
coder v0.0.0-devel
|
||||
|
||||
USAGE:
|
||||
coder users delete <username|user_id>
|
||||
|
||||
Delete a user by username or user_id.
|
||||
|
||||
Aliases: rm
|
||||
|
||||
———
|
||||
Run `coder --help` for a list of global options.
|
|
@ -6,8 +6,6 @@ USAGE:
|
|||
Update a user's status to 'suspended'. A suspended user cannot log into the
|
||||
platform
|
||||
|
||||
Aliases: rm, delete
|
||||
|
||||
$ coder users suspend example_user
|
||||
|
||||
OPTIONS:
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
package cli_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/cli/clitest"
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/cryptorand"
|
||||
"github.com/coder/coder/v2/pty/ptytest"
|
||||
)
|
||||
|
||||
func TestUserDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Username", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
client := coderdtest.New(t, nil)
|
||||
aUser := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
pw, err := cryptorand.String(16)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "colin5@coder.com",
|
||||
Username: "coolin",
|
||||
Password: pw,
|
||||
UserLoginType: codersdk.LoginTypePassword,
|
||||
OrganizationID: aUser.OrganizationID,
|
||||
DisableLogin: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
inv, root := clitest.New(t, "users", "delete", "coolin")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
errC := make(chan error)
|
||||
go func() {
|
||||
errC <- inv.Run()
|
||||
}()
|
||||
require.NoError(t, <-errC)
|
||||
pty.ExpectMatch("coolin")
|
||||
})
|
||||
|
||||
t.Run("UserID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
client := coderdtest.New(t, nil)
|
||||
aUser := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
pw, err := cryptorand.String(16)
|
||||
require.NoError(t, err)
|
||||
|
||||
user, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "colin5@coder.com",
|
||||
Username: "coolin",
|
||||
Password: pw,
|
||||
UserLoginType: codersdk.LoginTypePassword,
|
||||
OrganizationID: aUser.OrganizationID,
|
||||
DisableLogin: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
inv, root := clitest.New(t, "users", "delete", user.ID.String())
|
||||
clitest.SetupConfig(t, client, root)
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
errC := make(chan error)
|
||||
go func() {
|
||||
errC <- inv.Run()
|
||||
}()
|
||||
require.NoError(t, <-errC)
|
||||
pty.ExpectMatch("coolin")
|
||||
})
|
||||
|
||||
t.Run("UserID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
client := coderdtest.New(t, nil)
|
||||
aUser := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
pw, err := cryptorand.String(16)
|
||||
require.NoError(t, err)
|
||||
|
||||
user, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "colin5@coder.com",
|
||||
Username: "coolin",
|
||||
Password: pw,
|
||||
UserLoginType: codersdk.LoginTypePassword,
|
||||
OrganizationID: aUser.OrganizationID,
|
||||
DisableLogin: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
inv, root := clitest.New(t, "users", "delete", user.ID.String())
|
||||
clitest.SetupConfig(t, client, root)
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
errC := make(chan error)
|
||||
go func() {
|
||||
errC <- inv.Run()
|
||||
}()
|
||||
require.NoError(t, <-errC)
|
||||
pty.ExpectMatch("coolin")
|
||||
})
|
||||
|
||||
// TODO: reenable this test case. Fetching users without perms returns a
|
||||
// "user "testuser@coder.com" must be a member of at least one organization"
|
||||
// error.
|
||||
// t.Run("NoPerms", func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
// ctx := context.Background()
|
||||
// client := coderdtest.New(t, nil)
|
||||
// aUser := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
// pw, err := cryptorand.String(16)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// fmt.Println(aUser.OrganizationID)
|
||||
// toDelete, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
// Email: "colin5@coder.com",
|
||||
// Username: "coolin",
|
||||
// Password: pw,
|
||||
// UserLoginType: codersdk.LoginTypePassword,
|
||||
// OrganizationID: aUser.OrganizationID,
|
||||
// DisableLogin: false,
|
||||
// })
|
||||
// require.NoError(t, err)
|
||||
|
||||
// uClient, _ := coderdtest.CreateAnotherUser(t, client, aUser.OrganizationID)
|
||||
// _ = uClient
|
||||
// _ = toDelete
|
||||
|
||||
// inv, root := clitest.New(t, "users", "delete", "coolin")
|
||||
// clitest.SetupConfig(t, uClient, root)
|
||||
// require.ErrorContains(t, inv.Run(), "...")
|
||||
// })
|
||||
|
||||
t.Run("DeleteSelf", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
client := coderdtest.New(t, nil)
|
||||
aUser := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
pw, err := cryptorand.String(16)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
Email: "colin5@coder.com",
|
||||
Username: "coolin",
|
||||
Password: pw,
|
||||
UserLoginType: codersdk.LoginTypePassword,
|
||||
OrganizationID: aUser.OrganizationID,
|
||||
DisableLogin: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
coderdtest.CreateAnotherUser(t, client, aUser.OrganizationID)
|
||||
|
||||
inv, root := clitest.New(t, "users", "delete", "me")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
require.ErrorContains(t, inv.Run(), "You cannot delete yourself!")
|
||||
})
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/cli/clibase"
|
||||
"github.com/coder/coder/v2/cli/cliui"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/pretty"
|
||||
)
|
||||
|
||||
func (r *RootCmd) userDelete() *clibase.Cmd {
|
||||
client := new(codersdk.Client)
|
||||
cmd := &clibase.Cmd{
|
||||
Use: "delete <username|user_id>",
|
||||
Short: "Delete a user by username or user_id.",
|
||||
Middleware: clibase.Chain(
|
||||
clibase.RequireNArgs(1),
|
||||
r.InitClient(client),
|
||||
),
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
ctx := inv.Context()
|
||||
user, err := client.User(ctx, inv.Args[0])
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch user: %w", err)
|
||||
}
|
||||
|
||||
err = client.DeleteUser(ctx, user.ID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("delete user: %w", err)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(inv.Stderr,
|
||||
"Successfully deleted "+pretty.Sprint(cliui.DefaultStyles.Keyword, user.Username)+".",
|
||||
)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
|
@ -17,6 +17,7 @@ func (r *RootCmd) users() *clibase.Cmd {
|
|||
r.userCreate(),
|
||||
r.userList(),
|
||||
r.userSingle(),
|
||||
r.userDelete(),
|
||||
r.createUserStatusCommand(codersdk.UserStatusActive),
|
||||
r.createUserStatusCommand(codersdk.UserStatusSuspended),
|
||||
},
|
||||
|
|
|
@ -28,7 +28,6 @@ func (r *RootCmd) createUserStatusCommand(sdkStatus codersdk.UserStatus) *clibas
|
|||
case codersdk.UserStatusSuspended:
|
||||
verb = "suspend"
|
||||
pastVerb = "suspended"
|
||||
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))
|
||||
|
|
|
@ -3373,7 +3373,7 @@ const docTemplate = `{
|
|||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "User ID, name, or me",
|
||||
"description": "User ID, username, or me",
|
||||
"name": "user",
|
||||
"in": "path",
|
||||
"required": true
|
||||
|
|
|
@ -2961,7 +2961,7 @@
|
|||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "User ID, name, or me",
|
||||
"description": "User ID, username, or me",
|
||||
"name": "user",
|
||||
"in": "path",
|
||||
"required": true
|
||||
|
|
|
@ -501,7 +501,7 @@ func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) {
|
|||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Users
|
||||
// @Param user path string true "User ID, name, or me"
|
||||
// @Param user path string true "User ID, username, or me"
|
||||
// @Success 200 {object} codersdk.User
|
||||
// @Router /users/{user} [get]
|
||||
func (api *API) userByName(rw http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -346,9 +346,9 @@ curl -X GET http://coder-server:8080/api/v2/users/{user} \
|
|||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
| ------ | ---- | ------ | -------- | -------------------- |
|
||||
| `user` | path | string | true | User ID, name, or me |
|
||||
| Name | In | Type | Required | Description |
|
||||
| ------ | ---- | ------ | -------- | ------------------------ |
|
||||
| `user` | path | string | true | User ID, username, or me |
|
||||
|
||||
### Example responses
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ coder users [subcommand]
|
|||
| -------------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| [<code>activate</code>](./users_activate.md) | Update a user's status to 'active'. Active users can fully interact with the platform |
|
||||
| [<code>create</code>](./users_create.md) | |
|
||||
| [<code>delete</code>](./users_delete.md) | Delete a user by username or user_id. |
|
||||
| [<code>list</code>](./users_list.md) | |
|
||||
| [<code>show</code>](./users_show.md) | Show a single user. Use 'me' to indicate the currently authenticated user. |
|
||||
| [<code>suspend</code>](./users_suspend.md) | Update a user's status to 'suspended'. A suspended user cannot log into the platform |
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<!-- DO NOT EDIT | GENERATED CONTENT -->
|
||||
|
||||
# users delete
|
||||
|
||||
Delete a user by username or user_id.
|
||||
|
||||
Aliases:
|
||||
|
||||
- rm
|
||||
|
||||
## Usage
|
||||
|
||||
```console
|
||||
coder users delete <username|user_id>
|
||||
```
|
|
@ -4,11 +4,6 @@
|
|||
|
||||
Update a user's status to 'suspended'. A suspended user cannot log into the platform
|
||||
|
||||
Aliases:
|
||||
|
||||
- rm
|
||||
- delete
|
||||
|
||||
## Usage
|
||||
|
||||
```console
|
||||
|
|
|
@ -885,6 +885,11 @@
|
|||
"title": "users create",
|
||||
"path": "cli/users_create.md"
|
||||
},
|
||||
{
|
||||
"title": "users delete",
|
||||
"description": "Delete a user by username or user_id.",
|
||||
"path": "cli/users_delete.md"
|
||||
},
|
||||
{
|
||||
"title": "users list",
|
||||
"path": "cli/users_list.md"
|
||||
|
|
Loading…
Reference in New Issue