|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "encoding/json" |
| 5 | + "fmt" |
| 6 | + "net/http" |
| 7 | + "os" |
| 8 | + "strconv" |
| 9 | + "strings" |
| 10 | + "text/tabwriter" |
| 11 | + |
| 12 | + "github.com/spf13/pflag" |
| 13 | + |
| 14 | + "go.coder.com/cli" |
| 15 | + "go.coder.com/flog" |
| 16 | +) |
| 17 | + |
| 18 | +type urlsCmd struct{} |
| 19 | + |
| 20 | +// DevURL is the parsed json response record for a devURL from cemanager |
| 21 | +type DevURL struct { |
| 22 | + ID string `json:"id"` |
| 23 | + URL string `json:"url"` |
| 24 | + Port string `json:"port"` |
| 25 | + Access string `json:"access"` |
| 26 | +} |
| 27 | + |
| 28 | +var urlAccessLevel = map[string]string{ |
| 29 | + //Remote API endpoint requires these in uppercase |
| 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 portIsValid(port string) bool { |
| 37 | + p, err := strconv.ParseUint(port, 10, 16) |
| 38 | + if p < 1 { |
| 39 | + // port 0 means 'any free port', which we don't support |
| 40 | + err = strconv.ErrRange |
| 41 | + } |
| 42 | + if err != nil { |
| 43 | + fmt.Println("Invalid port") |
| 44 | + } |
| 45 | + return err == nil |
| 46 | +} |
| 47 | + |
| 48 | +func accessLevelIsValid(level string) bool { |
| 49 | + _, ok := urlAccessLevel[level] |
| 50 | + if !ok { |
| 51 | + fmt.Println("Invalid access level") |
| 52 | + } |
| 53 | + return ok |
| 54 | +} |
| 55 | + |
| 56 | +type createSubCmd struct { |
| 57 | + access string |
| 58 | +} |
| 59 | + |
| 60 | +func (sub *createSubCmd) RegisterFlags(fl *pflag.FlagSet) { |
| 61 | + fl.StringVarP(&sub.access, "access", "a", "private", "[private | org | authed | public] set devurl access") |
| 62 | +} |
| 63 | + |
| 64 | +func (sub createSubCmd) Spec() cli.CommandSpec { |
| 65 | + return cli.CommandSpec{ |
| 66 | + Name: "create", |
| 67 | + Usage: "<env name> <port> [--access <level>]", |
| 68 | + Desc: "create/update a devurl for external access", |
| 69 | + } |
| 70 | +} |
| 71 | + |
| 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) |
| 79 | + |
| 80 | + if envName == "" { |
| 81 | + exitUsage(fl) |
| 82 | + } |
| 83 | + |
| 84 | + if !portIsValid(port) { |
| 85 | + exitUsage(fl) |
| 86 | + } |
| 87 | + |
| 88 | + access = strings.ToUpper(sub.access) |
| 89 | + if !accessLevelIsValid(access) { |
| 90 | + exitUsage(fl) |
| 91 | + } |
| 92 | + |
| 93 | + entClient := requireAuth() |
| 94 | + |
| 95 | + env := findEnv(entClient, envName) |
| 96 | + |
| 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 | +type delSubCmd struct{} |
| 111 | + |
| 112 | +func (sub delSubCmd) Spec() cli.CommandSpec { |
| 113 | + return cli.CommandSpec{ |
| 114 | + Name: "del", |
| 115 | + Usage: "<env name> <port>", |
| 116 | + Desc: "delete a devurl", |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +// devURLID returns the ID of a devURL, given the env name and port. |
| 121 | +// ("", false) is returned if no match is found. |
| 122 | +func devURLID(port string, urls []DevURL) (string, bool) { |
| 123 | + for _, url := range urls { |
| 124 | + if url.Port == port { |
| 125 | + return url.ID, true |
| 126 | + } |
| 127 | + } |
| 128 | + return "", false |
| 129 | +} |
| 130 | + |
| 131 | +// Run deletes a devURL, specified by env ID and port, from the cemanager. |
| 132 | +func (sub delSubCmd) Run(fl *pflag.FlagSet) { |
| 133 | + envName := fl.Arg(0) |
| 134 | + port := fl.Arg(1) |
| 135 | + |
| 136 | + if envName == "" { |
| 137 | + exitUsage(fl) |
| 138 | + } |
| 139 | + |
| 140 | + if !portIsValid(port) { |
| 141 | + exitUsage(fl) |
| 142 | + } |
| 143 | + |
| 144 | + entClient := requireAuth() |
| 145 | + |
| 146 | + env := findEnv(entClient, envName) |
| 147 | + |
| 148 | + urlID, found := devURLID(port, urlList(envName)) |
| 149 | + if found { |
| 150 | + fmt.Printf("Deleting devurl for port %v\n", port) |
| 151 | + } else { |
| 152 | + flog.Fatal("No devurl found for port %v", port) |
| 153 | + } |
| 154 | + |
| 155 | + err := entClient.DelDevURL(env.ID, urlID) |
| 156 | + if err != nil { |
| 157 | + flog.Error("delete devurl: %s", err.Error()) |
| 158 | + } |
| 159 | +} |
| 160 | + |
| 161 | +func (cmd urlsCmd) Spec() cli.CommandSpec { |
| 162 | + return cli.CommandSpec{ |
| 163 | + Name: "urls", |
| 164 | + Usage: "<env name>", |
| 165 | + Desc: "get all development urls for external access", |
| 166 | + } |
| 167 | +} |
| 168 | + |
| 169 | +// urlList returns the list of active devURLs from the cemanager. |
| 170 | +func urlList(envName string) []DevURL { |
| 171 | + entClient := requireAuth() |
| 172 | + env := findEnv(entClient, envName) |
| 173 | + |
| 174 | + reqString := "%s/api/environments/%s/devurls?session_token=%s" |
| 175 | + reqURL := fmt.Sprintf(reqString, entClient.BaseURL, env.ID, entClient.Token) |
| 176 | + |
| 177 | + resp, err := http.Get(reqURL) |
| 178 | + if err != nil { |
| 179 | + flog.Fatal("%v", err) |
| 180 | + } |
| 181 | + defer resp.Body.Close() |
| 182 | + |
| 183 | + if resp.StatusCode != 200 { |
| 184 | + flog.Fatal("non-success status code: %d", resp.StatusCode) |
| 185 | + } |
| 186 | + |
| 187 | + dec := json.NewDecoder(resp.Body) |
| 188 | + |
| 189 | + devURLs := make([]DevURL, 0) |
| 190 | + err = dec.Decode(&devURLs) |
| 191 | + if err != nil { |
| 192 | + flog.Fatal("%v", err) |
| 193 | + } |
| 194 | + |
| 195 | + if len(devURLs) == 0 { |
| 196 | + fmt.Printf("no dev urls were found for environment: %s\n", envName) |
| 197 | + } |
| 198 | + |
| 199 | + return devURLs |
| 200 | +} |
| 201 | + |
| 202 | +// Run gets the list of active devURLs from the cemanager for the |
| 203 | +// specified environment and outputs info to stdout. |
| 204 | +func (cmd urlsCmd) Run(fl *pflag.FlagSet) { |
| 205 | + envName := fl.Arg(0) |
| 206 | + devURLs := urlList(envName) |
| 207 | + |
| 208 | + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent) |
| 209 | + for _, devURL := range devURLs { |
| 210 | + fmt.Fprintf(w, "%s\t%s\t%s\n", devURL.URL, devURL.Port, devURL.Access) |
| 211 | + } |
| 212 | + w.Flush() |
| 213 | +} |
| 214 | + |
| 215 | +func (cmd *urlsCmd) Subcommands() []cli.Command { |
| 216 | + return []cli.Command{ |
| 217 | + &createSubCmd{}, |
| 218 | + &delSubCmd{}, |
| 219 | + } |
| 220 | +} |
0 commit comments