#!/usr/bin/env python3 """Add the two publisher statements to the live backup bucket policy. Existing statement IDs are kept. The bootstrap key writes the merged policy. The receipt contains statement IDs and status codes only. """ from __future__ import annotations import json import subprocess from pathlib import Path import boto3 from botocore.exceptions import ClientError ROOT = Path("/home/worsch/railiance-platform") DESIRED = ROOT / "docs/object-storage/railiance-platform-pg-backup-policy.json" RECEIPT = Path("/home/worsch/.local/state/railiance/receipts/backup-bucket-policy-apply.json") BUCKET = "railiance-platform-pg-backup" ENDPOINT = "https://s3.nl-ams.scw.cloud" def kv(path): proc = subprocess.run(["bao", "kv", "get", "-format=json", path], capture_output=True) if proc.returncode: raise SystemExit(71) return json.loads(proc.stdout)["data"]["data"] def client(access_key, secret_key): return boto3.client( "s3", region_name="nl-ams", endpoint_url=ENDPOINT, aws_access_key_id=access_key, aws_secret_access_key=secret_key, ) def sids(document): return [item.get("Sid") for item in document.get("Statement", [])] def merged(live, desired): statements = list(live.get("Statement", [])) present = {item.get("Sid") for item in statements} for item in desired.get("Statement", []): if item.get("Sid") not in present: statements.append(item) present.add(item.get("Sid")) return { "Version": live.get("Version") or desired.get("Version"), "Id": live.get("Id") or desired.get("Id"), "Statement": statements, } def main(): backup = kv("platform/workloads/railiance/backup/object-storage") bootstrap = kv("platform/workloads/railiance/scaleway/bootstrap") desired = json.loads(DESIRED.read_text(encoding="utf-8")) reader = client(backup["ACCESS_KEY"], backup["SECRET_KEY"]) live = json.loads(reader.get_bucket_policy(Bucket=BUCKET)["Policy"]) policy = merged(live, desired) receipt = {"before": sids(live), "intended": sids(policy)} if sids(live) == sids(policy): receipt["put"] = "unchanged" else: writer = client(bootstrap["ACCESS_KEY"], bootstrap["SECRET_KEY"]) try: writer.put_bucket_policy(Bucket=BUCKET, Policy=json.dumps(policy)) receipt["put"] = "ok" except ClientError as exc: receipt["put"] = exc.response.get("Error", {}).get("Code", "error") try: after = json.loads(reader.get_bucket_policy(Bucket=BUCKET)["Policy"]) receipt["after"] = sids(after) except ClientError as exc: receipt["after_error"] = exc.response.get("Error", {}).get("Code", "error") RECEIPT.parent.mkdir(mode=0o700, exist_ok=True) RECEIPT.write_text(json.dumps(receipt, indent=2) + "\n") RECEIPT.chmod(0o600) if __name__ == "__main__": try: main() except SystemExit: raise except Exception: raise SystemExit(72) from None