La CI de certcheck rejoue, à chaque modification poussée, les contrôles que vous lancez à la main sur votre poste, dans un ordre fixe, et s'arrête à la première étape qui échoue. Cette page est la consigne du lab qui précède l'épreuve de la formation : elle vous montre la pipeline réelle de certcheck, étape par étape, puis comment lire un run rouge (une exécution de la pipeline qui a échoué), le rejouer chez vous et le réparer. Elle ne remplace pas le cours sur le modèle d'une pipeline : lisez d'abord Qu'est-ce qu'une pipeline CI/CD ?, qui définit job, runner et étape.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Nommer les six étapes de la pipeline de certcheck et leur rôle
- Comprendre pourquoi la première étape en échec arrête le job
- Lire un journal de run rouge : quelle étape, quelle commande, quelle cause
- Rejouer l'étape en échec sur votre poste, corriger, repousser
- Rejouer toute la pipeline sans pousser, avec
forgejo-runner exec(facultatif)
Prérequis
Section intitulée « Prérequis »- Avoir lu Qu'est-ce qu'une pipeline CI/CD ? (job, runner, étape) et suivi les leçons précédentes de la formation : tests avec pytest, ruff, Git et pull requests.
- De la leçon partagée sur la pipeline, vous utilisez ici les parties sur le job et ses étapes, le runner, le journal, et la différence entre cache et artefact. Ses sections sur les environnements de déploiement relèvent de la formation Pipelines CI/CD, hors de cette initiation.
- Pour faire le lab, une machine capable de faire tourner une machine virtuelle (voir « Mettre en pratique »). Sans elle, la lecture de la page suffit pour le modèle.
- Pour rejouer la pipeline sur votre propre poste (facultatif) : un Linux x86-64 avec Docker, et le lab lancé (le dépôt à cloner vient de la forge de la VM). Dans la VM du lab, rien de plus n'est nécessaire.
À quoi ressemble la pipeline de certcheck ?
Section intitulée « À quoi ressemble la pipeline de certcheck ? »Elle tient dans un seul fichier, .forgejo/workflows/ci.yml, qui déclare un
déclencheur et un job de six étapes. Le déclencheur dit quand la forge la
lance : à chaque push sur main et à chaque demande de fusion. Le job
integrer s'exécute sur un runner (la machine qui joue les étapes) portant
l'étiquette debian. Les étapes sont lues de haut en bas : regardez leur
ordre, il compte.
name: cion: push: branches: [main] pull_request:
permissions: contents: read
jobs: integrer: runs-on: debian steps: - name: Récupérer le code uses: https://data.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Préparer Python run: uv python find ">=3.12" - name: Installer les dépendances run: uv sync --locked - name: Qualité run: | uv run ruff check . uv run ruff format --check . - name: Tests run: uv run pytest -v - name: Construire le paquet run: uv buildLe schéma résume l'enchaînement : six étapes en file, une seule issue verte, et une flèche en pointillés vers le run rouge quand une étape échoue (ici Qualité, pour un import inutile, comme dans le run lu plus bas).
Le modèle complet (déclencheurs, jobs, étapes, ce que le dépôt déclare et ce que la forge exécute) est expliqué dans Étapes et déclencheurs d'une pipeline CI/CD : cette page se contente de lire celle de certcheck.
Que fait chacune des six étapes ?
Section intitulée « Que fait chacune des six étapes ? »Chaque étape rejoue un geste que vous connaissez déjà, et l'ordre va du moins cher au plus cher. Un défaut de forme se signale en quelques secondes ; il serait absurde d'attendre la fin des tests pour l'apprendre.
- Récupérer le code : le runner part d'une machine vide et clone le dépôt
au commit poussé. Sans cela, rien à contrôler ; si elle échoue, rien ne
peut suivre. L'option
persist-credentials: falsedemande à l'action de ne pas enregistrer le jeton de la forge dans la configuration Git pour les étapes suivantes : la pipeline n'a pas besoin de repousser du code, elle ne garde donc aucun accès inutile. - Préparer Python :
uv python find ">=3.12"cherche un interpréteur Python 3.12 ou plus récent et affiche son chemin. Si aucun ne convient, la commande se termine en erreur (code de sortie non nul) : mieux vaut échouer ici, avec un message clair, que plus loin sur une erreur de syntaxe incompréhensible. - Installer les dépendances :
uv sync --lockedinstalle exactement les versions du fichier de verrouillage. L'option--lockedfait échouer l'étape si le verrou ne correspond plus àpyproject.toml: le runner ne « corrige » pas en silence, il vous dit que le projet est incohérent (voir Dépendances Python). - Qualité :
ruff check .cherche les erreurs que le linter sait trouver (import inutilisé, nom indéfini),ruff format --check .vérifie que le code est formaté comme le projet l'a décidé, sans rien modifier. C'est le travail mécanique de la revue, confié à la machine (voir Relire une pull request). - Tests :
pytest -vlance la suite et affiche chaque test. C'est l'étape qui dit si le comportement promis tient toujours (voir Premiers tests avec pytest). - Construire le paquet :
uv buildfabrique le paquet installable. Si elle échoue alors que tout le reste passe, c'est l'emballage qui est cassé, pas le code.
Quand une étape échoue, la suivante ne s'exécute pas : le job s'arrête à la première étape en échec. On appelle cela échouer vite (fail fast). La conséquence se lit dans un journal : l'étape rouge est toujours la première cause à traiter, et les étapes grisées après elle ne sont pas des échecs supplémentaires, simplement des étapes qui n'ont pas eu lieu.
Comment lire un run rouge ?
Section intitulée « Comment lire un run rouge ? »On lit le journal par la fin : la dernière étape exécutée est la coupable.
On repère l'étape en échec, puis la commande qu'elle lançait, puis la ligne qui
dit pourquoi. Dans le lab, le run de départ est rouge sur main : l'étape
Qualité échoue à cause d'un import shutil que personne n'utilise dans
src/certcheck/cli.py. Le journal du run, dans l'onglet Actions de la forge,
montre cette sortie sous l'étape Qualité. C'est la sortie réelle de la commande
uv run ruff check . sur ce dépôt (relevée avec ruff 0.17.0, code de sortie 1) :
F401 [*] `shutil` imported but unused --> src/certcheck/cli.py:14:8 |12 | import logging13 | import os14 | import shutil | ^^^^^^15 | import sys16 | from pathlib import Path |help: Remove unused import: `shutil` |13 | import os - import shutil14 | import sys |
Found 1 error.[*] 1 fixable with `--fix`.Lisez-le en partant du bas. Found 1 error. dit qu'il n'y a qu'une cause. Le
message nomme la règle (F401), le fichier et la ligne (cli.py:14), et
montre le code autour. Dans le journal complet de la forge, cette sortie est
précédée de la ligne de l'étape Qualité, marquée en échec : c'est la
dernière étape exécutée, et celles qui suivent (Tests, Construire le paquet)
sont non exécutées, c'est le fail fast. Un code de sortie non nul (ici 1)
signale l'échec, comme pour n'importe quelle commande Unix.
Remarquez ce que le journal ne dit pas : quel commit a introduit le défaut. Le message du dernier commit ne parle pas forcément du code, et l'historique Git est l'outil qui le retrouve. Même méthode de lecture que dans Étapes et déclencheurs d'une pipeline CI/CD, dont la section « Comment lire un échec et le reproduire en local ? » traite aussi le cas où l'échec ne se reproduit pas chez vous (version d'outil, variable d'environnement, fichier absent du dépôt).
Comment rejouer l'étape en local, puis corriger ?
Section intitulée « Comment rejouer l'étape en local, puis corriger ? »Toute étape de la CI est une commande que vous pouvez lancer chez vous, dans le dépôt, et vous devez la rejouer avant de pousser. C'est tout l'intérêt d'une CI écrite avec des commandes ordinaires : le journal vous donne la commande, vous la tapez.
uv sync --lockeduv run ruff check .Vous obtenez le même message que sur la forge. Vous supprimez la ligne
import shutil, vous relancez la même commande, puis vous rejouez les étapes
suivantes (uv run ruff format --check ., uv run pytest -v, uv build) :
corriger une étape peut révéler la suivante, que le fail fast vous cachait.
Quand tout est vert chez vous, vous commitez et poussez : la forge relance la
pipeline, et le run suivant doit être vert.
Le linter propose une réparation automatique (--fix). Elle est pratique pour
un import inutilisé, mais relisez toujours la modification avant de la
commiter : ce qu'une machine répare n'est pas forcément ce que vous vouliez.
Comment rejouer toute la pipeline sur sa machine ?
Section intitulée « Comment rejouer toute la pipeline sur sa machine ? »Rejouer une commande montre une étape ; rejouer tout le fichier ci.yml
montre le travail du runner, et forgejo-runner exec le fait sans rien
pousser. Le fichier ci.yml s'appelle un workflow (la description de la
pipeline). Le programme forgejo-runner sait le lire et en jouer les six
étapes chez vous, dans l'ordre, avec le même fail fast que sur la forge : vous
voyez naître le journal que vous lisiez dans l'onglet Actions. Ce geste est
facultatif, le lab ne le note pas.
Dans la VM du lab, il n'y a rien à installer : le runner de la forge y est
déjà, avec uv. L'option -i désigne l'image de conteneur dans laquelle
jouer les étapes ; sa valeur spéciale -self-hosted veut dire « pas d'image :
directement sur cette machine ». C'est ce que fait le runner de la forge avec
son exécuteur host (la manière dont il lance les étapes) : même Python,
même uv, mêmes versions de ruff et de pytest.
cd ~/certcheckforgejo-runner exec -W .forgejo/workflows/ci.yml -E push -i -self-hostedL'option -W désigne le fichier de la pipeline, -E push l'événement
simulé, c'est-à-dire ce qui déclenche la pipeline (ici un push). Sur le dépôt
de départ, la sortie s'arrête au même endroit que le run rouge de la
forge. Extrait relevé le 2026-10-14 avec forgejo-runner 13.2.0, pictogrammes
retirés :
[ci/integrer] Run Main Récupérer le code[ci/integrer] Success - Main Récupérer le code[ci/integrer] Run Main Préparer Python[ci/integrer] Success - Main Préparer Python[ci/integrer] Run Main Installer les dépendances[ci/integrer] | Using CPython 3.13.5 interpreter at: /usr/bin/python3[ci/integrer] Success - Main Installer les dépendances[ci/integrer] Run Main Qualité[ci/integrer] | F401 [*] `shutil` imported but unused[ci/integrer] | Found 1 error.[ci/integrer] Failure - Main Qualité[ci/integrer] Skipping step 'Tests' due to ''[ci/integrer] Skipping step 'Construire le paquet' due to ''[ci/integrer] Job failedError: Job 'integrer' failedLe fail fast se lit en direct : Qualité échoue, Tests et
Construire le paquet sont sautés (Skipping step), et la commande se
termine avec le code de sortie 1. Une fois l'import retiré, le même appel
joue les six étapes, affiche 5 passed sous Tests et se termine sur
Job succeeded, code de sortie 0, en quelques secondes.
Sur votre propre poste, la même commande fonctionne si Docker est
installé : exec crée alors un conteneur jetable qui tient lieu de machine
du runner. Téléchargez dans votre dossier personnel le binaire de la même
version que le lab, publié par Forgejo pour Linux x86-64, et vérifiez son
empreinte SHA-256 avant de l'exécuter :
cd ~curl -fsSLO https://code.forgejo.org/forgejo/runner/releases/download/v13.2.0/forgejo-runner-13.2.0-linux-amd64echo "fadaec897f5e6641c363f87ecaf75f866f99c81b7097ec6b433e4c48123fcad1 forgejo-runner-13.2.0-linux-amd64" | sha256sum -cchmod +x forgejo-runner-13.2.0-linux-amd64La commande sha256sum -c doit afficher
forgejo-runner-13.2.0-linux-amd64: OK ; sinon, supprimez le fichier sans
le lancer. Pour avoir le dépôt sur votre poste, ouvrez le tunnel (une
redirection de port SSH, décrite dans « Mettre en pratique ») et gardez-le
ouvert pendant le clone : sans lui, git clone échoue sur Failed to connect to localhost port 3000. Dans ce clone, -i reçoit cette fois une image
(le modèle de machine dont le conteneur est créé) : l'image officielle d'uv,
avec uv 0.12.21 comme le lab et Python 3.13, épinglée par digest
(l'empreinte après @sha256: garantit que c'est exactement cette image et pas
une autre du même nom).
git clone http://localhost:3000/apprenant/certcheck.gitcd certcheck~/forgejo-runner-13.2.0-linux-amd64 exec -W .forgejo/workflows/ci.yml -E push \ -i ghcr.io/astral-sh/uv:0.12.21-python3.13-trixie@sha256:2978ff8ce5196e8c5d3eed7c965bbda0294dde49779715106fa9da453601b295Le premier appel télécharge l'image (7 secondes sur notre connexion, run
compris, davantage sur une ligne lente), les suivants ont pris 3 à 5 secondes,
et le conteneur est supprimé à la fin. La sortie est celle de la VM, à
quelques détails près : l'interpréteur est le Python 3.13.15 de l'image
(/usr/local/bin/python3) et uv affiche le téléchargement de ruff et de
pytest. Pour faire place nette ensuite, supprimez l'image avec docker image rm suivi de son nom complet, et le binaire avec
rm ~/forgejo-runner-13.2.0-linux-amd64.
Un run local ne remplace pas le run de la forge, et ses écarts se connaissent. C'est le run de la forge qui fait foi, et c'est lui que la validation du lab lit :
- ce qui est joué :
execcopie votre arbre de travail (les fichiers du dossier tels qu'ils sont maintenant), modifications non commitées comprises (hors fichiers listés dans.gitignore), alors que la forge clone le commit poussé. Un run local vert ne dit rien d'un fichier oublié dans le commit ; - l'étape Récupérer le code n'exécute pas l'action de checkout :
execla remplace par cette copie ; - les secrets de la forge ne sont pas transmis ; la pipeline de certcheck n'en utilise aucun ;
- dans un conteneur, uv retélécharge ruff et pytest à chaque run, car le conteneur repart de zéro : il faut un accès réseau.
Et act ? Le guide act présente cet
outil, conçu pour les workflows GitHub Actions : sa version 0.2.89
refuse ce fichier tel quel, car il n'accepte pas une action désignée par
URL complète (uses: https://...), syntaxe propre à Forgejo. Le runner de
Forgejo, lui, joue le fichier que la forge joue.
Que fait le lab ?
Section intitulée « Que fait le lab ? »Le lab vous remet un dépôt certcheck dont main est rouge, et vous fait
réparer ce run, puis provoquer vous-même deux échecs pour voir comment la CI
les signale. Vous lisez le run de départ, vous trouvez l'étape et la cause,
vous corrigez. Ensuite, dans une demande de fusion, vous cassez volontairement
un test, observez le run rouge, le réparez par un second commit vert ; puis
vous refaites la même chose avec le formatage. Vous conservez aussi dans un
fichier la sortie d'une commande de la pipeline rejouée en local sur un commit
rouge, et vous répondez dans un fichier YAML : l'ordre des étapes, celle qui
installe les dépendances, celle qui a échoué sur le premier run.
La validation lit l'état de la forge (statuts des runs, demandes de fusion, commits) et de la machine, jamais les commandes tapées. La pipeline fournie ne change pas : on la lit, on ne la réécrit pas.
Mettre en pratique
Section intitulée « Mettre en pratique »Ce lab a besoin d'une machine virtuelle : elle héberge une forge
Forgejo et son runner, qui jouent la pipeline pour de vrai. Il se lance
avec DSOXLab : dsoxlab provision crée la VM (sous KVM ou Incus), puis
dsoxlab run ci-certcheck pose l'état de départ et dsoxlab check ci-certcheck note le résultat. Si vous n'avez pas de quoi faire tourner une
VM, la lecture de cette page et du journal ci-dessus vous donne le modèle ; le
geste, lui, ne se prouve qu'avec le lab.
Pour ouvrir la forge dans votre navigateur, créez un tunnel avec
ssh -L 3000:localhost:3000 forge-1.lab, puis ouvrez http://localhost:3000 :
l'onglet Actions du dépôt liste les runs, et le bouton de nouvelle demande
de fusion sert aux deux échecs provoqués. Le challenge du lab donne les
identifiants du compte. L'installation de DSOXLab est décrite
dans la documentation DSOXLab.
Ce lab s'arrête à la CI : ni livraison, ni déploiement, ni
promotion. Ces sujets relèvent de la formation
Pipelines CI/CD, qui utilise la même forge mais un
autre projet fil rouge, inventaire : elle ne reprend pas le dépôt certcheck.
À retenir
Section intitulée « À retenir »- La pipeline de certcheck rejoue six étapes dans l'ordre : récupérer le code, préparer Python, installer les dépendances, qualité, tests, construire le paquet.
- La première étape en échec arrête le job : c'est le fail fast, et les étapes suivantes sont « non exécutées », pas fautives.
- Un journal se lit par la fin : étape rouge, commande lancée, ligne qui donne la cause.
- Une étape de CI est une commande ordinaire : on la rejoue en local avant de pousser.
- Corriger une étape peut révéler la suivante : on rejoue toute la chaîne.
forgejo-runner execrejoue toute la pipeline sans pousser, mais il joue l'arbre de travail : le run de la forge, sur le commit poussé, reste celui qui fait foi.