Skip to content
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
44 changes: 41 additions & 3 deletions internal/cli/plugin/plugin_github_asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,21 @@ func (g *GithubAsset) getReleaseAssets() ([]*github.ReleaseAsset, error) {

// download latest release if version is not specified
if g.version == nil {
release, _, err = g.ghClient.Repositories.GetLatestRelease(context.Background(), g.owner, g.name)
// download the 100 latest releases
const MaxPerPage = 100
releases, _, err := g.ghClient.Repositories.ListReleases(context.Background(), g.owner, g.name, &github.ListOptions{
Page: 0,
PerPage: MaxPerPage,
})

if err != nil {
return nil, fmt.Errorf("could not find latest release for %s", g.repository())
return nil, fmt.Errorf("could not fetch releases for %s %w", g.repository(), err)
}

// get the latest release that doesn't have prerelease info or metadata in the version tag
release = getLatestStableRelease(releases)
if release == nil {
return nil, fmt.Errorf("could not find latest stable release for %s", g.repository())
}
} else {
// try to find the release with the version tag with v prefix, if it does not exist try again without the prefix
Expand All @@ -100,6 +111,32 @@ func (g *GithubAsset) getReleaseAssets() ([]*github.ReleaseAsset, error) {
return release.Assets, nil
}

func getLatestStableRelease(releases []*github.RepositoryRelease) *github.RepositoryRelease {
var latestStableVersion *semver.Version
var latestStableRelease *github.RepositoryRelease

for _, release := range releases {
version, err := semver.NewVersion(*release.TagName)

// if we can't parse the version tag, skip this release
if err != nil {
continue
}

// if the version has pre-release info or metadata, skip this version
if version.Prerelease() != "" || version.Metadata() != "" {
continue
}

if latestStableVersion == nil || version.GreaterThan(latestStableVersion) {
latestStableVersion = version
latestStableRelease = release
}
}

return latestStableRelease
}

var architectureAliases = map[string][]string{
"amd64": {"x86_64"},
"arm64": {"aarch64"},
Expand Down Expand Up @@ -173,7 +210,7 @@ func (g *GithubAsset) getPluginAssetAsReadCloser(assetID int64) (io.ReadCloser,
}

func parseGithubReleaseValues(arg string) (*GithubAsset, error) {
regexPattern := `^((https?://(www\.)?)?github\.com/)?(?P<owner>[\w.\-]+)/(?P<name>[\w.\-]+)/?(@(?P<version>v?(\d+)(\.\d+)?(\.\d+)?|latest))?$`
regexPattern := `^((https?://(www\.)?)?github\.com/)?(?P<owner>[\w.\-]+)/(?P<name>[\w.\-]+)/?(@(?P<version>.+))?$`
regex, err := regexp.Compile(regexPattern)
if err != nil {
return nil, fmt.Errorf("error compiling regex: %w", err)
Expand All @@ -196,6 +233,7 @@ func parseGithubReleaseValues(arg string) (*GithubAsset, error) {
githubRelease := &GithubAsset{owner: groupMap["owner"], name: groupMap["name"]}

if version, ok := groupMap["version"]; ok && version != latest && version != "" {
version := strings.TrimPrefix(version, "v")
semverVersion, err := semver.NewVersion(version)
if err != nil {
return nil, fmt.Errorf(`the specified version "%s" is invalid, it needs to follow the rules of Semantic Versioning`, version)
Expand Down
189 changes: 144 additions & 45 deletions internal/cli/plugin/plugin_github_asset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/Masterminds/semver/v3"
"github.com/google/go-github/v61/github"
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/plugin"
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/pointer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -204,77 +205,96 @@ func Test_parseGithubRepoValues(t *testing.T) {
expectedOwner = "mongodb"
expectedName = "atlas-cli-plugin-example"
)
var expectedVersion, _ = semver.NewVersion("1.0.0")
var v1_0_0, _ = semver.NewVersion("1.0.0")
//nolint:revive,stylecheck
var v1_0_0_PRE, _ = semver.NewVersion("1.0.0-prerelease")
//nolint:revive,stylecheck
var v1_0_0_BETA_AND_META, _ = semver.NewVersion("1.0.0-beta+very-meta")

tests := []struct {
arg string
expectVersion bool
expectError bool
arg string
expectedVersion *semver.Version
expectError bool
}{
{
arg: "mongodb/atlas-cli-plugin-example",
expectVersion: false,
expectError: false,
arg: "mongodb/atlas-cli-plugin-example",
expectedVersion: nil,
expectError: false,
},
{
arg: "mongodb/atlas-cli-plugin-example@1.0.0",
expectedVersion: v1_0_0,
expectError: false,
},
{
arg: "mongodb/atlas-cli-plugin-example@v1.0.0",
expectedVersion: v1_0_0,
expectError: false,
},
{
arg: "mongodb/atlas-cli-plugin-example@1.0.0-prerelease",
expectedVersion: v1_0_0_PRE,
expectError: false,
},
{
arg: "mongodb/atlas-cli-plugin-example@1.0.0",
expectVersion: true,
expectError: false,
arg: "mongodb/atlas-cli-plugin-example@1.0.0-beta+very-meta",
expectedVersion: v1_0_0_BETA_AND_META,
expectError: false,
},
{
arg: "mongodb/atlas-cli-plugin-example@",
expectVersion: false,
expectError: true,
arg: "mongodb/atlas-cli-plugin-example@",
expectedVersion: nil,
expectError: true,
},
{
arg: "mongodb/atlas-cli-plugin-example/",
expectVersion: false,
expectError: false,
arg: "mongodb/atlas-cli-plugin-example/",
expectedVersion: nil,
expectError: false,
},
{
arg: "mongodb/atlas-cli-plugin-example/@v1",
expectVersion: true,
expectError: false,
arg: "mongodb/atlas-cli-plugin-example/@v1",
expectedVersion: v1_0_0,
expectError: false,
},
{
arg: "https://github.com/mongodb/atlas-cli-plugin-example",
expectVersion: false,
expectError: false,
arg: "https://github.com/mongodb/atlas-cli-plugin-example",
expectedVersion: nil,
expectError: false,
},
{
arg: "https://github.com/mongodb/atlas-cli-plugin-example@v1.0",
expectVersion: false,
expectError: false,
arg: "https://github.com/mongodb/atlas-cli-plugin-example@v1.0",
expectedVersion: v1_0_0,
expectError: false,
},
{
arg: "github.com/mongodb/atlas-cli-plugin-example/",
expectVersion: false,
expectError: false,
arg: "github.com/mongodb/atlas-cli-plugin-example/",
expectedVersion: nil,
expectError: false,
},
{
arg: "github.com/mongodb/atlas-cli-plugin-example/@v1.0.0",
expectVersion: true,
expectError: false,
arg: "github.com/mongodb/atlas-cli-plugin-example/@v1.0.0",
expectedVersion: v1_0_0,
expectError: false,
},
{
arg: "/mongodb/atlas-cli-plugin-example/",
expectVersion: false,
expectError: true,
arg: "/mongodb/atlas-cli-plugin-example/",
expectedVersion: nil,
expectError: true,
},
{
arg: "mongodb@atlas-cli-plugin-example",
expectVersion: false,
expectError: true,
arg: "mongodb@atlas-cli-plugin-example",
expectedVersion: nil,
expectError: true,
},
{
arg: "mongodb@atlas-cli-plugin-example@1.0",
expectVersion: false,
expectError: true,
arg: "mongodb@atlas-cli-plugin-example@1.0",
expectedVersion: nil,
expectError: true,
},
{
arg: "invalidArgString",
expectVersion: false,
expectError: true,
arg: "invalidArgString",
expectedVersion: nil,
expectError: true,
},
}

Expand All @@ -291,8 +311,12 @@ func Test_parseGithubRepoValues(t *testing.T) {
if githubRelease.name != expectedName {
t.Errorf("expected name: %s, got: %s", expectedName, githubRelease.owner)
}
if tt.expectVersion && !expectedVersion.Equal(githubRelease.version) {
t.Errorf("expected version: %s, got: %s", expectedVersion.String(), githubRelease.version.String())
if tt.expectedVersion != nil && !tt.expectedVersion.Equal(githubRelease.version) {
t.Errorf("expected version: %s, got: %s", tt.expectedVersion.String(), githubRelease.version.String())
}

if tt.expectedVersion == nil && githubRelease.version != nil {
t.Errorf("expected version to be nil, got: %s", githubRelease.version.String())
}
}
})
Expand Down Expand Up @@ -352,3 +376,78 @@ func Test_getPluginDirectoryName(t *testing.T) {
githubAsset := &GithubAsset{owner: "owner", name: "name"}
require.Equal(t, "owner@name", githubAsset.getPluginDirectoryName())
}

func Test_getLatestStableRelease(t *testing.T) {
tests := []struct {
name string
releases []*github.RepositoryRelease
expected *github.RepositoryRelease
}{
{
name: "Single valid value",
releases: []*github.RepositoryRelease{
{
TagName: pointer.Get("v1.0.0"),
},
},
expected: &github.RepositoryRelease{
TagName: pointer.Get("v1.0.0"),
},
},
{
name: "Single invalid value",
releases: []*github.RepositoryRelease{
{
TagName: pointer.Get("test"),
},
},
expected: nil,
},
{
name: "Single valid pre-release value",
releases: []*github.RepositoryRelease{
{
TagName: pointer.Get("v1.0.0-pre"),
},
},
expected: nil,
},
{
name: "Multiple",
releases: []*github.RepositoryRelease{
{
TagName: pointer.Get("v2.0.0-pre"),
},
{
TagName: pointer.Get("v2.0.0-beta"),
},
{
TagName: pointer.Get("v1.2.1"),
},
{
TagName: pointer.Get("v1.2.0"),
},
{
TagName: pointer.Get("v1.1.0"),
},
{
TagName: pointer.Get("v1.0.1"),
},
{
TagName: pointer.Get("v1.0.0"),
},
},
expected: &github.RepositoryRelease{
TagName: pointer.Get("v1.2.1"),
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := getLatestStableRelease(tt.releases)

assert.Equal(t, tt.expected, actual)
})
}
}
4 changes: 3 additions & 1 deletion internal/cli/plugin/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"os"
"path"
"regexp"
"strings"

"github.com/Masterminds/semver/v3"
"github.com/google/go-github/v61/github"
Expand Down Expand Up @@ -55,7 +56,7 @@ func printPluginUpdateWarning(p *plugin.Plugin, err error) {

// extract plugin specifier and version given the input argument of the update command.
func extractPluginSpecifierAndVersionFromArg(arg string) (string, *semver.Version, error) {
regexPattern := `^(?P<pluginValue>[^\s@]+)(@(?P<version>v?(\d+)(\.\d+)?(\.\d+)?|latest))?$`
regexPattern := `^(?P<pluginValue>[^\s@]+)(@(?P<version>.+))?$`
regex, err := regexp.Compile(regexPattern)
if err != nil {
return "", nil, fmt.Errorf("error compiling regex: %w", err)
Expand All @@ -78,6 +79,7 @@ func extractPluginSpecifierAndVersionFromArg(arg string) (string, *semver.Versio
var version *semver.Version

if versionValue, ok := groupMap["version"]; ok && versionValue != latest && versionValue != "" {
versionValue := strings.TrimPrefix(versionValue, "v")
semverVersion, err := semver.NewVersion(versionValue)
if err != nil {
return "", nil, fmt.Errorf(`the specified version "%s" is invalid, it needs to follow the rules of Semantic Versioning`, versionValue)
Expand Down
Loading
Loading