Skip to content

Commit d232aa2

Browse files
committed
feat: add sharing show command to the CLI
1 parent 909acbc commit d232aa2

File tree

3 files changed

+199
-2
lines changed

3 files changed

+199
-2
lines changed

cli/sharing.go

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"context"
45
"fmt"
56
"regexp"
67

@@ -29,14 +30,52 @@ func (r *RootCmd) sharing() *serpent.Command {
2930
Handler: func(inv *serpent.Invocation) error {
3031
return inv.Command.HelpHandler(inv)
3132
},
32-
Children: []*serpent.Command{r.shareWorkspace(orgContext)},
33-
Hidden: true,
33+
Children: []*serpent.Command{
34+
r.shareWorkspace(orgContext),
35+
r.statusWorkspaceSharing(),
36+
},
37+
Hidden: true,
3438
}
3539

3640
orgContext.AttachOptions(cmd)
3741
return cmd
3842
}
3943

44+
func (r *RootCmd) statusWorkspaceSharing() *serpent.Command {
45+
client := new(codersdk.Client)
46+
47+
cmd := &serpent.Command{
48+
Use: "status <workspace>",
49+
Short: "List all users and groups the given Workspace is shared with.",
50+
Aliases: []string{"list"},
51+
Middleware: serpent.Chain(
52+
r.InitClient(client),
53+
serpent.RequireNArgs(1),
54+
),
55+
Handler: func(inv *serpent.Invocation) error {
56+
workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0])
57+
if err != nil {
58+
return xerrors.Errorf("unable to fetch Workspace %s: %w", inv.Args[0], err)
59+
}
60+
61+
acl, err := client.WorkspaceACL(inv.Context(), workspace.ID)
62+
if err != nil {
63+
return xerrors.Errorf("unable to fetch ACL for Workspace: %w", err)
64+
}
65+
66+
out, err := workspaceACLToTable(inv.Context(), &acl)
67+
if err != nil {
68+
return err
69+
}
70+
71+
_, err = fmt.Fprintln(inv.Stdout, out)
72+
return err
73+
},
74+
}
75+
76+
return cmd
77+
}
78+
4079
func (r *RootCmd) shareWorkspace(orgContext *OrganizationContext) *serpent.Command {
4180
var (
4281
// Username regex taken from codersdk/name.go
@@ -229,3 +268,48 @@ func stringToWorkspaceRole(role string) (codersdk.WorkspaceRole, error) {
229268
role, codersdk.WorkspaceRoleAdmin, codersdk.WorkspaceRoleUse)
230269
}
231270
}
271+
272+
func workspaceACLToTable(ctx context.Context, acl *codersdk.WorkspaceACL) (string, error) {
273+
type workspaceShareRow struct {
274+
User string `table:"user"`
275+
Group string `table:"group,default_sort"`
276+
Role codersdk.WorkspaceRole `table:"role"`
277+
}
278+
279+
formatter := cliui.NewOutputFormatter(
280+
cliui.TableFormat(
281+
[]workspaceShareRow{}, []string{"User", "Group", "Role"}),
282+
cliui.JSONFormat())
283+
284+
outputRows := make([]workspaceShareRow, 0)
285+
for _, user := range acl.Users {
286+
if user.Role == codersdk.WorkspaceRoleDeleted {
287+
continue
288+
}
289+
290+
outputRows = append(outputRows, workspaceShareRow{
291+
User: user.Username,
292+
Group: defaultGroupDisplay,
293+
Role: user.Role,
294+
})
295+
}
296+
for _, group := range acl.Groups {
297+
if group.Role == codersdk.WorkspaceRoleDeleted {
298+
continue
299+
}
300+
301+
for _, user := range group.Members {
302+
outputRows = append(outputRows, workspaceShareRow{
303+
User: user.Username,
304+
Group: group.Name,
305+
Role: group.Role,
306+
})
307+
}
308+
}
309+
out, err := formatter.Format(ctx, outputRows)
310+
if err != nil {
311+
return "", err
312+
}
313+
314+
return out, nil
315+
}

cli/sharing_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,55 @@ func TestSharingShare(t *testing.T) {
168168
assert.True(t, found, fmt.Sprintf("expected to find the username %s and role %s in the command: %s", toShareWithUser.Username, codersdk.WorkspaceRoleAdmin, out.String()))
169169
})
170170
}
171+
172+
func TestSharingStatus(t *testing.T) {
173+
t.Parallel()
174+
175+
dv := coderdtest.DeploymentValues(t)
176+
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
177+
178+
t.Run("ListSharedUsers", func(t *testing.T) {
179+
t.Parallel()
180+
181+
var (
182+
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
183+
DeploymentValues: dv,
184+
})
185+
orgOwner = coderdtest.CreateFirstUser(t, client)
186+
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
187+
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
188+
OwnerID: workspaceOwner.ID,
189+
OrganizationID: orgOwner.OrganizationID,
190+
}).Do().Workspace
191+
_, toShareWithUser = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
192+
ctx = testutil.Context(t, testutil.WaitMedium)
193+
)
194+
195+
err := client.UpdateWorkspaceACL(ctx, workspace.ID, codersdk.UpdateWorkspaceACL{
196+
UserRoles: map[string]codersdk.WorkspaceRole{
197+
toShareWithUser.ID.String(): codersdk.WorkspaceRoleUse,
198+
},
199+
})
200+
require.NoError(t, err)
201+
202+
inv, root := clitest.New(t, "sharing", "status", workspace.Name, "--org", orgOwner.OrganizationID.String())
203+
clitest.SetupConfig(t, workspaceOwnerClient, root)
204+
205+
out := bytes.NewBuffer(nil)
206+
inv.Stdout = out
207+
err = inv.WithContext(ctx).Run()
208+
require.NoError(t, err)
209+
210+
found := false
211+
for _, line := range strings.Split(out.String(), "\n") {
212+
if strings.Contains(line, toShareWithUser.Username) && strings.Contains(line, string(codersdk.WorkspaceRoleUse)) {
213+
found = true
214+
}
215+
216+
if found {
217+
break
218+
}
219+
}
220+
assert.True(t, found, "expected to find username %s with role %s in the output: %s", toShareWithUser.Username, codersdk.WorkspaceRoleUse, out.String())
221+
})
222+
}

enterprise/cli/sharing_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,67 @@ func TestSharingShareEnterprise(t *testing.T) {
187187
})
188188
}
189189

190+
func TestSharingStatus(t *testing.T) {
191+
t.Parallel()
192+
193+
dv := coderdtest.DeploymentValues(t)
194+
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
195+
196+
t.Run("ListSharedUsers", func(t *testing.T) {
197+
t.Parallel()
198+
199+
var (
200+
client, db, orgOwner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
201+
Options: &coderdtest.Options{
202+
DeploymentValues: dv,
203+
},
204+
LicenseOptions: &coderdenttest.LicenseOptions{
205+
Features: license.Features{
206+
codersdk.FeatureTemplateRBAC: 1,
207+
},
208+
},
209+
})
210+
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
211+
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
212+
OwnerID: workspaceOwner.ID,
213+
OrganizationID: orgOwner.OrganizationID,
214+
}).Do().Workspace
215+
_, orgMember = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
216+
ctx = testutil.Context(t, testutil.WaitMedium)
217+
)
218+
219+
group, err := createGroupWithMembers(ctx, client, orgOwner.OrganizationID, "new-group", []uuid.UUID{orgMember.ID})
220+
require.NoError(t, err)
221+
222+
err = client.UpdateWorkspaceACL(ctx, workspace.ID, codersdk.UpdateWorkspaceACL{
223+
GroupRoles: map[string]codersdk.WorkspaceRole{
224+
group.ID.String(): codersdk.WorkspaceRoleUse,
225+
},
226+
})
227+
require.NoError(t, err)
228+
229+
inv, root := clitest.New(t, "sharing", "status", workspace.Name, "--org", orgOwner.OrganizationID.String())
230+
clitest.SetupConfig(t, workspaceOwnerClient, root)
231+
232+
out := bytes.NewBuffer(nil)
233+
inv.Stdout = out
234+
err = inv.WithContext(ctx).Run()
235+
require.NoError(t, err)
236+
237+
found := false
238+
for _, line := range strings.Split(out.String(), "\n") {
239+
if strings.Contains(line, orgMember.Username) && strings.Contains(line, string(codersdk.WorkspaceRoleUse)) && strings.Contains(line, group.Name) {
240+
found = true
241+
}
242+
243+
if found {
244+
break
245+
}
246+
}
247+
assert.True(t, found, "expected to find username %s with role %s in the output: %s", orgMember.Username, codersdk.WorkspaceRoleUse, out.String())
248+
})
249+
}
250+
190251
func createGroupWithMembers(ctx context.Context, client *codersdk.Client, orgID uuid.UUID, name string, memberIDs []uuid.UUID) (codersdk.Group, error) {
191252
group, err := client.CreateGroup(ctx, orgID, codersdk.CreateGroupRequest{
192253
Name: name,

0 commit comments

Comments
 (0)