mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add sharing add command to the CLI (#19576)
Adds a `sharing add` command for sharing Workspaces with other users and groups. The command allows sharing with multiple users, and groups within one command as well as specifying the role (`use`, or `admin`) defaulting to `use` if none is specified. In the current implementation when the command completes we show the user the current state of the workspace ACL. ``` $ coder sharing add apricot-catfish-86 --user=member:admin --group=contractors:use USER GROUP ROLE member - admin member contractors use ``` If a user is a part of multiple groups, or the workspace has been individually shared with them they will show up multiple times. Although this is a bit confusing at first glance it's important to be able to tell what the maximum role a user may have, and via what ACL they have it. --- One piece of UX to consider is that in order to be able to share a Workspace with a user they must have a role that can read that user. In the tests we give the user the `ScopedRoleOrgAuditor` role. Closes [coder/internal#859](https://github.com/coder/internal/issues/859)
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"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/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbfake"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
func TestSharingShare(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
|
||||
|
||||
t.Run("ShareWithUsers_Simple", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
})
|
||||
orgOwner = coderdtest.CreateFirstUser(t, client)
|
||||
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
|
||||
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
|
||||
OwnerID: workspaceOwner.ID,
|
||||
OrganizationID: orgOwner.OrganizationID,
|
||||
}).Do().Workspace
|
||||
_, toShareWithUser = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
|
||||
)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
inv, root := clitest.New(t, "sharing", "add", workspace.Name, "--org", orgOwner.OrganizationID.String(), "--user", toShareWithUser.Username)
|
||||
clitest.SetupConfig(t, workspaceOwnerClient, root)
|
||||
|
||||
out := bytes.NewBuffer(nil)
|
||||
inv.Stdout = out
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
acl, err := workspaceOwnerClient.WorkspaceACL(inv.Context(), workspace.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, acl.Users, codersdk.WorkspaceUser{
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser.ID,
|
||||
Username: toShareWithUser.Username,
|
||||
AvatarURL: toShareWithUser.AvatarURL,
|
||||
},
|
||||
Role: codersdk.WorkspaceRole("use"),
|
||||
})
|
||||
|
||||
assert.Contains(t, out.String(), toShareWithUser.Username)
|
||||
assert.Contains(t, out.String(), codersdk.WorkspaceRoleUse)
|
||||
})
|
||||
|
||||
t.Run("ShareWithUsers_Multiple", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
})
|
||||
orgOwner = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
|
||||
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
|
||||
OwnerID: workspaceOwner.ID,
|
||||
OrganizationID: orgOwner.OrganizationID,
|
||||
}).Do().Workspace
|
||||
|
||||
_, toShareWithUser1 = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
|
||||
_, toShareWithUser2 = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
|
||||
)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
inv, root := clitest.New(t,
|
||||
"sharing",
|
||||
"add", workspace.Name, "--org", orgOwner.OrganizationID.String(),
|
||||
fmt.Sprintf("--user=%s,%s", toShareWithUser1.Username, toShareWithUser2.Username),
|
||||
)
|
||||
clitest.SetupConfig(t, workspaceOwnerClient, root)
|
||||
|
||||
out := bytes.NewBuffer(nil)
|
||||
inv.Stdout = out
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
acl, err := workspaceOwnerClient.WorkspaceACL(inv.Context(), workspace.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, acl.Users, codersdk.WorkspaceUser{
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser1.ID,
|
||||
Username: toShareWithUser1.Username,
|
||||
AvatarURL: toShareWithUser1.AvatarURL,
|
||||
},
|
||||
Role: codersdk.WorkspaceRoleUse,
|
||||
})
|
||||
assert.Contains(t, acl.Users, codersdk.WorkspaceUser{
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser2.ID,
|
||||
Username: toShareWithUser2.Username,
|
||||
AvatarURL: toShareWithUser2.AvatarURL,
|
||||
},
|
||||
Role: codersdk.WorkspaceRoleUse,
|
||||
})
|
||||
|
||||
assert.Contains(t, out.String(), toShareWithUser1.Username)
|
||||
assert.Contains(t, out.String(), toShareWithUser2.Username)
|
||||
})
|
||||
|
||||
t.Run("ShareWithUsers_Roles", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
})
|
||||
orgOwner = coderdtest.CreateFirstUser(t, client)
|
||||
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
|
||||
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
|
||||
OwnerID: workspaceOwner.ID,
|
||||
OrganizationID: orgOwner.OrganizationID,
|
||||
}).Do().Workspace
|
||||
_, toShareWithUser = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
|
||||
)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
inv, root := clitest.New(t, "sharing", "add", workspace.Name,
|
||||
"--org", orgOwner.OrganizationID.String(),
|
||||
"--user", fmt.Sprintf("%s:admin", toShareWithUser.Username),
|
||||
)
|
||||
clitest.SetupConfig(t, workspaceOwnerClient, root)
|
||||
|
||||
out := bytes.NewBuffer(nil)
|
||||
inv.Stdout = out
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
acl, err := workspaceOwnerClient.WorkspaceACL(inv.Context(), workspace.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, acl.Users, codersdk.WorkspaceUser{
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser.ID,
|
||||
Username: toShareWithUser.Username,
|
||||
AvatarURL: toShareWithUser.AvatarURL,
|
||||
},
|
||||
Role: codersdk.WorkspaceRoleAdmin,
|
||||
})
|
||||
|
||||
found := false
|
||||
for _, line := range strings.Split(out.String(), "\n") {
|
||||
if strings.Contains(line, toShareWithUser.Username) && strings.Contains(line, string(codersdk.WorkspaceRoleAdmin)) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
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()))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user