railiance-platform/scripts/apply-backup-publisher-bucket-policy.py

93 lines
3 KiB
Python
Raw Permalink Normal View History

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