Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 70b113d

Browse files
authored
feat: add edit-role within user command (#17341)
1 parent 57ddb3c commit 70b113d

8 files changed

+223
-17
lines changed

cli/testdata/coder_users_--help.golden

+10-9
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ USAGE:
88
Aliases: user
99

1010
SUBCOMMANDS:
11-
activate Update a user's status to 'active'. Active users can fully
12-
interact with the platform
13-
create
14-
delete Delete a user by username or user_id.
15-
list
16-
show Show a single user. Use 'me' to indicate the currently
17-
authenticated user.
18-
suspend Update a user's status to 'suspended'. A suspended user cannot
19-
log into the platform
11+
activate Update a user's status to 'active'. Active users can fully
12+
interact with the platform
13+
create
14+
delete Delete a user by username or user_id.
15+
edit-roles Edit a user's roles by username or id
16+
list
17+
show Show a single user. Use 'me' to indicate the currently
18+
authenticated user.
19+
suspend Update a user's status to 'suspended'. A suspended user cannot
20+
log into the platform
2021

2122
———
2223
Run `coder --help` for a list of global options.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
coder v0.0.0-devel
2+
3+
USAGE:
4+
coder users edit-roles [flags] <username|user_id>
5+
6+
Edit a user's roles by username or id
7+
8+
OPTIONS:
9+
--roles string-array
10+
A list of roles to give to the user. This removes any existing roles
11+
the user may have. The available roles are: auditor, member, owner,
12+
template-admin, user-admin.
13+
14+
-y, --yes bool
15+
Bypass prompts.
16+
17+
———
18+
Run `coder --help` for a list of global options.

cli/usereditroles.go

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"slices"
6+
"sort"
7+
"strings"
8+
9+
"golang.org/x/xerrors"
10+
11+
"github.com/coder/coder/v2/cli/cliui"
12+
"github.com/coder/coder/v2/coderd/rbac"
13+
"github.com/coder/coder/v2/codersdk"
14+
"github.com/coder/serpent"
15+
)
16+
17+
func (r *RootCmd) userEditRoles() *serpent.Command {
18+
client := new(codersdk.Client)
19+
20+
roles := rbac.SiteRoles()
21+
22+
siteRoles := make([]string, 0)
23+
for _, role := range roles {
24+
siteRoles = append(siteRoles, role.Identifier.Name)
25+
}
26+
sort.Strings(siteRoles)
27+
28+
var givenRoles []string
29+
30+
cmd := &serpent.Command{
31+
Use: "edit-roles <username|user_id>",
32+
Short: "Edit a user's roles by username or id",
33+
Options: []serpent.Option{
34+
cliui.SkipPromptOption(),
35+
{
36+
Name: "roles",
37+
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, ", ")),
38+
Flag: "roles",
39+
Value: serpent.StringArrayOf(&givenRoles),
40+
},
41+
},
42+
Middleware: serpent.Chain(serpent.RequireNArgs(1), r.InitClient(client)),
43+
Handler: func(inv *serpent.Invocation) error {
44+
ctx := inv.Context()
45+
46+
user, err := client.User(ctx, inv.Args[0])
47+
if err != nil {
48+
return xerrors.Errorf("fetch user: %w", err)
49+
}
50+
51+
userRoles, err := client.UserRoles(ctx, user.Username)
52+
if err != nil {
53+
return xerrors.Errorf("fetch user roles: %w", err)
54+
}
55+
56+
var selectedRoles []string
57+
if len(givenRoles) > 0 {
58+
// Make sure all of the given roles are valid site roles
59+
for _, givenRole := range givenRoles {
60+
if !slices.Contains(siteRoles, givenRole) {
61+
siteRolesPretty := strings.Join(siteRoles, ", ")
62+
return xerrors.Errorf("The role %s is not valid. Please use one or more of the following roles: %s\n", givenRole, siteRolesPretty)
63+
}
64+
}
65+
66+
selectedRoles = givenRoles
67+
} else {
68+
selectedRoles, err = cliui.MultiSelect(inv, cliui.MultiSelectOptions{
69+
Message: "Select the roles you'd like to assign to the user",
70+
Options: siteRoles,
71+
Defaults: userRoles.Roles,
72+
})
73+
if err != nil {
74+
return xerrors.Errorf("selecting roles for user: %w", err)
75+
}
76+
}
77+
78+
_, err = client.UpdateUserRoles(ctx, user.Username, codersdk.UpdateRoles{
79+
Roles: selectedRoles,
80+
})
81+
if err != nil {
82+
return xerrors.Errorf("update user roles: %w", err)
83+
}
84+
85+
return nil
86+
},
87+
}
88+
89+
return cmd
90+
}

cli/usereditroles_test.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package cli_test
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/coder/coder/v2/cli/clitest"
11+
"github.com/coder/coder/v2/coderd/coderdtest"
12+
"github.com/coder/coder/v2/coderd/rbac"
13+
"github.com/coder/coder/v2/testutil"
14+
)
15+
16+
var roles = []string{"auditor", "user-admin"}
17+
18+
func TestUserEditRoles(t *testing.T) {
19+
t.Parallel()
20+
21+
t.Run("UpdateUserRoles", func(t *testing.T) {
22+
t.Parallel()
23+
24+
client := coderdtest.New(t, nil)
25+
owner := coderdtest.CreateFirstUser(t, client)
26+
userAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleOwner())
27+
_, member := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleMember())
28+
29+
inv, root := clitest.New(t, "users", "edit-roles", member.Username, fmt.Sprintf("--roles=%s", strings.Join(roles, ",")))
30+
clitest.SetupConfig(t, userAdmin, root)
31+
32+
// Create context with timeout
33+
ctx := testutil.Context(t, testutil.WaitShort)
34+
35+
err := inv.WithContext(ctx).Run()
36+
require.NoError(t, err)
37+
38+
memberRoles, err := client.UserRoles(ctx, member.Username)
39+
require.NoError(t, err)
40+
41+
require.ElementsMatch(t, memberRoles.Roles, roles)
42+
})
43+
44+
t.Run("UserNotFound", func(t *testing.T) {
45+
t.Parallel()
46+
47+
client := coderdtest.New(t, nil)
48+
owner := coderdtest.CreateFirstUser(t, client)
49+
userAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleUserAdmin())
50+
51+
// Setup command with non-existent user
52+
inv, root := clitest.New(t, "users", "edit-roles", "nonexistentuser")
53+
clitest.SetupConfig(t, userAdmin, root)
54+
55+
// Create context with timeout
56+
ctx := testutil.Context(t, testutil.WaitShort)
57+
58+
err := inv.WithContext(ctx).Run()
59+
require.Error(t, err)
60+
require.Contains(t, err.Error(), "fetch user")
61+
})
62+
}

cli/users.go

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ func (r *RootCmd) users() *serpent.Command {
1818
r.userList(),
1919
r.userSingle(),
2020
r.userDelete(),
21+
r.userEditRoles(),
2122
r.createUserStatusCommand(codersdk.UserStatusActive),
2223
r.createUserStatusCommand(codersdk.UserStatusSuspended),
2324
},

docs/manifest.json

+5
Original file line numberDiff line numberDiff line change
@@ -1605,6 +1605,11 @@
16051605
"description": "Delete a user by username or user_id.",
16061606
"path": "reference/cli/users_delete.md"
16071607
},
1608+
{
1609+
"title": "users edit-roles",
1610+
"description": "Edit a user's roles by username or id",
1611+
"path": "reference/cli/users_edit-roles.md"
1612+
},
16081613
{
16091614
"title": "users list",
16101615
"path": "reference/cli/users_list.md"

docs/reference/cli/users.md

+9-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/cli/users_edit-roles.md

+28
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)