[object Object]

← 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 →