From bf0b4128301bed0336fdbb6bfad452c847082dac Mon Sep 17 00:00:00 2001 From: David MICHENEAU Date: Wed, 30 Oct 2024 15:05:18 +0100 Subject: [PATCH] feat: Add calver modifier and prerelease --- api/v1alpha1/image_types.go | 2 +- .../bases/kimup.cloudavenue.io_images.yaml | 243 -------------- docs/rules/calver.md | 42 ++- internal/rules/calver.go | 130 +++++++- internal/rules/calver_test.go | 312 +++++++++++++++--- internal/rules/rules.go | 17 +- .../crd/kimup.cloudavenue.io_images.yaml | 4 + 7 files changed, 438 insertions(+), 312 deletions(-) delete mode 100644 config/crd/bases/kimup.cloudavenue.io_images.yaml diff --git a/api/v1alpha1/image_types.go b/api/v1alpha1/image_types.go index 0a13c49..38bf2da 100644 --- a/api/v1alpha1/image_types.go +++ b/api/v1alpha1/image_types.go @@ -76,7 +76,7 @@ type ( Name string `json:"name"` // +kubebuilder:validation:Required - // +kubebuilder:validation:Enum=calver-major;calver-minor;calver-patch;semver-major;semver-minor;semver-patch;regex;always + // +kubebuilder:validation:Enum=calver-major;calver-minor;calver-patch;calver-prerelease;semver-major;semver-minor;semver-patch;regex;always Type rules.Name `json:"type"` // +kubebuilder:validation:Optional diff --git a/config/crd/bases/kimup.cloudavenue.io_images.yaml b/config/crd/bases/kimup.cloudavenue.io_images.yaml deleted file mode 100644 index 9d3842a..0000000 --- a/config/crd/bases/kimup.cloudavenue.io_images.yaml +++ /dev/null @@ -1,243 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.16.1 - name: images.kimup.cloudavenue.io -spec: - group: kimup.cloudavenue.io - names: - kind: Image - listKind: ImageList - plural: images - singular: image - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.image - name: Image - type: string - - jsonPath: .status.tag - name: Tag - type: string - - jsonPath: .status.result - name: Last-Result - type: string - - jsonPath: .status.time - name: Last-Sync - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: Image is the Schema for the images API - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: ImageSpec defines the desired state of Image - properties: - baseTag: - default: latest - example: v1.2.0 - type: string - image: - type: string - imagePullSecrets: - items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: array - insecureSkipTLSVerify: - default: false - example: true - type: boolean - rules: - items: - description: ImageRule - properties: - actions: - items: - description: ImageAction - properties: - data: - properties: - value: - description: |- - Value is a string value to assign to the key. - if ValueFrom is specified, this value is ignored. - type: string - valueFrom: - description: ValueFrom is a reference to a field in - a secret or config map. - properties: - alertConfigRef: - description: AlertConfigRef is a reference to - a field in an alert configuration. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - configMapKeyRef: - description: ConfigMapKeyRef is a reference to - a field in a config map. - properties: - key: - description: The key to select. - type: string - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secretKeyRef: - description: SecretKeyRef is a reference to a - field in a secret. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - type: object - type: - enum: - - apply - - request-approval - - alert-discord - type: string - required: - - type - type: object - minItems: 1 - type: array - name: - type: string - type: - enum: - - calver-major - - calver-minor - - calver-patch - - semver-major - - semver-minor - - semver-patch - - regex - - always - type: string - value: - type: string - required: - - actions - - name - - type - type: object - minItems: 1 - type: array - triggers: - items: - description: ImageTrigger - properties: - type: - enum: - - crontab - - webhook - type: string - value: - type: string - required: - - type - type: object - minItems: 1 - type: array - required: - - image - - rules - - triggers - type: object - status: - description: ImageStatus defines the observed state of Image - properties: - result: - type: string - tag: - description: |- - INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - Important: Run "make" to regenerate code after modifying this file - type: string - time: - type: string - required: - - result - - tag - - time - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/docs/rules/calver.md b/docs/rules/calver.md index 7c052eb..d42e9e1 100644 --- a/docs/rules/calver.md +++ b/docs/rules/calver.md @@ -8,35 +8,67 @@ hide: The `calver` rule allows you to define a rule that will be executed when the image is updated with a new calver version. It follows the [Calendar Versioning](https://calver.org/) specification. A lot of options are available to match the version you want to update. -The format of the version [following this logic regex](https://regex101.com/r/25eVYJ/2). + +The format of the version [following this logic regex](https://regex101.com/r/dRh6UI/1). +```regex +^([0-9]{4}|[0-9]{2})(\.[0-9]{1,2})?(\.[0-9]{1,2})?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?$ +``` + +Format allowed: +```s +# YYYY.MM.XX +2024 +2024.01 +2024.01.01 + +# YY.MM.XX +24 +24.01 + +# YY.M.X +24.1 +24.1.1 + +2024.01.01-dev.1 +``` + * `calver-major`: Update the image with the latest major version. * `calver-minor`: Update the image with the latest minor version. * `calver-patch`: Update the image with the latest patch version. +* `calver-prerelease`: Update the image with the latest prerelease version. **`calver-major`** is the most restrictive and will only update the image when the major version is updated [calver documentation](https://calver.org/). +Most of time the major is a year date (eg: `2024`, `2025`, `2026`, ...). ``` { .yaml .no-copy title="calver rule" } version: 2024.0.0 -Match: >=2024.*.* # (1) +Match: >=2025.*.* # (1) ``` 1. :man_raising_hand: For more information about the calver range, you can check the [calver documentation](https://calver.org/). **`calver-minor`** is less restrictive and will update the image when the minor version is updated. +Most of time the minor is a month date (eg: `2024.01`, `2024.02`, `2024.03`, ...). ``` { .yaml .no-copy title="calver rule" } version: 2024.0.0 -Match: >=2024.1.* <2 # (1) +Match: >=2024.1.* and <2025.0.0 # (1) ``` 1. :man_raising_hand: For more information about the calver range, you can check the [calver documentation](https://calver.org/). **`calver-patch`** is the least restrictive and will update the image when the patch version is updated. +Most of time the patch is a day date (eg: `2024.01.01`, `2024.01.02`, `2024.01.03`, ...). ``` { .yaml .no-copy title="calver rule" } version: 2024.0.0 -Match: >=2024.0.1 <2024.1.0 # (1) +Match: >=2024.0.1 and < 2024.1.0 # (1) ``` -1. :man_raising_hand: For more information about the calver range, you can check the [calver documentation](https://calver.org/). +**`calver-prerelease`** is the least restrictive and will update the image when the prerelease version is updated. +The prerelease version is the part after the `-xxx.` in the version. It's an incremental number. (eg: `2024.0.0-dev.1`, `2024.0.0-dev.2`, `2024.0.0-dev.3`,...). +``` { .yaml .no-copy title="calver rule" } +version: 2024.0.0-dev.0 +Match: >=2024.0.0-dev.1 +``` ## Who to use diff --git a/internal/rules/calver.go b/internal/rules/calver.go index 26dbf73..6b917de 100644 --- a/internal/rules/calver.go +++ b/internal/rules/calver.go @@ -1,6 +1,8 @@ package rules import ( + "fmt" + "github.com/shipengqi/vc" "github.com/orange-cloudavenue/kube-image-updater/internal/log" @@ -21,12 +23,23 @@ type ( calverPatch struct { rule } + + // calverPrerelease - The prerelease is an optional part of the version. + calverPrerelease struct { + rule + } ) +// New a function to generate a Comparable instance. +var funcParseCalver = func(s string) (vc.Comparable, error) { + return vc.NewCalVerStr(s) +} + func init() { register(CalverMajor, &calverMajor{}) register(CalverMinor, &calverMinor{}) register(CalverPatch, &calverPatch{}) + register(CalverPrerelease, &calverPrerelease{}) } func (c *calverMajor) Evaluate() (matchWithRule bool, newTag string, err error) { @@ -43,8 +56,20 @@ func (c *calverMajor) Evaluate() (matchWithRule bool, newTag string, err error) continue } - if cv.Major() > actualCV.Major() { - return true, t, nil + // Contains no prerelease + if cv.Prerelease() == "" { + // Create a constraint (e.g. 2024.0.0: >=2025.0.0) + constraint, err := vc.NewConstraint(fmt.Sprintf(">=%s", actualCV.IncMajor()), funcParseCalver) + if err != nil { + log.WithError(err).WithField("constraint", t).Error("Error parsing constraint") + continue + } + + // Check if the constraint is satisfied + if constraint.Check(cv) { + c.SetNewTag(t) + return true, t, nil + } } } @@ -65,8 +90,20 @@ func (c *calverMinor) Evaluate() (matchWithRule bool, newTag string, err error) continue } - if cv.Minor() > actualCV.Minor() && cv.Major() == actualCV.Major() { - return true, t, nil + // Contains no prerelease + if cv.Prerelease() == "" { + // Create a constraint (e.g. 2024.0.0: >=2024.1.0 <2025.0.0) + constraint, err := vc.NewConstraint(fmt.Sprintf(">=%s <%s", actualCV.IncMinor(), actualCV.IncMajor()), funcParseCalver) + if err != nil { + log.WithError(err).WithField("constraint", t).Error("Error parsing constraint") + continue + } + + // Check if the constraint is satisfied + if constraint.Check(cv) { + c.SetNewTag(t) + return true, t, nil + } } } @@ -87,8 +124,89 @@ func (c *calverPatch) Evaluate() (matchWithRule bool, newTag string, err error) continue } - if cv.Patch() > actualCV.Patch() && cv.Minor() == actualCV.Minor() && cv.Major() == actualCV.Major() { - return true, t, nil + // Contains no prerelease + if cv.Prerelease() == "" { + // Create a constraint (e.g. 2024.0.0: >=2024.0.1 <2024.1.0) + constraint, err := vc.NewConstraint(fmt.Sprintf(">=%s <%s", actualCV.IncPatch(), actualCV.IncMinor()), funcParseCalver) + if err != nil { + log.WithError(err).WithField("constraint", t).Error("Error parsing constraint") + continue + } + + // Check if the constraint is satisfied + if constraint.Check(cv) { + c.SetNewTag(t) + return true, t, nil + } + } + } + + return false, "", nil +} + +// ***** calverModifier is not used in the current implementation ***** +// func (c *calverModifier) Evaluate() (matchWithRule bool, newTag string, err error) { +// actualCV, err := vc.NewCalVerStr(c.actualTag) +// if err != nil { +// log.WithError(err).WithField("tag", c.actualTag).Error("Error parsing actual tag") +// return false, "", err +// } + +// for _, t := range c.tags { +// cv, err := vc.NewCalVerStr(t) +// if err != nil { +// log.WithError(err).WithField("tag", t).Error("Error parsing tag") +// continue +// } + +// // Contains a modifier +// if cv.Prerelease() != "" { +// // Create a constraint (e.g. 2024.0.0-dev: >=2024.0.1-dev) +// constraint, err := vc.NewConstraint(fmt.Sprintf(">=%s", actualCV.IncPatch()), funcParseCalver) +// if err != nil { +// log.WithError(err).WithField("constraint", t).Error("Error parsing constraint") +// continue +// } + +// // Check if the constraint is satisfied +// if constraint.Check(cv) { +// c.SetNewTag(t) +// return true, t, nil +// } +// } +// } + +// return false, "", nil +// } + +func (c *calverPrerelease) Evaluate() (matchWithRule bool, newTag string, err error) { + actualCV, err := vc.NewCalVerStr(c.actualTag) + if err != nil { + log.WithError(err).WithField("tag", c.actualTag).Error("Error parsing actual tag") + return false, "", err + } + + for _, t := range c.tags { + cv, err := vc.NewCalVerStr(t) + if err != nil { + log.WithError(err).WithField("tag", t).Error("Error parsing tag") + continue + } + + // Contains a prerelease + if cv.Prerelease() != "" { + // Create a constraint (e.g. 2024.0.0-dev.0: >2024.0.0-dev.0) + constraint, err := vc.NewConstraint(fmt.Sprintf(">%s-%s", actualCV.Version(), actualCV.Prerelease()), funcParseCalver) + if err != nil { + log.WithError(err).WithField("constraint", t).Error("Error parsing constraint") + continue + } + + // Check if the constraint is satisfied + if constraint.Check(cv) { + c.SetNewTag(t) + return true, t, nil + } } } diff --git a/internal/rules/calver_test.go b/internal/rules/calver_test.go index 3b95b74..9daf514 100644 --- a/internal/rules/calver_test.go +++ b/internal/rules/calver_test.go @@ -8,6 +8,23 @@ import ( "github.com/orange-cloudavenue/kube-image-updater/internal/rules" ) +var listTest map[string]string = map[string]string{ + "YYYY": "2024", + "YYYY-dev": "2024-dev", + "YYYY.MM": "2024.01", + "YYYY.MM-dev": "2024.01-dev", + "YYYY.MM.DD": "2024.01.01", + "YYYY.MM.DD-dev": "2024.01.01-dev", + "YYYY.MM.DD-dev.prerelease": "2024.01.01-dev.1", + "YY": "24", + "YY-dev": "24-dev", + "YY.MM": "24.01", + "YY.M": "24.1", + "YY.M.D": "24.1.1", + "Invalid": "v2024", + "YYYY.MM.DD.WrongPrerelease": "2024.01.01.1", +} + func TestCalverMajor_Evaluate(t *testing.T) { tests := []struct { name string @@ -18,33 +35,51 @@ func TestCalverMajor_Evaluate(t *testing.T) { expectedError bool }{ { - name: "Valid major version increment", - actualTag: "2024.01.00", - tagsAvailable: []string{"2023.10.00", "2024.01.0", "2025.01.00"}, + // Unitary tests + name: "YYYY", + actualTag: listTest["YYYY"], + tagsAvailable: []string{"2023", "2024", "2025"}, expectedMatch: true, - expectedTag: "2025.01.00", + expectedTag: "2025", + expectedError: false, + }, + { + name: "YY", + actualTag: listTest["YY"], + tagsAvailable: []string{"23", "24", "25"}, + expectedMatch: true, + expectedTag: "25", + expectedError: false, + }, + { + name: "YYYY-dev", + actualTag: listTest["YYYY-dev"], + tagsAvailable: []string{"2023-dev", "2024-dev", "2025-dev"}, + expectedMatch: false, + expectedTag: "", expectedError: false, }, { - name: "No matching major version", - actualTag: "2024.01.00", - tagsAvailable: []string{"2023.10.00", "2024.10.01", "2024.01.10"}, + name: "YY-dev", + actualTag: listTest["YY-dev"], + tagsAvailable: []string{"23-dev", "24-dev", "25-dev"}, expectedMatch: false, expectedTag: "", expectedError: false, }, + // Errors tests { - name: "Invalid actual tag", - actualTag: "invalid", - tagsAvailable: []string{"2023.10.00", "2024.00.01", "2024.01.00"}, + name: "Invalid", + actualTag: listTest["Invalid"], + tagsAvailable: []string{"2023.10.00", "2024.01.0", "2025.01.00"}, expectedMatch: false, expectedTag: "", expectedError: true, }, { - name: "Invalid and no available tag", + name: "Invalid available tag", actualTag: "2024.01.0", - tagsAvailable: []string{"2023.01.0", "invalid"}, + tagsAvailable: []string{"v2023.01.0", "invalid"}, expectedMatch: false, expectedTag: "", expectedError: false, @@ -80,41 +115,50 @@ func TestCalverMinor_Evaluate(t *testing.T) { expectedError bool }{ { - name: "Valid minor version increment", - actualTag: "2024.0.0", - tagsAvailable: []string{"2024.1.0", "2024.0.1"}, + name: "YYYY.MM", + actualTag: listTest["YYYY.MM"], + tagsAvailable: []string{"2023.01", "2025.01", "2024.02"}, expectedMatch: true, - expectedTag: "2024.1.0", + expectedTag: "2024.02", expectedError: false, }, { - name: "No matching minor version", - actualTag: "2024.0.0", - tagsAvailable: []string{"2024.0.1", "2024.0.2"}, + name: "YYYY.MM-dev", + actualTag: listTest["YYYY.MM-dev"], + tagsAvailable: []string{"2023.01-dev", "2025.01-dev", "2024.02-dev"}, expectedMatch: false, expectedTag: "", expectedError: false, }, { - name: "Invalid actual tag", - actualTag: "invalid", - tagsAvailable: []string{"2024.1.0", "2024.0.1"}, - expectedMatch: false, - expectedTag: "", - expectedError: true, + name: "YY.MM", + actualTag: listTest["YY.MM"], + tagsAvailable: []string{"23.01", "25.01", "24.02"}, + expectedMatch: true, + expectedTag: "24.02", + expectedError: false, }, { - name: "Invalid available tag", - actualTag: "2024.0.0", - tagsAvailable: []string{"invalid", "2024.1.0"}, + name: "YY.M", + actualTag: listTest["YY.M"], + tagsAvailable: []string{"23.1", "25.1", "24.2"}, expectedMatch: true, - expectedTag: "2024.1.0", + expectedTag: "24.2", expectedError: false, }, + // Errors tests + { + name: "Invalid", + actualTag: listTest["Invalid"], + tagsAvailable: []string{"2023.10.00", "2024.01.0", "2025.01.00"}, + expectedMatch: false, + expectedTag: "", + expectedError: true, + }, { - name: "only major version increment", - actualTag: "2024.0.0", - tagsAvailable: []string{"2025.0.0", "2024.0.0"}, + name: "Invalid available tag", + actualTag: "2024.01.0", + tagsAvailable: []string{"v2023.01.0", "invalid"}, expectedMatch: false, expectedTag: "", expectedError: false, @@ -149,42 +193,212 @@ func TestCalverPatch_Evaluate(t *testing.T) { expectedTag string expectedError bool }{ + // Unitary tests { - name: "Valid patch version increment", - actualTag: "2024.0.0", - tagsAvailable: []string{"2024.0.1", "2024.0.2"}, + name: "YYYY.MM.DD", + actualTag: listTest["YYYY.MM.DD"], + tagsAvailable: []string{"2023.01.01", "2025.02.01", "2024.01.02"}, expectedMatch: true, - expectedTag: "2024.0.2", + expectedTag: "2024.01.02", expectedError: false, }, { - name: "No matching patch version", - actualTag: "2024.0.0", - tagsAvailable: []string{"2024.1.0", "2024.2.0"}, + name: "YYYY.MM.DD-dev", + actualTag: listTest["YYYY.MM.DD-dev"], + tagsAvailable: []string{"2023.01.01-dev", "2025.02.01-dev", "2024.01.02-dev"}, expectedMatch: false, expectedTag: "", expectedError: false, }, { - name: "Invalid actual tag", - actualTag: "invalid", - tagsAvailable: []string{"2024.0.1", "2024.0.2"}, + name: "YY.M.D", + actualTag: listTest["YY.M.D"], + tagsAvailable: []string{"23.1.1", "25.2.1", "24.1.2"}, + expectedMatch: true, + expectedTag: "24.1.2", + expectedError: false, + }, + // Errors tests + { + name: "YY.M.D-dev", + actualTag: listTest["YY.M.D-dev"], + tagsAvailable: []string{"23.1.1-dev", "25.2.1-dev", "24.1.2-dev"}, + expectedMatch: false, + expectedTag: "", + expectedError: true, + }, + { + name: "Invalid", + actualTag: listTest["Invalid"], + tagsAvailable: []string{"2023.10.00", "2024.01.0", "2025.01.00"}, expectedMatch: false, expectedTag: "", expectedError: true, }, { name: "Invalid available tag", - actualTag: "2024.0.0", - tagsAvailable: []string{"invalid", "2024.0.1"}, + actualTag: "2024.01.0", + tagsAvailable: []string{"v2023.01.0", "invalid"}, + expectedMatch: false, + expectedTag: "", + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r, err := rules.GetRule(rules.CalverPatch) + assert.NoError(t, err) + r.Init(tt.actualTag, tt.tagsAvailable, "") + match, newTag, err := r.Evaluate() + + if tt.expectedError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, tt.expectedMatch, match) + assert.Equal(t, tt.expectedTag, newTag) + }) + } +} + +// func TestCalverModifier_Evaluate(t *testing.T) { +// tests := []struct { +// name string +// actualTag string +// tagsAvailable []string +// expectedMatch bool +// expectedTag string +// expectedError bool +// }{ +// // Unitary tests +// { +// name: "YYYY-dev", +// actualTag: listTest["YYYY-dev"], +// tagsAvailable: []string{"2024-aaa", "2024-dev.1", "2025-dev"}, +// expectedMatch: true, +// expectedTag: "2025-dev", +// expectedError: false, +// }, +// { +// name: "YYYY.MM-dev", +// actualTag: listTest["YYYY.MM-dev"], +// tagsAvailable: []string{"2024.01-aaa", "2024.02-bbb", "2024.02-dev"}, +// expectedMatch: true, +// expectedTag: "2024.02-dev", +// expectedError: false, +// }, +// { +// name: "YYYY.MM.DD-dev", +// actualTag: listTest["YYYY.MM.DD-dev"], +// tagsAvailable: []string{"2024.01.01-aaa", "2024.01.01-dev", "2024.01.02-dev"}, +// expectedMatch: true, +// expectedTag: "2024.01.02-dev", +// expectedError: false, +// }, +// { +// name: "YY-dev", +// actualTag: listTest["YY-dev"], +// tagsAvailable: []string{"24-aaa", "24-dev", "25-dev"}, +// expectedMatch: true, +// expectedTag: "25-dev", +// expectedError: false, +// }, +// { +// name: "YY.MM-dev", +// actualTag: listTest["YY.MM-dev"], +// tagsAvailable: []string{"24.01-aaa", "24.01-dev", "24.02-dev"}, +// expectedMatch: true, +// expectedTag: "24.02-dev", +// expectedError: false, +// }, +// { +// name: "YY.M.D-dev", +// actualTag: listTest["YY.M.D-dev"], +// tagsAvailable: []string{"24.1.1-aaa", "24.1.1-dev", "24.1.2-dev"}, +// expectedMatch: true, +// expectedTag: "24.1.2-dev", +// expectedError: false, +// }, +// // Errors tests +// { +// name: "Invalid", +// actualTag: listTest["Invalid"], +// tagsAvailable: []string{"2023.10.00-dev", "24.01.0-dev", "2025.1.0-dev"}, +// expectedMatch: false, +// expectedTag: "", +// expectedError: true, +// }, +// { +// name: "Invalid available tag", +// actualTag: "2024.01.0", +// tagsAvailable: []string{"v2023.01.0", "invalid"}, +// expectedMatch: false, +// expectedTag: "", +// expectedError: false, +// }, +// } + +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// r, err := rules.GetRule(rules.CalverModifier) +// assert.NoError(t, err) +// r.Init(tt.actualTag, tt.tagsAvailable, "") +// match, newTag, err := r.Evaluate() + +// if tt.expectedError { +// assert.Error(t, err) +// } else { +// assert.NoError(t, err) +// } + +// assert.Equal(t, tt.expectedMatch, match) +// assert.Equal(t, tt.expectedTag, newTag) +// }) +// } +// } + +func TestCalverPrerelease_Evaluate(t *testing.T) { + tests := []struct { + name string + actualTag string + tagsAvailable []string + expectedMatch bool + expectedTag string + expectedError bool + }{ + // Unitary tests + { + name: "YYYY.MM.DD-dev.prerelease", + actualTag: listTest["YYYY.MM.DD-dev.prerelease"], + tagsAvailable: []string{"2023.01.01-dev.2", "2024.01.01-beta.2", "2024.01.01-dev.2"}, expectedMatch: true, - expectedTag: "2024.0.1", + expectedTag: "2024.01.01-dev.2", expectedError: false, }, + // Errors tests + { + name: "YYYY.MM.DD.WrongPrerelease", + actualTag: listTest["YYYY.MM.DD.WrongPrerelease"], + tagsAvailable: []string{"2024.01.01-aaa", "2024.01.01-dev", "2024.01.01.2"}, + expectedMatch: false, + expectedTag: "", + expectedError: true, + }, { - name: "only minor version increment", - actualTag: "2024.0.0", - tagsAvailable: []string{"2024.1.0", "2024.0.0"}, + name: "Invalid", + actualTag: listTest["Invalid"], + tagsAvailable: []string{"2023.10.00-dev", "24.01.0-dev", "2025.1.0-dev.2"}, + expectedMatch: false, + expectedTag: "", + expectedError: true, + }, + { + name: "Invalid available tag", + actualTag: "2024.01.0", + tagsAvailable: []string{"v2023.01.0.aaa", "invalid.prelease"}, expectedMatch: false, expectedTag: "", expectedError: false, @@ -193,7 +407,7 @@ func TestCalverPatch_Evaluate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - r, err := rules.GetRule(rules.SemverPatch) + r, err := rules.GetRule(rules.CalverPrerelease) assert.NoError(t, err) r.Init(tt.actualTag, tt.tagsAvailable, "") match, newTag, err := r.Evaluate() diff --git a/internal/rules/rules.go b/internal/rules/rules.go index 47c3b0a..86db747 100644 --- a/internal/rules/rules.go +++ b/internal/rules/rules.go @@ -23,14 +23,15 @@ type ( var rules = make(Rules) const ( - SemverMajor Name = "semver-major" - SemverMinor Name = "semver-minor" - SemverPatch Name = "semver-patch" - CalverMajor Name = "calver-major" - CalverMinor Name = "calver-minor" - CalverPatch Name = "calver-patch" - Regex Name = "regex" - Always Name = "always" + SemverMajor Name = "semver-major" + SemverMinor Name = "semver-minor" + SemverPatch Name = "semver-patch" + CalverMajor Name = "calver-major" + CalverMinor Name = "calver-minor" + CalverPatch Name = "calver-patch" + CalverPrerelease Name = "calver-prerelease" + Regex Name = "regex" + Always Name = "always" ) func register(name Name, rule RuleInterface) { diff --git a/manifests/crd/kimup.cloudavenue.io_images.yaml b/manifests/crd/kimup.cloudavenue.io_images.yaml index 12355ea..e91b6e3 100644 --- a/manifests/crd/kimup.cloudavenue.io_images.yaml +++ b/manifests/crd/kimup.cloudavenue.io_images.yaml @@ -180,6 +180,10 @@ spec: type: string type: enum: + - calver-major + - calver-minor + - calver-patch + - calver-prerelease - semver-major - semver-minor - semver-patch