Skip to content

Commit c016c3f

Browse files
RFC: Generic Reconciler for Bricks
This change shows an example of how a generic reconciler to be used for bricks could look like.
1 parent c86b789 commit c016c3f

File tree

3 files changed

+229
-147
lines changed

3 files changed

+229
-147
lines changed

api/v1alpha1/acl_types.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,18 @@ func (acl *AccessControlList) SetConditions(conditions []metav1.Condition) {
142142
acl.Status.Conditions = conditions
143143
}
144144

145+
func (acl *AccessControlList) GetDeviceRef() LocalObjectReference {
146+
return acl.Spec.DeviceRef
147+
}
148+
149+
func (acl *AccessControlList) GetProviderConfigRef() *TypedLocalObjectReference {
150+
return acl.Spec.ProviderConfigRef
151+
}
152+
153+
func (acl *AccessControlList) GetStatus() any {
154+
return acl.Status
155+
}
156+
145157
// +kubebuilder:object:root=true
146158

147159
// AccessControlListList contains a list of AccessControlList

internal/controller/acl_controller.go

Lines changed: 12 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ import (
77
"context"
88
"fmt"
99

10-
"k8s.io/apimachinery/pkg/api/equality"
11-
apierrors "k8s.io/apimachinery/pkg/api/errors"
12-
"k8s.io/apimachinery/pkg/api/meta"
1310
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1411
"k8s.io/apimachinery/pkg/runtime"
1512
kerrors "k8s.io/apimachinery/pkg/util/errors"
@@ -21,7 +18,6 @@ import (
2118

2219
"github.com/ironcore-dev/network-operator/api/v1alpha1"
2320
"github.com/ironcore-dev/network-operator/internal/conditions"
24-
"github.com/ironcore-dev/network-operator/internal/deviceutil"
2521
"github.com/ironcore-dev/network-operator/internal/provider"
2622
)
2723

@@ -46,129 +42,6 @@ type AccessControlListReconciler struct {
4642
// +kubebuilder:rbac:groups=networking.cloud.sap,resources=accesscontrollists/finalizers,verbs=update
4743
// +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
4844

49-
// Reconcile is part of the main kubernetes reconciliation loop which aims to
50-
// move the current state of the cluster closer to the desired state.
51-
//
52-
// For more details, check Reconcile and its Result here:
53-
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
54-
//
55-
// For more details about the method shape, read up here:
56-
// - https://ahmet.im/blog/controller-pitfalls/#reconcile-method-shape
57-
func (r *AccessControlListReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) {
58-
log := ctrl.LoggerFrom(ctx)
59-
log.Info("Reconciling resource")
60-
61-
obj := new(v1alpha1.AccessControlList)
62-
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
63-
if apierrors.IsNotFound(err) {
64-
// If the custom resource is not found then it usually means that it was deleted or not created
65-
// In this way, we will stop the reconciliation
66-
log.Info("Resource not found. Ignoring since object must be deleted")
67-
return ctrl.Result{}, nil
68-
}
69-
// Error reading the object - requeue the request.
70-
log.Error(err, "Failed to get resource")
71-
return ctrl.Result{}, err
72-
}
73-
74-
prov, ok := r.Provider().(provider.ACLProvider)
75-
if !ok {
76-
if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{
77-
Type: v1alpha1.ReadyCondition,
78-
Status: metav1.ConditionFalse,
79-
Reason: v1alpha1.NotImplementedReason,
80-
Message: "Provider does not implement provider.AccessControlListProvider",
81-
}) {
82-
return ctrl.Result{}, r.Status().Update(ctx, obj)
83-
}
84-
return ctrl.Result{}, nil
85-
}
86-
87-
device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name)
88-
if err != nil {
89-
return ctrl.Result{}, err
90-
}
91-
92-
conn, err := deviceutil.GetDeviceConnection(ctx, r, device)
93-
if err != nil {
94-
return ctrl.Result{}, err
95-
}
96-
97-
var cfg *provider.ProviderConfig
98-
if obj.Spec.ProviderConfigRef != nil {
99-
cfg, err = provider.GetProviderConfig(ctx, r, obj.Namespace, obj.Spec.ProviderConfigRef)
100-
if err != nil {
101-
return ctrl.Result{}, err
102-
}
103-
}
104-
105-
s := &aclScope{
106-
Device: device,
107-
ACL: obj,
108-
Connection: conn,
109-
ProviderConfig: cfg,
110-
Provider: prov,
111-
}
112-
113-
if !obj.DeletionTimestamp.IsZero() {
114-
if controllerutil.ContainsFinalizer(obj, v1alpha1.FinalizerName) {
115-
if err := r.finalize(ctx, s); err != nil {
116-
log.Error(err, "Failed to finalize resource")
117-
return ctrl.Result{}, err
118-
}
119-
controllerutil.RemoveFinalizer(obj, v1alpha1.FinalizerName)
120-
if err := r.Update(ctx, obj); err != nil {
121-
log.Error(err, "Failed to remove finalizer from resource")
122-
return ctrl.Result{}, err
123-
}
124-
}
125-
log.Info("Resource is being deleted, skipping reconciliation")
126-
return ctrl.Result{}, nil
127-
}
128-
129-
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers
130-
if !controllerutil.ContainsFinalizer(obj, v1alpha1.FinalizerName) {
131-
controllerutil.AddFinalizer(obj, v1alpha1.FinalizerName)
132-
if err := r.Update(ctx, obj); err != nil {
133-
log.Error(err, "Failed to add finalizer to resource")
134-
return ctrl.Result{}, err
135-
}
136-
log.Info("Added finalizer to resource")
137-
return ctrl.Result{}, nil
138-
}
139-
140-
orig := obj.DeepCopy()
141-
if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) {
142-
log.Info("Initializing status conditions")
143-
return ctrl.Result{}, r.Status().Update(ctx, obj)
144-
}
145-
146-
// Always attempt to update the metadata/status after reconciliation
147-
defer func() {
148-
if !equality.Semantic.DeepEqual(orig.ObjectMeta, obj.ObjectMeta) {
149-
if err := r.Patch(ctx, obj, client.MergeFrom(orig)); err != nil {
150-
log.Error(err, "Failed to update resource metadata")
151-
reterr = kerrors.NewAggregate([]error{reterr, err})
152-
}
153-
return
154-
}
155-
156-
if !equality.Semantic.DeepEqual(orig.Status, obj.Status) {
157-
if err := r.Status().Patch(ctx, obj, client.MergeFrom(orig)); err != nil {
158-
log.Error(err, "Failed to update status")
159-
reterr = kerrors.NewAggregate([]error{reterr, err})
160-
}
161-
}
162-
}()
163-
164-
if err := r.reconcile(ctx, s); err != nil {
165-
log.Error(err, "Failed to reconcile resource")
166-
return ctrl.Result{}, err
167-
}
168-
169-
return ctrl.Result{}, nil
170-
}
171-
17245
// SetupWithManager sets up the controller with the Manager.
17346
func (r *AccessControlListReconciler) SetupWithManager(mgr ctrl.Manager) error {
17447
labelSelector := metav1.LabelSelector{}
@@ -181,32 +54,24 @@ func (r *AccessControlListReconciler) SetupWithManager(mgr ctrl.Manager) error {
18154
return fmt.Errorf("failed to create label selector predicate: %w", err)
18255
}
18356

57+
rec := AsReconciler(r.Client, r.Provider, r)
18458
return ctrl.NewControllerManagedBy(mgr).
18559
For(&v1alpha1.AccessControlList{}).
18660
Named("accesscontrollist").
18761
WithEventFilter(filter).
188-
Complete(r)
189-
}
190-
191-
// scope holds the different objects that are read and used during the reconcile.
192-
type aclScope struct {
193-
Device *v1alpha1.Device
194-
ACL *v1alpha1.AccessControlList
195-
Connection *deviceutil.Connection
196-
ProviderConfig *provider.ProviderConfig
197-
Provider provider.ACLProvider
62+
Complete(rec)
19863
}
19964

200-
func (r *AccessControlListReconciler) reconcile(ctx context.Context, s *aclScope) (reterr error) {
201-
if s.ACL.Labels == nil {
202-
s.ACL.Labels = make(map[string]string)
65+
func (r *AccessControlListReconciler) Reconcile(ctx context.Context, s *TypedScope[*v1alpha1.AccessControlList, provider.ACLProvider]) (reterr error) {
66+
if s.Resource.Labels == nil {
67+
s.Resource.Labels = make(map[string]string)
20368
}
20469

205-
s.ACL.Labels[v1alpha1.DeviceLabel] = s.Device.Name
70+
s.Resource.Labels[v1alpha1.DeviceLabel] = s.Device.Name
20671

20772
// Ensure the AccessControlList is owned by the Device.
208-
if !controllerutil.HasControllerReference(s.ACL) {
209-
if err := controllerutil.SetOwnerReference(s.Device, s.ACL, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil {
73+
if !controllerutil.HasControllerReference(s.Resource, s.Device, r.Scheme) {
74+
if err := controllerutil.SetOwnerReference(s.Device, s.Resource, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil {
21075
return err
21176
}
21277
}
@@ -222,19 +87,19 @@ func (r *AccessControlListReconciler) reconcile(ctx context.Context, s *aclScope
22287

22388
// Ensure the AccessControlList is realized on the provider.
22489
err := s.Provider.EnsureACL(ctx, &provider.EnsureACLRequest{
225-
ACL: s.ACL,
90+
ACL: s.Resource,
22691
ProviderConfig: s.ProviderConfig,
22792
})
22893

22994
cond := conditions.FromError(err)
23095
// As this resource is configuration only, we use the Configured condition as top-level Ready condition.
23196
cond.Type = v1alpha1.ReadyCondition
232-
conditions.Set(s.ACL, cond)
97+
conditions.Set(s.Resource, cond)
23398

23499
return err
235100
}
236101

237-
func (r *AccessControlListReconciler) finalize(ctx context.Context, s *aclScope) (reterr error) {
102+
func (r *AccessControlListReconciler) Finalize(ctx context.Context, s *TypedScope[*v1alpha1.AccessControlList, provider.ACLProvider]) (reterr error) {
238103
if err := s.Provider.Connect(ctx, s.Connection); err != nil {
239104
return fmt.Errorf("failed to connect to provider: %w", err)
240105
}
@@ -245,7 +110,7 @@ func (r *AccessControlListReconciler) finalize(ctx context.Context, s *aclScope)
245110
}()
246111

247112
return s.Provider.DeleteACL(ctx, &provider.DeleteACLRequest{
248-
Name: s.ACL.Spec.Name,
113+
Name: s.Resource.Spec.Name,
249114
ProviderConfig: s.ProviderConfig,
250115
})
251116
}

0 commit comments

Comments
 (0)