From 39fc586624d8ecc4d91c44454d30a93fca643b55 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Thu, 14 Apr 2022 12:27:22 -0400 Subject: [PATCH 1/4] chore: use workspace name as arg in `coder workspaces create` --- cli/templatecreate.go | 14 ++++++-- cli/workspacecreate.go | 74 +++++++++++++++++++++++------------------- go.mod | 3 +- go.sum | 6 ++-- 4 files changed, 58 insertions(+), 39 deletions(-) diff --git a/cli/templatecreate.go b/cli/templatecreate.go index 8d0ebb1f1fb26..680a55b28389a 100644 --- a/cli/templatecreate.go +++ b/cli/templatecreate.go @@ -26,13 +26,14 @@ func templateCreate() *cobra.Command { provisioner string ) cmd := &cobra.Command{ - Use: "create [name]", + Use: "create [name]", Short: "Create a template from the current directory", RunE: func(cmd *cobra.Command, args []string) error { client, err := createClient(cmd) if err != nil { return err } + organization, err := currentOrganization(cmd, client) if err != nil { return err @@ -44,6 +45,7 @@ func templateCreate() *cobra.Command { } else { templateName = args[0] } + _, err = client.TemplateByName(cmd.Context(), organization.ID, templateName) if err == nil { return xerrors.Errorf("A template already exists named %q!", templateName) @@ -113,7 +115,7 @@ func templateCreate() *cobra.Command { "The "+cliui.Styles.Keyword.Render(templateName)+" template has been created! "+ "Developers can provision a workspace with this template using:")+"\n") - _, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+cliui.Styles.Code.Render("coder workspaces create "+templateName)) + _, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+cliui.Styles.Code.Render("coder workspaces create [workspace name] --template="+fmt.Sprintf("%q", templateName))) _, _ = fmt.Fprintln(cmd.OutOrStdout()) return nil @@ -214,9 +216,15 @@ func createValidTemplateVersion(cmd *cobra.Command, client *codersdk.Client, org if err != nil { return nil, nil, err } - return &version, parameters, cliui.WorkspaceResources(cmd.OutOrStdout(), resources, cliui.WorkspaceResourcesOptions{ + + err = cliui.WorkspaceResources(cmd.OutOrStdout(), resources, cliui.WorkspaceResourcesOptions{ HideAgentState: true, HideAccess: true, Title: "Template Preview", }) + if err != nil { + return nil, nil, xerrors.Errorf("preview template resources: %w", err) + } + + return &version, parameters, nil } diff --git a/cli/workspacecreate.go b/cli/workspacecreate.go index f7225da59d7b8..e9a8828f6eb92 100644 --- a/cli/workspacecreate.go +++ b/cli/workspacecreate.go @@ -2,10 +2,10 @@ package cli import ( "fmt" - "sort" "time" "github.com/spf13/cobra" + "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/coder/coder/cli/cliflag" @@ -17,50 +17,78 @@ import ( func workspaceCreate() *cobra.Command { var ( workspaceName string + templateName string ) cmd := &cobra.Command{ - Use: "create [template]", + Use: "create [name]", Short: "Create a workspace from a template", RunE: func(cmd *cobra.Command, args []string) error { client, err := createClient(cmd) if err != nil { return err } + organization, err := currentOrganization(cmd, client) if err != nil { return err } - templateName := "" if len(args) >= 1 { - templateName = args[0] + workspaceName = args[0] + } + + if workspaceName == "" { + workspaceName, err = cliui.Prompt(cmd, cliui.PromptOptions{ + Text: "Specify a name for your workspace:", + Validate: func(workspaceName string) error { + _, err = client.WorkspaceByName(cmd.Context(), codersdk.Me, workspaceName) + if err == nil { + return xerrors.Errorf("A workspace already exists named %q!", workspaceName) + } + return nil + }, + }) + if err != nil { + return err + } + } + + _, err = client.WorkspaceByName(cmd.Context(), codersdk.Me, workspaceName) + if err == nil { + return xerrors.Errorf("A workspace already exists named %q!", workspaceName) } var template codersdk.Template if templateName == "" { _, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Wrap.Render("Select a template below to preview the provisioned infrastructure:")) - templateNames := []string{} - templateByName := map[string]codersdk.Template{} templates, err := client.TemplatesByOrganization(cmd.Context(), organization.ID) if err != nil { return err } + + templateNames := make([]string, 0, len(templates)) + templateByName := make(map[string]codersdk.Template, len(templates)) + for _, template := range templates { templateName := template.Name + if template.WorkspaceOwnerCount > 0 { developerText := "developer" if template.WorkspaceOwnerCount != 1 { developerText = "developers" } + templateName += cliui.Styles.Placeholder.Render(fmt.Sprintf(" (used by %d %s)", template.WorkspaceOwnerCount, developerText)) } + templateNames = append(templateNames, templateName) templateByName[templateName] = template } - sort.Slice(templateNames, func(i, j int) bool { - return templateByName[templateNames[i]].WorkspaceOwnerCount > templateByName[templateNames[j]].WorkspaceOwnerCount + slices.SortFunc(templateNames, func(a, b string) bool { + return templateByName[a].WorkspaceOwnerCount > templateByName[b].WorkspaceOwnerCount }) + // Move the cursor up a single line for nicer display! option, err := cliui.Select(cmd, cliui.SelectOptions{ Options: templateNames, @@ -69,36 +97,13 @@ func workspaceCreate() *cobra.Command { if err != nil { return err } + template = templateByName[option] } else { template, err = client.TemplateByName(cmd.Context(), organization.ID, templateName) if err != nil { return xerrors.Errorf("get template by name: %w", err) } - if err != nil { - return err - } - } - - if workspaceName == "" { - workspaceName, err = cliui.Prompt(cmd, cliui.PromptOptions{ - Text: "Specify a name for your workspace:", - Validate: func(workspaceName string) error { - _, err = client.WorkspaceByName(cmd.Context(), codersdk.Me, workspaceName) - if err == nil { - return xerrors.Errorf("A workspace already exists named %q!", workspaceName) - } - return nil - }, - }) - if err != nil { - return err - } - } - - _, err = client.WorkspaceByName(cmd.Context(), codersdk.Me, workspaceName) - if err == nil { - return xerrors.Errorf("A workspace already exists named %q!", workspaceName) } templateVersion, err := client.TemplateVersion(cmd.Context(), template.ActiveVersionID) @@ -164,10 +169,12 @@ func workspaceCreate() *cobra.Command { if err != nil { return err } + err = cliui.WorkspaceBuild(cmd.Context(), cmd.OutOrStdout(), client, workspace.LatestBuild.ID, before) if err != nil { return err } + resources, err = client.WorkspaceResourcesByBuild(cmd.Context(), workspace.LatestBuild.ID) if err != nil { return err @@ -179,11 +186,12 @@ func workspaceCreate() *cobra.Command { if err != nil { return err } + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "The %s workspace has been created!\n", cliui.Styles.Keyword.Render(workspace.Name)) return nil }, } - cliflag.StringVarP(cmd.Flags(), &workspaceName, "name", "n", "CODER_WORKSPACE_NAME", "", "Specify a workspace name.") + cliflag.StringVarP(cmd.Flags(), &templateName, "template", "t", "CODER_TEMPLATE_NAME", "", "Specify a template name.") return cmd } diff --git a/go.mod b/go.mod index 435047b296f38..215ffceefcec2 100644 --- a/go.mod +++ b/go.mod @@ -92,7 +92,8 @@ require ( go.uber.org/atomic v1.9.0 go.uber.org/goleak v1.1.12 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd - golang.org/x/mod v0.5.1 + golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd + golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 diff --git a/go.sum b/go.sum index 9466a75731c4a..6ad9143a0e8ea 100644 --- a/go.sum +++ b/go.sum @@ -1787,6 +1787,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200901203048-c4f52b2c50aa/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200908183739-ae8ad444f925/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw= +golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd h1:zVFyTKZN/Q7mNRWSs1GOYnHM9NiFSJ54YVRsD0rNWT4= +golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1821,8 +1823,8 @@ golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hM golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 h1:LQmS1nU0twXLA96Kt7U9qtHJEbBk3z6Q0V4UXjZkpr4= +golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= From 154d2b4fd2ea1ae0d53ef77794301c6d2294e5e0 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Thu, 14 Apr 2022 12:36:34 -0400 Subject: [PATCH 2/4] presort templates --- cli/workspacecreate.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cli/workspacecreate.go b/cli/workspacecreate.go index e9a8828f6eb92..2f09cbadb330c 100644 --- a/cli/workspacecreate.go +++ b/cli/workspacecreate.go @@ -67,6 +67,10 @@ func workspaceCreate() *cobra.Command { return err } + slices.SortFunc(templates, func(a, b codersdk.Template) bool { + return a.WorkspaceOwnerCount > b.WorkspaceOwnerCount + }) + templateNames := make([]string, 0, len(templates)) templateByName := make(map[string]codersdk.Template, len(templates)) @@ -85,9 +89,6 @@ func workspaceCreate() *cobra.Command { templateNames = append(templateNames, templateName) templateByName[templateName] = template } - slices.SortFunc(templateNames, func(a, b string) bool { - return templateByName[a].WorkspaceOwnerCount > templateByName[b].WorkspaceOwnerCount - }) // Move the cursor up a single line for nicer display! option, err := cliui.Select(cmd, cliui.SelectOptions{ From fc113bb29effaf2c5ec199706623891562bc78fd Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Thu, 14 Apr 2022 12:40:04 -0400 Subject: [PATCH 3/4] reorder example command --- cli/templatecreate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/templatecreate.go b/cli/templatecreate.go index 680a55b28389a..3505e94a37940 100644 --- a/cli/templatecreate.go +++ b/cli/templatecreate.go @@ -115,7 +115,7 @@ func templateCreate() *cobra.Command { "The "+cliui.Styles.Keyword.Render(templateName)+" template has been created! "+ "Developers can provision a workspace with this template using:")+"\n") - _, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+cliui.Styles.Code.Render("coder workspaces create [workspace name] --template="+fmt.Sprintf("%q", templateName))) + _, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+cliui.Styles.Code.Render(fmt.Sprintf("coder workspaces create --template=%q [workspace name]", templateName))) _, _ = fmt.Fprintln(cmd.OutOrStdout()) return nil From 35eac1f99e0bebd7b1019ea8048f84ac95452bb4 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Thu, 14 Apr 2022 12:45:16 -0400 Subject: [PATCH 4/4] fix tests --- cli/workspacecreate_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/workspacecreate_test.go b/cli/workspacecreate_test.go index 26b751328dc34..83118da5c7e48 100644 --- a/cli/workspacecreate_test.go +++ b/cli/workspacecreate_test.go @@ -22,7 +22,7 @@ func TestWorkspaceCreate(t *testing.T) { version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) coderdtest.AwaitTemplateVersionJob(t, client, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) - cmd, root := clitest.New(t, "workspaces", "create", template.Name, "--name", "my-workspace") + cmd, root := clitest.New(t, "workspaces", "create", "my-workspace", "--template", template.Name) clitest.SetupConfig(t, client, root) doneChan := make(chan struct{}) pty := ptytest.New(t) @@ -52,7 +52,7 @@ func TestWorkspaceCreate(t *testing.T) { version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) coderdtest.AwaitTemplateVersionJob(t, client, version.ID) _ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) - cmd, root := clitest.New(t, "workspaces", "create", "--name", "my-workspace") + cmd, root := clitest.New(t, "workspaces", "create", "my-workspace") clitest.SetupConfig(t, client, root) doneChan := make(chan struct{}) pty := ptytest.New(t) @@ -82,7 +82,7 @@ func TestWorkspaceCreate(t *testing.T) { version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) coderdtest.AwaitTemplateVersionJob(t, client, version.ID) _ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) - cmd, root := clitest.New(t, "workspaces", "create", "--name", "") + cmd, root := clitest.New(t, "workspaces", "create", "") clitest.SetupConfig(t, client, root) doneChan := make(chan struct{}) pty := ptytest.New(t) @@ -134,7 +134,7 @@ func TestWorkspaceCreate(t *testing.T) { }) coderdtest.AwaitTemplateVersionJob(t, client, version.ID) _ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) - cmd, root := clitest.New(t, "workspaces", "create", "--name", "") + cmd, root := clitest.New(t, "workspaces", "create", "") clitest.SetupConfig(t, client, root) doneChan := make(chan struct{}) pty := ptytest.New(t)