Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
303 changes: 220 additions & 83 deletions cli/sharing.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

"golang.org/x/xerrors"

"github.com/google/uuid"

"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
Expand All @@ -15,8 +17,6 @@ import (
const defaultGroupDisplay = "-"

func (r *RootCmd) sharing() *serpent.Command {
orgContext := NewOrganizationContext()

cmd := &serpent.Command{
Use: "sharing [subcommand]",
Short: "Commands for managing shared workspaces",
Expand All @@ -25,13 +25,13 @@ func (r *RootCmd) sharing() *serpent.Command {
return inv.Command.HelpHandler(inv)
},
Children: []*serpent.Command{
r.shareWorkspace(orgContext),
r.shareWorkspace(),
r.unshareWorkspace(),
r.statusWorkspaceSharing(),
},
Hidden: true,
}

orgContext.AttachOptions(cmd)
return cmd
}

Expand Down Expand Up @@ -70,13 +70,14 @@ func (r *RootCmd) statusWorkspaceSharing() *serpent.Command {
return cmd
}

func (r *RootCmd) shareWorkspace(orgContext *OrganizationContext) *serpent.Command {
func (r *RootCmd) shareWorkspace() *serpent.Command {
var (
client = new(codersdk.Client)
users []string
groups []string

// Username regex taken from codersdk/name.go
nameRoleRegex = regexp.MustCompile(`(^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)+(?::([A-Za-z0-9-]+))?`)
client = new(codersdk.Client)
users []string
groups []string
)

cmd := &serpent.Command{
Expand Down Expand Up @@ -110,89 +111,130 @@ func (r *RootCmd) shareWorkspace(orgContext *OrganizationContext) *serpent.Comma
return xerrors.Errorf("could not fetch the workspace %s: %w", inv.Args[0], err)
}

org, err := orgContext.Selected(inv, client)
userRoleStrings := make([][2]string, len(users))
for index, user := range users {
userAndRole := nameRoleRegex.FindStringSubmatch(user)
if userAndRole == nil {
return xerrors.Errorf("invalid user format %q: must match pattern 'username:role'", user)
}

userRoleStrings[index] = [2]string{userAndRole[1], userAndRole[2]}
}

groupRoleStrings := make([][2]string, len(groups))
for index, group := range groups {
groupAndRole := nameRoleRegex.FindStringSubmatch(group)
if groupAndRole == nil {
return xerrors.Errorf("invalid group format %q: must match pattern 'group:role'", group)
}

groupRoleStrings[index] = [2]string{groupAndRole[1], groupAndRole[2]}
}

userRoles, groupRoles, err := fetchUsersAndGroups(inv.Context(), fetchUsersAndGroupsParams{
Client: client,
OrgID: workspace.OrganizationID,
OrgName: workspace.OrganizationName,
Users: userRoleStrings,
Groups: groupRoleStrings,
DefaultRole: codersdk.WorkspaceRoleUse,
})
if err != nil {
return err
}

userRoles := make(map[string]codersdk.WorkspaceRole, len(users))
if len(users) > 0 {
orgMembers, err := client.OrganizationMembers(inv.Context(), org.ID)
if err != nil {
return err
}
err = client.UpdateWorkspaceACL(inv.Context(), workspace.ID, codersdk.UpdateWorkspaceACL{
UserRoles: userRoles,
GroupRoles: groupRoles,
})
if err != nil {
return err
}

for _, user := range users {
userAndRole := nameRoleRegex.FindStringSubmatch(user)
if userAndRole == nil {
return xerrors.Errorf("invalid user format %q: must match pattern 'username:role'", user)
}

username := userAndRole[1]
role := userAndRole[2]
if role == "" {
role = string(codersdk.WorkspaceRoleUse)
}

userID := ""
for _, member := range orgMembers {
if member.Username == username {
userID = member.UserID.String()
break
}
}
if userID == "" {
return xerrors.Errorf("could not find user %s in the organization %s", username, org.Name)
}

workspaceRole, err := stringToWorkspaceRole(role)
if err != nil {
return err
}

userRoles[userID] = workspaceRole
}
acl, err := client.WorkspaceACL(inv.Context(), workspace.ID)
if err != nil {
return xerrors.Errorf("could not fetch current workspace ACL after sharing %w", err)
}

out, err := workspaceACLToTable(inv.Context(), &acl)
if err != nil {
return err
}

groupRoles := make(map[string]codersdk.WorkspaceRole)
if len(groups) > 0 {
orgGroups, err := client.Groups(inv.Context(), codersdk.GroupArguments{
Organization: org.ID.String(),
})
if err != nil {
return err
_, err = fmt.Fprintln(inv.Stdout, out)
return err
},
}

return cmd
}

func (r *RootCmd) unshareWorkspace() *serpent.Command {
var (
client = new(codersdk.Client)
users []string
groups []string
)

cmd := &serpent.Command{
Use: "remove <workspace> --user <user> --group <group>",
Aliases: []string{"unshare"},
Short: "Remove shared access for users or groups from a workspace.",
Options: serpent.OptionSet{
{
Name: "user",
Description: "A comma separated list of users to share the workspace with.",
Flag: "user",
Value: serpent.StringArrayOf(&users),
}, {
Name: "group",
Description: "A comma separated list of groups to share the workspace with.",
Flag: "group",
Value: serpent.StringArrayOf(&groups),
},
},
Middleware: serpent.Chain(
r.InitClient(client),
serpent.RequireNArgs(1),
),
Handler: func(inv *serpent.Invocation) error {
if len(users) == 0 && len(groups) == 0 {
return xerrors.New("at least one user or group must be provided")
}

workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0])
if err != nil {
return xerrors.Errorf("could not fetch the workspace %s: %w", inv.Args[0], err)
}

userRoleStrings := make([][2]string, len(users))
for index, user := range users {
if !codersdk.UsernameValidRegex.MatchString(user) {
return xerrors.Errorf("invalid username")
}

for _, group := range groups {
groupAndRole := nameRoleRegex.FindStringSubmatch(group)
if groupAndRole == nil {
return xerrors.Errorf("invalid group format %q: must match pattern 'group:role'", group)
}
groupName := groupAndRole[1]
role := groupAndRole[2]
if role == "" {
role = string(codersdk.WorkspaceRoleUse)
}

var orgGroup *codersdk.Group
for _, group := range orgGroups {
if group.Name == groupName {
orgGroup = &group
break
}
}

if orgGroup == nil {
return xerrors.Errorf("could not find group named %s belonging to the organization %s", groupName, org.Name)
}

workspaceRole, err := stringToWorkspaceRole(role)
if err != nil {
return err
}

groupRoles[orgGroup.ID.String()] = workspaceRole
userRoleStrings[index] = [2]string{user, ""}
}

groupRoleStrings := make([][2]string, len(groups))
for index, group := range groups {
if !codersdk.UsernameValidRegex.MatchString(group) {
return xerrors.Errorf("invalid group name")
}

groupRoleStrings[index] = [2]string{group, ""}
}

userRoles, groupRoles, err := fetchUsersAndGroups(inv.Context(), fetchUsersAndGroupsParams{
Client: client,
OrgID: workspace.OrganizationID,
OrgName: workspace.OrganizationName,
Users: userRoleStrings,
Groups: groupRoleStrings,
DefaultRole: codersdk.WorkspaceRoleDeleted,
})
if err != nil {
return err
}

err = client.UpdateWorkspaceACL(inv.Context(), workspace.ID, codersdk.UpdateWorkspaceACL{
Expand Down Expand Up @@ -227,9 +269,11 @@ func stringToWorkspaceRole(role string) (codersdk.WorkspaceRole, error) {
return codersdk.WorkspaceRoleUse, nil
case string(codersdk.WorkspaceRoleAdmin):
return codersdk.WorkspaceRoleAdmin, nil
case string(codersdk.WorkspaceRoleDeleted):
return codersdk.WorkspaceRoleDeleted, nil
default:
return "", xerrors.Errorf("invalid role %q: expected %q or %q",
role, codersdk.WorkspaceRoleAdmin, codersdk.WorkspaceRoleUse)
return "", xerrors.Errorf("invalid role %q: expected %q, %q, or \"%q\"",
role, codersdk.WorkspaceRoleAdmin, codersdk.WorkspaceRoleUse, codersdk.WorkspaceRoleDeleted)
}
}

Expand Down Expand Up @@ -277,3 +321,96 @@ func workspaceACLToTable(ctx context.Context, acl *codersdk.WorkspaceACL) (strin

return out, nil
}

type fetchUsersAndGroupsParams struct {
Client *codersdk.Client
OrgID uuid.UUID
OrgName string
Users [][2]string
Groups [][2]string
DefaultRole codersdk.WorkspaceRole
}

func fetchUsersAndGroups(ctx context.Context, params fetchUsersAndGroupsParams) (userRoles map[string]codersdk.WorkspaceRole, groupRoles map[string]codersdk.WorkspaceRole, err error) {
var (
client = params.Client
orgID = params.OrgID
orgName = params.OrgName
users = params.Users
groups = params.Groups
defaultRole = params.DefaultRole
)

userRoles = make(map[string]codersdk.WorkspaceRole, len(users))
if len(users) > 0 {
orgMembers, err := client.OrganizationMembers(ctx, orgID)
if err != nil {
return nil, nil, err
}

for _, user := range users {
username := user[0]
role := user[1]
if role == "" {
role = string(defaultRole)
}

userID := ""
for _, member := range orgMembers {
if member.Username == username {
userID = member.UserID.String()
break
}
}
if userID == "" {
return nil, nil, xerrors.Errorf("could not find user %s in the organization %s", username, orgName)
}

workspaceRole, err := stringToWorkspaceRole(role)
if err != nil {
return nil, nil, err
}

userRoles[userID] = workspaceRole
}
}

groupRoles = make(map[string]codersdk.WorkspaceRole)
if len(groups) > 0 {
orgGroups, err := client.Groups(ctx, codersdk.GroupArguments{
Organization: orgID.String(),
})
if err != nil {
return nil, nil, err
}

for _, group := range groups {
groupName := group[0]
role := group[1]
if role == "" {
role = string(defaultRole)
}

var orgGroup *codersdk.Group
for _, og := range orgGroups {
if og.Name == groupName {
orgGroup = &og
break
}
}

if orgGroup == nil {
return nil, nil, xerrors.Errorf("could not find group named %s belonging to the organization %s", groupName, orgName)
}

workspaceRole, err := stringToWorkspaceRole(role)
if err != nil {
return nil, nil, err
}

groupRoles[orgGroup.ID.String()] = workspaceRole
}
}

return userRoles, groupRoles, nil
}
Loading
Loading