validation avant release : test casse par (74), trois defauts d outillage
test_restauration fournit client_backup_attente_verrou et exige --retry-lock ; test_frontiere_refus lit le WAN sous if_wan ; prouver --verifier nomme les preuves en echec ; le Makefile n exporte plus une liste de cles de voute vide (valeur identique sur le poste). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5d7d2a8918
commit
f42d30b38a
5 changed files with 56 additions and 3 deletions
31
CHANGELOG.md
31
CHANGELOG.md
|
|
@ -1,5 +1,36 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-10-04 (76) — Validation avant release : un test cassé par (74), trois défauts d'outillage
|
||||||
|
|
||||||
|
**Contexte** : avant d'étiqueter, `make verifier` et `make ci`. La validation a trouvé une
|
||||||
|
régression **introduite par (74)**, que sa propre validation n'avait pas vue :
|
||||||
|
`--syntax-check` et `ansible-lint` seulement, pas `make test`.
|
||||||
|
|
||||||
|
**Corrigé** :
|
||||||
|
- `scripts/tests/test_restauration.py` : le test rendait `restaurer.sh.j2` sans
|
||||||
|
`client_backup_attente_verrou` (ajoutée en (74)) et échouait au rendu. Il la fournit
|
||||||
|
désormais, et son faux `restic` **exige** `--retry-lock` avant la sous-commande : l'attente
|
||||||
|
du verrou est couverte, plus seulement tolérée.
|
||||||
|
- `scripts/tests/test_frontiere_refus.py` (depuis le 2026-10-01) : il lisait le WAN sous
|
||||||
|
`interfaces.wan`, une clé que le devis ne produit pas (c'est `if_wan`). Il retombait sur
|
||||||
|
« wan », juste au site par chance.
|
||||||
|
- `scripts/prouver.py` : en mode `--verifier`, le harnais comptait les échecs (« 6 echec »)
|
||||||
|
sans les nommer. Il nomme maintenant chaque preuve en échec, avec son détail.
|
||||||
|
- `Makefile` : sur un clone sans aucune clé de voûte (la CI de la forge), l'export posait
|
||||||
|
`ANSIBLE_VAULT_IDENTITY_LIST=` vide ; Ansible le lisait comme un fichier de mot de passe
|
||||||
|
et refusait (« can not be a directory »). `make ci` mourait à sa première commande. Vide,
|
||||||
|
la variable n'est plus exportée. Sur le poste, la valeur exportée est **identique**
|
||||||
|
(mêmes 5 clés, comparée avant et après).
|
||||||
|
|
||||||
|
**Pas corrigé ici, et nommé** : `make ci` reste non conforme (77 OK, 6 échecs : P02, P20,
|
||||||
|
P74, P79, P81, P82). Le modèle public `exemples/modeles/socle` date d'avant la séparation
|
||||||
|
site / locataire et ne satisfait pas les gardes ajoutées depuis août. Décision de
|
||||||
|
l'exploitant : le modèle aura son propre dépôt, `OPS-Modele`, comme les locataires. Aucune
|
||||||
|
preuve n'a été assouplie pour le vieux modèle.
|
||||||
|
|
||||||
|
**Validation** : `make verifier` conforme (83/83), `ansible-lint` (0 échec). La release
|
||||||
|
attend une reconstruction qui prouve (73), (74) et ces corrections.
|
||||||
|
|
||||||
## 2026-10-04 (75) — Exporter les voûtes une seconde fois ne bute plus sur la première
|
## 2026-10-04 (75) — Exporter les voûtes une seconde fois ne bute plus sur la première
|
||||||
|
|
||||||
**Signalé par l'exploitant** : `make voutes-exporter VERS=<clé>` a refusé (« existe deja.
|
**Signalé par l'exploitant** : `make voutes-exporter VERS=<clé>` a refusé (« existe deja.
|
||||||
|
|
|
||||||
11
Makefile
11
Makefile
|
|
@ -21,7 +21,16 @@ export SETOPS_INSTANCE ?= instance
|
||||||
#
|
#
|
||||||
# `?=` : une valeur deja posee dans l'environnement gagne — la GUI et les runners peuvent
|
# `?=` : une valeur deja posee dans l'environnement gagne — la GUI et les runners peuvent
|
||||||
# nommer leurs propres cles sans que ce fichier ait a les connaitre.
|
# nommer leurs propres cles sans que ce fichier ait a les connaitre.
|
||||||
export ANSIBLE_VAULT_IDENTITY_LIST ?= $(shell python3 scripts/voutes.py identites 2>/dev/null)
|
#
|
||||||
|
# VIDE, ON N'EXPORTE RIEN (2026-10-04). Sur un clone sans aucune cle (la CI de la forge),
|
||||||
|
# `voutes.py` ne rend rien, et l'export posait quand meme `ANSIBLE_VAULT_IDENTITY_LIST=`.
|
||||||
|
# Ansible lit cette chaine vide comme un FICHIER de mot de passe, la resout en repertoire
|
||||||
|
# courant et refuse (« can not be a directory ») : `make ci` mourait a sa premiere commande
|
||||||
|
# sur un clone nu. Le poste a toujours des cles, il ne pouvait pas le voir.
|
||||||
|
SETOPS_VOUTES_IDENTITES := $(shell python3 scripts/voutes.py identites 2>/dev/null)
|
||||||
|
ifneq ($(strip $(SETOPS_VOUTES_IDENTITES)),)
|
||||||
|
export ANSIBLE_VAULT_IDENTITY_LIST ?= $(SETOPS_VOUTES_IDENTITES)
|
||||||
|
endif
|
||||||
# Inventaire de l'instance : un seul par instance dans le modèle « séparation par
|
# Inventaire de l'instance : un seul par instance dans le modèle « séparation par
|
||||||
# instance ». Détection rétro-compatible : principal > production > lab.
|
# instance ». Détection rétro-compatible : principal > production > lab.
|
||||||
# Surchargeable : make … SETOPS_INVENTAIRE=chemin/hosts.yml
|
# Surchargeable : make … SETOPS_INVENTAIRE=chemin/hosts.yml
|
||||||
|
|
|
||||||
|
|
@ -4972,6 +4972,12 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
print(f"{'CONFORME' if conforme else 'NON CONFORME'} : "
|
print(f"{'CONFORME' if conforme else 'NON CONFORME'} : "
|
||||||
f"{n_ok} OK, {n_echec} echec, {n_saute} saute.")
|
f"{n_ok} OK, {n_echec} echec, {n_saute} saute.")
|
||||||
if not ecrire_rapport:
|
if not ecrire_rapport:
|
||||||
|
# NOMMER CE QUI ECHOUE (2026-10-04) : le verdict seul (« 6 echec ») obligeait a
|
||||||
|
# reconstituer le contexte et relire le code pour savoir lesquelles.
|
||||||
|
for p, s, d in resultats:
|
||||||
|
if s == "ECHEC":
|
||||||
|
print(f" ECHEC {p['id']} — {p.get('titre') or p.get('nom') or ''} : "
|
||||||
|
f"{' '.join(str(d).split())[:300]}")
|
||||||
return 0 if conforme else 1
|
return 0 if conforme else 1
|
||||||
|
|
||||||
icone = {"OK": "✅", "ECHEC": "❌", "SAUTE": "⚪"}
|
icone = {"OK": "✅", "ECHEC": "❌", "SAUTE": "⚪"}
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,11 @@ def main() -> int:
|
||||||
if r.returncode != 0 or not r.stdout.strip().startswith("{"):
|
if r.returncode != 0 or not r.stdout.strip().startswith("{"):
|
||||||
print("SAUTE le devis de la frontiere ne se genere pas ici (site invisible)")
|
print("SAUTE le devis de la frontiere ne se genere pas ici (site invisible)")
|
||||||
return 0
|
return 0
|
||||||
regles = json.loads(r.stdout).get("regles") or []
|
devis = json.loads(r.stdout)
|
||||||
wan = str((json.loads(r.stdout).get("interfaces") or {}).get("wan") or "wan")
|
regles = devis.get("regles") or []
|
||||||
|
# `if_wan`, la cle que le devis produit. Le test lisait `interfaces.wan`, qui n'existe
|
||||||
|
# pas : il retombait sur « wan », juste au site par chance, faux sur le modele public.
|
||||||
|
wan = str(devis.get("if_wan") or "wan")
|
||||||
echecs = []
|
echecs = []
|
||||||
refus = [x for x in regles if x.get("action") == "reject"]
|
refus = [x for x in regles if x.get("action") == "reject"]
|
||||||
if any(x.get("interface") == wan for x in refus):
|
if any(x.get("interface") == wan for x in refus):
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,9 @@ def outil() -> None:
|
||||||
# SETOPS_TEST_DEPOT_ABSENT=1 imite restic 0.17+ face a un depot inexistant (code 10).
|
# SETOPS_TEST_DEPOT_ABSENT=1 imite restic 0.17+ face a un depot inexistant (code 10).
|
||||||
executable(bin_ / "restic", f"""#!/bin/bash
|
executable(bin_ / "restic", f"""#!/bin/bash
|
||||||
if [[ -n "${{SETOPS_TEST_DEPOT_ABSENT:-}}" ]]; then exit 10; fi
|
if [[ -n "${{SETOPS_TEST_DEPOT_ABSENT:-}}" ]]; then exit 10; fi
|
||||||
|
# L'outil passe `--retry-lock <duree>` avant la sous-commande (74) : on l'exige, puis on le saute.
|
||||||
|
[[ "$1" == --retry-lock && -n "$2" ]] || {{ echo "restic appele sans --retry-lock : $*" >&2; exit 99; }}
|
||||||
|
shift 2
|
||||||
case "$1" in
|
case "$1" in
|
||||||
snapshots) cat "{d}/snapshots.json" ;;
|
snapshots) cat "{d}/snapshots.json" ;;
|
||||||
restore)
|
restore)
|
||||||
|
|
@ -164,6 +167,7 @@ if (( ! avec_c )); then cat > "{log}/section.sql"; fi
|
||||||
client_backup_restauration_mise_de_cote=str(d / "mis-de-cote"),
|
client_backup_restauration_mise_de_cote=str(d / "mis-de-cote"),
|
||||||
client_backup_staging=str(staging),
|
client_backup_staging=str(staging),
|
||||||
client_backup_jobs=[{"nom": "courriel"}, {"nom": "postgresql"}],
|
client_backup_jobs=[{"nom": "courriel"}, {"nom": "postgresql"}],
|
||||||
|
client_backup_attente_verrou="30m",
|
||||||
).replace("/etc/ssh/ssh_host_ed25519_key.pub", str(cle)) \
|
).replace("/etc/ssh/ssh_host_ed25519_key.pub", str(cle)) \
|
||||||
.replace("/etc/setops/restic.pass", str(d / "restic.pass"))
|
.replace("/etc/setops/restic.pass", str(d / "restic.pass"))
|
||||||
script = d / "setops-restaurer"
|
script = d / "setops-restaurer"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue