Skip to content

Commit daced57

Browse files
authored
postgres database finalizer (#1750)
1 parent 3ff214c commit daced57

2 files changed

Lines changed: 125 additions & 3 deletions

File tree

internal/controller/postgresdatabase_controller.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
536654
func (r *PostgresDatabaseReconciler) SetupWithManager(mgr ctrl.Manager) error {
537655

internal/controller/postgresoperator_common_types.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package controller
22

33
import (
4-
corev1 "k8s.io/api/core/v1"
54
"time"
5+
6+
corev1 "k8s.io/api/core/v1"
67
)
78

89
// This struct is used to compare the merged configuration from PostgresClusterClass and PostgresClusterSpec
@@ -36,8 +37,9 @@ const (
3637
readOnlyEndpoint string = "ro"
3738
readWriteEndpoint string = "rw"
3839
// default database name
39-
defaultDatabaseName string = "postgres"
40-
defaultUsername string = "postgres"
40+
defaultDatabaseName string = "postgres"
41+
defaultUsername string = "postgres"
42+
postgresDatabaseFinalizerName string = "postgresdatabases.enterprise.splunk.com/finalizer"
4143

4244
// phases
4345
ready reconcilePhases = "Ready"
@@ -71,6 +73,8 @@ const (
7173
reasonSecretsCreationFailed conditionReasons = "SecretsCreationFailed"
7274
reasonWaitingForCNPG conditionReasons = "WaitingForCNPG"
7375
reasonUsersCreationFailed conditionReasons = "UsersCreationFailed"
76+
reasonUsersCleanupFailed conditionReasons = "UsersCleanupFailed"
77+
reasonDatabasesCleanupFailed conditionReasons = "DatabasesCleanupFailed"
7478
reasonUsersAvailable conditionReasons = "UsersAvailable"
7579

7680
// Additional condition reasons for clusterReady conditionType

0 commit comments

Comments
 (0)