@@ -87,6 +87,23 @@ func (r *PostgresDatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Req
8787 return r .updateStatus (ctx , postgresDB , conditionType , conditionStatus , reason , message , phase )
8888 }
8989
90+ // Handle finalizer: cleanup on deletion, register on creation
91+ if ! postgresDB .ObjectMeta .DeletionTimestamp .IsZero () {
92+ if err := r .handleDeletion (ctx , postgresDB ); err != nil {
93+ logger .Error (err , "Cleanup failed for PostgresDatabase" )
94+ return ctrl.Result {}, err
95+ }
96+ return ctrl.Result {}, nil
97+ }
98+ if ! controllerutil .ContainsFinalizer (postgresDB , postgresDatabaseFinalizerName ) {
99+ controllerutil .AddFinalizer (postgresDB , postgresDatabaseFinalizerName )
100+ if err := r .Update (ctx , postgresDB ); err != nil {
101+ logger .Error (err , "Failed to add finalizer to PostgresDatabase" )
102+ return ctrl.Result {}, err
103+ }
104+ return ctrl.Result {}, nil
105+ }
106+
90107 // ObservedGeneration is only written when all phases complete successfully,
91108 // so equality means nothing changed and there is no pending work.
92109 if postgresDB .Status .ObservedGeneration == postgresDB .Generation {
@@ -434,6 +451,12 @@ func (r *PostgresDatabaseReconciler) reconcileCNPGDatabases(
434451
435452 cnpgDBName := fmt .Sprintf ("%s-%s" , postgresDB .Name , dbSpec .Name )
436453
454+ // Map DeletionPolicy to CNPG's databaseReclaimPolicy
455+ reclaimPolicy := cnpgv1 .DatabaseReclaimDelete
456+ if dbSpec .DeletionPolicy == "Retain" {
457+ reclaimPolicy = cnpgv1 .DatabaseReclaimRetain
458+ }
459+
437460 cnpgDB := & cnpgv1.Database {
438461 ObjectMeta : metav1.ObjectMeta {
439462 Name : cnpgDBName ,
@@ -445,6 +468,7 @@ func (r *PostgresDatabaseReconciler) reconcileCNPGDatabases(
445468 ClusterRef : corev1.LocalObjectReference {
446469 Name : cluster .Status .ProvisionerRef .Name ,
447470 },
471+ ReclaimPolicy : reclaimPolicy ,
448472 },
449473 }
450474
@@ -532,6 +556,100 @@ func (r *PostgresDatabaseReconciler) updateStatus(
532556 return r .Status ().Update (ctx , db )
533557}
534558
559+ // handleDeletion runs cleanup when a PostgresDatabase is being deleted:
560+ // deletes all CNPG Database CRs, releases managed role ownership, and removes the finalizer.
561+ // The actual PostgreSQL databases and roles survive based on CNPG's databaseReclaimPolicy
562+ // (set during creation from DeletionPolicy: "retain" keeps the PG database, "delete" drops it).
563+ func (r * PostgresDatabaseReconciler ) handleDeletion (ctx context.Context , postgresDB * enterprisev4.PostgresDatabase ) error {
564+ logger := log .FromContext (ctx )
565+
566+ if err := r .removeDatabases (ctx , postgresDB ); err != nil {
567+ r .updateStatus (ctx , postgresDB , databasesReady , metav1 .ConditionFalse , reasonDatabasesCleanupFailed , fmt .Sprintf ("Failed to clean up databases: %v" , err ), failed )
568+ return err
569+ }
570+
571+ if err := r .removeUsersFromCluster (ctx , postgresDB ); err != nil {
572+ r .updateStatus (ctx , postgresDB , usersReady , metav1 .ConditionFalse , reasonUsersCleanupFailed , fmt .Sprintf ("Failed to clean up users: %v" , err ), failed )
573+ return err
574+ }
575+
576+ controllerutil .RemoveFinalizer (postgresDB , postgresDatabaseFinalizerName )
577+ if err := r .Update (ctx , postgresDB ); err != nil {
578+ return fmt .Errorf ("failed to remove finalizer: %w" , err )
579+ }
580+
581+ logger .Info ("Cleanup complete for PostgresDatabase" , "name" , postgresDB .Name )
582+ return nil
583+ }
584+
585+ // removeUsersFromCluster releases ownership of all managed roles by patching with an empty list.
586+ // The actual PostgreSQL roles are not dropped — they become unmanaged by CNPG.
587+ func (r * PostgresDatabaseReconciler ) removeUsersFromCluster (
588+ ctx context.Context ,
589+ postgresDB * enterprisev4.PostgresDatabase ,
590+ ) error {
591+ logger := log .FromContext (ctx )
592+
593+ cluster := & enterprisev4.PostgresCluster {}
594+ if err := r .Get (ctx , types.NamespacedName {Name : postgresDB .Spec .ClusterRef .Name , Namespace : postgresDB .Namespace }, cluster ); err != nil {
595+ if errors .IsNotFound (err ) {
596+ logger .Info ("PostgresCluster already deleted, skipping user cleanup" )
597+ return nil
598+ }
599+ return fmt .Errorf ("failed to get PostgresCluster for user cleanup: %w" , err )
600+ }
601+
602+ rolePatch := & unstructured.Unstructured {
603+ Object : map [string ]any {
604+ "apiVersion" : cluster .APIVersion ,
605+ "kind" : cluster .Kind ,
606+ "metadata" : map [string ]any {
607+ "name" : cluster .Name ,
608+ "namespace" : cluster .Namespace ,
609+ },
610+ "spec" : map [string ]any {
611+ "managedRoles" : []any {},
612+ },
613+ },
614+ }
615+
616+ fieldManager := fmt .Sprintf ("postgresdatabase-%s" , postgresDB .Name )
617+ if err := r .Patch (ctx , rolePatch , client .Apply , client .FieldOwner (fieldManager )); err != nil {
618+ return fmt .Errorf ("failed to release user ownership from PostgresCluster: %w" , err )
619+ }
620+
621+ logger .Info ("Released managed role ownership from PostgresCluster" , "postgresDatabase" , postgresDB .Name , "postgresCluster" , cluster .Name )
622+ return nil
623+ }
624+
625+ // removeDatabases deletes all CNPG Database CRs owned by this PostgresDatabase.
626+ // The actual PostgreSQL databases are retained or deleted based on the databaseReclaimPolicy
627+ // set on each CNPG Database CR during creation.
628+ func (r * PostgresDatabaseReconciler ) removeDatabases (
629+ ctx context.Context ,
630+ postgresDB * enterprisev4.PostgresDatabase ,
631+ ) error {
632+ logger := log .FromContext (ctx )
633+
634+ cnpgDBList := & cnpgv1.DatabaseList {}
635+ if err := r .List (ctx , cnpgDBList ,
636+ client .InNamespace (postgresDB .Namespace ),
637+ client.MatchingFields {".metadata.controller" : postgresDB .Name },
638+ ); err != nil {
639+ return fmt .Errorf ("failed to list CNPG Databases for cleanup: %w" , err )
640+ }
641+
642+ for _ , db := range cnpgDBList .Items {
643+ logger .Info ("Deleting CNPG Database CR" , "name" , db .Name , "reclaimPolicy" , db .Spec .ReclaimPolicy )
644+ if err := r .Delete (ctx , & db ); err != nil && ! errors .IsNotFound (err ) {
645+ return fmt .Errorf ("failed to delete CNPG Database %s: %w" , db .Name , err )
646+ }
647+ }
648+
649+ logger .Info ("All CNPG Database CRs deleted" , "count" , len (cnpgDBList .Items ))
650+ return nil
651+ }
652+
535653// SetupWithManager sets up the controller with the Manager.
536654func (r * PostgresDatabaseReconciler ) SetupWithManager (mgr ctrl.Manager ) error {
537655
0 commit comments