The live policy keeps the original backup application. Each new key can abort only inside its own prefix. Assistant: grok Assistant-Session: 01a11cdb-f9fe-7011-947c-936e72007e54
92 lines
3 KiB
Python
92 lines
3 KiB
Python
#!/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
|