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

Commit 22bffe8

Browse files
author
Russtopia
committed
Add subcommands 'create' and 'del' to manage devurls
1 parent 6450687 commit 22bffe8

File tree

5 files changed

+219
-22
lines changed

5 files changed

+219
-22
lines changed

cmd/coder/sync.go

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package main
22

33
import (
4-
"errors"
5-
"fmt"
64
"os"
75
"path/filepath"
86
"strings"
@@ -31,10 +29,6 @@ func (cmd *syncCmd) RegisterFlags(fl *pflag.FlagSet) {
3129
fl.BoolVarP(&cmd.init, "init", "i", false, "do initial transfer and exit")
3230
}
3331

34-
// See https://lxadm.com/Rsync_exit_codes#List_of_standard_rsync_exit_codes.
35-
var IncompatRsync = errors.New("rsync: exit status 2")
36-
var StreamErrRsync = errors.New("rsync: exit status 12")
37-
3832
func (cmd *syncCmd) Run(fl *pflag.FlagSet) {
3933
var (
4034
local = fl.Arg(0)
@@ -81,11 +75,7 @@ func (cmd *syncCmd) Run(fl *pflag.FlagSet) {
8175
err = s.Run()
8276
}
8377

84-
if fmt.Sprintf("%v", err) == fmt.Sprintf("%v", IncompatRsync) {
85-
flog.Fatal("no compatible rsync present on remote machine")
86-
} else if fmt.Sprintf("%v", err) == fmt.Sprintf("%v", StreamErrRsync) {
87-
flog.Fatal("error in rsync protocol datastream (no installed remote rsync?)")
88-
} else {
89-
flog.Fatal("sync: %v", err)
78+
if err != nil {
79+
flog.Fatal("%v", err)
9080
}
9181
}

cmd/coder/urls.go

Lines changed: 154 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"fmt"
66
"net/http"
77
"os"
8+
"strconv"
9+
"strings"
810
"text/tabwriter"
911

1012
"github.com/spf13/pflag"
@@ -14,36 +16,163 @@ import (
1416
)
1517

1618
type urlsCmd struct{}
19+
type createSubCmd struct {
20+
access string
21+
}
22+
type delSubCmd struct{}
1723

24+
// DevURL is the parsed json response record for a devURL from cemanager
1825
type DevURL struct {
26+
ID string `json:"id"`
1927
URL string `json:"url"`
2028
Port string `json:"port"`
2129
Access string `json:"access"`
2230
}
2331

24-
func (cmd urlsCmd) Spec() cli.CommandSpec {
32+
var urlAccessLevel = map[string]string{
33+
//Remote API endpoint requires these in uppercase
34+
"PRIVATE": "Only you can access",
35+
"ORG": "All members of your organization can access",
36+
"AUTHED": "Authenticated users can access",
37+
"PUBLIC": "Anyone on the internet can access this link",
38+
}
39+
40+
func portIsValid(port string) bool {
41+
p, err := strconv.ParseUint(port, 10, 16)
42+
if p < 1 {
43+
// port 0 means 'any free port', which we don't support
44+
err = strconv.ErrRange
45+
}
46+
if err != nil {
47+
fmt.Println("bad port")
48+
}
49+
return err == nil
50+
}
51+
52+
func accessLevelIsValid(level string) bool {
53+
_, ok := urlAccessLevel[level]
54+
if !ok {
55+
fmt.Println("Invalid access level")
56+
}
57+
return ok
58+
}
59+
60+
func (sub *createSubCmd) RegisterFlags(fl *pflag.FlagSet) {
61+
fl.StringVar(&sub.access, "a", "private", "[private | org | authed | public] set devurl access")
62+
}
63+
64+
func (sub createSubCmd) Spec() cli.CommandSpec {
2565
return cli.CommandSpec{
26-
Name: "urls",
27-
Usage: "<env name>",
28-
Desc: "get all development urls for external access",
66+
Name: "create",
67+
Usage: "<env name> <port> [--a access]",
68+
Desc: "create/update a devurl for external access",
2969
}
3070
}
3171

32-
func (cmd urlsCmd) Run(fl *pflag.FlagSet) {
33-
var envName = fl.Arg(0)
72+
// Run creates or updates a devURL, specified by env ID and port
73+
// (fl.Arg(0) and fl.Arg(1)), with access level (fl.Arg(2)) on
74+
// the cemanager.
75+
func (sub createSubCmd) Run(fl *pflag.FlagSet) {
76+
envName := fl.Arg(0)
77+
port := fl.Arg(1)
78+
access := fl.Arg(2)
3479

3580
if envName == "" {
3681
exitUsage(fl)
3782
}
3883

84+
if !portIsValid(port) {
85+
exitUsage(fl)
86+
}
87+
88+
access = strings.ToUpper(sub.access)
89+
if !accessLevelIsValid(access) {
90+
exitUsage(fl)
91+
}
92+
3993
entClient := requireAuth()
4094

4195
env := findEnv(entClient, envName)
4296

97+
_, found := devURLID(port, urlList(envName))
98+
if found {
99+
fmt.Printf("Updating devurl for port %v\n", port)
100+
} else {
101+
fmt.Printf("Adding devurl for port %v\n", port)
102+
}
103+
104+
err := entClient.UpsertDevURL(env.ID, port, access)
105+
if err != nil {
106+
flog.Error("upsert devurl: %s", err.Error())
107+
}
108+
}
109+
110+
func (sub delSubCmd) Spec() cli.CommandSpec {
111+
return cli.CommandSpec{
112+
Name: "del",
113+
Usage: "<env name> <port>",
114+
Desc: "delete a devurl",
115+
}
116+
}
117+
118+
// devURLID returns the ID of a devURL, given the env name and port.
119+
// ("", false) is returned if no match is found.
120+
func devURLID(port string, urls []DevURL) (string, bool) {
121+
for _, url := range urls {
122+
if url.Port == port {
123+
return url.ID, true
124+
}
125+
}
126+
return "", false
127+
}
128+
129+
// Run deletes a devURL, specified by env ID and port, from the cemanager.
130+
func (sub delSubCmd) Run(fl *pflag.FlagSet) {
131+
envName := fl.Arg(0)
132+
port := fl.Arg(1)
133+
134+
if envName == "" {
135+
exitUsage(fl)
136+
}
137+
138+
if !portIsValid(port) {
139+
exitUsage(fl)
140+
}
141+
142+
entClient := requireAuth()
143+
144+
env := findEnv(entClient, envName)
145+
146+
urlID, found := devURLID(port, urlList(envName))
147+
if found {
148+
fmt.Printf("Deleting devurl for port %v\n", port)
149+
} else {
150+
flog.Fatal("No devurl found for port %v", port)
151+
}
152+
153+
err := entClient.DelDevURL(env.ID, urlID)
154+
if err != nil {
155+
flog.Error("delete devurl: %s", err.Error())
156+
}
157+
}
158+
159+
func (cmd urlsCmd) Spec() cli.CommandSpec {
160+
return cli.CommandSpec{
161+
Name: "urls",
162+
Usage: "<env name>",
163+
Desc: "get all development urls for external access",
164+
}
165+
}
166+
167+
// urlList returns the list of active devURLs from the cemanager.
168+
func urlList(envName string) []DevURL {
169+
entClient := requireAuth()
170+
env := findEnv(entClient, envName)
171+
43172
reqString := "%s/api/environments/%s/devurls?session_token=%s"
44-
reqUrl := fmt.Sprintf(reqString, entClient.BaseURL, env.ID, entClient.Token)
173+
reqURL := fmt.Sprintf(reqString, entClient.BaseURL, env.ID, entClient.Token)
45174

46-
resp, err := http.Get(reqUrl)
175+
resp, err := http.Get(reqURL)
47176
if err != nil {
48177
flog.Fatal("%v", err)
49178
}
@@ -55,7 +184,7 @@ func (cmd urlsCmd) Run(fl *pflag.FlagSet) {
55184

56185
dec := json.NewDecoder(resp.Body)
57186

58-
var devURLs = make([]DevURL, 0)
187+
devURLs := make([]DevURL, 0)
59188
err = dec.Decode(&devURLs)
60189
if err != nil {
61190
flog.Fatal("%v", err)
@@ -65,9 +194,25 @@ func (cmd urlsCmd) Run(fl *pflag.FlagSet) {
65194
fmt.Printf("no dev urls were found for environment: %s\n", envName)
66195
}
67196

197+
return devURLs
198+
}
199+
200+
// Run gets the list of active devURLs from the cemanager for the
201+
// specified environment and outputs info to stdout.
202+
func (cmd urlsCmd) Run(fl *pflag.FlagSet) {
203+
envName := fl.Arg(0)
204+
devURLs := urlList(envName)
205+
68206
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent)
69207
for _, devURL := range devURLs {
70208
fmt.Fprintf(w, "%s\t%s\t%s\n", devURL.URL, devURL.Port, devURL.Access)
71209
}
72210
w.Flush()
73211
}
212+
213+
func (cmd *urlsCmd) Subcommands() []cli.Command {
214+
return []cli.Command{
215+
&createSubCmd{},
216+
&delSubCmd{},
217+
}
218+
}

internal/entclient/activity.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package entclient
22

3-
import "net/http"
3+
import (
4+
"net/http"
5+
)
46

57
func (c Client) PushActivity(source string, envID string) error {
68
res, err := c.request("POST", "/api/metrics/usage/push", map[string]string{

internal/entclient/devurl.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package entclient
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
)
7+
8+
func (c Client) DelDevURL(envID, urlID string) error {
9+
reqString := "/api/environments/%s/devurls/%s"
10+
reqUrl := fmt.Sprintf(reqString, envID, urlID)
11+
12+
res, err := c.request("DELETE", reqUrl, map[string]string{
13+
"environment_id": envID,
14+
"url_id": urlID,
15+
})
16+
if err != nil {
17+
return err
18+
}
19+
20+
if res.StatusCode != http.StatusOK {
21+
return bodyError(res)
22+
}
23+
24+
return nil
25+
}
26+
27+
func (c Client) UpsertDevURL(envID, port, access string) error {
28+
reqString := "/api/environments/%s/devurls"
29+
reqUrl := fmt.Sprintf(reqString, envID)
30+
31+
res, err := c.request("POST", reqUrl, map[string]string{
32+
"environment_id": envID,
33+
"port": port,
34+
"access": access,
35+
})
36+
if err != nil {
37+
return err
38+
}
39+
40+
if res.StatusCode != http.StatusOK {
41+
return bodyError(res)
42+
}
43+
44+
return nil
45+
}

internal/sync/sync.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ type Sync struct {
4141
Client *entclient.Client
4242
}
4343

44+
// See https://lxadm.com/Rsync_exit_codes#List_of_standard_rsync_exit_codes.
45+
const (
46+
rsyncExitCodeIncompat = 2
47+
rsyncExitCodeDataStream = 12
48+
)
49+
4450
func (s Sync) syncPaths(delete bool, local, remote string) error {
4551
self := os.Args[0]
4652

@@ -66,6 +72,15 @@ func (s Sync) syncPaths(delete bool, local, remote string) error {
6672
cmd.Stdin = os.Stdin
6773
err := cmd.Run()
6874
if err != nil {
75+
if exitError, ok := err.(*exec.ExitError); ok {
76+
if exitError.ExitCode() == rsyncExitCodeIncompat {
77+
return xerrors.Errorf("no compatible rsync on remote machine: rsync: %w", err)
78+
} else if exitError.ExitCode() == rsyncExitCodeDataStream {
79+
return xerrors.Errorf("protocol datastream error or no remote rsync found: %w", err)
80+
} else {
81+
return xerrors.Errorf("rsync: %w", err)
82+
}
83+
}
6984
return xerrors.Errorf("rsync: %w", err)
7085
}
7186
return nil

0 commit comments

Comments
 (0)