From 5d92778ffc637b821ef9a883333a5db07d65d237 Mon Sep 17 00:00:00 2001 From: Russtopia Date: Thu, 30 Jul 2020 08:24:02 -0700 Subject: [PATCH 1/6] Add custom prefix devurl --- cmd/coder/urls.go | 85 ++++++++++++++++++++++-------------- go.mod | 2 +- go.sum | 6 +++ internal/entclient/devurl.go | 64 ++++++++++++++++++++++----- 4 files changed, 111 insertions(+), 46 deletions(-) diff --git a/cmd/coder/urls.go b/cmd/coder/urls.go index 7626ae93..21596055 100644 --- a/cmd/coder/urls.go +++ b/cmd/coder/urls.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "os" + "regexp" "strconv" "strings" "text/tabwriter" @@ -21,7 +22,7 @@ type urlsCmd struct{} type DevURL struct { ID string `json:"id"` URL string `json:"url"` - Port string `json:"port"` + Port int `json:"port"` Access string `json:"access"` } @@ -33,55 +34,68 @@ var urlAccessLevel = map[string]string{ "PUBLIC": "Anyone on the internet can access this link", } -func portIsValid(port string) bool { +func validatePort(port string) (int, error) { p, err := strconv.ParseUint(port, 10, 16) + if err != nil { + flog.Error("Invalid port") + return 0, err + } if p < 1 { // port 0 means 'any free port', which we don't support err = strconv.ErrRange + flog.Error("Port must be > 0") + return 0, err } - if err != nil { - fmt.Println("Invalid port") - } - return err == nil + return int(p), nil } func accessLevelIsValid(level string) bool { _, ok := urlAccessLevel[level] if !ok { - fmt.Println("Invalid access level") + flog.Error("Invalid access level") } return ok } type createSubCmd struct { - access string + access string + urlname string } func (sub *createSubCmd) RegisterFlags(fl *pflag.FlagSet) { fl.StringVarP(&sub.access, "access", "a", "private", "[private | org | authed | public] set devurl access") + fl.StringVarP(&sub.urlname, "name", "n", "", "devurl name") } func (sub createSubCmd) Spec() cli.CommandSpec { return cli.CommandSpec{ - Name: "create", - Usage: " [--access ]", - Desc: "create/update a devurl for external access", + Name: "create", + Usage: " [--access ] [--name ]", + Aliases: []string{"edit"}, + Desc: "create or update a devurl for external access", } } +// devURLNameValidRx is the regex used to validate devurl names specified +// via the --name subcommand. Named devurls must begin with a letter, and +// consist solely of letters and digits, with a max length of 64 chars. +var devURLNameValidRx = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9]{0,63}$") + // Run creates or updates a devURL, specified by env ID and port // (fl.Arg(0) and fl.Arg(1)), with access level (fl.Arg(2)) on // the cemanager. func (sub createSubCmd) Run(fl *pflag.FlagSet) { envName := fl.Arg(0) port := fl.Arg(1) - access := fl.Arg(2) + name := fl.Arg(2) + access := fl.Arg(3) if envName == "" { exitUsage(fl) } - if !portIsValid(port) { + portNum, err := validatePort(port) + if err != nil { exitUsage(fl) } @@ -90,20 +104,28 @@ func (sub createSubCmd) Run(fl *pflag.FlagSet) { exitUsage(fl) } + name = sub.urlname + if name != "" && !devURLNameValidRx.MatchString(name) { + flog.Error("update devurl: name must be < 64 chars in length, begin with a letter and only contain letters or digits.") + return + } entClient := requireAuth() env := findEnv(entClient, envName) - _, found := devURLID(port, urlList(envName)) + urlID, found := devURLID(portNum, urlList(envName)) if found { - fmt.Printf("Updating devurl for port %v\n", port) + flog.Info("Updating devurl for port %v", port) + err := entClient.UpdateDevURL(env.ID, urlID, portNum, name, access) + if err != nil { + flog.Error("update devurl: %s", err.Error()) + } } else { - fmt.Printf("Adding devurl for port %v\n", port) - } - - err := entClient.UpsertDevURL(env.ID, port, access) - if err != nil { - flog.Error("upsert devurl: %s", err.Error()) + flog.Info("Adding devurl for port %v", port) + err := entClient.InsertDevURL(env.ID, portNum, name, access) + if err != nil { + flog.Error("insert devurl: %s", err.Error()) + } } } @@ -117,9 +139,10 @@ func (sub delSubCmd) Spec() cli.CommandSpec { } } -// devURLID returns the ID of a devURL, given the env name and port. +// devURLID returns the ID of a devURL, given the env name and port +// from a list of DevURL records. // ("", false) is returned if no match is found. -func devURLID(port string, urls []DevURL) (string, bool) { +func devURLID(port int, urls []DevURL) (string, bool) { for _, url := range urls { if url.Port == port { return url.ID, true @@ -137,22 +160,22 @@ func (sub delSubCmd) Run(fl *pflag.FlagSet) { exitUsage(fl) } - if !portIsValid(port) { + portNum, err := validatePort(port) + if err != nil { exitUsage(fl) } entClient := requireAuth() - env := findEnv(entClient, envName) - urlID, found := devURLID(port, urlList(envName)) + urlID, found := devURLID(portNum, urlList(envName)) if found { - fmt.Printf("Deleting devurl for port %v\n", port) + flog.Info("Deleting devurl for port %v", port) } else { flog.Fatal("No devurl found for port %v", port) } - err := entClient.DelDevURL(env.ID, urlID) + err = entClient.DelDevURL(env.ID, urlID) if err != nil { flog.Error("delete devurl: %s", err.Error()) } @@ -192,10 +215,6 @@ func urlList(envName string) []DevURL { flog.Fatal("%v", err) } - if len(devURLs) == 0 { - fmt.Printf("no dev urls were found for environment: %s\n", envName) - } - return devURLs } @@ -207,7 +226,7 @@ func (cmd urlsCmd) Run(fl *pflag.FlagSet) { w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent) for _, devURL := range devURLs { - fmt.Fprintf(w, "%s\t%s\t%s\n", devURL.URL, devURL.Port, devURL.Access) + fmt.Fprintf(w, "%s\t%d\t%s\n", devURL.URL, devURL.Port, devURL.Access) } w.Flush() } diff --git a/go.mod b/go.mod index 4f496165..71f90aa2 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 github.com/rjeczalik/notify v0.9.2 github.com/spf13/pflag v1.0.5 - go.coder.com/cli v0.4.0 + go.coder.com/cli v0.5.0 go.coder.com/flog v0.0.0-20190906214207-47dd47ea0512 golang.org/x/crypto v0.0.0-20200422194213-44a606286825 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a diff --git a/go.sum b/go.sum index 37756a28..cc2cac0b 100644 --- a/go.sum +++ b/go.sum @@ -163,6 +163,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= @@ -171,6 +173,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= go.coder.com/cli v0.4.0 h1:PruDGwm/CPFndyK/eMowZG3vzg5CgohRWeXWCTr3zi8= go.coder.com/cli v0.4.0/go.mod h1:hRTOURCR3LJF1FRW9arecgrzX+AHG7mfYMwThPIgq+w= +go.coder.com/cli v0.5.0 h1:7W9ECtZdVKaAc0Oe2uk5J/c0LCtsWufQz4NeX6YwP0g= +go.coder.com/cli v0.5.0/go.mod h1:h6091Eox0VdgJw2CDBvTyx7SnhduTm8qYM2bR2pewls= go.coder.com/flog v0.0.0-20190906214207-47dd47ea0512 h1:DjCS6dRQh+1PlfiBmnabxfdrzenb0tAwJqFxDEH/s9g= go.coder.com/flog v0.0.0-20190906214207-47dd47ea0512/go.mod h1:83JsYgXYv0EOaXjIMnaZ1Fl6ddNB3fJnDZ/8845mUJ8= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -300,6 +304,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/entclient/devurl.go b/internal/entclient/devurl.go index 9260dcce..e181bec4 100644 --- a/internal/entclient/devurl.go +++ b/internal/entclient/devurl.go @@ -3,16 +3,23 @@ package entclient import ( "fmt" "net/http" + + "golang.org/x/xerrors" ) +type delDevURLRequest struct { + EnvID string `json:"environment_id"` + DevURLID string `json:"url_id"` +} + // DelDevURL deletes the specified devurl func (c Client) DelDevURL(envID, urlID string) error { reqString := "/api/environments/%s/devurls/%s" reqURL := fmt.Sprintf(reqString, envID, urlID) - res, err := c.request("DELETE", reqURL, map[string]string{ - "environment_id": envID, - "url_id": urlID, + res, err := c.request("DELETE", reqURL, delDevURLRequest{ + EnvID: envID, + DevURLID: urlID, }) if err != nil { return err @@ -25,23 +32,56 @@ func (c Client) DelDevURL(envID, urlID string) error { return nil } -// UpsertDevURL upserts the specified devurl for the authenticated user -func (c Client) UpsertDevURL(envID, port, access string) error { +type createDevURLRequest struct { + EnvID string `json:"environment_id"` + Port int `json:"port"` + Access string `json:"access"` + Name string `json:"name"` +} + +// InsertDevURL inserts a new devurl for the authenticated user +func (c Client) InsertDevURL(envID string, port int, name, access string) error { reqString := "/api/environments/%s/devurls" reqURL := fmt.Sprintf(reqString, envID) - res, err := c.request("POST", reqURL, map[string]string{ - "environment_id": envID, - "port": port, - "access": access, + res, err := c.request("POST", reqURL, createDevURLRequest{ + EnvID: envID, + Port: port, + Access: access, + Name: name, }) + if res != nil && res.StatusCode == http.StatusConflict { + return xerrors.Errorf("Failed to create devurl. Check that the port and name are unique.") + } if err != nil { return err } + return nil +} - if res.StatusCode != http.StatusOK { - return bodyError(res) - } +type updateDevURLRequest struct { + EnvID string `json:"environment_id"` + Port int `json:"port"` + Access string `json:"access"` + Name string `json:"name"` +} + +// UpdateDevURL updates an existing devurl for the authenticated user +func (c Client) UpdateDevURL(envID, urlID string, port int, name, access string) error { + reqString := "/api/environments/%s/devurls/%s" + reqURL := fmt.Sprintf(reqString, envID, urlID) + res, err := c.request("PUT", reqURL, updateDevURLRequest{ + EnvID: envID, + Port: port, + Access: access, + Name: name, + }) + if res != nil && res.StatusCode == http.StatusConflict { + return xerrors.Errorf("Failed to update devurl. Check that the port and name are unique.") + } + if err != nil { + return err + } return nil } From 11f797a7f7a5124aaae913b07236f3775e44a5da Mon Sep 17 00:00:00 2001 From: Russtopia Date: Thu, 30 Jul 2020 08:48:24 -0700 Subject: [PATCH 2/6] Reverted err response --- internal/entclient/devurl.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/entclient/devurl.go b/internal/entclient/devurl.go index e181bec4..ee7ecce7 100644 --- a/internal/entclient/devurl.go +++ b/internal/entclient/devurl.go @@ -3,8 +3,6 @@ package entclient import ( "fmt" "net/http" - - "golang.org/x/xerrors" ) type delDevURLRequest struct { @@ -50,12 +48,14 @@ func (c Client) InsertDevURL(envID string, port int, name, access string) error Access: access, Name: name, }) - if res != nil && res.StatusCode == http.StatusConflict { - return xerrors.Errorf("Failed to create devurl. Check that the port and name are unique.") - } if err != nil { return err } + + if res.StatusCode != http.StatusOK { + return bodyError(res) + } + return nil } @@ -77,11 +77,13 @@ func (c Client) UpdateDevURL(envID, urlID string, port int, name, access string) Access: access, Name: name, }) - if res != nil && res.StatusCode == http.StatusConflict { - return xerrors.Errorf("Failed to update devurl. Check that the port and name are unique.") - } if err != nil { return err } + + if res.StatusCode != http.StatusOK { + return bodyError(res) + } + return nil } From 362ddc7cab9256c9574b61d82fe8b7c165dcf90a Mon Sep 17 00:00:00 2001 From: Russtopia Date: Thu, 30 Jul 2020 10:02:20 -0700 Subject: [PATCH 3/6] Enabled error response JSON body output --- internal/entclient/devurl.go | 3 +++ internal/entclient/error.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/entclient/devurl.go b/internal/entclient/devurl.go index ee7ecce7..65febfff 100644 --- a/internal/entclient/devurl.go +++ b/internal/entclient/devurl.go @@ -22,6 +22,7 @@ func (c Client) DelDevURL(envID, urlID string) error { if err != nil { return err } + defer res.Body.Close() if res.StatusCode != http.StatusOK { return bodyError(res) @@ -51,6 +52,7 @@ func (c Client) InsertDevURL(envID string, port int, name, access string) error if err != nil { return err } + defer res.Body.Close() if res.StatusCode != http.StatusOK { return bodyError(res) @@ -80,6 +82,7 @@ func (c Client) UpdateDevURL(envID, urlID string, port int, name, access string) if err != nil { return err } + defer res.Body.Close() if res.StatusCode != http.StatusOK { return bodyError(res) diff --git a/internal/entclient/error.go b/internal/entclient/error.go index 49f58669..25b31fde 100644 --- a/internal/entclient/error.go +++ b/internal/entclient/error.go @@ -8,7 +8,7 @@ import ( ) func bodyError(resp *http.Response) error { - byt, err := httputil.DumpResponse(resp, false) + byt, err := httputil.DumpResponse(resp, true) if err != nil { return xerrors.Errorf("dump response: %w", err) } From c71c3c9f5c645e97241d8a6ff953ea2db569f2b8 Mon Sep 17 00:00:00 2001 From: Russtopia Date: Thu, 30 Jul 2020 14:17:56 -0700 Subject: [PATCH 4/6] Add urls ls subcmd; add -o [human | json] support matching users cmd --- cmd/coder/urls.go | 90 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 28 deletions(-) diff --git a/cmd/coder/urls.go b/cmd/coder/urls.go index 21596055..8ebc9e58 100644 --- a/cmd/coder/urls.go +++ b/cmd/coder/urls.go @@ -18,6 +18,26 @@ import ( type urlsCmd struct{} +func (cmd *urlsCmd) Subcommands() []cli.Command { + return []cli.Command{ + &listSubCmd{}, + &createSubCmd{}, + &delSubCmd{}, + } +} + +func (cmd urlsCmd) Spec() cli.CommandSpec { + return cli.CommandSpec{ + Name: "urls", + Usage: "[subcommand] ", + Desc: "interact with environment devurls", + } +} + +func (cmd urlsCmd) Run(fl *pflag.FlagSet) { + exitUsage(fl) +} + // DevURL is the parsed json response record for a devURL from cemanager type DevURL struct { ID string `json:"id"` @@ -57,6 +77,48 @@ func accessLevelIsValid(level string) bool { return ok } +type listSubCmd struct { + outputFmt string +} + +// Run gets the list of active devURLs from the cemanager for the +// specified environment and outputs info to stdout. +func (sub listSubCmd) Run(fl *pflag.FlagSet) { + envName := fl.Arg(0) + devURLs := urlList(envName) + + switch sub.outputFmt { + case "human": + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent) + for _, devURL := range devURLs { + fmt.Fprintf(w, "%s\t%d\t%s\n", devURL.URL, devURL.Port, devURL.Access) + } + err := w.Flush() + if err != nil { + flog.Fatal("failed to flush writer: %v", err) + } + case "json": + err := json.NewEncoder(os.Stdout).Encode(devURLs) + if err != nil { + flog.Fatal("failed to encode devurls to json: %v", err) + } + default: + exitUsage(fl) + } +} + +func (sub *listSubCmd) RegisterFlags(fl *pflag.FlagSet) { + fl.StringVarP(&sub.outputFmt, "output", "o", "human", "output format (human | json)") +} + +func (sub *listSubCmd) Spec() cli.CommandSpec { + return cli.CommandSpec{ + Name: "ls", + Usage: " ", + Desc: "list all devurls", + } +} + type createSubCmd struct { access string urlname string @@ -181,14 +243,6 @@ func (sub delSubCmd) Run(fl *pflag.FlagSet) { } } -func (cmd urlsCmd) Spec() cli.CommandSpec { - return cli.CommandSpec{ - Name: "urls", - Usage: "", - Desc: "get all development urls for external access", - } -} - // urlList returns the list of active devURLs from the cemanager. func urlList(envName string) []DevURL { entClient := requireAuth() @@ -217,23 +271,3 @@ func urlList(envName string) []DevURL { return devURLs } - -// Run gets the list of active devURLs from the cemanager for the -// specified environment and outputs info to stdout. -func (cmd urlsCmd) Run(fl *pflag.FlagSet) { - envName := fl.Arg(0) - devURLs := urlList(envName) - - w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent) - for _, devURL := range devURLs { - fmt.Fprintf(w, "%s\t%d\t%s\n", devURL.URL, devURL.Port, devURL.Access) - } - w.Flush() -} - -func (cmd *urlsCmd) Subcommands() []cli.Command { - return []cli.Command{ - &createSubCmd{}, - &delSubCmd{}, - } -} From cc55a4aa905c50facd2ecdf777c47194f1c640e0 Mon Sep 17 00:00:00 2001 From: Russtopia Date: Thu, 30 Jul 2020 15:04:07 -0700 Subject: [PATCH 5/6] Add future support for integration devurl tests --- ci/integration/devurls_test.go | 94 ++++++++++++++++++++++++++++++++++ cmd/coder/urls.go | 1 + internal/entclient/devurl.go | 16 +++--- 3 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 ci/integration/devurls_test.go diff --git a/ci/integration/devurls_test.go b/ci/integration/devurls_test.go new file mode 100644 index 00000000..558a305d --- /dev/null +++ b/ci/integration/devurls_test.go @@ -0,0 +1,94 @@ +package integration + +import ( + "context" + "testing" + "time" + + "cdr.dev/coder-cli/ci/tcli" + "cdr.dev/slog/sloggers/slogtest/assert" +) + +func TestDevURLCLI(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) + defer cancel() + + c, err := tcli.NewContainerRunner(ctx, &tcli.ContainerConfig{ + Image: "codercom/enterprise-dev", + Name: "coder-cli-devurl-tests", + BindMounts: map[string]string{ + binpath: "/bin/coder", + }, + }) + assert.Success(t, "new run container", err) + defer c.Close() + + c.Run(ctx, "which coder").Assert(t, + tcli.Success(), + tcli.StdoutMatches("/usr/sbin/coder"), + tcli.StderrEmpty(), + ) + + c.Run(ctx, "coder urls ls").Assert(t, + tcli.Error(), + ) + + // The following cannot be enabled nor verified until either the + // integration testing dogfood target has environments created, or + // we implement the 'env create' command for coder-cli to create our + // own here. + + // If we were to create an env ourselves ... we could test devurls something like + + // // == Login + // headlessLogin(ctx, t, c) + + // // == urls ls should fail w/o supplying an envname + // c.Run(ctx, "coder urls ls").Assert(t, + // tcli.Error(), + // ) + + // // == env creation should succeed + // c.Run(ctx, "coder envs create env1 --from image1 --cores 1 --ram 2gb --disk 10gb --nogpu").Assert(t, + // tcli.Success()) + + // // == urls ls should succeed for a newly-created environment + // var durl entclient.DevURL + // c.Run(ctx, `coder urls ls -o json`).Assert(t, + // tcli.Success(), + // jsonUnmarshals(&durl), // though if a new env, durl should be empty + // ) + + // // == devurl creation w/default PRIVATE access + // c.Run(ctx, `coder urls create env1 3000`).Assert(t, + // tcli.Success()) + + // // == devurl create w/access == AUTHED + // c.Run(ctx, `coder urls create env1 3001 --access=AUTHED`).Assert(t, + // tcli.Success()) + + // // == devurl create with name + // c.Run(ctx, `coder urls create env1 3002 --access=PUBLIC --name=foobar`).Assert(t, + // tcli.Success()) + + // // == devurl ls should return well-formed entries incl. one with AUTHED access + // c.Run(ctx, `coder urls ls env1 -o json | jq -c '.[] | select( .access == "AUTHED")'`).Assert(t, + // tcli.Success(), + // jsonUnmarshals(&durl)) + + // // == devurl ls should return well-formed entries incl. one with name 'foobar' + // c.Run(ctx, `coder urls ls env1 -o json | jq -c '.[] | select( .name == "foobar")'`).Assert(t, + // tcli.Success(), + // jsonUnmarshals(&durl)) + + // // == devurl del should function + // c.Run(ctx, `coder urls del env1 3002`).Assert(t, + // tcli.Success()) + + // // == removed devurl should no longer be there + // c.Run(ctx, `coder urls ls env1 -o json | jq -c '.[] | select( .name == "foobar")'`).Assert(t, + // tcli.Error(), + // jsonUnmarshals(&durl)) + +} diff --git a/cmd/coder/urls.go b/cmd/coder/urls.go index 8ebc9e58..7f06e9e0 100644 --- a/cmd/coder/urls.go +++ b/cmd/coder/urls.go @@ -44,6 +44,7 @@ type DevURL struct { URL string `json:"url"` Port int `json:"port"` Access string `json:"access"` + Name string `json:"name"` } var urlAccessLevel = map[string]string{ diff --git a/internal/entclient/devurl.go b/internal/entclient/devurl.go index 65febfff..b2d34025 100644 --- a/internal/entclient/devurl.go +++ b/internal/entclient/devurl.go @@ -5,6 +5,15 @@ import ( "net/http" ) +// DevURL is the parsed json response record for a devURL from cemanager +type DevURL struct { + ID string `json:"id"` + URL string `json:"url"` + Port int `json:"port"` + Access string `json:"access"` + Name string `json:"name"` +} + type delDevURLRequest struct { EnvID string `json:"environment_id"` DevURLID string `json:"url_id"` @@ -61,12 +70,7 @@ func (c Client) InsertDevURL(envID string, port int, name, access string) error return nil } -type updateDevURLRequest struct { - EnvID string `json:"environment_id"` - Port int `json:"port"` - Access string `json:"access"` - Name string `json:"name"` -} +type updateDevURLRequest createDevURLRequest // UpdateDevURL updates an existing devurl for the authenticated user func (c Client) UpdateDevURL(envID, urlID string, port int, name, access string) error { From 1dc6c3ba86ee57a3036267aa0e3552c0eabebb81 Mon Sep 17 00:00:00 2001 From: Russtopia Date: Fri, 31 Jul 2020 10:51:35 -0700 Subject: [PATCH 6/6] use requireSuccess(); log.Fatal() where appropriate; change 'del' to 'rm' --- ci/integration/devurls_test.go | 4 ++-- cmd/coder/urls.go | 40 +++++++++++++--------------------- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/ci/integration/devurls_test.go b/ci/integration/devurls_test.go index 558a305d..09f1c0e4 100644 --- a/ci/integration/devurls_test.go +++ b/ci/integration/devurls_test.go @@ -82,8 +82,8 @@ func TestDevURLCLI(t *testing.T) { // tcli.Success(), // jsonUnmarshals(&durl)) - // // == devurl del should function - // c.Run(ctx, `coder urls del env1 3002`).Assert(t, + // // == devurl rm should function + // c.Run(ctx, `coder urls rm env1 3002`).Assert(t, // tcli.Success()) // // == removed devurl should no longer be there diff --git a/cmd/coder/urls.go b/cmd/coder/urls.go index 7f06e9e0..582fbdb1 100644 --- a/cmd/coder/urls.go +++ b/cmd/coder/urls.go @@ -88,6 +88,10 @@ func (sub listSubCmd) Run(fl *pflag.FlagSet) { envName := fl.Arg(0) devURLs := urlList(envName) + if len(devURLs) == 0 { + return + } + switch sub.outputFmt { case "human": w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent) @@ -95,14 +99,10 @@ func (sub listSubCmd) Run(fl *pflag.FlagSet) { fmt.Fprintf(w, "%s\t%d\t%s\n", devURL.URL, devURL.Port, devURL.Access) } err := w.Flush() - if err != nil { - flog.Fatal("failed to flush writer: %v", err) - } + requireSuccess(err, "failed to flush writer: %v", err) case "json": err := json.NewEncoder(os.Stdout).Encode(devURLs) - if err != nil { - flog.Fatal("failed to encode devurls to json: %v", err) - } + requireSuccess(err, "failed to encode devurls to json: %v", err) default: exitUsage(fl) } @@ -116,7 +116,7 @@ func (sub *listSubCmd) Spec() cli.CommandSpec { return cli.CommandSpec{ Name: "ls", Usage: " ", - Desc: "list all devurls", + Desc: "list all devurls for a given environment", } } @@ -169,7 +169,7 @@ func (sub createSubCmd) Run(fl *pflag.FlagSet) { name = sub.urlname if name != "" && !devURLNameValidRx.MatchString(name) { - flog.Error("update devurl: name must be < 64 chars in length, begin with a letter and only contain letters or digits.") + flog.Fatal("update devurl: name must be < 64 chars in length, begin with a letter and only contain letters or digits.") return } entClient := requireAuth() @@ -180,15 +180,11 @@ func (sub createSubCmd) Run(fl *pflag.FlagSet) { if found { flog.Info("Updating devurl for port %v", port) err := entClient.UpdateDevURL(env.ID, urlID, portNum, name, access) - if err != nil { - flog.Error("update devurl: %s", err.Error()) - } + requireSuccess(err, "update devurl: %s", err) } else { flog.Info("Adding devurl for port %v", port) err := entClient.InsertDevURL(env.ID, portNum, name, access) - if err != nil { - flog.Error("insert devurl: %s", err.Error()) - } + requireSuccess(err, "insert devurl: %s", err) } } @@ -196,9 +192,9 @@ type delSubCmd struct{} func (sub delSubCmd) Spec() cli.CommandSpec { return cli.CommandSpec{ - Name: "del", + Name: "rm", Usage: " ", - Desc: "delete a devurl", + Desc: "remove a devurl", } } @@ -239,9 +235,7 @@ func (sub delSubCmd) Run(fl *pflag.FlagSet) { } err = entClient.DelDevURL(env.ID, urlID) - if err != nil { - flog.Error("delete devurl: %s", err.Error()) - } + requireSuccess(err, "delete devurl: %s", err) } // urlList returns the list of active devURLs from the cemanager. @@ -253,9 +247,7 @@ func urlList(envName string) []DevURL { reqURL := fmt.Sprintf(reqString, entClient.BaseURL, env.ID, entClient.Token) resp, err := http.Get(reqURL) - if err != nil { - flog.Fatal("%v", err) - } + requireSuccess(err, "%v", err) defer resp.Body.Close() if resp.StatusCode != 200 { @@ -266,9 +258,7 @@ func urlList(envName string) []DevURL { devURLs := make([]DevURL, 0) err = dec.Decode(&devURLs) - if err != nil { - flog.Fatal("%v", err) - } + requireSuccess(err, "%v", err) return devURLs } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy