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

Commit 0a79ef7

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

File tree

5 files changed

+195
-22
lines changed

5 files changed

+195
-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: 130 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net/http"
77
"os"
8+
"strings"
89
"text/tabwriter"
910

1011
"github.com/spf13/pflag"
@@ -14,36 +15,140 @@ import (
1415
)
1516

1617
type urlsCmd struct{}
18+
type createSubCmd struct{}
19+
type delSubCmd struct{}
1720

21+
// DevURL is the parsed json response record for a devURL from cemanager
1822
type DevURL struct {
23+
ID string `json:"id"`
1924
URL string `json:"url"`
2025
Port string `json:"port"`
2126
Access string `json:"access"`
2227
}
2328

24-
func (cmd urlsCmd) Spec() cli.CommandSpec {
29+
var urlAccessLevel = map[string]string{
30+
"PRIVATE": "Only you can access",
31+
"ORG": "All members of your organization can access",
32+
"AUTHED": "Authenticated users can access",
33+
"PUBLIC": "Anyone on the internet can access this link",
34+
}
35+
36+
func isAccessLevelValid(level string) bool {
37+
_, ok := urlAccessLevel[level]
38+
return ok
39+
}
40+
41+
func (sub createSubCmd) Spec() cli.CommandSpec {
2542
return cli.CommandSpec{
26-
Name: "urls",
27-
Usage: "<env name>",
28-
Desc: "get all development urls for external access",
43+
Name: "create",
44+
Usage: "<env name> <port> [<access>]",
45+
Desc: `create/update a devurl for external access
46+
<access> is one of [PRIVATE | ORG | AUTHED | PUBLIC]`,
2947
}
3048
}
3149

32-
func (cmd urlsCmd) Run(fl *pflag.FlagSet) {
33-
var envName = fl.Arg(0)
50+
// Run creates or updates a devURL, specified by env ID and port
51+
// (fl.Arg(0) and fl.Arg(1)), with access level (fl.Arg(2)) on
52+
// the cemanager.
53+
func (sub createSubCmd) Run(fl *pflag.FlagSet) {
54+
envName := fl.Arg(0)
55+
port := fl.Arg(1)
56+
access := fl.Arg(2)
3457

3558
if envName == "" {
3659
exitUsage(fl)
3760
}
3861

62+
if access == "" {
63+
access = "PRIVATE"
64+
}
65+
66+
access = strings.ToUpper(access)
67+
68+
if !isAccessLevelValid(access) {
69+
fmt.Printf("Invalid access level '%v'\n", access)
70+
exitUsage(fl)
71+
}
72+
3973
entClient := requireAuth()
4074

4175
env := findEnv(entClient, envName)
4276

77+
_, found := devURLID(port, urlList(envName))
78+
if found {
79+
fmt.Printf("Updating devurl for port %v\n", port)
80+
} else {
81+
fmt.Printf("Adding devurl for port %v\n", port)
82+
}
83+
84+
err := entClient.UpsertDevURL(env.ID, port, access)
85+
if err != nil {
86+
flog.Error("upsert devurl: %s", err.Error())
87+
}
88+
}
89+
90+
func (sub delSubCmd) Spec() cli.CommandSpec {
91+
return cli.CommandSpec{
92+
Name: "del",
93+
Usage: "<env name> <port>",
94+
Desc: "delete a devurl",
95+
}
96+
}
97+
98+
// devURLID returns the ID of a devURL, given the env name and port.
99+
// ("", false) is returned if no match is found.
100+
func devURLID(port string, urls []DevURL) (string, bool) {
101+
for _, url := range urls {
102+
if url.Port == port {
103+
return url.ID, true
104+
}
105+
}
106+
return "", false
107+
}
108+
109+
// Run deletes a devURL, specified by env ID and port, from the cemanager.
110+
func (sub delSubCmd) Run(fl *pflag.FlagSet) {
111+
envName := fl.Arg(0)
112+
port := fl.Arg(1)
113+
114+
if envName == "" {
115+
exitUsage(fl)
116+
}
117+
118+
entClient := requireAuth()
119+
120+
env := findEnv(entClient, envName)
121+
122+
urlID, found := devURLID(port, urlList(envName))
123+
if found {
124+
fmt.Printf("Deleting devurl for port %v\n", port)
125+
} else {
126+
flog.Fatal("No devurl found for port %v", port)
127+
}
128+
129+
err := entClient.DelDevURL(env.ID, urlID)
130+
if err != nil {
131+
flog.Error("delete devurl: %s", err.Error())
132+
}
133+
}
134+
135+
func (cmd urlsCmd) Spec() cli.CommandSpec {
136+
return cli.CommandSpec{
137+
Name: "urls",
138+
Usage: "<env name>",
139+
Desc: "get all development urls for external access",
140+
}
141+
}
142+
143+
// urlList returns the list of active devURLs from the cemanager server.
144+
func urlList(envName string) []DevURL {
145+
entClient := requireAuth()
146+
env := findEnv(entClient, envName)
147+
43148
reqString := "%s/api/environments/%s/devurls?session_token=%s"
44-
reqUrl := fmt.Sprintf(reqString, entClient.BaseURL, env.ID, entClient.Token)
149+
reqURL := fmt.Sprintf(reqString, entClient.BaseURL, env.ID, entClient.Token)
45150

46-
resp, err := http.Get(reqUrl)
151+
resp, err := http.Get(reqURL)
47152
if err != nil {
48153
flog.Fatal("%v", err)
49154
}
@@ -55,7 +160,7 @@ func (cmd urlsCmd) Run(fl *pflag.FlagSet) {
55160

56161
dec := json.NewDecoder(resp.Body)
57162

58-
var devURLs = make([]DevURL, 0)
163+
devURLs := make([]DevURL, 0)
59164
err = dec.Decode(&devURLs)
60165
if err != nil {
61166
flog.Fatal("%v", err)
@@ -65,9 +170,25 @@ func (cmd urlsCmd) Run(fl *pflag.FlagSet) {
65170
fmt.Printf("no dev urls were found for environment: %s\n", envName)
66171
}
67172

173+
return devURLs
174+
}
175+
176+
// Run gets the list of active devURLs from the cemanager for the
177+
// specified environment and outputs info to stdout.
178+
func (cmd urlsCmd) Run(fl *pflag.FlagSet) {
179+
envName := fl.Arg(0)
180+
devURLs := urlList(envName)
181+
68182
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent)
69183
for _, devURL := range devURLs {
70184
fmt.Fprintf(w, "%s\t%s\t%s\n", devURL.URL, devURL.Port, devURL.Access)
71185
}
72186
w.Flush()
73187
}
188+
189+
func (cmd *urlsCmd) Subcommands() []cli.Command {
190+
return []cli.Command{
191+
&createSubCmd{},
192+
&delSubCmd{},
193+
}
194+
}

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)