Skip to content

Commit 83c34fc

Browse files
committed
Implement Boxcutter applier, revision controller
1 parent 7d4414b commit 83c34fc

File tree

7 files changed

+982
-10
lines changed

7 files changed

+982
-10
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
@@ -32,13 +32,18 @@ import (
3232
"github.com/spf13/cobra"
3333
rbacv1 "k8s.io/api/rbac/v1"
3434
apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
35+
"k8s.io/apimachinery/pkg/labels"
3536
k8slabels "k8s.io/apimachinery/pkg/labels"
37+
"k8s.io/apimachinery/pkg/selection"
3638
k8stypes "k8s.io/apimachinery/pkg/types"
3739
apimachineryrand "k8s.io/apimachinery/pkg/util/rand"
40+
"k8s.io/client-go/discovery"
3841
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
3942
_ "k8s.io/client-go/plugin/pkg/client/auth"
43+
"k8s.io/client-go/rest"
4044
"k8s.io/klog/v2"
4145
"k8s.io/utils/ptr"
46+
"pkg.package-operator.run/boxcutter/managedcache"
4247
ctrl "sigs.k8s.io/controller-runtime"
4348
crcache "sigs.k8s.io/controller-runtime/pkg/cache"
4449
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
@@ -272,7 +277,8 @@ func run() error {
272277
"Metrics will not be served since the TLS certificate and key file are not provided.")
273278
}
274279

275-
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
280+
restConfig := ctrl.GetConfigOrDie()
281+
mgr, err := ctrl.NewManager(restConfig, ctrl.Options{
276282
Scheme: scheme.Scheme,
277283
Metrics: metricsServerOptions,
278284
PprofBindAddress: cfg.pprofAddr,
@@ -462,6 +468,31 @@ func run() error {
462468
return err
463469
}
464470

471+
// Boxcutter
472+
discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig)
473+
if err != nil {
474+
setupLog.Error(err, "unable to create discovery client")
475+
return err
476+
}
477+
mapFunc := func(ctx context.Context, ce *ocv1.ClusterExtension, c *rest.Config, o crcache.Options) (*rest.Config, crcache.Options, error) {
478+
// TODO: Rest Config Mapping / change ServiceAccount
479+
480+
// Cache scoping
481+
req1, err := labels.NewRequirement(
482+
controllers.ClusterExtensionRevisionOwnerLabel, selection.Equals, []string{ce.Name})
483+
if err != nil {
484+
return nil, o, err
485+
}
486+
o.DefaultLabelSelector = labels.NewSelector().Add(*req1)
487+
488+
return c, o, nil
489+
}
490+
accessManager := managedcache.NewObjectBoundAccessManager[*ocv1.ClusterExtension](
491+
ctrl.Log.WithName("accessmanager"), mapFunc, restConfig, crcache.Options{
492+
Scheme: mgr.GetScheme(), Mapper: mgr.GetRESTMapper(),
493+
})
494+
// Boxcutter
495+
465496
if err = (&controllers.ClusterExtensionReconciler{
466497
Client: cl,
467498
Resolver: resolver,
@@ -476,6 +507,17 @@ func run() error {
476507
return err
477508
}
478509

510+
if err = (&controllers.ClusterExtensionRevisionReconciler{
511+
Client: cl,
512+
AccessManager: accessManager,
513+
Scheme: mgr.GetScheme(),
514+
RestMapper: mgr.GetRESTMapper(),
515+
DiscoveryClient: discoveryClient,
516+
}).SetupWithManager(mgr); err != nil {
517+
setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension")
518+
return err
519+
}
520+
479521
if err = (&controllers.ClusterCatalogReconciler{
480522
Client: cl,
481523
CatalogCache: catalogClientBackend,

0 commit comments

Comments
 (0)