mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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
This commit is contained in:
+79
-3
@@ -1,16 +1,34 @@
|
||||
package clitest
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/Netflix/go-expect"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/cli"
|
||||
"github.com/coder/coder/cli/config"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/provisioner/echo"
|
||||
)
|
||||
|
||||
var (
|
||||
// Used to ensure terminal output doesn't have anything crazy!
|
||||
// See: https://stackoverflow.com/a/29497680
|
||||
stripAnsi = regexp.MustCompile("[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))")
|
||||
)
|
||||
|
||||
// New creates a CLI instance with a configuration pointed to a
|
||||
// temporary testing directory.
|
||||
func New(t *testing.T, args ...string) (*cobra.Command, config.Root) {
|
||||
cmd := cli.Root()
|
||||
dir := t.TempDir()
|
||||
@@ -19,7 +37,27 @@ func New(t *testing.T, args ...string) (*cobra.Command, config.Root) {
|
||||
return cmd, root
|
||||
}
|
||||
|
||||
func StdoutLogs(t *testing.T) io.Writer {
|
||||
// SetupConfig applies the URL and SessionToken of the client to the config.
|
||||
func SetupConfig(t *testing.T, client *codersdk.Client, root config.Root) {
|
||||
err := root.Session().Write(client.SessionToken)
|
||||
require.NoError(t, err)
|
||||
err = root.URL().Write(client.URL.String())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// CreateProjectVersionSource writes the echo provisioner responses into a
|
||||
// new temporary testing directory.
|
||||
func CreateProjectVersionSource(t *testing.T, responses *echo.Responses) string {
|
||||
directory := t.TempDir()
|
||||
data, err := echo.Tar(responses)
|
||||
require.NoError(t, err)
|
||||
extractTar(t, data, directory)
|
||||
return directory
|
||||
}
|
||||
|
||||
// NewConsole creates a new TTY bound to the command provided.
|
||||
// All ANSI escape codes are stripped to provide clean output.
|
||||
func NewConsole(t *testing.T, cmd *cobra.Command) *expect.Console {
|
||||
reader, writer := io.Pipe()
|
||||
scanner := bufio.NewScanner(reader)
|
||||
t.Cleanup(func() {
|
||||
@@ -31,8 +69,46 @@ func StdoutLogs(t *testing.T) io.Writer {
|
||||
if scanner.Err() != nil {
|
||||
return
|
||||
}
|
||||
t.Log(scanner.Text())
|
||||
t.Log(stripAnsi.ReplaceAllString(scanner.Text(), ""))
|
||||
}
|
||||
}()
|
||||
return writer
|
||||
|
||||
console, err := expect.NewConsole(expect.WithStdout(writer))
|
||||
require.NoError(t, err)
|
||||
cmd.SetIn(console.Tty())
|
||||
cmd.SetOut(console.Tty())
|
||||
return console
|
||||
}
|
||||
|
||||
func extractTar(t *testing.T, data []byte, directory string) {
|
||||
reader := tar.NewReader(bytes.NewBuffer(data))
|
||||
for {
|
||||
header, err := reader.Next()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
require.NoError(t, err)
|
||||
// #nosec
|
||||
path := filepath.Join(directory, header.Name)
|
||||
mode := header.FileInfo().Mode()
|
||||
if mode == 0 {
|
||||
mode = 0600
|
||||
}
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
err = os.MkdirAll(path, mode)
|
||||
require.NoError(t, err)
|
||||
case tar.TypeReg:
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, mode)
|
||||
require.NoError(t, err)
|
||||
// Max file size of 10MB.
|
||||
_, err = io.CopyN(file, reader, (1<<20)*10)
|
||||
if errors.Is(err, io.EOF) {
|
||||
err = nil
|
||||
}
|
||||
require.NoError(t, err)
|
||||
err = file.Close()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
//go:build !windows
|
||||
|
||||
package clitest_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/coder/coder/cli/clitest"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m)
|
||||
}
|
||||
|
||||
func TestCli(t *testing.T) {
|
||||
t.Parallel()
|
||||
clitest.CreateProjectVersionSource(t, nil)
|
||||
client := coderdtest.New(t)
|
||||
cmd, config := clitest.New(t)
|
||||
clitest.SetupConfig(t, client, config)
|
||||
console := clitest.NewConsole(t, cmd)
|
||||
go func() {
|
||||
err := cmd.Execute()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
_, err := console.ExpectString("coder")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
Reference in New Issue
Block a user