teleo-infrastructure/ops/build_gcp_cloudsql_restore_proof.py
2026-07-13 07:13:18 +02:00

100 lines
4 KiB
Python
Executable file

#!/usr/bin/env python3
"""Build the prepared proof manifest for a Cloud SQL restore drill."""
from __future__ import annotations
import argparse
import hashlib
import json
from datetime import datetime, timezone
from pathlib import Path
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def build_proof(args: argparse.Namespace) -> dict[str, object]:
summary = json.loads(args.source_summary.read_text(encoding="utf-8"))
return {
"artifact": "gcp_cloudsql_restore_drill",
"generated_at_utc": datetime.now(timezone.utc).isoformat(),
"mode": "execute" if args.execute else "dry_run",
"status": "prepared_for_execute" if args.execute else "prepared",
"project_id": args.project_id,
"region": args.region,
"cloudsql_instance": args.instance,
"database": args.database,
"postgres_schema": summary["schema"],
"source_sqlite_backup": str(args.sqlite_backup),
"source_sqlite_backup_sha256": sha256(args.sqlite_backup),
"source_integrity_check": summary["source_integrity_check"],
"source_table_count": summary["table_count"],
"source_total_rows": summary["source_total_rows"],
"source_table_counts": {table["name"]: table["row_count"] for table in summary["tables"]},
"gcs_import_uri": args.gcs_import_uri,
"local_private_artifacts": {
"postgres_import_sql": str(args.import_sql),
"source_summary_json": str(args.source_summary),
"target_counts_sql": str(args.target_counts_sql),
},
"planned_commands": [
f"gcloud storage cp {args.import_sql} {args.gcs_import_uri}",
f"gcloud sql import sql {args.instance} {args.gcs_import_uri} --project={args.project_id} "
f"--database={args.database} --quiet --format=json",
"run target-counts.sql from a trusted VPC/Cloud SQL connector path and compare "
"source_total_rows/source_table_count",
],
"not_proven_by_this_artifact": [
"GCS object generation until EXECUTE=1 succeeds",
"Cloud SQL import operation until EXECUTE=1 succeeds",
"Cloud SQL query readback until target-counts.sql is run from a trusted VPC/connector path",
],
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--source-summary", type=Path, required=True)
parser.add_argument("--sqlite-backup", type=Path, required=True)
parser.add_argument("--import-sql", type=Path, required=True)
parser.add_argument("--target-counts-sql", type=Path, required=True)
parser.add_argument("--proof-json", type=Path, required=True)
parser.add_argument("--project-id", required=True)
parser.add_argument("--instance", required=True)
parser.add_argument("--database", required=True)
parser.add_argument("--region", required=True)
parser.add_argument("--gcs-import-uri", required=True)
parser.add_argument("--execute", action="store_true")
return parser.parse_args()
def main() -> int:
args = parse_args()
proof = build_proof(args)
args.proof_json.write_text(json.dumps(proof, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print(
json.dumps(
{
"artifact": proof["artifact"],
"mode": proof["mode"],
"status": proof["status"],
"source_integrity_check": proof["source_integrity_check"],
"source_table_count": proof["source_table_count"],
"source_total_rows": proof["source_total_rows"],
"gcs_import_uri": proof["gcs_import_uri"],
"proof": str(args.proof_json),
},
indent=2,
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())