deployer-tout : le genome est-il a jour avant de poser quoi que ce soit
Un runner tire ce qu on pousse, il ne le recoit pas. Trois fois ce soir un genome perime a menace de rebatir un etat depasse — et aucun des deux runners n aurait echoue : un deploiement depuis un genome perime REUSSIT. Il applique fidelement un etat qui n a plus cours, et se presente en vert. En retard : refus. En avance ou diverge : note et on continue. Forge injoignable : note et on continue — une forge en panne ne doit pas immobiliser une exploitation, le silence serait la faute. FORCE=1 passe outre. Eprouvee dans les trois sens. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
parent
8a9ac18462
commit
220e1e3e28
4 changed files with 165 additions and 2 deletions
37
CHANGELOG.md
37
CHANGELOG.md
|
|
@ -1,5 +1,42 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-09-13 (10) — Un runner TIRE ce qu'on pousse ; il ne le recoit pas
|
||||
|
||||
Trois fois dans une meme soiree, un genome perime a menace de rebatir un etat depasse :
|
||||
|
||||
- **le runner du SITE**, treize commits en retard, s'appretait a materialiser un locataire
|
||||
avec son ancien plan — serveur de sauvegarde inutile, cache d'artefacts en trop, runner
|
||||
sans pouvoir de configurer, et aucune machine avec son plancher de memoire ;
|
||||
- **le runner du LOCATAIRE**, clone pendant l'insemination donc avant trois correctifs,
|
||||
s'appretait a reposer un plan d'administration pointant le WAN d'un AUTRE site — et a
|
||||
refermer derriere lui la porte que l'exploitant venait tout juste de rouvrir.
|
||||
|
||||
### Pourquoi c'est le pire mode de defaillance de cette famille
|
||||
|
||||
**Aucun des deux n'aurait echoue.** Un deploiement depuis un genome perime REUSSIT : il
|
||||
applique fidelement un etat qui n'a plus cours. Il se presente en vert. Rien, dans la
|
||||
sortie, ne distingue « la flotte converge vers ce que tu veux » de « la flotte converge
|
||||
vers ce que tu voulais il y a trois heures ».
|
||||
|
||||
### La garde
|
||||
|
||||
`scripts/verifier_genome_a_jour.py`, en tete de `deployer-tout` :
|
||||
|
||||
| etat du depot | verdict |
|
||||
|---|---|
|
||||
| en RETARD | **refus** — deployer poserait un etat qu'on sait depasse |
|
||||
| en AVANCE | note, on continue — un mainteneur qui travaille localement est normal |
|
||||
| DIVERGE | note, on continue — c'est a l'humain de trancher, pas a une garde |
|
||||
| forge injoignable | note, on continue — une forge en panne ne doit pas bloquer une exploitation |
|
||||
|
||||
Ce dernier point est delibere : **le silence serait la faute**, pas le passage. Une garde
|
||||
qui immobilise la flotte quand la forge tousse serait pire que le defaut qu'elle surveille.
|
||||
|
||||
`FORCE=1` passe outre, pour le cas legitime ou l'on deploie sciemment un etat local.
|
||||
|
||||
Eprouvee dans les trois sens sur un clone recule de deux commits : elle mord, elle se
|
||||
tait, et `FORCE=1` la leve.
|
||||
|
||||
## 2026-09-13 (9) — Le site expose sept intrants ; le controle n'en comparait que cinq
|
||||
|
||||
`site_intrants --verifier` rapportait **CONFORME** avec un aplomb complet sur un locataire
|
||||
|
|
|
|||
1
Makefile
1
Makefile
|
|
@ -907,6 +907,7 @@ deployer-tout: _instance-requise ## Deploie TOUTE la flotte dans l'ordre des cou
|
|||
printf '%s\n' 'Relancer avec CONFIRMER=true. Astuce: tester d abord en idempotent avec MODE_CHECK=1.'; \
|
||||
exit 2; \
|
||||
fi; \
|
||||
python3 scripts/verifier_genome_a_jour.py || exit 2; \
|
||||
python3 scripts/orchestrer.py verifier; \
|
||||
python3 scripts/orchestrer.py ecrire; \
|
||||
vault_chiffre="$$(grep -rlsIF '$$ANSIBLE_VAULT' $(dir $(INVENTAIRE_PRODUCTION))group_vars 2>/dev/null | head -1 || true)"; \
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 5 pool(s) Proxmox, 44 VM placee(s), aucun nom ni VMID en collision. |
|
||||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 33 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 22, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
|
||||
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
|
||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 64 scripts expliques et atteignables, 121 cibles make documentees, 68 roles avec README. |
|
||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 65 scripts expliques et atteignables, 121 cibles make documentees, 68 roles avec README. |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (149 cle(s) declaree(s) par l'instance). |
|
||||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 34 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). |
|
||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 45 document(s) declarent leur lecteur (39 genere(s) exempte(s)). |
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 41 role(s) serveur/client tous nommes, 41 groupe(s) cite(s) en table existent tous. |
|
||||
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
|
||||
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
|
||||
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 60 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
|
||||
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 61 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
|
||||
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 5 edge(s) emettent un certificat portant les noms publies (instance-ci-1646753/production, OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre |
|
||||
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 153 regle(s) du site. |
|
||||
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
|
||||
|
|
|
|||
125
scripts/verifier_genome_a_jour.py
Executable file
125
scripts/verifier_genome_a_jour.py
Executable file
|
|
@ -0,0 +1,125 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Les depots locaux sont-ils a jour sur leur forge, AVANT de deployer ?
|
||||
|
||||
UN RUNNER TIRE CE QU'ON POUSSE — IL NE LE RECOIT PAS (2026-09-13).
|
||||
|
||||
Trois fois dans une meme soiree, un runner perime a menace de rebatir un etat depasse :
|
||||
|
||||
- le runner du SITE, treize commits en retard, s'appretait a materialiser un locataire
|
||||
avec son ancien plan — serveur de sauvegarde inutile, cache d'artefacts en trop,
|
||||
runner sans pouvoir de configurer, et aucune machine avec son plancher de memoire ;
|
||||
- le runner du LOCATAIRE, clone pendant l'insemination donc avant trois correctifs,
|
||||
s'appretait a reposer un plan d'administration pointant le WAN d'un AUTRE site — et
|
||||
a refermer derriere lui la porte que l'exploitant venait de rouvrir.
|
||||
|
||||
AUCUN DES DEUX N'AURAIT ECHOUE. Un deploiement depuis un genome perime REUSSIT : il
|
||||
applique fidelement un etat qui n'a plus cours. C'est le mode de defaillance le plus
|
||||
couteux de cette famille, parce qu'il se presente en vert.
|
||||
|
||||
CE QUE CETTE GARDE FAIT, ET CE QU'ELLE NE FAIT PAS :
|
||||
|
||||
en RETARD -> REFUS. Deployer poserait un etat qu'on sait depasse.
|
||||
en AVANCE -> note, et on continue. Un mainteneur qui travaille localement est normal.
|
||||
DIVERGE -> note, et on continue. C'est a l'humain de trancher, pas a une garde.
|
||||
injoignable -> note, et on continue. Une forge en panne ne doit pas bloquer une
|
||||
exploitation ; on le DIT, c'est tout. Le silence serait la faute.
|
||||
|
||||
`FORCE=1` passe outre — pour le cas legitime ou l'on deploie sciemment un etat local.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
RACINE = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(RACINE / "scripts"))
|
||||
|
||||
DELAI_FETCH = 20 # s — une forge lente ne doit pas immobiliser un deploiement
|
||||
|
||||
|
||||
def _git(depot: Path, *args: str, delai: int | None = None) -> tuple[int, str]:
|
||||
try:
|
||||
p = subprocess.run(["git", "-C", str(depot), *args],
|
||||
capture_output=True, text=True, timeout=delai)
|
||||
return p.returncode, (p.stdout or p.stderr).strip()
|
||||
except subprocess.TimeoutExpired:
|
||||
return 124, "delai depasse"
|
||||
except Exception as e: # git absent, droits…
|
||||
return 1, f"{e.__class__.__name__}: {e}"
|
||||
|
||||
|
||||
def depots_a_verifier() -> list[tuple[str, Path]]:
|
||||
"""Le moteur, et l'ecosysteme monte. Les deux que le deploiement LIT."""
|
||||
out: list[tuple[str, Path]] = [("moteur", RACINE)]
|
||||
try:
|
||||
from inventory_rules import instance_courante
|
||||
inst = instance_courante()
|
||||
if inst and Path(inst).resolve() != RACINE:
|
||||
out.append(("instance", Path(inst).resolve()))
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
def etat(depot: Path) -> tuple[str, str]:
|
||||
"""(verdict, detail) — verdict dans {a-jour, retard, avance, diverge, inconnu}."""
|
||||
if not (depot / ".git").exists():
|
||||
return "inconnu", "pas un depot git"
|
||||
rc, _ = _git(depot, "fetch", "--quiet", delai=DELAI_FETCH)
|
||||
if rc != 0:
|
||||
return "inconnu", "forge injoignable"
|
||||
rc, amont = _git(depot, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}")
|
||||
if rc != 0:
|
||||
return "inconnu", "aucune branche de suivi"
|
||||
rc, compte = _git(depot, "rev-list", "--left-right", "--count", f"HEAD...{amont}")
|
||||
if rc != 0:
|
||||
return "inconnu", compte
|
||||
devant, derriere = (compte.split() + ["0", "0"])[:2]
|
||||
devant, derriere = int(devant), int(derriere)
|
||||
if derriere and devant:
|
||||
return "diverge", f"{devant} devant, {derriere} derriere ({amont})"
|
||||
if derriere:
|
||||
return "retard", f"{derriere} commit(s) derriere {amont}"
|
||||
if devant:
|
||||
return "avance", f"{devant} commit(s) devant {amont}"
|
||||
return "a-jour", amont
|
||||
|
||||
|
||||
def verifier() -> int:
|
||||
retards, notes = [], []
|
||||
for role, depot in depots_a_verifier():
|
||||
verdict, detail = etat(depot)
|
||||
ligne = f" {role:<9} {depot.name:<20} {detail}"
|
||||
if verdict == "retard":
|
||||
retards.append(ligne)
|
||||
elif verdict != "a-jour":
|
||||
notes.append(f"{ligne} [{verdict}]")
|
||||
|
||||
for n in notes:
|
||||
print("note :" + n, file=sys.stderr)
|
||||
|
||||
if not retards:
|
||||
print("OK : genome a jour.")
|
||||
return 0
|
||||
|
||||
print("Refus: le genome local est EN RETARD sur sa forge.", file=sys.stderr)
|
||||
for r in retards:
|
||||
print(r, file=sys.stderr)
|
||||
print("", file=sys.stderr)
|
||||
print("Un deploiement depuis un genome perime REUSSIT — il applique fidelement",
|
||||
file=sys.stderr)
|
||||
print("un etat qui n'a plus cours, et se presente en vert.", file=sys.stderr)
|
||||
print("", file=sys.stderr)
|
||||
print(" git -C <depot> pull --ff-only puis relancer", file=sys.stderr)
|
||||
print(" FORCE=1 <commande> pour deployer l'etat local sciemment",
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if os.environ.get("FORCE") in ("1", "true", "yes"):
|
||||
print("note : FORCE=1 — controle du genome passe outre.", file=sys.stderr)
|
||||
raise SystemExit(0)
|
||||
raise SystemExit(verifier())
|
||||
Loading…
Reference in a new issue