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

Skip to content

Commit fb10d6f

Browse files
committed
Improve workspace create flow
1 parent c50d170 commit fb10d6f

File tree

7 files changed

+178
-53
lines changed

7 files changed

+178
-53
lines changed

cli/cliui/select.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ func Select(cmd *cobra.Command, opts SelectOptions) (string, error) {
5050
if flag.Lookup("test.v") != nil {
5151
return opts.Options[0], nil
5252
}
53-
opts.HideSearch = false
5453
var value string
5554
err := survey.AskOne(&survey.Select{
5655
Options: opts.Options,

cli/start.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -423,8 +423,8 @@ func newProvisionerDaemon(ctx context.Context, client *codersdk.Client, logger s
423423

424424
return provisionerd.New(client.ListenProvisionerDaemon, &provisionerd.Options{
425425
Logger: logger,
426-
PollInterval: 50 * time.Millisecond,
427-
UpdateInterval: 50 * time.Millisecond,
426+
PollInterval: 500 * time.Millisecond,
427+
UpdateInterval: 500 * time.Millisecond,
428428
Provisioners: provisionerd.Provisioners{
429429
string(database.ProvisionerTypeTerraform): proto.NewDRPCProvisionerClient(provisionersdk.Conn(terraformClient)),
430430
},

cli/templatecreate.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package cli
22

33
import (
4-
"errors"
54
"fmt"
65
"os"
76
"path/filepath"
@@ -10,7 +9,6 @@ import (
109
"time"
1110

1211
"github.com/briandowns/spinner"
13-
"github.com/manifoldco/promptui"
1412
"github.com/spf13/cobra"
1513
"golang.org/x/xerrors"
1614

@@ -98,9 +96,6 @@ func templateCreate() *cobra.Command {
9896
IsConfirm: true,
9997
})
10098
if err != nil {
101-
if errors.Is(err, promptui.ErrAbort) {
102-
return nil
103-
}
10499
return err
105100
}
106101
}

cli/templatecreate_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ func TestTemplateCreate(t *testing.T) {
3535
require.NoError(t, err)
3636
}()
3737
matches := []string{
38-
"Create template?", "yes",
38+
"Create and upload", "yes",
39+
"Confirm create?", "yes",
3940
}
4041
for i := 0; i < len(matches); i += 2 {
4142
match := matches[i]

cli/templateinit.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ func templateInit() *cobra.Command {
2828
exampleByName[example.Name] = example
2929
}
3030

31-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Wrap.Render("A template defines infrastructure as code to be provisioned for individual developer workspaces. Select an example to get started:\n"))
31+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Wrap.Render(
32+
"A template defines infrastructure as code to be provisioned "+
33+
"for individual developer workspaces. Select an example to get started:\n"))
3234
option, err := cliui.Select(cmd, cliui.SelectOptions{
3335
Options: exampleNames,
3436
})

cli/workspacecreate.go

Lines changed: 53 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
11
package cli
22

33
import (
4-
"errors"
54
"fmt"
65
"sort"
76
"time"
87

9-
"github.com/fatih/color"
10-
"github.com/manifoldco/promptui"
118
"github.com/spf13/cobra"
129
"golang.org/x/xerrors"
1310

11+
"github.com/coder/coder/cli/cliflag"
1412
"github.com/coder/coder/cli/cliui"
1513
"github.com/coder/coder/coderd/database"
1614
"github.com/coder/coder/codersdk"
1715
)
1816

1917
func workspaceCreate() *cobra.Command {
2018
var (
21-
templateName string
19+
workspaceName string
2220
)
2321
cmd := &cobra.Command{
24-
Use: "create <name>",
25-
Args: cobra.ExactArgs(1),
22+
Use: "create [template]",
2623
Short: "Create a workspace from a template",
2724
RunE: func(cmd *cobra.Command, args []string) error {
2825
client, err := createClient(cmd)
@@ -34,9 +31,14 @@ func workspaceCreate() *cobra.Command {
3431
return err
3532
}
3633

34+
templateName := ""
35+
if len(args) >= 1 {
36+
templateName = args[0]
37+
}
38+
3739
var template codersdk.Template
3840
if templateName == "" {
39-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Wrap.Render("Select a template:"))
41+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Wrap.Render("Select a template below to preview the provisioned infrastructure:"))
4042

4143
templateNames := []string{}
4244
templateByName := map[string]codersdk.Template{}
@@ -45,8 +47,16 @@ func workspaceCreate() *cobra.Command {
4547
return err
4648
}
4749
for _, template := range templates {
48-
templateNames = append(templateNames, template.Name)
49-
templateByName[template.Name] = template
50+
templateName := template.Name
51+
if template.WorkspaceOwnerCount > 0 {
52+
developerText := "developer"
53+
if template.WorkspaceOwnerCount != 1 {
54+
developerText = "developers"
55+
}
56+
templateName += cliui.Styles.Placeholder.Render(fmt.Sprintf(" (used by %d %s)", template.WorkspaceOwnerCount, developerText))
57+
}
58+
templateNames = append(templateNames, templateName)
59+
templateByName[templateName] = template
5060
}
5161
sort.Slice(templateNames, func(i, j int) bool {
5262
return templateByName[templateNames[i]].WorkspaceOwnerCount > templateByName[templateNames[j]].WorkspaceOwnerCount
@@ -70,10 +80,22 @@ func workspaceCreate() *cobra.Command {
7080
}
7181
}
7282

73-
_, _ = fmt.Fprintln(cmd.OutOrStdout())
74-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Prompt.String()+"Creating with the "+cliui.Styles.Field.Render(template.Name)+" template...")
83+
if workspaceName == "" {
84+
workspaceName, err = cliui.Prompt(cmd, cliui.PromptOptions{
85+
Text: "Specify a name for your workspace:",
86+
Validate: func(workspaceName string) error {
87+
_, err = client.WorkspaceByName(cmd.Context(), codersdk.Me, workspaceName)
88+
if err == nil {
89+
return xerrors.Errorf("A workspace already exists named %q!", workspaceName)
90+
}
91+
return nil
92+
},
93+
})
94+
if err != nil {
95+
return err
96+
}
97+
}
7598

76-
workspaceName := args[0]
7799
_, err = client.WorkspaceByName(cmd.Context(), codersdk.Me, workspaceName)
78100
if err == nil {
79101
return xerrors.Errorf("A workspace already exists named %q!", workspaceName)
@@ -95,10 +117,9 @@ func workspaceCreate() *cobra.Command {
95117
continue
96118
}
97119
if !printed {
98-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("This template has customizable parameters! These can be changed after create, but may have unintended side effects (like data loss).")+"\r\n")
120+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("This template has customizable parameters. Values can be changed after create, but may have unintended side effects (like data loss).")+"\r\n")
99121
printed = true
100122
}
101-
102123
value, err := cliui.ParameterSchema(cmd, parameterSchema)
103124
if err != nil {
104125
return err
@@ -110,11 +131,8 @@ func workspaceCreate() *cobra.Command {
110131
DestinationScheme: parameterSchema.DefaultDestinationScheme,
111132
})
112133
}
113-
if printed {
114-
_, _ = fmt.Fprintln(cmd.OutOrStdout())
115-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.FocusedPrompt.String()+"Previewing resources...")
116-
_, _ = fmt.Fprintln(cmd.OutOrStdout())
117-
}
134+
_, _ = fmt.Fprintln(cmd.OutOrStdout())
135+
118136
resources, err := client.TemplateVersionResources(cmd.Context(), templateVersion.ID)
119137
if err != nil {
120138
return err
@@ -123,20 +141,17 @@ func workspaceCreate() *cobra.Command {
123141
WorkspaceName: workspaceName,
124142
// Since agent's haven't connected yet, hiding this makes more sense.
125143
HideAgentState: true,
144+
Title: "Workspace Preview",
126145
})
127146
if err != nil {
128147
return err
129148
}
130149

131150
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
132-
Text: fmt.Sprintf("Create workspace %s?", color.HiCyanString(workspaceName)),
133-
Default: "yes",
151+
Text: "Confirm create?",
134152
IsConfirm: true,
135153
})
136154
if err != nil {
137-
if errors.Is(err, promptui.ErrAbort) {
138-
return nil
139-
}
140155
return err
141156
}
142157

@@ -149,29 +164,26 @@ func workspaceCreate() *cobra.Command {
149164
if err != nil {
150165
return err
151166
}
152-
err = cliui.ProvisionerJob(cmd.Context(), cmd.OutOrStdout(), cliui.ProvisionerJobOptions{
153-
Fetch: func() (codersdk.ProvisionerJob, error) {
154-
build, err := client.WorkspaceBuild(cmd.Context(), workspace.LatestBuild.ID)
155-
return build.Job, err
156-
},
157-
Cancel: func() error {
158-
return client.CancelWorkspaceBuild(cmd.Context(), workspace.LatestBuild.ID)
159-
},
160-
Logs: func() (<-chan codersdk.ProvisionerJobLog, error) {
161-
return client.WorkspaceBuildLogsAfter(cmd.Context(), workspace.LatestBuild.ID, before)
162-
},
163-
})
167+
err = cliui.WorkspaceBuild(cmd.Context(), cmd.OutOrStdout(), client, workspace.LatestBuild.ID, before)
168+
if err != nil {
169+
return err
170+
}
171+
resources, err = client.WorkspaceResourcesByBuild(cmd.Context(), workspace.LatestBuild.ID)
164172
if err != nil {
165173
return err
166174
}
167-
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\nThe %s workspace has been created!\n\n", cliui.Styles.Keyword.Render(workspace.Name))
168-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+cliui.Styles.Code.Render("coder ssh "+workspace.Name))
169-
_, _ = fmt.Fprintln(cmd.OutOrStdout())
170175

171-
return err
176+
err = cliui.WorkspaceResources(cmd.OutOrStdout(), resources, cliui.WorkspaceResourcesOptions{
177+
WorkspaceName: workspaceName,
178+
})
179+
if err != nil {
180+
return err
181+
}
182+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "The %s workspace has been created!\n", cliui.Styles.Keyword.Render(workspace.Name))
183+
return nil
172184
},
173185
}
174-
cmd.Flags().StringVarP(&templateName, "template", "p", "", "Specify a template name.")
186+
cliflag.StringVarP(cmd.Flags(), &workspaceName, "name", "n", "CODER_WORKSPACE_NAME", "", "Specify a workspace name.")
175187

176188
return cmd
177189
}

cli/workspacecreate_test.go

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77

88
"github.com/coder/coder/cli/clitest"
99
"github.com/coder/coder/coderd/coderdtest"
10+
"github.com/coder/coder/provisioner/echo"
11+
"github.com/coder/coder/provisionersdk/proto"
1012
"github.com/coder/coder/pty/ptytest"
1113
)
1214

@@ -20,7 +22,7 @@ func TestWorkspaceCreate(t *testing.T) {
2022
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
2123
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
2224
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
23-
cmd, root := clitest.New(t, "workspaces", "create", "my-workspace", "--template", template.Name)
25+
cmd, root := clitest.New(t, "workspaces", "create", template.Name, "--name", "my-workspace")
2426
clitest.SetupConfig(t, client, root)
2527
doneChan := make(chan struct{})
2628
pty := ptytest.New(t)
@@ -32,7 +34,121 @@ func TestWorkspaceCreate(t *testing.T) {
3234
require.NoError(t, err)
3335
}()
3436
matches := []string{
35-
"Create workspace", "yes",
37+
"Confirm create", "yes",
38+
}
39+
for i := 0; i < len(matches); i += 2 {
40+
match := matches[i]
41+
value := matches[i+1]
42+
pty.ExpectMatch(match)
43+
pty.WriteLine(value)
44+
}
45+
<-doneChan
46+
})
47+
t.Run("CreateFromList", func(t *testing.T) {
48+
t.Parallel()
49+
client := coderdtest.New(t, nil)
50+
user := coderdtest.CreateFirstUser(t, client)
51+
coderdtest.NewProvisionerDaemon(t, client)
52+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
53+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
54+
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
55+
cmd, root := clitest.New(t, "workspaces", "create", "--name", "my-workspace")
56+
clitest.SetupConfig(t, client, root)
57+
doneChan := make(chan struct{})
58+
pty := ptytest.New(t)
59+
cmd.SetIn(pty.Input())
60+
cmd.SetOut(pty.Output())
61+
go func() {
62+
defer close(doneChan)
63+
err := cmd.Execute()
64+
require.NoError(t, err)
65+
}()
66+
matches := []string{
67+
"Confirm create", "yes",
68+
}
69+
for i := 0; i < len(matches); i += 2 {
70+
match := matches[i]
71+
value := matches[i+1]
72+
pty.ExpectMatch(match)
73+
pty.WriteLine(value)
74+
}
75+
<-doneChan
76+
})
77+
t.Run("FromNothing", func(t *testing.T) {
78+
t.Parallel()
79+
client := coderdtest.New(t, nil)
80+
user := coderdtest.CreateFirstUser(t, client)
81+
coderdtest.NewProvisionerDaemon(t, client)
82+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
83+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
84+
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
85+
cmd, root := clitest.New(t, "workspaces", "create", "--name", "")
86+
clitest.SetupConfig(t, client, root)
87+
doneChan := make(chan struct{})
88+
pty := ptytest.New(t)
89+
cmd.SetIn(pty.Input())
90+
cmd.SetOut(pty.Output())
91+
go func() {
92+
defer close(doneChan)
93+
err := cmd.Execute()
94+
require.NoError(t, err)
95+
}()
96+
matches := []string{
97+
"Specify a name", "my-workspace",
98+
"Confirm create?", "yes",
99+
}
100+
for i := 0; i < len(matches); i += 2 {
101+
match := matches[i]
102+
value := matches[i+1]
103+
pty.ExpectMatch(match)
104+
pty.WriteLine(value)
105+
}
106+
<-doneChan
107+
})
108+
t.Run("WithParameter", func(t *testing.T) {
109+
t.Parallel()
110+
client := coderdtest.New(t, nil)
111+
user := coderdtest.CreateFirstUser(t, client)
112+
coderdtest.NewProvisionerDaemon(t, client)
113+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
114+
Parse: []*proto.Parse_Response{{
115+
Type: &proto.Parse_Response_Complete{
116+
Complete: &proto.Parse_Complete{
117+
ParameterSchemas: []*proto.ParameterSchema{{
118+
AllowOverrideSource: true,
119+
Name: "region",
120+
Description: "description",
121+
DefaultSource: &proto.ParameterSource{
122+
Scheme: proto.ParameterSource_DATA,
123+
Value: "something",
124+
},
125+
DefaultDestination: &proto.ParameterDestination{
126+
Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE,
127+
},
128+
}},
129+
},
130+
},
131+
}},
132+
Provision: echo.ProvisionComplete,
133+
ProvisionDryRun: echo.ProvisionComplete,
134+
})
135+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
136+
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
137+
cmd, root := clitest.New(t, "workspaces", "create", "--name", "")
138+
clitest.SetupConfig(t, client, root)
139+
doneChan := make(chan struct{})
140+
pty := ptytest.New(t)
141+
cmd.SetIn(pty.Input())
142+
cmd.SetOut(pty.Output())
143+
go func() {
144+
defer close(doneChan)
145+
err := cmd.Execute()
146+
require.NoError(t, err)
147+
}()
148+
matches := []string{
149+
"Specify a name", "my-workspace",
150+
"Enter a value", "bananas",
151+
"Confirm create?", "yes",
36152
}
37153
for i := 0; i < len(matches); i += 2 {
38154
match := matches[i]

0 commit comments

Comments
 (0)