Skip to content

Commit 628ae95

Browse files
authored
Introduce Masterminds/semver (#374)
* Update Version regex to support ranges Fixes #345 Add positive and negative test cases. Signed-off-by: Todd Short <[email protected]> * Introduce Masterminds/semver Fixes #346 Add support for Masterminds/semver for .spec.Version This is a bit more entangled into the code than I expected, most instances of bsemver were replaced. Signed-off-by: Todd Short <[email protected]> * fixup! Introduce Masterminds/semver Signed-off-by: Todd Short <[email protected]> --------- Signed-off-by: Todd Short <[email protected]>
1 parent db08a62 commit 628ae95

File tree

12 files changed

+130
-17
lines changed

12 files changed

+130
-17
lines changed

api/v1alpha1/operator_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type OperatorSpec struct {
2929
PackageName string `json:"packageName"`
3030

3131
//+kubebuilder:validation:MaxLength:=64
32-
//+kubebuilder:validation:Pattern=^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$
32+
//+kubebuilder:validation:Pattern=`^(\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\^)\s*(v?(0|[1-9]\d*|[x|X|\*])(\.(0|[1-9]\d*|x|X|\*]))?(\.(0|[1-9]\d*|x|X|\*))?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?)\s*)((?:\s+|,\s*|\s*\|\|\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\^)\s*(v?(0|[1-9]\d*|x|X|\*])(\.(0|[1-9]\d*|x|X|\*))?(\.(0|[1-9]\d*|x|X|\*]))?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?)\s*)*$`
3333
//+kubebuilder:Optional
3434
// Version is an optional semver constraint on the package version. If not specified, the latest version available of the package will be installed.
3535
// If specified, the specific version of the package will be installed so long as it is available in any of the content sources available.

config/crd/bases/operators.operatorframework.io_operators.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ spec:
5151
sources available. Examples: 1.2.3, 1.0.0-alpha, 1.0.0-rc.1 \n For
5252
more information on semver, please see https://semver.org/"
5353
maxLength: 64
54-
pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$
54+
pattern: ^(\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\^)\s*(v?(0|[1-9]\d*|[x|X|\*])(\.(0|[1-9]\d*|x|X|\*]))?(\.(0|[1-9]\d*|x|X|\*))?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?)\s*)((?:\s+|,\s*|\s*\|\|\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\^)\s*(v?(0|[1-9]\d*|x|X|\*])(\.(0|[1-9]\d*|x|X|\*))?(\.(0|[1-9]\d*|x|X|\*]))?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?)\s*)*$
5555
type: string
5656
required:
5757
- packageName

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/operator-framework/operator-controller
33
go 1.20
44

55
require (
6+
github.com/Masterminds/semver/v3 v3.2.0
67
github.com/blang/semver/v4 v4.0.0
78
github.com/go-logr/logr v1.2.4
89
github.com/onsi/ginkgo/v2 v2.11.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
4848
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
4949
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
5050
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
51+
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
52+
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
5153
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
5254
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
5355
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=

internal/controllers/admission_test.go

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ var _ = Describe("Operator Spec Validations", func() {
6161
"1.2.3-pre+bad_metadata",
6262
"1.2.-3",
6363
".1.2.3",
64+
"<<1.2.3",
65+
">>1.2.3",
66+
">~1.2.3",
67+
"==1.2.3",
68+
"=!1.2.3",
69+
"!1.2.3",
70+
"1.Y",
71+
">1.2.3 && <2.3.4",
72+
">1.2.3;<2.3.4",
73+
"1.2.3 - 2.3.4",
6474
}
6575
for _, invalidSemver := range invalidSemvers {
6676
err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{
@@ -69,7 +79,54 @@ var _ = Describe("Operator Spec Validations", func() {
6979
}))
7080

7181
Expect(err).To(HaveOccurred(), "expected error for invalid semver %q", invalidSemver)
72-
Expect(err.Error()).To(ContainSubstring("spec.version in body should match '^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(-(0|[1-9]\\d*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+([0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*))?$'"))
82+
// Don't need to include the whole regex, this should be enough to match the MasterMinds regex
83+
Expect(err.Error()).To(ContainSubstring("spec.version in body should match '^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)"))
84+
}
85+
})
86+
It("should pass if a valid semver range given", func() {
87+
validSemvers := []string{
88+
">=1.2.3",
89+
"=>1.2.3",
90+
">= 1.2.3",
91+
">=v1.2.3",
92+
">= v1.2.3",
93+
"<=1.2.3",
94+
"=<1.2.3",
95+
"=1.2.3",
96+
"!=1.2.3",
97+
"<1.2.3",
98+
">1.2.3",
99+
"~1.2.2",
100+
"~>1.2.3",
101+
"^1.2.3",
102+
"1.2.3",
103+
"v1.2.3",
104+
"1.x",
105+
"1.X",
106+
"1.*",
107+
"1.2.x",
108+
"1.2.X",
109+
"1.2.*",
110+
">=1.2.3 <2.3.4",
111+
">=1.2.3,<2.3.4",
112+
">=1.2.3, <2.3.4",
113+
"<1.2.3||>2.3.4",
114+
"<1.2.3|| >2.3.4",
115+
"<1.2.3 ||>2.3.4",
116+
"<1.2.3 || >2.3.4",
117+
">1.0.0,<1.2.3 || >2.1.0",
118+
"<1.2.3-abc >2.3.4-def",
119+
"<1.2.3-abc+def >2.3.4-ghi+jkl",
120+
}
121+
for _, validSemver := range validSemvers {
122+
op := operator(operatorsv1alpha1.OperatorSpec{
123+
PackageName: "package",
124+
Version: validSemver,
125+
})
126+
err := cl.Create(ctx, op)
127+
Expect(err).NotTo(HaveOccurred(), "expected success for semver range '%q': %w", validSemver, err)
128+
err = cl.Delete(ctx, op)
129+
Expect(err).NotTo(HaveOccurred(), "unexpected error deleting valid semver '%q': %w", validSemver, err)
73130
}
74131
})
75132
It("should fail if an invalid channel name is given", func() {

internal/controllers/validators/validators.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package validators
33
import (
44
"fmt"
55

6-
bsemver "github.com/blang/semver/v4"
6+
mmsemver "github.com/Masterminds/semver/v3"
77

88
operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
99
)
@@ -18,7 +18,7 @@ func validateSemver(operator *operatorsv1alpha1.Operator) error {
1818
if operator.Spec.Version == "" {
1919
return nil
2020
}
21-
if _, err := bsemver.Parse(operator.Spec.Version); err != nil {
21+
if _, err := mmsemver.NewConstraint(operator.Spec.Version); err != nil {
2222
return fmt.Errorf("invalid .spec.version: %w", err)
2323
}
2424
return nil

internal/resolution/entities/bundle_entity_test.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"testing"
66

7+
mmsemver "github.com/Masterminds/semver/v3"
78
bsemver "github.com/blang/semver/v4"
89
. "github.com/onsi/ginkgo/v2"
910
. "github.com/onsi/gomega"
@@ -48,7 +49,7 @@ var _ = Describe("BundleEntity", func() {
4849
})
4950

5051
Describe("Version", func() {
51-
It("should return the bundle version if present", func() {
52+
It("should return the bundle blang version if present", func() {
5253
entity := input.NewEntity("operatorhub/prometheus/0.14.0", map[string]string{
5354
"olm.package": "{\"packageName\":\"prometheus\",\"version\":\"0.14.0\"}",
5455
})
@@ -57,6 +58,17 @@ var _ = Describe("BundleEntity", func() {
5758
Expect(err).ToNot(HaveOccurred())
5859
Expect(*version).To(Equal(bsemver.MustParse("0.14.0")))
5960
})
61+
It("should return the bundle Masterminds version if present", func() {
62+
entity := input.NewEntity("operatorhub/prometheus/0.14.0", map[string]string{
63+
"olm.package": "{\"packageName\":\"prometheus\",\"version\":\"0.14.0\"}",
64+
})
65+
bundleEntity := olmentity.NewBundleEntity(entity)
66+
bVersion, err := bundleEntity.Version()
67+
Expect(err).ToNot(HaveOccurred())
68+
mVersion, err := mmsemver.NewVersion(bVersion.String())
69+
Expect(err).ToNot(HaveOccurred())
70+
Expect(*mVersion).To(Equal(*mmsemver.MustParse("0.14.0")))
71+
})
6072
It("should return an error if the property is not found", func() {
6173
entity := input.NewEntity("operatorhub/prometheus/0.14.0", map[string]string{})
6274
bundleEntity := olmentity.NewBundleEntity(entity)

internal/resolution/util/predicates/predicates.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package predicates
22

33
import (
4+
mmsemver "github.com/Masterminds/semver/v3"
45
bsemver "github.com/blang/semver/v4"
56
"github.com/operator-framework/deppy/pkg/deppy/input"
67

@@ -18,7 +19,26 @@ func WithPackageName(packageName string) input.Predicate {
1819
}
1920
}
2021

21-
func InSemverRange(semverRange bsemver.Range) input.Predicate {
22+
func InMastermindsSemverRange(semverRange *mmsemver.Constraints) input.Predicate {
23+
return func(entity *input.Entity) bool {
24+
bundleEntity := olmentity.NewBundleEntity(entity)
25+
bVersion, err := bundleEntity.Version()
26+
if err != nil {
27+
return false
28+
}
29+
// No error should occur here because the simple version was successfully parsed by blang
30+
// We are unaware of any tests cases that would cause one to fail but not the other
31+
// This will cause code coverage to drop for this line. We don't ignore the error because
32+
// there might be that one extreme edge case that might cause one to fail but not the other
33+
mVersion, err := mmsemver.NewVersion(bVersion.String())
34+
if err != nil {
35+
return false
36+
}
37+
return semverRange.Check(mVersion)
38+
}
39+
}
40+
41+
func InBlangSemverRange(semverRange bsemver.Range) input.Predicate {
2242
return func(entity *input.Entity) bool {
2343
bundleEntity := olmentity.NewBundleEntity(entity)
2444
bundleVersion, err := bundleEntity.Version()

internal/resolution/util/predicates/predicates_test.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package predicates_test
33
import (
44
"testing"
55

6+
mmsemver "github.com/Masterminds/semver/v3"
67
bsemver "github.com/blang/semver/v4"
78
. "github.com/onsi/ginkgo/v2"
89
. "github.com/onsi/gomega"
@@ -33,20 +34,40 @@ var _ = Describe("Predicates", func() {
3334
})
3435
})
3536

36-
Describe("InSemverRange", func() {
37+
Describe("InMastermindsSemverRange", func() {
38+
It("should return true when the entity has the has version in the right range", func() {
39+
entity := input.NewEntity("test", map[string]string{
40+
property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`,
41+
})
42+
inRange, err := mmsemver.NewConstraint(">=1.0.0")
43+
Expect(err).NotTo(HaveOccurred())
44+
notInRange, err := mmsemver.NewConstraint(">=2.0.0")
45+
Expect(err).NotTo(HaveOccurred())
46+
Expect(predicates.InMastermindsSemverRange(inRange)(entity)).To(BeTrue())
47+
Expect(predicates.InMastermindsSemverRange(notInRange)(entity)).To(BeFalse())
48+
})
49+
It("should return false when the entity does not have a version", func() {
50+
entity := input.NewEntity("test", map[string]string{})
51+
inRange, err := mmsemver.NewConstraint(">=1.0.0")
52+
Expect(err).NotTo(HaveOccurred())
53+
Expect(predicates.InMastermindsSemverRange(inRange)(entity)).To(BeFalse())
54+
})
55+
})
56+
57+
Describe("InBlangSemverRange", func() {
3758
It("should return true when the entity has the has version in the right range", func() {
3859
entity := input.NewEntity("test", map[string]string{
3960
property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`,
4061
})
4162
inRange := bsemver.MustParseRange(">=1.0.0")
4263
notInRange := bsemver.MustParseRange(">=2.0.0")
43-
Expect(predicates.InSemverRange(inRange)(entity)).To(BeTrue())
44-
Expect(predicates.InSemverRange(notInRange)(entity)).To(BeFalse())
64+
Expect(predicates.InBlangSemverRange(inRange)(entity)).To(BeTrue())
65+
Expect(predicates.InBlangSemverRange(notInRange)(entity)).To(BeFalse())
4566
})
4667
It("should return false when the entity does not have a version", func() {
4768
entity := input.NewEntity("test", map[string]string{})
4869
inRange := bsemver.MustParseRange(">=1.0.0")
49-
Expect(predicates.InSemverRange(inRange)(entity)).To(BeFalse())
70+
Expect(predicates.InBlangSemverRange(inRange)(entity)).To(BeFalse())
5071
})
5172
})
5273

internal/resolution/variablesources/bundles_and_dependencies.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func (b *BundlesAndDepsVariableSource) getEntityDependencies(ctx context.Context
9191
if err != nil {
9292
return nil, err
9393
}
94-
packageDependencyBundles, err := entitySource.Filter(ctx, input.And(predicates.WithPackageName(requiredPackage.PackageName), predicates.InSemverRange(semverRange)))
94+
packageDependencyBundles, err := entitySource.Filter(ctx, input.And(predicates.WithPackageName(requiredPackage.PackageName), predicates.InBlangSemverRange(semverRange)))
9595
if err != nil {
9696
return nil, err
9797
}

0 commit comments

Comments
 (0)