Set-OPS-Public/playbooks/maintenance/depots_perimes.yml

157 lines
6.9 KiB
YAML
Raw Permalink Normal View History

depots perimes : ce qui n'existe qu'ici ne se detruit pas depuis ici serveur_ops clone ce que le plan declare et ne retire rien : le runner de Chezlepro-locataire portait encore le clone de SITE-Chezlepro, la carte de son hebergeur, longtemps apres que son plan ait cesse de la declarer. Le retrait n'entre pas dans le role. Un role qui efface des dossiers a chaque passage est une grenade degoupillee : une faute de frappe dans serveur_ops_depots suffirait a perdre du travail local. C'est donc un geste separe, qui regarde par defaut et n'efface que sur CONFIRMER=true. Trois choses ne sont jamais retirees, meme confirmees : ce qui n'est pas un depot git, ce qui porte des modifications non validees, ce qui porte des commits qu'aucun distant ne porte. La garde a servi au premier essai : le releve a nomme SITE-Chezlepro et venv, et venv a ete ecarte parce que ce n'est pas un depot git. Une version naive aurait efface l'environnement Python du runner en se disant satisfaite. Passe deux fois sur le runner reel : regarder (changed=0), puis confirmer (changed=1) avec relecture. Le runner ne porte plus que Set-OPS-public et OPS-Chezlepro ; sa console rend portee=tenant, materialiser=False, fabric=False — le pouvoir de LIRE la fabric est tombe avec la carte, et c'est juste. Valide : syntax-check du playbook, runbooks.py verifier a 0 ecart (la cible neuve est portee par le runbook Filiation), make test a 0 echec. P02 reste en echec pour la raison anterieure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:18:59 -04:00
---
# LES DEPOTS D'UN RUNNER SUIVENT SA DECLARATION — DANS LES DEUX SENS.
#
# `serveur_ops` clone ce que `serveur_ops_depots` declare. Il ne retire rien : un depot
# raye du plan reste sur le disque, et le runner garde de quoi lire un ecosysteme qu'il
# ne declare plus. Mesure du 2026-09-20 : le runner de Chezlepro-LOCATAIRE portait encore
# le clone de `SITE-Chezlepro` — la carte de son hebergeur — apres que son plan ait cesse
# de la declarer.
#
# POURQUOI CE N'EST PAS DANS LE ROLE. Retirer un depot est DESTRUCTIF, et la regle du
# depot est qu'une action destructive s'annonce et se confirme. Un role qui efface des
# dossiers a chaque passage serait une grenade degoupillee : il suffirait d'une faute de
# frappe dans `serveur_ops_depots` pour perdre un travail local. On en fait donc un geste
# separe, qui REGARDE par defaut et n'efface que sur `CONFIRMER=true`.
#
# CE QUI NE SERA JAMAIS EFFACE, meme avec la confirmation :
# - un dossier qui n'est pas un depot git (on ne sait pas ce que c'est) ;
# - un depot qui porte des modifications non validees ;
# - un depot qui porte des commits qu'aucun distant ne porte.
# Dans ces trois cas le playbook le DIT et passe. Ce qui n'existe qu'ici ne se detruit
# pas depuis ici.
#
# make depots-perimes regarde, n'efface rien
# make depots-perimes CONFIRMER=true retire ceux qui ne portent rien d'unique
- name: Les dépôts qu'un runner ne déclare plus
hosts: "{{ cible | default('serveur_ops') }}"
become: true
become_user: "{{ serveur_ops_utilisateur | default('setops') }}"
gather_facts: false
vars:
racine: "{{ serveur_ops_racine | default('/opt/setops') }}"
confirme: "{{ (lookup('env', 'CONFIRMER') | default('', true)) == 'true' }}"
tasks:
- name: Exiger de savoir ce que ce runner déclare
ansible.builtin.assert:
that:
- (serveur_ops_depots | default([])) | length > 0
fail_msg: >-
Cet hôte ne déclare aucun dépôt (`serveur_ops_depots`). Sans la liste de ce
qu'il DOIT porter, on ne peut pas dire ce qui est périmé — et surtout pas
l'effacer.
- name: Lister ce que le runner porte
ansible.builtin.find:
paths: "{{ racine }}"
file_type: directory
recurse: false
hidden: false
register: presents
- name: Retenir ceux que le plan ne déclare plus
ansible.builtin.set_fact:
perimes: >-
{{ presents.files | map(attribute='path') | map('basename') | list
| difference(serveur_ops_depots | map(attribute='dest') | list) }}
- name: Le dire quand il n'y a rien à retirer
ansible.builtin.debug:
msg: >-
Ce runner ne porte que ce qu'il déclare
({{ serveur_ops_depots | map(attribute='dest') | join(', ') }}).
when: perimes | length == 0
# ÉCRIRE, PUIS RELIRE — ici : REGARDER avant de détruire. Trois questions par dépôt, et
# la réponse décide. `git status` et `git log` ne modifient rien.
- name: Regarder ce que chaque dépôt périmé porte d'unique
ansible.builtin.shell:
cmd: |
console : un tronc, deux branches — assembler deux fois est ce qui fait deriver Il y avait deux assembleurs de charge, un par sorte de console. Ils ont derive deux fois le meme jour : en forme (serveurs valait [] d'un cote et {} de l'autre, et charger() levait), puis en contenu (cinq registres servis vides alors que le site a SON plan — 9 serveurs, 21 applications, 2 bases, 1 domaine, tous invisibles). Console assemble, une seule fois, et fixe les clefs et leurs formes. Les branches ne decident que de ce qui leur appartient : d'ou vient le plan, d'ou vient l'inventaire, quels pouvoirs elles portent. ConsoleLocataire configure, ConsoleSite materialise et sert desormais son propre plan, ConsolePoste herite du locataire et sait en plus sur quelle fabric poser. Le role et la portee ne se confondent pas : le role est une propriete de la classe, la portee se calcule depuis les pouvoirs. Un poste prive de la voute du site reste l'atelier du mainteneur et n'engendre pourtant rien. Le decoupage a montre un trou aussitot : ConsolePoste heritait du refus d'un locataire — « elle ne sait pas sur quelle fabric poser » — alors qu'il monte la carte ; ce qui lui manque est la voute, et accuser la mauvaise absence fait chercher au mauvais endroit. Le jugement des assistants remonte dans le tronc : il etait ecrit dans la route qui liste ET dans celle qui execute, et celle qui se trompe est toujours celle qui execute. Valide : make test a 0 echec, 8 tests de rendu sous node dont deux neufs, console lancee pour de vrai (13 serveurs, 24 applications, 17 runbooks, 127 etapes, 0 ecart). make verifier a aussi attrape une faute que j'avais laissee passer sur depots_perimes.yml — risky-shell-pipe, corrige. P02 et P60 restent, et P60 demande une republication du wiki. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 21:55:49 -04:00
# `pipefail` SANS `-e` : la regle `risky-shell-pipe` l'exige des qu'un `|`
# parait — ici ce sont des `||`, mais l'armer ne coute rien et dit la bonne
# intention. Pas de `-e` : un `git` qui echoue sur un dossier douteux doit
# laisser ce script REPONDRE (le depot sera juge « pas un depot git »), pas
# faire tomber la tache sans rien dire.
set -uo pipefail
depots perimes : ce qui n'existe qu'ici ne se detruit pas depuis ici serveur_ops clone ce que le plan declare et ne retire rien : le runner de Chezlepro-locataire portait encore le clone de SITE-Chezlepro, la carte de son hebergeur, longtemps apres que son plan ait cesse de la declarer. Le retrait n'entre pas dans le role. Un role qui efface des dossiers a chaque passage est une grenade degoupillee : une faute de frappe dans serveur_ops_depots suffirait a perdre du travail local. C'est donc un geste separe, qui regarde par defaut et n'efface que sur CONFIRMER=true. Trois choses ne sont jamais retirees, meme confirmees : ce qui n'est pas un depot git, ce qui porte des modifications non validees, ce qui porte des commits qu'aucun distant ne porte. La garde a servi au premier essai : le releve a nomme SITE-Chezlepro et venv, et venv a ete ecarte parce que ce n'est pas un depot git. Une version naive aurait efface l'environnement Python du runner en se disant satisfaite. Passe deux fois sur le runner reel : regarder (changed=0), puis confirmer (changed=1) avec relecture. Le runner ne porte plus que Set-OPS-public et OPS-Chezlepro ; sa console rend portee=tenant, materialiser=False, fabric=False — le pouvoir de LIRE la fabric est tombe avec la carte, et c'est juste. Valide : syntax-check du playbook, runbooks.py verifier a 0 ecart (la cible neuve est portee par le runbook Filiation), make test a 0 echec. P02 reste en echec pour la raison anterieure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:18:59 -04:00
d={{ (racine ~ '/' ~ item) | quote }}
if [ ! -d "$d/.git" ]; then echo '{"git": false}'; exit 0; fi
console : un tronc, deux branches — assembler deux fois est ce qui fait deriver Il y avait deux assembleurs de charge, un par sorte de console. Ils ont derive deux fois le meme jour : en forme (serveurs valait [] d'un cote et {} de l'autre, et charger() levait), puis en contenu (cinq registres servis vides alors que le site a SON plan — 9 serveurs, 21 applications, 2 bases, 1 domaine, tous invisibles). Console assemble, une seule fois, et fixe les clefs et leurs formes. Les branches ne decident que de ce qui leur appartient : d'ou vient le plan, d'ou vient l'inventaire, quels pouvoirs elles portent. ConsoleLocataire configure, ConsoleSite materialise et sert desormais son propre plan, ConsolePoste herite du locataire et sait en plus sur quelle fabric poser. Le role et la portee ne se confondent pas : le role est une propriete de la classe, la portee se calcule depuis les pouvoirs. Un poste prive de la voute du site reste l'atelier du mainteneur et n'engendre pourtant rien. Le decoupage a montre un trou aussitot : ConsolePoste heritait du refus d'un locataire — « elle ne sait pas sur quelle fabric poser » — alors qu'il monte la carte ; ce qui lui manque est la voute, et accuser la mauvaise absence fait chercher au mauvais endroit. Le jugement des assistants remonte dans le tronc : il etait ecrit dans la route qui liste ET dans celle qui execute, et celle qui se trompe est toujours celle qui execute. Valide : make test a 0 echec, 8 tests de rendu sous node dont deux neufs, console lancee pour de vrai (13 serveurs, 24 applications, 17 runbooks, 127 etapes, 0 ecart). make verifier a aussi attrape une faute que j'avais laissee passer sur depots_perimes.yml — risky-shell-pipe, corrige. P02 et P60 restent, et P60 demande une republication du wiki. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 21:55:49 -04:00
# AUCUN TUBE ICI, ET C'EST VOULU. `… | head -c 2000` bornait la sortie ; avec
# `pipefail` — qu'ansible-lint exige a juste titre — `head` qui ferme tot fait
# echouer la commande, et sans `pipefail` c'est l'echec de `git` qui se perd.
# On ne borne pas ce qu'on ne lit pas : seule la VACUITE de ces deux sorties
# est interrogee, jamais leur contenu.
sale=$(git -C "$d" status --porcelain 2>/dev/null)
depots perimes : ce qui n'existe qu'ici ne se detruit pas depuis ici serveur_ops clone ce que le plan declare et ne retire rien : le runner de Chezlepro-locataire portait encore le clone de SITE-Chezlepro, la carte de son hebergeur, longtemps apres que son plan ait cesse de la declarer. Le retrait n'entre pas dans le role. Un role qui efface des dossiers a chaque passage est une grenade degoupillee : une faute de frappe dans serveur_ops_depots suffirait a perdre du travail local. C'est donc un geste separe, qui regarde par defaut et n'efface que sur CONFIRMER=true. Trois choses ne sont jamais retirees, meme confirmees : ce qui n'est pas un depot git, ce qui porte des modifications non validees, ce qui porte des commits qu'aucun distant ne porte. La garde a servi au premier essai : le releve a nomme SITE-Chezlepro et venv, et venv a ete ecarte parce que ce n'est pas un depot git. Une version naive aurait efface l'environnement Python du runner en se disant satisfaite. Passe deux fois sur le runner reel : regarder (changed=0), puis confirmer (changed=1) avec relecture. Le runner ne porte plus que Set-OPS-public et OPS-Chezlepro ; sa console rend portee=tenant, materialiser=False, fabric=False — le pouvoir de LIRE la fabric est tombe avec la carte, et c'est juste. Valide : syntax-check du playbook, runbooks.py verifier a 0 ecart (la cible neuve est portee par le runbook Filiation), make test a 0 echec. P02 reste en echec pour la raison anterieure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:18:59 -04:00
# Des commits qu'AUCUN distant ne porte : ce qui n'existe qu'ici.
console : un tronc, deux branches — assembler deux fois est ce qui fait deriver Il y avait deux assembleurs de charge, un par sorte de console. Ils ont derive deux fois le meme jour : en forme (serveurs valait [] d'un cote et {} de l'autre, et charger() levait), puis en contenu (cinq registres servis vides alors que le site a SON plan — 9 serveurs, 21 applications, 2 bases, 1 domaine, tous invisibles). Console assemble, une seule fois, et fixe les clefs et leurs formes. Les branches ne decident que de ce qui leur appartient : d'ou vient le plan, d'ou vient l'inventaire, quels pouvoirs elles portent. ConsoleLocataire configure, ConsoleSite materialise et sert desormais son propre plan, ConsolePoste herite du locataire et sait en plus sur quelle fabric poser. Le role et la portee ne se confondent pas : le role est une propriete de la classe, la portee se calcule depuis les pouvoirs. Un poste prive de la voute du site reste l'atelier du mainteneur et n'engendre pourtant rien. Le decoupage a montre un trou aussitot : ConsolePoste heritait du refus d'un locataire — « elle ne sait pas sur quelle fabric poser » — alors qu'il monte la carte ; ce qui lui manque est la voute, et accuser la mauvaise absence fait chercher au mauvais endroit. Le jugement des assistants remonte dans le tronc : il etait ecrit dans la route qui liste ET dans celle qui execute, et celle qui se trompe est toujours celle qui execute. Valide : make test a 0 echec, 8 tests de rendu sous node dont deux neufs, console lancee pour de vrai (13 serveurs, 24 applications, 17 runbooks, 127 etapes, 0 ecart). make verifier a aussi attrape une faute que j'avais laissee passer sur depots_perimes.yml — risky-shell-pipe, corrige. P02 et P60 restent, et P60 demande une republication du wiki. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 21:55:49 -04:00
seuls=$(git -C "$d" log --branches --not --remotes --oneline 2>/dev/null)
depots perimes : ce qui n'existe qu'ici ne se detruit pas depuis ici serveur_ops clone ce que le plan declare et ne retire rien : le runner de Chezlepro-locataire portait encore le clone de SITE-Chezlepro, la carte de son hebergeur, longtemps apres que son plan ait cesse de la declarer. Le retrait n'entre pas dans le role. Un role qui efface des dossiers a chaque passage est une grenade degoupillee : une faute de frappe dans serveur_ops_depots suffirait a perdre du travail local. C'est donc un geste separe, qui regarde par defaut et n'efface que sur CONFIRMER=true. Trois choses ne sont jamais retirees, meme confirmees : ce qui n'est pas un depot git, ce qui porte des modifications non validees, ce qui porte des commits qu'aucun distant ne porte. La garde a servi au premier essai : le releve a nomme SITE-Chezlepro et venv, et venv a ete ecarte parce que ce n'est pas un depot git. Une version naive aurait efface l'environnement Python du runner en se disant satisfaite. Passe deux fois sur le runner reel : regarder (changed=0), puis confirmer (changed=1) avec relecture. Le runner ne porte plus que Set-OPS-public et OPS-Chezlepro ; sa console rend portee=tenant, materialiser=False, fabric=False — le pouvoir de LIRE la fabric est tombe avec la carte, et c'est juste. Valide : syntax-check du playbook, runbooks.py verifier a 0 ecart (la cible neuve est portee par le runbook Filiation), make test a 0 echec. P02 reste en echec pour la raison anterieure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:18:59 -04:00
printf '{"git": true, "sale": %s, "seuls": %s}\n' \
"$([ -n "$sale" ] && echo true || echo false)" \
"$([ -n "$seuls" ] && echo true || echo false)"
executable: /bin/bash
loop: "{{ perimes }}"
register: examen
changed_when: false
- name: Nommer ce qui est retenu, et pourquoi
ansible.builtin.debug:
msg: >-
{{ item.item }} : {{
'ce n_est pas un dépôt git — on ne sait pas ce que c_est, on n_y touche pas'
if not (item.stdout | from_json).git else
'des modifications non validées vivent ici' if (item.stdout | from_json).sale else
'des commits qu_aucun distant ne porte vivent ici' if (item.stdout | from_json).seuls else
'ne porte rien d_unique — retirable'
}}
loop: "{{ examen.results }}"
loop_control:
label: "{{ item.item }}"
- name: Retenir ceux qu'on peut retirer sans rien perdre
ansible.builtin.set_fact:
retirables: >-
{{ examen.results
| selectattr('stdout')
| selectattr('stdout', 'search', '"git": true')
| rejectattr('stdout', 'search', '"sale": true')
| rejectattr('stdout', 'search', '"seuls": true')
| map(attribute='item') | list }}
- name: Dire ce qu'une confirmation retirerait
ansible.builtin.debug:
msg: >-
{{ retirables | length }} dépôt(s) retirable(s) : {{ retirables | join(', ') }}.
Rien n'a été touché — relancer avec CONFIRMER=true pour les retirer.
when:
- retirables | length > 0
- not (confirme | bool)
- name: Retirer les dépôts que ce runner ne déclare plus
ansible.builtin.file:
path: "{{ racine }}/{{ item }}"
state: absent
loop: "{{ retirables }}"
when:
- confirme | bool
- not ansible_check_mode
# RELIRE APRÈS AVOIR ÉCRIT. Un `state: absent` qui se dit satisfait sans que le
# dossier ait disparu est exactement le genre de succès qui ment.
- name: Vérifier qu'ils ont bien disparu
ansible.builtin.stat:
path: "{{ racine }}/{{ item }}"
loop: "{{ retirables }}"
register: apres
when: confirme | bool
- name: Refuser un retrait qui n'a pas eu lieu
ansible.builtin.assert:
that:
- not (item.stat.exists | default(false))
fail_msg: "{{ item.item }} est toujours là après le retrait."
success_msg: "{{ item.item }} : retiré."
loop: "{{ apres.results | default([]) }}"
loop_control:
label: "{{ item.item | default('') }}"
when: confirme | bool