Skip to content

Commit 843cf71

Browse files
authored
CLOUDP-100237: Propose homebrew update command on version update alert (#863)
1 parent dde89d6 commit 843cf71

File tree

6 files changed

+140
-67
lines changed

6 files changed

+140
-67
lines changed

internal/cli/root/builder.go

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ package root
1717
import (
1818
"fmt"
1919
"io"
20+
"os"
21+
"os/exec"
22+
"path/filepath"
2023
"runtime"
24+
"strings"
25+
"time"
2126

2227
"github.com/Masterminds/semver/v3"
2328
"github.com/mongodb/mongocli/internal/cli"
@@ -36,7 +41,7 @@ import (
3641
)
3742

3843
type BuilderOpts struct {
39-
store store.VersionDescriber
44+
store store.ReleaseVersionDescriber
4045
}
4146

4247
// rootBuilder conditionally adds children commands as needed.
@@ -139,18 +144,67 @@ func (opts *BuilderOpts) hasNewVersionAvailable() (newVersionAvailable bool, new
139144
return false, "", err
140145
}
141146

142-
svLatestVersion, err := semver.NewVersion(latestVersion)
147+
svLatestVersion, err := semver.NewVersion(latestVersion.Version)
143148
if err != nil {
144149
return false, "", err
145150
}
146151

147-
if svCurrentVersion.Compare(svLatestVersion) < 0 {
148-
return true, latestVersion, nil
152+
if svCurrentVersion.Compare(svLatestVersion) < 0 && (!isHomebrew() || isAtLeast24HoursPast(latestVersion.PublishedAt)) {
153+
return true, latestVersion.Version, nil
149154
}
150155

151156
return false, "", nil
152157
}
153158

159+
func isAtLeast24HoursPast(t time.Time) bool {
160+
return !t.IsZero() && time.Since(t) >= time.Hour*24
161+
}
162+
163+
func isHomebrew() bool {
164+
brewFormulaPath, err := homebrewFormulaPath()
165+
if err != nil {
166+
return false
167+
}
168+
169+
executablePath, err := executableCurrentPath()
170+
if err != nil {
171+
return false
172+
}
173+
174+
return strings.HasPrefix(executablePath, brewFormulaPath)
175+
}
176+
177+
func homebrewFormulaPath() (string, error) {
178+
formula := config.ToolName
179+
brewFormulaPathBytes, err := exec.Command("brew", "--prefix", "--installed", formula).Output()
180+
if err != nil {
181+
return "", err
182+
}
183+
184+
brewFormulaPath := strings.TrimSpace(string(brewFormulaPathBytes))
185+
186+
brewFormulaPath, err = filepath.EvalSymlinks(brewFormulaPath)
187+
if err != nil {
188+
return "", err
189+
}
190+
191+
return brewFormulaPath, nil
192+
}
193+
194+
func executableCurrentPath() (string, error) {
195+
executablePath, err := os.Executable()
196+
if err != nil {
197+
return "", err
198+
}
199+
200+
executablePath, err = filepath.EvalSymlinks(executablePath)
201+
if err != nil {
202+
return "", err
203+
}
204+
205+
return executablePath, nil
206+
}
207+
154208
func shouldSkipPrintNewVersion(w io.Writer) bool {
155209
return config.SkipUpdateCheck() || !cli.IsTerminal(w)
156210
}
@@ -161,13 +215,20 @@ func (opts *BuilderOpts) printNewVersionAvailable(w io.Writer) error {
161215
return err
162216
}
163217
if newVersionAvailable {
218+
var upgradeInstructions string
219+
if isHomebrew() {
220+
upgradeInstructions = `To upgrade, run "brew update && brew upgrade mongocli".`
221+
} else {
222+
upgradeInstructions = `To upgrade, see: https://dochub.mongodb.org/core/mongocli-install.`
223+
}
224+
164225
newVersionTemplate := `
165226
A new version of %s is available '%s'!
166-
To upgrade, see: https://dochub.mongodb.org/core/mongocli-install.
227+
%s
167228
168229
To disable this alert, run "mongocli config set skip_update_check true".
169230
`
170-
_, err = fmt.Fprintf(w, newVersionTemplate, config.ToolName, latestVersion)
231+
_, err = fmt.Fprintf(w, newVersionTemplate, config.ToolName, latestVersion, upgradeInstructions)
171232
return err
172233
}
173234
return nil

internal/cli/root/builder_test.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ import (
2121
"bytes"
2222
"fmt"
2323
"testing"
24+
"time"
2425

2526
"github.com/golang/mock/gomock"
2627
"github.com/mongodb/mongocli/internal/mocks"
28+
"github.com/mongodb/mongocli/internal/store"
2729
"github.com/mongodb/mongocli/internal/version"
2830
)
2931

@@ -124,22 +126,22 @@ func TestBuilder(t *testing.T) {
124126
func TestOutputOpts_printNewVersionAvailable(t *testing.T) {
125127
tests := []struct {
126128
currentVersion string
127-
latestVersion string
129+
latestVersion *store.ReleaseInformation
128130
wantPrint bool
129131
}{
130132
{
131133
currentVersion: "v1.0.0",
132-
latestVersion: "v2.0.0",
134+
latestVersion: &store.ReleaseInformation{Version: "v2.0.0", PublishedAt: time.Now()},
133135
wantPrint: true,
134136
},
135137
{
136138
currentVersion: "v1.0.0",
137-
latestVersion: "v1.0.0",
139+
latestVersion: &store.ReleaseInformation{Version: "v1.0.0", PublishedAt: time.Now()},
138140
wantPrint: false,
139141
},
140142
{
141143
currentVersion: "v1.0.0-123",
142-
latestVersion: "v1.0.0",
144+
latestVersion: &store.ReleaseInformation{Version: "v1.0.0", PublishedAt: time.Now()},
143145
wantPrint: false,
144146
},
145147
}
@@ -153,7 +155,7 @@ func TestOutputOpts_printNewVersionAvailable(t *testing.T) {
153155
}()
154156

155157
ctrl := gomock.NewController(t)
156-
mockStore := mocks.NewMockVersionDescriber(ctrl)
158+
mockStore := mocks.NewMockReleaseVersionDescriber(ctrl)
157159
defer ctrl.Finish()
158160

159161
mockStore.
@@ -178,7 +180,7 @@ A new version of mongocli is available '%v'!
178180
To upgrade, see: https://dochub.mongodb.org/core/mongocli-install.
179181
180182
To disable this alert, run "mongocli config set skip_update_check true".
181-
`, tt.latestVersion)
183+
`, tt.latestVersion.Version)
182184
}
183185

184186
if got := bufOut.String(); got != want {

internal/mocks/.keep

Whitespace-only changes.

internal/mocks/mock_release_version.go

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/mocks/mock_version.go

Lines changed: 0 additions & 49 deletions
This file was deleted.

internal/store/version.go renamed to internal/store/release_version.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,31 @@ package store
1616

1717
import (
1818
"context"
19+
"time"
1920

2021
"github.com/google/go-github/v38/github"
2122
)
2223

23-
//go:generate mockgen -destination=../mocks/mock_version.go -package=mocks github.com/mongodb/mongocli/internal/store VersionDescriber
24+
//go:generate mockgen -destination=../mocks/mock_release_version.go -package=mocks github.com/mongodb/mongocli/internal/store ReleaseVersionDescriber
2425

25-
type VersionDescriber interface {
26-
LatestVersion() (string, error)
26+
type ReleaseVersionDescriber interface {
27+
LatestVersion() (*ReleaseInformation, error)
28+
}
29+
30+
type ReleaseInformation struct {
31+
Version string
32+
PublishedAt time.Time
2733
}
2834

2935
// LatestVersion encapsulates the logic to manage different cloud providers.
30-
func (s *Store) LatestVersion() (string, error) {
36+
func (s *Store) LatestVersion() (*ReleaseInformation, error) {
3137
client := github.NewClient(nil)
3238
release, _, err := client.Repositories.GetLatestRelease(context.Background(), "mongodb", "mongocli")
3339
if err != nil {
34-
return "", err
40+
return nil, err
3541
}
36-
return release.GetTagName(), nil
42+
return &ReleaseInformation{
43+
Version: release.GetTagName(),
44+
PublishedAt: release.PublishedAt.Time,
45+
}, nil
3746
}

0 commit comments

Comments
 (0)