From 4d88604a0785695dcd05bbc7c35ea10ebc1eb264 Mon Sep 17 00:00:00 2001 From: Johan LEROY Date: Wed, 23 Sep 2026 11:56:33 +0200 Subject: [PATCH] =?UTF-8?q?fix(deploy):=20rejoue=20la=20synchronisation=20?= =?UTF-8?q?dynv6=20quand=20l'API=20ne=20r=C3=A9pond=20pas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'API dynv6 laisse par intermittence une écriture sans réponse, parfois appliquée malgré tout. La synchronisation est rejouée jusqu'à trois fois et relit l'état avant chaque écriture : une création aboutie malgré le délai n'est jamais dupliquée. Délai par appel porté à 60 s. Validé contre le vrai dynv6 depuis la VM : zone, rec et dev visent 10.101.200.37, et un certificat de test Let's Encrypt a été émis par DNS-01 pour dev.enervision-g3.dynv6.net. --- scripts/provision-host.sh | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/scripts/provision-host.sh b/scripts/provision-host.sh index 1d44ce1..70e01ba 100755 --- a/scripts/provision-host.sh +++ b/scripts/provision-host.sh @@ -143,7 +143,7 @@ publier_dns() { [[ -r "$JETON_DNS" ]] || { echo "pas de jeton $JETON_DNS : ni DNS ni Let's Encrypt"; return 0; } DNS_TOKEN="$(tr -d '[:space:]' < "$JETON_DNS")" python3 - "$DOMAINE" "$ADRESSE" rec dev <<'PY' \ || echo "DNS : dynv6 refuse la mise à jour de $DOMAINE, enregistrements inchangés" >&2 -import json, os, sys, urllib.request +import json, os, sys, time, urllib.request domaine, adresse, *sous_noms = sys.argv[1:] @@ -151,22 +151,34 @@ def appel(methode, chemin, corps=None): requete = urllib.request.Request( f"https://dynv6.com/api/v2/{chemin}", method=methode, data=None if corps is None else json.dumps(corps).encode(), - headers={"Authorization": f"Bearer {os.environ['DNS_TOKEN']}", + headers={"Authorization": f"Bearer {os.environ['DNS_TOKEN']}", "User-Agent": "enervision-provision", "Content-Type": "application/json", "Accept": "application/json"}) - with urllib.request.urlopen(requete, timeout=20) as reponse: + with urllib.request.urlopen(requete, timeout=60) as reponse: contenu = reponse.read() return json.loads(contenu) if contenu else None -zone = appel("GET", f"zones/by-name/{domaine}") -if zone.get("ipv4address") != adresse: - appel("PATCH", f"zones/{zone['id']}", {"ipv4address": adresse}) -existants = {(r["type"], r["name"]): r for r in appel("GET", f"zones/{zone['id']}/records")} -for nom in sous_noms: - actuel = existants.get(("A", nom)) - if actuel is None: - appel("POST", f"zones/{zone['id']}/records", {"type": "A", "name": nom, "data": adresse}) - elif actuel["data"] != adresse: - appel("PATCH", f"zones/{zone['id']}/records/{actuel['id']}", {"data": adresse}) +def synchroniser(): + zone = appel("GET", f"zones/by-name/{domaine}") + if zone.get("ipv4address") != adresse: + appel("PATCH", f"zones/{zone['id']}", {"ipv4address": adresse}) + existants = {(r["type"], r["name"]): r for r in appel("GET", f"zones/{zone['id']}/records")} + for nom in sous_noms: + actuel = existants.get(("A", nom)) + if actuel is None: + appel("POST", f"zones/{zone['id']}/records", {"type": "A", "name": nom, "data": adresse}) + elif actuel["data"] != adresse: + appel("PATCH", f"zones/{zone['id']}/records/{actuel['id']}", {"data": adresse}) + +# dynv6 laisse parfois une écriture sans réponse, appliquée ou non : chaque essai relit l'état +# avant d'écrire, si bien qu'une création aboutie malgré le délai n'est jamais dupliquée. +for essai in range(3): + try: + synchroniser() + break + except OSError: + if essai == 2: + raise + time.sleep(5) print(f"DNS : {domaine}, {', '.join(sous_noms)} visent {adresse}") PY }