2020-08-06 20:58:47 +03:00
|
|
|
package cli
|
|
|
|
|
|
|
|
import (
|
2022-12-30 20:10:56 +03:00
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
"time"
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2022-12-30 20:10:56 +03:00
|
|
|
"github.com/neilotoole/sq/libsq/core/errz"
|
2020-08-06 20:58:47 +03:00
|
|
|
"github.com/spf13/cobra"
|
2022-12-30 20:10:56 +03:00
|
|
|
"golang.org/x/mod/semver"
|
2020-08-06 20:58:47 +03:00
|
|
|
|
|
|
|
"github.com/neilotoole/sq/cli/buildinfo"
|
|
|
|
)
|
|
|
|
|
2021-02-22 10:37:00 +03:00
|
|
|
func newVersionCmd() *cobra.Command {
|
2020-08-06 20:58:47 +03:00
|
|
|
cmd := &cobra.Command{
|
|
|
|
Use: "version",
|
|
|
|
Short: "Print sq version",
|
2021-02-22 10:37:00 +03:00
|
|
|
RunE: execVersion,
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2022-12-30 20:10:56 +03:00
|
|
|
cmd.Flags().BoolP(flagJSON, flagJSONShort, false, flagJSONUsage)
|
|
|
|
|
2021-02-22 10:37:00 +03:00
|
|
|
return cmd
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2021-02-22 10:37:00 +03:00
|
|
|
func execVersion(cmd *cobra.Command, args []string) error {
|
|
|
|
rc := RunContextFrom(cmd.Context())
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2022-12-30 20:10:56 +03:00
|
|
|
// We'd like to display that there's an update available, but
|
|
|
|
// we don't want to wait around long for that.
|
|
|
|
// So, we swallow (but log) any error from the goroutine.
|
|
|
|
ctx, cancelFn := context.WithTimeout(cmd.Context(), time.Second*2)
|
|
|
|
defer cancelFn()
|
|
|
|
|
|
|
|
resultCh := make(chan string)
|
|
|
|
go func() {
|
|
|
|
var err error
|
|
|
|
v, err := fetchBrewVersion(ctx)
|
|
|
|
if err != nil {
|
|
|
|
rc.Log.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// OK if v is empty
|
|
|
|
resultCh <- v
|
|
|
|
}()
|
|
|
|
|
|
|
|
var latestVersion string
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
case latestVersion = <-resultCh:
|
|
|
|
if latestVersion != "" && !strings.HasPrefix(latestVersion, "v") {
|
|
|
|
latestVersion = "v" + latestVersion
|
2022-12-30 20:25:45 +03:00
|
|
|
if !semver.IsValid(latestVersion) {
|
|
|
|
return errz.Errorf("invalid semver from brew repo: {%s}", latestVersion)
|
|
|
|
}
|
2022-12-30 20:10:56 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc.writers.versionw.Version(buildinfo.Info(), latestVersion)
|
|
|
|
}
|
|
|
|
|
|
|
|
func fetchBrewVersion(ctx context.Context) (string, error) {
|
|
|
|
const u = `https://raw.githubusercontent.com/neilotoole/homebrew-sq/master/sq.rb`
|
|
|
|
|
2022-12-30 20:28:01 +03:00
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, http.NoBody)
|
2022-12-30 20:10:56 +03:00
|
|
|
if err != nil {
|
|
|
|
return "", errz.Err(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return "", errz.Wrap(err, "failed to check edgectl brew repo")
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
2022-12-30 20:28:01 +03:00
|
|
|
return "", errz.Errorf("failed to check edgectl brew repo: %d %s",
|
|
|
|
resp.StatusCode, http.StatusText(resp.StatusCode))
|
2022-12-30 20:10:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return "", errz.Wrap(err, "failed to read edgectl brew repo body")
|
|
|
|
}
|
|
|
|
|
|
|
|
return getVersionFromBrewFormula(body)
|
|
|
|
}
|
|
|
|
|
|
|
|
// getVersionFromBrewFormula returns the first brew version
|
|
|
|
// from f, which is a brew ruby formula. The version is returned
|
|
|
|
// without a "v" prefix, e.g. "0.1.2", not "v0.1.2".
|
|
|
|
func getVersionFromBrewFormula(f []byte) (string, error) {
|
|
|
|
var (
|
|
|
|
line string
|
|
|
|
val string
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
sc := bufio.NewScanner(bytes.NewReader(f))
|
|
|
|
for sc.Scan() {
|
|
|
|
line = sc.Text()
|
|
|
|
if err = sc.Err(); err != nil {
|
|
|
|
return "", errz.Err(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
val = strings.TrimSpace(line)
|
|
|
|
if strings.HasPrefix(val, `version "`) {
|
|
|
|
// found it
|
|
|
|
val = val[9:]
|
|
|
|
val = strings.TrimSuffix(val, `"`)
|
|
|
|
if !semver.IsValid("v" + val) { // semver pkg requires "v" prefix
|
|
|
|
return "", errz.Errorf("invalid brew formula: invalid semver")
|
|
|
|
}
|
|
|
|
return val, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.HasPrefix(line, "bottle") {
|
|
|
|
// Gone too far
|
|
|
|
return "", errz.New("unable to parse brew formula")
|
|
|
|
}
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2022-12-30 20:10:56 +03:00
|
|
|
if sc.Err() != nil {
|
|
|
|
return "", errz.Wrap(err, "invalid brew formula")
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2022-12-30 20:10:56 +03:00
|
|
|
return "", errz.New("invalid brew formula")
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|