Skip to content

Commit 7326d40

Browse files
committed
SQUASH: separate function with tests
1 parent 9be61f0 commit 7326d40

File tree

4 files changed

+125
-55
lines changed

4 files changed

+125
-55
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module sigs.k8s.io/controller-runtime
33
go 1.24.0
44

55
require (
6-
github.com/blang/semver/v4 v4.0.0
6+
github.com/Masterminds/semver/v3 v3.4.0
77
github.com/evanphx/json-patch/v5 v5.9.11
88
github.com/fsnotify/fsnotify v1.9.0
99
github.com/go-logr/logr v1.4.2
@@ -37,6 +37,7 @@ require (
3737
cel.dev/expr v0.24.0 // indirect
3838
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
3939
github.com/beorn7/perks v1.0.1 // indirect
40+
github.com/blang/semver/v4 v4.0.0 // indirect
4041
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
4142
github.com/cespare/xxhash/v2 v2.3.0 // indirect
4243
github.com/davecgh/go-spew v1.1.1 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
22
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
3+
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
4+
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
35
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
46
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
57
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=

pkg/envtest/binaries.go

Lines changed: 49 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,9 @@ import (
3232
"path"
3333
"path/filepath"
3434
"runtime"
35-
"sort"
3635
"strings"
3736

38-
"github.com/blang/semver/v4"
37+
"github.com/Masterminds/semver/v3"
3938
"sigs.k8s.io/yaml"
4039
)
4140

@@ -111,6 +110,33 @@ type archive struct {
111110
SelfLink string `json:"selfLink"`
112111
}
113112

113+
// interpretVersion parses a "SemVer-ish" request into a single [semver.Version]
114+
// or a looser set of [semver.Constraints].
115+
//
116+
// This does *not* understand wildcards, comparators, ranges, or other special syntax.
117+
func interpretVersion(request string) (*semver.Version, *semver.Constraints) {
118+
// When no version is requested, look for one in the release index.
119+
if request == "" {
120+
c, _ := semver.NewConstraint("*")
121+
return nil, c
122+
}
123+
124+
// When an exact version is requested, don't look for it in the release index.
125+
if v, err := semver.StrictNewVersion(strings.TrimPrefix(request, "v")); err == nil {
126+
return v, nil
127+
}
128+
129+
// When it is an inexact version, turn it into a search of the release index.
130+
if _, err := semver.NewVersion(request); err == nil {
131+
if c, err := semver.NewConstraint(request); err == nil {
132+
return nil, c
133+
}
134+
}
135+
136+
// The request isn't a version at all.
137+
return nil, nil
138+
}
139+
114140
func downloadBinaryAssets(ctx context.Context, binaryAssetsDirectory, binaryAssetsVersion, binaryAssetsIndexURL string) (string, string, string, error) {
115141
if binaryAssetsIndexURL == "" {
116142
binaryAssetsIndexURL = DefaultBinaryAssetsIndexURL
@@ -124,54 +150,24 @@ func downloadBinaryAssets(ctx context.Context, binaryAssetsDirectory, binaryAsse
124150
}
125151
}
126152

127-
var requestedRange semver.Range
128-
if binaryAssetsVersion != "" {
129-
binaryAssetsVersion = strings.TrimPrefix(binaryAssetsVersion, "v")
130-
parsedVersion, errV := semver.ParseTolerant(binaryAssetsVersion)
131-
parsedRange, errR := semver.ParseRange(binaryAssetsVersion)
132-
133-
switch {
134-
// When an exact version is requested, don't look for it in the release index.
135-
case errV == nil && parsedVersion.String() == binaryAssetsVersion:
136-
requestedRange = nil
137-
138-
// When the version looks like a range, apply it to the index.
139-
case errR == nil:
140-
requestedRange = parsedRange
141-
142-
// When the version isn't exact, turn it into a range.
143-
//
144-
// The `setup-envtest` tool interprets a partial version to mean "latest stable with that prefix."
145-
// For example, "1" and "1.2" are akin to "1.x.x" and "1.2.x" in [semver.ParseRange].
146-
// [semver.ParseTolerant] fills in missing minor or patch with "0", so this replaces those with "x".
147-
//
148-
// That *should* produce a valid range. If it doesn't, use the original value and skip the index.
149-
case errV == nil:
150-
suffix := strings.TrimPrefix(parsedVersion.FinalizeVersion(), binaryAssetsVersion)
151-
suffix = strings.ReplaceAll(suffix, "0", "x")
152-
parsedRange, errR = semver.ParseRange(binaryAssetsVersion + suffix)
153-
154-
if errR == nil {
155-
requestedRange = parsedRange
156-
} else {
157-
requestedRange = nil
158-
}
159-
}
160-
} else {
161-
// When no version is requested, look for one in the release index.
162-
requestedRange = semver.MustParseRange(">0.0.0")
153+
version, search := interpretVersion(binaryAssetsVersion)
154+
155+
// When an exact version is requested, use its canonical form to download without the release index.
156+
if version != nil {
157+
binaryAssetsVersion = version.String()
163158
}
164159

165-
// When a range a versions is requested, select one from the release index.
160+
// When a range of versions is requested, select a stable one from the release index.
166161
var binaryAssetsIndex *index
167-
if requestedRange != nil {
162+
if search != nil {
168163
var err error
169164
binaryAssetsIndex, err = getIndex(ctx, binaryAssetsIndexURL)
170165
if err != nil {
171166
return "", "", "", err
172167
}
173168

174-
binaryAssetsVersion, err = latestStableVersionFromIndex(binaryAssetsIndex, requestedRange)
169+
search.IncludePrerelease = false
170+
binaryAssetsVersion, err = latestVersionFromIndex(binaryAssetsIndex, search)
175171
if err != nil {
176172
return "", "", "", err
177173
}
@@ -293,34 +289,33 @@ func downloadBinaryAssetsArchive(ctx context.Context, index *index, version stri
293289
return readBody(resp, out, archiveName, archive.Hash)
294290
}
295291

296-
func latestStableVersionFromIndex(index *index, satisfying semver.Range) (string, error) {
292+
func latestVersionFromIndex(index *index, satisfying *semver.Constraints) (string, error) {
297293
if len(index.Releases) == 0 {
298-
return "", fmt.Errorf("failed to find latest stable version from index: index is empty")
294+
return "", fmt.Errorf("failed to find latest version from index: index is empty")
299295
}
300296

301-
parsedVersions := []semver.Version{}
297+
var maxVersion *semver.Version
302298
for releaseVersion := range index.Releases {
303-
v, err := semver.ParseTolerant(releaseVersion)
299+
v, err := semver.NewVersion(releaseVersion)
304300
if err != nil {
305301
return "", fmt.Errorf("failed to parse version %q: %w", releaseVersion, err)
306302
}
307303

308-
// Filter out pre-releases and undesirable versions.
309-
if len(v.Pre) > 0 || !satisfying(v) {
304+
// Filter out undesirable versions.
305+
if !satisfying.Check(v) {
310306
continue
311307
}
312308

313-
parsedVersions = append(parsedVersions, v)
309+
if maxVersion == nil || v.GreaterThan(maxVersion) {
310+
maxVersion = v
311+
}
314312
}
315313

316-
if len(parsedVersions) == 0 {
317-
return "", fmt.Errorf("failed to find latest stable version from index: index does not have stable versions")
314+
if maxVersion == nil {
315+
return "", fmt.Errorf("failed to find latest version from index: nothing matches %q", satisfying)
318316
}
319317

320-
sort.Slice(parsedVersions, func(i, j int) bool {
321-
return parsedVersions[i].GT(parsedVersions[j])
322-
})
323-
return "v" + parsedVersions[0].String(), nil
318+
return "v" + maxVersion.String(), nil
324319
}
325320

326321
func getIndex(ctx context.Context, indexURL string) (*index, error) {

pkg/envtest/binaries_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,85 @@ import (
2828
"os"
2929
"path"
3030
"runtime"
31+
"testing"
3132

33+
"github.com/Masterminds/semver/v3"
3234
. "github.com/onsi/ginkgo/v2"
3335
. "github.com/onsi/gomega"
3436
"github.com/onsi/gomega/ghttp"
3537
"sigs.k8s.io/yaml"
3638
)
3739

40+
func TestInterpretVersion(t *testing.T) {
41+
t.Parallel()
42+
43+
testCases := []struct {
44+
name string
45+
in []string
46+
47+
canonical string
48+
included []string
49+
excluded []string
50+
}{
51+
{
52+
name: "an exact release is not a search",
53+
in: []string{"v1.2.3", "1.2.3"},
54+
canonical: "1.2.3",
55+
},
56+
{
57+
name: "an exact prerelease is not a search",
58+
in: []string{"v1.2.0-beta.1", "1.2.0-beta.1"},
59+
canonical: "1.2.0-beta.1",
60+
},
61+
{
62+
name: "a version prefix is a search",
63+
in: []string{"v1.20", "1.20", "001.20"},
64+
included: []string{"1.20.0", "v1.20.1", "1.20.999"},
65+
excluded: []string{"1.19.999", "1.20.0-beta.1", "v1.20.0-rc.1", "v1.21.0"},
66+
},
67+
{
68+
name: "a version prefix can be short",
69+
in: []string{"v2", "2", "02"},
70+
included: []string{"2.0.0", "v2.5.6"},
71+
excluded: []string{"1.3.0", "v2.33.0-beta.0", "3.1.0"},
72+
},
73+
{
74+
name: "weird stuff is ignored",
75+
in: []string{
76+
"asdf", "version", "vegeta4",
77+
"the.1", "2ne1",
78+
"=7.8.9", "10.x", "*",
79+
},
80+
// no canonical version and no search
81+
},
82+
}
83+
84+
for _, tc := range testCases {
85+
t.Run(tc.name, func(t *testing.T) {
86+
for _, in := range tc.in {
87+
g := NewWithT(t)
88+
version, search := interpretVersion(in)
89+
90+
if version != nil {
91+
g.Expect(version.String()).To(Equal(tc.canonical))
92+
} else {
93+
g.Expect(tc.canonical).To(BeEmpty())
94+
}
95+
96+
if search != nil {
97+
compare := func(s string) bool { return search.Check(semver.MustParse(s)) }
98+
99+
g.Expect(tc.included).To(HaveEach(WithTransform(compare, BeTrue())))
100+
g.Expect(tc.excluded).To(HaveEach(WithTransform(compare, BeFalse())))
101+
} else {
102+
g.Expect(tc.included).To(BeEmpty())
103+
g.Expect(tc.excluded).To(BeEmpty())
104+
}
105+
}
106+
})
107+
}
108+
}
109+
38110
var _ = Describe("Test download binaries", func() {
39111
var downloadDirectory string
40112
var server *ghttp.Server

0 commit comments

Comments
 (0)