From d7d53f222cac6f3f9396162493cef1413ec57ea3 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Thu, 5 Mar 2020 12:25:33 +0530 Subject: [PATCH] cli: add version flag in update-cli command (#3996) Co-authored-by: Shahidh K Muhammed --- cli/commands/update-cli.go | 83 +++++++++++++++++++++++++++++--------- cli/update/update.go | 31 +++++--------- 2 files changed, 74 insertions(+), 40 deletions(-) diff --git a/cli/commands/update-cli.go b/cli/commands/update-cli.go index 2c0d28f2a4e..a6145547f2a 100644 --- a/cli/commands/update-cli.go +++ b/cli/commands/update-cli.go @@ -5,6 +5,8 @@ import ( "os" "strings" + "github.com/Masterminds/semver" + "github.com/hasura/graphql-engine/cli" "github.com/hasura/graphql-engine/cli/update" "github.com/pkg/errors" @@ -20,6 +22,9 @@ const updateCLICmdExample = ` # Update CLI to latest version: # To disable auto-update check on the CLI, set # "show_update_notification": false # in ~/.hasura/config.json + + # Update CLI to a specific version (say v1.2.0-beta.1): + hasura update-cli --version v1.2.0-beta.1 ` // NewUpdateCLICmd returns the update-cli command. @@ -29,7 +34,7 @@ func NewUpdateCLICmd(ec *cli.ExecutionContext) *cobra.Command { } updateCmd := &cobra.Command{ Use: updateCLICmdUse, - Short: "Update the CLI to latest version", + Short: "Update the CLI to latest or a specific version", SilenceUsage: true, PreRunE: func(cmd *cobra.Command, args []string) error { return ec.Prepare() @@ -39,43 +44,77 @@ func NewUpdateCLICmd(ec *cli.ExecutionContext) *cobra.Command { }, Example: updateCLICmdExample, } + + f := updateCmd.Flags() + f.StringVar(&opts.version, "version", "", "a specific version to install") + return updateCmd } type updateOptions struct { EC *cli.ExecutionContext + + version string } -func (o *updateOptions) run(showPrompt bool) error { +func (o *updateOptions) run(showPrompt bool) (err error) { currentVersion := o.EC.Version.CLISemver if currentVersion == nil { return errors.Errorf("cannot update from a non-semver version: %s", o.EC.Version.GetCLIVersion()) } - o.EC.Spin("Checking for update... ") - hasUpdate, latestVersion, err := update.HasUpdate(currentVersion, o.EC.LastUpdateCheckFile) - o.EC.Spinner.Stop() - if err != nil { - return errors.Wrap(err, "command: check update") + var versionToBeInstalled *semver.Version + if o.version != "" { + // parse the version + versionToBeInstalled, err = semver.NewVersion(o.version) + if err != nil { + return errors.Wrap(err, "unable to parse version") + } + } else { + o.EC.Spin("Checking for update... ") + hasUpdate, latestVersion, hasPreReleaseUpdate, preReleaseVersion, err := update.HasUpdate(currentVersion, o.EC.LastUpdateCheckFile) + o.EC.Spinner.Stop() + if err != nil { + return errors.Wrap(err, "command: check update") + } + + ec.Logger.Debugln("hasUpdate: ", hasUpdate, "latestVersion: ", latestVersion, "hasPreReleaseUpdate: ", hasPreReleaseUpdate, "preReleaseVersion: ", preReleaseVersion, "currentVersion:", currentVersion) + + if showPrompt { + switch { + case hasUpdate: + ok := ask2confirm(latestVersion.String(), o.EC.Logger) + if !ok { + o.EC.Logger.Info("skipping update, run 'hasura update-cli' to update manually") + return nil + } + versionToBeInstalled = latestVersion + case hasPreReleaseUpdate: + o.EC.Logger.Infof(`a new pre-release version is available: +- %s (changelog: %s) +to update cli to this version, execute: + + hasura update-cli --version %s + +`, preReleaseVersion.Original(), getChangeLogLink(preReleaseVersion), preReleaseVersion.Original()) + return nil + } + } else { + if hasUpdate { + versionToBeInstalled = latestVersion + } + } } - ec.Logger.Debugln("hasUpdate: ", hasUpdate, "latestVersion: ", latestVersion, "currentVersion:", currentVersion) - - if !hasUpdate { + if versionToBeInstalled == nil { o.EC.Logger.WithField("version", currentVersion).Info("hasura cli is up to date") return nil } - if showPrompt { - ok := ask2confirm(latestVersion.String(), o.EC.Logger) - if !ok { - o.EC.Logger.Info("skipping update, run 'hasura update-cli' to update manually") - return nil - } - } + ec.Logger.Debugln("versionToBeInstalled: ", versionToBeInstalled.String()) - o.EC.Spin(fmt.Sprintf("Updating cli to v%s... ", latestVersion.String())) - err = update.ApplyUpdate(latestVersion) + o.EC.Spin(fmt.Sprintf("Updating cli to v%s... ", versionToBeInstalled.String())) + err = update.ApplyUpdate(versionToBeInstalled) o.EC.Spinner.Stop() if err != nil { if os.IsPermission(err) { @@ -84,7 +123,7 @@ func (o *updateOptions) run(showPrompt bool) error { return errors.Wrap(err, "apply update") } - o.EC.Logger.WithField("version", "v"+latestVersion.String()).Info("Updated to latest version") + o.EC.Logger.WithField("version", "v"+versionToBeInstalled.String()).Info("Updated to latest version") return nil } @@ -106,3 +145,7 @@ func ask2confirm(v string, log *logrus.Logger) bool { } return false } + +func getChangeLogLink(version *semver.Version) string { + return fmt.Sprintf("https://github.com/hasura/graphql-engine/releases/tag/%s", version.Original()) +} diff --git a/cli/update/update.go b/cli/update/update.go index f047a955216..1bc86df61ea 100644 --- a/cli/update/update.go +++ b/cli/update/update.go @@ -22,28 +22,24 @@ import ( const updateCheckURL = "https://releases.hasura.io/graphql-engine?agent=cli" type updateCheckResponse struct { - Latest string `json:"latest"` + Latest *semver.Version `json:"latest"` + PreRelease *semver.Version `json:"prerelease"` } -func getLatestVersion() (*semver.Version, error) { +func getLatestVersion() (*semver.Version, *semver.Version, error) { res, err := http.Get(updateCheckURL) if err != nil { - return nil, errors.Wrap(err, "update check request") + return nil, nil, errors.Wrap(err, "update check request") } defer res.Body.Close() var response updateCheckResponse err = json.NewDecoder(res.Body).Decode(&response) if err != nil { - return nil, errors.Wrap(err, "decoding update check response") + return nil, nil, errors.Wrap(err, "decoding update check response") } - v, err := semver.NewVersion(response.Latest) - if err != nil { - return nil, errors.Wrap(err, "semver parsing") - } - - return v, nil + return response.Latest, response.PreRelease, nil } func buildAssetURL(v string) string { @@ -88,23 +84,18 @@ func downloadAsset(url, fileName, filePath string) (*os.File, error) { return asset, nil } -// HasUpdate tells us if there is a new update available. -func HasUpdate(currentVersion *semver.Version, timeFile string) (bool, *semver.Version, error) { +// HasUpdate tells us if there is a new stable or prerelease update available. +func HasUpdate(currentVersion *semver.Version, timeFile string) (bool, *semver.Version, bool, *semver.Version, error) { if timeFile != "" { defer writeTimeToFile(timeFile, time.Now().UTC()) } - latestVersion, err := getLatestVersion() + latestVersion, preReleaseVersion, err := getLatestVersion() if err != nil { - return false, nil, errors.Wrap(err, "get latest version") + return false, nil, false, nil, errors.Wrap(err, "get latest version") } - c, err := semver.NewConstraint(fmt.Sprintf("> %s", currentVersion.String())) - if err != nil { - return false, nil, errors.Wrap(err, "semver constraint build") - } - - return c.Check(latestVersion), latestVersion, nil + return latestVersion.GreaterThan(currentVersion), latestVersion, preReleaseVersion.GreaterThan(currentVersion), preReleaseVersion, nil } // ApplyUpdate downloads and applies the update indicated by version v.