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
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
n'a pas d'autre condition. Arrêt à la première étape en échec, avec la commande de reprise
`TENANT=` désigne l'écosystème en toutes lettres ; le nom qu'exige `raser` en est DÉRIVÉ
(`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/`.
**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
# 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
# en-tete). Le nom de l'ecosysteme s'ecrit en toutes lettres (`INSTANCE=`), comme pour
# `raser`. L'armement marque une pause, sauf `ARMER=oui`. `DEPUIS=<etape>` reprend apres un
# en-tete). `TENANT=` designe l'ecosysteme en toutes lettres : le nom qu'exige `raser` en est
# derive, pas redemande. L'armement marque une pause, sauf `ARMER=oui`. `DEPUIS=<etape>` reprend apres un
# arret. Le journal complet va dans `<locataire>/logs/`.
.PHONY: reconstruire-locataire
reconstruire-locataire: ## Rase et reconstruit un locataire, du site a la recette, etat remis — TENANT= INSTANCE= CONFIRMER=true
@if [[ -z "$(TENANT)" || -z "$(INSTANCE)" ]]; then \
printf '%s\n' 'Refus: TENANT=<OPS-X> et INSTANCE=<nom de l ecosysteme, en toutes lettres> requis.'; \
reconstruire-locataire: ## Rase et reconstruit un locataire, du site a la recette, etat remis — TENANT=OPS-X CONFIRMER=true
@if [[ -z "$(TENANT)" ]]; then \
printf '%s\n' 'Refus: TENANT=<OPS-X> requis (le dossier du locataire, en toutes lettres).'; \
exit 2; \
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 oui,$(ARMER)),--armer)

View file

@ -480,7 +480,7 @@ runbooks:
nature: destructif
portee: poste
duree: "long"
variables: [TENANT, INSTANCE, DEPUIS, ARMER]
variables: [TENANT, DEPUIS, ARMER]
fixes: {CONFIRMER: "true"}
pourquoi: >-
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
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]:
"""Dossier de l'instance montee, et son nom court (celui qu'il faudra taper)."""
base = instance_courante()
if not base.exists():
raise SystemExit("Aucune instance montee : rien a raser.")
reel = base.resolve()
nom = reel.name.removeprefix("OPS-").lower()
return reel, nom
return reel, nom_court(reel)
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) :
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
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`
@ -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
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
@ -42,6 +48,7 @@ sys.path.insert(0, str(RACINE / "scripts"))
from devis_reseau import DOSSIER_INSTANCES # 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"]
SSH = ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=15", "-o", "ServerAliveInterval=30",
@ -134,8 +141,6 @@ def main() -> int:
global JOURNAL
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
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("--depuis", choices=ETAPES, default=ETAPES[0], help="reprendre a cette etape")
ap.add_argument("--armer", action="store_true",
@ -148,6 +153,7 @@ def main() -> int:
T = args.tenant
dossier = DOSSIER_INSTANCES / T
ecosysteme = nom_court(dossier)
inv = dossier / "inventories" / "principal" / "hosts.yml"
if not inv.is_file():
print(f"REFUS : {inv} introuvable.")
@ -155,7 +161,7 @@ def main() -> int:
runner_nom, runner_ip = runner_du_locataire(T)
a_faire = ETAPES[ETAPES.index(args.depuis):]
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(" Relancer avec CONFIRMER=true.")
return 2
@ -172,7 +178,7 @@ def main() -> 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"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
site_tire = False
@ -189,7 +195,7 @@ def main() -> int:
filtre=("fatal:", "PLAY RECAP", "failed="))
elif nom == "raser":
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"))
elif nom == "creer":
rc = sur_site(args, f"SETOPS_INSTANCE={args.racine_runner}/{T} make flotte-creer "