cli: add version flag in update-cli command (#3996)

Co-authored-by: Shahidh K Muhammed <muhammedshahid.k@gmail.com>
This commit is contained in:
Shahidh K Muhammed 2020-03-05 12:25:33 +05:30 committed by GitHub
parent cb75660b3e
commit d7d53f222c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 74 additions and 40 deletions

View File

@ -5,6 +5,8 @@ import (
"os" "os"
"strings" "strings"
"github.com/Masterminds/semver"
"github.com/hasura/graphql-engine/cli" "github.com/hasura/graphql-engine/cli"
"github.com/hasura/graphql-engine/cli/update" "github.com/hasura/graphql-engine/cli/update"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -20,6 +22,9 @@ const updateCLICmdExample = ` # Update CLI to latest version:
# To disable auto-update check on the CLI, set # To disable auto-update check on the CLI, set
# "show_update_notification": false # "show_update_notification": false
# in ~/.hasura/config.json # 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. // NewUpdateCLICmd returns the update-cli command.
@ -29,7 +34,7 @@ func NewUpdateCLICmd(ec *cli.ExecutionContext) *cobra.Command {
} }
updateCmd := &cobra.Command{ updateCmd := &cobra.Command{
Use: updateCLICmdUse, Use: updateCLICmdUse,
Short: "Update the CLI to latest version", Short: "Update the CLI to latest or a specific version",
SilenceUsage: true, SilenceUsage: true,
PreRunE: func(cmd *cobra.Command, args []string) error { PreRunE: func(cmd *cobra.Command, args []string) error {
return ec.Prepare() return ec.Prepare()
@ -39,43 +44,77 @@ func NewUpdateCLICmd(ec *cli.ExecutionContext) *cobra.Command {
}, },
Example: updateCLICmdExample, Example: updateCLICmdExample,
} }
f := updateCmd.Flags()
f.StringVar(&opts.version, "version", "", "a specific version to install")
return updateCmd return updateCmd
} }
type updateOptions struct { type updateOptions struct {
EC *cli.ExecutionContext 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 currentVersion := o.EC.Version.CLISemver
if currentVersion == nil { if currentVersion == nil {
return errors.Errorf("cannot update from a non-semver version: %s", o.EC.Version.GetCLIVersion()) return errors.Errorf("cannot update from a non-semver version: %s", o.EC.Version.GetCLIVersion())
} }
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... ") o.EC.Spin("Checking for update... ")
hasUpdate, latestVersion, err := update.HasUpdate(currentVersion, o.EC.LastUpdateCheckFile) hasUpdate, latestVersion, hasPreReleaseUpdate, preReleaseVersion, err := update.HasUpdate(currentVersion, o.EC.LastUpdateCheckFile)
o.EC.Spinner.Stop() o.EC.Spinner.Stop()
if err != nil { if err != nil {
return errors.Wrap(err, "command: check update") return errors.Wrap(err, "command: check update")
} }
ec.Logger.Debugln("hasUpdate: ", hasUpdate, "latestVersion: ", latestVersion, "currentVersion:", currentVersion) ec.Logger.Debugln("hasUpdate: ", hasUpdate, "latestVersion: ", latestVersion, "hasPreReleaseUpdate: ", hasPreReleaseUpdate, "preReleaseVersion: ", preReleaseVersion, "currentVersion:", currentVersion)
if !hasUpdate {
o.EC.Logger.WithField("version", currentVersion).Info("hasura cli is up to date")
return nil
}
if showPrompt { if showPrompt {
switch {
case hasUpdate:
ok := ask2confirm(latestVersion.String(), o.EC.Logger) ok := ask2confirm(latestVersion.String(), o.EC.Logger)
if !ok { if !ok {
o.EC.Logger.Info("skipping update, run 'hasura update-cli' to update manually") o.EC.Logger.Info("skipping update, run 'hasura update-cli' to update manually")
return nil 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
}
}
} }
o.EC.Spin(fmt.Sprintf("Updating cli to v%s... ", latestVersion.String())) if versionToBeInstalled == nil {
err = update.ApplyUpdate(latestVersion) o.EC.Logger.WithField("version", currentVersion).Info("hasura cli is up to date")
return nil
}
ec.Logger.Debugln("versionToBeInstalled: ", versionToBeInstalled.String())
o.EC.Spin(fmt.Sprintf("Updating cli to v%s... ", versionToBeInstalled.String()))
err = update.ApplyUpdate(versionToBeInstalled)
o.EC.Spinner.Stop() o.EC.Spinner.Stop()
if err != nil { if err != nil {
if os.IsPermission(err) { if os.IsPermission(err) {
@ -84,7 +123,7 @@ func (o *updateOptions) run(showPrompt bool) error {
return errors.Wrap(err, "apply update") 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 return nil
} }
@ -106,3 +145,7 @@ func ask2confirm(v string, log *logrus.Logger) bool {
} }
return false return false
} }
func getChangeLogLink(version *semver.Version) string {
return fmt.Sprintf("https://github.com/hasura/graphql-engine/releases/tag/%s", version.Original())
}

View File

@ -22,28 +22,24 @@ import (
const updateCheckURL = "https://releases.hasura.io/graphql-engine?agent=cli" const updateCheckURL = "https://releases.hasura.io/graphql-engine?agent=cli"
type updateCheckResponse struct { 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) res, err := http.Get(updateCheckURL)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "update check request") return nil, nil, errors.Wrap(err, "update check request")
} }
defer res.Body.Close() defer res.Body.Close()
var response updateCheckResponse var response updateCheckResponse
err = json.NewDecoder(res.Body).Decode(&response) err = json.NewDecoder(res.Body).Decode(&response)
if err != nil { 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) return response.Latest, response.PreRelease, nil
if err != nil {
return nil, errors.Wrap(err, "semver parsing")
}
return v, nil
} }
func buildAssetURL(v string) string { func buildAssetURL(v string) string {
@ -88,23 +84,18 @@ func downloadAsset(url, fileName, filePath string) (*os.File, error) {
return asset, nil return asset, nil
} }
// HasUpdate tells us if there is a new update available. // HasUpdate tells us if there is a new stable or prerelease update available.
func HasUpdate(currentVersion *semver.Version, timeFile string) (bool, *semver.Version, error) { func HasUpdate(currentVersion *semver.Version, timeFile string) (bool, *semver.Version, bool, *semver.Version, error) {
if timeFile != "" { if timeFile != "" {
defer writeTimeToFile(timeFile, time.Now().UTC()) defer writeTimeToFile(timeFile, time.Now().UTC())
} }
latestVersion, err := getLatestVersion() latestVersion, preReleaseVersion, err := getLatestVersion()
if err != nil { 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())) return latestVersion.GreaterThan(currentVersion), latestVersion, preReleaseVersion.GreaterThan(currentVersion), preReleaseVersion, nil
if err != nil {
return false, nil, errors.Wrap(err, "semver constraint build")
}
return c.Check(latestVersion), latestVersion, nil
} }
// ApplyUpdate downloads and applies the update indicated by version v. // ApplyUpdate downloads and applies the update indicated by version v.