@@ -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+
59161func (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