Skip to content

Commit 4c4d966

Browse files
authored
feat: add ability to make workspace for other user from cli (coder#8481)
* feat: add ability to make workspace for other user from cli * Add example to show functionality
1 parent 5fd77ad commit 4c4d966

File tree

5 files changed

+97
-12
lines changed

5 files changed

+97
-12
lines changed

cli/create.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,25 @@ func (r *RootCmd) create() *clibase.Cmd {
3131
Annotations: workspaceCommand,
3232
Use: "create [name]",
3333
Short: "Create a workspace",
34-
Middleware: clibase.Chain(r.InitClient(client)),
34+
Long: formatExamples(
35+
example{
36+
Description: "Create a workspace for another user (if you have permission)",
37+
Command: "coder create <username>/<workspace_name>",
38+
},
39+
),
40+
Middleware: clibase.Chain(r.InitClient(client)),
3541
Handler: func(inv *clibase.Invocation) error {
3642
organization, err := CurrentOrganization(inv, client)
3743
if err != nil {
3844
return err
3945
}
4046

47+
workspaceOwner := codersdk.Me
4148
if len(inv.Args) >= 1 {
42-
workspaceName = inv.Args[0]
49+
workspaceOwner, workspaceName, err = splitNamedWorkspace(inv.Args[0])
50+
if err != nil {
51+
return err
52+
}
4353
}
4454

4555
if workspaceName == "" {
@@ -58,7 +68,7 @@ func (r *RootCmd) create() *clibase.Cmd {
5868
}
5969
}
6070

61-
_, err = client.WorkspaceByOwnerAndName(inv.Context(), codersdk.Me, workspaceName, codersdk.WorkspaceOptions{})
71+
_, err = client.WorkspaceByOwnerAndName(inv.Context(), workspaceOwner, workspaceName, codersdk.WorkspaceOptions{})
6272
if err == nil {
6373
return xerrors.Errorf("A workspace already exists named %q!", workspaceName)
6474
}
@@ -146,7 +156,7 @@ func (r *RootCmd) create() *clibase.Cmd {
146156
ttlMillis = &template.MaxTTLMillis
147157
}
148158

149-
workspace, err := client.CreateWorkspace(inv.Context(), organization.ID, codersdk.Me, codersdk.CreateWorkspaceRequest{
159+
workspace, err := client.CreateWorkspace(inv.Context(), organization.ID, workspaceOwner, codersdk.CreateWorkspaceRequest{
150160
TemplateID: template.ID,
151161
Name: workspaceName,
152162
AutostartSchedule: schedSpec,

cli/create_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,63 @@ func TestCreate(t *testing.T) {
7979
}
8080
})
8181

82+
t.Run("CreateForOtherUser", func(t *testing.T) {
83+
t.Parallel()
84+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
85+
owner := coderdtest.CreateFirstUser(t, client)
86+
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
87+
Parse: echo.ParseComplete,
88+
ProvisionApply: provisionCompleteWithAgent,
89+
ProvisionPlan: provisionCompleteWithAgent,
90+
})
91+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
92+
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
93+
_, user := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
94+
args := []string{
95+
"create",
96+
user.Username + "/their-workspace",
97+
"--template", template.Name,
98+
"--start-at", "9:30AM Mon-Fri US/Central",
99+
"--stop-after", "8h",
100+
}
101+
102+
inv, root := clitest.New(t, args...)
103+
clitest.SetupConfig(t, client, root)
104+
doneChan := make(chan struct{})
105+
pty := ptytest.New(t).Attach(inv)
106+
go func() {
107+
defer close(doneChan)
108+
err := inv.Run()
109+
assert.NoError(t, err)
110+
}()
111+
matches := []struct {
112+
match string
113+
write string
114+
}{
115+
{match: "compute.main"},
116+
{match: "smith (linux, i386)"},
117+
{match: "Confirm create", write: "yes"},
118+
}
119+
for _, m := range matches {
120+
pty.ExpectMatch(m.match)
121+
if len(m.write) > 0 {
122+
pty.WriteLine(m.write)
123+
}
124+
}
125+
<-doneChan
126+
127+
ws, err := client.WorkspaceByOwnerAndName(context.Background(), user.Username, "their-workspace", codersdk.WorkspaceOptions{})
128+
if assert.NoError(t, err, "expected workspace to be created") {
129+
assert.Equal(t, ws.TemplateName, template.Name)
130+
if assert.NotNil(t, ws.AutostartSchedule) {
131+
assert.Equal(t, *ws.AutostartSchedule, "CRON_TZ=US/Central 30 9 * * Mon-Fri")
132+
}
133+
if assert.NotNil(t, ws.TTLMillis) {
134+
assert.Equal(t, *ws.TTLMillis, 8*time.Hour.Milliseconds())
135+
}
136+
}
137+
})
138+
82139
t.Run("InheritStopAfterFromTemplate", func(t *testing.T) {
83140
t.Parallel()
84141
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})

cli/root.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -626,24 +626,30 @@ func CurrentOrganization(inv *clibase.Invocation, client *codersdk.Client) (code
626626
return orgs[0], nil
627627
}
628628

629-
// namedWorkspace fetches and returns a workspace by an identifier, which may be either
630-
// a bare name (for a workspace owned by the current user) or a "user/workspace" combination,
631-
// where user is either a username or UUID.
632-
func namedWorkspace(ctx context.Context, client *codersdk.Client, identifier string) (codersdk.Workspace, error) {
629+
func splitNamedWorkspace(identifier string) (owner string, workspaceName string, err error) {
633630
parts := strings.Split(identifier, "/")
634631

635-
var owner, name string
636632
switch len(parts) {
637633
case 1:
638634
owner = codersdk.Me
639-
name = parts[0]
635+
workspaceName = parts[0]
640636
case 2:
641637
owner = parts[0]
642-
name = parts[1]
638+
workspaceName = parts[1]
643639
default:
644-
return codersdk.Workspace{}, xerrors.Errorf("invalid workspace name: %q", identifier)
640+
return "", "", xerrors.Errorf("invalid workspace name: %q", identifier)
645641
}
642+
return owner, workspaceName, nil
643+
}
646644

645+
// namedWorkspace fetches and returns a workspace by an identifier, which may be either
646+
// a bare name (for a workspace owned by the current user) or a "user/workspace" combination,
647+
// where user is either a username or UUID.
648+
func namedWorkspace(ctx context.Context, client *codersdk.Client, identifier string) (codersdk.Workspace, error) {
649+
owner, name, err := splitNamedWorkspace(identifier)
650+
if err != nil {
651+
return codersdk.Workspace{}, err
652+
}
647653
return client.WorkspaceByOwnerAndName(ctx, owner, name, codersdk.WorkspaceOptions{})
648654
}
649655

cli/testdata/coder_create_--help.golden

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ Usage: coder create [flags] [name]
22

33
Create a workspace
44

5+
- Create a workspace for another user (if you have permission):
6+
7+
 $ coder create <username>/<workspace_name> 
8+
59
Options
610
--build-options bool
711
Prompt for one-time build options defined with ephemeral parameters.

docs/cli/create.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ Create a workspace
1010
coder create [flags] [name]
1111
```
1212

13+
## Description
14+
15+
```console
16+
- Create a workspace for another user (if you have permission):
17+
18+
$ coder create <username>/<workspace_name>
19+
```
20+
1321
## Options
1422

1523
### --build-options

0 commit comments

Comments
 (0)