← back to Cf Dns Migration
stage.py fixes: GoDaddy read backoff/retry (kills false-empties from 60/min throttle), skip SRV, non-IPv4 A guard, per-domain pacing + flushed output
9f7662afa613e2649524f8dcd913f4be47d98790 · 2026-07-22 21:43:03 -0700 · Steve
Files touched
Diff
commit 9f7662afa613e2649524f8dcd913f4be47d98790
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jul 22 21:43:03 2026 -0700
stage.py fixes: GoDaddy read backoff/retry (kills false-empties from 60/min throttle), skip SRV, non-IPv4 A guard, per-domain pacing + flushed output
---
stage.py | 39 ++++++++++++++++++++++++++++++++-------
1 file changed, 32 insertions(+), 7 deletions(-)
diff --git a/stage.py b/stage.py
index 3e3e7ca..670c5d7 100644
--- a/stage.py
+++ b/stage.py
@@ -48,10 +48,29 @@ def req(url, method="GET", headers=None, body=None):
return 0, {"error": str(ex)}
# ---- GoDaddy ----
-def gd_records(domain):
- _, d = req(f"https://api.godaddy.com/v1/domains/{domain}/records",
- headers={"Authorization": f"sso-key {GD_KEY}:{GD_SEC}"})
- return d if isinstance(d, list) else []
+def gd_records(domain, tries=5):
+ """Read with backoff — GoDaddy throttles ~60 req/min and returns 429; the
+ earlier run's 135 'no records' were false empties from being throttled."""
+ for i in range(tries):
+ st, d = req(f"https://api.godaddy.com/v1/domains/{domain}/records",
+ headers={"Authorization": f"sso-key {GD_KEY}:{GD_SEC}"})
+ if st == 200 and isinstance(d, list):
+ return d
+ if st == 429 or st == 0 or (isinstance(d, dict) and d.get("code") == "TOO_MANY_REQUESTS"):
+ wait = 0
+ if isinstance(d, dict):
+ try: wait = int(d.get("retryAfterSec", 0))
+ except Exception: wait = 0
+ time.sleep(max(wait, 2 * (i + 1)))
+ continue
+ if isinstance(d, list):
+ return d
+ time.sleep(1.5)
+ return []
+
+def is_ipv4(s):
+ p = str(s).split(".")
+ return len(p) == 4 and all(x.isdigit() and 0 <= int(x) <= 255 for x in p)
# ---- Cloudflare ----
CF_H = {"Authorization": f"Bearer {CF_TOKEN}", "Content-Type": "application/json"}
@@ -79,12 +98,17 @@ def gd_to_cf(domain, r):
"""Map a GoDaddy record to a Cloudflare dns_records payload, or None to skip."""
t = r.get("type")
name = r.get("name", "@")
- if t in ("NS", "SOA") or name in SKIP_NAMES or t not in ("A","AAAA","CNAME","MX","TXT","SRV","CAA"):
+ # SRV skipped — GoDaddy's are almost always autodiscover/client defaults and
+ # need weight/port fields CF requires; not worth carrying.
+ if t in ("NS", "SOA", "SRV") or name in SKIP_NAMES or t not in ("A","AAAA","CNAME","MX","TXT","CAA"):
return None
fqdn = domain if name == "@" else f"{name}.{domain}"
data = r.get("data", "")
if t == "CNAME" and data in ("@", ""):
data = domain
+ # Guard against GoDaddy parked/forwarding A records whose data isn't an IP.
+ if t == "A" and not is_ipv4(data): return None
+ if t == "AAAA" and ":" not in str(data): return None
rec = {"type": t, "name": fqdn, "content": data, "ttl": max(int(r.get("ttl", 600) or 600), 60), "proxied": False}
if t == "MX":
rec["priority"] = int(r.get("priority", 10) or 10)
@@ -176,11 +200,12 @@ def main():
print(f"{'DRY-RUN' if dry else 'STAGING'} {len(domains)} domain(s)\n")
results = []
for d in domains:
- print(f"── {d}")
+ print(f"── {d}", flush=True)
res = stage(d, dry)
results.append(res)
print(f" {res['status']}: +{res['added']} new, {res['existing']} already present"
- + (f" | NS: {res['ns']}" if res['ns'] else ""))
+ + (f" | NS: {res['ns']}" if res['ns'] else ""), flush=True)
+ time.sleep(0.7) # pace GoDaddy reads (~60/min cap)
# emit the NS-swap worklist
out = "staged-nameservers.csv"
if not dry:
← cf3dacb CF DNS migration: Phase-0 inventory (286 domains classified)
·
back to Cf Dns Migration
·
migrate GoDaddy auth → Bearer PAT (TK-10 key rotation); sso- 0393dd3 →