Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 81
add Release version as an optional field in the CSV#454
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| package release | ||
| import ( | ||
| "encoding/json" | ||
| "slices" | ||
| "strings" | ||
| semver "github.com/blang/semver/v4" | ||
| ) | ||
| // +k8s:openapi-gen=true | ||
| // OperatorRelease is a wrapper around a slice of semver.PRVersion which supports correct | ||
| // marshaling to YAML and JSON. | ||
| // +kubebuilder:validation:Type=string | ||
grokspawn marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| // +kubebuilder:validation:MaxLength=20 | ||
| // +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" | ||
| // +kubebuilder:validation:XValidation:rule="!self.split('.').exists(x, x.matches('^0[0-9]+$'))",message="numeric identifiers in release version must not have leading zeros" | ||
| type OperatorRelease struct { | ||
| Release []semver.PRVersion `json:"-"` | ||
| } | ||
| // DeepCopyInto creates a deep-copy of the Version value. | ||
| func (v *OperatorRelease) DeepCopyInto(out *OperatorRelease) { | ||
| out.Release = slices.Clone(v.Release) | ||
| } | ||
| // MarshalJSON implements the encoding/json.Marshaler interface. | ||
| func (v OperatorRelease) MarshalJSON() ([]byte, error) { | ||
| segments := []string{} | ||
| for _, segment := range v.Release { | ||
| segments = append(segments, segment.String()) | ||
| } | ||
| return json.Marshal(strings.Join(segments, ".")) | ||
| } | ||
| // UnmarshalJSON implements the encoding/json.Unmarshaler interface. | ||
| func (v *OperatorRelease) UnmarshalJSON(data []byte) (err error) { | ||
| var versionString string | ||
| if err = json.Unmarshal(data, &versionString); err != nil { | ||
| return | ||
| } | ||
| segments := strings.Split(versionString, ".") | ||
| for _, segment := range segments { | ||
| release, err := semver.NewPRVersion(segment) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| v.Release = append(v.Release, release) | ||
| } | ||
| return nil | ||
| } | ||
| // OpenAPISchemaType is used by the kube-openapi generator when constructing | ||
| // the OpenAPI spec of this type. | ||
| // | ||
| // See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators | ||
| func (_ OperatorRelease) OpenAPISchemaType() []string { return []string{"string"} } | ||
| // OpenAPISchemaFormat is used by the kube-openapi generator when constructing | ||
| // the OpenAPI spec of this type. | ||
| // "semver" is not a standard openapi format but tooling may use the value regardless | ||
| func (_ OperatorRelease) OpenAPISchemaFormat() string { return "semver" } | ||
| func (r OperatorRelease) String() string { | ||
| segments := []string{} | ||
| for _, segment := range r.Release { | ||
| segments = append(segments, segment.String()) | ||
| } | ||
| return strings.Join(segments, ".") | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| package release | ||
| import ( | ||
| "encoding/json" | ||
| "testing" | ||
| semver "github.com/blang/semver/v4" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
| func TestOperatorReleaseMarshal(t *testing.T) { | ||
grokspawn marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| tests := []struct { | ||
| name string | ||
| in OperatorRelease | ||
| out []byte | ||
| err error | ||
| }{ | ||
| { | ||
| name: "single-segment", | ||
| in: OperatorRelease{Release: []semver.PRVersion{mustNewPRVersion("1")}}, | ||
| out: []byte(`"1"`), | ||
| }, | ||
| { | ||
| name: "two-segments", | ||
| in: OperatorRelease{Release: []semver.PRVersion{mustNewPRVersion("1"), mustNewPRVersion("0")}}, | ||
| out: []byte(`"1.0"`), | ||
| }, | ||
| { | ||
| name: "multi-segment", | ||
| in: OperatorRelease{Release: []semver.PRVersion{ | ||
| mustNewPRVersion("1"), | ||
| mustNewPRVersion("2"), | ||
| mustNewPRVersion("3"), | ||
| }}, | ||
| out: []byte(`"1.2.3"`), | ||
| }, | ||
| { | ||
| name: "numeric-segments", | ||
| in: OperatorRelease{Release: []semver.PRVersion{ | ||
| mustNewPRVersion("20240101"), | ||
| mustNewPRVersion("12345"), | ||
| }}, | ||
| out: []byte(`"20240101.12345"`), | ||
| }, | ||
| { | ||
| name: "alphanumeric-segments", | ||
| in: OperatorRelease{Release: []semver.PRVersion{ | ||
| mustNewPRVersion("alpha"), | ||
| mustNewPRVersion("beta"), | ||
| mustNewPRVersion("1"), | ||
| }}, | ||
| out: []byte(`"alpha.beta.1"`), | ||
| }, | ||
| { | ||
| name: "alphanumeric-with-hyphens", | ||
| in: OperatorRelease{Release: []semver.PRVersion{ | ||
| mustNewPRVersion("rc-1"), | ||
| mustNewPRVersion("build-123"), | ||
| }}, | ||
| out: []byte(`"rc-1.build-123"`), | ||
| }, | ||
| { | ||
| name: "mixed-alphanumeric", | ||
| in: OperatorRelease{Release: []semver.PRVersion{ | ||
| mustNewPRVersion("1"), | ||
| mustNewPRVersion("2-beta"), | ||
| mustNewPRVersion("x86-64"), | ||
| }}, | ||
| out: []byte(`"1.2-beta.x86-64"`), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| m, err := tt.in.MarshalJSON() | ||
| require.Equal(t, tt.out, m, string(m)) | ||
| require.Equal(t, tt.err, err) | ||
| }) | ||
| } | ||
| } | ||
| func TestOperatorReleaseUnmarshal(t *testing.T) { | ||
| type TestStruct struct { | ||
| Release OperatorRelease `json:"r"` | ||
| } | ||
| tests := []struct { | ||
| name string | ||
| in []byte | ||
| out TestStruct | ||
| err error | ||
| }{ | ||
| { | ||
| name: "single-segment", | ||
| in: []byte(`{"r": "1"}`), | ||
| out: TestStruct{Release: OperatorRelease{Release: []semver.PRVersion{mustNewPRVersion("1")}}}, | ||
| }, | ||
| { | ||
| name: "two-segments", | ||
| in: []byte(`{"r": "1.0"}`), | ||
| out: TestStruct{Release: OperatorRelease{Release: []semver.PRVersion{mustNewPRVersion("1"), mustNewPRVersion("0")}}}, | ||
| }, | ||
| { | ||
| name: "multi-segment", | ||
| in: []byte(`{"r": "1.2.3"}`), | ||
| out: TestStruct{Release: OperatorRelease{Release: []semver.PRVersion{ | ||
| mustNewPRVersion("1"), | ||
| mustNewPRVersion("2"), | ||
| mustNewPRVersion("3"), | ||
| }}}, | ||
| }, | ||
| { | ||
| name: "numeric-segments", | ||
| in: []byte(`{"r": "20240101.12345"}`), | ||
| out: TestStruct{Release: OperatorRelease{Release: []semver.PRVersion{ | ||
| mustNewPRVersion("20240101"), | ||
| mustNewPRVersion("12345"), | ||
| }}}, | ||
| }, | ||
| { | ||
| name: "alphanumeric-segments", | ||
| in: []byte(`{"r": "alpha.beta.1"}`), | ||
| out: TestStruct{Release: OperatorRelease{Release: []semver.PRVersion{ | ||
| mustNewPRVersion("alpha"), | ||
| mustNewPRVersion("beta"), | ||
| mustNewPRVersion("1"), | ||
| }}}, | ||
| }, | ||
| { | ||
| name: "alphanumeric-with-hyphens", | ||
| in: []byte(`{"r": "rc-1.build-123"}`), | ||
| out: TestStruct{Release: OperatorRelease{Release: []semver.PRVersion{ | ||
| mustNewPRVersion("rc-1"), | ||
| mustNewPRVersion("build-123"), | ||
| }}}, | ||
| }, | ||
| { | ||
| name: "mixed-alphanumeric", | ||
| in: []byte(`{"r": "1.2-beta.x86-64"}`), | ||
| out: TestStruct{Release: OperatorRelease{Release: []semver.PRVersion{ | ||
| mustNewPRVersion("1"), | ||
| mustNewPRVersion("2-beta"), | ||
| mustNewPRVersion("x86-64"), | ||
| }}}, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| s := TestStruct{} | ||
| err := json.Unmarshal(tt.in, &s) | ||
| require.Equal(t, tt.out, s) | ||
| require.Equal(t, tt.err, err) | ||
| }) | ||
| } | ||
| } | ||
| func mustNewPRVersion(s string) semver.PRVersion { | ||
| v, err := semver.NewPRVersion(s) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
| return v | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -13,6 +13,7 @@ import ( | ||
| "k8s.io/apimachinery/pkg/labels" | ||
| "k8s.io/apimachinery/pkg/util/intstr" | ||
| "github.com/operator-framework/api/pkg/lib/release" | ||
| "github.com/operator-framework/api/pkg/lib/version" | ||
| ) | ||
| @@ -274,8 +275,25 @@ type APIServiceDefinitions struct { | ||
| // that can manage apps for a given version. | ||
| // +k8s:openapi-gen=true | ||
| type ClusterServiceVersionSpec struct { | ||
| InstallStrategy NamedInstallStrategy `json:"install"` | ||
| Version version.OperatorVersion `json:"version,omitempty"` | ||
| InstallStrategy NamedInstallStrategy `json:"install"` | ||
| Version version.OperatorVersion `json:"version,omitempty"` | ||
| // release specifies the packaging version of the operator, defaulting to empty | ||
| // release is optional | ||
| // | ||
| // A ClusterServiceVersion's release field is used to distinguish between different builds of the same operator version | ||
| // This is useful for operators that need to make changes to the CSV which don't affect their functionality, | ||
| // for example: | ||
| // - to fix a typo in their description | ||
| // - to add/amend annotations or labels | ||
| // - to amend examples or documentation | ||
| // - to produce different builds for different environments | ||
| // | ||
| // It is up to operator authors to determine the semantics of release versions they use | ||
| // for their operator. All release versions must conform to the semver prerelease format | ||
| // (dot-separated identifiers containing only alphanumerics and hyphens) and are limited | ||
| // to a maximum length of 20 characters. | ||
| // +optional | ||
| Release release.OperatorRelease `json:"release,omitzero"` | ||
grokspawn marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. grokspawn marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| Maturity string `json:"maturity,omitempty"` | ||
| CustomResourceDefinitions CustomResourceDefinitions `json:"customresourcedefinitions,omitempty"` | ||
| APIServiceDefinitions APIServiceDefinitions `json:"apiservicedefinitions,omitempty"` | ||
| @@ -595,6 +613,7 @@ type ResourceInstance struct { | ||
| // +kubebuilder:subresource:status | ||
| // +kubebuilder:printcolumn:name="Display",type=string,JSONPath=`.spec.displayName`,description="The name of the CSV" | ||
| // +kubebuilder:printcolumn:name="Version",type=string,JSONPath=`.spec.version`,description="The version of the CSV" | ||
| // +kubebuilder:printcolumn:name="Release",type=string,JSONPath=`.spec.release`,description="The release of this version of the CSV" | ||
| // +kubebuilder:printcolumn:name="Replaces",type=string,JSONPath=`.spec.replaces`,description="The name of a CSV that this one replaces" | ||
| // +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase` | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.