Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Masterminds/semver #374

Merged
merged 3 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/v1alpha1/operator_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type OperatorSpec struct {
PackageName string `json:"packageName"`

//+kubebuilder:validation:MaxLength:=64
//+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-]+)*))?$
//+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*)*$`
ncdc marked this conversation as resolved.
Show resolved Hide resolved
//+kubebuilder:Optional
// Version is an optional semver constraint on the package version. If not specified, the latest version available of the package will be installed.
// If specified, the specific version of the package will be installed so long as it is available in any of the content sources available.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ spec:
sources available. Examples: 1.2.3, 1.0.0-alpha, 1.0.0-rc.1 \n For
more information on semver, please see https://semver.org/"
maxLength: 64
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-]+)*))?$
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*)*$
type: string
required:
- packageName
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/operator-framework/operator-controller
go 1.20

require (
github.com/Masterminds/semver/v3 v3.2.0
github.com/blang/semver/v4 v4.0.0
github.com/go-logr/logr v1.2.4
github.com/onsi/ginkgo/v2 v2.11.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
Expand Down
59 changes: 58 additions & 1 deletion internal/controllers/admission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ var _ = Describe("Operator Spec Validations", func() {
"1.2.3-pre+bad_metadata",
"1.2.-3",
".1.2.3",
"<<1.2.3",
">>1.2.3",
">~1.2.3",
"==1.2.3",
"=!1.2.3",
"!1.2.3",
"1.Y",
">1.2.3 && <2.3.4",
">1.2.3;<2.3.4",
"1.2.3 - 2.3.4",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth including the blang '!=' equivalent here? (e.g. "!1.2.3")

Copy link
Contributor Author

@tmshort tmshort Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably.

}
for _, invalidSemver := range invalidSemvers {
err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{
Expand All @@ -69,7 +79,54 @@ var _ = Describe("Operator Spec Validations", func() {
}))

Expect(err).To(HaveOccurred(), "expected error for invalid semver %q", invalidSemver)
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-]+)*))?$'"))
// Don't need to include the whole regex, this should be enough to match the MasterMinds regex
Expect(err.Error()).To(ContainSubstring("spec.version in body should match '^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)"))
}
})
It("should pass if a valid semver range given", func() {
validSemvers := []string{
">=1.2.3",
"=>1.2.3",
">= 1.2.3",
">=v1.2.3",
">= v1.2.3",
"<=1.2.3",
"=<1.2.3",
"=1.2.3",
"!=1.2.3",
"<1.2.3",
">1.2.3",
"~1.2.2",
"~>1.2.3",
"^1.2.3",
"1.2.3",
"v1.2.3",
"1.x",
"1.X",
"1.*",
"1.2.x",
"1.2.X",
"1.2.*",
">=1.2.3 <2.3.4",
">=1.2.3,<2.3.4",
">=1.2.3, <2.3.4",
"<1.2.3||>2.3.4",
"<1.2.3|| >2.3.4",
"<1.2.3 ||>2.3.4",
"<1.2.3 || >2.3.4",
">1.0.0,<1.2.3 || >2.1.0",
"<1.2.3-abc >2.3.4-def",
"<1.2.3-abc+def >2.3.4-ghi+jkl",
}
for _, validSemver := range validSemvers {
op := operator(operatorsv1alpha1.OperatorSpec{
PackageName: "package",
Version: validSemver,
})
err := cl.Create(ctx, op)
Expect(err).NotTo(HaveOccurred(), "expected success for semver range '%q': %w", validSemver, err)
err = cl.Delete(ctx, op)
Expect(err).NotTo(HaveOccurred(), "unexpected error deleting valid semver '%q': %w", validSemver, err)
}
})
It("should fail if an invalid channel name is given", func() {
Expand Down
4 changes: 2 additions & 2 deletions internal/controllers/validators/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package validators
import (
"fmt"

bsemver "github.com/blang/semver/v4"
mmsemver "github.com/Masterminds/semver/v3"

operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
)
Expand All @@ -18,7 +18,7 @@ func validateSemver(operator *operatorsv1alpha1.Operator) error {
if operator.Spec.Version == "" {
return nil
}
if _, err := bsemver.Parse(operator.Spec.Version); err != nil {
if _, err := mmsemver.NewConstraint(operator.Spec.Version); err != nil {
return fmt.Errorf("invalid .spec.version: %w", err)
}
return nil
Expand Down
14 changes: 13 additions & 1 deletion internal/resolution/entities/bundle_entity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"testing"

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

Describe("Version", func() {
It("should return the bundle version if present", func() {
It("should return the bundle blang version if present", func() {
entity := input.NewEntity("operatorhub/prometheus/0.14.0", map[string]string{
"olm.package": "{\"packageName\":\"prometheus\",\"version\":\"0.14.0\"}",
})
Expand All @@ -57,6 +58,17 @@ var _ = Describe("BundleEntity", func() {
Expect(err).ToNot(HaveOccurred())
Expect(*version).To(Equal(bsemver.MustParse("0.14.0")))
})
It("should return the bundle Masterminds version if present", func() {
entity := input.NewEntity("operatorhub/prometheus/0.14.0", map[string]string{
"olm.package": "{\"packageName\":\"prometheus\",\"version\":\"0.14.0\"}",
})
bundleEntity := olmentity.NewBundleEntity(entity)
bVersion, err := bundleEntity.Version()
Expect(err).ToNot(HaveOccurred())
mVersion, err := mmsemver.NewVersion(bVersion.String())
Expect(err).ToNot(HaveOccurred())
Expect(*mVersion).To(Equal(*mmsemver.MustParse("0.14.0")))
})
It("should return an error if the property is not found", func() {
entity := input.NewEntity("operatorhub/prometheus/0.14.0", map[string]string{})
bundleEntity := olmentity.NewBundleEntity(entity)
Expand Down
22 changes: 21 additions & 1 deletion internal/resolution/util/predicates/predicates.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package predicates

import (
mmsemver "github.com/Masterminds/semver/v3"
bsemver "github.com/blang/semver/v4"
"github.com/operator-framework/deppy/pkg/deppy/input"

Expand All @@ -18,7 +19,26 @@
}
}

func InSemverRange(semverRange bsemver.Range) input.Predicate {
func InMastermindsSemverRange(semverRange *mmsemver.Constraints) input.Predicate {
return func(entity *input.Entity) bool {
bundleEntity := olmentity.NewBundleEntity(entity)
bVersion, err := bundleEntity.Version()
if err != nil {
return false
}
// No error should occur here because the simple version was successfully parsed by blang
// We are unaware of any tests cases that would cause one to fail but not the other
// This will cause code coverage to drop for this line. We don't ignore the error because
// there might be that one extreme edge case that might cause one to fail but not the other
mVersion, err := mmsemver.NewVersion(bVersion.String())
if err != nil {
return false

Check warning on line 35 in internal/resolution/util/predicates/predicates.go

View check run for this annotation

Codecov / codecov/patch

internal/resolution/util/predicates/predicates.go#L35

Added line #L35 was not covered by tests
}
return semverRange.Check(mVersion)
}
}

func InBlangSemverRange(semverRange bsemver.Range) input.Predicate {
return func(entity *input.Entity) bool {
bundleEntity := olmentity.NewBundleEntity(entity)
bundleVersion, err := bundleEntity.Version()
Expand Down
29 changes: 25 additions & 4 deletions internal/resolution/util/predicates/predicates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package predicates_test
import (
"testing"

mmsemver "github.com/Masterminds/semver/v3"
bsemver "github.com/blang/semver/v4"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -33,20 +34,40 @@ var _ = Describe("Predicates", func() {
})
})

Describe("InSemverRange", func() {
Describe("InMastermindsSemverRange", func() {
It("should return true when the entity has the has version in the right range", func() {
entity := input.NewEntity("test", map[string]string{
property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`,
})
inRange, err := mmsemver.NewConstraint(">=1.0.0")
Expect(err).NotTo(HaveOccurred())
notInRange, err := mmsemver.NewConstraint(">=2.0.0")
Expect(err).NotTo(HaveOccurred())
Expect(predicates.InMastermindsSemverRange(inRange)(entity)).To(BeTrue())
Expect(predicates.InMastermindsSemverRange(notInRange)(entity)).To(BeFalse())
})
It("should return false when the entity does not have a version", func() {
entity := input.NewEntity("test", map[string]string{})
inRange, err := mmsemver.NewConstraint(">=1.0.0")
Expect(err).NotTo(HaveOccurred())
Expect(predicates.InMastermindsSemverRange(inRange)(entity)).To(BeFalse())
})
})

Describe("InBlangSemverRange", func() {
It("should return true when the entity has the has version in the right range", func() {
entity := input.NewEntity("test", map[string]string{
property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`,
})
inRange := bsemver.MustParseRange(">=1.0.0")
notInRange := bsemver.MustParseRange(">=2.0.0")
Expect(predicates.InSemverRange(inRange)(entity)).To(BeTrue())
Expect(predicates.InSemverRange(notInRange)(entity)).To(BeFalse())
Expect(predicates.InBlangSemverRange(inRange)(entity)).To(BeTrue())
Expect(predicates.InBlangSemverRange(notInRange)(entity)).To(BeFalse())
})
It("should return false when the entity does not have a version", func() {
entity := input.NewEntity("test", map[string]string{})
inRange := bsemver.MustParseRange(">=1.0.0")
Expect(predicates.InSemverRange(inRange)(entity)).To(BeFalse())
Expect(predicates.InBlangSemverRange(inRange)(entity)).To(BeFalse())
})
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func (b *BundlesAndDepsVariableSource) getEntityDependencies(ctx context.Context
if err != nil {
return nil, err
}
packageDependencyBundles, err := entitySource.Filter(ctx, input.And(predicates.WithPackageName(requiredPackage.PackageName), predicates.InSemverRange(semverRange)))
packageDependencyBundles, err := entitySource.Filter(ctx, input.And(predicates.WithPackageName(requiredPackage.PackageName), predicates.InBlangSemverRange(semverRange)))
if err != nil {
return nil, err
}
Expand Down
8 changes: 4 additions & 4 deletions internal/resolution/variablesources/required_package.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"context"
"fmt"

bsemver "github.com/blang/semver/v4"
mmsemver "github.com/Masterminds/semver/v3"
"github.com/operator-framework/deppy/pkg/deppy"
"github.com/operator-framework/deppy/pkg/deppy/input"

Expand All @@ -21,14 +21,14 @@ type RequiredPackageVariableSourceOption func(*RequiredPackageVariableSource) er
func InVersionRange(versionRange string) RequiredPackageVariableSourceOption {
return func(r *RequiredPackageVariableSource) error {
if versionRange != "" {
vr, err := bsemver.ParseRange(versionRange)
vr, err := mmsemver.NewConstraint(versionRange)
if err == nil {
r.versionRange = versionRange
r.predicates = append(r.predicates, predicates.InSemverRange(vr))
r.predicates = append(r.predicates, predicates.InMastermindsSemverRange(vr))
return nil
}

return fmt.Errorf("invalid version range '%s': %v", versionRange, err)
return fmt.Errorf("invalid version range '%s': %w", versionRange, err)
}
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ var _ = Describe("RequiredPackageVariableSource", func() {
It("should filter by version range", func() {
// recreate source with version range option
var err error
rpvs, err = variablesources.NewRequiredPackageVariableSource(packageName, variablesources.InVersionRange(">=1.0.0 !2.0.0 <3.0.0"))
rpvs, err = variablesources.NewRequiredPackageVariableSource(packageName, variablesources.InVersionRange(">=1.0.0 !=2.0.0 <3.0.0"))
ncdc marked this conversation as resolved.
Show resolved Hide resolved
Expect(err).NotTo(HaveOccurred())

variables, err := rpvs.GetVariables(context.TODO(), mockEntitySource)
Expand Down
Loading