From 8bce29c3ab4beaa836ef58657a3b679b2e7f9189 Mon Sep 17 00:00:00 2001 From: Todd Short Date: Fri, 25 Aug 2023 10:25:33 -0400 Subject: [PATCH] Update Version regex to support ranges Fixes #345 Add positive and negative test cases. Signed-off-by: Todd Short --- api/v1alpha1/operator_types.go | 2 +- ...rators.operatorframework.io_operators.yaml | 2 +- internal/controllers/admission_test.go | 58 ++++++++++++++++++- 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/api/v1alpha1/operator_types.go b/api/v1alpha1/operator_types.go index ae3eb3985..b46997eb1 100644 --- a/api/v1alpha1/operator_types.go +++ b/api/v1alpha1/operator_types.go @@ -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*)*$` //+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. diff --git a/config/crd/bases/operators.operatorframework.io_operators.yaml b/config/crd/bases/operators.operatorframework.io_operators.yaml index 95203347b..9f96e0201 100644 --- a/config/crd/bases/operators.operatorframework.io_operators.yaml +++ b/config/crd/bases/operators.operatorframework.io_operators.yaml @@ -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 diff --git a/internal/controllers/admission_test.go b/internal/controllers/admission_test.go index ae568d019..c253ea8cc 100644 --- a/internal/controllers/admission_test.go +++ b/internal/controllers/admission_test.go @@ -61,6 +61,15 @@ 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.Y", + ">1.2.3 && <2.3.4", + ">1.2.3;<2.3.4", + "1.2.3 - 2.3.4", } for _, invalidSemver := range invalidSemvers { err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ @@ -69,7 +78,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() {