Initial Pantheon infrastructure

This commit is contained in:
2026-08-03 22:56:18 +10:00
commit afc9ee12f8
36 changed files with 4408 additions and 0 deletions
+212
View File
@@ -0,0 +1,212 @@
#!/usr/bin/env python3
import json
import os
from datetime import datetime
from pathlib import Path
import requests
import yaml
from dotenv import load_dotenv
load_dotenv(Path(__file__).with_name(".env"))
TOKEN = os.getenv("CF_API_TOKEN")
ZONE = os.getenv("CF_ZONE_ID")
if not TOKEN:
raise RuntimeError("CF_API_TOKEN missing")
if not ZONE:
raise RuntimeError("CF_ZONE_ID missing")
HEADERS = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json",
}
API = f"https://api.cloudflare.com/client/v4/zones/{ZONE}/dns_records"
# -----------------------------------------------------
# Backup current DNS
# -----------------------------------------------------
print("Backing up current Cloudflare zone...")
records = requests.get(API, headers=HEADERS).json()["result"]
backup_dir = Path("backups")
backup_dir.mkdir(exist_ok=True)
backup_file = backup_dir / f"{datetime.now():%Y-%m-%d_%H-%M-%S}.yaml"
yaml.safe_dump(records, open(backup_file, "w"), sort_keys=False)
print(f"Backup saved to {backup_file}")
# -----------------------------------------------------
# Load execution plan
# -----------------------------------------------------
plan = json.load(open("plan.json"))
def print_section(title, symbol, items):
print()
print("=" * 70)
print(title)
print("=" * 70)
if not items:
print(" None")
return
for item in items:
if "name" in item:
print(f" {symbol} {item['name']}")
elif "old" in item:
print(f" {symbol} {item['old']} -> {item['new']}")
print("\nExecution Plan")
print("=" * 70)
print_section(
f"CREATE ({len(plan['create'])})",
"+",
sorted(plan["create"], key=lambda x: x["name"])
)
print_section(
f"RENAME ({len(plan['rename'])})",
"~",
sorted(plan["rename"], key=lambda x: x["old"])
)
print_section(
f"UPDATE ({len(plan['update'])})",
"*",
sorted(plan["update"], key=lambda x: x["name"])
)
print_section(
f"DELETE ({len(plan['delete'])})",
"-",
sorted(plan["delete"], key=lambda x: x["name"])
)
print()
answer = input("Apply these changes to Cloudflare? [y/N]: ").strip().lower()
if answer not in ("y", "yes"):
print("\nAborted.")
raise SystemExit
if answer != "y":
print("Cancelled.")
raise SystemExit
# -----------------------------------------------------
# CREATE
# -----------------------------------------------------
print("\nCreating records...")
for item in plan["create"]:
body = {
"type": item["desired"]["type"],
"name": item["name"],
"content": item["desired"]["content"],
"ttl": item["desired"]["ttl"],
"proxied": item["desired"]["proxied"],
}
r = requests.post(API, headers=HEADERS, json=body)
r.raise_for_status()
print(f" + {item['name']}")
# -----------------------------------------------------
# RENAME
# -----------------------------------------------------
print("\nRenaming records...")
for item in plan["rename"]:
body = {
"type": item["desired"]["type"],
"name": item["new"],
"content": item["desired"]["content"],
"ttl": item["desired"]["ttl"],
"proxied": item["desired"]["proxied"],
}
r = requests.patch(
f"{API}/{item['id']}",
headers=HEADERS,
json=body,
)
r.raise_for_status()
print(f" ~ {item['old']} -> {item['new']}")
# -----------------------------------------------------
# UPDATE
# -----------------------------------------------------
print("\nUpdating records...")
for item in plan["update"]:
body = {
"type": item["desired"]["type"],
"name": item["name"],
"content": item["desired"]["content"],
"ttl": item["desired"]["ttl"],
"proxied": item["desired"]["proxied"],
}
r = requests.patch(
f"{API}/{item['id']}",
headers=HEADERS,
json=body,
)
r = requests.patch(
f"{API}/{item['id']}",
headers=HEADERS,
json=body,
)
if not r.ok:
print(f"\nERROR updating {item['name']}")
print(r.status_code)
print(r.text)
raise SystemExit
print(f" * {item['name']}")
# -----------------------------------------------------
# DELETE
# -----------------------------------------------------
print("\nDeleting records...")
for item in plan["delete"]:
r = requests.delete(
f"{API}/{item['id']}",
headers=HEADERS,
)
r.raise_for_status()
print(f" - {item['name']}")
print("\nDone.")