@@ -588,17 +588,30 @@ def preparePipelinesNamespace(
588588 f"Deleting to recreate with storageClassName='{ storageClass } '."
589589 )
590590
591- # Remove the pvc-protection finalizer, then force-delete (grace_period_seconds=0).
592- # These two together are equivalent to: kubectl delete pvc --grace-period=0 --force
593- # Without both, the PVC gets stuck in Terminating — Kubernetes re-adds the finalizer
594- # as long as the PVC is Bound, and grace_period=0 tells the API server not to wait for it.
595- pvcAPI .patch (
596- name = "config-pvc" ,
597- namespace = namespace ,
598- body = {"metadata" : {"finalizers" : []}},
599- content_type = "application/merge-patch+json" ,
600- )
601- pvcAPI .delete (name = "config-pvc" , namespace = namespace , grace_period_seconds = 0 )
591+ # Unbind the PVC by removing claimRef from its backing PV.
592+ # While the PVC is Bound, PVCProtectionController keeps re-adding the pvc-protection
593+ # finalizer, blocking deletion. Removing claimRef moves the PVC to Lost/Released,
594+ # causing the controller to drop the finalizer itself — then a normal delete works cleanly.
595+ pvName = existingConfigPVC .spec .volumeName
596+ if pvName :
597+ logger .info (f"Unbinding config-pvc from PV '{ pvName } ' to allow clean deletion." )
598+ pvAPI = dynClient .resources .get (api_version = "v1" , kind = "PersistentVolume" )
599+ pvAPI .patch (
600+ name = pvName ,
601+ body = {"spec" : {"claimRef" : None }},
602+ content_type = "application/merge-patch+json" ,
603+ )
604+ # Wait for the PVC to leave Bound state — once unbound, PVCProtectionController
605+ # removes the finalizer itself so the subsequent delete goes through cleanly.
606+ for _ in range (30 ):
607+ current = pvcAPI .get (name = "config-pvc" , namespace = namespace )
608+ if current .status .phase != "Bound" :
609+ logger .info (f"config-pvc is now '{ current .status .phase } ', finalizer released." )
610+ break
611+ logger .debug ("config-pvc still Bound, waiting 2s..." )
612+ sleep (2 )
613+
614+ pvcAPI .delete (name = "config-pvc" , namespace = namespace )
602615 logger .info ("Waiting for config-pvc deletion to complete..." )
603616 while True :
604617 try :
@@ -608,6 +621,16 @@ def preparePipelinesNamespace(
608621 except NotFoundError :
609622 logger .info ("config-pvc deletion confirmed." )
610623 break
624+
625+ # Delete the now-orphaned PV so it does not accumulate across upgrades.
626+ # The new config-pvc will get a fresh PV provisioned automatically.
627+ if pvName :
628+ try :
629+ pvAPI .delete (name = pvName )
630+ logger .info (f"Deleted orphaned PV '{ pvName } '." )
631+ except NotFoundError :
632+ pass # already gone (e.g. reclaimPolicy: Delete handled it)
633+
611634 except NotFoundError :
612635 pass # PVC does not exist yet, will be created fresh below
613636
0 commit comments