Add tools/apps-pg-restore-drill.sh and make target; T04 evidence for vergabe_db row-count gate. Archive finished RAILIANCE-WP-0014.
101 lines
No EOL
3.4 KiB
Bash
Executable file
101 lines
No EOL
3.4 KiB
Bash
Executable file
#!/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}" |