Set-OPS-Public/scripts/tests/test_verrou_runner.py
Daniel Allaire 422b68846b serveur_ops : creer les depots de roues et de collections avant la copie
La copie fichier par fichier du verrou ne cree pas son dossier, contrairement
a l'ancienne copie du cache entier. La reconstruction de Technolibre s'est
arretee a l'insemination sur un runner neuf (roues bien prises au site).
test_verrou_runner.py exige desormais que chaque dossier vise soit cree avant.

CHANGELOG (121) : l'arret, et le blocage site-pki-01 -> 10.37.32.1:8006 vu a
la frontiere, qui est la sonde de connectivite (resultat attendu).

94 preuves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:22:01 -04:00

93 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""`verrou-runner.yml` : ce que le runner installe, une version par bibliotheque, coherent avec
les epingles — et plus aucune resolution contre PyPI ou Galaxy dans les taches.
POURQUOI (2026-10-09). Le runner de Technolibre portait deux versions de six bibliotheques :
`pip download` resolvait contre PyPI a chaque passage, et PyPI avait bouge entre
l'insemination et le montage. Le verrou nomme chaque fichier ; ce test garde qu'il reste un
verrou (une version par bibliotheque), que ses roues vont au Python declare, et que les
epingles ecrites ailleurs disent la meme chose que lui.
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
import yaml
RACINE = Path(__file__).resolve().parents[2]
ECHECS: list[str] = []
def verifier(cond: bool, msg: str) -> None:
print(("OK " if cond else "ECHEC ") + msg)
if not cond:
ECHECS.append(msg)
def norme(nom: str) -> str:
return re.sub(r"[-_.]+", "_", nom).lower()
def main() -> int:
v = yaml.safe_load((RACINE / "verrou-runner.yml").read_text())
roues, colls = v["python"], v["collections"]
for e in roues + colls:
verifier(e["url"].endswith("/" + e["nom"]) and re.fullmatch(r"[0-9a-f]{64}", str(e["sha256"])) is not None,
f"{e['nom']} : url qui le nomme, sha256 bien formee")
versions: dict[str, set[str]] = {}
for e in roues:
dist, ver = e["nom"].split("-")[0], e["nom"].split("-")[1]
versions.setdefault(norme(dist), set()).add(ver)
doubles = {d: sorted(vs) for d, vs in versions.items() if len(vs) > 1}
verifier(not doubles, f"une seule version par bibliotheque ({len(versions)} bibliotheques){' : ' + str(doubles) if doubles else ''}")
cible = "cp" + str(v["python_cible"]).replace(".", "")
hors = [e["nom"] for e in roues if e["nom"].split("-")[2].startswith("cp") and "abi3" not in e["nom"]
and e["nom"].split("-")[2] != cible]
verifier(not hors, f"les roues compilees vont a Python {v['python_cible']} ({cible}){' : ' + str(hors) if hors else ''}")
epingles = {}
for l in (RACINE / "requirements-python.txt").read_text().splitlines():
m = re.match(r"^\s*([A-Za-z0-9_.-]+)==([^\s#]+)", l)
if m:
epingles[norme(m.group(1))] = m.group(2)
defauts = yaml.safe_load((RACINE / "roles/serveur_ops/defaults/main.yml").read_text())
m = re.fullmatch(r"\s*([A-Za-z0-9_.-]+)==(\S+)\s*", defauts["serveur_ops_ansible"])
verifier(m is not None, f"serveur_ops_ansible est une epingle exacte ({defauts['serveur_ops_ansible']})")
if m:
epingles[norme(m.group(1))] = m.group(2)
for dist, ver in sorted(epingles.items()):
verifier(versions.get(dist) == {ver}, f"{dist}=={ver} epingle, et c'est la version du verrou ({sorted(versions.get(dist, []))})")
req = yaml.safe_load((RACINE / "requirements.yml").read_text())["collections"]
attendus = sorted(f"{c['name'].replace('.', '-')}-{c['version']}.tar.gz" for c in req)
verifier(sorted(e["nom"] for e in colls) == attendus, f"les collections du verrou sont celles de requirements.yml ({attendus})")
taches = [l for l in (RACINE / "roles/serveur_ops/tasks/main.yml").read_text().splitlines()
if not l.lstrip().startswith("#")]
texte = "\n".join(taches)
verifier('"download"' not in texte and "collection download" not in texte,
"plus aucune resolution contre PyPI ou Galaxy dans serveur_ops (pip / collection download)")
# Une copie fichier par fichier ne cree pas son dossier (2026-10-09 : reconstruction de
# Technolibre arretee sur un runner neuf). Chaque dossier vise doit etre assure AVANT.
assures: set[str] = set()
for t in yaml.safe_load((RACINE / "roles/serveur_ops/tasks/main.yml").read_text()):
f = t.get("ansible.builtin.file") or {}
if f.get("state") == "directory":
assures.add(f["path"])
c = t.get("ansible.builtin.copy") or {}
if str(c.get("dest", "")).endswith("/{{ item.nom }}"):
dossier = c["dest"].rsplit("/", 1)[0]
verifier(dossier in assures, f"« {t['name']} » : son dossier est cree avant ({dossier})")
verifier("verrou-runner.yml" in (RACINE / "roles/serveur_artefacts/tasks/main.yml").read_text(),
"le site tient le verrou du runner")
if ECHECS:
print(f"\n{len(ECHECS)} echec(s).")
return 1
print("\nLe verrou nomme chaque fichier du runner, une version par bibliotheque, et le site le tient.")
return 0
if __name__ == "__main__":
sys.exit(main())