From d11c17bcc027172673d6be08317846c0d4921caa Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Thu, 10 Apr 2025 14:41:56 +0000 Subject: [PATCH 01/14] feat: add an alias for org edit-roles to the users cli path --- cli/users.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cli/users.go b/cli/users.go index 3e6173880c0a3..d24c625085d82 100644 --- a/cli/users.go +++ b/cli/users.go @@ -18,9 +18,18 @@ func (r *RootCmd) users() *serpent.Command { r.userList(), r.userSingle(), r.userDelete(), + r.userEditRoles(), r.createUserStatusCommand(codersdk.UserStatusActive), r.createUserStatusCommand(codersdk.UserStatusSuspended), }, } return cmd } + +func (r *RootCmd) userEditRoles() *serpent.Command { + orgContext := NewOrganizationContext() + cmd := r.assignOrganizationRoles(orgContext) + cmd.Short = "Edit a member's roles" + + return cmd +} From d6a1dc222ddb8c76b690d5aa075684f53ff53490 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Thu, 10 Apr 2025 14:54:45 +0000 Subject: [PATCH 02/14] chore: comment code --- cli/users.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/users.go b/cli/users.go index d24c625085d82..04876921ae5c7 100644 --- a/cli/users.go +++ b/cli/users.go @@ -26,6 +26,8 @@ func (r *RootCmd) users() *serpent.Command { return cmd } +// An alias for `organization members edit-roles` for single-organization +// deployments. func (r *RootCmd) userEditRoles() *serpent.Command { orgContext := NewOrganizationContext() cmd := r.assignOrganizationRoles(orgContext) From ff0603d7218aa541b0639e9cbdcc429f4dd6417d Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Thu, 10 Apr 2025 14:58:43 +0000 Subject: [PATCH 03/14] fix: make gen --- cli/testdata/coder_users_--help.golden | 19 ++++++++++--------- .../coder_users_edit-roles_--help.golden | 11 +++++++++++ docs/manifest.json | 5 +++++ docs/reference/cli/users.md | 17 +++++++++-------- docs/reference/cli/users_edit-roles.md | 14 ++++++++++++++ .../terraform/testdata/resources/version.txt | 2 +- 6 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 cli/testdata/coder_users_edit-roles_--help.golden create mode 100644 docs/reference/cli/users_edit-roles.md diff --git a/cli/testdata/coder_users_--help.golden b/cli/testdata/coder_users_--help.golden index 338fea4febc86..9f178ec281c00 100644 --- a/cli/testdata/coder_users_--help.golden +++ b/cli/testdata/coder_users_--help.golden @@ -8,15 +8,16 @@ USAGE: Aliases: user 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. - suspend Update a user's status to 'suspended'. A suspended user cannot - log into 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. + edit-roles Edit a member's roles + 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 ——— Run `coder --help` for a list of global options. diff --git a/cli/testdata/coder_users_edit-roles_--help.golden b/cli/testdata/coder_users_edit-roles_--help.golden new file mode 100644 index 0000000000000..d6adc95b5a400 --- /dev/null +++ b/cli/testdata/coder_users_edit-roles_--help.golden @@ -0,0 +1,11 @@ +coder v0.0.0-devel + +USAGE: + coder users edit-roles [roles...] + + Edit a member's roles + + Aliases: edit-role + +——— +Run `coder --help` for a list of global options. diff --git a/docs/manifest.json b/docs/manifest.json index df535a1687807..c8e051182946a 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1592,6 +1592,11 @@ "description": "Delete a user by username or user_id.", "path": "reference/cli/users_delete.md" }, + { + "title": "users edit-roles", + "description": "Edit a member's roles", + "path": "reference/cli/users_edit-roles.md" + }, { "title": "users list", "path": "reference/cli/users_list.md" diff --git a/docs/reference/cli/users.md b/docs/reference/cli/users.md index 174e08fe9f3a0..d4d970ca1ada9 100644 --- a/docs/reference/cli/users.md +++ b/docs/reference/cli/users.md @@ -15,11 +15,12 @@ coder users [subcommand] ## Subcommands -| Name | Purpose | -|----------------------------------------------|---------------------------------------------------------------------------------------| -| [create](./users_create.md) | | -| [list](./users_list.md) | | -| [show](./users_show.md) | Show a single user. Use 'me' to indicate the currently authenticated user. | -| [delete](./users_delete.md) | Delete a user by username or user_id. | -| [activate](./users_activate.md) | Update a user's status to 'active'. Active users can fully interact with the platform | -| [suspend](./users_suspend.md) | Update a user's status to 'suspended'. A suspended user cannot log into the platform | +| Name | Purpose | +|--------------------------------------------------|---------------------------------------------------------------------------------------| +| [create](./users_create.md) | | +| [list](./users_list.md) | | +| [show](./users_show.md) | Show a single user. Use 'me' to indicate the currently authenticated user. | +| [delete](./users_delete.md) | Delete a user by username or user_id. | +| [edit-roles](./users_edit-roles.md) | Edit a member's roles | +| [activate](./users_activate.md) | Update a user's status to 'active'. Active users can fully interact with the platform | +| [suspend](./users_suspend.md) | Update a user's status to 'suspended'. A suspended user cannot log into the platform | diff --git a/docs/reference/cli/users_edit-roles.md b/docs/reference/cli/users_edit-roles.md new file mode 100644 index 0000000000000..cc08f01fb2fb5 --- /dev/null +++ b/docs/reference/cli/users_edit-roles.md @@ -0,0 +1,14 @@ + +# users edit-roles + +Edit a member's roles + +Aliases: + +* edit-role + +## Usage + +```console +coder users edit-roles [roles...] +``` diff --git a/provisioner/terraform/testdata/resources/version.txt b/provisioner/terraform/testdata/resources/version.txt index 0a5af26df3fdb..3d0e62313ced1 100644 --- a/provisioner/terraform/testdata/resources/version.txt +++ b/provisioner/terraform/testdata/resources/version.txt @@ -1 +1 @@ -1.11.3 +1.11.4 From 85ef42ee4da858761c82c97eb0c1ed7292a8ff77 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 11 Apr 2025 17:22:54 +0000 Subject: [PATCH 04/14] feat: create a command for updating user site-wide roles --- cli/usereditroles.go | 63 ++++++++++++++++++++++++++++++++++++++++++++ cli/userroles.go | 62 +++++++++++++++++++++++++++++++++++++++++++ cli/users.go | 10 ------- 3 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 cli/usereditroles.go create mode 100644 cli/userroles.go diff --git a/cli/usereditroles.go b/cli/usereditroles.go new file mode 100644 index 0000000000000..4fe46b2497d8b --- /dev/null +++ b/cli/usereditroles.go @@ -0,0 +1,63 @@ +package cli + +import ( + // "fmt" + "github.com/coder/coder/v2/cli/cliui" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/serpent" + "golang.org/x/xerrors" +) + +func (r *RootCmd) userEditRoles() *serpent.Command { + client := new(codersdk.Client) + cmd := &serpent.Command{ + Use: "edit-roles ", + Short: "Edit a user's roles by username or id", + 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) + } + + roles, err := client.ListSiteRoles(ctx) + if err != nil { + return xerrors.Errorf("fetch site roles: %w", err) + } + + var siteRoles = make([]string, 0) + for _, role := range roles { + if role.Assignable { + siteRoles = append(siteRoles, role.Name) + } + } + + userRoles, err := client.UserRoles(ctx, user.Username) + if err != nil { + return xerrors.Errorf("fetch user roles: %w", err) + } + + 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 +} diff --git a/cli/userroles.go b/cli/userroles.go new file mode 100644 index 0000000000000..1716f92864e49 --- /dev/null +++ b/cli/userroles.go @@ -0,0 +1,62 @@ +package cli + +import ( + "github.com/coder/coder/v2/cli/cliui" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/serpent" + "golang.org/x/xerrors" +) + +func (r *RootCmd) userEditRoles() *serpent.Command { + client := new(codersdk.Client) + cmd := &serpent.Command{ + Use: "edit-roles ", + Short: "Edit a user's roles by username or id", + 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) + } + + roles, err := client.ListSiteRoles(ctx) + if err != nil { + return xerrors.Errorf("fetch site roles: %w", err) + } + + var siteRoles = make([]string, 0) + for _, role := range roles { + if role.Assignable { + siteRoles = append(siteRoles, role.Name) + } + } + + userRoles, err := client.UserRoles(ctx, user.Username) + if err != nil { + return xerrors.Errorf("fetch user roles: %w", err) + } + + 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 +} diff --git a/cli/users.go b/cli/users.go index 04876921ae5c7..fa15fcddad0ee 100644 --- a/cli/users.go +++ b/cli/users.go @@ -25,13 +25,3 @@ func (r *RootCmd) users() *serpent.Command { } return cmd } - -// An alias for `organization members edit-roles` for single-organization -// deployments. -func (r *RootCmd) userEditRoles() *serpent.Command { - orgContext := NewOrganizationContext() - cmd := r.assignOrganizationRoles(orgContext) - cmd.Short = "Edit a member's roles" - - return cmd -} From 2b2e8fe91edcceb7ec9671039873a27d0c596075 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 11 Apr 2025 17:25:44 +0000 Subject: [PATCH 05/14] fix: remove duplicate file --- cli/usereditroles.go | 1 - cli/userroles.go | 62 -------------------------------------------- 2 files changed, 63 deletions(-) delete mode 100644 cli/userroles.go diff --git a/cli/usereditroles.go b/cli/usereditroles.go index 4fe46b2497d8b..1716f92864e49 100644 --- a/cli/usereditroles.go +++ b/cli/usereditroles.go @@ -1,7 +1,6 @@ package cli import ( - // "fmt" "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/codersdk" "github.com/coder/serpent" diff --git a/cli/userroles.go b/cli/userroles.go deleted file mode 100644 index 1716f92864e49..0000000000000 --- a/cli/userroles.go +++ /dev/null @@ -1,62 +0,0 @@ -package cli - -import ( - "github.com/coder/coder/v2/cli/cliui" - "github.com/coder/coder/v2/codersdk" - "github.com/coder/serpent" - "golang.org/x/xerrors" -) - -func (r *RootCmd) userEditRoles() *serpent.Command { - client := new(codersdk.Client) - cmd := &serpent.Command{ - Use: "edit-roles ", - Short: "Edit a user's roles by username or id", - 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) - } - - roles, err := client.ListSiteRoles(ctx) - if err != nil { - return xerrors.Errorf("fetch site roles: %w", err) - } - - var siteRoles = make([]string, 0) - for _, role := range roles { - if role.Assignable { - siteRoles = append(siteRoles, role.Name) - } - } - - userRoles, err := client.UserRoles(ctx, user.Username) - if err != nil { - return xerrors.Errorf("fetch user roles: %w", err) - } - - 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 -} From fc2ccf11ecb3d76d7cbe6e646b1bd8be38b928f0 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 11 Apr 2025 17:32:41 +0000 Subject: [PATCH 06/14] chore: make gen --- cli/testdata/coder_users_--help.golden | 2 +- cli/testdata/coder_users_edit-roles_--help.golden | 6 ++---- docs/manifest.json | 2 +- docs/reference/cli/users.md | 2 +- docs/reference/cli/users_edit-roles.md | 8 ++------ 5 files changed, 7 insertions(+), 13 deletions(-) diff --git a/cli/testdata/coder_users_--help.golden b/cli/testdata/coder_users_--help.golden index 9f178ec281c00..585588cbc6e18 100644 --- a/cli/testdata/coder_users_--help.golden +++ b/cli/testdata/coder_users_--help.golden @@ -12,7 +12,7 @@ SUBCOMMANDS: interact with the platform create delete Delete a user by username or user_id. - edit-roles Edit a member's roles + 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. diff --git a/cli/testdata/coder_users_edit-roles_--help.golden b/cli/testdata/coder_users_edit-roles_--help.golden index d6adc95b5a400..6c24f67c0988a 100644 --- a/cli/testdata/coder_users_edit-roles_--help.golden +++ b/cli/testdata/coder_users_edit-roles_--help.golden @@ -1,11 +1,9 @@ coder v0.0.0-devel USAGE: - coder users edit-roles [roles...] + coder users edit-roles - Edit a member's roles - - Aliases: edit-role + Edit a user's roles by username or id ——— Run `coder --help` for a list of global options. diff --git a/docs/manifest.json b/docs/manifest.json index c8e051182946a..be6fd393303f6 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1594,7 +1594,7 @@ }, { "title": "users edit-roles", - "description": "Edit a member's roles", + "description": "Edit a user's roles by username or id", "path": "reference/cli/users_edit-roles.md" }, { diff --git a/docs/reference/cli/users.md b/docs/reference/cli/users.md index d4d970ca1ada9..d942699d6ee31 100644 --- a/docs/reference/cli/users.md +++ b/docs/reference/cli/users.md @@ -21,6 +21,6 @@ coder users [subcommand] | [list](./users_list.md) | | | [show](./users_show.md) | Show a single user. Use 'me' to indicate the currently authenticated user. | | [delete](./users_delete.md) | Delete a user by username or user_id. | -| [edit-roles](./users_edit-roles.md) | Edit a member's roles | +| [edit-roles](./users_edit-roles.md) | Edit a user's roles by username or id | | [activate](./users_activate.md) | Update a user's status to 'active'. Active users can fully interact with the platform | | [suspend](./users_suspend.md) | Update a user's status to 'suspended'. A suspended user cannot log into the platform | diff --git a/docs/reference/cli/users_edit-roles.md b/docs/reference/cli/users_edit-roles.md index cc08f01fb2fb5..b8bcf190ac675 100644 --- a/docs/reference/cli/users_edit-roles.md +++ b/docs/reference/cli/users_edit-roles.md @@ -1,14 +1,10 @@ # users edit-roles -Edit a member's roles - -Aliases: - -* edit-role +Edit a user's roles by username or id ## Usage ```console -coder users edit-roles [roles...] +coder users edit-roles ``` From 42bb16df547e30c02d09ff3fa955c7a228dcf602 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 11 Apr 2025 18:30:13 +0000 Subject: [PATCH 07/14] fix: sort roles so they always appear in the same order --- cli/usereditroles.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/usereditroles.go b/cli/usereditroles.go index 1716f92864e49..930fac3850200 100644 --- a/cli/usereditroles.go +++ b/cli/usereditroles.go @@ -1,6 +1,8 @@ package cli import ( + "sort" + "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/codersdk" "github.com/coder/serpent" @@ -38,6 +40,7 @@ func (r *RootCmd) userEditRoles() *serpent.Command { return xerrors.Errorf("fetch user roles: %w", err) } + sort.Strings(siteRoles) selectedRoles, err := cliui.MultiSelect(inv, cliui.MultiSelectOptions{ Message: "Select the roles you'd like to assign to the user", Options: siteRoles, From 45894ee4fb7d6b29f32a6cf8f2b29255b8fefcef Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 11 Apr 2025 20:28:24 +0000 Subject: [PATCH 08/14] chore: fmt --- cli/usereditroles.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/usereditroles.go b/cli/usereditroles.go index 930fac3850200..1d39c387a7f08 100644 --- a/cli/usereditroles.go +++ b/cli/usereditroles.go @@ -28,7 +28,7 @@ func (r *RootCmd) userEditRoles() *serpent.Command { return xerrors.Errorf("fetch site roles: %w", err) } - var siteRoles = make([]string, 0) + siteRoles := make([]string, 0) for _, role := range roles { if role.Assignable { siteRoles = append(siteRoles, role.Name) From 1e049b31adcca3eaafdefd926822c118dcb4f018 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Fri, 11 Apr 2025 20:30:43 +0000 Subject: [PATCH 09/14] chore: fmt --- cli/usereditroles.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/usereditroles.go b/cli/usereditroles.go index 1d39c387a7f08..3328679b93da2 100644 --- a/cli/usereditroles.go +++ b/cli/usereditroles.go @@ -3,10 +3,11 @@ package cli import ( "sort" + "golang.org/x/xerrors" + "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/codersdk" "github.com/coder/serpent" - "golang.org/x/xerrors" ) func (r *RootCmd) userEditRoles() *serpent.Command { From 681ecba2c54e2bfa7b84d3a96aa75229ad7e094b Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 15 Apr 2025 19:30:40 +0000 Subject: [PATCH 10/14] feat: add roles flag to user edit-roles command for passing in roles --- cli/usereditroles.go | 54 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/cli/usereditroles.go b/cli/usereditroles.go index 3328679b93da2..b6a6f7791aed6 100644 --- a/cli/usereditroles.go +++ b/cli/usereditroles.go @@ -1,7 +1,9 @@ package cli import ( + "slices" "sort" + "strings" "golang.org/x/xerrors" @@ -12,9 +14,20 @@ import ( func (r *RootCmd) userEditRoles() *serpent.Command { client := new(codersdk.Client) + + var givenRoles []string + cmd := &serpent.Command{ - Use: "edit-roles ", - Short: "Edit a user's roles by username or id", + Use: "edit-roles ", + Short: "Edit a user's roles by username or id", + Options: []serpent.Option{ + cliui.SkipPromptOption(), + { + Name: "roles", + Description: "A list of roles to give to the user. This removes any existing roles the user may have.", + Flag: "roles", + Value: serpent.StringArrayOf(&givenRoles)}, + }, Middleware: serpent.Chain(serpent.RequireNArgs(1), r.InitClient(client)), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() @@ -35,20 +48,41 @@ func (r *RootCmd) userEditRoles() *serpent.Command { siteRoles = append(siteRoles, role.Name) } } + sort.Strings(siteRoles) userRoles, err := client.UserRoles(ctx, user.Username) if err != nil { return xerrors.Errorf("fetch user roles: %w", err) } - sort.Strings(siteRoles) - 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) + var selectedRoles []string + if len(givenRoles) > 0 { + // If the none role is present ignore all other roles. + // This is so there is a way to clear roles from the CLI without making a + // new command. + if slices.Contains(givenRoles, "none") { + selectedRoles = []string{} + } else { + // 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, or none\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{ From a2b349df5b386e7ba35a15a62a41063191db249c Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 15 Apr 2025 20:13:56 +0000 Subject: [PATCH 11/14] fix: use static rbac method for getting site roles instead of API call --- cli/usereditroles.go | 46 ++++++++++++----------------- cli/usereditroles_test.go | 61 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 28 deletions(-) create mode 100644 cli/usereditroles_test.go diff --git a/cli/usereditroles.go b/cli/usereditroles.go index b6a6f7791aed6..af7278ead2546 100644 --- a/cli/usereditroles.go +++ b/cli/usereditroles.go @@ -1,6 +1,7 @@ package cli import ( + "fmt" "slices" "sort" "strings" @@ -8,6 +9,7 @@ import ( "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" ) @@ -15,6 +17,14 @@ import ( 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{ @@ -24,7 +34,7 @@ func (r *RootCmd) userEditRoles() *serpent.Command { cliui.SkipPromptOption(), { Name: "roles", - Description: "A list of roles to give to the user. This removes any existing roles the user may have.", + 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)}, }, @@ -37,19 +47,6 @@ func (r *RootCmd) userEditRoles() *serpent.Command { return xerrors.Errorf("fetch user: %w", err) } - roles, err := client.ListSiteRoles(ctx) - if err != nil { - return xerrors.Errorf("fetch site roles: %w", err) - } - - siteRoles := make([]string, 0) - for _, role := range roles { - if role.Assignable { - siteRoles = append(siteRoles, role.Name) - } - } - sort.Strings(siteRoles) - userRoles, err := client.UserRoles(ctx, user.Username) if err != nil { return xerrors.Errorf("fetch user roles: %w", err) @@ -57,22 +54,15 @@ func (r *RootCmd) userEditRoles() *serpent.Command { var selectedRoles []string if len(givenRoles) > 0 { - // If the none role is present ignore all other roles. - // This is so there is a way to clear roles from the CLI without making a - // new command. - if slices.Contains(givenRoles, "none") { - selectedRoles = []string{} - } else { - // 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, or none\n", givenRole, siteRolesPretty) - } + // 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 } + + selectedRoles = givenRoles } else { selectedRoles, err = cliui.MultiSelect(inv, cliui.MultiSelectOptions{ Message: "Select the roles you'd like to assign to the user", diff --git a/cli/usereditroles_test.go b/cli/usereditroles_test.go new file mode 100644 index 0000000000000..49fd46ee9a8c0 --- /dev/null +++ b/cli/usereditroles_test.go @@ -0,0 +1,61 @@ +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) + _, 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, client, 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") + }) +} From ea2e9089d2e88c1007ae2b93b3efecf23719f7a9 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 15 Apr 2025 20:23:50 +0000 Subject: [PATCH 12/14] chore: fmt --- cli/usereditroles.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/usereditroles.go b/cli/usereditroles.go index af7278ead2546..f94c2e25dd0b5 100644 --- a/cli/usereditroles.go +++ b/cli/usereditroles.go @@ -36,7 +36,8 @@ func (r *RootCmd) userEditRoles() *serpent.Command { 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)}, + Value: serpent.StringArrayOf(&givenRoles), + }, }, Middleware: serpent.Chain(serpent.RequireNArgs(1), r.InitClient(client)), Handler: func(inv *serpent.Invocation) error { From c4d380609ce61fa499e5e2193059c0ee7db04fc3 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 15 Apr 2025 20:29:59 +0000 Subject: [PATCH 13/14] chore: lint --- cli/usereditroles.go | 1 - cli/usereditroles_test.go | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/usereditroles.go b/cli/usereditroles.go index f94c2e25dd0b5..815d8f47dc186 100644 --- a/cli/usereditroles.go +++ b/cli/usereditroles.go @@ -73,7 +73,6 @@ func (r *RootCmd) userEditRoles() *serpent.Command { if err != nil { return xerrors.Errorf("selecting roles for user: %w", err) } - } _, err = client.UpdateUserRoles(ctx, user.Username, codersdk.UpdateRoles{ diff --git a/cli/usereditroles_test.go b/cli/usereditroles_test.go index 49fd46ee9a8c0..bd12092501808 100644 --- a/cli/usereditroles_test.go +++ b/cli/usereditroles_test.go @@ -23,10 +23,11 @@ func TestUserEditRoles(t *testing.T) { 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, client, root) + clitest.SetupConfig(t, userAdmin, root) // Create context with timeout ctx := testutil.Context(t, testutil.WaitShort) From 35ed6adc9b7228fd2f256b593af07dcf12513cb7 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 15 Apr 2025 20:40:47 +0000 Subject: [PATCH 14/14] chore: make gen --- .../coder_users_edit-roles_--help.golden | 11 +++++++++- docs/reference/cli/users_edit-roles.md | 20 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/cli/testdata/coder_users_edit-roles_--help.golden b/cli/testdata/coder_users_edit-roles_--help.golden index 6c24f67c0988a..02dd9155b4d4e 100644 --- a/cli/testdata/coder_users_edit-roles_--help.golden +++ b/cli/testdata/coder_users_edit-roles_--help.golden @@ -1,9 +1,18 @@ coder v0.0.0-devel USAGE: - coder users edit-roles + coder users edit-roles [flags] 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. diff --git a/docs/reference/cli/users_edit-roles.md b/docs/reference/cli/users_edit-roles.md index b8bcf190ac675..23e0baa42afff 100644 --- a/docs/reference/cli/users_edit-roles.md +++ b/docs/reference/cli/users_edit-roles.md @@ -6,5 +6,23 @@ Edit a user's roles by username or id ## Usage ```console -coder users edit-roles +coder users edit-roles [flags] ``` + +## Options + +### -y, --yes + +| | | +|------|-------------------| +| Type | bool | + +Bypass prompts. + +### --roles + +| | | +|------|---------------------------| +| Type | 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. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy