7
7
8
8
"golang.org/x/xerrors"
9
9
10
+ "github.com/google/uuid"
11
+
10
12
"github.com/coder/coder/v2/cli/cliui"
11
13
"github.com/coder/coder/v2/codersdk"
12
14
"github.com/coder/serpent"
@@ -15,8 +17,6 @@ import (
15
17
const defaultGroupDisplay = "-"
16
18
17
19
func (r * RootCmd ) sharing () * serpent.Command {
18
- orgContext := NewOrganizationContext ()
19
-
20
20
cmd := & serpent.Command {
21
21
Use : "sharing [subcommand]" ,
22
22
Short : "Commands for managing shared workspaces" ,
@@ -25,13 +25,13 @@ func (r *RootCmd) sharing() *serpent.Command {
25
25
return inv .Command .HelpHandler (inv )
26
26
},
27
27
Children : []* serpent.Command {
28
- r .shareWorkspace (orgContext ),
28
+ r .shareWorkspace (),
29
+ r .unshareWorkspace (),
29
30
r .statusWorkspaceSharing (),
30
31
},
31
32
Hidden : true ,
32
33
}
33
34
34
- orgContext .AttachOptions (cmd )
35
35
return cmd
36
36
}
37
37
@@ -70,13 +70,14 @@ func (r *RootCmd) statusWorkspaceSharing() *serpent.Command {
70
70
return cmd
71
71
}
72
72
73
- func (r * RootCmd ) shareWorkspace (orgContext * OrganizationContext ) * serpent.Command {
73
+ func (r * RootCmd ) shareWorkspace () * serpent.Command {
74
74
var (
75
+ client = new (codersdk.Client )
76
+ users []string
77
+ groups []string
78
+
75
79
// Username regex taken from codersdk/name.go
76
80
nameRoleRegex = regexp .MustCompile (`(^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)+(?::([A-Za-z0-9-]+))?` )
77
- client = new (codersdk.Client )
78
- users []string
79
- groups []string
80
81
)
81
82
82
83
cmd := & serpent.Command {
@@ -110,89 +111,130 @@ func (r *RootCmd) shareWorkspace(orgContext *OrganizationContext) *serpent.Comma
110
111
return xerrors .Errorf ("could not fetch the workspace %s: %w" , inv .Args [0 ], err )
111
112
}
112
113
113
- org , err := orgContext .Selected (inv , client )
114
+ userRoleStrings := make ([][2 ]string , len (users ))
115
+ for index , user := range users {
116
+ userAndRole := nameRoleRegex .FindStringSubmatch (user )
117
+ if userAndRole == nil {
118
+ return xerrors .Errorf ("invalid user format %q: must match pattern 'username:role'" , user )
119
+ }
120
+
121
+ userRoleStrings [index ] = [2 ]string {userAndRole [1 ], userAndRole [2 ]}
122
+ }
123
+
124
+ groupRoleStrings := make ([][2 ]string , len (groups ))
125
+ for index , group := range groups {
126
+ groupAndRole := nameRoleRegex .FindStringSubmatch (group )
127
+ if groupAndRole == nil {
128
+ return xerrors .Errorf ("invalid group format %q: must match pattern 'group:role'" , group )
129
+ }
130
+
131
+ groupRoleStrings [index ] = [2 ]string {groupAndRole [1 ], groupAndRole [2 ]}
132
+ }
133
+
134
+ userRoles , groupRoles , err := fetchUsersAndGroups (inv .Context (), fetchUsersAndGroupsParams {
135
+ Client : client ,
136
+ OrgID : workspace .OrganizationID ,
137
+ OrgName : workspace .OrganizationName ,
138
+ Users : userRoleStrings ,
139
+ Groups : groupRoleStrings ,
140
+ DefaultRole : codersdk .WorkspaceRoleUse ,
141
+ })
114
142
if err != nil {
115
143
return err
116
144
}
117
145
118
- userRoles := make (map [string ]codersdk.WorkspaceRole , len (users ))
119
- if len (users ) > 0 {
120
- orgMembers , err := client .OrganizationMembers (inv .Context (), org .ID )
121
- if err != nil {
122
- return err
123
- }
146
+ err = client .UpdateWorkspaceACL (inv .Context (), workspace .ID , codersdk.UpdateWorkspaceACL {
147
+ UserRoles : userRoles ,
148
+ GroupRoles : groupRoles ,
149
+ })
150
+ if err != nil {
151
+ return err
152
+ }
124
153
125
- for _ , user := range users {
126
- userAndRole := nameRoleRegex .FindStringSubmatch (user )
127
- if userAndRole == nil {
128
- return xerrors .Errorf ("invalid user format %q: must match pattern 'username:role'" , user )
129
- }
130
-
131
- username := userAndRole [1 ]
132
- role := userAndRole [2 ]
133
- if role == "" {
134
- role = string (codersdk .WorkspaceRoleUse )
135
- }
136
-
137
- userID := ""
138
- for _ , member := range orgMembers {
139
- if member .Username == username {
140
- userID = member .UserID .String ()
141
- break
142
- }
143
- }
144
- if userID == "" {
145
- return xerrors .Errorf ("could not find user %s in the organization %s" , username , org .Name )
146
- }
147
-
148
- workspaceRole , err := stringToWorkspaceRole (role )
149
- if err != nil {
150
- return err
151
- }
152
-
153
- userRoles [userID ] = workspaceRole
154
- }
154
+ acl , err := client .WorkspaceACL (inv .Context (), workspace .ID )
155
+ if err != nil {
156
+ return xerrors .Errorf ("could not fetch current workspace ACL after sharing %w" , err )
157
+ }
158
+
159
+ out , err := workspaceACLToTable (inv .Context (), & acl )
160
+ if err != nil {
161
+ return err
155
162
}
156
163
157
- groupRoles := make (map [string ]codersdk.WorkspaceRole )
158
- if len (groups ) > 0 {
159
- orgGroups , err := client .Groups (inv .Context (), codersdk.GroupArguments {
160
- Organization : org .ID .String (),
161
- })
162
- if err != nil {
163
- return err
164
+ _ , err = fmt .Fprintln (inv .Stdout , out )
165
+ return err
166
+ },
167
+ }
168
+
169
+ return cmd
170
+ }
171
+
172
+ func (r * RootCmd ) unshareWorkspace () * serpent.Command {
173
+ var (
174
+ client = new (codersdk.Client )
175
+ users []string
176
+ groups []string
177
+ )
178
+
179
+ cmd := & serpent.Command {
180
+ Use : "remove <workspace> --user <user> --group <group>" ,
181
+ Aliases : []string {"unshare" },
182
+ Short : "Remove shared access for users or groups from a workspace." ,
183
+ Options : serpent.OptionSet {
184
+ {
185
+ Name : "user" ,
186
+ Description : "A comma separated list of users to share the workspace with." ,
187
+ Flag : "user" ,
188
+ Value : serpent .StringArrayOf (& users ),
189
+ }, {
190
+ Name : "group" ,
191
+ Description : "A comma separated list of groups to share the workspace with." ,
192
+ Flag : "group" ,
193
+ Value : serpent .StringArrayOf (& groups ),
194
+ },
195
+ },
196
+ Middleware : serpent .Chain (
197
+ r .InitClient (client ),
198
+ serpent .RequireNArgs (1 ),
199
+ ),
200
+ Handler : func (inv * serpent.Invocation ) error {
201
+ if len (users ) == 0 && len (groups ) == 0 {
202
+ return xerrors .New ("at least one user or group must be provided" )
203
+ }
204
+
205
+ workspace , err := namedWorkspace (inv .Context (), client , inv .Args [0 ])
206
+ if err != nil {
207
+ return xerrors .Errorf ("could not fetch the workspace %s: %w" , inv .Args [0 ], err )
208
+ }
209
+
210
+ userRoleStrings := make ([][2 ]string , len (users ))
211
+ for index , user := range users {
212
+ if ! codersdk .UsernameValidRegex .MatchString (user ) {
213
+ return xerrors .Errorf ("invalid username" )
164
214
}
165
215
166
- for _ , group := range groups {
167
- groupAndRole := nameRoleRegex .FindStringSubmatch (group )
168
- if groupAndRole == nil {
169
- return xerrors .Errorf ("invalid group format %q: must match pattern 'group:role'" , group )
170
- }
171
- groupName := groupAndRole [1 ]
172
- role := groupAndRole [2 ]
173
- if role == "" {
174
- role = string (codersdk .WorkspaceRoleUse )
175
- }
176
-
177
- var orgGroup * codersdk.Group
178
- for _ , group := range orgGroups {
179
- if group .Name == groupName {
180
- orgGroup = & group
181
- break
182
- }
183
- }
184
-
185
- if orgGroup == nil {
186
- return xerrors .Errorf ("could not find group named %s belonging to the organization %s" , groupName , org .Name )
187
- }
188
-
189
- workspaceRole , err := stringToWorkspaceRole (role )
190
- if err != nil {
191
- return err
192
- }
193
-
194
- groupRoles [orgGroup .ID .String ()] = workspaceRole
216
+ userRoleStrings [index ] = [2 ]string {user , "" }
217
+ }
218
+
219
+ groupRoleStrings := make ([][2 ]string , len (groups ))
220
+ for index , group := range groups {
221
+ if ! codersdk .UsernameValidRegex .MatchString (group ) {
222
+ return xerrors .Errorf ("invalid group name" )
195
223
}
224
+
225
+ groupRoleStrings [index ] = [2 ]string {group , "" }
226
+ }
227
+
228
+ userRoles , groupRoles , err := fetchUsersAndGroups (inv .Context (), fetchUsersAndGroupsParams {
229
+ Client : client ,
230
+ OrgID : workspace .OrganizationID ,
231
+ OrgName : workspace .OrganizationName ,
232
+ Users : userRoleStrings ,
233
+ Groups : groupRoleStrings ,
234
+ DefaultRole : codersdk .WorkspaceRoleDeleted ,
235
+ })
236
+ if err != nil {
237
+ return err
196
238
}
197
239
198
240
err = client .UpdateWorkspaceACL (inv .Context (), workspace .ID , codersdk.UpdateWorkspaceACL {
@@ -227,9 +269,11 @@ func stringToWorkspaceRole(role string) (codersdk.WorkspaceRole, error) {
227
269
return codersdk .WorkspaceRoleUse , nil
228
270
case string (codersdk .WorkspaceRoleAdmin ):
229
271
return codersdk .WorkspaceRoleAdmin , nil
272
+ case string (codersdk .WorkspaceRoleDeleted ):
273
+ return codersdk .WorkspaceRoleDeleted , nil
230
274
default :
231
- return "" , xerrors .Errorf ("invalid role %q: expected %q or %q " ,
232
- role , codersdk .WorkspaceRoleAdmin , codersdk .WorkspaceRoleUse )
275
+ return "" , xerrors .Errorf ("invalid role %q: expected %q, %q, or \" %q \" " ,
276
+ role , codersdk .WorkspaceRoleAdmin , codersdk .WorkspaceRoleUse , codersdk . WorkspaceRoleDeleted )
233
277
}
234
278
}
235
279
@@ -277,3 +321,96 @@ func workspaceACLToTable(ctx context.Context, acl *codersdk.WorkspaceACL) (strin
277
321
278
322
return out , nil
279
323
}
324
+
325
+ type fetchUsersAndGroupsParams struct {
326
+ Client * codersdk.Client
327
+ OrgID uuid.UUID
328
+ OrgName string
329
+ Users [][2 ]string
330
+ Groups [][2 ]string
331
+ DefaultRole codersdk.WorkspaceRole
332
+ }
333
+
334
+ func fetchUsersAndGroups (ctx context.Context , params fetchUsersAndGroupsParams ) (userRoles map [string ]codersdk.WorkspaceRole , groupRoles map [string ]codersdk.WorkspaceRole , err error ) {
335
+ var (
336
+ client = params .Client
337
+ orgID = params .OrgID
338
+ orgName = params .OrgName
339
+ users = params .Users
340
+ groups = params .Groups
341
+ defaultRole = params .DefaultRole
342
+ )
343
+
344
+ userRoles = make (map [string ]codersdk.WorkspaceRole , len (users ))
345
+ if len (users ) > 0 {
346
+ orgMembers , err := client .OrganizationMembers (ctx , orgID )
347
+ if err != nil {
348
+ return nil , nil , err
349
+ }
350
+
351
+ for _ , user := range users {
352
+ username := user [0 ]
353
+ role := user [1 ]
354
+ if role == "" {
355
+ role = string (defaultRole )
356
+ }
357
+
358
+ userID := ""
359
+ for _ , member := range orgMembers {
360
+ if member .Username == username {
361
+ userID = member .UserID .String ()
362
+ break
363
+ }
364
+ }
365
+ if userID == "" {
366
+ return nil , nil , xerrors .Errorf ("could not find user %s in the organization %s" , username , orgName )
367
+ }
368
+
369
+ workspaceRole , err := stringToWorkspaceRole (role )
370
+ if err != nil {
371
+ return nil , nil , err
372
+ }
373
+
374
+ userRoles [userID ] = workspaceRole
375
+ }
376
+ }
377
+
378
+ groupRoles = make (map [string ]codersdk.WorkspaceRole )
379
+ if len (groups ) > 0 {
380
+ orgGroups , err := client .Groups (ctx , codersdk.GroupArguments {
381
+ Organization : orgID .String (),
382
+ })
383
+ if err != nil {
384
+ return nil , nil , err
385
+ }
386
+
387
+ for _ , group := range groups {
388
+ groupName := group [0 ]
389
+ role := group [1 ]
390
+ if role == "" {
391
+ role = string (defaultRole )
392
+ }
393
+
394
+ var orgGroup * codersdk.Group
395
+ for _ , og := range orgGroups {
396
+ if og .Name == groupName {
397
+ orgGroup = & og
398
+ break
399
+ }
400
+ }
401
+
402
+ if orgGroup == nil {
403
+ return nil , nil , xerrors .Errorf ("could not find group named %s belonging to the organization %s" , groupName , orgName )
404
+ }
405
+
406
+ workspaceRole , err := stringToWorkspaceRole (role )
407
+ if err != nil {
408
+ return nil , nil , err
409
+ }
410
+
411
+ groupRoles [orgGroup .ID .String ()] = workspaceRole
412
+ }
413
+ }
414
+
415
+ return userRoles , groupRoles , nil
416
+ }
0 commit comments