From 4f9ee582f306865769f6bbbc420d3b81877dd2dc Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 12 Jul 2023 14:40:21 -0400 Subject: [PATCH 1/3] feat: add ability to make workspace for other user from cli --- cli/create.go | 10 +++++--- cli/create_test.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++ cli/root.go | 22 +++++++++++------- 3 files changed, 78 insertions(+), 11 deletions(-) diff --git a/cli/create.go b/cli/create.go index b7ffbec5819cb..a7cf860a6ab75 100644 --- a/cli/create.go +++ b/cli/create.go @@ -36,8 +36,12 @@ func (r *RootCmd) create() *clibase.Cmd { return err } + workspaceOwner := codersdk.Me if len(inv.Args) >= 1 { - workspaceName = inv.Args[0] + workspaceOwner, workspaceName, err = splitNamedWorkspace(inv.Args[0]) + if err != nil { + return err + } } if workspaceName == "" { @@ -56,7 +60,7 @@ func (r *RootCmd) create() *clibase.Cmd { } } - _, err = client.WorkspaceByOwnerAndName(inv.Context(), codersdk.Me, workspaceName, codersdk.WorkspaceOptions{}) + _, err = client.WorkspaceByOwnerAndName(inv.Context(), workspaceOwner, workspaceName, codersdk.WorkspaceOptions{}) if err == nil { return xerrors.Errorf("A workspace already exists named %q!", workspaceName) } @@ -143,7 +147,7 @@ func (r *RootCmd) create() *clibase.Cmd { ttlMillis = &template.MaxTTLMillis } - workspace, err := client.CreateWorkspace(inv.Context(), organization.ID, codersdk.Me, codersdk.CreateWorkspaceRequest{ + workspace, err := client.CreateWorkspace(inv.Context(), organization.ID, workspaceOwner, codersdk.CreateWorkspaceRequest{ TemplateID: template.ID, Name: workspaceName, AutostartSchedule: schedSpec, diff --git a/cli/create_test.go b/cli/create_test.go index 176dce82656e6..dbdc2c7a3bb68 100644 --- a/cli/create_test.go +++ b/cli/create_test.go @@ -79,6 +79,63 @@ func TestCreate(t *testing.T) { } }) + t.Run("CreateForOtherUser", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + owner := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionApply: provisionCompleteWithAgent, + ProvisionPlan: provisionCompleteWithAgent, + }) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) + _, user := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) + args := []string{ + "create", + user.Username + "/their-workspace", + "--template", template.Name, + "--start-at", "9:30AM Mon-Fri US/Central", + "--stop-after", "8h", + } + + inv, root := clitest.New(t, args...) + clitest.SetupConfig(t, client, root) + doneChan := make(chan struct{}) + pty := ptytest.New(t).Attach(inv) + go func() { + defer close(doneChan) + err := inv.Run() + assert.NoError(t, err) + }() + matches := []struct { + match string + write string + }{ + {match: "compute.main"}, + {match: "smith (linux, i386)"}, + {match: "Confirm create", write: "yes"}, + } + for _, m := range matches { + pty.ExpectMatch(m.match) + if len(m.write) > 0 { + pty.WriteLine(m.write) + } + } + <-doneChan + + ws, err := client.WorkspaceByOwnerAndName(context.Background(), user.Username, "their-workspace", codersdk.WorkspaceOptions{}) + if assert.NoError(t, err, "expected workspace to be created") { + assert.Equal(t, ws.TemplateName, template.Name) + if assert.NotNil(t, ws.AutostartSchedule) { + assert.Equal(t, *ws.AutostartSchedule, "CRON_TZ=US/Central 30 9 * * Mon-Fri") + } + if assert.NotNil(t, ws.TTLMillis) { + assert.Equal(t, *ws.TTLMillis, 8*time.Hour.Milliseconds()) + } + } + }) + t.Run("InheritStopAfterFromTemplate", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) diff --git a/cli/root.go b/cli/root.go index c5843f97f5337..4c268235a0f96 100644 --- a/cli/root.go +++ b/cli/root.go @@ -626,24 +626,30 @@ func CurrentOrganization(inv *clibase.Invocation, client *codersdk.Client) (code return orgs[0], nil } -// namedWorkspace fetches and returns a workspace by an identifier, which may be either -// a bare name (for a workspace owned by the current user) or a "user/workspace" combination, -// where user is either a username or UUID. -func namedWorkspace(ctx context.Context, client *codersdk.Client, identifier string) (codersdk.Workspace, error) { +func splitNamedWorkspace(identifier string) (owner string, workspaceName string, err error) { parts := strings.Split(identifier, "/") - var owner, name string switch len(parts) { case 1: owner = codersdk.Me - name = parts[0] + workspaceName = parts[0] case 2: owner = parts[0] - name = parts[1] + workspaceName = parts[1] default: - return codersdk.Workspace{}, xerrors.Errorf("invalid workspace name: %q", identifier) + return "", "", xerrors.Errorf("invalid workspace name: %q", identifier) } + return owner, workspaceName, nil +} +// namedWorkspace fetches and returns a workspace by an identifier, which may be either +// a bare name (for a workspace owned by the current user) or a "user/workspace" combination, +// where user is either a username or UUID. +func namedWorkspace(ctx context.Context, client *codersdk.Client, identifier string) (codersdk.Workspace, error) { + owner, name, err := splitNamedWorkspace(identifier) + if err != nil { + return codersdk.Workspace{}, err + } return client.WorkspaceByOwnerAndName(ctx, owner, name, codersdk.WorkspaceOptions{}) } From ca260b1785943a1dc7deda135999b1aac3859c05 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 12 Jul 2023 14:42:44 -0400 Subject: [PATCH 2/3] Add example to show functionality --- cli/create.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cli/create.go b/cli/create.go index a7cf860a6ab75..b82401bf608d0 100644 --- a/cli/create.go +++ b/cli/create.go @@ -29,7 +29,13 @@ func (r *RootCmd) create() *clibase.Cmd { Annotations: workspaceCommand, Use: "create [name]", Short: "Create a workspace", - Middleware: clibase.Chain(r.InitClient(client)), + Long: formatExamples( + example{ + Description: "Create a workspace for another user (if you have permission)", + Command: "coder create /", + }, + ), + Middleware: clibase.Chain(r.InitClient(client)), Handler: func(inv *clibase.Invocation) error { organization, err := CurrentOrganization(inv, client) if err != nil { From e64813a52368cf83978885f85dac0aa76c0de49f Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 12 Jul 2023 14:46:30 -0400 Subject: [PATCH 3/3] make gen --- cli/testdata/coder_create_--help.golden | 4 ++++ docs/cli/create.md | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/cli/testdata/coder_create_--help.golden b/cli/testdata/coder_create_--help.golden index 8d7c4ce9653e5..6c8bcc46908c9 100644 --- a/cli/testdata/coder_create_--help.golden +++ b/cli/testdata/coder_create_--help.golden @@ -2,6 +2,10 @@ Usage: coder create [flags] [name] Create a workspace +- Create a workspace for another user (if you have permission): + +  $ coder create /  + Options --rich-parameter-file string, $CODER_RICH_PARAMETER_FILE Specify a file path with values for rich parameters defined in the diff --git a/docs/cli/create.md b/docs/cli/create.md index 1a76673c86945..3f7ff099b16c8 100644 --- a/docs/cli/create.md +++ b/docs/cli/create.md @@ -10,6 +10,14 @@ Create a workspace coder create [flags] [name] ``` +## Description + +```console + - Create a workspace for another user (if you have permission): + + $ coder create / +``` + ## Options ### --rich-parameter-file