From 99e5198b00d2e422e6a9183c490d665555e9a6e7 Mon Sep 17 00:00:00 2001 From: Marc O'Morain Date: Wed, 18 Jul 2018 11:50:18 +0100 Subject: [PATCH] Add an `update check` command to check for updates. --- client/client.go | 2 +- cmd/root.go | 1 + cmd/root_test.go | 2 +- cmd/update.go | 81 ++++++++++++++++++++++++++++++++++++++++++++++ cmd/version.go | 1 - version/version.go | 9 ++++++ 6 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 cmd/update.go diff --git a/client/client.go b/client/client.go index 7b29bc224..51fa866e6 100644 --- a/client/client.go +++ b/client/client.go @@ -28,7 +28,7 @@ func NewClient(endpoint string, logger *logger.Logger) *graphql.Client { func NewAuthorizedRequest(token, query string) *graphql.Request { req := graphql.NewRequest(query) req.Header.Set("Authorization", token) - req.Header.Set("User-Agent", fmt.Sprintf("circleci-cli/%s-%s", version.Version, version.Commit)) + req.Header.Set("User-Agent", version.UserAgent()) return req } diff --git a/cmd/root.go b/cmd/root.go index 4d1ba8372..77e40192c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -48,6 +48,7 @@ func MakeCommands() *cobra.Command { rootCmd.AddCommand(newOrbCommand()) rootCmd.AddCommand(newBuildCommand()) rootCmd.AddCommand(newVersionCommand()) + rootCmd.AddCommand(newUpdateCommand()) rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Enable verbose logging.") rootCmd.PersistentFlags().StringP("endpoint", "e", defaultEndpoint, "the endpoint of your CircleCI GraphQL API") rootCmd.PersistentFlags().StringP("token", "t", "", "your token for using CircleCI") diff --git a/cmd/root_test.go b/cmd/root_test.go index 27d3bf0e3..52af4e799 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -16,7 +16,7 @@ var _ = Describe("Root", func() { It("can create commands", func() { commands := cmd.MakeCommands() - Expect(len(commands.Commands())).To(Equal(8)) + Expect(len(commands.Commands())).To(Equal(9)) }) }) diff --git a/cmd/update.go b/cmd/update.go new file mode 100644 index 000000000..78b1d20c4 --- /dev/null +++ b/cmd/update.go @@ -0,0 +1,81 @@ +package cmd + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "unicode/utf8" + + "github.com/CircleCI-Public/circleci-cli/version" + "github.com/spf13/cobra" +) + +func newUpdateCommand() *cobra.Command { + update := &cobra.Command{ + Use: "update", + Short: "Update the tool", + } + + update.AddCommand(&cobra.Command{ + Use: "check", + Short: "Check if there are any updates available", + RunE: checkForUpdates, + }) + + return update +} + +func trimFirstRune(s string) string { + _, i := utf8.DecodeRuneInString(s) + return s[i:] +} + +func checkForUpdates(cmd *cobra.Command, args []string) error { + + url := "https://api.github.com/repos/CircleCI-Public/circleci-cli/releases/latest" + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return err + } + + req.Header.Set("User-Agent", version.UserAgent()) + + client := http.Client{} + res, err := client.Do(req) + if err != nil { + return err + } + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + + var release struct { + // There are other fields in this response that we could use to download the + // binaries on behalf of the user. + // https://developer.github.com/v3/repos/releases/#get-the-latest-release + HTML string `json:"html_url"` + Tag string `json:"tag_name"` + } + + if err := json.Unmarshal(body, &release); err != nil { + return err + } + + latest := trimFirstRune(release.Tag) + + Logger.Debug("Latest version: %s", latest) + Logger.Debug("Current Version: %s", version.Version) + + if latest == version.Version { + Logger.Info("Already up-to-date.") + } else { + Logger.Infof("A new release is available (%s)", release.Tag) + Logger.Infof("You are running %s", version.Version) + Logger.Infof("You can download it from %s", release.HTML) + } + + return nil +} diff --git a/cmd/version.go b/cmd/version.go index 544372372..1dc62bbc7 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -13,5 +13,4 @@ func newVersionCommand() *cobra.Command { Logger.Infof("%s (%s)", version.Version, version.Commit) }, } - } diff --git a/version/version.go b/version/version.go index ba3bfd01e..a77b2c193 100644 --- a/version/version.go +++ b/version/version.go @@ -1,5 +1,9 @@ package version +import ( + "fmt" +) + // These vars set by `goreleaser`: var ( // Version is the current Git tag (the v prefix is stripped) or the name of the snapshot, if you’re using the --snapshot flag @@ -7,3 +11,8 @@ var ( // Commit is the current git commit SHA Commit = "dirty-local-tree" ) + +// UserAgent returns the user agent that should be user for external requests +func UserAgent() string { + return fmt.Sprintf("circleci-cli/%s-%s", Version, Commit) +}