mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: add edit-role within user command (#17341)
This commit is contained in:
+8
-7
@@ -8,15 +8,16 @@ USAGE:
|
||||
Aliases: user
|
||||
|
||||
SUBCOMMANDS:
|
||||
activate Update a user's status to 'active'. Active users can fully
|
||||
interact with the platform
|
||||
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.
|
||||
delete Delete a user by username or user_id.
|
||||
edit-roles Edit a user's roles by username or id
|
||||
list
|
||||
show Show a single user. Use 'me' to indicate the currently
|
||||
authenticated user.
|
||||
suspend Update a user's status to 'suspended'. A suspended user cannot
|
||||
log into the platform
|
||||
show Show a single user. Use 'me' to indicate the currently
|
||||
authenticated user.
|
||||
suspend Update a user's status to 'suspended'. A suspended user cannot
|
||||
log into the platform
|
||||
|
||||
———
|
||||
Run `coder --help` for a list of global options.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
coder v0.0.0-devel
|
||||
|
||||
USAGE:
|
||||
coder users edit-roles [flags] <username|user_id>
|
||||
|
||||
Edit a user's roles by username or id
|
||||
|
||||
OPTIONS:
|
||||
--roles string-array
|
||||
A list of roles to give to the user. This removes any existing roles
|
||||
the user may have. The available roles are: auditor, member, owner,
|
||||
template-admin, user-admin.
|
||||
|
||||
-y, --yes bool
|
||||
Bypass prompts.
|
||||
|
||||
———
|
||||
Run `coder --help` for a list of global options.
|
||||
@@ -0,0 +1,90 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/cli/cliui"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/serpent"
|
||||
)
|
||||
|
||||
func (r *RootCmd) userEditRoles() *serpent.Command {
|
||||
client := new(codersdk.Client)
|
||||
|
||||
roles := rbac.SiteRoles()
|
||||
|
||||
siteRoles := make([]string, 0)
|
||||
for _, role := range roles {
|
||||
siteRoles = append(siteRoles, role.Identifier.Name)
|
||||
}
|
||||
sort.Strings(siteRoles)
|
||||
|
||||
var givenRoles []string
|
||||
|
||||
cmd := &serpent.Command{
|
||||
Use: "edit-roles <username|user_id>",
|
||||
Short: "Edit a user's roles by username or id",
|
||||
Options: []serpent.Option{
|
||||
cliui.SkipPromptOption(),
|
||||
{
|
||||
Name: "roles",
|
||||
Description: fmt.Sprintf("A list of roles to give to the user. This removes any existing roles the user may have. The available roles are: %s.", strings.Join(siteRoles, ", ")),
|
||||
Flag: "roles",
|
||||
Value: serpent.StringArrayOf(&givenRoles),
|
||||
},
|
||||
},
|
||||
Middleware: serpent.Chain(serpent.RequireNArgs(1), r.InitClient(client)),
|
||||
Handler: func(inv *serpent.Invocation) error {
|
||||
ctx := inv.Context()
|
||||
|
||||
user, err := client.User(ctx, inv.Args[0])
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch user: %w", err)
|
||||
}
|
||||
|
||||
userRoles, err := client.UserRoles(ctx, user.Username)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch user roles: %w", err)
|
||||
}
|
||||
|
||||
var selectedRoles []string
|
||||
if len(givenRoles) > 0 {
|
||||
// Make sure all of the given roles are valid site roles
|
||||
for _, givenRole := range givenRoles {
|
||||
if !slices.Contains(siteRoles, givenRole) {
|
||||
siteRolesPretty := strings.Join(siteRoles, ", ")
|
||||
return xerrors.Errorf("The role %s is not valid. Please use one or more of the following roles: %s\n", givenRole, siteRolesPretty)
|
||||
}
|
||||
}
|
||||
|
||||
selectedRoles = givenRoles
|
||||
} else {
|
||||
selectedRoles, err = cliui.MultiSelect(inv, cliui.MultiSelectOptions{
|
||||
Message: "Select the roles you'd like to assign to the user",
|
||||
Options: siteRoles,
|
||||
Defaults: userRoles.Roles,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("selecting roles for user: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = client.UpdateUserRoles(ctx, user.Username, codersdk.UpdateRoles{
|
||||
Roles: selectedRoles,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update user roles: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"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/coderd/rbac"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
var roles = []string{"auditor", "user-admin"}
|
||||
|
||||
func TestUserEditRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("UpdateUserRoles", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := coderdtest.New(t, nil)
|
||||
owner := coderdtest.CreateFirstUser(t, client)
|
||||
userAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleOwner())
|
||||
_, member := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleMember())
|
||||
|
||||
inv, root := clitest.New(t, "users", "edit-roles", member.Username, fmt.Sprintf("--roles=%s", strings.Join(roles, ",")))
|
||||
clitest.SetupConfig(t, userAdmin, root)
|
||||
|
||||
// Create context with timeout
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
memberRoles, err := client.UserRoles(ctx, member.Username)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.ElementsMatch(t, memberRoles.Roles, roles)
|
||||
})
|
||||
|
||||
t.Run("UserNotFound", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := coderdtest.New(t, nil)
|
||||
owner := coderdtest.CreateFirstUser(t, client)
|
||||
userAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleUserAdmin())
|
||||
|
||||
// Setup command with non-existent user
|
||||
inv, root := clitest.New(t, "users", "edit-roles", "nonexistentuser")
|
||||
clitest.SetupConfig(t, userAdmin, root)
|
||||
|
||||
// Create context with timeout
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "fetch user")
|
||||
})
|
||||
}
|
||||
@@ -18,6 +18,7 @@ func (r *RootCmd) users() *serpent.Command {
|
||||
r.userList(),
|
||||
r.userSingle(),
|
||||
r.userDelete(),
|
||||
r.userEditRoles(),
|
||||
r.createUserStatusCommand(codersdk.UserStatusActive),
|
||||
r.createUserStatusCommand(codersdk.UserStatusSuspended),
|
||||
},
|
||||
|
||||
@@ -1605,6 +1605,11 @@
|
||||
"description": "Delete a user by username or user_id.",
|
||||
"path": "reference/cli/users_delete.md"
|
||||
},
|
||||
{
|
||||
"title": "users edit-roles",
|
||||
"description": "Edit a user's roles by username or id",
|
||||
"path": "reference/cli/users_edit-roles.md"
|
||||
},
|
||||
{
|
||||
"title": "users list",
|
||||
"path": "reference/cli/users_list.md"
|
||||
|
||||
Generated
+9
-8
@@ -15,11 +15,12 @@ coder users [subcommand]
|
||||
|
||||
## Subcommands
|
||||
|
||||
| Name | Purpose |
|
||||
|----------------------------------------------|---------------------------------------------------------------------------------------|
|
||||
| [<code>create</code>](./users_create.md) | |
|
||||
| [<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>delete</code>](./users_delete.md) | Delete a user by username or user_id. |
|
||||
| [<code>activate</code>](./users_activate.md) | Update a user's status to 'active'. Active users can fully interact with the platform |
|
||||
| [<code>suspend</code>](./users_suspend.md) | Update a user's status to 'suspended'. A suspended user cannot log into the platform |
|
||||
| Name | Purpose |
|
||||
|--------------------------------------------------|---------------------------------------------------------------------------------------|
|
||||
| [<code>create</code>](./users_create.md) | |
|
||||
| [<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>delete</code>](./users_delete.md) | Delete a user by username or user_id. |
|
||||
| [<code>edit-roles</code>](./users_edit-roles.md) | Edit a user's roles by username or id |
|
||||
| [<code>activate</code>](./users_activate.md) | Update a user's status to 'active'. Active users can fully interact with the platform |
|
||||
| [<code>suspend</code>](./users_suspend.md) | Update a user's status to 'suspended'. A suspended user cannot log into the platform |
|
||||
|
||||
Generated
+28
@@ -0,0 +1,28 @@
|
||||
<!-- DO NOT EDIT | GENERATED CONTENT -->
|
||||
# users edit-roles
|
||||
|
||||
Edit a user's roles by username or id
|
||||
|
||||
## Usage
|
||||
|
||||
```console
|
||||
coder users edit-roles [flags] <username|user_id>
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### -y, --yes
|
||||
|
||||
| | |
|
||||
|------|-------------------|
|
||||
| Type | <code>bool</code> |
|
||||
|
||||
Bypass prompts.
|
||||
|
||||
### --roles
|
||||
|
||||
| | |
|
||||
|------|---------------------------|
|
||||
| Type | <code>string-array</code> |
|
||||
|
||||
A list of roles to give to the user. This removes any existing roles the user may have. The available roles are: auditor, member, owner, template-admin, user-admin.
|
||||
Reference in New Issue
Block a user