From 97f3ceaee09a8da37178a91d8d2c708084158b5c Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Mon, 27 Jul 2020 13:00:53 -0500 Subject: [PATCH] Build for windows --- .github/workflows/ci.yml | 2 +- client_test.go | 4 ++ dev/client/main.go | 7 ++- localexec.go | 87 ----------------------------------- localexec_test.go | 5 ++- localexec_unix.go | 97 ++++++++++++++++++++++++++++++++++++++++ localexec_windows.go | 30 +++++++++++++ tty_test.go | 6 ++- 8 files changed, 145 insertions(+), 93 deletions(-) create mode 100644 localexec_unix.go create mode 100644 localexec_windows.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15ae538..51811b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,4 +43,4 @@ jobs: - name: test uses: ./ci/image with: - args: go test ./... + args: go test ./... \ No newline at end of file diff --git a/client_test.go b/client_test.go index 66c0a51..4d03316 100644 --- a/client_test.go +++ b/client_test.go @@ -19,6 +19,7 @@ import ( ) func TestRemoteStdin(t *testing.T) { + t.Parallel() inputs := []string{ "pwd", "echo 123\n456", @@ -72,6 +73,7 @@ func mockConn(ctx context.Context, t *testing.T) (*websocket.Conn, *httptest.Ser } func TestRemoteExec(t *testing.T) { + t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() @@ -83,6 +85,7 @@ func TestRemoteExec(t *testing.T) { } func TestRemoteExecFail(t *testing.T) { + t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() @@ -111,6 +114,7 @@ func testExecerFail(ctx context.Context, t *testing.T, execer Execer) { } func TestStderrVsStdout(t *testing.T) { + t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() diff --git a/dev/client/main.go b/dev/client/main.go index 56898e2..e361e02 100644 --- a/dev/client/main.go +++ b/dev/client/main.go @@ -1,3 +1,5 @@ +// +build !windows + package main import ( @@ -9,10 +11,11 @@ import ( "cdr.dev/wsep" "github.com/spf13/pflag" - "go.coder.com/cli" - "go.coder.com/flog" "golang.org/x/crypto/ssh/terminal" "nhooyr.io/websocket" + + "go.coder.com/cli" + "go.coder.com/flog" ) type notty struct { diff --git a/localexec.go b/localexec.go index 085f24c..8f1ed86 100644 --- a/localexec.go +++ b/localexec.go @@ -1,15 +1,9 @@ package wsep import ( - "bytes" - "context" "io" - "io/ioutil" - "os" "os/exec" - "syscall" - "github.com/creack/pty" "golang.org/x/xerrors" ) @@ -17,16 +11,6 @@ import ( type LocalExecer struct { } -type localProcess struct { - // tty may be nil - tty *os.File - cmd *exec.Cmd - - stdin io.WriteCloser - stdout io.Reader - stderr io.Reader -} - func (l *localProcess) Stdin() io.WriteCloser { return l.stdin } @@ -53,81 +37,10 @@ func (l *localProcess) Close() error { return l.cmd.Process.Kill() } -func (l *localProcess) Resize(ctx context.Context, rows, cols uint16) error { - if l.tty == nil { - return nil - } - return pty.Setsize(l.tty, &pty.Winsize{ - Rows: rows, - Cols: cols, - }) -} - func (l *localProcess) Pid() int { return l.cmd.Process.Pid } -// Start executes the given command locally -func (l LocalExecer) Start(ctx context.Context, c Command) (Process, error) { - var ( - process localProcess - err error - ) - process.cmd = exec.CommandContext(ctx, c.Command, c.Args...) - process.cmd.Env = append(os.Environ(), c.Env...) - process.cmd.Dir = c.WorkingDir - - if c.GID != 0 || c.UID != 0 { - process.cmd.SysProcAttr = &syscall.SysProcAttr{ - Credential: &syscall.Credential{}, - } - } - if c.GID != 0 { - process.cmd.SysProcAttr.Credential.Gid = c.GID - } - if c.UID != 0 { - process.cmd.SysProcAttr.Credential.Uid = c.UID - } - - if c.TTY { - // This special WSEP_TTY variable helps debug unexpected TTYs. - process.cmd.Env = append(process.cmd.Env, "WSEP_TTY=true") - process.tty, err = pty.Start(process.cmd) - if err != nil { - return nil, xerrors.Errorf("start command with pty: %w", err) - } - process.stdout = process.tty - process.stderr = ioutil.NopCloser(bytes.NewReader(nil)) - process.stdin = process.tty - } else { - if c.Stdin { - process.stdin, err = process.cmd.StdinPipe() - if err != nil { - return nil, xerrors.Errorf("create pipe: %w", err) - } - } else { - process.stdin = disabledStdinWriter{} - } - - process.stdout, err = process.cmd.StdoutPipe() - if err != nil { - return nil, xerrors.Errorf("create pipe: %w", err) - } - - process.stderr, err = process.cmd.StderrPipe() - if err != nil { - return nil, xerrors.Errorf("create pipe: %w", err) - } - - err = process.cmd.Start() - if err != nil { - return nil, xerrors.Errorf("start command: %w", err) - } - } - - return &process, nil -} - type disabledStdinWriter struct{} func (w disabledStdinWriter) Close() error { diff --git a/localexec_test.go b/localexec_test.go index b33cb27..8a454c4 100644 --- a/localexec_test.go +++ b/localexec_test.go @@ -15,6 +15,7 @@ import ( ) func TestLocalExec(t *testing.T) { + t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() @@ -58,11 +59,12 @@ func testExecer(ctx context.Context, t *testing.T, execer Execer) { } func TestExitCode(t *testing.T) { + t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() process, err := LocalExecer{}.Start(ctx, Command{ - Command: "/bin/sh", + Command: "sh", Args: []string{"-c", `"fakecommand"`}, }) assert.Success(t, "start local cmd", err) @@ -97,6 +99,7 @@ func TestStdin(t *testing.T) { } func TestStdinFail(t *testing.T) { + t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/localexec_unix.go b/localexec_unix.go new file mode 100644 index 0000000..a40b127 --- /dev/null +++ b/localexec_unix.go @@ -0,0 +1,97 @@ +// +build !windows + +package wsep + +import ( + "bytes" + "context" + "io" + "io/ioutil" + "os" + "os/exec" + "syscall" + + "github.com/creack/pty" + "golang.org/x/xerrors" +) + +type localProcess struct { + // tty may be nil + tty *os.File + cmd *exec.Cmd + + stdin io.WriteCloser + stdout io.Reader + stderr io.Reader +} + +func (l *localProcess) Resize(_ context.Context, rows, cols uint16) error { + if l.tty == nil { + return nil + } + return pty.Setsize(l.tty, &pty.Winsize{ + Rows: rows, + Cols: cols, + }) +} + +// Start executes the given command locally +func (l LocalExecer) Start(ctx context.Context, c Command) (Process, error) { + var ( + process localProcess + err error + ) + process.cmd = exec.CommandContext(ctx, c.Command, c.Args...) + process.cmd.Env = append(os.Environ(), c.Env...) + process.cmd.Dir = c.WorkingDir + + if c.GID != 0 || c.UID != 0 { + process.cmd.SysProcAttr = &syscall.SysProcAttr{ + Credential: &syscall.Credential{}, + } + } + if c.GID != 0 { + process.cmd.SysProcAttr.Credential.Gid = c.GID + } + if c.UID != 0 { + process.cmd.SysProcAttr.Credential.Uid = c.UID + } + + if c.TTY { + // This special WSEP_TTY variable helps debug unexpected TTYs. + process.cmd.Env = append(process.cmd.Env, "WSEP_TTY=true") + process.tty, err = pty.Start(process.cmd) + if err != nil { + return nil, xerrors.Errorf("start command with pty: %w", err) + } + process.stdout = process.tty + process.stderr = ioutil.NopCloser(bytes.NewReader(nil)) + process.stdin = process.tty + } else { + if c.Stdin { + process.stdin, err = process.cmd.StdinPipe() + if err != nil { + return nil, xerrors.Errorf("create pipe: %w", err) + } + } else { + process.stdin = disabledStdinWriter{} + } + + process.stdout, err = process.cmd.StdoutPipe() + if err != nil { + return nil, xerrors.Errorf("create pipe: %w", err) + } + + process.stderr, err = process.cmd.StderrPipe() + if err != nil { + return nil, xerrors.Errorf("create pipe: %w", err) + } + + err = process.cmd.Start() + if err != nil { + return nil, xerrors.Errorf("start command: %w", err) + } + } + + return &process, nil +} diff --git a/localexec_windows.go b/localexec_windows.go new file mode 100644 index 0000000..302ab9c --- /dev/null +++ b/localexec_windows.go @@ -0,0 +1,30 @@ +// +build windows + +package wsep + +import ( + "context" + "io" + "os/exec" + + "golang.org/x/xerrors" +) + +type localProcess struct { + // tty may be nil + tty uintptr + cmd *exec.Cmd + + stdin io.WriteCloser + stdout io.Reader + stderr io.Reader +} + +func (l *localProcess) Resize(_ context.Context, rows, cols uint16) error { + return xerrors.Errorf("Windows local execution is not supported") +} + +// Start executes the given command locally +func (l LocalExecer) Start(ctx context.Context, c Command) (Process, error) { + return nil, xerrors.Errorf("Windows local execution is not supported") +} diff --git a/tty_test.go b/tty_test.go index 5895c45..913fb62 100644 --- a/tty_test.go +++ b/tty_test.go @@ -13,6 +13,8 @@ import ( ) func TestTTY(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() @@ -26,11 +28,11 @@ func TestTTY(t *testing.T) { func testTTY(ctx context.Context, t *testing.T, e Execer) { process, err := e.Start(ctx, Command{ - Command: "bash", + Command: "sh", TTY: true, Stdin: true, }) - assert.Success(t, "start bash", err) + assert.Success(t, "start sh", err) var wg sync.WaitGroup wg.Add(1) go func() {