#!/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())