Skip to content

Commit b4e56a1

Browse files
committed
Enhance: Implement resource cleanup and reconciliation logic in OvercommitClass controller
1 parent e5cb3e7 commit b4e56a1

1 file changed

Lines changed: 220 additions & 35 deletions

File tree

internal/controller/overcommitclass/overcommitclass_controller.go

Lines changed: 220 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@ import (
1313

1414
resources "github.com/InditexTech/k8s-overcommit-operator/internal/resources"
1515
"github.com/InditexTech/k8s-overcommit-operator/internal/utils"
16-
certmanager "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
17-
admissionv1 "k8s.io/api/admissionregistration/v1"
18-
appsv1 "k8s.io/api/apps/v1"
1916
corev1 "k8s.io/api/core/v1"
20-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2117
"k8s.io/apimachinery/pkg/runtime"
2218
ctrl "sigs.k8s.io/controller-runtime"
2319
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -56,8 +52,115 @@ func (r *OvercommitClassReconciler) SetupWithManager(mgr ctrl.Manager) error {
5652
Complete(r)
5753
}
5854

55+
// cleanupResources ensures that all resources associated with the OvercommitClass CR are deleted.
56+
func (r *OvercommitClassReconciler) cleanupResources(ctx context.Context, overcommitClass *overcommit.OvercommitClass) error {
57+
logger := log.FromContext(ctx)
58+
logger.Info("Cleaning up resources associated with OvercommitClass CR", "name", overcommitClass.Name)
59+
60+
// Delete Deployment
61+
deployment := resources.CreateDeployment(*overcommitClass)
62+
if deployment != nil {
63+
err := r.Delete(ctx, deployment)
64+
if err != nil && client.IgnoreNotFound(err) != nil {
65+
logger.Error(err, "Failed to delete Deployment")
66+
return err
67+
}
68+
}
69+
70+
// Delete Service
71+
service := resources.CreateService(overcommitClass.Name)
72+
if service != nil {
73+
err := r.Delete(ctx, service)
74+
if err != nil && client.IgnoreNotFound(err) != nil {
75+
logger.Error(err, "Failed to delete Service")
76+
return err
77+
}
78+
}
79+
80+
// Delete Certificate
81+
certificate := resources.CreateCertificate(overcommitClass.Name, *service)
82+
if certificate != nil {
83+
err := r.Delete(ctx, certificate)
84+
if err != nil && client.IgnoreNotFound(err) != nil {
85+
logger.Error(err, "Failed to delete Certificate")
86+
return err
87+
}
88+
}
89+
90+
// Delete MutatingWebhookConfiguration
91+
label, err := utils.GetOvercommitLabel(ctx, r.Client)
92+
if err != nil {
93+
logger.Error(err, "Failed to get Overcommit label")
94+
return err
95+
}
96+
97+
webhookConfig := resources.CreateMutatingWebhookConfiguration(*overcommitClass, *service, *certificate, label)
98+
if webhookConfig != nil {
99+
err := r.Delete(ctx, webhookConfig)
100+
if err != nil && client.IgnoreNotFound(err) != nil {
101+
logger.Error(err, "Failed to delete MutatingWebhookConfiguration")
102+
return err
103+
}
104+
}
105+
106+
logger.Info("Successfully cleaned up resources for OvercommitClass", "name", overcommitClass.Name)
107+
return nil
108+
}
109+
110+
// envVarsEqual compares two slices of environment variables to see if they're equal
111+
func envVarsEqual(a, b []corev1.EnvVar) bool {
112+
if len(a) != len(b) {
113+
return false
114+
}
115+
116+
// Create maps for easier comparison
117+
mapA := make(map[string]string)
118+
mapB := make(map[string]string)
119+
120+
for _, env := range a {
121+
mapA[env.Name] = env.Value
122+
}
123+
124+
for _, env := range b {
125+
mapB[env.Name] = env.Value
126+
}
127+
128+
// Compare maps
129+
for key, valueA := range mapA {
130+
if valueB, exists := mapB[key]; !exists || valueA != valueB {
131+
return false
132+
}
133+
}
134+
135+
return true
136+
}
137+
138+
// mapsEqual compares two string maps to see if they're equal
139+
func mapsEqual(a, b map[string]string) bool {
140+
// Handle nil cases
141+
if a == nil && b == nil {
142+
return true
143+
}
144+
if a == nil || b == nil {
145+
return false
146+
}
147+
148+
if len(a) != len(b) {
149+
return false
150+
}
151+
152+
for key, valueA := range a {
153+
if valueB, exists := b[key]; !exists || valueA != valueB {
154+
return false
155+
}
156+
}
157+
158+
return true
159+
}
160+
59161
func (r *OvercommitClassReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
60162
logger := log.FromContext(ctx)
163+
logger.Info("Starting reconciliation", "name", req.Name, "namespace", req.Namespace, "time", time.Now().Format("15:04:05"))
61164

62165
label, err := utils.GetOvercommitLabel(ctx, r.Client)
63166
if err != nil {
@@ -69,27 +172,48 @@ func (r *OvercommitClassReconciler) Reconcile(ctx context.Context, req ctrl.Requ
69172

70173
err = r.Get(ctx, req.NamespacedName, overcommitClass)
71174
if err != nil {
72-
logger.Info("Deleting resources for the class", "name", req.Name)
73-
err := ensureResourceDeleted(ctx, r.Client, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: req.Name + "-webhook-deployment", Namespace: "k8s-overcommit"}})
74-
if err != nil {
75-
logger.Error(err, "Failed to delete Deployment")
175+
if client.IgnoreNotFound(err) != nil {
176+
return ctrl.Result{}, err
76177
}
77-
err = ensureResourceDeleted(ctx, r.Client, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: req.Name + "-webhook-service", Namespace: "k8s-overcommit"}})
178+
// CR not found, nothing to do
179+
logger.Info("OvercommitClass CR not found, skipping reconciliation")
180+
return ctrl.Result{}, nil
181+
}
182+
183+
// Check if the CR is being deleted
184+
if !overcommitClass.ObjectMeta.DeletionTimestamp.IsZero() {
185+
logger.Info("OvercommitClass CR is being deleted, cleaning up resources")
186+
187+
// Clean up resources
188+
err := r.cleanupResources(ctx, overcommitClass)
78189
if err != nil {
79-
logger.Error(err, "Failed to delete Service")
190+
logger.Error(err, "Failed to clean up resources")
191+
return ctrl.Result{}, err
80192
}
81-
err = ensureResourceDeleted(ctx, r.Client, &certmanager.Certificate{ObjectMeta: metav1.ObjectMeta{Name: req.Name + "-webhook-certificate", Namespace: "k8s-overcommit"}})
193+
194+
// Remove finalizer if cleanup is successful
195+
controllerutil.RemoveFinalizer(overcommitClass, "overcommitclass.finalizer")
196+
err = r.Update(ctx, overcommitClass)
82197
if err != nil {
83-
logger.Error(err, "Failed to delete Certificate")
198+
logger.Error(err, "Failed to remove finalizer")
199+
return ctrl.Result{}, err
84200
}
85-
err = ensureResourceDeleted(ctx, r.Client, &admissionv1.MutatingWebhookConfiguration{ObjectMeta: metav1.ObjectMeta{Name: req.Name + "overcommit-webhook", Namespace: "k8s-overcommit"}})
201+
202+
return ctrl.Result{}, nil
203+
}
204+
205+
// Add finalizer if not present
206+
if !controllerutil.ContainsFinalizer(overcommitClass, "overcommitclass.finalizer") {
207+
logger.Info("Adding finalizer to OvercommitClass CR")
208+
controllerutil.AddFinalizer(overcommitClass, "overcommitclass.finalizer")
209+
err = r.Update(ctx, overcommitClass)
86210
if err != nil {
87-
logger.Error(err, "Failed to delete MutatingWebhookConfiguration")
88-
}
89-
if getTotalClasses(ctx, r.Client) != nil {
90-
logger.Error(err, "Failed to update metrics")
211+
logger.Error(err, "Failed to add finalizer")
212+
return ctrl.Result{}, err
91213
}
92-
return ctrl.Result{}, err
214+
// Return early to trigger a new reconciliation with the updated object
215+
logger.Info("Finalizer added, requeuing reconciliation")
216+
return ctrl.Result{}, nil
93217
}
94218
// Check if the OvercommitClass has the correct owner reference
95219
overcommitResource, err := utils.GetOvercommit(ctx, r.Client)
@@ -133,51 +257,110 @@ func (r *OvercommitClassReconciler) Reconcile(ctx context.Context, req ctrl.Requ
133257
return ctrl.Result{}, nil
134258
}
135259

136-
logger.Info("Reconciling resources for the class", "name", overcommitClass)
260+
logger.Info("Reconciling resources for the class", "name", overcommitClass.Name)
261+
262+
// Create resource definitions
137263
deployment := resources.CreateDeployment(*overcommitClass)
138264
service := resources.CreateService(overcommitClass.Name)
139265
certificate := resources.CreateCertificate(overcommitClass.Name, *service)
140266
webhookConfig := resources.CreateMutatingWebhookConfiguration(*overcommitClass, *service, *certificate, label)
141267

142-
// Use CreateOrUpdate for each resource
268+
// Reconcile Deployment with improved logic
143269
_, err = controllerutil.CreateOrUpdate(ctx, r.Client, deployment, func() error {
144-
// Update the deployment spec if needed
270+
// Regenerate the desired deployment spec
145271
updatedDeployment := resources.CreateDeployment(*overcommitClass)
146-
deployment.Spec = updatedDeployment.Spec
147-
return controllerutil.SetControllerReference(overcommitClass, deployment, r.Scheme)
272+
273+
// Only update if there are actual differences
274+
if deployment.CreationTimestamp.IsZero() {
275+
// New deployment, set everything
276+
deployment.Spec = updatedDeployment.Spec
277+
deployment.ObjectMeta.Labels = updatedDeployment.ObjectMeta.Labels
278+
deployment.ObjectMeta.Annotations = updatedDeployment.ObjectMeta.Annotations
279+
return controllerutil.SetControllerReference(overcommitClass, deployment, r.Scheme)
280+
} else {
281+
// Existing deployment, only update specific fields if needed
282+
updated := false
283+
284+
// Check if image changed
285+
if len(updatedDeployment.Spec.Template.Spec.Containers) > 0 && len(deployment.Spec.Template.Spec.Containers) > 0 {
286+
if updatedDeployment.Spec.Template.Spec.Containers[0].Image != deployment.Spec.Template.Spec.Containers[0].Image {
287+
deployment.Spec.Template.Spec.Containers[0].Image = updatedDeployment.Spec.Template.Spec.Containers[0].Image
288+
updated = true
289+
}
290+
}
291+
292+
// Update environment variables if they changed
293+
if len(updatedDeployment.Spec.Template.Spec.Containers) > 0 && len(deployment.Spec.Template.Spec.Containers) > 0 {
294+
if !envVarsEqual(updatedDeployment.Spec.Template.Spec.Containers[0].Env, deployment.Spec.Template.Spec.Containers[0].Env) {
295+
deployment.Spec.Template.Spec.Containers[0].Env = updatedDeployment.Spec.Template.Spec.Containers[0].Env
296+
updated = true
297+
}
298+
}
299+
300+
// Update template annotations if they changed
301+
if !mapsEqual(updatedDeployment.Spec.Template.Annotations, deployment.Spec.Template.Annotations) {
302+
deployment.Spec.Template.Annotations = updatedDeployment.Spec.Template.Annotations
303+
updated = true
304+
}
305+
306+
// Update template labels if they changed
307+
if !mapsEqual(updatedDeployment.Spec.Template.Labels, deployment.Spec.Template.Labels) {
308+
deployment.Spec.Template.Labels = updatedDeployment.Spec.Template.Labels
309+
updated = true
310+
}
311+
312+
// Only set controller reference if we actually updated something
313+
if updated {
314+
return controllerutil.SetControllerReference(overcommitClass, deployment, r.Scheme)
315+
}
316+
}
317+
return nil
148318
})
149319
if err != nil {
150320
logger.Error(err, "Failed to create or update Deployment")
151321
return ctrl.Result{}, err
152322
}
153323

324+
// Reconcile Service with improved logic
154325
_, err = controllerutil.CreateOrUpdate(ctx, r.Client, service, func() error {
155-
// Update the service spec if needed
156-
updatedService := resources.CreateService(overcommitClass.Name)
157-
service.Spec = updatedService.Spec
158-
return controllerutil.SetControllerReference(overcommitClass, service, r.Scheme)
326+
// Only update spec if this is a new resource
327+
if service.CreationTimestamp.IsZero() {
328+
updatedService := resources.CreateService(overcommitClass.Name)
329+
service.Spec = updatedService.Spec
330+
return controllerutil.SetControllerReference(overcommitClass, service, r.Scheme)
331+
}
332+
return nil
159333
})
160334
if err != nil {
161335
logger.Error(err, "Failed to create or update Service")
162336
return ctrl.Result{}, err
163337
}
164338

339+
// Reconcile Certificate with improved logic
165340
_, err = controllerutil.CreateOrUpdate(ctx, r.Client, certificate, func() error {
166-
// Update the certificate spec if needed
167-
updatedCertificate := resources.CreateCertificate(overcommitClass.Name, *service)
168-
certificate.Spec = updatedCertificate.Spec
169-
return controllerutil.SetControllerReference(overcommitClass, certificate, r.Scheme)
341+
// Only update spec if this is a new resource
342+
if certificate.CreationTimestamp.IsZero() {
343+
updatedCertificate := resources.CreateCertificate(overcommitClass.Name, *service)
344+
certificate.Spec = updatedCertificate.Spec
345+
return controllerutil.SetControllerReference(overcommitClass, certificate, r.Scheme)
346+
}
347+
return nil
170348
})
171349
if err != nil {
172350
logger.Error(err, "Failed to create or update Certificate")
173351
return ctrl.Result{}, err
174352
}
175353

354+
// Reconcile MutatingWebhookConfiguration with improved logic
176355
_, err = controllerutil.CreateOrUpdate(ctx, r.Client, webhookConfig, func() error {
177-
// Update the webhook configuration spec if needed
178-
updatedWebhookConfig := resources.CreateMutatingWebhookConfiguration(*overcommitClass, *service, *certificate, label)
179-
webhookConfig.Webhooks = updatedWebhookConfig.Webhooks
180-
return controllerutil.SetControllerReference(overcommitClass, webhookConfig, r.Scheme)
356+
// Only update webhooks if this is a new resource
357+
if webhookConfig.CreationTimestamp.IsZero() {
358+
updatedWebhookConfig := resources.CreateMutatingWebhookConfiguration(*overcommitClass, *service, *certificate, label)
359+
webhookConfig.Annotations = updatedWebhookConfig.Annotations
360+
webhookConfig.Webhooks = updatedWebhookConfig.Webhooks
361+
return controllerutil.SetControllerReference(overcommitClass, webhookConfig, r.Scheme)
362+
}
363+
return nil
181364
})
182365
if err != nil {
183366
logger.Error(err, "Failed to create or update MutatingWebhookConfiguration")
@@ -195,6 +378,8 @@ func (r *OvercommitClassReconciler) Reconcile(ctx context.Context, req ctrl.Requ
195378
return ctrl.Result{}, err
196379
}
197380

381+
// Only requeue periodically for status checks, not immediately
382+
logger.Info("Reconciliation completed successfully", "nextReconcile", "10 seconds", "time", time.Now().Format("15:04:05"))
198383
return ctrl.Result{
199384
RequeueAfter: 10 * time.Second,
200385
}, nil

0 commit comments

Comments
 (0)