#!/usr/bin/env bash set -euo pipefail PROJECT="${PROJECT:-${GCP_PROJECT_ID:-${GOOGLE_CLOUD_PROJECT:-plenary-truck-485623-p7}}}" DRY_RUN_RAW="${SUPABASE_RETAINED_PV_CLEANUP_DRY_RUN:-false}" DRY_RUN="false" if [[ "${DRY_RUN_RAW,,}" =~ ^(1|true|yes|on)$ ]]; then DRY_RUN="true" fi echo "==> Finding stale Supabase PVs (Released + Retain, Supabase namespace/storageClass)..." echo "==> Dry run: ${DRY_RUN}" echo "==> Project: ${PROJECT}" mapfile -t CANDIDATES < <( kubectl get pv -o json | jq -r ' .items[] | . as $pv | ($pv.spec.claimRef.namespace // "") as $claimNs | ($pv.spec.storageClassName // "") as $sc | select( ($pv.status.phase == "Released") and (($pv.spec.persistentVolumeReclaimPolicy // "") == "Retain") and ( $sc == "supabase-standard" or $claimNs == "supabase" ) ) | ( ($pv.spec.csi.volumeHandle // $pv.spec.gcePersistentDisk.pdName // "") ) as $handle | ( ($pv.metadata.labels["topology.kubernetes.io/zone"] // $pv.metadata.labels["failure-domain.beta.kubernetes.io/zone"] // $pv.spec.csi.volumeAttributes["topology.gke.io/zone"] // ( if ($handle | test("/zones/[^/]+/disks/[^/]+$")) then ($handle | capture("/zones/(?[^/]+)/disks/").z) else "" end ) ) ) as $zone | [ ($pv.metadata.name // ""), $claimNs, ($pv.spec.claimRef.name // ""), ($pv.status.phase // ""), ($pv.spec.persistentVolumeReclaimPolicy // ""), $sc, $handle, $zone ] | @tsv ' ) if [[ ${#CANDIDATES[@]} -eq 0 ]]; then echo "==> No stale Supabase Released/Retain PV candidates found." exit 0 fi echo "==> Candidates:" for row in "${CANDIDATES[@]}"; do IFS=$'\t' read -r PV CLAIM_NS CLAIM_NAME PHASE RECLAIM SC DISK ZONE <<<"$row" echo " - PV=${PV} claim=${CLAIM_NS}/${CLAIM_NAME:-} phase=${PHASE} reclaim=${RECLAIM} sc=${SC} disk=${DISK:-} zone=${ZONE:-}" done for row in "${CANDIDATES[@]}"; do IFS=$'\t' read -r PV CLAIM_NS CLAIM_NAME PHASE RECLAIM SC DISK ZONE <<<"$row" NAME="${DISK##*/}" if [[ "$DRY_RUN" == "true" ]]; then echo "[dry-run] kubectl delete pv ${PV} --ignore-not-found" if [[ -n "$DISK" && "$DISK" != "null" ]]; then if [[ -n "$ZONE" ]]; then echo "[dry-run] gcloud compute disks delete ${NAME} --zone ${ZONE} --project ${PROJECT} --quiet" else echo "[dry-run] skip disk delete for ${DISK} (missing zone)" fi else echo "[dry-run] skip disk delete for PV ${PV} (no disk handle)" fi continue fi echo "==> Deleting PV ${PV}" kubectl delete pv "$PV" --ignore-not-found if [[ -z "$DISK" || "$DISK" == "null" ]]; then echo "[WARN] PV ${PV} has no disk handle; skipped disk delete" continue fi if [[ -z "$ZONE" ]]; then echo "[WARN] Disk ${DISK} zone is unknown; skipped disk delete" continue fi if ! command -v gcloud >/dev/null 2>&1; then echo "[WARN] gcloud not found; skipped disk delete for ${DISK}" continue fi echo "==> Deleting GCE disk ${NAME} (zone=${ZONE})" gcloud compute disks delete "$NAME" --zone "$ZONE" --project "$PROJECT" --quiet \ || echo "[WARN] disk delete failed: ${NAME}" done echo "==> Cleanup complete"