100 lines
4 KiB
Python
Executable file
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())
|