Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RSDK-4975] CLI update warning #3585

Merged
merged 17 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ BUILD_CHANNEL ?= local
PATH_WITH_TOOLS="`pwd`/$(TOOL_BIN):`pwd`/node_modules/.bin:${PATH}"

GIT_REVISION = $(shell git rev-parse HEAD | tr -d '\n')
TAG_VERSION?=$(shell git tag --points-at | sort -Vr | head -n1)
LDFLAGS = -ldflags "-s -w -extld="$(shell pwd)/etc/ld_wrapper.sh" -X 'go.viam.com/rdk/config.Version=${TAG_VERSION}' -X 'go.viam.com/rdk/config.GitRevision=${GIT_REVISION}'"
TAG_VERSION?=$(shell git tag --points-at | sort -Vr | head -n1 | grep . || echo "(dev)")
DATE_COMPILED?=$(shell date +'%Y-%m-%d')
Copy link
Member

@zaporter-work zaporter-work Feb 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[opt] A friend of mine mentioned that using the go debug vcs.time would work as well.

See: https://pkg.go.dev/runtime/debug#BuildSetting
We report this in viam --debug version

Note: if you compile the cli with go build ./viam/main.go the vcs debug info won't be there. golang/go#51279

I don't want to drag down this PR in refactors so I don't see a strong reason to do this now, but might be worth considering in the future.

LDFLAGS = -ldflags "-s -w -extld="$(shell pwd)/etc/ld_wrapper.sh" -X 'go.viam.com/rdk/config.Version=${TAG_VERSION}' -X 'go.viam.com/rdk/config.GitRevision=${GIT_REVISION}' -X 'go.viam.com/rdk/config.DateCompiled=${DATE_COMPILED}'"
ifeq ($(shell command -v dpkg >/dev/null && dpkg --print-architecture),armhf)
GOFLAGS += -tags=no_tflite
endif
Expand Down
10 changes: 10 additions & 0 deletions cli/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const (
aliasRobotFlag = "robot"
partFlag = "part"

// TODO: RSDK-6683.
quietFlag = "quiet"
mattjperez marked this conversation as resolved.
Show resolved Hide resolved

logsFlagErrors = "errors"
logsFlagTail = "tail"

Expand Down Expand Up @@ -109,6 +112,12 @@ var app = &cli.App{
Aliases: []string{"vvv"},
Usage: "enable debug logging",
},
&cli.BoolFlag{
Name: quietFlag,
benjirewis marked this conversation as resolved.
Show resolved Hide resolved
Value: false,
Aliases: []string{"q"},
Usage: "suppress warnings",
},
},
Commands: []*cli.Command{
{
Expand All @@ -124,6 +133,7 @@ var app = &cli.App{
},
},
Action: LoginAction,
After: CheckUpdateAction,
Subcommands: []*cli.Command{
{
Name: "print-access-token",
Expand Down
134 changes: 131 additions & 3 deletions cli/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ package cli

import (
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"runtime/debug"
"strings"
"time"

"github.com/Masterminds/semver/v3"
"github.com/fullstorydev/grpcurl"
"github.com/google/uuid"
"github.com/jhump/protoreflect/grpcreflect"
Expand Down Expand Up @@ -40,6 +43,10 @@ import (
"go.viam.com/rdk/services/shell"
)

const (
rdkReleaseURL = "https://api.github.com/repos/viamrobotics/rdk/releases/latest"
)

// viamClient wraps a cli.Context and provides all the CLI command functionality
// needed to talk to the app and data services but not directly to robot parts.
type viamClient struct {
Expand Down Expand Up @@ -380,6 +387,129 @@ func RobotsPartShellAction(c *cli.Context) error {
)
}

// checkUpdateResponse holds the values used to hold release information.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: rename this too

type getLatestReleaseResponse struct {
Name string `json:"name"`
TagName string `json:"tag_name"`
TarballURL string `json:"tarball_url"`
}

func getLatestReleaseVersion() (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

resp := getLatestReleaseResponse{}

req, err := http.NewRequestWithContext(ctx, http.MethodGet, rdkReleaseURL, nil)
if err != nil {
return "", err
}

client := http.DefaultClient
res, err := client.Do(req)
if err != nil {
return "", err
}

err = json.NewDecoder(res.Body).Decode(&resp)
if err != nil {
return "", err
}

defer utils.UncheckedError(res.Body.Close())
return resp.TagName, err
}

// CheckUpdateAction is the corresponding Action for 'check-update'.
func CheckUpdateAction(c *cli.Context) error {
if c.Bool(quietFlag) {
return nil
}

dateCompiledRaw := rconfig.DateCompiled
mattjperez marked this conversation as resolved.
Show resolved Hide resolved

// `go build` will not set the compilation flags needed for this check
if dateCompiledRaw == "" {
return nil
}

dateCompiled, err := time.Parse("2006-01-02", dateCompiledRaw)
if err != nil {
warningf(c.App.ErrWriter, "CLI Update Check: failed to parse compilation date: %w", err)
mattjperez marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

// install is less than six weeks old
if time.Since(dateCompiled) < time.Hour*24*7*6 {
return nil
}

conf, err := configFromCache()
if err != nil {
if !os.IsNotExist(err) {
utils.UncheckedError(err)
return nil
}
conf = &config{}
}

var lastCheck time.Time
if conf.LastUpdateCheck == "" {
conf.LastUpdateCheck = time.Now().Format("2006-01-02")
} else {
lastCheck, err = time.Parse("2006-01-02", conf.LastUpdateCheck)
if err != nil {
warningf(c.App.ErrWriter, "CLI Update Check: failed to parse date of last check: %w", err)
return nil
}
}

// The latest version info is cached to limit api calls to once every three days
if time.Since(lastCheck) < time.Hour*24*3 && conf.LatestVersion != "" {
warningf(c.App.ErrWriter, "CLI Update Check: Your CLI is more than 6 weeks old. "+
"Consider updating to version: %s", conf.LatestVersion)
return nil
}

latestRelease, err := getLatestReleaseVersion()
if err != nil {
warningf(c.App.ErrWriter, "CLI Update Check: failed to get latest release information: %w", err)
return nil
}

latestVersion, err := semver.NewVersion(latestRelease)
if err != nil {
warningf(c.App.ErrWriter, "CLI Update Check: failed to parse latest version: %w", err)
return nil
}

conf.LatestVersion = latestVersion.String()

err = storeConfigToCache(conf)
if err != nil {
utils.UncheckedError(err)
}

appVersion := rconfig.Version
if appVersion == "(dev)" {
warningf(c.App.ErrWriter, "CLI Update Check: Your CLI is more than 6 weeks old. "+
"Consider updating to version: %s", latestVersion.Original())
return nil
}

localVersion, err := semver.NewVersion(appVersion)
if err != nil {
warningf(c.App.ErrWriter, "CLI Update Check: failed to parse compiled version: %w", err)
return nil
}

if localVersion.LessThan(latestVersion) {
warningf(c.App.ErrWriter, "CLI Update Check: Your CLI is out of date. Consider updating to version %s", latestVersion.Original())
}

return nil
}

// VersionAction is the corresponding Action for 'version'.
func VersionAction(c *cli.Context) error {
info, ok := debug.ReadBuildInfo()
Expand Down Expand Up @@ -408,10 +538,8 @@ func VersionAction(c *cli.Context) error {
if dep, ok := deps["go.viam.com/api"]; ok {
apiVersion = dep.Version
}

appVersion := rconfig.Version
if appVersion == "" {
appVersion = "(dev)"
}
printf(c.App.Writer, "Version %s Git=%s API=%s", appVersion, version, apiVersion)
return nil
}
Expand Down
6 changes: 4 additions & 2 deletions cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ func storeConfigToCache(cfg *config) error {
}

type config struct {
BaseURL string `json:"base_url"`
Auth authMethod `json:"auth"`
BaseURL string `json:"base_url"`
Auth authMethod `json:"auth"`
LastUpdateCheck string `json:"last_update_check"`
LatestVersion string `json:"latest_version"`
}

func (conf *config) tryUnmarshallWithToken(configBytes []byte) error {
Expand Down
6 changes: 0 additions & 6 deletions cli/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,6 @@ func infof(w io.Writer, format string, a ...interface{}) {
}

// warningf prints a message prefixed with a bold yellow "Warning: ".
//
// NOTE(benjirewis): we disable the unparam linter here. Our usages of warningf
// do not currently make use of the variadic `a` parameter but may in the
// future. unparam will complain until it does.
//
//nolint:unparam
mattjperez marked this conversation as resolved.
Show resolved Hide resolved
func warningf(w io.Writer, format string, a ...interface{}) {
if _, err := color.New(color.Bold, color.FgYellow).Fprint(w, "Warning: "); err != nil {
log.Fatal(err)
Expand Down
5 changes: 3 additions & 2 deletions config/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ import (

// RDK versioning variables which are replaced by LD flags.
var (
Version = ""
GitRevision = ""
Version = ""
GitRevision = ""
DateCompiled = ""
)

func getAgentInfo() (*apppb.AgentInfo, error) {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.19
require (
github.com/AlekSi/gocov-xml v1.0.0
github.com/CPRT/roboclaw v0.0.0-20190825181223-76871438befc
github.com/Masterminds/semver/v3 v3.2.1
github.com/Masterminds/sprig v2.22.0+incompatible
github.com/NYTimes/gziphandler v1.1.1
github.com/a8m/envsubst v1.4.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy86
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
Expand Down
Loading