From b6bf3905f37b13863efc5c5309bb154853ac6daa Mon Sep 17 00:00:00 2001
From: Scott Rigby <scott@r6by.com>
Date: Mon, 10 Jan 2022 14:47:45 -0500
Subject: [PATCH] Implement reusable GetTagMatchingVersionOrConstraint

Largely borrowed from (IndexFile).Get. However there is not currently a nice
way to make this code also usable to the repo package, as IndexFile depends on
a list of index Entries containing a nexted version.

We could refactor this later to somehow use the same shared function, but for
now keeping separate.

Signed-off-by: Scott Rigby <scott@r6by.com>
---
 internal/experimental/registry/util.go | 31 ++++++++++++++++++++++++++
 pkg/getter/ocigetter.go                | 27 +++++++++++-----------
 2 files changed, 44 insertions(+), 14 deletions(-)

diff --git a/internal/experimental/registry/util.go b/internal/experimental/registry/util.go
index 909a803a..36713676 100644
--- a/internal/experimental/registry/util.go
+++ b/internal/experimental/registry/util.go
@@ -23,6 +23,8 @@ import (
 	"io"
 	"strings"
 
+	"github.com/Masterminds/semver"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	orascontext "oras.land/oras-go/pkg/context"
 	"oras.land/oras-go/pkg/registry"
@@ -46,6 +48,35 @@ func ContainsTag(tags []string, tag string) bool {
 	return false
 }
 
+func GetTagMatchingVersionOrConstraint(tags []string, versionString string) (string, error) {
+	var constraint *semver.Constraints
+	if versionString == "" {
+		// If string is empty, set wildcard constraint
+		constraint, _ = semver.NewConstraint("*")
+	} else {
+		// Otherwise set constraint to the string given
+		var err error
+		constraint, err = semver.NewConstraint(versionString)
+		if err != nil {
+			return "", err
+		}
+	}
+
+	// Otherwise try to find the first available version matching the string,
+	// in case it is a constraint
+	for _, v := range tags {
+		test, err := semver.NewVersion(v)
+		if err != nil {
+			continue
+		}
+		if constraint.Check(test) {
+			return v, nil
+		}
+	}
+
+	return "", errors.Errorf("Could not locate a version matching provided version string %s", versionString)
+}
+
 // extractChartMeta is used to extract a chart metadata from a byte array
 func extractChartMeta(chartData []byte) (*chart.Metadata, error) {
 	ch, err := loader.LoadArchive(bytes.NewReader(chartData))
diff --git a/pkg/getter/ocigetter.go b/pkg/getter/ocigetter.go
index fbfcf713..0de5987d 100644
--- a/pkg/getter/ocigetter.go
+++ b/pkg/getter/ocigetter.go
@@ -30,7 +30,7 @@ type OCIGetter struct {
 	opts options
 }
 
-//Get performs a Get from repo.Getter and returns the body.
+// Get performs a Get from repo.Getter and returns the body.
 func (g *OCIGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
 	for _, opt := range options {
 		opt(&g.opts)
@@ -57,23 +57,22 @@ func (g *OCIGetter) get(href string) (*bytes.Buffer, error) {
 	if err != nil {
 		return nil, err
 	}
+	if len(tags) == 0 {
+		return nil, errors.Errorf("Unable to locate any tags in provided repository: %s", ref)
+	}
 
-	//Determine if version provided. If not
+	// Determine if version provided
+	// If empty, try to get the highest available tag
+	// If exact version, try to find it
+	// If semver constraint string, try to find a match
 	providedVersion := g.opts.version
-	if g.opts.version == "" {
-		if len(tags) > 0 {
-			providedVersion = tags[0]
-		} else {
-			return nil, errors.Errorf("Unable to locate any tags in provided repository: %s", ref)
-		}
-
-	} else {
-		if !registry.ContainsTag(tags, providedVersion) {
-			return nil, errors.Errorf("Could not located provided version %s in repository %s", providedVersion, ref)
-		}
+
+	tag, err := registry.GetTagMatchingVersionOrConstraint(tags, providedVersion)
+	if err != nil {
+		return nil, err
 	}
 
-	ref = fmt.Sprintf("%s:%s", ref, providedVersion)
+	ref = fmt.Sprintf("%s:%s", ref, tag)
 
 	result, err := client.Pull(ref, pullOpts...)
 	if err != nil {
-- 
GitLab