Skip to content

CLOUDP-113344: Improve time of new version checker #1010

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

Merged
merged 20 commits into from
Mar 14, 2022
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
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncV
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog=
github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
Expand Down Expand Up @@ -188,9 +189,9 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github/v39 v39.0.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE=
github.com/google/go-github/v42 v42.0.0 h1:YNT0FwjPrEysRkLIiKuEfSvBPCGKphW5aS5PxwaoLec=
github.com/google/go-github/v42 v42.0.0/go.mod h1:jgg/jvyI0YlDOM1/ps6XYh04HNQ3vKf0CVko62/EhRg=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand Down
75 changes: 66 additions & 9 deletions internal/cli/root/atlas/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
package atlas

import (
"context"
"fmt"
"io"
"runtime"
"strings"
"time"

"github.com/mongodb/mongocli/internal/cli"
"github.com/mongodb/mongocli/internal/cli/alerts"
Expand Down Expand Up @@ -55,17 +56,22 @@ import (
"github.com/mongodb/mongocli/internal/cli/performanceadvisor"
"github.com/mongodb/mongocli/internal/config"
"github.com/mongodb/mongocli/internal/flag"
"github.com/mongodb/mongocli/internal/homebrew"
"github.com/mongodb/mongocli/internal/latestrelease"
"github.com/mongodb/mongocli/internal/usage"
"github.com/mongodb/mongocli/internal/validate"
"github.com/mongodb/mongocli/internal/version"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

const atlas = "atlas"

type BuilderOpts struct {
store latestrelease.Printer
type Notifier struct {
currentVersion string
finder latestrelease.VersionFinder
filesystem afero.Fs
writer io.Writer
}

// Builder conditionally adds children commands as needed.
Expand Down Expand Up @@ -108,13 +114,18 @@ func Builder(profile *string) *cobra.Command {
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
w := cmd.ErrOrStderr()
fs := afero.NewOsFs()
f, _ := latestrelease.NewVersionFinder(fs, version.NewReleaseVersionDescriber())

notifier := &Notifier{
currentVersion: latestrelease.VersionFromTag(version.Version, config.ToolName),
finder: f,
filesystem: fs,
writer: w,
}

if !config.SkipUpdateCheck() && cli.IsTerminal(w) {
opts := &BuilderOpts{
store: latestrelease.NewPrinter(context.Background()),
}

_ = opts.store.PrintNewVersionAvailable(w, version.Version, config.ToolName, config.BinName())
if check, isHb := notifier.shouldCheck(); check {
_ = notifier.notifyIfApplicable(isHb)
}
},
}
Expand Down Expand Up @@ -189,3 +200,49 @@ func formattedVersion() string {
runtime.GOARCH,
runtime.Compiler)
}

func (n *Notifier) shouldCheck() (shouldCheck, isHb bool) {
shouldCheck = !config.SkipUpdateCheck() && cli.IsTerminal(n.writer)
isHb = false

if !shouldCheck {
return shouldCheck, isHb
}

c, _ := homebrew.NewChecker(n.filesystem)
isHb = c.IsHomebrew()

return shouldCheck, isHb
}

func (n *Notifier) notifyIfApplicable(isHb bool) error {
release, err := n.finder.Find()
if err != nil || release == nil {
return err
}

// homebrew is an external dependency we give them 24h to have the cli available there
if isHb && !isAtLeast24HoursPast(release.PublishedAt) {
return nil
}

var upgradeInstructions string
if isHb {
upgradeInstructions = fmt.Sprintf(`To upgrade, run "brew update && brew upgrade %s".`, homebrew.FormulaName(config.ToolName))
} else {
upgradeInstructions = fmt.Sprintf(`To upgrade, see: https://dochub.mongodb.org/core/%s-install.`, config.ToolName)
}

newVersionTemplate := `
A new version of %s is available '%s'!
%s

To disable this alert, run "%s config set skip_update_check true".
`
_, err = fmt.Fprintf(n.writer, newVersionTemplate, config.ToolName, release.Version, upgradeInstructions, config.BinName())
return err
}

func isAtLeast24HoursPast(t time.Time) bool {
return !t.IsZero() && time.Since(t) >= time.Hour*24
}
98 changes: 98 additions & 0 deletions internal/cli/root/atlas/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,18 @@
package atlas

import (
"bytes"
"fmt"
"testing"

"github.com/golang/mock/gomock"
"github.com/google/go-github/v42/github"
"github.com/mongodb/mongocli/internal/config"
"github.com/mongodb/mongocli/internal/latestrelease"
"github.com/mongodb/mongocli/internal/mocks"
"github.com/mongodb/mongocli/internal/test"
"github.com/mongodb/mongocli/internal/version"
"github.com/spf13/afero"
)

func TestBuilder(t *testing.T) {
Expand All @@ -31,3 +40,92 @@ func TestBuilder(t *testing.T) {
[]string{},
)
}

func TestOutputOpts_notifyIfApplicable(t *testing.T) {
tests := testCases()
for _, tt := range tests {
t.Run(fmt.Sprintf("%v / %v", tt.currentVersion, tt.release.GetTagName()), func(t *testing.T) {
config.ToolName = config.AtlasCLI
prevVersion := version.Version
version.Version = tt.currentVersion
defer func() {
version.Version = prevVersion
}()

ctrl := gomock.NewController(t)
mockDescriber := mocks.NewMockReleaseVersionDescriber(ctrl)
defer ctrl.Finish()

mockDescriber.
EXPECT().
LatestWithCriteria(gomock.Any(), gomock.Any(), gomock.Any()).
Return(tt.release, nil).
Times(1)

bufOut := new(bytes.Buffer)
fs := afero.NewMemMapFs()
finder, _ := latestrelease.NewVersionFinder(fs, mockDescriber)

notifier := &Notifier{
currentVersion: latestrelease.VersionFromTag(version.Version, config.ToolName),
finder: finder,
filesystem: fs,
writer: bufOut,
}

if err := notifier.notifyIfApplicable(false); err != nil {
t.Errorf("notifyIfApplicable() unexpected error:%v", err)
}

v := latestrelease.VersionFromTag(tt.release.GetTagName(), config.ToolName)
want := ""
if tt.expectNewVersion {
want = fmt.Sprintf(`
A new version of %s is available '%v'!
To upgrade, see: https://dochub.mongodb.org/core/%s-install.

To disable this alert, run "%s config set skip_update_check true".
`, config.ToolName, v, config.ToolName, config.BinName())
}

if got := bufOut.String(); got != want {
t.Errorf("notifyIfApplicable() got = %v, want %v", got, want)
}
})
}
}

type testCase struct {
currentVersion string
expectNewVersion bool
release *github.RepositoryRelease
}

func testCases() []testCase {
f := false
atlasV := "atlascli/v2.0.0"

tests := []testCase{
{
currentVersion: "v1.0.0",
expectNewVersion: true,
release: &github.RepositoryRelease{TagName: &atlasV, Prerelease: &f, Draft: &f},
},
{
currentVersion: "atlascli/v1.0.0",
expectNewVersion: true,
release: &github.RepositoryRelease{TagName: &atlasV, Prerelease: &f, Draft: &f},
},
{
currentVersion: "v3.0.0",
expectNewVersion: false,
release: &github.RepositoryRelease{TagName: &atlasV, Prerelease: &f, Draft: &f},
},
{
currentVersion: "v3.0.0-123",
expectNewVersion: false,
release: &github.RepositoryRelease{TagName: &atlasV, Prerelease: &f, Draft: &f},
},
}
return tests
}
74 changes: 63 additions & 11 deletions internal/cli/root/mongocli/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
package mongocli

import (
"context"
"fmt"
"io"
"runtime"
"time"

"github.com/mongodb/mongocli/internal/cli"
"github.com/mongodb/mongocli/internal/cli/atlas"
Expand All @@ -30,15 +30,20 @@ import (
"github.com/mongodb/mongocli/internal/cli/opsmanager"
"github.com/mongodb/mongocli/internal/config"
"github.com/mongodb/mongocli/internal/flag"
"github.com/mongodb/mongocli/internal/homebrew"
"github.com/mongodb/mongocli/internal/latestrelease"
"github.com/mongodb/mongocli/internal/search"
"github.com/mongodb/mongocli/internal/usage"
"github.com/mongodb/mongocli/internal/version"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

type BuilderOpts struct {
store latestrelease.Printer
type Notifier struct {
currentVersion string
finder latestrelease.VersionFinder
filesystem afero.Fs
writer io.Writer
}

// Builder conditionally adds children commands as needed.
Expand All @@ -59,14 +64,19 @@ func Builder(profile *string, argsWithoutProg []string) *cobra.Command {
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
w := cmd.ErrOrStderr()
if shouldSkipPrintNewVersion(w) {
return
}
opts := &BuilderOpts{
store: latestrelease.NewPrinter(context.Background()),
fs := afero.NewOsFs()
f, _ := latestrelease.NewVersionFinder(fs, version.NewReleaseVersionDescriber())

notifier := &Notifier{
currentVersion: latestrelease.VersionFromTag(version.Version, config.ToolName),
finder: f,
filesystem: fs,
writer: w,
}

_ = opts.store.PrintNewVersionAvailable(w, version.Version, config.ToolName, config.BinName())
if check, isHb := notifier.shouldCheck(); check {
_ = notifier.notifyIfApplicable(isHb)
}
},
}
rootCmd.SetVersionTemplate(formattedVersion())
Expand Down Expand Up @@ -132,6 +142,48 @@ func formattedVersion() string {
runtime.Compiler)
}

func shouldSkipPrintNewVersion(w io.Writer) bool {
return config.SkipUpdateCheck() || !cli.IsTerminal(w)
func (n *Notifier) shouldCheck() (shouldCheck, isHb bool) {
shouldCheck = !config.SkipUpdateCheck() && cli.IsTerminal(n.writer)
isHb = false

if !shouldCheck {
return shouldCheck, isHb
}

c, _ := homebrew.NewChecker(n.filesystem)
isHb = c.IsHomebrew()

return shouldCheck, isHb
}

func (n *Notifier) notifyIfApplicable(isHb bool) error {
release, err := n.finder.Find()
if err != nil || release == nil {
return err
}

// homebrew is an external dependency we give them 24h to have the cli available there
if isHb && !isAtLeast24HoursPast(release.PublishedAt) {
return nil
}

var upgradeInstructions string
if isHb {
upgradeInstructions = fmt.Sprintf(`To upgrade, run "brew update && brew upgrade %s".`, homebrew.FormulaName(config.ToolName))
} else {
upgradeInstructions = fmt.Sprintf(`To upgrade, see: https://dochub.mongodb.org/core/%s-install.`, config.ToolName)
}

newVersionTemplate := `
A new version of %s is available '%s'!
%s

To disable this alert, run "%s config set skip_update_check true".
`
_, err = fmt.Fprintf(n.writer, newVersionTemplate, config.ToolName, release.Version, upgradeInstructions, config.BinName())
return err
}

func isAtLeast24HoursPast(t time.Time) bool {
return !t.IsZero() && time.Since(t) >= time.Hour*24
}
Loading