reconstruire-locataire : le nom de l'ecosysteme n'est plus demande deux fois

TENANT= le designe en toutes lettres ; le nom qu'exige raser en est
derive par raser.nom_court (une seule derivation). raser seul garde
son garde-fou.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-09-30 15:56:42 -04:00
parent a5a21ee4c5
commit 249d37d2c4
5 changed files with 35 additions and 17 deletions

View file

@ -11,8 +11,12 @@ fois la voûte du locataire et l'accès au runner du site) :
sauf `ARMER=oui`) → `monter` (`make monter-flotte` sur le runner du locataire, suivi toutes sauf `ARMER=oui`) → `monter` (`make monter-flotte` sur le runner du locataire, suivi toutes
les 30 s) → `parefeu` (`--flotte`) → `bilan` (ce que chaque jeu d'état est devenu). les 30 s) → `parefeu` (`--flotte`) → `bilan` (ce que chaque jeu d'état est devenu).
Le nom de l'écosystème s'écrit en toutes lettres (`INSTANCE=`), comme pour `raser`, qui `TENANT=` désigne l'écosystème en toutes lettres ; le nom qu'exige `raser` en est DÉRIVÉ
n'a pas d'autre condition. Arrêt à la première étape en échec, avec la commande de reprise (`raser.nom_court`, une seule dérivation), pas redemandé. La première version exigeait aussi
`INSTANCE=` — relevé par l'exploitant : « pourquoi cette cible a besoin de se faire dire
"chezlepro" deux fois ? ». Le garde-fou de `raser` n'a de sens que pour `raser` seul, qui rase
l'écosystème du lien `instance/` ; ici, rien d'implicite n'est à confirmer. `raser` n'a pas
d'autre condition que sa confirmation. Arrêt à la première étape en échec, avec la commande de reprise
(`DEPUIS=<étape>`) ; journal complet dans `<locataire>/logs/`. (`DEPUIS=<étape>`) ; journal complet dans `<locataire>/logs/`.
**Le runner du site se met à jour avant sa première étape** (quelle que soit la reprise) : **Le runner du site se met à jour avant sa première étape** (quelle que soit la reprise) :

View file

@ -1451,16 +1451,16 @@ monter-flotte: _instance-requise ## Monte une flotte deja creee : flux, AC et DN
# RECONSTRUIRE UN LOCATAIRE DE BOUT EN BOUT, DEPUIS LE POSTE (2026-09-30). Les deux # RECONSTRUIRE UN LOCATAIRE DE BOUT EN BOUT, DEPUIS LE POSTE (2026-09-30). Les deux
# reconstructions du 2026-09-30 passaient par cinq endroits et des commandes tapees a la # reconstructions du 2026-09-30 passaient par cinq endroits et des commandes tapees a la
# main ; la sequence vit desormais dans `scripts/reconstruire_locataire.py` (voir son # main ; la sequence vit desormais dans `scripts/reconstruire_locataire.py` (voir son
# en-tete). Le nom de l'ecosysteme s'ecrit en toutes lettres (`INSTANCE=`), comme pour # en-tete). `TENANT=` designe l'ecosysteme en toutes lettres : le nom qu'exige `raser` en est
# `raser`. L'armement marque une pause, sauf `ARMER=oui`. `DEPUIS=<etape>` reprend apres un # derive, pas redemande. L'armement marque une pause, sauf `ARMER=oui`. `DEPUIS=<etape>` reprend apres un
# arret. Le journal complet va dans `<locataire>/logs/`. # arret. Le journal complet va dans `<locataire>/logs/`.
.PHONY: reconstruire-locataire .PHONY: reconstruire-locataire
reconstruire-locataire: ## Rase et reconstruit un locataire, du site a la recette, etat remis — TENANT= INSTANCE= CONFIRMER=true reconstruire-locataire: ## Rase et reconstruit un locataire, du site a la recette, etat remis — TENANT=OPS-X CONFIRMER=true
@if [[ -z "$(TENANT)" || -z "$(INSTANCE)" ]]; then \ @if [[ -z "$(TENANT)" ]]; then \
printf '%s\n' 'Refus: TENANT=<OPS-X> et INSTANCE=<nom de l ecosysteme, en toutes lettres> requis.'; \ printf '%s\n' 'Refus: TENANT=<OPS-X> requis (le dossier du locataire, en toutes lettres).'; \
exit 2; \ exit 2; \
fi fi
python3 scripts/reconstruire_locataire.py --tenant "$(TENANT)" --instance "$(INSTANCE)" \ python3 scripts/reconstruire_locataire.py --tenant "$(TENANT)" \
$(if $(filter true,$(CONFIRMER)),--confirmer) $(if $(DEPUIS),--depuis "$(DEPUIS)") \ $(if $(filter true,$(CONFIRMER)),--confirmer) $(if $(DEPUIS),--depuis "$(DEPUIS)") \
$(if $(filter oui,$(ARMER)),--armer) $(if $(filter oui,$(ARMER)),--armer)

View file

@ -480,7 +480,7 @@ runbooks:
nature: destructif nature: destructif
portee: poste portee: poste
duree: "long" duree: "long"
variables: [TENANT, INSTANCE, DEPUIS, ARMER] variables: [TENANT, DEPUIS, ARMER]
fixes: {CONFIRMER: "true"} fixes: {CONFIRMER: "true"}
pourquoi: >- pourquoi: >-
TOUT, d'une commande, depuis le poste : sauvegarder, raser et recreer (runner du TOUT, d'une commande, depuis le poste : sauvegarder, raser et recreer (runner du

View file

@ -41,14 +41,22 @@ from inventory_rules import instance_courante # noqa: E402
RACINE = Path(__file__).resolve().parent.parent RACINE = Path(__file__).resolve().parent.parent
def nom_court(dossier: Path) -> str:
"""Le nom qu'il faut TAPER pour raser cet ecosysteme : `OPS-Technolibre` -> `technolibre`.
Une seule derivation : `reconstruire_locataire.py` l'importe au lieu de redemander le
nom (2026-09-30) — la ou l'ecosysteme est deja designe explicitement par `TENANT=`.
"""
return dossier.name.removeprefix("OPS-").lower()
def instance_active() -> tuple[Path, str]: def instance_active() -> tuple[Path, str]:
"""Dossier de l'instance montee, et son nom court (celui qu'il faudra taper).""" """Dossier de l'instance montee, et son nom court (celui qu'il faudra taper)."""
base = instance_courante() base = instance_courante()
if not base.exists(): if not base.exists():
raise SystemExit("Aucune instance montee : rien a raser.") raise SystemExit("Aucune instance montee : rien a raser.")
reel = base.resolve() reel = base.resolve()
nom = reel.name.removeprefix("OPS-").lower() return reel, nom_court(reel)
return reel, nom
def plan_derive(base: Path) -> list[tuple[str, int]]: def plan_derive(base: Path) -> list[tuple[str, int]]:

View file

@ -15,7 +15,7 @@ pas les secrets du locataire, et c'est voulu : l'armement reste un geste du post
LES ETAPES, dans l'ordre (`--depuis` reprend a l'une d'elles apres un arret) : LES ETAPES, dans l'ordre (`--depuis` reprend a l'une d'elles apres un arret) :
sauvegarder poste deposer l'etat de chaque noeud MAINTENANT (sinon on perd la journee) sauvegarder poste deposer l'etat de chaque noeud MAINTENANT (sinon on perd la journee)
raser runner du site detruire les VM — le nom de l'ecosysteme s'ecrit en toutes lettres raser runner du site detruire les VM
creer runner du site recreer les VM depuis le gabarit creer runner du site recreer les VM depuis le gabarit
inseminer runner du site amorcer le runner du locataire (socle + moteur, sans secret) inseminer runner du site amorcer le runner du locataire (socle + moteur, sans secret)
armer poste lui remettre sa voute et sa cle — pause, sauf `--armer` armer poste lui remettre sa voute et sa cle — pause, sauf `--armer`
@ -26,6 +26,12 @@ LES ETAPES, dans l'ordre (`--depuis` reprend a l'une d'elles apres un arret) :
La premiere etape en echec arrete tout, et le dit. `raser` n'a pas d'autre condition que sa La premiere etape en echec arrete tout, et le dit. `raser` n'a pas d'autre condition que sa
confirmation (decision de l'exploitant) : c'est la PROCEDURE qui sauvegarde avant. confirmation (decision de l'exploitant) : c'est la PROCEDURE qui sauvegarde avant.
LE NOM N'EST DEMANDE QU'UNE FOIS. `raser`, seul, exige qu'on tape le nom de l'ecosysteme
parce qu'il rase celui du lien `instance/`, qui peut pointer n'importe ou. Ici l'ecosysteme
est deja designe en toutes lettres par `TENANT=` : le nom que `raser` attend en est DERIVE
(`raser.nom_court`), pas redemande. Une premiere version l'exigeait deux fois — une
occasion de se tromper de plus, et aucune protection de plus (2026-09-30).
""" """
from __future__ import annotations from __future__ import annotations
@ -42,6 +48,7 @@ sys.path.insert(0, str(RACINE / "scripts"))
from devis_reseau import DOSSIER_INSTANCES # noqa: E402 from devis_reseau import DOSSIER_INSTANCES # noqa: E402
from eprouver_parefeu import _ip_par_hote # noqa: E402 from eprouver_parefeu import _ip_par_hote # noqa: E402
from raser import nom_court # noqa: E402
ETAPES = ["sauvegarder", "raser", "creer", "inseminer", "armer", "monter", "parefeu", "bilan"] ETAPES = ["sauvegarder", "raser", "creer", "inseminer", "armer", "monter", "parefeu", "bilan"]
SSH = ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=15", "-o", "ServerAliveInterval=30", SSH = ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=15", "-o", "ServerAliveInterval=30",
@ -134,8 +141,6 @@ def main() -> int:
global JOURNAL global JOURNAL
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
ap.add_argument("--tenant", required=True, help="dossier du locataire, ex. OPS-Technolibre") ap.add_argument("--tenant", required=True, help="dossier du locataire, ex. OPS-Technolibre")
ap.add_argument("--instance", required=True,
help="nom de l'ecosysteme, ecrit en toutes lettres (celui qu'exige `raser`)")
ap.add_argument("--confirmer", action="store_true", help="sans lui, rien n'est fait") ap.add_argument("--confirmer", action="store_true", help="sans lui, rien n'est fait")
ap.add_argument("--depuis", choices=ETAPES, default=ETAPES[0], help="reprendre a cette etape") ap.add_argument("--depuis", choices=ETAPES, default=ETAPES[0], help="reprendre a cette etape")
ap.add_argument("--armer", action="store_true", ap.add_argument("--armer", action="store_true",
@ -148,6 +153,7 @@ def main() -> int:
T = args.tenant T = args.tenant
dossier = DOSSIER_INSTANCES / T dossier = DOSSIER_INSTANCES / T
ecosysteme = nom_court(dossier)
inv = dossier / "inventories" / "principal" / "hosts.yml" inv = dossier / "inventories" / "principal" / "hosts.yml"
if not inv.is_file(): if not inv.is_file():
print(f"REFUS : {inv} introuvable.") print(f"REFUS : {inv} introuvable.")
@ -155,7 +161,7 @@ def main() -> int:
runner_nom, runner_ip = runner_du_locataire(T) runner_nom, runner_ip = runner_du_locataire(T)
a_faire = ETAPES[ETAPES.index(args.depuis):] a_faire = ETAPES[ETAPES.index(args.depuis):]
if not args.confirmer: if not args.confirmer:
print(f"Refus : RECONSTRUCTION de {T} ({args.instance}) — ses VM seront DETRUITES puis refaites.") print(f"Refus : RECONSTRUCTION de {T} ({ecosysteme}) — ses VM seront DETRUITES puis refaites.")
print(f" etapes : {' -> '.join(a_faire)} ; runner du locataire : {runner_nom} ({runner_ip})") print(f" etapes : {' -> '.join(a_faire)} ; runner du locataire : {runner_nom} ({runner_ip})")
print(" Relancer avec CONFIRMER=true.") print(" Relancer avec CONFIRMER=true.")
return 2 return 2
@ -172,7 +178,7 @@ def main() -> int:
def arret(nom: str, rc: int, quoi: str = "") -> int: def arret(nom: str, rc: int, quoi: str = "") -> int:
dire(f"\nARRET a l'etape « {nom} » (code {rc}){' — ' + quoi if quoi else ''}.") dire(f"\nARRET a l'etape « {nom} » (code {rc}){' — ' + quoi if quoi else ''}.")
dire(f"Reprendre, une fois la cause comprise : make reconstruire-locataire TENANT={T} " dire(f"Reprendre, une fois la cause comprise : make reconstruire-locataire TENANT={T} "
f"INSTANCE={args.instance} CONFIRMER=true DEPUIS={nom}") f"CONFIRMER=true DEPUIS={nom}")
return rc or 1 return rc or 1
site_tire = False site_tire = False
@ -189,7 +195,7 @@ def main() -> int:
filtre=("fatal:", "PLAY RECAP", "failed=")) filtre=("fatal:", "PLAY RECAP", "failed="))
elif nom == "raser": elif nom == "raser":
rc = sur_site(args, f"SETOPS_INSTANCE={args.racine_runner}/{T} make raser CONFIRMER=true " rc = sur_site(args, f"SETOPS_INSTANCE={args.racine_runner}/{T} make raser CONFIRMER=true "
f"INSTANCE={shlex.quote(args.instance)}", f"INSTANCE={shlex.quote(ecosysteme)}",
filtre=("detruite", "VM detruites", "Refus", "REFUS", "ERREUR")) filtre=("detruite", "VM detruites", "Refus", "REFUS", "ERREUR"))
elif nom == "creer": elif nom == "creer":
rc = sur_site(args, f"SETOPS_INSTANCE={args.racine_runner}/{T} make flotte-creer " rc = sur_site(args, f"SETOPS_INSTANCE={args.racine_runner}/{T} make flotte-creer "