Pick the most recent chart/tag for ambiguous semver matches

Signed-off-by: Illia Ovchynnikov <illia.ovchynnikov@gmail.com>
This commit is contained in:
Illia Ovchynnikov 2020-10-17 15:30:16 +02:00
parent ae91271dc1
commit 394b5c3bd0
No known key found for this signature in database
GPG Key ID: 4387FB2319360C11
3 changed files with 61 additions and 8 deletions

View File

@ -98,11 +98,7 @@ func (r *ChartRepository) Get(name, version string) (*repo.ChartVersion, error)
if err != nil {
continue
}
// NB: given the entries are already sorted in LoadIndex,
// there is a high probability the first match would be
// the right match to return. However, due to the fact that
// we use a different semver package than Helm does, we still
// need to sort it by our own rules.
if match != nil && !match(v) {
continue
}
@ -112,7 +108,25 @@ func (r *ChartRepository) Get(name, version string) (*repo.ChartVersion, error)
if len(filteredVersions) == 0 {
return nil, fmt.Errorf("no chart version found for %s-%s", name, version)
}
sort.Sort(sort.Reverse(filteredVersions))
// Sort versions
sort.SliceStable(filteredVersions, func(i, j int) bool {
// Reverse
return !(func() bool {
left := filteredVersions[i]
right := filteredVersions[j]
if !left.EQ(right) {
return left.LT(right)
}
// Having chart creation timestamp at our disposal, we put package with the
// same version into a chronological order. This is especially important for
// versions that differ only by build metadata, because it is not considered
// a part of the comparable version in Semver
return lookup[left.String()].Created.Before(lookup[right.String()].Created)
})()
})
latest := filteredVersions[0]
if latestStable {

View File

@ -23,6 +23,7 @@ import (
"reflect"
"strings"
"testing"
"time"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/getter"
@ -104,6 +105,11 @@ func TestChartRepository_Get(t *testing.T) {
i.Add(&chart.Metadata{Name: "chart", Version: "exact"}, "chart-exact.tgz", "http://example.com/charts", "sha256:1234567890")
i.Add(&chart.Metadata{Name: "chart", Version: "0.1.0"}, "chart-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890abc")
i.Add(&chart.Metadata{Name: "chart", Version: "0.1.1"}, "chart-0.1.1.tgz", "http://example.com/charts", "sha256:1234567890abc")
i.Add(&chart.Metadata{Name: "chart", Version: "0.1.5+b.min.minute"}, "chart-0.1.5+b.min.minute.tgz", "http://example.com/charts", "sha256:1234567890abc")
i.Entries["chart"][len(i.Entries["chart"])-1].Created = time.Now().Add(-time.Minute)
i.Add(&chart.Metadata{Name: "chart", Version: "0.1.5+a.min.hour"}, "chart-0.1.5+a.min.hour.tgz", "http://example.com/charts", "sha256:1234567890abc")
i.Entries["chart"][len(i.Entries["chart"])-1].Created = time.Now().Add(-time.Hour)
i.Add(&chart.Metadata{Name: "chart", Version: "0.1.5+c.now"}, "chart-0.1.5+c.now.tgz", "http://example.com/charts", "sha256:1234567890abc")
i.Add(&chart.Metadata{Name: "chart", Version: "0.2.0"}, "chart-0.2.0.tgz", "http://example.com/charts", "sha256:1234567890abc")
i.Add(&chart.Metadata{Name: "chart", Version: "1.0.0"}, "chart-1.0.0.tgz", "http://example.com/charts", "sha256:1234567890abc")
i.Add(&chart.Metadata{Name: "chart", Version: "1.1.0-rc.1"}, "chart-1.1.0-rc.1.tgz", "http://example.com/charts", "sha256:1234567890abc")
@ -152,6 +158,12 @@ func TestChartRepository_Get(t *testing.T) {
chartName: "non-existing",
wantErr: true,
},
{
name: "match newest if ambiguous",
chartName: "chart",
chartVersion: "0.1.5",
wantVersion: "0.1.5+c.now",
},
}
for _, tt := range tests {

View File

@ -19,6 +19,8 @@ package git
import (
"context"
"fmt"
"sort"
"time"
"github.com/blang/semver/v4"
"github.com/go-git/go-git/v5"
@ -189,7 +191,19 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth tr
}
tags := make(map[string]string)
tagTimestamps := make(map[string]time.Time)
_ = repoTags.ForEach(func(t *plumbing.Reference) error {
revision := plumbing.Revision(t.Name().String())
hash, err := repo.ResolveRevision(revision)
if err != nil {
return fmt.Errorf("unable to resolve tag revision: %w", err)
}
commit, err := repo.CommitObject(*hash)
if err != nil {
return fmt.Errorf("unable to resolve commit of a tag revision: %w", err)
}
tagTimestamps[t.Name().Short()] = commit.Committer.When
tags[t.Name().Short()] = t.Strings()[1]
return nil
})
@ -203,12 +217,25 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth tr
svTags[v.String()] = tag
}
}
if len(svers) == 0 {
return nil, "", fmt.Errorf("no match found for semver: %s", c.semVer)
}
semver.Sort(svers)
// Sort versions
sort.SliceStable(svers, func(i, j int) bool {
left := svers[i]
right := svers[j]
if !left.EQ(right) {
return left.LT(right)
}
// Having tag target timestamps at our disposal, we further try to sort
// versions into a chronological order. This is especially important for
// versions that differ only by build metadata, because it is not considered
// a part of the comparable version in Semver
return tagTimestamps[left.String()].Before(tagTimestamps[right.String()])
})
v := svers[len(svers)-1]
t := svTags[v.String()]