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

Skip to content

Commit 8766a33

Browse files
committed
Add create workspace support
1 parent bff96b6 commit 8766a33

File tree

9 files changed

+366
-69
lines changed

9 files changed

+366
-69
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"nhooyr",
4545
"nolint",
4646
"nosec",
47+
"ntqry",
4748
"oneof",
4849
"parameterscopeid",
4950
"promptui",

cli/clitest/clitest.go

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,36 @@
11
package clitest
22

33
import (
4+
"archive/tar"
45
"bufio"
6+
"bytes"
57
"context"
8+
"errors"
69
"io"
10+
"os"
11+
"path/filepath"
12+
"regexp"
713
"testing"
814

915
"github.com/spf13/cobra"
1016
"github.com/stretchr/testify/require"
17+
"golang.org/x/xerrors"
1118

1219
"github.com/coder/coder/cli"
1320
"github.com/coder/coder/cli/config"
1421
"github.com/coder/coder/coderd"
1522
"github.com/coder/coder/coderd/coderdtest"
1623
"github.com/coder/coder/codersdk"
24+
"github.com/coder/coder/provisioner/echo"
1725
)
1826

27+
var (
28+
// Used to ensure terminal output doesn't have anything crazy!
29+
stripAnsi = regexp.MustCompile("[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))")
30+
)
31+
32+
// New creates a CLI instance with a configuration pointed to a
33+
// temporary testing directory.
1934
func New(t *testing.T, args ...string) (*cobra.Command, config.Root) {
2035
cmd := cli.Root()
2136
dir := t.TempDir()
@@ -24,6 +39,8 @@ func New(t *testing.T, args ...string) (*cobra.Command, config.Root) {
2439
return cmd, root
2540
}
2641

42+
// CreateInitialUser creates the initial user and write's the session
43+
// token to the config root provided.
2744
func CreateInitialUser(t *testing.T, client *codersdk.Client, root config.Root) coderd.CreateInitialUserRequest {
2845
user := coderdtest.CreateInitialUser(t, client)
2946
resp, err := client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
@@ -38,6 +55,19 @@ func CreateInitialUser(t *testing.T, client *codersdk.Client, root config.Root)
3855
return user
3956
}
4057

58+
// CreateProjectVersionSource writes the echo provisioner responses into a
59+
// new temporary testing directory.
60+
func CreateProjectVersionSource(t *testing.T, responses *echo.Responses) string {
61+
directory := t.TempDir()
62+
data, err := echo.Tar(responses)
63+
require.NoError(t, err)
64+
err = extractTar(data, directory)
65+
require.NoError(t, err)
66+
return directory
67+
}
68+
69+
// StdoutLogs provides a writer to t.Log that strips
70+
// all ANSI escape codes.
4171
func StdoutLogs(t *testing.T) io.Writer {
4272
reader, writer := io.Pipe()
4373
scanner := bufio.NewScanner(reader)
@@ -50,8 +80,52 @@ func StdoutLogs(t *testing.T) io.Writer {
5080
if scanner.Err() != nil {
5181
return
5282
}
53-
t.Log(scanner.Text())
83+
t.Log(stripAnsi.ReplaceAllString(scanner.Text(), ""))
5484
}
5585
}()
5686
return writer
5787
}
88+
89+
func extractTar(data []byte, directory string) error {
90+
reader := tar.NewReader(bytes.NewBuffer(data))
91+
for {
92+
header, err := reader.Next()
93+
if errors.Is(err, io.EOF) {
94+
break
95+
}
96+
if err != nil {
97+
return xerrors.Errorf("read project source archive: %w", err)
98+
}
99+
path := filepath.Join(directory, header.Name)
100+
mode := header.FileInfo().Mode()
101+
if mode == 0 {
102+
mode = 0600
103+
}
104+
switch header.Typeflag {
105+
case tar.TypeDir:
106+
err = os.MkdirAll(path, mode)
107+
if err != nil {
108+
return xerrors.Errorf("mkdir: %w", err)
109+
}
110+
case tar.TypeReg:
111+
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, mode)
112+
if err != nil {
113+
return xerrors.Errorf("create file %q: %w", path, err)
114+
}
115+
// Max file size of 10MB.
116+
_, err = io.CopyN(file, reader, (1<<20)*10)
117+
if errors.Is(err, io.EOF) {
118+
err = nil
119+
}
120+
if err != nil {
121+
_ = file.Close()
122+
return err
123+
}
124+
err = file.Close()
125+
if err != nil {
126+
return err
127+
}
128+
}
129+
}
130+
return nil
131+
}

cli/login.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func login() *cobra.Command {
4949
}
5050
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s Your Coder deployment hasn't been set up!\n", color.HiBlackString(">"))
5151

52-
_, err := runPrompt(cmd, &promptui.Prompt{
52+
_, err := prompt(cmd, &promptui.Prompt{
5353
Label: "Would you like to create the first user?",
5454
IsConfirm: true,
5555
Default: "y",
@@ -61,23 +61,23 @@ func login() *cobra.Command {
6161
if err != nil {
6262
return xerrors.Errorf("get current user: %w", err)
6363
}
64-
username, err := runPrompt(cmd, &promptui.Prompt{
64+
username, err := prompt(cmd, &promptui.Prompt{
6565
Label: "What username would you like?",
6666
Default: currentUser.Username,
6767
})
6868
if err != nil {
6969
return xerrors.Errorf("pick username prompt: %w", err)
7070
}
7171

72-
organization, err := runPrompt(cmd, &promptui.Prompt{
72+
organization, err := prompt(cmd, &promptui.Prompt{
7373
Label: "What is the name of your organization?",
7474
Default: "acme-corp",
7575
})
7676
if err != nil {
7777
return xerrors.Errorf("pick organization prompt: %w", err)
7878
}
7979

80-
email, err := runPrompt(cmd, &promptui.Prompt{
80+
email, err := prompt(cmd, &promptui.Prompt{
8181
Label: "What's your email?",
8282
Validate: func(s string) error {
8383
err := validator.New().Var(s, "email")
@@ -91,7 +91,7 @@ func login() *cobra.Command {
9191
return xerrors.Errorf("specify email prompt: %w", err)
9292
}
9393

94-
password, err := runPrompt(cmd, &promptui.Prompt{
94+
password, err := prompt(cmd, &promptui.Prompt{
9595
Label: "Enter a password:",
9696
Mask: '*',
9797
})

cli/projectcreate.go

Lines changed: 35 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,18 @@ package cli
33
import (
44
"archive/tar"
55
"bytes"
6+
"errors"
67
"fmt"
78
"io"
89
"os"
910
"path/filepath"
10-
"strings"
1111
"time"
1212

1313
"github.com/briandowns/spinner"
1414
"github.com/fatih/color"
1515
"github.com/google/uuid"
1616
"github.com/manifoldco/promptui"
1717
"github.com/spf13/cobra"
18-
"github.com/xlab/treeprint"
1918
"golang.org/x/xerrors"
2019

2120
"github.com/coder/coder/coderd"
@@ -27,7 +26,8 @@ import (
2726

2827
func projectCreate() *cobra.Command {
2928
var (
30-
directory string
29+
directory string
30+
provisioner string
3131
)
3232
cmd := &cobra.Command{
3333
Use: "create",
@@ -41,16 +41,19 @@ func projectCreate() *cobra.Command {
4141
if err != nil {
4242
return err
4343
}
44-
_, err = runPrompt(cmd, &promptui.Prompt{
44+
_, err = prompt(cmd, &promptui.Prompt{
4545
Default: "y",
4646
IsConfirm: true,
4747
Label: fmt.Sprintf("Set up %s in your organization?", color.New(color.FgHiCyan).Sprintf("%q", directory)),
4848
})
4949
if err != nil {
50+
if errors.Is(err, promptui.ErrAbort) {
51+
return nil
52+
}
5053
return err
5154
}
5255

53-
name, err := runPrompt(cmd, &promptui.Prompt{
56+
name, err := prompt(cmd, &promptui.Prompt{
5457
Default: filepath.Base(directory),
5558
Label: "What's your project's name?",
5659
Validate: func(s string) error {
@@ -65,7 +68,7 @@ func projectCreate() *cobra.Command {
6568
return err
6669
}
6770

68-
job, err := doProjectLoop(cmd, client, organization, directory, []coderd.CreateParameterValueRequest{})
71+
job, err := validateProjectVersionSource(cmd, client, organization, database.ProvisionerType(provisioner), directory)
6972
if err != nil {
7073
return err
7174
}
@@ -77,27 +80,44 @@ func projectCreate() *cobra.Command {
7780
return err
7881
}
7982

83+
_, err = prompt(cmd, &promptui.Prompt{
84+
Label: "Create project?",
85+
IsConfirm: true,
86+
Default: "y",
87+
})
88+
if err != nil {
89+
if errors.Is(err, promptui.ErrAbort) {
90+
return nil
91+
}
92+
return err
93+
}
94+
8095
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s The %s project has been created!\n", color.HiBlackString(">"), color.HiCyanString(project.Name))
81-
_, err = runPrompt(cmd, &promptui.Prompt{
96+
_, err = prompt(cmd, &promptui.Prompt{
8297
Label: "Create a new workspace?",
8398
IsConfirm: true,
8499
Default: "y",
85100
})
86101
if err != nil {
102+
if errors.Is(err, promptui.ErrAbort) {
103+
return nil
104+
}
87105
return err
88106
}
89107

90-
fmt.Printf("Create a new workspace now!\n")
91108
return nil
92109
},
93110
}
94111
currentDirectory, _ := os.Getwd()
95112
cmd.Flags().StringVarP(&directory, "directory", "d", currentDirectory, "Specify the directory to create from")
113+
cmd.Flags().StringVarP(&provisioner, "provisioner", "p", "terraform", "Customize the provisioner backend")
114+
// This is for testing! There's only 1 provisioner type right now.
115+
cmd.Flags().MarkHidden("provisioner")
96116

97117
return cmd
98118
}
99119

100-
func doProjectLoop(cmd *cobra.Command, client *codersdk.Client, organization coderd.Organization, directory string, params []coderd.CreateParameterValueRequest) (*coderd.ProvisionerJob, error) {
120+
func validateProjectVersionSource(cmd *cobra.Command, client *codersdk.Client, organization coderd.Organization, provisioner database.ProvisionerType, directory string, parameters ...coderd.CreateParameterValueRequest) (*coderd.ProvisionerJob, error) {
101121
spin := spinner.New(spinner.CharSets[5], 100*time.Millisecond)
102122
spin.Writer = cmd.OutOrStdout()
103123
spin.Suffix = " Uploading current directory..."
@@ -118,8 +138,8 @@ func doProjectLoop(cmd *cobra.Command, client *codersdk.Client, organization cod
118138
job, err := client.CreateProjectVersionImportProvisionerJob(cmd.Context(), organization.Name, coderd.CreateProjectImportJobRequest{
119139
StorageMethod: database.ProvisionerStorageMethodFile,
120140
StorageSource: resp.Hash,
121-
Provisioner: database.ProvisionerTypeTerraform,
122-
ParameterValues: params,
141+
Provisioner: provisioner,
142+
ParameterValues: parameters,
123143
})
124144
if err != nil {
125145
return nil, err
@@ -168,20 +188,20 @@ func doProjectLoop(cmd *cobra.Command, client *codersdk.Client, organization cod
168188
if parameterSchema.Name == parameter.CoderWorkspaceTransition {
169189
continue
170190
}
171-
value, err := runPrompt(cmd, &promptui.Prompt{
191+
value, err := prompt(cmd, &promptui.Prompt{
172192
Label: fmt.Sprintf("Enter value for %s:", color.HiCyanString(parameterSchema.Name)),
173193
})
174194
if err != nil {
175195
return nil, err
176196
}
177-
params = append(params, coderd.CreateParameterValueRequest{
197+
parameters = append(parameters, coderd.CreateParameterValueRequest{
178198
Name: parameterSchema.Name,
179199
SourceValue: value,
180200
SourceScheme: database.ParameterSourceSchemeData,
181201
DestinationScheme: parameterSchema.DefaultDestinationScheme,
182202
})
183203
}
184-
return doProjectLoop(cmd, client, organization, directory, params)
204+
return validateProjectVersionSource(cmd, client, organization, provisioner, directory, parameters...)
185205
}
186206

187207
if job.Status != coderd.ProvisionerJobStatusSucceeded {
@@ -198,50 +218,7 @@ func doProjectLoop(cmd *cobra.Command, client *codersdk.Client, organization cod
198218
if err != nil {
199219
return nil, err
200220
}
201-
return &job, outputProjectInformation(cmd, parameterSchemas, parameterValues, resources)
202-
}
203-
204-
func outputProjectInformation(cmd *cobra.Command, parameterSchemas []coderd.ParameterSchema, parameterValues []coderd.ComputedParameterValue, resources []coderd.ProjectImportJobResource) error {
205-
schemaByID := map[string]coderd.ParameterSchema{}
206-
for _, schema := range parameterSchemas {
207-
schemaByID[schema.ID.String()] = schema
208-
}
209-
210-
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\n %s\n\n", color.HiBlackString("Parameters"))
211-
for _, value := range parameterValues {
212-
schema, ok := schemaByID[value.SchemaID.String()]
213-
if !ok {
214-
return xerrors.Errorf("schema not found: %s", value.Name)
215-
}
216-
displayValue := value.SourceValue
217-
if !schema.RedisplayValue {
218-
displayValue = "<redacted>"
219-
}
220-
output := fmt.Sprintf("%s %s %s", color.HiCyanString(value.Name), color.HiBlackString("="), displayValue)
221-
if value.DefaultSourceValue {
222-
output += " (default value)"
223-
} else if value.Scope != database.ParameterScopeImportJob {
224-
output += fmt.Sprintf(" (inherited from %s)", value.Scope)
225-
}
226-
227-
root := treeprint.NewWithRoot(output)
228-
if schema.Description != "" {
229-
root.AddBranch(fmt.Sprintf("%s\n%s\n", color.HiBlackString("Description"), schema.Description))
230-
}
231-
if schema.AllowOverrideSource {
232-
root.AddBranch(fmt.Sprintf("%s Users can customize this value!", color.HiYellowString("+")))
233-
}
234-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+strings.Join(strings.Split(root.String(), "\n"), "\n "))
235-
}
236-
_, _ = fmt.Fprintf(cmd.OutOrStdout(), " %s\n\n", color.HiBlackString("Resources"))
237-
for _, resource := range resources {
238-
transition := color.HiGreenString("start")
239-
if resource.Transition == database.WorkspaceTransitionStop {
240-
transition = color.HiRedString("stop")
241-
}
242-
_, _ = fmt.Fprintf(cmd.OutOrStdout(), " %s %s on %s\n\n", color.HiCyanString(resource.Type), color.HiCyanString(resource.Name), transition)
243-
}
244-
return nil
221+
return &job, displayProjectImportInfo(cmd, parameterSchemas, parameterValues, resources)
245222
}
246223

247224
func tarDirectory(directory string) ([]byte, error) {

0 commit comments

Comments
 (0)