fix(deploy): rejoue la synchronisation dynv6 quand l'API ne répond pas

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.
This commit is contained in:
Johan LEROY
2026-09-23 11:56:33 +02:00
parent 22a88e193c
commit 4d88604a07
+25 -13
View File
@@ -143,7 +143,7 @@ publier_dns() {
[[ -r "$JETON_DNS" ]] || { echo "pas de jeton $JETON_DNS : ni DNS ni Let's Encrypt"; return 0; } [[ -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' \ 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 || 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:] domaine, adresse, *sous_noms = sys.argv[1:]
@@ -151,22 +151,34 @@ def appel(methode, chemin, corps=None):
requete = urllib.request.Request( requete = urllib.request.Request(
f"https://dynv6.com/api/v2/{chemin}", method=methode, f"https://dynv6.com/api/v2/{chemin}", method=methode,
data=None if corps is None else json.dumps(corps).encode(), 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"}) "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() contenu = reponse.read()
return json.loads(contenu) if contenu else None return json.loads(contenu) if contenu else None
zone = appel("GET", f"zones/by-name/{domaine}") def synchroniser():
if zone.get("ipv4address") != adresse: zone = appel("GET", f"zones/by-name/{domaine}")
appel("PATCH", f"zones/{zone['id']}", {"ipv4address": adresse}) if zone.get("ipv4address") != adresse:
existants = {(r["type"], r["name"]): r for r in appel("GET", f"zones/{zone['id']}/records")} appel("PATCH", f"zones/{zone['id']}", {"ipv4address": adresse})
for nom in sous_noms: existants = {(r["type"], r["name"]): r for r in appel("GET", f"zones/{zone['id']}/records")}
actuel = existants.get(("A", nom)) for nom in sous_noms:
if actuel is None: actuel = existants.get(("A", nom))
appel("POST", f"zones/{zone['id']}/records", {"type": "A", "name": nom, "data": adresse}) if actuel is None:
elif actuel["data"] != adresse: appel("POST", f"zones/{zone['id']}/records", {"type": "A", "name": nom, "data": adresse})
appel("PATCH", f"zones/{zone['id']}/records/{actuel['id']}", {"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}") print(f"DNS : {domaine}, {', '.join(sous_noms)} visent {adresse}")
PY PY
} }