Skip to content

Commit 663bbcc

Browse files
committed
review resolutions
Signed-off-by: grokspawn <jordan@nimblewidget.com>
1 parent 7db47d7 commit 663bbcc

File tree

6 files changed

+120
-11
lines changed

6 files changed

+120
-11
lines changed

crds/operators.coreos.com_clusterserviceversions.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8993,7 +8993,26 @@ spec:
89938993
name:
89948994
type: string
89958995
release:
8996+
description: |-
8997+
release specifies the packaging version of the operator, defaulting to empty
8998+
release is optional
8999+
9000+
A ClusterServiceVersion's release field is used to provide a secondary ordering
9001+
for operators that share the same semantic version. This is useful for
9002+
operators that need to make changes to the CSV which don't affect their functionality,
9003+
for example, to update their base image, or to fix a typo in their description.
9004+
9005+
It is up to operator authors to determine the semantics of release versions they use
9006+
for their operator. All release versions must conform to the semver prerelease format
9007+
(dot-separated identifiers containing only alphanumerics and hyphens) and are limited
9008+
to a maximum length of 20 characters.
89969009
type: string
9010+
maxLength: 20
9011+
x-kubernetes-validations:
9012+
- rule: self.matches('^[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*$')
9013+
message: release version must be a valid semver prerelease format (dot-separated identifiers containing only alphanumerics and hyphens)
9014+
- rule: '!self.split(''.'').exists(x, x.matches(''^0[0-9]+$''))'
9015+
message: numeric identifiers in release version must not have leading zeros
89979016
replaces:
89989017
description: The name of a CSV this one replaces. Should match the `metadata.Name` field of the old CSV.
89999018
type: string

crds/zz_defs.go

Lines changed: 7 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/lib/release/release.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package release
22

33
import (
44
"encoding/json"
5+
"slices"
56
"strings"
67

78
semver "github.com/blang/semver/v4"
@@ -11,14 +12,16 @@ import (
1112
// OperatorRelease is a wrapper around a slice of semver.PRVersion which supports correct
1213
// marshaling to YAML and JSON.
1314
// +kubebuilder:validation:Type=string
15+
// +kubebuilder:validation:MaxLength=20
16+
// +kubebuilder:validation:XValidation:rule="self.matches('^[0-9A-Za-z-]+(\\\\.[0-9A-Za-z-]+)*$')",message="release version must be a valid semver prerelease format (dot-separated identifiers containing only alphanumerics and hyphens)"
17+
// +kubebuilder:validation:XValidation:rule="!self.split('.').exists(x, x.matches('^0[0-9]+$'))",message="numeric identifiers in release version must not have leading zeros"
1418
type OperatorRelease struct {
1519
Release []semver.PRVersion `json:"-"`
1620
}
1721

1822
// DeepCopyInto creates a deep-copy of the Version value.
1923
func (v *OperatorRelease) DeepCopyInto(out *OperatorRelease) {
20-
out.Release = make([]semver.PRVersion, len(v.Release))
21-
copy(out.Release, v.Release)
24+
out.Release = slices.Clone(v.Release)
2225
}
2326

2427
// MarshalJSON implements the encoding/json.Marshaler interface.
@@ -60,3 +63,11 @@ func (_ OperatorRelease) OpenAPISchemaType() []string { return []string{"string"
6063
// the OpenAPI spec of this type.
6164
// "semver" is not a standard openapi format but tooling may use the value regardless
6265
func (_ OperatorRelease) OpenAPISchemaFormat() string { return "semver" }
66+
67+
func (r OperatorRelease) String() string {
68+
segments := []string{}
69+
for _, segment := range r.Release {
70+
segments = append(segments, segment.String())
71+
}
72+
return strings.Join(segments, ".")
73+
}

pkg/lib/release/release_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,32 @@ func TestOperatorReleaseMarshal(t *testing.T) {
4242
}},
4343
out: []byte(`"20240101.12345"`),
4444
},
45+
{
46+
name: "alphanumeric-segments",
47+
in: OperatorRelease{Release: []semver.PRVersion{
48+
mustNewPRVersion("alpha"),
49+
mustNewPRVersion("beta"),
50+
mustNewPRVersion("1"),
51+
}},
52+
out: []byte(`"alpha.beta.1"`),
53+
},
54+
{
55+
name: "alphanumeric-with-hyphens",
56+
in: OperatorRelease{Release: []semver.PRVersion{
57+
mustNewPRVersion("rc-1"),
58+
mustNewPRVersion("build-123"),
59+
}},
60+
out: []byte(`"rc-1.build-123"`),
61+
},
62+
{
63+
name: "mixed-alphanumeric",
64+
in: OperatorRelease{Release: []semver.PRVersion{
65+
mustNewPRVersion("1"),
66+
mustNewPRVersion("2-beta"),
67+
mustNewPRVersion("x86-64"),
68+
}},
69+
out: []byte(`"1.2-beta.x86-64"`),
70+
},
4571
}
4672
for _, tt := range tests {
4773
t.Run(tt.name, func(t *testing.T) {
@@ -89,6 +115,32 @@ func TestOperatorReleaseUnmarshal(t *testing.T) {
89115
mustNewPRVersion("12345"),
90116
}}},
91117
},
118+
{
119+
name: "alphanumeric-segments",
120+
in: []byte(`{"r": "alpha.beta.1"}`),
121+
out: TestStruct{Release: OperatorRelease{Release: []semver.PRVersion{
122+
mustNewPRVersion("alpha"),
123+
mustNewPRVersion("beta"),
124+
mustNewPRVersion("1"),
125+
}}},
126+
},
127+
{
128+
name: "alphanumeric-with-hyphens",
129+
in: []byte(`{"r": "rc-1.build-123"}`),
130+
out: TestStruct{Release: OperatorRelease{Release: []semver.PRVersion{
131+
mustNewPRVersion("rc-1"),
132+
mustNewPRVersion("build-123"),
133+
}}},
134+
},
135+
{
136+
name: "mixed-alphanumeric",
137+
in: []byte(`{"r": "1.2-beta.x86-64"}`),
138+
out: TestStruct{Release: OperatorRelease{Release: []semver.PRVersion{
139+
mustNewPRVersion("1"),
140+
mustNewPRVersion("2-beta"),
141+
mustNewPRVersion("x86-64"),
142+
}}},
143+
},
92144
}
93145
for _, tt := range tests {
94146
t.Run(tt.name, func(t *testing.T) {

pkg/operators/v1alpha1/clusterserviceversion_types.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,18 @@ type APIServiceDefinitions struct {
277277
type ClusterServiceVersionSpec struct {
278278
InstallStrategy NamedInstallStrategy `json:"install"`
279279
Version version.OperatorVersion `json:"version,omitempty"`
280+
// release specifies the packaging version of the operator, defaulting to empty
281+
// release is optional
282+
//
283+
// A ClusterServiceVersion's release field is used to provide a secondary ordering
284+
// for operators that share the same semantic version. This is useful for
285+
// operators that need to make changes to the CSV which don't affect their functionality,
286+
// for example, to update their base image, or to fix a typo in their description.
287+
//
288+
// It is up to operator authors to determine the semantics of release versions they use
289+
// for their operator. All release versions must conform to the semver prerelease format
290+
// (dot-separated identifiers containing only alphanumerics and hyphens) and are limited
291+
// to a maximum length of 20 characters.
280292
// +optional
281293
Release release.OperatorRelease `json:"release,omitzero"`
282294
Maturity string `json:"maturity,omitempty"`

pkg/validation/internal/bundle.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,26 @@ func validateBundle(bundle *manifests.Bundle) (result errors.ManifestResult) {
4242
if sizeErrors != nil {
4343
result.Add(sizeErrors...)
4444
}
45+
nameErrors := validateBundleName(bundle)
46+
if nameErrors != nil {
47+
result.Add(nameErrors...)
48+
}
4549
return result
4650
}
4751

52+
func validateBundleName(bundle *manifests.Bundle) []errors.Error {
53+
var errs []errors.Error
54+
// bundle naming with a specified release version must follow the pattern
55+
// <package-name>-v<csv-version>-<release-version>
56+
if len(bundle.CSV.Spec.Release.Release) > 0 {
57+
expectedName := fmt.Sprintf("%s-v%s-%s", bundle.Package, bundle.CSV.Spec.Version.String(), bundle.CSV.Spec.Release.String())
58+
if bundle.Name != expectedName {
59+
errs = append(errs, errors.ErrInvalidBundle(fmt.Sprintf("bundle name with release versioning %q does not match expected name %q", bundle.Name, expectedName), bundle.Name))
60+
}
61+
}
62+
return errs
63+
}
64+
4865
func validateServiceAccounts(bundle *manifests.Bundle) []errors.Error {
4966
// get service account names defined in the csv
5067
saNamesFromCSV := make(map[string]struct{}, 0)

0 commit comments

Comments
 (0)