feat(backup): apps-pg restore drill + close WP-0014
Add tools/apps-pg-restore-drill.sh and make target; T04 evidence for vergabe_db row-count gate. Archive finished RAILIANCE-WP-0014.
This commit is contained in:
parent
a0cb579679
commit
dc65a3b9fd
5 changed files with 135 additions and 7 deletions
101
tools/apps-pg-restore-drill.sh
Executable file
101
tools/apps-pg-restore-drill.sh
Executable file
|
|
@ -0,0 +1,101 @@
|
|||
#!/usr/bin/env bash
|
||||
# Isolated apps-pg restore drill for vergabe_db (RAILIANCE-WP-0013-T04).
|
||||
# Restores a local pg_dump into a temporary database on the apps-pg cluster.
|
||||
set -euo pipefail
|
||||
|
||||
APPS_PG_NAMESPACE="${APPS_PG_NAMESPACE:-databases}"
|
||||
APPS_PG_CLUSTER="${APPS_PG_CLUSTER:-apps-pg}"
|
||||
APPS_PG_POD="${APPS_PG_POD:-}"
|
||||
SOURCE_DB="${APPS_PG_DATABASE:-vergabe_db}"
|
||||
BACKUP_DIR="${BACKUP_DIR:-$HOME/.cache/railiance/backups/apps-pg}"
|
||||
BACKUP_FILE="${APPS_PG_BACKUP_FILE:-}"
|
||||
DRILL_DB="${APPS_PG_RESTORE_DB:-vergabe_restore_drill}"
|
||||
EVIDENCE_DIR="${EVIDENCE_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/docs/evidence}"
|
||||
|
||||
if [[ -z "$APPS_PG_POD" ]]; then
|
||||
APPS_PG_POD="$(
|
||||
kubectl get pods -n "$APPS_PG_NAMESPACE" \
|
||||
-l "cnpg.io/cluster=${APPS_PG_CLUSTER},role=primary" \
|
||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true
|
||||
)"
|
||||
fi
|
||||
if [[ -z "$APPS_PG_POD" ]]; then
|
||||
echo "ERROR: primary pod for ${APPS_PG_CLUSTER} not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$BACKUP_FILE" ]]; then
|
||||
BACKUP_FILE="$(
|
||||
find "$BACKUP_DIR" -maxdepth 1 -name "apps-pg-${SOURCE_DB}-*.dump" -type f 2>/dev/null \
|
||||
| sort -r | head -1
|
||||
)"
|
||||
fi
|
||||
if [[ -z "$BACKUP_FILE" || ! -f "$BACKUP_FILE" ]]; then
|
||||
echo "ERROR: no backup file found for ${SOURCE_DB} under ${BACKUP_DIR}" >&2
|
||||
echo "Run: make apps-pg-backup-dry-run" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
psql_exec() {
|
||||
kubectl exec -n "$APPS_PG_NAMESPACE" "$APPS_PG_POD" -c postgres -- \
|
||||
psql -U postgres -v ON_ERROR_STOP=1 "$@"
|
||||
}
|
||||
|
||||
count_tables() {
|
||||
local db="$1"
|
||||
psql_exec -d "$db" -Atc \
|
||||
"SELECT COALESCE(SUM(n_live_tup),0)::bigint FROM pg_stat_user_tables;"
|
||||
}
|
||||
|
||||
echo "apps-pg restore drill"
|
||||
echo " cluster: ${APPS_PG_CLUSTER}"
|
||||
echo " pod: ${APPS_PG_POD}"
|
||||
echo " source: ${SOURCE_DB}"
|
||||
echo " backup: ${BACKUP_FILE}"
|
||||
echo " drill db: ${DRILL_DB}"
|
||||
echo
|
||||
|
||||
psql_exec -d postgres -c "DROP DATABASE IF EXISTS \"${DRILL_DB}\";"
|
||||
psql_exec -d postgres -c "CREATE DATABASE \"${DRILL_DB}\";"
|
||||
echo "ok: created drill database ${DRILL_DB}"
|
||||
|
||||
kubectl exec -i -n "$APPS_PG_NAMESPACE" "$APPS_PG_POD" -c postgres -- \
|
||||
pg_restore -U postgres -d "$DRILL_DB" --no-owner --no-acl --clean --if-exists \
|
||||
< "$BACKUP_FILE"
|
||||
echo "ok: pg_restore completed"
|
||||
|
||||
PROD_ROWS="$(count_tables "$SOURCE_DB")"
|
||||
DRILL_ROWS="$(count_tables "$DRILL_DB")"
|
||||
echo "row estimate — production ${SOURCE_DB}: ${PROD_ROWS}"
|
||||
echo "row estimate — drill ${DRILL_DB}: ${DRILL_ROWS}"
|
||||
|
||||
if [[ "$PROD_ROWS" != "$DRILL_ROWS" ]]; then
|
||||
echo "ERROR: row-count mismatch between production and restore drill" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "ok: row-count gate passed"
|
||||
|
||||
TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
mkdir -p "$EVIDENCE_DIR"
|
||||
EVIDENCE_FILE="${EVIDENCE_DIR}/apps-pg-restore-drill-${TS}.json"
|
||||
python3 - <<PY
|
||||
import json, pathlib
|
||||
payload = {
|
||||
"captured_at": "${TS}",
|
||||
"workplan": "RAILIANCE-WP-0013",
|
||||
"cluster": "${APPS_PG_CLUSTER}",
|
||||
"source_database": "${SOURCE_DB}",
|
||||
"restore_database": "${DRILL_DB}",
|
||||
"backup_file": "$(basename "$BACKUP_FILE")",
|
||||
"production_row_estimate": int("${PROD_ROWS}"),
|
||||
"restored_row_estimate": int("${DRILL_ROWS}"),
|
||||
"row_count_gate": "pass",
|
||||
"notes": "Isolated restore on live CNPG primary; drill DB dropped after evidence write.",
|
||||
}
|
||||
path = pathlib.Path("${EVIDENCE_FILE}")
|
||||
path.write_text(json.dumps(payload, indent=2) + "\\n", encoding="utf-8")
|
||||
print(f"ok: evidence {path}")
|
||||
PY
|
||||
|
||||
psql_exec -d postgres -c "DROP DATABASE IF EXISTS \"${DRILL_DB}\";"
|
||||
echo "ok: dropped drill database ${DRILL_DB}"
|
||||
Loading…
Add table
Add a link
Reference in a new issue