@@ -19,6 +19,7 @@ import (
1919 "k8s.io/client-go/dynamic"
2020 ctrl "sigs.k8s.io/controller-runtime"
2121 "sigs.k8s.io/controller-runtime/pkg/client"
22+ "sigs.k8s.io/controller-runtime/pkg/finalizer"
2223 logf "sigs.k8s.io/controller-runtime/pkg/log"
2324
2425 "go.miloapis.net/search/internal/cel"
@@ -57,12 +58,17 @@ type ResourceIndexPolicyReconciler struct {
5758
5859 RetryBaseDelay time.Duration
5960 RetryMaxDelay time.Duration
61+
62+ Finalizers finalizer.Finalizers
6063}
6164
6265const (
63- ReadyConditionType = "Ready"
64- ReadyConditionReason = "PolicyReady"
65- NotReadyConditionReason = "PolicyNotReady"
66+ FinalizerName = "search.miloapis.com/cleanup"
67+
68+ ReadyConditionType = "Ready"
69+ ReadyConditionReason = "PolicyReady"
70+ NotReadyConditionReason = "PolicyNotReady"
71+ TerminatingConditionReason = "Terminating"
6672
6773 SearchIndexReadyConditionType = "SearchIndexReady"
6874 IndexCreatedReason = "IndexCreated"
@@ -83,8 +89,9 @@ const (
8389 ReindexingInProgressReason = "ReindexingInProgress"
8490)
8591
86- // +kubebuilder:rbac:groups=search.miloapis.com,resources=resourceindexpolicies,verbs=get;list;watch
92+ // +kubebuilder:rbac:groups=search.miloapis.com,resources=resourceindexpolicies,verbs=get;list;watch;update;patch
8793// +kubebuilder:rbac:groups=search.miloapis.com,resources=resourceindexpolicies/status,verbs=get;update;patch
94+ // +kubebuilder:rbac:groups=search.miloapis.com,resources=resourceindexpolicies/finalizers,verbs=update
8895// +kubebuilder:rbac:groups=*,resources=*,verbs=get;list;watch
8996
9097// Reconcile matches the state of the cluster with the desired state of a ResourceIndexPolicy.
@@ -104,9 +111,26 @@ func (r *ResourceIndexPolicyReconciler) Reconcile(ctx context.Context, req ctrl.
104111 return ctrl.Result {}, err
105112 }
106113
107- // Check if the policy is being deleted
114+ // Run finalizers:
115+ finalizeResult , err := r .Finalizers .Finalize (ctx , policy )
116+ if err != nil {
117+ return ctrl.Result {}, fmt .Errorf ("failed to run finalizers: %w" , err )
118+ }
119+ if finalizeResult .Updated {
120+ logger .Info ("Finalizer updated the policy object, persisting to API server" )
121+ if updateErr := r .Client .Update (ctx , policy ); updateErr != nil {
122+ if errors .IsConflict (updateErr ) {
123+ logger .Info ("Conflict updating policy after finalizer update; requeuing" )
124+ return ctrl.Result {Requeue : true }, nil
125+ }
126+ logger .Error (updateErr , "Failed to update ResourceIndexPolicy after finalizer update" )
127+ return ctrl.Result {}, updateErr
128+ }
129+ return ctrl.Result {}, nil
130+ }
131+
108132 if policy .GetDeletionTimestamp () != nil {
109- logger .Info ("ResourceIndexPolicy is being deleted" )
133+ logger .Info ("ResourceIndexPolicy is being deleted, skipping reconciliation " )
110134 return ctrl.Result {}, nil
111135 }
112136
@@ -493,8 +517,67 @@ func (r *ResourceIndexPolicyReconciler) publishReindexMessages(
493517 return nil
494518}
495519
520+ // resourceIndexPolicyFinalizer handles Meilisearch cleanup when a
521+ // ResourceIndexPolicy is deleted.
522+ type resourceIndexPolicyFinalizer struct {
523+ Client client.Client
524+ SearchSDK * meilisearch.SDKClient
525+ }
526+
527+ func (f * resourceIndexPolicyFinalizer ) Finalize (ctx context.Context , obj client.Object ) (finalizer.Result , error ) {
528+ log := logf .FromContext (ctx ).WithName ("resourceindexpolicy-finalizer" )
529+ log .Info ("Finalizing ResourceIndexPolicy" )
530+
531+ policy , ok := obj .(* searchv1alpha1.ResourceIndexPolicy )
532+ if ! ok {
533+ return finalizer.Result {}, fmt .Errorf ("object is not a ResourceIndexPolicy" )
534+ }
535+
536+ // Set Ready=False immediately so consumers can observe the terminating state.
537+ oldStatus := policy .Status .DeepCopy ()
538+ meta .SetStatusCondition (& policy .Status .Conditions , metav1.Condition {
539+ Type : ReadyConditionType ,
540+ Status : metav1 .ConditionFalse ,
541+ Reason : TerminatingConditionReason ,
542+ Message : "ResourceIndexPolicy is being deleted" ,
543+ })
544+ if err := utils .UpdateStatusIfChanged (ctx , f .Client , log , policy , oldStatus , & policy .Status ); err != nil {
545+ log .Error (err , "Failed to update ResourceIndexPolicy status" )
546+ return finalizer.Result {}, err
547+ }
548+
549+ // Remove all documents then delete the index itself.
550+ searchIndex := policy .Status .IndexName
551+ if searchIndex == "" {
552+ log .Info ("No index name found, skipping cleanup" )
553+ return finalizer.Result {}, nil
554+ }
555+ log .Info ("Deleting all documents from search index" , "index" , searchIndex )
556+ if err := f .SearchSDK .DeleteAllDocuments (searchIndex ); err != nil {
557+ log .Error (err , "Failed to delete all documents from search index" )
558+ return finalizer.Result {}, err
559+ }
560+
561+ log .Info ("Deleting search index" , "index" , searchIndex )
562+ if err := f .SearchSDK .DeleteIndex (searchIndex ); err != nil {
563+ log .Error (err , "Failed to delete search index" )
564+ return finalizer.Result {}, err
565+ }
566+
567+ log .Info ("ResourceIndexPolicy cleanup complete" )
568+ return finalizer.Result {}, nil
569+ }
570+
496571// SetupWithManager sets up the controller with the Manager.
497572func (r * ResourceIndexPolicyReconciler ) SetupWithManager (mgr ctrl.Manager ) error {
573+ r .Finalizers = finalizer .NewFinalizers ()
574+ if err := r .Finalizers .Register (FinalizerName , & resourceIndexPolicyFinalizer {
575+ Client : r .Client ,
576+ SearchSDK : r .SearchSDK ,
577+ }); err != nil {
578+ return fmt .Errorf ("failed to register finalizer: %w" , err )
579+ }
580+
498581 return ctrl .NewControllerManagedBy (mgr ).
499582 For (& searchv1alpha1.ResourceIndexPolicy {}).
500583 Complete (r )
0 commit comments