Skip to content

Commit 4a61e98

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

File tree

7 files changed

+131
-5
lines changed

7 files changed

+131
-5
lines changed

crds/operators.coreos.com_clusterserviceversions.yaml

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ spec:
2727
jsonPath: .spec.version
2828
name: Version
2929
type: string
30-
- description: The release version of the CSV
30+
- description: The release of this version of the CSV
3131
jsonPath: .spec.release
3232
name: Release
3333
type: string
@@ -8993,7 +8993,29 @@ 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:
9004+
- to fix a typo in their description
9005+
- to add/amend annotations or labels
9006+
- to amend examples or documentation
9007+
9008+
It is up to operator authors to determine the semantics of release versions they use
9009+
for their operator. All release versions must conform to the semver prerelease format
9010+
(dot-separated identifiers containing only alphanumerics and hyphens) and are limited
9011+
to a maximum length of 20 characters.
89969012
type: string
9013+
maxLength: 20
9014+
x-kubernetes-validations:
9015+
- rule: self.matches('^[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*$')
9016+
message: release version must be composed of dot-separated identifiers containing only alphanumerics and hyphens
9017+
- rule: '!self.split(''.'').exists(x, x.matches(''^0[0-9]+$''))'
9018+
message: numeric identifiers in release version must not have leading zeros
89979019
replaces:
89989020
description: The name of a CSV this one replaces. Should match the `metadata.Name` field of the old CSV.
89999021
type: string

crds/zz_defs.go

Lines changed: 1 addition & 1 deletion
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 composed of 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/manifests/bundleloader.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ func (b *bundleLoader) LoadBundle() error {
3939

4040
errs = append(errs, b.calculateCompressedBundleSize())
4141
b.addChannelsFromAnnotationsFile()
42+
b.addPackageFromAnnotationsFile()
4243

4344
if !b.foundCSV {
4445
errs = append(errs, fmt.Errorf("unable to find a csv in bundle directory %s", b.dir))
@@ -68,6 +69,14 @@ func (b *bundleLoader) addChannelsFromAnnotationsFile() {
6869
}
6970
}
7071

72+
func (b *bundleLoader) addPackageFromAnnotationsFile() {
73+
if b.bundle == nil {
74+
// None of this is relevant if the bundle was not found
75+
return
76+
}
77+
b.bundle.Package = b.annotationsFile.Annotations.PackageName
78+
}
79+
7180
// Compress the bundle to check its size
7281
func (b *bundleLoader) calculateCompressedBundleSize() error {
7382
if b.bundle == nil {

pkg/operators/v1alpha1/clusterserviceversion_types.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,21 @@ 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:
287+
// - to fix a typo in their description
288+
// - to add/amend annotations or labels
289+
// - to amend examples or documentation
290+
//
291+
// It is up to operator authors to determine the semantics of release versions they use
292+
// for their operator. All release versions must conform to the semver prerelease format
293+
// (dot-separated identifiers containing only alphanumerics and hyphens) and are limited
294+
// to a maximum length of 20 characters.
280295
// +optional
281296
Release release.OperatorRelease `json:"release,omitzero"`
282297
Maturity string `json:"maturity,omitempty"`
@@ -598,7 +613,7 @@ type ResourceInstance struct {
598613
// +kubebuilder:subresource:status
599614
// +kubebuilder:printcolumn:name="Display",type=string,JSONPath=`.spec.displayName`,description="The name of the CSV"
600615
// +kubebuilder:printcolumn:name="Version",type=string,JSONPath=`.spec.version`,description="The version of the CSV"
601-
// +kubebuilder:printcolumn:name="Release",type=string,JSONPath=`.spec.release`,description="The release version of the CSV"
616+
// +kubebuilder:printcolumn:name="Release",type=string,JSONPath=`.spec.release`,description="The release of this version of the CSV"
602617
// +kubebuilder:printcolumn:name="Replaces",type=string,JSONPath=`.spec.replaces`,description="The name of a CSV that this one replaces"
603618
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
604619

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)