Set-OPS-Public/roles/serveur_artefacts/tasks/main.yml

247 lines
11 KiB
YAML
Raw Permalink Normal View History

---
# LE CACHE NE DOIT PAS DÉPENDRE DE LUI-MÊME POUR SE RÉPARER.
#
# La première version faisait `update_cache: true` à chaque passage. Sur l'hôte qui PORTE
# le cache, cet `apt update` passe par le cache lui-même — et en mode hors ligne, il est
# refusé. Le rôle qui devait remettre le service en ligne ne pouvait donc plus s'exécuter :
#
# « Failed to update apt cache after 5 retries » (mesuré le 2026-08-23, après
# l'épreuve hors ligne : il a fallu réparer la machine à la main)
#
# Un service dont la remise en route exige qu'il fonctionne déjà est un piège. On ne
# rafraîchit donc l'index qu'à la PREMIÈRE installation, quand aucun cache n'existe encore
# et qu'`apt` va forcément à l'amont.
- name: Le cache est-il déjà installé ?
ansible.builtin.stat:
path: /usr/sbin/apt-cacher-ng
register: serveur_artefacts_present
- name: Installer le cache d'artefacts
ansible.builtin.apt:
name: "{{ serveur_artefacts_paquets }}"
state: present
update_cache: "{{ not serveur_artefacts_present.stat.exists }}"
when: not ansible_check_mode
- name: Assurer le répertoire de cache
ansible.builtin.file:
path: "{{ serveur_artefacts_cache }}"
state: directory
owner: apt-cacher-ng
group: apt-cacher-ng
mode: "0755"
when: not ansible_check_mode
# apt-cacher-ng lit TOUS les `*.conf` de son répertoire, par ordre alphabétique, et le
# dernier lu gagne. Le préfixe `zzz-` place donc notre configuration après celle du
# paquet Debian et celle de debconf, sans avoir à les éditer — on ajoute, on ne remplace
# pas. Une valeur posée ici l'emporte, et `apt purge` rend la machine à son état d'origine.
artefacts : deux filets — un cache mort ne doit plus rendre un tenant irreparable CE CACHE EST UN MAILLON DONT DEPEND SA PROPRE RECONSTRUCTION. Toute la flotte y prend ses paquets. S il pointe vers un amont mort, apt echoue sur les quinze machines, donc le socle echoue, donc le deploiement n atteint jamais la couche qui poserait le bon amont. LE CORRECTIF SE RETROUVE DANS LA COUCHE QUE LA PANNE EMPECHE D ATTEINDRE, et il faut une main pour en sortir. C est arrive le 2026-08-31 : l amont pointait sur le cache de patient 0, eteint la veille pour liberer de la RAM. apt rendait « 503 Connection timeout » EN CITANT L ADRESSE DU CACHE LOCAL, jamais celle de l amont manquant — la panne accusait le maillon visible. DEUX FILETS, SYMETRIQUES. serveur_artefacts sonde l amont AVANT d ecrire, et refuse s il est muet : mieux vaut un cache qui garde sa configuration precedente qu un cache qu on vient de rendre inutilisable pour toute la flotte. client_artefacts sonde la source AVANT de detourner apt vers elle. Ce fichier ECRASE le plancher d amorcage pose par le socle — le poser sur un cache mort prive la machine du cache du SITE, qui lui fonctionnait. En refusant, le plancher reste : la flotte est degradee mais debout, et REPARABLE PAR UN DEPLOIEMENT. `ignore_errors` ET NON `failed_when: false` — la difference est toute la garde. failed_when: false REECRIT le verdict : la tache n est plus jamais failed, donc `is not failed` est toujours vrai, donc l assertion ne peut pas tirer. Ecrite ainsi, ma premiere version a declare « joignable » un amont mesure MUET la seconde d avant. Une garde qui ne peut pas echouer ne garde rien. Deux controles verifies sur la machine : amont eteint -> refuse ; amont reel -> pose. make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec, 0 saute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-31 18:31:21 -04:00
# --- LE FILET : NE PAS POSER UN AMONT INJOIGNABLE ----------------------------
#
# CE CACHE EST UN MAILLON DONT DEPEND SA PROPRE RECONSTRUCTION (mesure du 2026-08-31).
#
# Toute la flotte du tenant prend ses paquets ici. Si ce cache pointe vers un amont mort,
# `apt` echoue sur les QUINZE machines — donc le socle echoue, donc le deploiement
# n'atteint jamais la couche qui poserait le bon amont. Le correctif se retrouve dans la
# couche que la panne empeche d'atteindre, et il faut une main pour en sortir.
#
# C'est exactement ce qui est arrive : l'amont pointait sur le cache d'un autre ecosysteme, eteint
artefacts : deux filets — un cache mort ne doit plus rendre un tenant irreparable CE CACHE EST UN MAILLON DONT DEPEND SA PROPRE RECONSTRUCTION. Toute la flotte y prend ses paquets. S il pointe vers un amont mort, apt echoue sur les quinze machines, donc le socle echoue, donc le deploiement n atteint jamais la couche qui poserait le bon amont. LE CORRECTIF SE RETROUVE DANS LA COUCHE QUE LA PANNE EMPECHE D ATTEINDRE, et il faut une main pour en sortir. C est arrive le 2026-08-31 : l amont pointait sur le cache de patient 0, eteint la veille pour liberer de la RAM. apt rendait « 503 Connection timeout » EN CITANT L ADRESSE DU CACHE LOCAL, jamais celle de l amont manquant — la panne accusait le maillon visible. DEUX FILETS, SYMETRIQUES. serveur_artefacts sonde l amont AVANT d ecrire, et refuse s il est muet : mieux vaut un cache qui garde sa configuration precedente qu un cache qu on vient de rendre inutilisable pour toute la flotte. client_artefacts sonde la source AVANT de detourner apt vers elle. Ce fichier ECRASE le plancher d amorcage pose par le socle — le poser sur un cache mort prive la machine du cache du SITE, qui lui fonctionnait. En refusant, le plancher reste : la flotte est degradee mais debout, et REPARABLE PAR UN DEPLOIEMENT. `ignore_errors` ET NON `failed_when: false` — la difference est toute la garde. failed_when: false REECRIT le verdict : la tache n est plus jamais failed, donc `is not failed` est toujours vrai, donc l assertion ne peut pas tirer. Ecrite ainsi, ma premiere version a declare « joignable » un amont mesure MUET la seconde d avant. Une garde qui ne peut pas echouer ne garde rien. Deux controles verifies sur la machine : amont eteint -> refuse ; amont reel -> pose. make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec, 0 saute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-31 18:31:21 -04:00
# la veille pour liberer de la RAM. `apt` rendait `503 Connection timeout` EN CITANT
# L'ADRESSE DU CACHE LOCAL, jamais celle de l'amont manquant — la panne accusait le
# maillon visible.
#
# On mesure donc AVANT d'ecrire. Un amont injoignable est refuse : mieux vaut un cache
# qui garde sa configuration precedente et un message clair, qu'un cache qu'on vient de
# rendre inutilisable pour toute la flotte.
- name: L'amont declare repond-il ?
ansible.builtin.wait_for:
host: "{{ serveur_artefacts_amont | urlsplit('hostname') }}"
port: "{{ serveur_artefacts_amont | urlsplit('port') | default(3142, true) | int }}"
timeout: 10
register: serveur_artefacts_amont_sonde
changed_when: false
# `ignore_errors` ET NON `failed_when: false` — LA DIFFERENCE EST TOUTE LA GARDE.
#
# `failed_when: false` REECRIT le verdict : la tache n'est plus jamais `failed`, donc
# `is not failed` est toujours vrai, donc l'assertion qui suit ne peut PAS tirer. Ecrite
# ainsi, elle a declare « joignable » un amont mesure MUET la seconde d'avant
# (2026-08-31). Une garde qui ne peut pas echouer ne garde rien.
#
# `ignore_errors` laisse le verdict intact et se contente de ne pas arreter le play :
# c'est ce qu'il faut pour SONDER puis DECIDER.
ignore_errors: true
when:
- serveur_artefacts_amont | length > 0
- not ansible_check_mode
- name: Refuser un amont injoignable plutot que de rendre ce cache inutilisable
ansible.builtin.assert:
that:
- serveur_artefacts_amont_sonde is not failed
fail_msg: >-
L'amont declare ({{ serveur_artefacts_amont }}) ne repond pas. Le poser rendrait ce
cache — et donc `apt` sur TOUTE la flotte de cet ecosysteme — inutilisable, et le
deploiement ne pourrait plus atteindre la couche qui le corrigerait.
Verifier que la machine d'amont tourne, ou vider `serveur_artefacts_amont` pour
aller directement chez Debian (c'est une emancipation : voir
docs/filiation-emancipation.md).
success_msg: "Amont {{ serveur_artefacts_amont }} joignable."
when:
- serveur_artefacts_amont | length > 0
- not ansible_check_mode
depot de binaires : le site tient ce que les runners allaient chercher Le cache du site couvrait apt ; quatre artefacts arrivaient autrement, parce qu ils ne vivent dans aucun depot apt. Le controleur les tire puis les pousse par SSH. Mesure du 2026-09-12 : le cache du runner du site est ABSENT. Un second locataire monte depuis lui sortait chercher 570 Mo sur codeberg.org, github.com et download.nextcloud.com, alors que le meme ecosysteme ne demandait plus un seul paquet a Debian. Le poste du mainteneur les a depuis toujours : personne ne l avait vu. Pas de relais transparent, et la mesure tranche : github.com redirige vers une URL signee valable une heure, differente a chaque requete. Un cache qui la prend pour cle ne fait jamais mouche. Le relais marcherait pour deux amonts sur quatre. Donc un vrai depot, dans le service qui existe deja. LocalDirs d apt-cacher-ng publie un repertoire du disque sous un prefixe, eprouve AVANT d ecrire le role. Aucun service, aucun port, aucun certificat, aucun flux nouveaux : l ingress 3142 pair flotte couvre exactement ce chemin. Les versions ne sont pas recopiees : le role lit les defauts des quatre consommateurs. Les quatre roles recoivent une tache AJOUTEE, placee avant leur stat de cache — si le depot sert, le stat le voit et la tache amont se saute d elle-meme. Aucune tache existante n a change. P70 exige que tout dest ecrit sous un cache_local figure au depot. Une liste qui suit une autre prend du retard ; celle-ci est nee avec sa garde. Deux marches payees en chemin : - failed_when: false REECRIT le verdict, donc la premiere garde de signature ne gardait rien. Elles mesurent le fichier desormais. - file: state=directory cree les parents en 0750 : apt-cacher-ng, qui ne tourne pas en root, rendait 403 sur chaque fichier. Un chemin se traverse en entier. Verifie sur l infrastructure : 6/6 artefacts servis (200/206) depuis le runner du site ET depuis une machine du locataire a travers la frontiere ; les 6 empreintes SHA-256 sont identiques a celles qui ont construit Chezlepro ; second passage changed=0. make prouver : 69 OK, 0 echec, 1 saute. ansible-lint : 0 failure, profil production. Inclut aussi force: true sur cinq telechargements de cles : une reprise conditionnelle ne reprend rien (304 Not Modified, size 0, attempts 5). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-12 23:45:00 -04:00
# LE REPERTOIRE AVANT LA DIRECTIVE QUI LE PUBLIE. `LocalDirs` pointant sur un chemin
# absent, apt-cacher-ng ne refuse pas de demarrer : il rend 503 sur ce prefixe, et le
# message parle de « File or directory not found » — ce qui accuse le fichier demande,
# pas la racine manquante.
# LE PARENT AUSSI, ET C'EST LUI QUI A MORDU (mesure du 2026-09-12).
#
# `file: state=directory` cree les parents manquants avec les droits par defaut — ici
# `0750 root:root`. apt-cacher-ng ne tourne PAS en root : il n'a pas pu traverser
# `/var/lib/setops` et rendait 403 sur chaque fichier du depot, alors que le repertoire
# final etait bien en 0755 et les fichiers bien la.
#
# 403 ACCUSE LE FICHIER DEMANDE, pas le repertoire qui le contient — et encore moins le
# GRAND-PARENT. On a donc cherche du cote des motifs de fichiers d'apt-cacher-ng, qui
# n'y etaient pour rien. Un chemin se traverse en entier ; un droit se verifie de meme.
- name: Assurer la racine d'état de Set-OPS, traversable par les services
ansible.builtin.file:
path: "{{ serveur_artefacts_directs_dir | dirname }}"
state: directory
owner: root
group: root
mode: "0755"
when: serveur_artefacts_directs_actif | bool
- name: Assurer le répertoire du dépôt de binaires directs
ansible.builtin.file:
path: "{{ serveur_artefacts_directs_dir }}"
state: directory
owner: root
group: root
mode: "0755"
when: serveur_artefacts_directs_actif | bool
- name: Déployer la configuration Set-OPS
ansible.builtin.template:
src: zzz-setops.conf.j2
dest: /etc/apt-cacher-ng/zzz-setops.conf
owner: root
group: root
mode: "0644"
notify: Redémarrer le cache d'artefacts
- name: Activer et démarrer le cache d'artefacts
ansible.builtin.systemd:
name: "{{ serveur_artefacts_service }}"
enabled: true
state: started
when: not ansible_check_mode
- name: Appliquer les redémarrages avant de vérifier
ansible.builtin.meta: flush_handlers
# ÉCRIRE, PUIS RELIRE (D-68). Un service « active » qui n'écoute pas sur le port attendu
# est un vert sur un périmètre vide : les clients le découvriraient à leur premier
# `apt update`, c'est-à-dire au pire moment.
- name: Le cache écoute-t-il vraiment ?
ansible.builtin.wait_for:
host: "127.0.0.1"
port: "{{ serveur_artefacts_port }}"
timeout: 30
when: not ansible_check_mode
sondes : les six premieres, et le patron qui les rend eprouvables Ajout de meta/supervision.yml a six roles, chacun deposant sa propre sonde selon le contrat des greffons Nagios. DEUX PRINCIPES POSES AU DOCUMENT DE CONCEPTION, parce que la premiere sonde les a imposes : 1. UNE SONDE DOIT POUVOIR ETRE MISE EN DEFAUT PAR PARAMETRE. Cible et seuils sont des variables du role : on la prouve rouge avec un port ferme ou un seuil impossible, sur une machine reelle, sans rien casser, et aussi souvent qu on veut. Une sonde qu on ne peut prouver qu en cassant un service ne sera prouvee qu une fois. 2. LA SONDE VIT LA OU VIT LA VERITE. « Ce noeud est-il collecte ? » est une sonde de serveur_prometheus, pas de client_metrique : une seule y voit les N noeuds, et surtout elle voit le cas SILENCIEUX — celui qui a cesse d etre collecte ne peut pas s en plaindre. LES SIX : cache-apt (artefacts, repond + place), resolution (resolveur, zone interne ET Internet — deux chemins distincts), forge (forgejo, son propre /api/healthz), collecte (prometheus, 15/15 cibles), tableaux (grafana, base ok), ingestion (loki, PRET a ingerer, pas seulement en ecoute). TROIS FOIS J AI ECRIT LA SONDE AVANT DE MESURER, ET TROIS FOIS ELLE A EU TORT. La forge : port 443 et chemin des depots INVENTES — elle ecoute en 3000 derriere l edge et n a legitimement aucun depot. Loki : j ai conclu « panne persistante » sur deux lectures prises a quelques secondes d intervalle, juste apres un redemarrage ; l anneau etait ACTIVE et la reponse est passee a ready moins d une minute plus tard. Le delai de stabilisation est desormais un AVERTISSEMENT nomme, pas une panne. On demande au service ce qu il pense de lui-meme quand il sait le dire (healthz, /ready, /api/health) plutot que d inventer un critere de l exterieur. make prouver : CONFORME. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 02:48:55 -04:00
# --- Sonde de supervision (docs/supervision-conception.md) --------------------------
# Le role qui possede la verite depose sa propre sonde ; le porteur (`client_sante`) la
# fait tourner et pousse le verdict, sans savoir ce qu'elle mesure.
- name: Assurer le repertoire des sondes de supervision
ansible.builtin.file:
path: /usr/local/lib/setops/sondes
state: directory
owner: root
group: root
mode: "0755"
- name: Deposer la sonde « cache-apt »
ansible.builtin.template:
src: sonde-cache-apt.sh.j2
dest: /usr/local/lib/setops/sondes/cache-apt.sh
owner: root
group: root
mode: "0750"
cache-apt scindee — et la scission a revele que moteur aurait alarme a tort LA SCISSION. cache-apt fondait deux causes aux DELAIS differents : « ne repond pas » arrete tout apt de l ecosysteme, on agit dans la minute ; « volume a 90 % » est un billet pour demain. Les fondre obligeait soit a reveiller quelqu un pour un disque, soit a traiter une panne comme un billet. Deux sondes, chacune avec son etat, son historique, son acquittement — et prouvees INDEPENDANTES : port ferme -> cache-apt CRITIQUE, cache-apt-volume OK seuil impossible -> cache-apt OK, cache-apt-volume AVERTISSEMENT CE QUE LA VERIFICATION A REVELE. Un resultat pousse et accepte (code 200) n apparaissait pas en base. Hypothese testee et confirmee : IcingaDB n ECRIT service_state QUE SUR CHANGEMENT D ETAT. Un OK identique repete ne produit aucune ecriture ; un AVERTISSEMENT pousse ensuite est ecrit en 13 s. Or la sonde moteur, ecrite quelques heures plus tot, lisait exactement max(last_update) de service_state pour juger de la fraicheur. Elle mesurait le CHANGEMENT. Sur un ecosysteme parfaitement STABLE — celui qu on veut — plus rien ne change, last_update vieillit, et elle serait passee en avertissement a 15 min puis en critique a 90. Une alarme qui se declenche PARCE QUE tout va bien, avec un delai qui l aurait rendue difficile a rattacher a sa cause. La bonne source existait : icingadb_instance porte le battement du synchroniseur, ecrit en continu. Releve a 1 s sur un systeme sain. Seuils ramenes de 15/90 min a 1/5 min. Controle negatif rejoue. Dix-huit services, tous verts sauf sauvegarde 7/9 (deja connu). make prouver : CONFORME, 64 OK, 0 echec, 0 saute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 03:27:52 -04:00
- name: Deposer la sonde « cache-apt-volume »
ansible.builtin.template:
src: sonde-cache-apt-volume.sh.j2
dest: /usr/local/lib/setops/sondes/cache-apt-volume.sh
owner: root
group: root
mode: "0750"
depot de binaires : le site tient ce que les runners allaient chercher Le cache du site couvrait apt ; quatre artefacts arrivaient autrement, parce qu ils ne vivent dans aucun depot apt. Le controleur les tire puis les pousse par SSH. Mesure du 2026-09-12 : le cache du runner du site est ABSENT. Un second locataire monte depuis lui sortait chercher 570 Mo sur codeberg.org, github.com et download.nextcloud.com, alors que le meme ecosysteme ne demandait plus un seul paquet a Debian. Le poste du mainteneur les a depuis toujours : personne ne l avait vu. Pas de relais transparent, et la mesure tranche : github.com redirige vers une URL signee valable une heure, differente a chaque requete. Un cache qui la prend pour cle ne fait jamais mouche. Le relais marcherait pour deux amonts sur quatre. Donc un vrai depot, dans le service qui existe deja. LocalDirs d apt-cacher-ng publie un repertoire du disque sous un prefixe, eprouve AVANT d ecrire le role. Aucun service, aucun port, aucun certificat, aucun flux nouveaux : l ingress 3142 pair flotte couvre exactement ce chemin. Les versions ne sont pas recopiees : le role lit les defauts des quatre consommateurs. Les quatre roles recoivent une tache AJOUTEE, placee avant leur stat de cache — si le depot sert, le stat le voit et la tache amont se saute d elle-meme. Aucune tache existante n a change. P70 exige que tout dest ecrit sous un cache_local figure au depot. Une liste qui suit une autre prend du retard ; celle-ci est nee avec sa garde. Deux marches payees en chemin : - failed_when: false REECRIT le verdict, donc la premiere garde de signature ne gardait rien. Elles mesurent le fichier desormais. - file: state=directory cree les parents en 0750 : apt-cacher-ng, qui ne tourne pas en root, rendait 403 sur chaque fichier. Un chemin se traverse en entier. Verifie sur l infrastructure : 6/6 artefacts servis (200/206) depuis le runner du site ET depuis une machine du locataire a travers la frontiere ; les 6 empreintes SHA-256 sont identiques a celles qui ont construit Chezlepro ; second passage changed=0. make prouver : 69 OK, 0 echec, 1 saute. ansible-lint : 0 failure, profil production. Inclut aussi force: true sur cinq telechargements de cles : une reprise conditionnelle ne reprend rien (304 Not Modified, size 0, attempts 5). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-12 23:45:00 -04:00
# --- REMPLIR LE DEPOT DE BINAIRES DIRECTS --------------------------------------------
#
# CETTE MACHINE EST LA SEULE A SORTIR, et c'est le meme principe que pour apt : un seul
# telechargement pour tout le site, y compris pour les ecosystemes qui naitront demain.
#
# ON LIT LES DEFAUTS DES ROLES CONSOMMATEURS, on ne recopie pas leurs versions. Sans ca,
# la version vivrait a deux endroits et le depot finirait par servir, en silence, un
# artefact que plus personne ne demande.
#
# `include_vars` SANS `name:` — DELIBEREMENT. Enfermees dans un dictionnaire, les valeurs
# qui se citent entre elles ne se resolvent plus : `serveur_forgejo_url` reference
# `serveur_forgejo_version`. Le depot connait deja cette marche, payee sur le devis des
# certificats ; on ne la repaye pas.
- name: Lire ce que les rôles consommateurs déclarent télécharger
ansible.builtin.include_vars:
dir: "{{ role_path }}/../{{ item }}/defaults"
loop: "{{ serveur_artefacts_directs_roles }}"
loop_control:
label: "{{ item }}"
when: serveur_artefacts_directs_actif | bool
# `force: false` ET UN `dest` NOMME PAR LA VERSION : une publication ne change pas sous
# le meme nom, donc un fichier deja present est le bon. Une nouvelle version porte un
# autre nom et se telecharge d'elle-meme — le depot se met a jour en suivant les roles,
# sans qu'on ait rien a purger.
#
# LES REPRISES SONT OBLIGATOIRES, PAS PRUDENTIELLES. `packages.smallstep.com` a deja fait
# echouer un deploiement de quarante minutes en pendant au premier essai et en repondant
# en 0,48 s au second ; codeberg.org n'a aucune raison d'etre plus fiable.
- name: Tenir les binaires directs à la disposition des écosystèmes
ansible.builtin.get_url:
url: "{{ item.url }}"
dest: "{{ serveur_artefacts_directs_dir }}/{{ item.nom }}"
mode: "0644"
force: false
timeout: 60
loop: "{{ serveur_artefacts_directs }}"
loop_control:
label: "{{ item.nom }}"
register: serveur_artefacts_directs_tires
retries: 5
delay: 6
until: serveur_artefacts_directs_tires is succeeded
# UN AMONT MUET NE DOIT PAS ARRETER LE SITE. Ce depot est une COMMODITE : sans lui,
# chaque runner sort lui-meme, comme avant. Le faire echouer rendrait le site
# indeployable le jour ou codeberg.org tousse — on aurait echange une sortie Internet
# contre une dependance a Internet, ce qui est exactement l'inverse du but.
failed_when: false
when:
- serveur_artefacts_directs_actif | bool
- not ansible_check_mode
# ON LE DIT, FORT. Un depot a moitie rempli sert a moitie, et le runner qui complete en
# sortant sur Internet le fait sans que rien ne le signale.
- name: Dire ce que le dépôt de binaires ne tient PAS
ansible.builtin.debug:
msg: >-
{{ (serveur_artefacts_directs_tires.results | default([])
| selectattr('failed', 'defined') | selectattr('failed')
| map(attribute='item.nom') | list) | join(', ') }}
— absent(s) du depot. Les runners qui en ont besoin sortiront sur Internet.
when:
- serveur_artefacts_directs_actif | bool
- not ansible_check_mode
- (serveur_artefacts_directs_tires.results | default([])
| selectattr('failed', 'defined') | selectattr('failed') | list | length) > 0