Skip to content

Commit 154b9bc

Browse files
authored
feat: Add "coder projects create" command (#246)
* Refactor parameter parsing to return nil values if none computed * Refactor parameter to allow for hiding redisplay * Refactor parameters to enable schema matching * Refactor provisionerd to dynamically update parameter schemas * Refactor job update for provisionerd * Handle multiple states correctly when provisioning a project * Add project import job resource table * Basic creation flow works! * Create project fully works!!! * Only show job status if completed * Add create workspace support * Replace Netflix/go-expect with ActiveState * Fix linting errors * Use forked chzyer/readline * Add create workspace CLI * Add CLI test * Move jobs to their own APIs * Remove go-expect * Fix requested changes * Skip workspacecreate test on windows
1 parent df13fef commit 154b9bc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+3192
-2218
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: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,34 @@
11
package clitest
22

33
import (
4+
"archive/tar"
45
"bufio"
6+
"bytes"
7+
"errors"
58
"io"
9+
"os"
10+
"path/filepath"
11+
"regexp"
612
"testing"
713

14+
"github.com/Netflix/go-expect"
815
"github.com/spf13/cobra"
16+
"github.com/stretchr/testify/require"
917

1018
"github.com/coder/coder/cli"
1119
"github.com/coder/coder/cli/config"
20+
"github.com/coder/coder/codersdk"
21+
"github.com/coder/coder/provisioner/echo"
1222
)
1323

24+
var (
25+
// Used to ensure terminal output doesn't have anything crazy!
26+
// See: https://stackoverflow.com/a/29497680
27+
stripAnsi = regexp.MustCompile("[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))")
28+
)
29+
30+
// New creates a CLI instance with a configuration pointed to a
31+
// temporary testing directory.
1432
func New(t *testing.T, args ...string) (*cobra.Command, config.Root) {
1533
cmd := cli.Root()
1634
dir := t.TempDir()
@@ -19,7 +37,27 @@ func New(t *testing.T, args ...string) (*cobra.Command, config.Root) {
1937
return cmd, root
2038
}
2139

22-
func StdoutLogs(t *testing.T) io.Writer {
40+
// SetupConfig applies the URL and SessionToken of the client to the config.
41+
func SetupConfig(t *testing.T, client *codersdk.Client, root config.Root) {
42+
err := root.Session().Write(client.SessionToken)
43+
require.NoError(t, err)
44+
err = root.URL().Write(client.URL.String())
45+
require.NoError(t, err)
46+
}
47+
48+
// CreateProjectVersionSource writes the echo provisioner responses into a
49+
// new temporary testing directory.
50+
func CreateProjectVersionSource(t *testing.T, responses *echo.Responses) string {
51+
directory := t.TempDir()
52+
data, err := echo.Tar(responses)
53+
require.NoError(t, err)
54+
extractTar(t, data, directory)
55+
return directory
56+
}
57+
58+
// NewConsole creates a new TTY bound to the command provided.
59+
// All ANSI escape codes are stripped to provide clean output.
60+
func NewConsole(t *testing.T, cmd *cobra.Command) *expect.Console {
2361
reader, writer := io.Pipe()
2462
scanner := bufio.NewScanner(reader)
2563
t.Cleanup(func() {
@@ -31,8 +69,46 @@ func StdoutLogs(t *testing.T) io.Writer {
3169
if scanner.Err() != nil {
3270
return
3371
}
34-
t.Log(scanner.Text())
72+
t.Log(stripAnsi.ReplaceAllString(scanner.Text(), ""))
3573
}
3674
}()
37-
return writer
75+
76+
console, err := expect.NewConsole(expect.WithStdout(writer))
77+
require.NoError(t, err)
78+
cmd.SetIn(console.Tty())
79+
cmd.SetOut(console.Tty())
80+
return console
81+
}
82+
83+
func extractTar(t *testing.T, data []byte, directory string) {
84+
reader := tar.NewReader(bytes.NewBuffer(data))
85+
for {
86+
header, err := reader.Next()
87+
if errors.Is(err, io.EOF) {
88+
break
89+
}
90+
require.NoError(t, err)
91+
// #nosec
92+
path := filepath.Join(directory, header.Name)
93+
mode := header.FileInfo().Mode()
94+
if mode == 0 {
95+
mode = 0600
96+
}
97+
switch header.Typeflag {
98+
case tar.TypeDir:
99+
err = os.MkdirAll(path, mode)
100+
require.NoError(t, err)
101+
case tar.TypeReg:
102+
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, mode)
103+
require.NoError(t, err)
104+
// Max file size of 10MB.
105+
_, err = io.CopyN(file, reader, (1<<20)*10)
106+
if errors.Is(err, io.EOF) {
107+
err = nil
108+
}
109+
require.NoError(t, err)
110+
err = file.Close()
111+
require.NoError(t, err)
112+
}
113+
}
38114
}

cli/clitest/clitest_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//go:build !windows
2+
3+
package clitest_test
4+
5+
import (
6+
"testing"
7+
8+
"github.com/coder/coder/cli/clitest"
9+
"github.com/coder/coder/coderd/coderdtest"
10+
"github.com/stretchr/testify/require"
11+
"go.uber.org/goleak"
12+
)
13+
14+
func TestMain(m *testing.M) {
15+
goleak.VerifyTestMain(m)
16+
}
17+
18+
func TestCli(t *testing.T) {
19+
t.Parallel()
20+
clitest.CreateProjectVersionSource(t, nil)
21+
client := coderdtest.New(t)
22+
cmd, config := clitest.New(t)
23+
clitest.SetupConfig(t, client, config)
24+
console := clitest.NewConsole(t, cmd)
25+
go func() {
26+
err := cmd.Execute()
27+
require.NoError(t, err)
28+
}()
29+
_, err := console.ExpectString("coder")
30+
require.NoError(t, err)
31+
}

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/login_test.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import (
88
"github.com/coder/coder/cli/clitest"
99
"github.com/coder/coder/coderd/coderdtest"
1010
"github.com/stretchr/testify/require"
11-
12-
"github.com/Netflix/go-expect"
1311
)
1412

1513
func TestLogin(t *testing.T) {
@@ -24,12 +22,9 @@ func TestLogin(t *testing.T) {
2422

2523
t.Run("InitialUserTTY", func(t *testing.T) {
2624
t.Parallel()
27-
console, err := expect.NewConsole(expect.WithStdout(clitest.StdoutLogs(t)))
28-
require.NoError(t, err)
2925
client := coderdtest.New(t)
3026
root, _ := clitest.New(t, "login", client.URL.String())
31-
root.SetIn(console.Tty())
32-
root.SetOut(console.Tty())
27+
console := clitest.NewConsole(t, root)
3328
go func() {
3429
err := root.Execute()
3530
require.NoError(t, err)
@@ -45,12 +40,12 @@ func TestLogin(t *testing.T) {
4540
for i := 0; i < len(matches); i += 2 {
4641
match := matches[i]
4742
value := matches[i+1]
48-
_, err = console.ExpectString(match)
43+
_, err := console.ExpectString(match)
4944
require.NoError(t, err)
5045
_, err = console.SendLine(value)
5146
require.NoError(t, err)
5247
}
53-
_, err = console.ExpectString("Welcome to Coder")
48+
_, err := console.ExpectString("Welcome to Coder")
5449
require.NoError(t, err)
5550
})
5651
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy