github.com/section/sectionctl@v1.12.3/commands/version.go (about) 1 package commands 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net/http" 10 "net/url" 11 "os" 12 "runtime" 13 "time" 14 15 "github.com/fatih/color" 16 hversion "github.com/hashicorp/go-version" 17 "github.com/section/sectionctl/version" 18 ) 19 20 // VersionCmd handles authenticating the CLI against Section's API 21 type VersionCmd struct { 22 in io.Reader 23 out io.Writer 24 LatestReleaseURL *url.URL `hidden default:"https://api.github.com/repos/section/sectionctl/releases/latest"` 25 Timeout time.Duration `hidden default:"5s"` 26 } 27 28 // Run executes the `login` command 29 func (c *VersionCmd) Run() (err error) { 30 reply := make(chan string, 1) 31 errs := make(chan error, 1) 32 go c.checkVersion(reply, errs) 33 34 fmt.Fprintf(c.Out(), "%s\n", c.String()) 35 36 if version.Version == "dev" { 37 return err 38 } 39 40 var v string 41 select { 42 case v = <-reply: 43 case err = <-errs: 44 return err 45 } 46 47 current, err := hversion.NewVersion(version.Version) 48 if err != nil { 49 return err 50 } 51 latest, err := hversion.NewVersion(v) 52 if err != nil { 53 return err 54 } 55 56 if current.LessThan(latest) { 57 green := color.New(color.FgGreen) 58 green.Fprintf(c.Out(), "\nA new version of sectionctl is available: %s\n", v) 59 fmt.Fprintf(c.Out(), "\nDownload at https://github.com/section/sectionctl/releases/%s\n", v) 60 } 61 62 return err 63 } 64 65 func (c *VersionCmd) checkVersion(latest chan string, errs chan error) { 66 ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) 67 defer cancel() 68 if c.LatestReleaseURL == nil { 69 errs <- fmt.Errorf("no release URL specified") 70 return 71 } 72 req, err := http.NewRequestWithContext(ctx, "GET", c.LatestReleaseURL.String(), nil) 73 if err != nil { 74 errs <- fmt.Errorf("unable to make request: %w", err) 75 return 76 } 77 ua := fmt.Sprintf("sectionctl (%s; %s-%s)", version.Version, runtime.GOARCH, runtime.GOOS) 78 req.Header.Set("User-Agent", ua) 79 80 var client http.Client 81 resp, err := client.Do(req) 82 if err != nil { 83 errs <- fmt.Errorf("unable to perform request: %w", err) 84 return 85 } 86 defer resp.Body.Close() 87 88 if resp.StatusCode != http.StatusOK { 89 errs <- fmt.Errorf("bad response from GitHub. Please try again later") 90 return 91 } 92 93 body, err := ioutil.ReadAll(resp.Body) 94 if err != nil { 95 errs <- fmt.Errorf("unable to read response: %w", err) 96 return 97 } 98 99 var latestResp struct { 100 TagName string `json:"tag_name"` 101 } 102 err = json.Unmarshal(body, &latestResp) 103 if err != nil { 104 errs <- fmt.Errorf("unable to decode response: %w", err) 105 return 106 } 107 latest <- latestResp.TagName 108 } 109 110 func (c VersionCmd) String() string { 111 return fmt.Sprintf("%s (%s-%s)", version.Version, runtime.GOOS, runtime.GOARCH) 112 } 113 114 // In returns the input to read from 115 func (c *VersionCmd) In() io.Reader { 116 if c.in != nil { 117 return c.in 118 } 119 return os.Stdin 120 } 121 122 // Out returns the output to write to 123 func (c *VersionCmd) Out() io.Writer { 124 if c.out != nil { 125 return c.out 126 } 127 return os.Stdout 128 }