pr_01m47d24b0e6n91zwymwxg0vpx/scripts/cloudflare-setup.py
| 1 | """Makes what Deployments needs that `wrangler login` cannot: a proxied |
| 2 | wildcard DNS record on the apps' zone, and an API token for the deployments |
| 3 | service (Workers Scripts: Edit, Account Analytics: Read). |
| 4 | |
| 5 | Run by scripts/setup-deployments.sh with CLOUDFLARE_EMAIL and |
| 6 | CLOUDFLARE_API_KEY (a Global API Key) set. Prints only ids and outcomes, |
| 7 | never the key or the new token. Skips what exists. |
| 8 | """ |
| 9 | import json |
| 10 | import os |
| 11 | import sys |
| 12 | import urllib.error |
| 13 | import urllib.request |
| 14 | |
| 15 | ACCOUNT = os.environ.get("CLOUDFLARE_ACCOUNT_ID") or "1e6f2cffa3f445920836e8ebe446bb58" |
| 16 | EMAIL = os.environ.get("CLOUDFLARE_EMAIL", "") |
| 17 | KEY = os.environ.get("CLOUDFLARE_API_KEY", "") |
| 18 | ZONE = os.environ.get("G1T_APPS_ZONE", "g1t.page") |
| 19 | TOKEN_FILE = os.environ["TOKEN_FILE"] |
| 20 | API = "https://api.cloudflare.com/client/v4" |
| 21 | |
| 22 | if not KEY or not EMAIL: |
| 23 | sys.exit("CLOUDFLARE_EMAIL and CLOUDFLARE_API_KEY must be set") |
| 24 | |
| 25 | |
| 26 | def call(method, path, body=None): |
| 27 | request = urllib.request.Request( |
| 28 | API + path, |
| 29 | method=method, |
| 30 | data=json.dumps(body).encode() if body is not None else None, |
| 31 | headers={ |
| 32 | "X-Auth-Email": EMAIL, |
| 33 | "X-Auth-Key": KEY, |
| 34 | "Content-Type": "application/json", |
| 35 | # Cloudflare refuses Python's default user agent. |
| 36 | "User-Agent": "g1t-setup", |
| 37 | }, |
| 38 | ) |
| 39 | try: |
| 40 | return json.load(urllib.request.urlopen(request)) |
| 41 | except urllib.error.HTTPError as error: |
| 42 | return json.loads(error.read() or b"{}") | {"http": error.code} |
| 43 | |
| 44 | |
| 45 | zones = call("GET", f"/zones?name={ZONE}") |
| 46 | if not zones.get("result"): |
| 47 | sys.exit(f"zone {ZONE} not found: {zones.get('errors')}") |
| 48 | zone = zones["result"][0]["id"] |
| 49 | |
| 50 | records = call("GET", f"/zones/{zone}/dns_records?name=*.{ZONE}") |
| 51 | if records.get("result"): |
| 52 | print(f"*.{ZONE}: record exists") |
| 53 | else: |
| 54 | made = call("POST", f"/zones/{zone}/dns_records", { |
| 55 | "type": "AAAA", "name": "*", "content": "100::", "proxied": True, "ttl": 1, |
| 56 | "comment": "g1t deployments: every app goes to the dispatch Worker", |
| 57 | }) |
| 58 | print(f"*.{ZONE}:", "record created" if made.get("success") else made.get("errors")) |
| 59 | |
| 60 | if os.path.exists(TOKEN_FILE): |
| 61 | print("token: exists in .credentials; not making another") |
| 62 | sys.exit(0) |
| 63 | |
| 64 | groups = call("GET", "/user/tokens/permission_groups") |
| 65 | wanted = {"Workers Scripts Write", "Account Analytics Read"} |
| 66 | ids = [g["id"] for g in groups.get("result", []) if g["name"] in wanted] |
| 67 | if len(ids) != len(wanted): |
| 68 | sys.exit("could not find the permission groups for the token") |
| 69 | created = call("POST", "/user/tokens", { |
| 70 | "name": "g1t deployments service (Workers for Platforms uploads, analytics)", |
| 71 | "policies": [{ |
| 72 | "effect": "allow", |
| 73 | "resources": {f"com.cloudflare.api.account.{ACCOUNT}": "*"}, |
| 74 | "permission_groups": [{"id": i} for i in ids], |
| 75 | }], |
| 76 | }) |
| 77 | if not created.get("success"): |
| 78 | sys.exit(f"token not created: {created.get('errors')}") |
| 79 | os.makedirs(os.path.dirname(TOKEN_FILE), exist_ok=True) |
| 80 | with open(TOKEN_FILE, "w", encoding="utf-8") as f: |
| 81 | f.write(created["result"]["value"] + "\n") |
| 82 | print("token: created and saved to .credentials") |