From c4113479e1d62baf9d9d9b083b927425d87a59ff Mon Sep 17 00:00:00 2001 From: Pavan Kumar Date: Sun, 16 Aug 2015 17:21:04 +0000 Subject: [PATCH] check if version is supported 1) if a cli version was bad, we'll blacklist it at parse and such cli's should not be used to execute anything 2) instead of forced update, we now print a message asking to update everytime a new release is made 3) use git releases to download the new binary 4) also support linux_arm now --- main.go | 46 ++++++---------------------------- update_cmd.go | 62 ++++++++++++++++++++++++++-------------------- update_cmd_test.go | 39 ++++++++--------------------- utils.go | 19 ++++++++++++++ utils_test.go | 49 ++++++++++++++++++++++++++++++++++++ 5 files changed, 122 insertions(+), 93 deletions(-) diff --git a/main.go b/main.go index 47c3973..6cb4b46 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "io" "net/url" "os" - "os/exec" "os/signal" "path/filepath" "runtime" @@ -26,10 +25,7 @@ const ( defaultBaseURL = "https://api.parse.com/1/" ) -var ( - autoUpdate = false - userAgent = fmt.Sprintf("parse-cli-%s-%s", runtime.GOOS, version) -) +var userAgent = fmt.Sprintf("parse-cli-%s-%s", runtime.GOOS, version) type versionCmd struct{} @@ -377,39 +373,13 @@ func main() { } e.Client = client - // autoUpdate is false for all non-production builds - // so we never auto update - // for production builds autoUpdate is true but - // we suppress auto-update iff PARSE_NOUPDATE is not set - if autoUpdate && os.Getenv("PARSE_NOUPDATE") == "" { - // Perform a best effort update - updated, err := (&updateCmd{}).updateCLI(&e) - if err != nil { - cmd := exec.Command(os.Args[0], "version") - if err := cmd.Run(); err != nil { - fmt.Fprintf(e.Out, `parse cli corrupted during update. -Please follow instructions at: - https://parse.com/apps/quickstart#cloud_code -to install a new cli.`) - os.Exit(1) - } - } - - if updated { - // Re-run the command with the updated CLI - cmd := exec.Cmd{ - Path: os.Args[0], - Args: os.Args, - Stdin: os.Stdin, - Stdout: os.Stdout, - Stderr: os.Stderr, - } - - if err := cmd.Run(); err != nil { - os.Exit(1) - } - return - } + message, err := checkIfSupported(&e, version) + if err != nil { + fmt.Fprintln(e.Err, err) + os.Exit(1) + } + if message != "" { + fmt.Fprintln(e.Err, message) } if err := rootCmd(&e).Execute(); err != nil { diff --git a/update_cmd.go b/update_cmd.go index fbfd3b2..efce3d5 100644 --- a/update_cmd.go +++ b/update_cmd.go @@ -4,9 +4,7 @@ import ( "fmt" "net/http" "net/url" - "path" "runtime" - "strings" "github.com/facebookgo/stackerr" "github.com/inconshreveable/go-update" @@ -15,50 +13,60 @@ import ( ) const ( - macCliDownloadURL = "https://parse.com/downloads/cloud_code/cli/parse-osx/latest" - unixCliDownloadURL = "https://parse.com/downloads/cloud_code/cli/parse-linux/latest" - windowsCliDownloadURL = "https://parse.com/downloads/cloud_code/cli/parse-windows/latest" + macDownload = "parse" + windowsDownload = "parse.exe" + linuxDownload = "parse_linux" + linuxArmDownload = "parse_linux_arm" + downloadURLFormat = "https://github.com/ParsePlatform/parse-cli/releases/download/release_%s/%s" ) type updateCmd struct{} -func (u *updateCmd) latestVersion(e *env, downloadURL string) (string, error) { - dURL, err := url.Parse(downloadURL) - if err != nil { - return "", stackerr.Wrap(err) +func (u *updateCmd) latestVersion(e *env) (string, error) { + v := make(url.Values) + v.Set("version", "latest") + req := &http.Request{ + Method: "GET", + URL: &url.URL{Path: "supported", RawQuery: v.Encode()}, } - resp, err := e.Client.Do(&http.Request{Method: "HEAD", URL: dURL}, nil, nil) - if err != nil { - return "", nil // if unable to fetch latest cli version, do not abort! + var res struct { + Version string `json:"version"` } - base := path.Base(resp.Header.Get("Location")) - base = strings.TrimSuffix(base, ".exe") - //parse-os-2.0.2 - splits := strings.Split(base, "-") + if _, err := e.Client.Do(req, nil, &res); err != nil { + return "", stackerr.Wrap(err) + } - return splits[len(splits)-1], nil + return res.Version, nil } func (u *updateCmd) updateCLI(e *env) (bool, error) { - downloadURL := unixCliDownloadURL - switch runtime.GOOS { - case "windows": - downloadURL = windowsCliDownloadURL - case "darwin": - downloadURL = macCliDownloadURL - } + ostype := runtime.GOOS + arch := runtime.GOARCH - latestVersion, err := u.latestVersion(e, downloadURL) + latestVersion, err := u.latestVersion(e) if err != nil { - return false, stackerr.Wrap(err) + return false, err } - if latestVersion == "" || latestVersion == version { return false, nil } + var downloadURL string + switch ostype { + case "darwin": + downloadURL = fmt.Sprintf(downloadURLFormat, latestVersion, macDownload) + case "windows": + downloadURL = fmt.Sprintf(downloadURLFormat, latestVersion, windowsDownload) + case "linux": + if arch == "arm" { + downloadURL = fmt.Sprintf(downloadURLFormat, latestVersion, linuxArmDownload) + } else { + downloadURL = fmt.Sprintf(downloadURLFormat, latestVersion, linuxDownload) + } + } + exec, err := osext.Executable() if err != nil { return false, stackerr.Wrap(err) diff --git a/update_cmd_test.go b/update_cmd_test.go index 2ba0945..77247a0 100644 --- a/update_cmd_test.go +++ b/update_cmd_test.go @@ -3,10 +3,10 @@ package main import ( "io/ioutil" "net/http" - "strings" "testing" "github.com/facebookgo/ensure" + "github.com/facebookgo/jsonpipe" "github.com/facebookgo/parse" ) @@ -17,37 +17,20 @@ func TestLatestVersion(t *testing.T) { defer h.Stop() ht := transportFunc(func(r *http.Request) (*http.Response, error) { - var result string - switch r.URL.String() { - case windowsCliDownloadURL: - result = "http://parse-cli.aws.com/hash/parse-windows-2.0.2.exe" - case unixCliDownloadURL: - result = "http://parse-cli.aws.com/hash/parse-linux-2.0.2" - case macCliDownloadURL: - result = "http://parse-cli.aws.com/hash/parse-osx-2.0.2" - } - resp := http.Response{ + ensure.DeepEqual(t, r.URL.Path, "/1/supported") + return &http.Response{ StatusCode: http.StatusOK, - Body: ioutil.NopCloser(strings.NewReader("Success!")), - } - if resp.Header == nil { - resp.Header = make(http.Header) - } - resp.Header.Set("Location", result) - return &resp, nil + Body: ioutil.NopCloser( + jsonpipe.Encode( + map[string]string{"version": "2.0.2"}, + ), + ), + }, nil }) h.env.Client = &Client{client: &parse.Client{Transport: ht}} u := new(updateCmd) - version, err := u.latestVersion(h.env, unixCliDownloadURL) + latestVersion, err := u.latestVersion(h.env) ensure.Nil(t, err) - ensure.DeepEqual(t, version, "2.0.2") - - version, err = u.latestVersion(h.env, macCliDownloadURL) - ensure.Nil(t, err) - ensure.DeepEqual(t, version, "2.0.2") - - version, err = u.latestVersion(h.env, windowsCliDownloadURL) - ensure.Nil(t, err) - ensure.DeepEqual(t, version, "2.0.2") + ensure.DeepEqual(t, latestVersion, "2.0.2") } diff --git a/utils.go b/utils.go index 05a0cdb..15028e6 100644 --- a/utils.go +++ b/utils.go @@ -3,6 +3,7 @@ package main import ( "errors" "fmt" + "net/http" "net/url" "regexp" "strconv" @@ -104,3 +105,21 @@ func getHostFromURL(urlStr string) (string, error) { } return server, nil } + +func checkIfSupported(e *env, version string) (string, error) { + v := make(url.Values) + v.Set("version", version) + req := &http.Request{ + Method: "GET", + URL: &url.URL{Path: "supported", RawQuery: v.Encode()}, + } + + var res struct { + Warning string `json:"warning"` + } + + if _, err := e.Client.Do(req, nil, &res); err != nil { + return "", stackerr.Wrap(err) + } + return res.Warning, nil +} diff --git a/utils_test.go b/utils_test.go index 174c99d..af389b1 100644 --- a/utils_test.go +++ b/utils_test.go @@ -2,10 +2,14 @@ package main import ( "crypto/rand" + "io/ioutil" + "net/http" "regexp" "testing" "github.com/facebookgo/ensure" + "github.com/facebookgo/jsonpipe" + "github.com/facebookgo/parse" ) func TestBothEmpty(t *testing.T) { @@ -83,3 +87,48 @@ func TestGetHostFromURL(t *testing.T) { host, err = getHostFromURL(urlStr) ensure.Err(t, err, regexp.MustCompile("not a valid url")) } + +func TestIsSupportedWarning(t *testing.T) { + t.Parallel() + + h := newHarness(t) + defer h.Stop() + + ht := transportFunc(func(r *http.Request) (*http.Response, error) { + ensure.DeepEqual(t, r.URL.Path, "/1/supported") + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser( + jsonpipe.Encode( + map[string]string{"warning": "please update"}, + ), + ), + }, nil + }) + h.env.Client = &Client{client: &parse.Client{Transport: ht}} + message, err := checkIfSupported(h.env, "2.0.2") + ensure.Nil(t, err) + ensure.DeepEqual(t, message, "please update") +} + +func TestIsSupportedError(t *testing.T) { + t.Parallel() + + h := newHarness(t) + defer h.Stop() + + ht := transportFunc(func(r *http.Request) (*http.Response, error) { + ensure.DeepEqual(t, r.URL.Path, "/1/supported") + return &http.Response{ + StatusCode: http.StatusBadRequest, + Body: ioutil.NopCloser( + jsonpipe.Encode( + map[string]string{"error": "not supported"}, + ), + ), + }, nil + }) + h.env.Client = &Client{client: &parse.Client{Transport: ht}} + _, err := checkIfSupported(h.env, "2.0.2") + ensure.Err(t, err, regexp.MustCompile("not supported")) +}