Skip to content

Commit f8bf7e2

Browse files
committed
add Release version as an optional field in the CSV
Signed-off-by: grokspawn <jordan@nimblewidget.com>
1 parent e9c7bb5 commit f8bf7e2

File tree

8 files changed

+187
-5
lines changed

8 files changed

+187
-5
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ $(YQ): $(LOCALBIN)
129129
GOBIN=$(LOCALBIN) go install $(GO_INSTALL_OPTS) github.com/mikefarah/yq/v4@$(YQ_VERSION)
130130

131131
.PHONY: kind
132-
kind: $(KIND) ## Download yq locally if necessary.
132+
kind: $(KIND) ## Download kind locally if necessary.
133133
$(KIND): $(LOCALBIN)
134134
GOBIN=$(LOCALBIN) go install $(GO_INSTALL_OPTS) sigs.k8s.io/kind@latest
135135

crds/operators.coreos.com_clusterserviceversions.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ spec:
2727
jsonPath: .spec.version
2828
name: Version
2929
type: string
30+
- description: The release version of the CSV
31+
jsonPath: .spec.release
32+
name: Release
33+
type: string
3034
- description: The name of a CSV that this one replaces
3135
jsonPath: .spec.replaces
3236
name: Replaces
@@ -8988,6 +8992,8 @@ spec:
89888992
type: string
89898993
name:
89908994
type: string
8995+
release:
8996+
type: string
89918997
replaces:
89928998
description: The name of a CSV this one replaces. Should match the `metadata.Name` field of the old CSV.
89938999
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: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package release
2+
3+
import (
4+
"encoding/json"
5+
"strings"
6+
7+
semver "github.com/blang/semver/v4"
8+
)
9+
10+
// +k8s:openapi-gen=true
11+
// OperatorRelease is a wrapper around a slice of semver.PRVersion which supports correct
12+
// marshaling to YAML and JSON.
13+
// +kubebuilder:validation:Type=string
14+
type OperatorRelease struct {
15+
Release []semver.PRVersion `json:"-"`
16+
}
17+
18+
// DeepCopyInto creates a deep-copy of the Version value.
19+
func (v *OperatorRelease) DeepCopyInto(out *OperatorRelease) {
20+
out.Release = make([]semver.PRVersion, len(v.Release))
21+
copy(out.Release, v.Release)
22+
}
23+
24+
// MarshalJSON implements the encoding/json.Marshaler interface.
25+
func (v OperatorRelease) MarshalJSON() ([]byte, error) {
26+
segments := []string{}
27+
for _, segment := range v.Release {
28+
segments = append(segments, segment.String())
29+
}
30+
return json.Marshal(strings.Join(segments, "."))
31+
}
32+
33+
// UnmarshalJSON implements the encoding/json.Unmarshaler interface.
34+
func (v *OperatorRelease) UnmarshalJSON(data []byte) (err error) {
35+
var versionString string
36+
37+
if err = json.Unmarshal(data, &versionString); err != nil {
38+
return
39+
}
40+
41+
segments := strings.Split(versionString, ".")
42+
for _, segment := range segments {
43+
release, err := semver.NewPRVersion(segment)
44+
if err != nil {
45+
return err
46+
}
47+
v.Release = append(v.Release, release)
48+
}
49+
50+
return nil
51+
}
52+
53+
// OpenAPISchemaType is used by the kube-openapi generator when constructing
54+
// the OpenAPI spec of this type.
55+
//
56+
// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
57+
func (_ OperatorRelease) OpenAPISchemaType() []string { return []string{"string"} }
58+
59+
// OpenAPISchemaFormat is used by the kube-openapi generator when constructing
60+
// the OpenAPI spec of this type.
61+
// "semver" is not a standard openapi format but tooling may use the value regardless
62+
func (_ OperatorRelease) OpenAPISchemaFormat() string { return "semver" }

pkg/lib/release/release_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package release
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
semver "github.com/blang/semver/v4"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestOperatorReleaseMarshal(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
in OperatorRelease
15+
out []byte
16+
err error
17+
}{
18+
{
19+
name: "single-segment",
20+
in: OperatorRelease{Release: []semver.PRVersion{mustNewPRVersion("1")}},
21+
out: []byte(`"1"`),
22+
},
23+
{
24+
name: "two-segments",
25+
in: OperatorRelease{Release: []semver.PRVersion{mustNewPRVersion("1"), mustNewPRVersion("0")}},
26+
out: []byte(`"1.0"`),
27+
},
28+
{
29+
name: "multi-segment",
30+
in: OperatorRelease{Release: []semver.PRVersion{
31+
mustNewPRVersion("1"),
32+
mustNewPRVersion("2"),
33+
mustNewPRVersion("3"),
34+
}},
35+
out: []byte(`"1.2.3"`),
36+
},
37+
{
38+
name: "numeric-segments",
39+
in: OperatorRelease{Release: []semver.PRVersion{
40+
mustNewPRVersion("20240101"),
41+
mustNewPRVersion("12345"),
42+
}},
43+
out: []byte(`"20240101.12345"`),
44+
},
45+
}
46+
for _, tt := range tests {
47+
t.Run(tt.name, func(t *testing.T) {
48+
m, err := tt.in.MarshalJSON()
49+
require.Equal(t, tt.out, m, string(m))
50+
require.Equal(t, tt.err, err)
51+
})
52+
}
53+
}
54+
55+
func TestOperatorReleaseUnmarshal(t *testing.T) {
56+
type TestStruct struct {
57+
Release OperatorRelease `json:"r"`
58+
}
59+
tests := []struct {
60+
name string
61+
in []byte
62+
out TestStruct
63+
err error
64+
}{
65+
{
66+
name: "single-segment",
67+
in: []byte(`{"r": "1"}`),
68+
out: TestStruct{Release: OperatorRelease{Release: []semver.PRVersion{mustNewPRVersion("1")}}},
69+
},
70+
{
71+
name: "two-segments",
72+
in: []byte(`{"r": "1.0"}`),
73+
out: TestStruct{Release: OperatorRelease{Release: []semver.PRVersion{mustNewPRVersion("1"), mustNewPRVersion("0")}}},
74+
},
75+
{
76+
name: "multi-segment",
77+
in: []byte(`{"r": "1.2.3"}`),
78+
out: TestStruct{Release: OperatorRelease{Release: []semver.PRVersion{
79+
mustNewPRVersion("1"),
80+
mustNewPRVersion("2"),
81+
mustNewPRVersion("3"),
82+
}}},
83+
},
84+
{
85+
name: "numeric-segments",
86+
in: []byte(`{"r": "20240101.12345"}`),
87+
out: TestStruct{Release: OperatorRelease{Release: []semver.PRVersion{
88+
mustNewPRVersion("20240101"),
89+
mustNewPRVersion("12345"),
90+
}}},
91+
},
92+
}
93+
for _, tt := range tests {
94+
t.Run(tt.name, func(t *testing.T) {
95+
s := TestStruct{}
96+
err := json.Unmarshal(tt.in, &s)
97+
require.Equal(t, tt.out, s)
98+
require.Equal(t, tt.err, err)
99+
})
100+
}
101+
}
102+
103+
func mustNewPRVersion(s string) semver.PRVersion {
104+
v, err := semver.NewPRVersion(s)
105+
if err != nil {
106+
panic(err)
107+
}
108+
return v
109+
}

pkg/operators/v1alpha1/clusterserviceversion_types.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"k8s.io/apimachinery/pkg/labels"
1414
"k8s.io/apimachinery/pkg/util/intstr"
1515

16+
"github.com/operator-framework/api/pkg/lib/release"
1617
"github.com/operator-framework/api/pkg/lib/version"
1718
)
1819

@@ -274,8 +275,10 @@ type APIServiceDefinitions struct {
274275
// that can manage apps for a given version.
275276
// +k8s:openapi-gen=true
276277
type ClusterServiceVersionSpec struct {
277-
InstallStrategy NamedInstallStrategy `json:"install"`
278-
Version version.OperatorVersion `json:"version,omitempty"`
278+
InstallStrategy NamedInstallStrategy `json:"install"`
279+
Version version.OperatorVersion `json:"version,omitempty"`
280+
// +optional
281+
Release release.OperatorRelease `json:"release,omitzero"`
279282
Maturity string `json:"maturity,omitempty"`
280283
CustomResourceDefinitions CustomResourceDefinitions `json:"customresourcedefinitions,omitempty"`
281284
APIServiceDefinitions APIServiceDefinitions `json:"apiservicedefinitions,omitempty"`
@@ -595,6 +598,7 @@ type ResourceInstance struct {
595598
// +kubebuilder:subresource:status
596599
// +kubebuilder:printcolumn:name="Display",type=string,JSONPath=`.spec.displayName`,description="The name of the CSV"
597600
// +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"
598602
// +kubebuilder:printcolumn:name="Replaces",type=string,JSONPath=`.spec.replaces`,description="The name of a CSV that this one replaces"
599603
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
600604

pkg/operators/v1alpha1/zz_generated.deepcopy.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/validation/internal/typecheck.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func checkEmptyFields(result *errors.ManifestResult, v reflect.Value, parentStru
2828

2929
// Omitted field tags will contain ",omitempty", and ignored tags will
3030
// match "-" exactly, respectively.
31-
isOptionalField := strings.Contains(tag, ",omitempty") || tag == "-"
31+
isOptionalField := strings.Contains(tag, ",omitempty") || strings.Contains(tag, ",omitzero") || tag == "-"
3232
emptyVal := fieldValue.IsZero()
3333

3434
newParentStructName := fieldType.Name

0 commit comments

Comments
 (0)