Skip to content

Commit 1ebdd6f

Browse files
committed
cli work to show role complete
1 parent 1ff534f commit 1ebdd6f

File tree

6 files changed

+171
-25
lines changed

6 files changed

+171
-25
lines changed

coderd/roles.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ func (api *API) AssignableSiteRoles(rw http.ResponseWriter, r *http.Request) {
3131
return
3232
}
3333

34-
roles := rbac.SiteRoles()
35-
customRoles, err := api.Database.CustomRoles(ctx, database.CustomRolesParams{
34+
dbCustomRoles, err := api.Database.CustomRoles(ctx, database.CustomRolesParams{
3635
// Only site wide custom roles to be included
3736
ExcludeOrgRoles: true,
3837
})
@@ -41,14 +40,15 @@ func (api *API) AssignableSiteRoles(rw http.ResponseWriter, r *http.Request) {
4140
return
4241
}
4342

44-
for _, customRole := range customRoles {
43+
customRoles := make([]rbac.Role, 0, len(dbCustomRoles))
44+
for _, customRole := range dbCustomRoles {
4545
rbacRole, err := rolestore.ConvertDBRole(customRole)
4646
if err == nil {
47-
roles = append(roles, rbacRole)
47+
customRoles = append(customRoles, rbacRole)
4848
}
4949
}
5050

51-
httpapi.Write(ctx, rw, http.StatusOK, assignableRoles(actorRoles.Roles, roles))
51+
httpapi.Write(ctx, rw, http.StatusOK, assignableRoles(actorRoles.Roles, rbac.SiteRoles(), customRoles))
5252
}
5353

5454
// assignableOrgRoles returns all org wide roles that can be assigned.
@@ -72,10 +72,10 @@ func (api *API) assignableOrgRoles(rw http.ResponseWriter, r *http.Request) {
7272
}
7373

7474
roles := rbac.OrganizationRoles(organization.ID)
75-
httpapi.Write(ctx, rw, http.StatusOK, assignableRoles(actorRoles.Roles, roles))
75+
httpapi.Write(ctx, rw, http.StatusOK, assignableRoles(actorRoles.Roles, roles, []rbac.Role{}))
7676
}
7777

78-
func assignableRoles(actorRoles rbac.ExpandableRoles, roles []rbac.Role) []codersdk.AssignableRoles {
78+
func assignableRoles(actorRoles rbac.ExpandableRoles, roles []rbac.Role, customRoles []rbac.Role) []codersdk.AssignableRoles {
7979
assignable := make([]codersdk.AssignableRoles, 0)
8080
for _, role := range roles {
8181
// The member role is implied, and not assignable.
@@ -87,6 +87,15 @@ func assignableRoles(actorRoles rbac.ExpandableRoles, roles []rbac.Role) []coder
8787
assignable = append(assignable, codersdk.AssignableRoles{
8888
Role: db2sdk.Role(role),
8989
Assignable: rbac.CanAssignRole(actorRoles, role.Name),
90+
BuiltIn: true,
91+
})
92+
}
93+
94+
for _, role := range customRoles {
95+
assignable = append(assignable, codersdk.AssignableRoles{
96+
Role: db2sdk.Role(role),
97+
Assignable: rbac.CanAssignRole(actorRoles, role.Name),
98+
BuiltIn: false,
9099
})
91100
}
92101
return assignable

codersdk/roles.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ type SlimRole struct {
1919
}
2020

2121
type AssignableRoles struct {
22-
Role
23-
Assignable bool `json:"assignable"`
22+
Role `table:"r,recursive_inline"`
23+
Assignable bool `json:"assignable" table:"assignable"`
24+
// BuiltIn roles are immutable
25+
BuiltIn bool `json:"built_in" table:"built_in"`
2426
}
2527

2628
// Permission is the format passed into the rego.
@@ -33,12 +35,12 @@ type Permission struct {
3335

3436
// Role is a longer form of SlimRole used to edit custom roles.
3537
type Role struct {
36-
Name string `json:"name"`
37-
DisplayName string `json:"display_name"`
38-
SitePermissions []Permission `json:"site_permissions"`
38+
Name string `json:"name" table:"name,default_sort"`
39+
DisplayName string `json:"display_name" table:"display_name"`
40+
SitePermissions []Permission `json:"site_permissions" table:"site_permissions"`
3941
// map[<org_id>] -> Permissions
40-
OrganizationPermissions map[string][]Permission `json:"organization_permissions"`
41-
UserPermissions []Permission `json:"user_permissions"`
42+
OrganizationPermissions map[string][]Permission `json:"organization_permissions" table:"org_permissions"`
43+
UserPermissions []Permission `json:"user_permissions" table:"user_permissions"`
4244
}
4345

4446
// PatchRole will upsert a custom site wide role

enterprise/cli/rolescmd.go

Lines changed: 125 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
package cli
22

3-
import "github.com/coder/serpent"
3+
import (
4+
"fmt"
5+
"slices"
6+
"strings"
7+
8+
"golang.org/x/xerrors"
9+
10+
"github.com/coder/coder/v2/cli/cliui"
11+
"github.com/coder/coder/v2/codersdk"
12+
"github.com/coder/serpent"
13+
)
414

515
// **NOTE** Only covers site wide roles at present. Org scoped roles maybe
616
// should be nested under some command that scopes to an org??
@@ -13,21 +23,131 @@ func (r *RootCmd) roles() *serpent.Command {
1323
Handler: func(inv *serpent.Invocation) error {
1424
return inv.Command.HelpHandler(inv)
1525
},
16-
Hidden: true,
17-
Children: []*serpent.Command{},
26+
Hidden: true,
27+
Children: []*serpent.Command{
28+
r.showRole(),
29+
r.editRole(),
30+
},
31+
}
32+
return cmd
33+
}
34+
35+
func (r *RootCmd) editRole() *serpent.Command {
36+
formatter := roleFormatter()
37+
38+
client := new(codersdk.Client)
39+
cmd := &serpent.Command{
40+
Use: "edit <role_name>",
41+
Short: "Edit a role",
42+
Middleware: serpent.Chain(
43+
serpent.RequireNArgs(1),
44+
r.InitClient(client),
45+
),
46+
Handler: func(inv *serpent.Invocation) error {
47+
ctx := inv.Context()
48+
roles, err := client.ListSiteRoles(ctx)
49+
if err != nil {
50+
return xerrors.Errorf("listing roles: %w", err)
51+
}
52+
53+
// Make sure the role actually exists first
54+
var role codersdk.AssignableRoles
55+
for _, r := range roles {
56+
if strings.EqualFold(inv.Args[0], r.Name) {
57+
role = r
58+
break
59+
}
60+
}
61+
62+
// TODO: Allow role creation
63+
if role.Name == "" {
64+
return xerrors.Errorf("role %q not found", inv.Args[0])
65+
}
66+
67+
return nil
68+
},
1869
}
70+
71+
formatter.AttachOptions(&cmd.Options)
1972
return cmd
2073
}
2174

75+
type assignableRolesTableRow struct {
76+
Name string `table:"name,default_sort"`
77+
DisplayName string `table:"display_name"`
78+
SitePermissions string ` table:"site_permissions"`
79+
// map[<org_id>] -> Permissions
80+
OrganizationPermissions string `table:"org_permissions"`
81+
UserPermissions string `table:"user_permissions"`
82+
Assignable bool `table:"assignable"`
83+
BuiltIn bool `table:"built_in"`
84+
}
85+
2286
func (r *RootCmd) showRole() *serpent.Command {
87+
formatter := roleFormatter()
88+
89+
client := new(codersdk.Client)
2390
cmd := &serpent.Command{
24-
Use: "show",
91+
Use: "show [role_names ...]",
2592
Short: "Show role(s)",
26-
Handler: func(i *serpent.Invocation) error {
93+
Middleware: serpent.Chain(
94+
r.InitClient(client),
95+
),
96+
Handler: func(inv *serpent.Invocation) error {
97+
ctx := inv.Context()
98+
roles, err := client.ListSiteRoles(ctx)
99+
if err != nil {
100+
return xerrors.Errorf("listing roles: %w", err)
101+
}
102+
103+
if len(inv.Args) > 0 {
104+
// filter roles
105+
filtered := make([]codersdk.AssignableRoles, 0)
106+
for _, role := range roles {
107+
if slices.ContainsFunc(inv.Args, func(s string) bool {
108+
return strings.EqualFold(s, role.Name)
109+
}) {
110+
filtered = append(filtered, role)
111+
}
112+
}
113+
roles = filtered
114+
}
27115

116+
out, err := formatter.Format(inv.Context(), roles)
117+
if err != nil {
118+
return err
119+
}
120+
121+
_, err = fmt.Fprintln(inv.Stdout, out)
28122
return nil
29123
},
30124
}
125+
formatter.AttachOptions(&cmd.Options)
31126

32127
return cmd
33128
}
129+
130+
func roleFormatter() *cliui.OutputFormatter {
131+
return cliui.NewOutputFormatter(
132+
cliui.ChangeFormatterData(
133+
cliui.TableFormat([]assignableRolesTableRow{}, []string{"name", "display_name", "built_in", "site_permissions", "org_permissions", "user_permissions"}),
134+
func(data any) (any, error) {
135+
input := data.([]codersdk.AssignableRoles)
136+
rows := make([]assignableRolesTableRow, 0, len(input))
137+
for _, role := range input {
138+
rows = append(rows, assignableRolesTableRow{
139+
Name: role.Name,
140+
DisplayName: role.DisplayName,
141+
SitePermissions: fmt.Sprintf("%d permissions", len(role.SitePermissions)),
142+
OrganizationPermissions: fmt.Sprintf("%d organizations", len(role.OrganizationPermissions)),
143+
UserPermissions: fmt.Sprintf("%d permissions", len(role.UserPermissions)),
144+
Assignable: role.Assignable,
145+
BuiltIn: role.BuiltIn,
146+
})
147+
}
148+
return rows, nil
149+
},
150+
),
151+
cliui.JSONFormat(),
152+
)
153+
}

enterprise/cli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func (r *RootCmd) enterpriseOnly() []*serpent.Command {
1717
r.licenses(),
1818
r.groups(),
1919
r.provisionerDaemons(),
20+
r.roles(),
2021
}
2122
}
2223

site/src/api/typesGenerated.ts

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/src/testHelpers/entities.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -229,40 +229,53 @@ export const MockUpdateCheck: TypesGen.UpdateCheckResponse = {
229229
version: "v99.999.9999+c9cdf14",
230230
};
231231

232-
export const MockOwnerRole: TypesGen.SlimRole = {
232+
export const MockOwnerRole: TypesGen.Role = {
233233
name: "owner",
234234
display_name: "Owner",
235+
site_permissions: [],
236+
organization_permissions: {},
237+
user_permissions: [],
235238
};
236239

237-
export const MockUserAdminRole: TypesGen.SlimRole = {
240+
export const MockUserAdminRole: TypesGen.Role = {
238241
name: "user_admin",
239242
display_name: "User Admin",
243+
site_permissions: [],
244+
organization_permissions: {},
245+
user_permissions: [],
240246
};
241247

242-
export const MockTemplateAdminRole: TypesGen.SlimRole = {
248+
export const MockTemplateAdminRole: TypesGen.Role = {
243249
name: "template_admin",
244250
display_name: "Template Admin",
251+
site_permissions: [],
252+
organization_permissions: {},
253+
user_permissions: [],
245254
};
246255

247256
export const MockMemberRole: TypesGen.SlimRole = {
248257
name: "member",
249258
display_name: "Member",
250259
};
251260

252-
export const MockAuditorRole: TypesGen.SlimRole = {
261+
export const MockAuditorRole: TypesGen.Role = {
253262
name: "auditor",
254263
display_name: "Auditor",
264+
site_permissions: [],
265+
organization_permissions: {},
266+
user_permissions: [],
255267
};
256268

257269
// assignableRole takes a role and a boolean. The boolean implies if the
258270
// actor can assign (add/remove) the role from other users.
259271
export function assignableRole(
260-
role: TypesGen.SlimRole,
272+
role: TypesGen.Role,
261273
assignable: boolean,
262274
): TypesGen.AssignableRoles {
263275
return {
264276
...role,
265277
assignable: assignable,
278+
built_in: true,
266279
};
267280
}
268281

0 commit comments

Comments
 (0)