Set-OPS-Public/scripts/publier.py
Daniel Allaire 91dc90f22b publier : eregion et la forge du site en un seul geste, verifie
La forge du site fait autorite et n etait nourrie que par un geste a part,
genome-pousser, qu on oublie. make publier pousse, porte, et verifie ; il refuse
si eregion porte des commits absents du poste.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:20:06 -04:00

115 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""Publier le genome : eregion ET la forge du site, en un seul geste.
POURQUOI (2026-09-28). La forge du SITE fait autorite (D-81) : un ecosysteme s'y reproduit.
Mais les commits naissent sur le poste, et n'y arrivent que par `make genome-pousser` — un
geste A PART, que rien ne force ni ne signale. Un `git push` vers eregion ne la met pas a
jour. Le 2026-08-26, elle avait quatre commits de retard, dont le correctif du pare-feu
Proxmox : un ecosysteme reconstruit ce jour-la aurait reproduit le defaut qu'on croyait
repare. Le 2026-09-28 encore, `make genome-etat` la trouvait en retard d'un commit.
Le retard vient d'un geste qu'on oublie : on fait du geste un seul geste.
CE QUE CA FAIT, pour chaque depot que le runner du site porte (`serveur_ops_depots`, la MEME
liste que `genome-pousser` — lue, pas recopiee) :
1. recuperer eregion, et REFUSER si eregion porte des commits que le poste n'a pas (une
demande de fusion acceptee, par exemple) : publier sans les integrer les ecraserait
sur la forge du site ;
2. signaler ce qui n'est pas committe — cela ne part pas ;
3. pousser sur eregion ;
puis `make genome-pousser`, puis `make genome-etat` : la forge du site doit porter la meme
tete que le poste, sinon on le dit.
make publier tous les depots du genome
make publier DEPOT=ops-chezlepro
"""
from __future__ import annotations
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
RACINE = Path(__file__).resolve().parent.parent
DEPOTS = RACINE.parent
def git(depot: Path, *args: str) -> subprocess.CompletedProcess:
return subprocess.run(["git", "-C", str(depot), *args], capture_output=True, text=True)
def genome() -> list[dict]:
r = subprocess.run(["ansible-inventory", "-i", "scripts/site_inventaire.py", "--host", "site-ops-01"],
capture_output=True, text=True, cwd=RACINE, env=os.environ)
try:
return json.loads(r.stdout).get("serveur_ops_depots") or []
except ValueError:
raise SystemExit("Inventaire du site illisible : la liste du genome est inconnue.\n" + r.stderr[:300])
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
ap.add_argument("--depot", default="", help="un seul depot (nom sur la forge)")
args = ap.parse_args()
liste = [d for d in genome() if not args.depot or d["depot"] == args.depot]
if not liste:
raise SystemExit(f"Aucun depot du genome ne s'appelle « {args.depot} ».")
refus = []
for d in liste:
depot = DEPOTS / d["dest"]
if not (depot / ".git").is_dir():
print(f" - {d['depot']:<17} pas de copie sur ce poste — rien a publier")
continue
branche = git(depot, "branch", "--show-current").stdout.strip()
if git(depot, "remote", "get-url", "eregion").returncode != 0:
print(f" - {d['depot']:<17} aucun remote `eregion`")
continue
git(depot, "fetch", "-q", "eregion")
distant = f"eregion/{branche}"
if git(depot, "rev-parse", "--verify", "-q", distant).returncode == 0:
manquants = git(depot, "rev-list", "--count", f"HEAD..{distant}").stdout.strip()
if manquants not in ("", "0"):
refus.append(d["depot"])
print(f" !! {d['depot']:<17} eregion porte {manquants} commit(s) absent(s) du poste "
f"— les integrer d'abord (`git pull --rebase eregion {branche}`)")
continue
sale = [l for l in git(depot, "status", "--porcelain").stdout.splitlines() if l.strip()]
if sale:
print(f" ~ {d['depot']:<17} {len(sale)} changement(s) NON committe(s) — ils ne partent pas")
avance = git(depot, "rev-list", "--count", f"{distant}..HEAD").stdout.strip() or "?"
r = git(depot, "push", "-q", "eregion", branche)
if r.returncode != 0:
refus.append(d["depot"])
print(f" !! {d['depot']:<17} push refuse : {r.stderr.strip()[:120]}")
continue
print(f" ok {d['depot']:<17} eregion a jour ({avance} commit(s) pousse(s))")
if refus:
print(f"\nREFUS : {', '.join(refus)}. Rien n'a ete porte a la forge du site — la corriger "
f"en retard vaut mieux que la corriger avec un depot incomplet.")
return 2
print("\nPorter a la forge du site (runner) :")
env = dict(os.environ, **({"DEPOT": args.depot} if args.depot else {}))
if subprocess.run(["make", "--no-print-directory", "genome-pousser"] + ([f"DEPOT={args.depot}"] if args.depot else []),
cwd=RACINE, env=env, capture_output=True).returncode != 0:
print(" ECHEC de `make genome-pousser` : eregion est a jour, la forge du site NON.")
return 3
r = subprocess.run(["make", "--no-print-directory", "genome-etat"], cwd=RACINE,
capture_output=True, text=True)
for ligne in r.stdout.splitlines():
if '"msg": "' in ligne and " : poste " in ligne:
print(" " + ligne.split('"msg": "', 1)[1].replace("\\n", " ").rstrip('"'))
if r.returncode != 0:
print(" La forge du site ne porte PAS la tete du poste — voir `make genome-etat`.")
return 4
print("Publie : eregion et la forge du site portent la tete du poste.")
return 0
if __name__ == "__main__":
sys.exit(main())