Skip to content

Commit 2d7b918

Browse files
committed
Implement Boxcutter applier, revision controller
1 parent 414db44 commit 2d7b918

File tree

7 files changed

+993
-19
lines changed

7 files changed

+993
-19
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
22+
"k8s.io/apimachinery/pkg/types"
23+
)
24+
25+
const ClusterExtensionRevisionKind = "ClusterExtensionRevision"
26+
27+
// ClusterExtensionRevisionSpec defines the desired state of ClusterExtensionRevision.
28+
type ClusterExtensionRevisionSpec struct {
29+
// Specifies the lifecycle state of the ClusterExtensionRevision.
30+
// +kubebuilder:default="Active"
31+
// +kubebuilder:validation:Enum=Active;Paused;Archived
32+
// +kubebuilder:validation:XValidation:rule="oldSelf == "Active" || oldSelf == "Paused" || oldSelf == 'Archived' && oldSelf == self", message="can not un-archive"
33+
LifecycleState ClusterExtensionRevisionLifecycleState `json:"lifecycleState,omitempty"`
34+
// +kubebuilder:validation:Required
35+
// +kubebuilder:validation:XValidation:rule="self == oldSelf", message="revision is immutable"
36+
Revision int64 `json:"revision"`
37+
// +kubebuilder:validation:Required
38+
// +kubebuilder:validation:XValidation:rule="self == oldSelf", message="phases is immutable"
39+
Phases []ClusterExtensionRevisionPhase `json:"phases"`
40+
// +kubebuilder:validation:XValidation:rule="self == oldSelf", message="previous is immutable"
41+
Previous []ClusterExtensionRevisionPrevious `json:"previous"`
42+
}
43+
44+
// ClusterExtensionRevisionLifecycleState specifies the lifecycle state of the ClusterExtensionRevision.
45+
type ClusterExtensionRevisionLifecycleState string
46+
47+
const (
48+
// ClusterExtensionRevisionLifecycleStateActive / "Active" is the default lifecycle state.
49+
ClusterExtensionRevisionLifecycleStateActive ClusterExtensionRevisionLifecycleState = "Active"
50+
// ClusterExtensionRevisionLifecycleStatePaused / "Paused" disables reconciliation of the ClusterExtensionRevision.
51+
// Only Status updates will still propagated, but object changes will not be reconciled.
52+
ClusterExtensionRevisionLifecycleStatePaused ClusterExtensionRevisionLifecycleState = "Paused"
53+
// ClusterExtensionRevisionLifecycleStateArchived / "Archived" disables reconciliation while also "scaling to zero",
54+
// which deletes all objects that are not excluded via the pausedFor property and
55+
// removes itself from the owner list of all other objects previously under management.
56+
ClusterExtensionRevisionLifecycleStateArchived ClusterExtensionRevisionLifecycleState = "Archived"
57+
)
58+
59+
type ClusterExtensionRevisionPhase struct {
60+
Name string `json:"name"`
61+
Objects []ClusterExtensionRevisionObject `json:"objects"`
62+
}
63+
64+
type ClusterExtensionRevisionObject struct {
65+
// +kubebuilder:validation:EmbeddedResource
66+
// +kubebuilder:pruning:PreserveUnknownFields
67+
Object unstructured.Unstructured `json:"object"`
68+
}
69+
70+
type ClusterExtensionRevisionPrevious struct {
71+
// +kubebuilder:validation:Required
72+
Name string `json:"name"`
73+
// +kubebuilder:validation:Required
74+
UID types.UID `json:"uid"`
75+
}
76+
77+
// ClusterExtensionRevisionStatus defines the observed state of a ClusterExtensionRevision.
78+
type ClusterExtensionRevisionStatus struct {
79+
// +patchMergeKey=type
80+
// +patchStrategy=merge
81+
// +listType=map
82+
// +listMapKey=type
83+
// +optional
84+
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
85+
}
86+
87+
// +kubebuilder:object:root=true
88+
// +kubebuilder:resource:scope=Cluster
89+
// +kubebuilder:subresource:status
90+
91+
// ClusterExtensionRevision is the Schema for the clusterextensionrevisions API
92+
type ClusterExtensionRevision struct {
93+
metav1.TypeMeta `json:",inline"`
94+
metav1.ObjectMeta `json:"metadata,omitempty"`
95+
96+
// spec is an optional field that defines the desired state of the ClusterExtension.
97+
// +optional
98+
Spec ClusterExtensionRevisionSpec `json:"spec,omitempty"`
99+
100+
// status is an optional field that defines the observed state of the ClusterExtension.
101+
// +optional
102+
Status ClusterExtensionRevisionStatus `json:"status,omitempty"`
103+
}
104+
105+
// +kubebuilder:object:root=true
106+
107+
// ClusterExtensionRevisionList contains a list of ClusterExtensionRevision
108+
type ClusterExtensionRevisionList struct {
109+
metav1.TypeMeta `json:",inline"`
110+
111+
// +optional
112+
metav1.ListMeta `json:"metadata,omitempty"`
113+
114+
// items is a required list of ClusterExtensionRevision objects.
115+
//
116+
// +kubebuilder:validation:Required
117+
Items []ClusterExtensionRevision `json:"items"`
118+
}
119+
120+
func init() {
121+
SchemeBuilder.Register(&ClusterExtensionRevision{}, &ClusterExtensionRevisionList{})
122+
}

api/v1/zz_generated.deepcopy.go

Lines changed: 162 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: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,18 @@ import (
3434
rbacv1 "k8s.io/api/rbac/v1"
3535
apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
3636
"k8s.io/apimachinery/pkg/fields"
37+
"k8s.io/apimachinery/pkg/labels"
3738
k8slabels "k8s.io/apimachinery/pkg/labels"
39+
"k8s.io/apimachinery/pkg/selection"
3840
k8stypes "k8s.io/apimachinery/pkg/types"
3941
apimachineryrand "k8s.io/apimachinery/pkg/util/rand"
42+
"k8s.io/client-go/discovery"
4043
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
4144
_ "k8s.io/client-go/plugin/pkg/client/auth"
45+
"k8s.io/client-go/rest"
4246
"k8s.io/klog/v2"
4347
"k8s.io/utils/ptr"
48+
"pkg.package-operator.run/boxcutter/managedcache"
4449
ctrl "sigs.k8s.io/controller-runtime"
4550
crcache "sigs.k8s.io/controller-runtime/pkg/cache"
4651
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
@@ -266,7 +271,8 @@ func run() error {
266271
"Metrics will not be served since the TLS certificate and key file are not provided.")
267272
}
268273

269-
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
274+
restConfig := ctrl.GetConfigOrDie()
275+
mgr, err := ctrl.NewManager(restConfig, ctrl.Options{
270276
Scheme: scheme.Scheme,
271277
Metrics: metricsServerOptions,
272278
PprofBindAddress: cfg.pprofAddr,
@@ -437,6 +443,31 @@ func run() error {
437443
return err
438444
}
439445

446+
// Boxcutter
447+
discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig)
448+
if err != nil {
449+
setupLog.Error(err, "unable to create discovery client")
450+
return err
451+
}
452+
mapFunc := func(ctx context.Context, ce *ocv1.ClusterExtension, c *rest.Config, o crcache.Options) (*rest.Config, crcache.Options, error) {
453+
// TODO: Rest Config Mapping / change ServiceAccount
454+
455+
// Cache scoping
456+
req1, err := labels.NewRequirement(
457+
controllers.ClusterExtensionRevisionOwnerLabel, selection.Equals, []string{ce.Name})
458+
if err != nil {
459+
return nil, o, err
460+
}
461+
o.DefaultLabelSelector = labels.NewSelector().Add(*req1)
462+
463+
return c, o, nil
464+
}
465+
accessManager := managedcache.NewObjectBoundAccessManager[*ocv1.ClusterExtension](
466+
ctrl.Log.WithName("accessmanager"), mapFunc, restConfig, crcache.Options{
467+
Scheme: mgr.GetScheme(), Mapper: mgr.GetRESTMapper(),
468+
})
469+
// Boxcutter
470+
440471
if err = (&controllers.ClusterExtensionReconciler{
441472
Client: cl,
442473
Resolver: resolver,
@@ -451,6 +482,17 @@ func run() error {
451482
return err
452483
}
453484

485+
if err = (&controllers.ClusterExtensionRevisionReconciler{
486+
Client: cl,
487+
AccessManager: accessManager,
488+
Scheme: mgr.GetScheme(),
489+
RestMapper: mgr.GetRESTMapper(),
490+
DiscoveryClient: discoveryClient,
491+
}).SetupWithManager(mgr); err != nil {
492+
setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension")
493+
return err
494+
}
495+
454496
if err = (&controllers.ClusterCatalogReconciler{
455497
Client: cl,
456498
CatalogCache: catalogClientBackend,

0 commit comments

Comments
 (0)