From af09eeeab48c4163071c2672264e43c2ae22eeb1 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 11 Nov 2021 16:19:03 +0000 Subject: [PATCH] Sort versions from releases JSON response (#37) * Sort versions from releases JSON response * bump Go requirement to 1.16 (for go-cmp) * add E2E test for Versions.List --- .go-version | 2 +- fs/fs_unix.go | 1 + fs/fs_unix_test.go | 1 + go.mod | 3 +- internal/releasesjson/checksum_downloader.go | 4 +- internal/releasesjson/product_version.go | 41 ++++++++++++++ internal/releasesjson/releases.go | 20 ++----- releases/latest_version.go | 13 ++--- releases/versions.go | 15 +++-- releases/versions_test.go | 59 ++++++++++++++++++++ 10 files changed, 124 insertions(+), 35 deletions(-) create mode 100644 internal/releasesjson/product_version.go create mode 100644 releases/versions_test.go diff --git a/.go-version b/.go-version index 141f2e8..15b989e 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.15.0 +1.16.0 diff --git a/fs/fs_unix.go b/fs/fs_unix.go index 43e87e5..95c5c11 100644 --- a/fs/fs_unix.go +++ b/fs/fs_unix.go @@ -1,4 +1,5 @@ //go:build !windows +// +build !windows package fs diff --git a/fs/fs_unix_test.go b/fs/fs_unix_test.go index cd58165..75c4535 100644 --- a/fs/fs_unix_test.go +++ b/fs/fs_unix_test.go @@ -1,4 +1,5 @@ //go:build !windows +// +build !windows package fs diff --git a/go.mod b/go.mod index 13c4934..89617e5 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,10 @@ module github.com/hashicorp/hc-install -go 1.15 +go 1.16 require ( github.com/go-git/go-git/v5 v5.4.2 + github.com/google/go-cmp v0.3.0 github.com/hashicorp/go-checkpoint v0.5.0 github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-multierror v1.1.1 diff --git a/internal/releasesjson/checksum_downloader.go b/internal/releasesjson/checksum_downloader.go index e37b985..4936dda 100644 --- a/internal/releasesjson/checksum_downloader.go +++ b/internal/releasesjson/checksum_downloader.go @@ -51,7 +51,7 @@ func (cd *ChecksumDownloader) DownloadAndVerifyChecksums() (ChecksumFileMap, err client := httpclient.NewHTTPClient() sigURL := fmt.Sprintf("%s/%s/%s/%s", cd.BaseURL, url.PathEscape(cd.ProductVersion.Name), - url.PathEscape(cd.ProductVersion.Version), + url.PathEscape(cd.ProductVersion.RawVersion), url.PathEscape(sigFilename)) cd.Logger.Printf("downloading signature from %s", sigURL) sigResp, err := client.Get(sigURL) @@ -67,7 +67,7 @@ func (cd *ChecksumDownloader) DownloadAndVerifyChecksums() (ChecksumFileMap, err shasumsURL := fmt.Sprintf("%s/%s/%s/%s", cd.BaseURL, url.PathEscape(cd.ProductVersion.Name), - url.PathEscape(cd.ProductVersion.Version), + url.PathEscape(cd.ProductVersion.RawVersion), url.PathEscape(cd.ProductVersion.SHASUMS)) cd.Logger.Printf("downloading checksums from %s", shasumsURL) sumsResp, err := client.Get(shasumsURL) diff --git a/internal/releasesjson/product_version.go b/internal/releasesjson/product_version.go new file mode 100644 index 0000000..5eecb01 --- /dev/null +++ b/internal/releasesjson/product_version.go @@ -0,0 +1,41 @@ +package releasesjson + +import "github.com/hashicorp/go-version" + +// ProductVersion is a wrapper around a particular product version like +// "consul 0.5.1". A ProductVersion may have one or more builds. +type ProductVersion struct { + Name string `json:"name"` + RawVersion string `json:"version"` + Version *version.Version `json:"-"` + SHASUMS string `json:"shasums,omitempty"` + SHASUMSSig string `json:"shasums_signature,omitempty"` + SHASUMSSigs []string `json:"shasums_signatures,omitempty"` + Builds ProductBuilds `json:"builds"` +} + +type ProductVersionsMap map[string]*ProductVersion + +type ProductVersions []*ProductVersion + +func (pv ProductVersions) Len() int { + return len(pv) +} + +func (pv ProductVersions) Less(i, j int) bool { + return pv[i].Version.LessThan(pv[j].Version) +} + +func (pv ProductVersions) Swap(i, j int) { + pv[i], pv[j] = pv[j], pv[i] +} + +func (pvm ProductVersionsMap) AsSlice() ProductVersions { + versions := make(ProductVersions, 0) + + for _, pVersion := range pvm { + versions = append(versions, pVersion) + } + + return versions +} diff --git a/internal/releasesjson/releases.go b/internal/releasesjson/releases.go index 3de19fa..24e32ff 100644 --- a/internal/releasesjson/releases.go +++ b/internal/releasesjson/releases.go @@ -18,19 +18,8 @@ const defaultBaseURL = "https://releases.hashicorp.com" // Product is a top-level product like "Consul" or "Nomad". A Product may have // one or more versions. type Product struct { - Name string `json:"name"` - Versions map[string]*ProductVersion `json:"versions"` -} - -// ProductVersion is a wrapper around a particular product version like -// "consul 0.5.1". A ProductVersion may have one or more builds. -type ProductVersion struct { - Name string `json:"name"` - Version string `json:"version"` - SHASUMS string `json:"shasums,omitempty"` - SHASUMSSig string `json:"shasums_signature,omitempty"` - SHASUMSSigs []string `json:"shasums_signatures,omitempty"` - Builds ProductBuilds `json:"builds"` + Name string `json:"name"` + Versions ProductVersionsMap `json:"versions"` } type ProductBuilds []*ProductBuild @@ -71,7 +60,7 @@ func (r *Releases) SetLogger(logger *log.Logger) { r.logger = logger } -func (r *Releases) ListProductVersions(ctx context.Context, productName string) (map[string]*ProductVersion, error) { +func (r *Releases) ListProductVersions(ctx context.Context, productName string) (ProductVersionsMap, error) { client := httpclient.NewHTTPClient() productIndexURL := fmt.Sprintf("%s/%s/index.json", @@ -122,7 +111,10 @@ func (r *Releases) ListProductVersions(ctx context.Context, productName string) // Remove (currently unsupported) enterprise // version and any other "custom" build delete(p.Versions, rawVersion) + continue } + + p.Versions[rawVersion].Version = v } return p.Versions, nil diff --git a/releases/latest_version.go b/releases/latest_version.go index c0bae40..c5c1807 100644 --- a/releases/latest_version.go +++ b/releases/latest_version.go @@ -149,20 +149,15 @@ func (lv *LatestVersion) Remove(ctx context.Context) error { return nil } -func (lv *LatestVersion) findLatestMatchingVersion(pvs map[string]*rjson.ProductVersion, vc version.Constraints) (*rjson.ProductVersion, bool) { +func (lv *LatestVersion) findLatestMatchingVersion(pvs rjson.ProductVersionsMap, vc version.Constraints) (*rjson.ProductVersion, bool) { versions := make(version.Collection, 0) - for _, pv := range pvs { - v, err := version.NewVersion(pv.Version) - if err != nil { - continue - } - - if !lv.IncludePrereleases && v.Prerelease() != "" { + for _, pv := range pvs.AsSlice() { + if !lv.IncludePrereleases && pv.Version.Prerelease() != "" { // skip prereleases if desired continue } - versions = append(versions, v) + versions = append(versions, pv.Version) } if len(versions) == 0 { diff --git a/releases/versions.go b/releases/versions.go index 618ec9b..bf0f799 100644 --- a/releases/versions.go +++ b/releases/versions.go @@ -3,6 +3,7 @@ package releases import ( "context" "fmt" + "sort" "time" "github.com/hashicorp/go-version" @@ -54,21 +55,19 @@ func (v *Versions) List(ctx context.Context) ([]src.Source, error) { return nil, err } - installables := make([]src.Source, 0) - for _, pv := range pvs { - installableVersion, err := version.NewVersion(pv.Version) - if err != nil { - continue - } + versions := pvs.AsSlice() + sort.Stable(versions) - if !v.Constraints.Check(installableVersion) { + installables := make([]src.Source, 0) + for _, pv := range versions { + if !v.Constraints.Check(pv.Version) { // skip version which doesn't match constraint continue } ev := &ExactVersion{ Product: v.Product, - Version: installableVersion, + Version: pv.Version, InstallDir: v.Install.Dir, Timeout: v.Install.Timeout, diff --git a/releases/versions_test.go b/releases/versions_test.go new file mode 100644 index 0000000..7055cfc --- /dev/null +++ b/releases/versions_test.go @@ -0,0 +1,59 @@ +package releases + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/go-version" + "github.com/hashicorp/hc-install/internal/testutil" + "github.com/hashicorp/hc-install/product" + "github.com/hashicorp/hc-install/src" +) + +func TestVersions_List(t *testing.T) { + testutil.EndToEndTest(t) + + cons, err := version.NewConstraint(">= 1.0.0, < 1.0.10") + if err != nil { + t.Fatal(err) + } + + versions := &Versions{ + Product: product.Terraform, + Constraints: cons, + } + + ctx := context.Background() + sources, err := versions.List(ctx) + if err != nil { + t.Fatal(err) + } + + expectedVersions := []string{ + "1.0.0", + "1.0.1", + "1.0.2", + "1.0.3", + "1.0.4", + "1.0.5", + "1.0.6", + "1.0.7", + "1.0.8", + "1.0.9", + } + if diff := cmp.Diff(expectedVersions, sourcesToRawVersions(sources)); diff != "" { + t.Fatalf("unexpected versions: %s", diff) + } +} + +func sourcesToRawVersions(srcs []src.Source) []string { + rawVersions := make([]string, len(srcs)) + + for idx, src := range srcs { + source := src.(*ExactVersion) + rawVersions[idx] = source.Version.String() + } + + return rawVersions +}