En --check, la sonde de l'organisation (une lecture uri) etait sautee et la tache suivante echouait sur un statut absent, cache par no_log. La suite (colis, reception, poussee) est faite de command et d'ecritures que --check saute aussi : le verdict n'aurait rien prouve. Les deux sondes de la forge tournent desormais en simulation. En --check, le playbook lit la branche de la forge, la compare au poste (rev-list --left-right), nomme pour chaque depot ce que la poussee ferait, et s'arrete avant la premiere ecriture. Temoin : quatre cas nommes contre des clones jetables ; a jour partout contre les vrais depots, aux deux sites. CHANGELOG 150. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
532 lines
24 KiB
YAML
532 lines
24 KiB
YAML
---
|
|
# LE RUNNER DU SITE POUSSE LE GENOME SUR LA FORGE DU SITE.
|
|
#
|
|
# POURQUOI CE DETOUR, ET POURQUOI IL EST BON (2026-08-26).
|
|
#
|
|
# La forge du genome vit DANS le site, sur un reseau que le poste de l'exploitant ne route
|
|
# pas — et c'est voulu : le poste n'a de patte que sur l'administration, la frontiere
|
|
# police le reste. Il ne peut donc pas pousser lui-meme.
|
|
#
|
|
# On ne perce pas un chemin pour lui. Le RUNNER existe exactement pour ca : il est dans le
|
|
# site, il a le genome, il a une identite. On lui PORTE les commits dans un `git bundle` —
|
|
# un fichier, verifiable, qui ne demande aucun reseau — et c'est LUI qui pousse.
|
|
#
|
|
# Mesure du 2026-08-26 : la forge du site etait restee quatre commits en arriere du poste,
|
|
# sans que rien ne le signale. Le runner clonait donc un moteur perime — dont, ce jour-la,
|
|
# le correctif qui desarme le pare-feu Proxmox. Un ecosysteme qui se reproduit depuis une
|
|
# forge en retard reproduit ses defauts.
|
|
#
|
|
# CE QUI EST POUSSE SE DERIVE, il ne s'ecrit pas : `serveur_ops_depots` (au plan du site)
|
|
# dit ce que le runner porte. On pousse ceux dont une copie locale existe a cote du
|
|
# moteur, et rien d'autre.
|
|
#
|
|
# make genome-pousser tous les depots portes par le runner
|
|
# make genome-pousser DEPOT=<nom> un seul (le nom du depot sur la forge)
|
|
# make genome-pousser ARGS=--check ce que la poussee ferait, depot par depot, sans rien ecrire
|
|
#
|
|
# L'ECRITURE S'AUTHENTIFIE, LA LECTURE NON. `serveur_ops` clone en anonyme, par doctrine.
|
|
# Pousser est un autre acte : le runner s'y presente avec SA cle — celle que `serveur_ops`
|
|
# lui a generee — declaree en cle de depot, en ecriture, sur ces depots-la seulement.
|
|
# `url` reste en HTTPS anonyme ; seul `pushurl` passe par SSH.
|
|
|
|
- name: Le runner du site pousse le genome sur la forge du site
|
|
hosts: serveur_ops_site
|
|
become: true
|
|
become_user: "{{ serveur_ops_utilisateur | default('setops') }}"
|
|
gather_facts: false
|
|
|
|
vars:
|
|
genome_racine: "{{ serveur_ops_racine | default('/opt/setops') }}"
|
|
genome_depot_demande: "{{ lookup('env', 'SETOPS_DEPOT') | default('', true) }}"
|
|
# Le dossier qui contient le moteur ET ses depots freres, sur le CONTROLEUR.
|
|
genome_freres: "{{ playbook_dir | dirname | dirname | dirname }}"
|
|
# Le dossier du moteur, tel que le plan le nomme — jamais ecrit en dur.
|
|
genome_moteur: "{{ (serveur_ops_depots | selectattr('role', 'eq', 'moteur') | first).dest }}"
|
|
|
|
tasks:
|
|
- name: Exiger de savoir quoi pousser
|
|
ansible.builtin.assert:
|
|
that:
|
|
- (serveur_ops_depots | default([])) | length > 0
|
|
fail_msg: >-
|
|
Ce poste ne déclare aucun dépôt (`serveur_ops_depots`). Rien à pousser — et
|
|
probablement rien à piloter non plus : voir le plan de cette machine.
|
|
|
|
# Ce que le runner porte, restreint a ce dont une copie existe sur le controleur.
|
|
# Un depot declare mais absent d'ici n'est pas une faute : il vit ailleurs.
|
|
- name: Voir lesquels de ces dépôts existent sur le contrôleur
|
|
ansible.builtin.stat:
|
|
path: "{{ genome_freres }}/{{ item.dest }}/.git"
|
|
delegate_to: localhost
|
|
become: false
|
|
loop: "{{ serveur_ops_depots }}"
|
|
loop_control:
|
|
label: "{{ item.dest }}"
|
|
register: genome_locaux
|
|
|
|
- name: Retenir ceux qu'on peut effectivement porter
|
|
ansible.builtin.set_fact:
|
|
genome_a_pousser: >-
|
|
{{ genome_locaux.results
|
|
| selectattr('stat.exists')
|
|
| map(attribute='item') | list
|
|
| selectattr('depot', 'equalto', genome_depot_demande)
|
|
| list
|
|
if genome_depot_demande | length > 0
|
|
else genome_locaux.results | selectattr('stat.exists')
|
|
| map(attribute='item') | list }}
|
|
|
|
- name: Dire ce qui va être poussé
|
|
ansible.builtin.debug:
|
|
msg: >-
|
|
{{ genome_a_pousser | map(attribute='depot') | list
|
|
| ternary(genome_a_pousser | map(attribute='depot') | list,
|
|
'aucun dépôt à pousser') }}
|
|
|
|
- name: Refuser un DEPOT= qui ne désigne rien
|
|
ansible.builtin.assert:
|
|
that: genome_a_pousser | length > 0
|
|
fail_msg: >-
|
|
Aucun dépôt à pousser. {{ 'Le dépôt `' ~ genome_depot_demande ~ '` n est pas
|
|
porté par ce runner, ou n a pas de copie locale à côté du moteur.'
|
|
if genome_depot_demande | length > 0 else
|
|
'Aucun des dépôts déclarés n a de copie locale à côté du moteur.' }}
|
|
|
|
# LA CLE DU RUNNER DOIT ETRE AUTORISEE SUR CE QU'IL POUSSE — sinon la forge refuse,
|
|
# depot par depot, et l'exploitant doit aller cliquer. La cible s'en charge : la
|
|
# declaration est idempotente (422 = deja la), et sa portee est exactement ce qu'on
|
|
# pousse, jamais plus large.
|
|
# UNE FORGE NEUVE, UN RUNNER NEUF (2026-10-13, premier site construit depuis zero). Le
|
|
# runner du site ne fabrique sa cle qu'APRES avoir clone le genome, et il ne peut cloner
|
|
# que ce que cette commande a pousse — qui exige sa cle. Personne n'allait au bout. On
|
|
# fabrique donc la cle ici si elle manque, A L'IDENTIQUE de `serveur_ops` (meme commande,
|
|
# meme commentaire, `creates:`) : le role la retrouvera et ne la refera pas.
|
|
- name: Assurer le répertoire SSH du runner
|
|
ansible.builtin.file:
|
|
path: "{{ genome_racine }}/.ssh"
|
|
state: directory
|
|
mode: "0700"
|
|
|
|
- name: Fabriquer la clé du runner si elle n'existe pas encore
|
|
ansible.builtin.command:
|
|
cmd: >-
|
|
ssh-keygen -t {{ serveur_ops_cle_type | default('ed25519') }} -N ''
|
|
-C "setops@{{ inventory_hostname }}.{{ domaine_interne }}"
|
|
-f {{ genome_racine }}/.ssh/id_{{ serveur_ops_cle_type | default('ed25519') }}
|
|
creates: "{{ genome_racine }}/.ssh/id_{{ serveur_ops_cle_type | default('ed25519') }}"
|
|
|
|
# L'ORGANISATION ET SES DEPOTS N'ETAIENT CREES PAR PERSONNE : au site de Chezlepro, une
|
|
# fois, a la main. Meme forme qu'elle : organisation PUBLIQUE ; le moteur et les modeles
|
|
# PUBLICS (le runner les clone en anonyme) ; la carte du site et les plans des
|
|
# locataires PRIVES (`serveur_ops_roles_prives`, lus en SSH par cle de deploiement).
|
|
- name: L'organisation du génome existe-t-elle ?
|
|
ansible.builtin.uri:
|
|
url: "https://forge.{{ domaine_interne }}/api/v1/orgs/{{ serveur_ops_forge_organisation | default('genome') }}"
|
|
validate_certs: false
|
|
status_code: [200, 404]
|
|
# UNE LECTURE, DONC AUSSI EN SIMULATION : sans elle, `--check` sautait la sonde et la
|
|
# tache suivante echouait sur un statut absent (2026-10-14).
|
|
check_mode: false
|
|
register: genome_org
|
|
|
|
- name: Créer l'organisation du génome
|
|
ansible.builtin.uri:
|
|
url: "https://forge.{{ domaine_interne }}/api/v1/orgs"
|
|
method: POST
|
|
url_username: forgejo-admin
|
|
url_password: "{{ vault_forgejo_admin }}"
|
|
force_basic_auth: true
|
|
validate_certs: false
|
|
body_format: json
|
|
body:
|
|
username: "{{ serveur_ops_forge_organisation | default('genome') }}"
|
|
visibility: public
|
|
status_code: 201
|
|
no_log: true
|
|
when: genome_org.status == 404
|
|
|
|
- name: Quels dépôts poussés existent déjà sur la forge ?
|
|
ansible.builtin.uri:
|
|
url: "https://forge.{{ domaine_interne }}/api/v1/repos/{{ serveur_ops_forge_organisation | default('genome') }}/{{ item.depot }}"
|
|
url_username: forgejo-admin
|
|
url_password: "{{ vault_forgejo_admin }}"
|
|
force_basic_auth: true
|
|
validate_certs: false
|
|
status_code: [200, 404]
|
|
loop: "{{ genome_a_pousser }}"
|
|
loop_control:
|
|
label: "{{ item.depot }}"
|
|
no_log: true
|
|
check_mode: false
|
|
register: genome_depots_forge
|
|
|
|
# LA SIMULATION DIT CE QUE LA POUSSEE FERAIT, PUIS S'ARRETE (2026-10-14).
|
|
#
|
|
# Tout ce qui suit est `uri` en ecriture ou `command` : `--check` le saute, et le verdict
|
|
# final comparait alors une forge intacte a des colis jamais recus. On ne lit donc que la
|
|
# forge et le poste, on compare, et on rend la main avant la premiere ecriture.
|
|
- name: Simulation — la branche que porte la forge
|
|
ansible.builtin.uri:
|
|
url: >-
|
|
https://forge.{{ domaine_interne }}/api/v1/repos/{{ serveur_ops_forge_organisation
|
|
| default('genome') }}/{{ item.item.depot }}/branches/{{ item.item.branche | default('main') }}
|
|
url_username: forgejo-admin
|
|
url_password: "{{ vault_forgejo_admin }}"
|
|
force_basic_auth: true
|
|
validate_certs: false
|
|
status_code: [200, 404]
|
|
loop: "{{ genome_depots_forge.results }}"
|
|
loop_control:
|
|
label: "{{ item.item.depot }}"
|
|
register: genome_sim_forge
|
|
retries: 4
|
|
delay: 3
|
|
until: genome_sim_forge is succeeded
|
|
check_mode: false
|
|
no_log: true
|
|
when:
|
|
- ansible_check_mode
|
|
- item.status == 200
|
|
|
|
# `--left-right` : a gauche ce que la forge porte et que le poste n'a pas (le runner
|
|
# refusera, `--ff-only`), a droite ce qui serait pousse. rc 128 : le poste ne connait
|
|
# pas le commit de la forge — l'avance rapide est impossible a juger d'ici.
|
|
- name: Simulation — l'écart entre la forge et le poste
|
|
ansible.builtin.command:
|
|
argv:
|
|
- git
|
|
- -C
|
|
- "{{ genome_freres }}/{{ item.item.item.dest }}"
|
|
- rev-list
|
|
- --count
|
|
- --left-right
|
|
- "{{ item.json.commit.id }}...refs/heads/{{ item.item.item.branche | default('main') }}"
|
|
delegate_to: localhost
|
|
become: false
|
|
loop: "{{ genome_sim_forge.results }}"
|
|
loop_control:
|
|
label: "{{ item.item.item.depot }}"
|
|
register: genome_sim_ecart
|
|
check_mode: false
|
|
changed_when: false
|
|
failed_when: false
|
|
when:
|
|
- ansible_check_mode
|
|
- item.status | default(0) == 200
|
|
|
|
- name: Simulation — ce que la poussée ferait
|
|
vars:
|
|
genome_sim_lignes: >-
|
|
{%- set out = [] -%}
|
|
{%- if genome_org.status == 404 -%}
|
|
{%- set _ = out.append('organisation ' ~ (serveur_ops_forge_organisation | default('genome')) ~ ' : à créer') -%}
|
|
{%- endif -%}
|
|
{%- for r in genome_sim_ecart.results -%}
|
|
{%- set d = r.item.item.item.depot -%}
|
|
{%- if r.item.item.status == 404 -%}
|
|
{%- set _ = out.append(d ~ ' : dépôt à créer, puis tout poussé') -%}
|
|
{%- elif r.item.status | default(0) == 404 -%}
|
|
{%- set _ = out.append(d ~ ' : dépôt vide, tout poussé') -%}
|
|
{%- elif r.rc != 0 -%}
|
|
{%- set _ = out.append(d ~ ' : la forge porte ' ~ r.item.json.commit.id[:7]
|
|
~ ', inconnu du poste — avance rapide impossible à juger d ici') -%}
|
|
{%- else -%}
|
|
{%- set lr = r.stdout.split() | map('int') | list -%}
|
|
{%- if lr[0] > 0 -%}
|
|
{%- set _ = out.append(d ~ ' : la forge a ' ~ lr[0] ~ ' commit(s) absent(s) du poste — le runner REFUSERA (--ff-only)') -%}
|
|
{%- elif lr[1] == 0 -%}
|
|
{%- set _ = out.append(d ~ ' : à jour (' ~ r.item.json.commit.id[:7] ~ ')') -%}
|
|
{%- else -%}
|
|
{%- set _ = out.append(d ~ ' : ' ~ r.item.json.commit.id[:7] ~ ' → ' ~ lr[1] ~ ' commit(s) en avance rapide') -%}
|
|
{%- endif -%}
|
|
{%- endif -%}
|
|
{%- endfor -%}
|
|
{{ out }}
|
|
ansible.builtin.debug:
|
|
msg: "{{ genome_sim_lignes }}"
|
|
when: ansible_check_mode
|
|
|
|
- name: Simulation — s'arrêter avant la première écriture
|
|
ansible.builtin.meta: end_host
|
|
when: ansible_check_mode
|
|
|
|
- name: Créer les dépôts qui manquent
|
|
ansible.builtin.uri:
|
|
url: "https://forge.{{ domaine_interne }}/api/v1/orgs/{{ serveur_ops_forge_organisation | default('genome') }}/repos"
|
|
method: POST
|
|
url_username: forgejo-admin
|
|
url_password: "{{ vault_forgejo_admin }}"
|
|
force_basic_auth: true
|
|
validate_certs: false
|
|
body_format: json
|
|
body:
|
|
name: "{{ item.item.depot }}"
|
|
private: "{{ item.item.role in (serveur_ops_roles_prives | default(['instance', 'tenant', 'hebergeur'])) }}"
|
|
default_branch: "{{ item.item.branche | default('main') }}"
|
|
auto_init: false
|
|
status_code: 201
|
|
loop: "{{ genome_depots_forge.results }}"
|
|
loop_control:
|
|
label: "{{ item.item.depot }}"
|
|
no_log: true
|
|
when: item.status == 404
|
|
|
|
- name: Lire la clé publique du runner
|
|
ansible.builtin.slurp:
|
|
src: "{{ genome_racine }}/.ssh/id_{{ serveur_ops_cle_type | default('ed25519') }}.pub"
|
|
register: genome_cle
|
|
|
|
- name: Autoriser cette clé en écriture sur les dépôts poussés
|
|
ansible.builtin.uri:
|
|
url: >-
|
|
https://forge.{{ domaine_interne }}/api/v1/repos/{{ serveur_ops_forge_organisation
|
|
| default('genome') }}/{{ item.depot }}/keys
|
|
method: POST
|
|
url_username: forgejo-admin
|
|
url_password: "{{ vault_forgejo_admin }}"
|
|
force_basic_auth: true
|
|
validate_certs: false
|
|
body_format: json
|
|
body:
|
|
title: "runner du site — {{ inventory_hostname }}"
|
|
key: "{{ genome_cle.content | b64decode | trim }}"
|
|
read_only: false
|
|
status_code: [201, 422]
|
|
loop: "{{ genome_a_pousser }}"
|
|
loop_control:
|
|
label: "{{ item.depot }}"
|
|
register: genome_cles
|
|
changed_when: false
|
|
no_log: true
|
|
|
|
- name: Un endroit pour fabriquer les colis, sur le contrôleur
|
|
ansible.builtin.tempfile:
|
|
state: directory
|
|
suffix: -genome
|
|
delegate_to: localhost
|
|
become: false
|
|
register: genome_atelier
|
|
|
|
- name: Un endroit pour déposer les colis
|
|
ansible.builtin.file:
|
|
path: "{{ genome_racine }}/.bundles"
|
|
state: directory
|
|
owner: "{{ serveur_ops_utilisateur | default('setops') }}"
|
|
group: "{{ serveur_ops_utilisateur | default('setops') }}"
|
|
mode: "0700"
|
|
|
|
# LA LOGIQUE GIT VIT DANS `scripts/genome_colis.py`, comme partout ici : le playbook
|
|
# orchestre, le script fait. Il voyage AVEC le moteur — le runner en a donc une copie
|
|
# dès qu'il l'a cloné, et rien de spécial n'est déposé sur la machine.
|
|
- name: Emballer chaque dépôt sur le contrôleur
|
|
# `argv` ET NON `cmd` : le dossier des dépôts frères peut contenir des espaces —
|
|
# c'est le cas ici (« Espace Chezlepro »). Une chaîne `cmd` est découpée sur les
|
|
# blancs, et python recevait « /home/…/Espace » comme nom de fichier.
|
|
ansible.builtin.command:
|
|
argv:
|
|
- python3
|
|
- "{{ genome_freres }}/{{ genome_moteur }}/scripts/genome_colis.py"
|
|
- emballer
|
|
- "{{ genome_freres }}/{{ item.dest }}"
|
|
- "{{ genome_atelier.path }}/{{ item.depot }}.bundle"
|
|
- --branche
|
|
- "{{ item.branche | default('main') }}"
|
|
delegate_to: localhost
|
|
become: false
|
|
loop: "{{ genome_a_pousser }}"
|
|
loop_control:
|
|
label: "{{ item.dest }}"
|
|
register: genome_emballe
|
|
changed_when: true
|
|
|
|
- name: Porter les colis jusqu'au runner
|
|
ansible.builtin.copy:
|
|
src: "{{ genome_atelier.path }}/{{ item.depot }}.bundle"
|
|
dest: "{{ genome_racine }}/.bundles/{{ item.depot }}.bundle"
|
|
owner: "{{ serveur_ops_utilisateur | default('setops') }}"
|
|
group: "{{ serveur_ops_utilisateur | default('setops') }}"
|
|
mode: "0600"
|
|
loop: "{{ genome_a_pousser }}"
|
|
loop_control:
|
|
label: "{{ item.depot }}"
|
|
|
|
# L'OUTIL VOYAGE AVEC LE COLIS, il n'est pas supposé déjà là (2026-08-26).
|
|
#
|
|
# Première version : on lançait `scripts/genome_colis.py` depuis le moteur cloné sur
|
|
# le runner. Circulaire — le runner ne reçoit cette version du script qu'APRÈS la
|
|
# poussée qu'elle sert à faire. Et ça se reproduirait à chaque évolution de l'outil :
|
|
# le runner en retard ne saurait jamais rattraper son retard.
|
|
#
|
|
# C'est le contrôleur qui détient la version qui fait autorité. Il la porte.
|
|
- name: Porter l'outil lui-même jusqu'au runner
|
|
ansible.builtin.copy:
|
|
src: "{{ genome_freres }}/{{ genome_moteur }}/scripts/genome_colis.py"
|
|
dest: "{{ genome_racine }}/.bundles/genome_colis.py"
|
|
owner: "{{ serveur_ops_utilisateur | default('setops') }}"
|
|
group: "{{ serveur_ops_utilisateur | default('setops') }}"
|
|
mode: "0700"
|
|
|
|
- name: Le runner vérifie, avance en avance rapide, et pousse
|
|
ansible.builtin.command:
|
|
argv:
|
|
- python3
|
|
- "{{ genome_racine }}/.bundles/genome_colis.py"
|
|
- recevoir
|
|
- "{{ genome_racine }}/{{ item.dest }}"
|
|
- "{{ genome_racine }}/.bundles/{{ item.depot }}.bundle"
|
|
- "git@forge.{{ domaine_interne }}:{{ serveur_ops_forge_organisation | default('genome') }}/{{ item.depot }}.git"
|
|
- --cle
|
|
- "{{ genome_racine }}/.ssh/id_{{ serveur_ops_cle_type | default('ed25519') }}"
|
|
- --branche
|
|
- "{{ item.branche | default('main') }}"
|
|
loop: "{{ genome_a_pousser }}"
|
|
loop_control:
|
|
label: "{{ item.depot }}"
|
|
register: genome_recu
|
|
# `changed_when: false` ET C'EST VOULU. Dans une boucle, ni le registre (pas encore
|
|
# defini) ni `stdout` nu ne sont lisibles ici — les deux ont ete essayes, les deux
|
|
# ont leve. Plutot qu'une condition qui ment ou qui casse, on ne pretend rien : ce
|
|
# qui a change est dit par le VERDICT, plus bas, qui compare la forge au runner et
|
|
# nomme la transition. La verite vient de la forge, pas d'un drapeau de tache.
|
|
changed_when: false
|
|
|
|
- name: Demander à la forge ce qu'elle porte maintenant
|
|
# On interroge son API plutôt que `git ls-remote` : c'est la forge elle-même qui
|
|
# répond. Lecture authentifiée depuis le 2026-10-11 (dépôts privés).
|
|
ansible.builtin.uri:
|
|
url: >-
|
|
https://forge.{{ domaine_interne }}/api/v1/repos/{{ serveur_ops_forge_organisation
|
|
| default('genome') }}/{{ item.depot }}/branches/{{ item.branche | default('main') }}
|
|
# AUTHENTIFIE (2026-10-11) : les depots des locataires et du site deviennent prives, et
|
|
# l'API anonyme y repondrait 404 — un « absent » qui n'en serait pas un.
|
|
url_username: forgejo-admin
|
|
url_password: "{{ vault_forgejo_admin }}"
|
|
force_basic_auth: true
|
|
validate_certs: false
|
|
status_code: [200]
|
|
loop: "{{ genome_a_pousser }}"
|
|
loop_control:
|
|
label: "{{ item.depot }}"
|
|
register: genome_forge
|
|
# LE PREMIER CONTACT AVEC LA FORGE RATE PARFOIS (mesure du 2026-08-26 : une fois sur
|
|
# six, poignee TLS expiree, puis cinq reponses de suite). Ce n'est pas le chemin —
|
|
# il est prouve — c'est l'acceptation TLS apres un temps d'inactivite. On reessaie
|
|
# plutot que de rendre un verdict sur un alea.
|
|
retries: 4
|
|
delay: 3
|
|
until: genome_forge is succeeded
|
|
changed_when: false
|
|
|
|
- name: La forge porte-t-elle exactement ce que le runner porte ?
|
|
ansible.builtin.assert:
|
|
that:
|
|
- genome_forge.results[idx].json.commit.id
|
|
== (genome_recu.results[idx].stdout | from_json).apres
|
|
fail_msg: >-
|
|
{{ item.depot }} : la forge porte
|
|
{{ genome_forge.results[idx].json.commit.id[:8] | default('rien') }} alors que
|
|
le runner porte
|
|
{{ (genome_recu.results[idx].stdout | from_json).apres[:8] }}. La poussée n'a pas
|
|
abouti — ne pas cloner depuis cette forge tant que ce n'est pas réglé.
|
|
success_msg: >-
|
|
{{ item.depot }} :
|
|
{{ (genome_recu.results[idx].stdout | from_json).avant[:7] }} →
|
|
{{ (genome_recu.results[idx].stdout | from_json).apres[:7] }}, et la forge le porte.
|
|
loop: "{{ genome_a_pousser }}"
|
|
loop_control:
|
|
label: "{{ item.depot }}"
|
|
index_var: idx
|
|
|
|
# LES RELEASES AUSSI (2026-10-07). Le colis ne portait que la branche : `v2026.08.21` et
|
|
# `v2026.10.07` manquaient a la forge du site. Il porte desormais les etiquettes ; on
|
|
# demande a la forge si elle a TOUTES celles du poste. 404 = aucune etiquette sur ce
|
|
# depot, ce qui est juste quand le poste n'en a pas non plus.
|
|
- name: Demander à la forge les étiquettes qu'elle porte
|
|
ansible.builtin.uri:
|
|
url: >-
|
|
https://forge.{{ domaine_interne }}/api/v1/repos/{{ serveur_ops_forge_organisation
|
|
| default('genome') }}/{{ item.depot }}/git/refs/tags
|
|
# AUTHENTIFIE (2026-10-11) : les depots des locataires et du site deviennent prives, et
|
|
# l'API anonyme y repondrait 404 — un « absent » qui n'en serait pas un.
|
|
url_username: forgejo-admin
|
|
url_password: "{{ vault_forgejo_admin }}"
|
|
force_basic_auth: true
|
|
validate_certs: false
|
|
status_code: [200, 404]
|
|
loop: "{{ genome_a_pousser }}"
|
|
loop_control:
|
|
label: "{{ item.depot }}"
|
|
register: genome_forge_etiquettes
|
|
retries: 4
|
|
delay: 3
|
|
until: genome_forge_etiquettes is succeeded
|
|
changed_when: false
|
|
|
|
- name: La forge porte-t-elle toutes les releases du poste ?
|
|
vars:
|
|
genome_etiquettes_poste: "{{ (genome_emballe.results[idx].stdout | from_json).etiquettes | default([]) }}"
|
|
genome_etiquettes_forge: >-
|
|
{{ (genome_forge_etiquettes.results[idx].json | default([]) if genome_forge_etiquettes.results[idx].status == 200 else [])
|
|
| map(attribute='ref') | map('regex_replace', '^refs/tags/', '') | list }}
|
|
ansible.builtin.assert:
|
|
that:
|
|
- genome_etiquettes_poste | difference(genome_etiquettes_forge) | length == 0
|
|
fail_msg: >-
|
|
{{ item.depot }} : la forge n'a pas
|
|
{{ genome_etiquettes_poste | difference(genome_etiquettes_forge) | join(', ') }}.
|
|
success_msg: >-
|
|
{{ item.depot }} : {{ genome_etiquettes_poste | length }} etiquette(s) du poste, toutes sur la forge.
|
|
loop: "{{ genome_a_pousser }}"
|
|
loop_control:
|
|
label: "{{ item.depot }}"
|
|
index_var: idx
|
|
|
|
# LE RUNNER DU SITE AVANCE SON PROPRE CLONE, ET SA CONSOLE NE LE SAIT PAS (2026-09-20).
|
|
#
|
|
# C'est LUI qui pousse, donc il doit d'abord recevoir : ce playbook fait avancer le
|
|
# moteur du site sans qu'aucun role ne passe. `serveur_ops` redemarre bien la console
|
|
# quand le clone avance, mais il ne verra jamais cet avancement-ci — le clone sera
|
|
# deja a jour quand il passera. Le site garderait donc une console servant l'ancien
|
|
# code, exactement le defaut corrige le meme jour chez les locataires.
|
|
#
|
|
# `python3 scripts/inventory_gui.py` lit son code au demarrage, et seulement la. Ce
|
|
# qui a avance doit etre relu, et personne d'autre que ce playbook ne sait qu'il a
|
|
# avance : le verdict ci-dessus nomme la transition, il suffit de l'ecouter.
|
|
- name: Retenir ce que le colis du moteur a fait bouger
|
|
ansible.builtin.set_fact:
|
|
genome_moteur_colis: >-
|
|
{{ (genome_recu.results | default([]))
|
|
| selectattr('item.dest', 'equalto', genome_moteur)
|
|
| map(attribute='stdout') | list }}
|
|
|
|
- name: Voir si ce runner sert une console
|
|
ansible.builtin.stat:
|
|
path: /etc/systemd/system/setops-gui.service
|
|
register: genome_console_unite
|
|
|
|
- name: Redémarrer la console du site quand son moteur a avancé
|
|
ansible.builtin.systemd:
|
|
name: setops-gui.service
|
|
state: restarted
|
|
daemon_reload: true
|
|
become: true
|
|
become_user: root
|
|
when:
|
|
- genome_console_unite.stat.exists
|
|
- genome_moteur_colis | length > 0
|
|
- (genome_moteur_colis[0] | from_json).avant
|
|
!= (genome_moteur_colis[0] | from_json).apres
|
|
|
|
# Le genome n'a pas a trainer en double sur la machine : ce qui compte est dans les
|
|
# depots et sur la forge.
|
|
- name: Retirer les colis
|
|
ansible.builtin.file:
|
|
path: "{{ genome_racine }}/.bundles"
|
|
state: absent
|
|
|
|
- name: Retirer l'atelier du contrôleur
|
|
ansible.builtin.file:
|
|
path: "{{ genome_atelier.path }}"
|
|
state: absent
|
|
delegate_to: localhost
|
|
become: false
|