Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 64491e0

Browse files
committed
Implement tar directory transfer
1 parent 115d35b commit 64491e0

File tree

9 files changed

+136
-38
lines changed

9 files changed

+136
-38
lines changed

Diff for: cmd/coder/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func (r *rootCmd) Subcommands() []cli.Command {
2828
return []cli.Command{
2929
loginCmd{},
3030
logoutCmd{},
31+
&shellCmd{},
3132
&syncCmd{},
3233
}
3334
}

Diff for: cmd/coder/shell.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"io"
6+
"os"
7+
8+
"github.com/spf13/pflag"
9+
"go.coder.com/cli"
10+
"go.coder.com/flog"
11+
12+
client "cdr.dev/coder-cli/internal/client"
13+
"cdr.dev/coder-cli/wush"
14+
)
15+
16+
type shellCmd struct {
17+
}
18+
19+
func (cmd *shellCmd) Spec() cli.CommandSpec {
20+
return cli.CommandSpec{
21+
Name: "sh",
22+
Usage: "<env name> -- <command [command args...]>",
23+
Desc: "executes a remote command on the environment",
24+
RawArgs: true,
25+
}
26+
}
27+
28+
func (cmd *shellCmd) Run(fl *pflag.FlagSet) {
29+
if len(fl.Args()) < 3 {
30+
exitUsage(fl)
31+
}
32+
var (
33+
envName = fl.Arg(0)
34+
_ = fl.Arg(1)
35+
command = fl.Arg(2)
36+
args = fl.Args()[3:]
37+
)
38+
39+
entClient := requireAuth()
40+
env := findEnv(entClient, envName)
41+
42+
conn, err := entClient.DialWush(
43+
env,
44+
&client.WushOptions{
45+
TTY: false,
46+
Stdin: true,
47+
}, command, args...)
48+
if err != nil {
49+
flog.Fatal("dial wush: %v", err)
50+
}
51+
ctx := context.Background()
52+
53+
wc := wush.NewClient(ctx, conn)
54+
go io.Copy(wc.Stdin, os.Stdin)
55+
go io.Copy(os.Stdout, wc.Stdout)
56+
go io.Copy(os.Stderr, wc.Stderr)
57+
58+
exitCode, err := wc.Wait()
59+
if err != nil {
60+
flog.Fatal("wush error: %v", err)
61+
}
62+
os.Exit(int(exitCode))
63+
}

Diff for: cmd/coder/sync.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ outer:
5252
return uo
5353
}
5454

55-
func (cmd *syncCmd) findEnv(client *client.Client, name string) client.Environment {
55+
func findEnv(client *client.Client, name string) client.Environment {
5656
me, err := client.Me()
5757
if err != nil {
5858
flog.Fatal("get self: %+v", err)
@@ -86,11 +86,11 @@ func (cmd *syncCmd) findEnv(client *client.Client, name string) client.Environme
8686
}
8787

8888
func (cmd *syncCmd) bench(client *client.Client, env client.Environment) {
89-
conn, err := client.Wush(env, "cat")
89+
conn, err := client.DialWush(env, nil, "cat")
9090
if err != nil {
9191
flog.Fatal("wush failed: %v", err)
9292
}
93-
wc := wush.Dial(context.Background(), conn)
93+
wc := wush.NewClient(context.Background(), conn)
9494
bar := pb.New64(cmd.benchSize)
9595
bar.Start()
9696
go io.Copy(ioutil.Discard, wc.Stdout)
@@ -133,7 +133,7 @@ func (cmd *syncCmd) Run(fl *pflag.FlagSet) {
133133
remoteDir = remoteTokens[1]
134134
)
135135

136-
env := cmd.findEnv(client, envName)
136+
env := findEnv(client, envName)
137137

138138
if cmd.benchSize > 0 {
139139
cmd.bench(client, env)

Diff for: go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/cheggaaa/pb/v3 v3.0.4
77
github.com/dustin/go-humanize v1.0.0
88
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
9+
github.com/mattn/go-isatty v0.0.12
910
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
1011
github.com/rjeczalik/notify v0.9.2
1112
github.com/spf13/pflag v1.0.5

Diff for: go.sum

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
2727
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
2828
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
2929
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
30+
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
31+
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
3032
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
3133
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
3234
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
@@ -46,6 +48,7 @@ golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5h
4648
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
4749
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
4850
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
51+
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
4952
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
5053
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5154
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

Diff for: internal/client/env.go

+17-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package client
33
import (
44
"context"
55
"net/url"
6+
"strconv"
67
"time"
78

89
"nhooyr.io/websocket"
@@ -23,7 +24,17 @@ func (c Client) Envs(user *User, org Org) ([]Environment, error) {
2324
return envs, err
2425
}
2526

26-
func (c Client) Wush(env Environment, cmd string, args ...string) (*websocket.Conn, error) {
27+
type WushOptions struct {
28+
TTY bool
29+
Stdin bool
30+
}
31+
32+
var defaultWushOptions = WushOptions{
33+
TTY: false,
34+
Stdin: true,
35+
}
36+
37+
func (c Client) DialWush(env Environment, opts *WushOptions, cmd string, args ...string) (*websocket.Conn, error) {
2738
u := c.copyURL()
2839
if c.BaseURL.Scheme == "https" {
2940
u.Scheme = "wss"
@@ -34,8 +45,11 @@ func (c Client) Wush(env Environment, cmd string, args ...string) (*websocket.Co
3445
query := make(url.Values)
3546
query.Set("command", cmd)
3647
query["args[]"] = args
37-
query.Set("tty", "false")
38-
query.Set("stdin", "true")
48+
if opts == nil {
49+
opts = &defaultWushOptions
50+
}
51+
query.Set("tty", strconv.FormatBool(opts.TTY))
52+
query.Set("stdin", strconv.FormatBool(opts.Stdin))
3953

4054
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
4155
defer cancel()

Diff for: internal/stty/doc.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Package stty provides facilities for configuring the calling tty.
2+
package stty
3+
4+
import (
5+
"os/exec"
6+
7+
"golang.org/x/xerrors"
8+
)
9+
10+
func EnableCBreak() error {
11+
out, err := exec.Command("stty", "cbreak").CombinedOutput()
12+
if err != nil {
13+
return xerrors.Errorf("stty: %w\n%s", err, out)
14+
}
15+
return nil
16+
}
17+
18+
func DisableEcho() error {
19+
out, err := exec.Command("stty", "-echo").CombinedOutput()
20+
if err != nil {
21+
return xerrors.Errorf("stty: %w\n%s", err, out)
22+
}
23+
return nil
24+
}
25+
26+
func SubTermMode() error {
27+
err := EnableCBreak()
28+
if err != nil {
29+
return err
30+
}
31+
err = DisableEcho()
32+
if err != nil {
33+
return err
34+
}
35+
return nil
36+
}

Diff for: internal/sync/sync.go

+9-29
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"os"
99
"os/exec"
1010
"path"
11-
"path/filepath"
1211
"time"
1312

1413
"github.com/cheggaaa/pb/v3"
@@ -33,39 +32,26 @@ type Sync struct {
3332
barWriter io.Writer
3433
}
3534

36-
func (s Sync) pushFile(ctx context.Context, path string) error {
37-
conn, err := s.Wush(s.Environment, "scp", "-qprt", s.RemoteDir)
35+
func (s Sync) pushDirectory(ctx context.Context, path string) error {
36+
conn, err := s.DialWush(s.Environment, nil, "sh", "-c", "cd "+s.RemoteDir+"; tar xvzf -")
3837
if err != nil {
3938
return err
4039
}
4140
defer conn.Close(websocket.StatusNormalClosure, "")
4241

43-
wc := wush.Dial(ctx, conn)
42+
wc := wush.NewClient(ctx, conn)
4443

4544
// This starts scp in local mode
46-
cmd := exec.Command("scp", "-qprf", path)
47-
stdin, err := cmd.StdinPipe()
48-
if err != nil {
49-
return err
50-
}
51-
52-
go func() {
53-
defer stdin.Close()
45+
cmd := exec.Command("tar", "-czf", "-", ".")
46+
cmd.Dir = path
5447

55-
_, err = io.Copy(io.MultiWriter(stdin, &debugWriter{
56-
Prefix: "s->c",
57-
W: os.Stderr,
58-
}), wc.Stdout)
59-
if err != nil {
60-
flog.Error("s->c copy fail: %v", err)
61-
}
62-
}()
48+
go io.Copy(os.Stderr, wc.Stdout)
49+
go io.Copy(os.Stderr, wc.Stderr)
6350

6451
cmd.Stdout = io.MultiWriter(s.barWriter, wc.Stdin, &debugWriter{
6552
Prefix: "c->s",
6653
W: os.Stderr,
6754
})
68-
go io.Copy(os.Stderr, wc.Stderr)
6955
err = cmd.Run()
7056
if err != nil {
7157
return xerrors.Errorf("scp: %w", err)
@@ -81,7 +67,7 @@ func (s Sync) pushFileLog(ctx context.Context, path string) error {
8167

8268
start := time.Now()
8369
fmt.Printf("transferring %v...\t", info.Name())
84-
err = s.pushFile(ctx, path)
70+
err = s.pushDirectory(ctx, path)
8571
if err != nil {
8672
fmt.Printf("failed\n")
8773
return err
@@ -104,13 +90,7 @@ func (s Sync) initSync(ctx context.Context) error {
10490
s.barWriter = bar.NewProxyWriter(ioutil.Discard)
10591

10692
start := time.Now()
107-
err := filepath.Walk(s.LocalDir, func(path string, info os.FileInfo, err error) error {
108-
if path == s.LocalDir {
109-
// scp can't resolve the self directory
110-
return nil
111-
}
112-
return s.pushFile(ctx, path)
113-
})
93+
err := s.pushDirectory(ctx, s.LocalDir)
11494
if err == nil {
11595
bar.Finish()
11696
flog.Info("finished initial sync (%v)", time.Since(start).Truncate(time.Millisecond))

Diff for: wush/client.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ func (w *stdinWriter) Close() error {
6969
})
7070
}
7171

72-
// Dial begins multiplexing the Wush connection
72+
// NewClient begins multiplexing the Wush connection
7373
// into independent streams.
7474
// It will cancel all goroutines when the provided context cancels.
75-
func Dial(ctx context.Context, conn *websocket.Conn) *Client {
75+
func NewClient(ctx context.Context, conn *websocket.Conn) *Client {
7676
var (
7777
stdoutReader, stdoutWriter = io.Pipe()
7878
stderrReader, stderrWriter = io.Pipe()

0 commit comments

Comments
 (0)