diff --git a/ansible/arcodange/factory/roles/ci_base_image/defaults/main.yml b/ansible/arcodange/factory/roles/ci_base_image/defaults/main.yml index 39ce6f2..e242e9a 100644 --- a/ansible/arcodange/factory/roles/ci_base_image/defaults/main.yml +++ b/ansible/arcodange/factory/roles/ci_base_image/defaults/main.yml @@ -31,6 +31,15 @@ ci_base_image_tag: latest ci_base_image_bun_version: '1.3.14' ci_base_image_playwright_version: '1.61.1' +# ⚠ NODE 20 EST OBLIGATOIRE, ET CE N'EST PAS UN CONFORT. +# `runner-images:ubuntu-latest-ca` livre Node **18** (v18.20.8, constaté en +# lançant l'image). Or `nuxi` importe `node:util.styleText`, absent de Node 18 : +# c'est la raison d'être du `container: node:20-bookworm` que portait la CI de +# kadans, et que son CLAUDE.md interdit de retirer. Sans cette surcharge, tout +# job Nuxt basculé sur cette image casse au premier build, sur un message qui +# parle d'un import introuvable et jamais d'une version de Node. +ci_base_image_node_major: 20 + # Épinglage par conteneur factice — remède décrit par # docs/adr/20260407-docker-storage-gitea-runner.md §1, jusqu'ici resté à l'état # de proposition (system_docker.yml n'applique que le data-root et les log-opts). diff --git a/ansible/arcodange/factory/roles/ci_base_image/files/Dockerfile b/ansible/arcodange/factory/roles/ci_base_image/files/Dockerfile index ea85c29..560aba0 100644 --- a/ansible/arcodange/factory/roles/ci_base_image/files/Dockerfile +++ b/ansible/arcodange/factory/roles/ci_base_image/files/Dockerfile @@ -42,7 +42,43 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright # la couche redeviendrait impossible à transporter. # ══════════════════════════════════════════════════════════════════════════ -# 1. Bun (~274 Mo) +# 0. NODE 20, ET C'EST OBLIGATOIRE — pas une préférence. +# +# ⚠ `runner-images:ubuntu-latest-ca` livre **Node 18** (v18.20.8, vérifié en le +# lançant). Or `nuxi` importe `node:util.styleText`, ABSENT de Node 18 : c'est la +# raison d'être du `container: node:20-bookworm` que la CI de kadans portait, et +# que son `CLAUDE.md` interdit explicitement de retirer. +# +# Sans cette couche, une CI qui bascule sur cette image casse au premier `nuxt +# build` — et le message parle d'un import introuvable, pas d'une version de Node. +# Le défaut a été trouvé en CONSTRUISANT l'image puis en lançant `node --version` +# dedans ; aucune lecture du Dockerfile ne l'aurait montré. +# ⚠⚠ ET INSTALLER NE SUFFIT PAS — il faut aussi que `node` RÉSOLVE vers le bon. +# Constaté en lançant l'image : après l'installation de Node 20 par apt, +# `node --version` rendait toujours **v18.20.8**, parce que l'image de base +# précuit un node pour le toolcache d'act et le met EN TÊTE du PATH : +# +# which node → /opt/acttoolcache/node/18.20.8/arm64/bin/node +# PATH → /opt/acttoolcache/node/18.20.8/arm64/bin:/usr/local/sbin:/usr/bin:… +# /usr/bin/node --version → v20.20.2 ← le bon, mais il PERD +# +# On retire donc l'entrée 18 du toolcache : le segment de PATH devient inexistant +# (inoffensif) et la résolution retombe sur /usr/bin/node, en 20. +# ⚠ Conséquence assumée : `actions/setup-node` ne trouvera plus de Node 18 +# préinstallé dans cette image. Aucun workflow de kadans ne l'utilise, et l'image +# n'est servie qu'aux jobs qui DEMANDENT le label `ci-node-playwright`. +ARG NODE_MAJOR=20 +RUN curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" -o /tmp/nodesource.sh \ + && bash /tmp/nodesource.sh \ + && apt-get install -y --no-install-recommends nodejs \ + && rm -f /tmp/nodesource.sh \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /opt/acttoolcache/node \ + && echo "node résolu : $(which node) $(node --version)" \ + && node --version | grep -q "^v${NODE_MAJOR}\." + +# 1. Bun (~180 Mo) — installé APRÈS Node 20, pour que son npm global soit celui +# de Node 20 et non celui de Node 18. RUN npm install -g "bun@${BUN_VERSION}" \ && npm cache clean --force @@ -59,7 +95,7 @@ RUN npx --yes "playwright@${PLAYWRIGHT_VERSION}" install chromium # la confronte à son `bun.lock` : une dérive de version doit échouer FORT, avec sa # cause, plutôt que de se manifester par un « Executable doesn't exist » à # vingt minutes de là. -RUN printf 'bun=%s\nplaywright=%s\n' \ - "$(bun --version)" "${PLAYWRIGHT_VERSION}" \ +RUN printf 'node=%s\nbun=%s\nplaywright=%s\n' \ + "$(node --version)" "$(bun --version)" "${PLAYWRIGHT_VERSION}" \ > /etc/ci-base.versions \ && cat /etc/ci-base.versions diff --git a/ansible/arcodange/factory/roles/ci_base_image/tasks/main.yml b/ansible/arcodange/factory/roles/ci_base_image/tasks/main.yml index 427459c..5ac76f2 100644 --- a/ansible/arcodange/factory/roles/ci_base_image/tasks/main.yml +++ b/ansible/arcodange/factory/roles/ci_base_image/tasks/main.yml @@ -14,6 +14,7 @@ rebuild: '{{ "always" if ci_base_image_force_rebuild else "never" }}' args: CI_BASE_FROM: '{{ ci_base_image_from }}' + NODE_MAJOR: '{{ ci_base_image_node_major }}' BUN_VERSION: '{{ ci_base_image_bun_version }}' PLAYWRIGHT_VERSION: '{{ ci_base_image_playwright_version }}' register: ci_base_image_build @@ -40,13 +41,19 @@ # Contrôle de sortie : on VÉRIFIE que l'image répond, plutôt que de supposer que # le build a suffi. Une image construite mais dont `bun` n'est pas dans le PATH # passerait le build et casserait tous les jobs. -- name: Vérifier que l'image livre bien bun et chromium +- name: Vérifier que l'image livre bien Node {{ ci_base_image_node_major }}, bun et chromium ansible.builtin.command: cmd: >- docker run --rm {{ ci_base_image_name }}:{{ ci_base_image_tag }} - sh -c "bun --version && cat /etc/ci-base.versions" + sh -c "node --version && bun --version && ls /ms-playwright && cat /etc/ci-base.versions" register: ci_base_image_check changed_when: false + # ⚠ La version de Node est VÉRIFIÉE, pas supposée : l'image de base en livre + # une trop ancienne (18), et une régression silencieuse ici casserait tout job + # Nuxt sur un message qui ne nomme pas la cause. + failed_when: >- + ci_base_image_check.rc != 0 + or ('v' ~ ci_base_image_node_major ~ '.') not in ci_base_image_check.stdout - name: Ce que l'image contient réellement ansible.builtin.debug: