From 715f9de84d715cac5c36922acf96a0943ab9ff14 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Sat, 3 Oct 2026 21:52:25 -0400 Subject: [PATCH] =?UTF-8?q?paquets=20tiers=20:=20les=20=C3=A9pingles=20tie?= =?UTF-8?q?nnent,=20cache=20ou=20pas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - épingles posées en préférences apt (990) par chaque rôle et par le socle avant son dist-upgrade ; garde : paquet plus récent ou épingle inopérante - quatre épingles relevées (step-cli, icingadb-redis, icinga-php-*) - paquets_tiers : --force-confold aux mises à jour ; une mise à jour compte comme un changement - CHANGELOG 66 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 47 ++++++++++++++++ paquets-tiers.yml | 8 +-- roles/common_packages/tasks/main.yml | 12 +++++ roles/paquets_tiers/defaults/main.yml | 16 ++++++ roles/paquets_tiers/tasks/epingles.yml | 75 ++++++++++++++++++++++++++ roles/paquets_tiers/tasks/main.yml | 21 ++++++-- 6 files changed, 172 insertions(+), 7 deletions(-) create mode 100644 roles/paquets_tiers/tasks/epingles.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 245e66e..3b80374 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,52 @@ # CHANGELOG — Set-OPS +## 2026-10-03 (66) — Les épingles tiennent, cache ou pas + +**Demande de l'exploitant** : faire respecter les épingles de `paquets-tiers.yml`. + +**Deux fuites, mesurées sur les 21 paquets épinglés et toutes les machines.** (1) Sans le +cache du contrôleur — c'est le cas des runners — les rôles installaient la dernière version +publiée (`apt install alloy`, sans version). (2) Le socle (`common_packages`, `upgrade: full`) +montait tout au-delà de l'épingle, cache ou pas. Résultat le jour même : sept paquets plus +récents que leur épingle chez les deux locataires, et un au site (`icinga-php-thirdparty` +1.0.1, alors que le cache avait posé 1.0.0). + +**Épingles relevées** sur ce que portent les locataires (en plus des trois de Grafana, 65) : +`step-cli` 0.31.0-1, `icingadb-redis` 8.2.10-1, `icinga-php-library` et +`icinga-php-thirdparty` 1.0.1-1. Cache retiré, empreintes vérifiées. + +**Fait — les épingles deviennent des préférences apt** (`paquets_tiers/tasks/epingles.yml`), +une par paquet, priorité 990 : apt préfère la version épinglée à toute plus récente, y monte +un paquet plus ancien, et ne rétrograde jamais. Éprouvé avant d'écrire : au site, épingle sur +1.20.0-1 alors que 1.20.1-1 existe — candidat 1.20.0-1, `dist-upgrade` s'y arrête ; chez un +locataire déjà en 1.20.1-1 — rien ne bouge. Posées par chaque rôle pour ses paquets, et par +le socle pour TOUTES, avant son `dist-upgrade`. **Garde** : refus si un paquet est déjà plus +récent que son épingle (la relever), ou si l'épingle ne prend pas — version absente de +l'index, qu'apt ignore sans un mot (contrôle réservé à l'application réelle : en simulation, +la préférence n'est pas écrite). + +**Deux défauts de `paquets_tiers` révélés par la première vraie mise à jour.** Le rôle ne +faisait jusque-là que des premières installations. (1) Sur `site-dns-01`, `alloy` 1.19.2 -> +1.20.1 a demandé quoi faire de `/etc/default/alloy`, modifié par `client_journal` — sans +personne pour répondre : paquet déballé, NON configuré (`iU`). Réparé à la main +(`dpkg --configure --force-confold`, fichier local gardé), puis corrigé : `apt-get` reçoit +désormais `--force-confdef --force-confold`, comme le socle. Éprouvé sur `site-cache-01` +(`ii`, port local conservé). (2) La garde `changed_when` cherchait « 0 nouvellement +installés », qu'apt écrit aussi lors d'une mise à jour : la montée se rapportait « ok ». +Corrigé. + +**Appliqué au site** (`client_journal`, 9 VM) : `alloy` 1.20.1-1 partout, configuré, préférence +posée, aucune perte d'envoi. Les autres épingles se poseront au prochain passage du socle ou +des rôles concernés — au site comme chez les locataires, qui n'ont encore rien reçu de ceci. + +**Vu au passage, non traité** : le socle réinstalle `cloud-init` à chaque passage (« Installer +les paquets communs ») et `serveur_durci` le retire derrière — un va-et-vient à chaque +déploiement complet. + +**Validation** : `ansible-lint` (0 violation), `--syntax-check` des sept playbooks touchés, +`make verifier` conforme (83/83) ; socle simulé sur `site-dns-01` (20 préférences, garde au +vert). + ## 2026-10-03 (65) — Épingles Grafana relevées sur ce que portent les locataires **Demande de l'exploitant** : relever les épingles (point 4 du rapport d'anomalies). diff --git a/paquets-tiers.yml b/paquets-tiers.yml index 4806c82..aeb8dae 100644 --- a/paquets-tiers.yml +++ b/paquets-tiers.yml @@ -22,7 +22,7 @@ depots: composant: main # Sur CHAQUE machine, via `client_pki`. Le paquet le plus critique de la liste. paquets: - step-cli: 0.30.6-1 + step-cli: 0.31.0-1 icinga: uri: https://packages.icinga.com/debian @@ -37,12 +37,12 @@ depots: icinga-archive-keyring: 2.0.0-1+debian13 icingacli: 2.14.0-2+debian13 icingadb: 1.5.1-8+debian13 - icingadb-redis: 8.2.9-1+debian13 + icingadb-redis: 8.2.10-1+debian13 icingadb-web: 1.4.0-1+debian13 icinga-l10n: 1.4.0-1+debian13 icinga-php-legacy: 1.1.0-1+debian13 - icinga-php-library: 1.0.0-2+debian13 - icinga-php-thirdparty: 1.0.0-2+debian13 + icinga-php-library: 1.0.1-1+debian13 + icinga-php-thirdparty: 1.0.1-1+debian13 icingaweb2: 2.14.0-2+debian13 icingaweb2-common: 2.14.0-2+debian13 icingaweb2-module-monitoring: 2.12.6-1+debian13 diff --git a/roles/common_packages/tasks/main.yml b/roles/common_packages/tasks/main.yml index 595b449..2d5dc9c 100644 --- a/roles/common_packages/tasks/main.yml +++ b/roles/common_packages/tasks/main.yml @@ -116,6 +116,18 @@ cache_valid_time: 3600 lock_timeout: "{{ common_packages_lock_timeout }}" +# LES ÉPINGLES AVANT LA MISE À JOUR (2026-10-03). `upgrade: full` montait les paquets +# tiers au-dela de `paquets-tiers.yml` : au site, `icinga-php-thirdparty` etait passe de +# 1.0.0 (pose par le cache) a 1.0.1 sans que personne l'ait decide. Les preferences apt de +# TOUTES les epingles sont posees ici, sur toute machine du socle : une epingle sur un +# paquet absent ne fait rien, et fixe la version de sa premiere installation. +- name: Épingler tous les paquets tiers avant la mise à jour + ansible.builtin.include_role: + name: paquets_tiers + tasks_from: epingles.yml + vars: + paquets_tiers_toutes_epingles: true + - name: Appliquer les mises à jour disponibles ansible.builtin.apt: upgrade: full diff --git a/roles/paquets_tiers/defaults/main.yml b/roles/paquets_tiers/defaults/main.yml index 78eae70..7bef9c9 100644 --- a/roles/paquets_tiers/defaults/main.yml +++ b/roles/paquets_tiers/defaults/main.yml @@ -20,3 +20,19 @@ paquets_tiers_noms: [] # télécharge une fois depuis un poste qui a Internet, on déploie ensuite sans en avoir. paquets_tiers_cache: "{{ lookup('env', 'HOME') }}/.cache/setops/paquets" paquets_tiers_dest: "/var/cache/setops/paquets" + +# LES ÉPINGLES S'APPLIQUENT AUSSI SANS CACHE (2026-10-03). Elles ne valaient que pour ce +# qui sortait du cache du contrôleur. Un runner — qui n'a pas ce cache — installait la +# dernière version publiée, et le `dist-upgrade` du socle (`common_packages`) montait tout +# au-delà de l'épingle, cache ou pas. Mesuré le jour même : sept paquets sur vingt et un +# plus récents que leur épingle chez les locataires, et un au site. +# +# Le fichier qui fait foi est celui du dépôt — le même que lit `make cacher-paquets`. +paquets_tiers_fichier_epingles: "{{ role_path }}/../../paquets-tiers.yml" +# 990 : apt PRÉFÈRE la version épinglée à toute version plus récente (500 par défaut), y +# monte un paquet plus ancien, mais ne RÉTROGRADE jamais un paquet déjà plus récent — ce +# que ferait 1000 et plus. Éprouvé sur `site-dns-01` et `web-dorsal-01` avant d'écrire. +paquets_tiers_priorite: 990 +# `true` : épingler TOUS les paquets de paquets-tiers.yml, pas seulement `paquets_tiers_noms`. +# C'est l'appel du socle, avant son `dist-upgrade` (voir common_packages). +paquets_tiers_toutes_epingles: false diff --git a/roles/paquets_tiers/tasks/epingles.yml b/roles/paquets_tiers/tasks/epingles.yml new file mode 100644 index 0000000..0f1a422 --- /dev/null +++ b/roles/paquets_tiers/tasks/epingles.yml @@ -0,0 +1,75 @@ +--- +# LES ÉPINGLES, POSÉES COMME PRÉFÉRENCES APT — cache ou pas (voir defaults/main.yml). +# Une préférence par paquet : plusieurs rôles appellent celui-ci sur la même machine, et +# un fichier commun serait réécrit par chacun avec ses seuls paquets. +- name: Lire les épingles du dépôt + ansible.builtin.set_fact: + paquets_tiers_toutes: >- + {{ (lookup('ansible.builtin.file', paquets_tiers_fichier_epingles) | from_yaml).depots + | dict2items | map(attribute='value.paquets') | list | combine }} + +- name: Retenir celles de cet appel (toutes, pour le socle) + ansible.builtin.set_fact: + paquets_tiers_epingles: >- + {{ paquets_tiers_toutes if (paquets_tiers_toutes_epingles | bool) + else (paquets_tiers_toutes | dict2items | selectattr('key', 'in', paquets_tiers_noms) + | items2dict) }} + +- name: Épingler les paquets tiers (préférence apt) + ansible.builtin.copy: + dest: "/etc/apt/preferences.d/setops-{{ item.key }}" + content: | + # Géré par Set-OPS (rôle paquets_tiers, depuis paquets-tiers.yml). Ne pas éditer. + Package: {{ item.key }} + Pin: version {{ item.value }} + Pin-Priority: {{ paquets_tiers_priorite }} + owner: root + group: root + mode: "0644" + loop: "{{ paquets_tiers_epingles | dict2items }}" + loop_control: + label: "{{ item.key }}={{ item.value }}" + +# CE QUE L'ÉPINGLE NE PEUT PAS FAIRE, ET QU'IL FAUT DIRE. Deux cas où elle ne tient pas : +# - le paquet est DÉJÀ plus récent : 990 ne rétrograde pas (c'est voulu) — l'épingle est +# en retard sur la machine, et il faut la relever, pas laisser l'écart dormir ; +# - la version épinglée n'est PAS dans l'index du dépôt : apt ignore la préférence sans +# un mot et reprend la dernière version — la candidate est alors plus récente. +# Ce second contrôle n'a pas cours en simulation : la préférence n'y est pas écrite, et la +# candidate lue serait celle d'avant l'épingle — une fausse alerte à chaque `--check`. +- name: Comparer installé, candidat et épingle + ansible.builtin.shell: | + set -uo pipefail + {% for nom, version in paquets_tiers_epingles.items() %} + inst=$(dpkg-query -W -f='${Version}' {{ nom }} 2>/dev/null || true) + cand=$(apt-cache policy {{ nom }} 2>/dev/null | awk '/Candidate:/ {print $2}') + if [ -n "$inst" ] && dpkg --compare-versions "$inst" gt "{{ version }}"; then + echo "PLUS_RECENT {{ nom }} installe=$inst epingle={{ version }}" + fi + {% if not ansible_check_mode %} + if [ -n "$cand" ] && [ "$cand" != "(none)" ] && dpkg --compare-versions "$cand" gt "{{ version }}" \ + && { [ -z "$inst" ] || dpkg --compare-versions "$cand" gt "$inst"; }; then + echo "INOPERANTE {{ nom }} candidat=$cand epingle={{ version }}" + fi + {% endif %} + {% endfor %} + true + args: + executable: /bin/bash + environment: + LC_ALL: C + register: paquets_tiers_ecarts + changed_when: false + check_mode: false + when: paquets_tiers_epingles | length > 0 + +- name: Refuser une épingle qui ne tient pas + ansible.builtin.assert: + that: + - paquets_tiers_ecarts.stdout_lines | default([]) | length == 0 + fail_msg: >- + Épingle(s) de paquets-tiers.yml qui ne tiennent pas sur {{ inventory_hostname }} : + {{ paquets_tiers_ecarts.stdout_lines | default([]) | join(' ; ') }}. + PLUS_RECENT : relever l'épingle sur la version installée (puis `make cacher-paquets`). + INOPERANTE : la version épinglée n'est pas dans l'index du dépôt — en choisir une qui y est. + when: paquets_tiers_epingles | length > 0 diff --git a/roles/paquets_tiers/tasks/main.yml b/roles/paquets_tiers/tasks/main.yml index d38e69c..04633b0 100644 --- a/roles/paquets_tiers/tasks/main.yml +++ b/roles/paquets_tiers/tasks/main.yml @@ -2,6 +2,10 @@ # DÉGRADER, JAMAIS DEVINER. Sans cache sur le contrôleur, ce rôle ne fait rien et le DIT : # l'appelant installera depuis le dépôt distant, comme avant. On ne casse pas un # déploiement connecté parce qu'un cache manque. +# LES ÉPINGLES D'ABORD — voir `epingles.yml`, que le socle appelle aussi pour TOUTES. +- name: Épingler les paquets demandés (préférences apt) + ansible.builtin.include_tasks: epingles.yml + - name: Lire le manifeste du cache du contrôleur ansible.builtin.slurp: src: "{{ paquets_tiers_cache }}/MANIFESTE" @@ -65,6 +69,13 @@ loop_control: label: "{{ item.key }}" +# GARDER LES FICHIERS DE CONFIGURATION QUE LES RÔLES ONT MODIFIÉS (2026-10-03). Tant que ce +# rôle ne faisait que des PREMIÈRES installations, aucun fichier n'avait été touché. Depuis +# que les épingles montent, il MET À JOUR : sur `site-dns-01`, `alloy` 1.19.2 -> 1.20.1 a +# demandé quoi faire de `/etc/default/alloy` (le port y est imposé par `client_journal`), +# sans personne pour répondre — « fin de fichier sur l'entrée standard », paquet déballé et +# NON configuré. Mêmes options que le socle (`common_packages`) : garder la version locale. +# # EN UN SEUL APPEL, ET C'EST LE POINT. `apt` sait résoudre un ENSEMBLE de fichiers locaux # — l'un dépendant de l'autre — alors qu'une installation paquet par paquet échouerait sur # l'ordre. Icinga en apporte dix-sept qui se dépendent mutuellement : les poser un à un @@ -74,12 +85,16 @@ - name: Installer les paquets tiers depuis le cache ansible.builtin.command: argv: >- - {{ ["apt-get", "install", "-y", "--no-install-recommends"] + {{ ["apt-get", "install", "-y", "--no-install-recommends", + "-o", "Dpkg::Options::=--force-confdef", "-o", "Dpkg::Options::=--force-confold"] + (paquets_tiers_disponibles.values() | map('regex_replace', '^', paquets_tiers_dest ~ '/') | list) }} environment: DEBIAN_FRONTEND: noninteractive register: paquets_tiers_pose - changed_when: "'0 nouvellement installés' not in paquets_tiers_pose.stdout - and '0 newly installed' not in paquets_tiers_pose.stdout" + # UNE MISE À JOUR EST UN CHANGEMENT (2026-10-03). La garde ne cherchait que « 0 nouvellement + # installés », qu'apt écrit AUSSI quand il met à jour (« 1 mis à jour, 0 nouvellement + # installés ») : `alloy` 1.19.2 -> 1.20.1 sur `site-cache-01` s'est rapporté « ok ». + changed_when: "'0 mis à jour, 0 nouvellement installés' not in paquets_tiers_pose.stdout + and '0 upgraded, 0 newly installed' not in paquets_tiers_pose.stdout" when: paquets_tiers_disponibles | length > 0