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 3 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
4 changes: 3 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bradleyfalzon/ghinstallation/v2 v2.0.3/go.mod h1:tlgi+JWCXnKFx/Y4WtnDbZEINo31N5bcvnCoqieefmk=
github.com/briandowns/spinner v1.18.1 h1:yhQmQtM1zsqFsouh09Bk/jCjd50pC3EOGsh28gLVvwY=
github.com/briandowns/spinner v1.18.1/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
Expand Down Expand Up @@ -138,6 +139,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 +190,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: 68 additions & 7 deletions internal/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,51 @@ package file
import (
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/mongodb/mongocli/internal/search"
"github.com/spf13/afero"
"gopkg.in/yaml.v2"
)

var supportedExts = []string{"json", "yaml", "yml"}
const (
yamlName = "yaml"
jsonName = "json"
ymlName = "yml"
configPermission = 0700
filePermission = 0600
)

var supportedExts = []string{jsonName, yamlName, ymlName}

// configType gets the config type from a given file path.
func configType(filename string) (string, error) {
ext := filepath.Ext(filename)

if len(ext) <= 1 {
return "", fmt.Errorf("filename: %s requires valid extension", filename)
}

configType := ext[1:]
if !search.StringInSlice(supportedExts, configType) {
return "", fmt.Errorf("unsupported file type: %s", configType)
}
return configType, nil
}

// Load loads a given filename into the out interface.
// The file should be a valid json or yaml format.
func Load(fs afero.Fs, filename string, out interface{}) error {
if exists, err := afero.Exists(fs, filename); !exists || err != nil {
return fmt.Errorf("file not found: %s", filename)
}
ext := filepath.Ext(filename)
if len(ext) <= 1 {
return fmt.Errorf("filename: %s requires valid extension", filename)

configType, err := configType(filename)
if err != nil {
return err
}
configType := ext[1:]

if !search.StringInSlice(supportedExts, configType) {
return fmt.Errorf("unsupported file type: %s", configType)
}
Expand All @@ -46,15 +71,51 @@ func Load(fs afero.Fs, filename string, out interface{}) error {
}

switch configType {
case "yaml", "yml":
case yamlName, ymlName:
if err := yaml.Unmarshal(file, out); err != nil {
return err
}
case "json":
case jsonName:
if err := json.Unmarshal(file, out); err != nil {
return err
}
}

return nil
}

// Save saves a given data interface into a given file path
// The file should be a valid json or yaml format.
func Save(fs afero.Fs, filePath string, data interface{}) error {
var content []byte

configType, err := configType(filePath)
if err != nil {
return err
}

if !search.StringInSlice(supportedExts, configType) {
return fmt.Errorf("unsupported file type: %s", configType)
}

switch configType {
case yamlName, ymlName:
content, err = yaml.Marshal(data)
if err != nil {
return err
}
case jsonName:
content, err = json.Marshal(data)
if err != nil {
return err
}
}

err = os.MkdirAll(filepath.Dir(filePath), configPermission)
if err != nil {
return err
}

err = afero.WriteFile(fs, filePath, content, filePermission)
return err
}
81 changes: 70 additions & 11 deletions internal/file/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,58 +18,117 @@
package file_test

import (
"encoding/json"
"fmt"
"testing"

"github.com/mongodb/mongocli/internal/file"
"github.com/spf13/afero"
"gopkg.in/yaml.v2"
)

func TestLoad(t *testing.T) {
t.Run("file does not exists", func(t *testing.T) {
const (
noExtFileName = "test"
txtFileName = "test.txt"
jsonFileName = "test.json"
yamlFileName = "test.yaml"
unsupportedMsg = "unsupported file type: txt"
)

func TestFile(t *testing.T) {
t.Run("load file does not exists", func(t *testing.T) {
appFS := afero.NewMemMapFs()
filename := "test.xml"
err := file.Load(appFS, filename, nil)
if err == nil || err.Error() != fmt.Sprintf("file not found: %s", filename) {
t.Errorf("Load() unexpected error: %v", err)
}
})
t.Run("file with no ext", func(t *testing.T) {
t.Run("load file with no ext", func(t *testing.T) {
appFS := afero.NewMemMapFs()
filename := "test"
filename := noExtFileName
_ = afero.WriteFile(appFS, filename, []byte(""), 0600)
err := file.Load(appFS, filename, nil)
if err == nil || err.Error() != fmt.Sprintf("filename: %s requires valid extension", filename) {
t.Errorf("Load() unexpected error: %v", err)
}
})
t.Run("file with invalid ext", func(t *testing.T) {
t.Run("load file with invalid ext", func(t *testing.T) {
appFS := afero.NewMemMapFs()
filename := "test.test"
filename := txtFileName
_ = afero.WriteFile(appFS, filename, []byte(""), 0600)
err := file.Load(appFS, filename, nil)
if err == nil || err.Error() != "unsupported file type: test" {
if err == nil || err.Error() != unsupportedMsg {
t.Errorf("Load() unexpected error: %v", err)
}
})
t.Run("valid json file", func(t *testing.T) {
t.Run("load valid json file", func(t *testing.T) {
appFS := afero.NewMemMapFs()
filename := "test.json"
filename := jsonFileName
_ = afero.WriteFile(appFS, filename, []byte("{}"), 0600)
out := new(map[string]interface{})
err := file.Load(appFS, filename, out)
if err != nil {
t.Fatalf("Load() unexpected error: %v", err)
}
})
t.Run("valid yaml file", func(t *testing.T) {
t.Run("load valid yaml file", func(t *testing.T) {
appFS := afero.NewMemMapFs()
filename := "test.yaml"
filename := yamlFileName
_ = afero.WriteFile(appFS, filename, []byte(""), 0600)
out := new(map[string]interface{})
err := file.Load(appFS, filename, out)
if err != nil {
t.Fatalf("Load() unexpected error: %v", err)
}
})
t.Run("save file with no ext", func(t *testing.T) {
appFS := afero.NewMemMapFs()
filename := "test"
err := file.Save(appFS, filename, nil)
if err == nil || err.Error() != "filename: test requires valid extension" {
t.Errorf("Save() unexpected error: %v", err)
}
})
t.Run("save file with wrong ext", func(t *testing.T) {
appFS := afero.NewMemMapFs()
filename := txtFileName
err := file.Save(appFS, filename, nil)
if err == nil || err.Error() != unsupportedMsg {
t.Errorf("Save() unexpected error: %v", err)
}
})
t.Run("save valid json file", func(t *testing.T) {
appFS := afero.NewMemMapFs()
filename := jsonFileName

b := []byte(`{"Name":"MongoDB","Age":100,"Children":["mongocli", "atlascli"]}`)
j, _ := json.Marshal(b)

err := file.Save(appFS, filename, j)
if err != nil {
t.Fatalf("Save() unexpected error: %v", err)
}
})
t.Run("save valid yaml file", func(t *testing.T) {
appFS := afero.NewMemMapFs()
filename := yamlFileName

type Test struct {
name string
age int
}

tYaml := Test{
name: "MongoDB",
age: 100,
}

yamlData, _ := yaml.Marshal(&tYaml)

err := file.Save(appFS, filename, yamlData)
if err != nil {
t.Fatalf("Save() unexpected error: %v", err)
}
})
}
51 changes: 40 additions & 11 deletions internal/latestrelease/finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,25 @@ import (
"github.com/Masterminds/semver/v3"
"github.com/google/go-github/v42/github"
"github.com/mongodb/mongocli/internal/version"
"github.com/spf13/afero"
)

//go:generate mockgen -destination=../mocks/mock_release_version.go -package=mocks github.com/mongodb/mongocli/internal/version VersionFinder

type VersionFinder interface {
HasNewVersionAvailable(v, tool string) (newVersionAvailable bool, newVersion string, err error)
}

func NewVersionFinder(ctx context.Context, re version.ReleaseVersionDescriber) VersionFinder {
return &latestReleaseVersionFinder{c: ctx, r: re}
return &latestReleaseVersionFinder{c: ctx, r: re, s: NewStore(afero.NewOsFs())}
}

func NewVersionFinderWithStore(ctx context.Context, re version.ReleaseVersionDescriber, store Store) VersionFinder {
return &latestReleaseVersionFinder{c: ctx, r: re, s: store}
}

type latestReleaseVersionFinder struct {
c context.Context
r version.ReleaseVersionDescriber
s Store
}

func versionFromTag(release *github.RepositoryRelease, toolName string) string {
Expand All @@ -52,15 +56,14 @@ func isValidTagForTool(tag, tool string) bool {
return strings.Contains(tag, tool)
}

func (s *latestReleaseVersionFinder) searchLatestVersionPerTool(currentVersion *semver.Version, toolName string) (bool, *version.ReleaseInformation, error) {
release, err := s.r.LatestWithCriteria(minPageSize, isValidTagForTool, toolName)
func (f *latestReleaseVersionFinder) searchLatestVersionPerTool(currentVersion *semver.Version, toolName string) (bool, *version.ReleaseInformation, error) {
release, err := f.r.LatestWithCriteria(minPageSize, isValidTagForTool, toolName)

if err != nil || release == nil {
return false, nil, err
}

v, err := semver.NewVersion(versionFromTag(release, toolName))

if err != nil {
return false, nil, err
}
Expand All @@ -74,11 +77,31 @@ func (s *latestReleaseVersionFinder) searchLatestVersionPerTool(currentVersion *
return false, nil, nil
}

func (s *latestReleaseVersionFinder) HasNewVersionAvailable(v, tool string) (newVersionAvailable bool, newVersion string, err error) {
if v == "" {
func (f *latestReleaseVersionFinder) storedLatestVersionAvailable(tool string, currentVersion *semver.Version) (needRefresh bool, foundVersion string, err error) {
latestVersionStored, _ := f.s.LoadLatestVersion(tool)
// no valid store version, need to fetch from GitHub
if latestVersionStored == "" {
return true, "", nil
}
// retrieved an invalid version, need to fetch from GitHub
v, err := semver.NewVersion(latestVersionStored)
if err != nil {
return true, "", err
}
// found a valid store higher latest version, no need to refresh
if currentVersion.Compare(v) < 0 {
return false, v.Original(), nil
}
// found a lower or equal latest version, no need to refresh
return false, "", nil
}

func (f *latestReleaseVersionFinder) HasNewVersionAvailable(currentV, tool string) (newVersionAvailable bool, newVersion string, err error) {
if currentV == "" {
return false, "", nil
}
svCurrentVersion, err := semver.NewVersion(v)

svCurrentVersion, err := semver.NewVersion(currentV)
if err != nil {
return false, "", err
}
Expand All @@ -90,12 +113,18 @@ func (s *latestReleaseVersionFinder) HasNewVersionAvailable(v, tool string) (new
}
}

newVersionAvailable, newV, err := s.searchLatestVersionPerTool(svCurrentVersion, tool)
needRefresh, storedLatestVersion, err := f.storedLatestVersionAvailable(tool, svCurrentVersion)
if !needRefresh {
return storedLatestVersion != "", storedLatestVersion, err
}

newVersionAvailable, newV, err := f.searchLatestVersionPerTool(svCurrentVersion, tool)
if err != nil {
return false, "", err
}

if newVersionAvailable && (!isHomebrew(tool) || isAtLeast24HoursPast(newV.PublishedAt)) {
if newVersionAvailable && (!isHomebrew(tool, f.s) || isAtLeast24HoursPast(newV.PublishedAt)) {
_ = f.s.SaveLatestVersion(tool, newV.Version)
return newVersionAvailable, newV.Version, nil
}

Expand Down
Loading