Skip to content

Commit 8329b45

Browse files
authored
Merge pull request #2 from cdr/bind-cmd
consolidate bind to single command
2 parents c062712 + 9350080 commit 8329b45

File tree

7 files changed

+157
-221
lines changed

7 files changed

+157
-221
lines changed

internal/client/client.go

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,9 @@ package client
22

33
import (
44
"net/url"
5-
6-
"go.coder.com/cloud-agent/internal/config"
75
)
86

97
type Client struct {
10-
token string
11-
baseURL *url.URL
12-
}
13-
14-
func FromEnv() (*Client, error) {
15-
token, err := config.SessionToken.Read()
16-
if err != nil {
17-
return nil, err
18-
}
19-
20-
u, err := config.URL.Read()
21-
if err != nil {
22-
return nil, err
23-
}
24-
25-
parsed, err := url.Parse(u)
26-
if err != nil {
27-
return nil, err
28-
}
29-
30-
return &Client{
31-
token: token,
32-
baseURL: parsed,
33-
}, nil
8+
Token string
9+
BaseURL *url.URL
3410
}

internal/client/request.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ func (c *Client) request(method, path string, body interface{}) (*http.Response,
1616
return nil, xerrors.Errorf("marshal body: %w", err)
1717
}
1818

19-
req, err := http.NewRequest(method, c.baseURL.String()+path, bytes.NewReader(b))
19+
req, err := http.NewRequest(method, c.BaseURL.String()+path, bytes.NewReader(b))
2020
if err != nil {
2121
return nil, xerrors.Errorf("new request: %w", err)
2222
}
2323

24-
req.Header.Set(sessionHeader, c.token)
24+
req.Header.Set(sessionHeader, c.Token)
2525

2626
return http.DefaultClient.Do(req)
2727
}

internal/cmd/bind.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"net/url"
6+
"os"
7+
"regexp"
8+
"strings"
9+
"time"
10+
11+
"cdr.dev/slog/sloggers/sloghuman"
12+
"github.com/spf13/pflag"
13+
"golang.org/x/xerrors"
14+
15+
"go.coder.com/cli"
16+
"go.coder.com/cloud-agent/internal/client"
17+
"go.coder.com/cloud-agent/internal/config"
18+
"go.coder.com/cloud-agent/internal/ideproxy"
19+
"go.coder.com/flog"
20+
)
21+
22+
var (
23+
DefaultCloudURL = "https://cloud.coder.com"
24+
)
25+
26+
var codeServerNameRx = regexp.MustCompile("^[a-z][a-z0-9_]{0,50}$")
27+
28+
type bindCmd struct {
29+
cloudURL string
30+
codeServerAddr string
31+
}
32+
33+
func (c *bindCmd) Spec() cli.CommandSpec {
34+
return cli.CommandSpec{
35+
Name: "bind",
36+
Usage: "[NAME]",
37+
Desc: "Bind a server to Coder Cloud. A name will be generated from the hostname if one is not provided.",
38+
}
39+
}
40+
41+
func (c *bindCmd) RegisterFlags(fl *pflag.FlagSet) {
42+
fl.StringVar(&c.cloudURL, "cloud-url", DefaultCloudURL, "The Coder Cloud URL to connect to.")
43+
fl.StringVar(&c.codeServerAddr,
44+
"code-server-addr",
45+
"localhost:8080",
46+
"The address of the code-server instance to proxy.",
47+
)
48+
}
49+
50+
func (c *bindCmd) Run(fl *pflag.FlagSet) {
51+
var (
52+
err error
53+
ctx = context.Background()
54+
)
55+
56+
name := fl.Arg(0)
57+
if name == "" {
58+
// Generate a name based on the hostname if one is not provided.
59+
name, err = genServerName()
60+
if err != nil {
61+
flog.Fatal("Failed to generate server name: %v", err.Error())
62+
}
63+
}
64+
65+
if !codeServerNameRx.MatchString(name) {
66+
flog.Fatal("Name must conform to regex %s", codeServerNameRx.String())
67+
}
68+
69+
cloudURL, err := url.Parse(c.cloudURL)
70+
if err != nil {
71+
flog.Fatal("Invalid Cloud URL: %v", err.Error())
72+
}
73+
74+
token, err := config.SessionToken.Read()
75+
if xerrors.Is(err, os.ErrNotExist) {
76+
token, err = login(cloudURL.String(), name)
77+
}
78+
if err != nil {
79+
flog.Fatal("Failed to login: %v", err)
80+
}
81+
82+
cli := client.Client{
83+
Token: token,
84+
BaseURL: cloudURL,
85+
}
86+
87+
// Register the server with Coder Cloud. This is an idempotent
88+
// operation.
89+
cs, err := cli.RegisterCodeServer(name)
90+
if err != nil {
91+
flog.Fatal("Failed to register server: %v", err)
92+
}
93+
94+
// Get the Access URL for the user.
95+
url, err := cli.AccessURL(cs.ID)
96+
if err != nil {
97+
flog.Fatal("Failed to query server: %v", err)
98+
}
99+
100+
agent := &ideproxy.Agent{
101+
Log: sloghuman.Make(os.Stderr),
102+
CodeServerID: cs.ID,
103+
SessionToken: token,
104+
CloudProxyURL: c.cloudURL,
105+
CodeServerAddr: os.Getenv(c.codeServerAddr),
106+
}
107+
108+
proxy := func() {
109+
err = agent.Proxy(ctx)
110+
if err != nil {
111+
flog.Error("Connection to Coder-Cloud disrupted, re-establishing connection: %v", err.Error())
112+
}
113+
}
114+
115+
flog.Info("Proxying code-server to Coder Cloud, you can access your IDE at %v", url)
116+
117+
proxy()
118+
119+
// Avoid a super tight loop.
120+
ticker := time.NewTicker(time.Second)
121+
for range ticker.C {
122+
proxy()
123+
}
124+
}
125+
126+
func login(url, serverName string) (string, error) {
127+
token, err := client.Login(url, serverName)
128+
if err != nil {
129+
return "", xerrors.Errorf("unable to login: %w", err)
130+
}
131+
132+
err = config.SessionToken.Write(token)
133+
if err != nil {
134+
return "", xerrors.Errorf("write session token to file: %w", err)
135+
}
136+
137+
return token, nil
138+
}
139+
140+
func genServerName() (string, error) {
141+
hostname, err := os.Hostname()
142+
if err != nil {
143+
xerrors.Errorf("get hostname: %w", err)
144+
}
145+
146+
hostname = strings.ToLower(hostname)
147+
148+
// Only use the first token.
149+
hostname = strings.Split(hostname, ".")[0]
150+
// '-' are not allowed, convert them to '_'.
151+
return strings.Replace(hostname, "-", "_", -1), nil
152+
}

internal/cmd/cmd.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ func (c *rootCmd) Spec() cli.CommandSpec {
2727

2828
func (c *rootCmd) Subcommands() []cli.Command {
2929
return []cli.Command{
30-
&linkCmd{},
31-
&proxyCmd{},
30+
&bindCmd{},
3231
&versionCmd{},
3332
}
3433
}

internal/cmd/link.go

Lines changed: 0 additions & 91 deletions
This file was deleted.

0 commit comments

Comments
 (0)