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

Commit e9b3021

Browse files
committed
Implement rsync
1 parent 64491e0 commit e9b3021

20 files changed

+256
-261
lines changed

README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,24 @@ Environment.
1010
$ coder login https://my-coder-enterprise.com
1111
```
1212

13-
## Setting up a Sync
13+
## Setting up a Live Sync
14+
15+
Ensure that `rsync` is installed locally and in your environment.
1416

1517
``
16-
$ coder sync ~/Projects/cdr/enterprise my-env:~/enterprise
18+
$ coder sync ~/Projects/cdr/enterprise/. my-env:~/enterprise
1719
``
1820

1921
## Caveats
2022

2123
- The `coder login` flow will not work when the CLI is ran from a different network
22-
than the browser. #1
23-
- Windows doesn't work out of the box. The `scp` utility is required in PATH.
24+
than the browser. [Issue](https://github.com/cdr/coder-cli/issues/1)
25+
26+
## Sync Architecture
27+
28+
We decided to use `rsync` because other solutions are extremely slow for the initial
29+
sync.
30+
31+
Later, we may use `mutagen` for a two-way sync alternative when
32+
it supports custom transports.
33+

cmd/coder/auth.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package main
2+
3+
import (
4+
"net/url"
5+
6+
"go.coder.com/flog"
7+
8+
"cdr.dev/coder-cli/internal/entclient"
9+
"cdr.dev/coder-cli/internal/config"
10+
)
11+
12+
func requireAuth() *entclient.Client {
13+
sessionToken, err := config.Session.Read()
14+
if err != nil {
15+
flog.Fatal("read session: %v (did you run coder login?)", err)
16+
}
17+
18+
rawURL, err := config.URL.Read()
19+
if err != nil {
20+
flog.Fatal("read url: %v (did you run coder login?)", err)
21+
}
22+
23+
u, err := url.Parse(rawURL)
24+
if err != nil {
25+
flog.Fatal("url misformatted: %v (try runing coder login)", err)
26+
}
27+
28+
return &entclient.Client{
29+
BaseURL: u,
30+
Token: sessionToken,
31+
}
32+
}

cmd/coder/ceapi.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package main
2+
3+
import (
4+
"go.coder.com/flog"
5+
6+
"cdr.dev/coder-cli/internal/entclient"
7+
)
8+
9+
// Helpers for working with the Coder Enterprise API.
10+
11+
// userOrgs gets a list of orgs the user is apart of.
12+
func userOrgs(user *entclient.User, orgs []entclient.Org) []entclient.Org {
13+
var uo []entclient.Org
14+
outer:
15+
for _, org := range orgs {
16+
for _, member := range org.Members {
17+
if member.ID != user.ID {
18+
continue
19+
}
20+
uo = append(uo, org)
21+
continue outer
22+
}
23+
}
24+
return uo
25+
}
26+
27+
func findEnv(client *entclient.Client, name string) entclient.Environment {
28+
me, err := client.Me()
29+
if err != nil {
30+
flog.Fatal("get self: %+v", err)
31+
}
32+
33+
orgs, err := client.Orgs()
34+
if err != nil {
35+
flog.Fatal("get orgs: %+v", err)
36+
}
37+
38+
orgs = userOrgs(me, orgs)
39+
40+
var found []string
41+
42+
for _, org := range orgs {
43+
envs, err := client.Envs(me, org)
44+
if err != nil {
45+
flog.Fatal("get envs for %v: %+v", org.Name, err)
46+
}
47+
for _, env := range envs {
48+
found = append(found, env.Name)
49+
if env.Name != name {
50+
continue
51+
}
52+
return env
53+
}
54+
}
55+
flog.Info("found %q", found)
56+
flog.Fatal("environment %q not found", name)
57+
panic("unreachable")
58+
}

cmd/coder/exit.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,8 @@ import (
44
"os"
55

66
"github.com/spf13/pflag"
7-
"go.coder.com/flog"
87
)
98

10-
func exitOnError(err error) {
11-
if err != nil {
12-
flog.Fatal("%+v", err.Error())
13-
}
14-
}
15-
16-
func exitAfter(err error) {
17-
exitOnError(err)
18-
os.Exit(0)
19-
}
20-
219
func exitUsage(fl *pflag.FlagSet) {
2210
fl.Usage()
2311
os.Exit(1)

cmd/coder/login.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"go.coder.com/cli"
1212
"go.coder.com/flog"
1313

14-
"cdr.dev/coder-cli/internal/client"
1514
"cdr.dev/coder-cli/internal/config"
1615
"cdr.dev/coder-cli/internal/loginsrv"
1716
)
@@ -26,29 +25,6 @@ func (cmd loginCmd) Spec() cli.CommandSpec {
2625
Desc: "authenticate this client for future operations",
2726
}
2827
}
29-
30-
func requireAuth() *client.Client {
31-
sessionToken, err := config.Session.Read()
32-
if err != nil {
33-
flog.Fatal("read session: %v (did you run coder login?)", err)
34-
}
35-
36-
rawURL, err := config.URL.Read()
37-
if err != nil {
38-
flog.Fatal("read url: %v (did you run coder login?)", err)
39-
}
40-
41-
u, err := url.Parse(rawURL)
42-
if err != nil {
43-
flog.Fatal("url misformatted: %v (try runing coder login)", err)
44-
}
45-
46-
return &client.Client{
47-
BaseURL: u,
48-
Token: sessionToken,
49-
}
50-
}
51-
5228
func (cmd loginCmd) Run(fl *pflag.FlagSet) {
5329
rawURL := fl.Arg(0)
5430
if rawURL == "" {

cmd/coder/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ func (r *rootCmd) Spec() cli.CommandSpec {
2626

2727
func (r *rootCmd) Subcommands() []cli.Command {
2828
return []cli.Command{
29-
loginCmd{},
30-
logoutCmd{},
29+
&loginCmd{},
30+
&logoutCmd{},
3131
&shellCmd{},
3232
&syncCmd{},
3333
}

cmd/coder/shell.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"go.coder.com/cli"
1010
"go.coder.com/flog"
1111

12-
client "cdr.dev/coder-cli/internal/client"
12+
client "cdr.dev/coder-cli/internal/entclient"
1313
"cdr.dev/coder-cli/wush"
1414
)
1515

@@ -19,25 +19,26 @@ type shellCmd struct {
1919
func (cmd *shellCmd) Spec() cli.CommandSpec {
2020
return cli.CommandSpec{
2121
Name: "sh",
22-
Usage: "<env name> -- <command [command args...]>",
22+
Usage: "<env name> <command [command args...]>",
2323
Desc: "executes a remote command on the environment",
2424
RawArgs: true,
2525
}
2626
}
2727

2828
func (cmd *shellCmd) Run(fl *pflag.FlagSet) {
29-
if len(fl.Args()) < 3 {
29+
if len(fl.Args()) < 2 {
3030
exitUsage(fl)
3131
}
3232
var (
3333
envName = fl.Arg(0)
34-
_ = fl.Arg(1)
35-
command = fl.Arg(2)
36-
args = fl.Args()[3:]
34+
command = fl.Arg(1)
35+
args = fl.Args()[2:]
3736
)
3837

39-
entClient := requireAuth()
40-
env := findEnv(entClient, envName)
38+
var (
39+
entClient = requireAuth()
40+
env = findEnv(entClient, envName)
41+
)
4142

4243
conn, err := entClient.DialWush(
4344
env,

cmd/coder/sync.go

Lines changed: 8 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,18 @@
11
package main
22

33
import (
4-
"context"
5-
"crypto/rand"
6-
"io"
7-
"io/ioutil"
84
"os"
95
"strings"
106

11-
"github.com/cheggaaa/pb/v3"
127
"github.com/spf13/pflag"
138
"go.coder.com/cli"
149
"go.coder.com/flog"
1510

16-
"cdr.dev/coder-cli/internal/client"
1711
"cdr.dev/coder-cli/internal/sync"
18-
"cdr.dev/coder-cli/wush"
1912
)
2013

2114
type syncCmd struct {
22-
init bool
23-
benchSize int64
15+
init bool
2416
}
2517

2618
func (cmd *syncCmd) Spec() cli.CommandSpec {
@@ -33,76 +25,6 @@ func (cmd *syncCmd) Spec() cli.CommandSpec {
3325

3426
func (cmd *syncCmd) RegisterFlags(fl *pflag.FlagSet) {
3527
fl.BoolVarP(&cmd.init, "init", "i", false, "do inititial transfer and exit")
36-
fl.Int64Var(&cmd.benchSize, "bench", 0, "bench test the wush endpoint")
37-
}
38-
39-
// userOrgs gets a list of orgs the user is apart of.
40-
func userOrgs(user *client.User, orgs []client.Org) []client.Org {
41-
var uo []client.Org
42-
outer:
43-
for _, org := range orgs {
44-
for _, member := range org.Members {
45-
if member.ID != user.ID {
46-
continue
47-
}
48-
uo = append(uo, org)
49-
continue outer
50-
}
51-
}
52-
return uo
53-
}
54-
55-
func findEnv(client *client.Client, name string) client.Environment {
56-
me, err := client.Me()
57-
if err != nil {
58-
flog.Fatal("get self: %+v", err)
59-
}
60-
61-
orgs, err := client.Orgs()
62-
if err != nil {
63-
flog.Fatal("get orgs: %+v", err)
64-
}
65-
66-
orgs = userOrgs(me, orgs)
67-
68-
var found []string
69-
70-
for _, org := range orgs {
71-
envs, err := client.Envs(me, org)
72-
if err != nil {
73-
flog.Fatal("get envs for %v: %+v", org.Name, err)
74-
}
75-
for _, env := range envs {
76-
found = append(found, env.Name)
77-
if env.Name != name {
78-
continue
79-
}
80-
return env
81-
}
82-
}
83-
flog.Info("found %q", found)
84-
flog.Fatal("environment %q not found", name)
85-
panic("unreachable")
86-
}
87-
88-
func (cmd *syncCmd) bench(client *client.Client, env client.Environment) {
89-
conn, err := client.DialWush(env, nil, "cat")
90-
if err != nil {
91-
flog.Fatal("wush failed: %v", err)
92-
}
93-
wc := wush.NewClient(context.Background(), conn)
94-
bar := pb.New64(cmd.benchSize)
95-
bar.Start()
96-
go io.Copy(ioutil.Discard, wc.Stdout)
97-
io.Copy(
98-
bar.NewProxyWriter(wc.Stdin),
99-
io.LimitReader(rand.Reader, cmd.benchSize),
100-
)
101-
wc.Stdin.Close()
102-
code, err := wc.Wait()
103-
if err != nil || code != 0 {
104-
flog.Error("bench: (code %v) %v", code, err)
105-
}
10628
}
10729

10830
func (cmd *syncCmd) Run(fl *pflag.FlagSet) {
@@ -114,7 +36,7 @@ func (cmd *syncCmd) Run(fl *pflag.FlagSet) {
11436
exitUsage(fl)
11537
}
11638

117-
client := requireAuth()
39+
entClient := requireAuth()
11840

11941
info, err := os.Stat(local)
12042
if err != nil {
@@ -133,22 +55,17 @@ func (cmd *syncCmd) Run(fl *pflag.FlagSet) {
13355
remoteDir = remoteTokens[1]
13456
)
13557

136-
env := findEnv(client, envName)
137-
138-
if cmd.benchSize > 0 {
139-
cmd.bench(client, env)
140-
return
141-
}
58+
env := findEnv(entClient, envName)
14259

14360
s := sync.Sync{
14461
Init: cmd.init,
62+
Environment: env,
14563
RemoteDir: remoteDir,
14664
LocalDir: local,
147-
Client: client,
148-
Environment: env,
65+
Client: entClient,
14966
}
150-
err = s.Run()
151-
if err != nil {
152-
flog.Fatal("sync: %v", err)
67+
for err == nil || err == sync.ErrRestartSync {
68+
err = s.Run()
15369
}
70+
flog.Fatal("sync: %v", err)
15471
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.14
55
require (
66
github.com/cheggaaa/pb/v3 v3.0.4
77
github.com/dustin/go-humanize v1.0.0
8+
github.com/gorilla/websocket v1.4.1
89
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
910
github.com/mattn/go-isatty v0.0.12
1011
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
1313
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
1414
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
1515
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
16+
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
1617
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
1718
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f h1:dKccXx7xA56UNqOcFIbuqFjAWPVtP688j5QMgmo6OHU=
1819
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f/go.mod h1:4rEELDSfUAlBSyUjPG0JnaNGjf13JySHFeRdD/3dLP0=

0 commit comments

Comments
 (0)