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  }