Skip to content

Commit 3ece210

Browse files
committed
Introduce ClusterExtensionRevision API
1 parent 302d2df commit 3ece210

File tree

12 files changed

+2048
-2
lines changed

12 files changed

+2048
-2
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ manifests: $(CONTROLLER_GEN) $(KUSTOMIZE) #EXHELP Generate WebhookConfiguration,
150150
mkdir $(CRD_WORKING_DIR)
151151
$(CONTROLLER_GEN) --load-build-tags=$(GO_BUILD_TAGS) crd paths="./api/v1/..." output:crd:artifacts:config=$(CRD_WORKING_DIR)
152152
mv $(CRD_WORKING_DIR)/olm.operatorframework.io_clusterextensions.yaml $(KUSTOMIZE_OPCON_CRDS_DIR)
153+
mv $(CRD_WORKING_DIR)/olm.operatorframework.io_clusterextensionrevisions.yaml $(KUSTOMIZE_OPCON_CRDS_DIR)
153154
mv $(CRD_WORKING_DIR)/olm.operatorframework.io_clustercatalogs.yaml $(KUSTOMIZE_CATD_CRDS_DIR)
154155
rmdir $(CRD_WORKING_DIR)
155156
# Generate the remaining operator-controller manifests
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
Copyright 2024.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1
18+
19+
import (
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
)
22+
23+
// ClusterExtensionRevisionSpec defines the desired state of ClusterExtensionRevision
24+
type ClusterExtensionRevisionSpec struct {
25+
// clusterExtensionRef is a required reference to the ClusterExtension
26+
// that this revision represents an available upgrade for.
27+
//
28+
// +kubebuilder:validation:Required
29+
ClusterExtensionRef ClusterExtensionReference `json:"clusterExtensionRef"`
30+
31+
// version is a required field that specifies the exact version of the bundle
32+
// that represents this available upgrade.
33+
//
34+
// version follows the semantic versioning standard as defined in https://semver.org/.
35+
//
36+
// +kubebuilder:validation:Required
37+
// +kubebuilder:validation:XValidation:rule="self.matches(\"^([0-9]+)(\\\\.[0-9]+)?(\\\\.[0-9]+)?(-([-0-9A-Za-z]+(\\\\.[-0-9A-Za-z]+)*))?(\\\\+([-0-9A-Za-z]+(-\\\\.[-0-9A-Za-z]+)*))?\")",message="version must be well-formed semver"
38+
Version string `json:"version"`
39+
// bundleMetadata contains the complete metadata for the bundle that represents
40+
// this available upgrade.
41+
//
42+
// +kubebuilder:validation:Required
43+
BundleMetadata BundleMetadata `json:"bundleMetadata"`
44+
45+
// availableSince indicates when this upgrade revision was first detected
46+
// as being available. This helps track how long an upgrade has been pending.
47+
//
48+
// +kubebuilder:validation:Required
49+
AvailableSince metav1.Time `json:"availableSince"`
50+
51+
// approved indicates whether this upgrade revision has been approved for execution.
52+
// When set to true, the controller will automatically update the corresponding
53+
// ClusterExtension to trigger the upgrade to this version.
54+
//
55+
// +optional
56+
Approved bool `json:"approved,omitempty"`
57+
}
58+
59+
// ClusterExtensionReference identifies a ClusterExtension
60+
type ClusterExtensionReference struct {
61+
// name is the name of the ClusterExtension
62+
//
63+
// +kubebuilder:validation:Required
64+
// +kubebuilder:validation:MaxLength:=253
65+
// +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\")",message="name must be a valid DNS1123 subdomain"
66+
Name string `json:"name"`
67+
}
68+
69+
// ClusterExtensionRevisionStatus defines the observed state of ClusterExtensionRevision
70+
type ClusterExtensionRevisionStatus struct {
71+
// conditions represent the latest available observations of the ClusterExtensionRevision's current state.
72+
//
73+
// +patchMergeKey=type
74+
// +patchStrategy=merge
75+
// +listType=map
76+
// +listMapKey=type
77+
// +optional
78+
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
79+
}
80+
81+
// +kubebuilder:object:root=true
82+
// +kubebuilder:subresource:status
83+
// +kubebuilder:resource:scope=Cluster
84+
// +kubebuilder:printcolumn:name="Extension",type=string,JSONPath=".spec.clusterExtensionRef.name"
85+
// +kubebuilder:printcolumn:name="Version",type=string,JSONPath=".spec.version"
86+
// +kubebuilder:printcolumn:name="Channel",type=string,JSONPath=".spec.channel"
87+
// +kubebuilder:printcolumn:name="Available Since",type=date,JSONPath=".spec.availableSince"
88+
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=".metadata.creationTimestamp"
89+
90+
// ClusterExtensionRevision represents an available upgrade for a ClusterExtension.
91+
// It is created automatically by the operator-controller when new versions become
92+
// available in catalogs that represent valid upgrade paths for installed ClusterExtensions.
93+
type ClusterExtensionRevision struct {
94+
metav1.TypeMeta `json:",inline"`
95+
metav1.ObjectMeta `json:"metadata,omitempty"`
96+
97+
// spec defines the available upgrade revision details.
98+
//
99+
// +kubebuilder:validation:Required
100+
Spec ClusterExtensionRevisionSpec `json:"spec"`
101+
102+
// status represents the current status of this ClusterExtensionRevision.
103+
//
104+
// +optional
105+
Status ClusterExtensionRevisionStatus `json:"status,omitempty"`
106+
}
107+
108+
// +kubebuilder:object:root=true
109+
110+
// ClusterExtensionRevisionList contains a list of ClusterExtensionRevision
111+
type ClusterExtensionRevisionList struct {
112+
metav1.TypeMeta `json:",inline"`
113+
metav1.ListMeta `json:"metadata,omitempty"`
114+
Items []ClusterExtensionRevision `json:"items"`
115+
}
116+
117+
func init() {
118+
SchemeBuilder.Register(&ClusterExtensionRevision{}, &ClusterExtensionRevisionList{})
119+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
Copyright 2024.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1
18+
19+
import (
20+
"testing"
21+
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
)
24+
25+
func TestClusterExtensionRevisionTypes(t *testing.T) {
26+
// Test that we can create a ClusterExtensionRevision with all required fields
27+
revision := &ClusterExtensionRevision{
28+
ObjectMeta: metav1.ObjectMeta{
29+
Name: "test-extension-1.2.0",
30+
},
31+
Spec: ClusterExtensionRevisionSpec{
32+
ClusterExtensionRef: ClusterExtensionReference{
33+
Name: "test-extension",
34+
},
35+
Version: "1.2.0",
36+
BundleMetadata: BundleMetadata{
37+
Name: "test-operator.v1.2.0",
38+
Version: "1.2.0",
39+
},
40+
AvailableSince: metav1.Now(),
41+
Approved: false,
42+
},
43+
Status: ClusterExtensionRevisionStatus{
44+
Conditions: []metav1.Condition{
45+
{
46+
Type: "Available",
47+
Status: metav1.ConditionTrue,
48+
Reason: "UpgradeDetected",
49+
},
50+
},
51+
},
52+
}
53+
54+
// Verify the spec fields are accessible
55+
if revision.Spec.ClusterExtensionRef.Name != "test-extension" {
56+
t.Errorf("expected ClusterExtensionRef.Name to be 'test-extension', got %q", revision.Spec.ClusterExtensionRef.Name)
57+
}
58+
59+
if revision.Spec.Version != "1.2.0" {
60+
t.Errorf("expected Version to be '1.2.0', got %q", revision.Spec.Version)
61+
}
62+
63+
}
64+
65+
func TestClusterExtensionRevisionList(t *testing.T) {
66+
// Test that we can create a ClusterExtensionRevisionList
67+
revisionList := &ClusterExtensionRevisionList{
68+
Items: []ClusterExtensionRevision{
69+
{
70+
ObjectMeta: metav1.ObjectMeta{
71+
Name: "test-extension-1.2.0",
72+
},
73+
Spec: ClusterExtensionRevisionSpec{
74+
ClusterExtensionRef: ClusterExtensionReference{
75+
Name: "test-extension",
76+
},
77+
Version: "1.2.0",
78+
BundleMetadata: BundleMetadata{
79+
Name: "test-operator.v1.2.0",
80+
Version: "1.2.0",
81+
},
82+
AvailableSince: metav1.Now(),
83+
},
84+
},
85+
},
86+
}
87+
88+
if len(revisionList.Items) != 1 {
89+
t.Errorf("expected 1 item in list, got %d", len(revisionList.Items))
90+
}
91+
}

api/v1/zz_generated.deepcopy.go

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

cmd/operator-controller/main.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,16 @@ func run() error {
476476
return err
477477
}
478478

479+
if err = (&controllers.ClusterExtensionRevisionReconciler{
480+
Client: cl,
481+
Scheme: mgr.GetScheme(),
482+
Resolver: resolver,
483+
InstalledBundleGetter: &controllers.DefaultInstalledBundleGetter{ActionClientGetter: acg},
484+
}).SetupWithManager(mgr); err != nil {
485+
setupLog.Error(err, "unable to create controller", "controller", "ClusterExtensionRevision")
486+
return err
487+
}
488+
479489
if err = (&controllers.ClusterCatalogReconciler{
480490
Client: cl,
481491
CatalogCache: catalogClientBackend,

0 commit comments

Comments
 (0)