Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English
Read in English

Artefacts : construire une fois, nommer, stocker, retrouver

Par mis à jour le

Améliorer cette page

Un artefact est le fichier que la pipeline produit et que les environnements installent : archive, paquet, binaire ou image de conteneur. La règle qui évite la plupart des mauvaises surprises tient en une phrase : on le construit une seule fois, on le nomme par une version et une empreinte SHA-256 (une signature de son contenu), on le stocke dans un registre (le service qui range les artefacts publiés) qui refuse l'écrasement, puis on le retrouve et on le vérifie au lieu de le refaire. Cette leçon le montre sur un petit service Python, avec le registre de paquets d'une forge locale, et mesure ce qui arrive quand on reconstruit quand même.

  • Distinguer la version d'un artefact (son nom pour les humains) de son empreinte (son identité pour la machine).
  • Expliquer pourquoi un registre refuse de republier une version existante.
  • Publier l'artefact que la pipeline vient de construire, sur une étiquette de version, avec un jeton dédié.
  • Retrouver un artefact publié et vérifier son empreinte avant de l'utiliser.
  • Justifier, mesures à l'appui, qu'on ne reconstruit pas un artefact déjà publié.
  • Faire un commit et créer une branche avec Git.
  • Lancer une commande dans un terminal et lire son code de sortie.
  • Lire un fichier YAML.
  • Avoir lu la leçon Étapes et déclencheurs, dont le fil rouge est repris ici.

Aucun compte GitHub ni GitLab n'est nécessaire : le fil rouge tourne sur une forge locale (Forgejo 16.0.6) et son runner (Forgejo Runner 13.2.0) dans une machine virtuelle de lab. La syntaxe des workflows Forgejo est proche de celle de GitHub Actions. Cette leçon n'en enseigne pas le détail : pour la syntaxe, voir les formations GitHub Actions et GitLab CI.

Un artefact est le résultat d'une construction (build) : ce que l'on déploie, pas ce que l'on écrit. Selon la technologie, c'est un binaire, une archive, un paquet système ou une image de conteneur. Le fil rouge du lab produit un fichier inventaire-1.0.0.pyz, une application Python empaquetée en un seul fichier exécutable par python3 -m zipapp.

Le fichier Makefile du dépôt porte la cible qui le produit : make build écrit dist/inventaire-<version>.pyz et le fichier .sha256 qui l'accompagne.

  • Répertoireinventaire/
    • Répertoire.forgejo/
      • Répertoireworkflows/
        • ci.yml
    • Répertoiresrc/
      • Répertoireinventaire/
        • __init__.py
        • __main__.py
        • donnees.py
    • Répertoiretests/
      • test_donnees.py
    • Makefile
    • Répertoiredist/
      • inventaire-1.0.0.pyz
      • inventaire-1.0.0.pyz.sha256

La version est écrite une seule fois, dans src/inventaire/__init__.py, et le répertoire dist/ n'existe qu'après make build : il n'est jamais versionné, puisque le dépôt porte la source et le registre l'artefact.

Deux noms désignent le même fichier, et ils ne servent pas à la même chose.

  • La version (1.0.0) est un nom choisi par une personne. Elle dit quelle livraison on veut, elle se communique à l'oral et se range dans un journal.
  • L'empreinte (digest) est le résultat de la fonction de hachage SHA-256 appliquée au contenu du fichier : une suite de 64 caractères (chiffres et lettres de a à f) qui sert de signature du contenu. Elle dépend de chaque octet, donc un seul octet modifié change toute l'empreinte.

La version peut mentir (deux fichiers différents portent le même nom si l'on s'y prend mal). L'empreinte ne ment pas : deux fichiers de même empreinte sont identiques. C'est pourquoi on publie les deux et que l'on compare l'empreinte, pas le nom, quand il faut être sûr.

Cette leçon traite l'artefact sous la forme d'un fichier. Le vocabulaire général (job, runner, cache) est posé dans Qu'est-ce qu'une pipeline CI/CD ?, les gestionnaires de dépôts dans le guide Artefacts, et le cas des images de conteneur, avec leurs propres empreintes, dans Images en CI/CD.

Pourquoi un registre refuse-t-il d'écraser une version ?

Section intitulée « Pourquoi un registre refuse-t-il d'écraser une version ? »

Un registre est le service qui range les artefacts publiés, par nom et par version, et les redonne à qui les demande. Forgejo en embarque un, le registre de paquets, avec un type générique qui accepte n'importe quel fichier : c'est celui du lab.

Chaque fichier y a une adresse prévisible, de la forme /api/packages/<propriétaire>/generic/<paquet>/<version>/<fichier>. Pour le fil rouge, la version 1.0.0 vit à l'adresse suivante.

http://localhost:3000/api/packages/apprenant/generic/inventaire/1.0.0/inventaire-1.0.0.pyz

Le registre applique la règle d'immuabilité : une version publiée ne se remplace pas par un simple envoi. Dans le lab, republier la même version est refusé, avec le code HTTP 409 (conflit) et le message package file already exists. La documentation de Forgejo précise qu'un même nom de fichier ne se publie pas deux fois dans un paquet, sauf à supprimer d'abord la version : l'immuabilité protège contre l'écrasement accidentel, pas contre une suppression volontaire par une personne habilitée.

Ce refus est voulu. Si l'on pouvait écraser 1.0.0, la version ne désignerait plus un fichier précis : la recette aurait testé un fichier, la production en recevrait un autre, et personne ne saurait lequel tourne. Pour corriger un défaut, on publie une nouvelle version (1.0.1), jamais un remplacement silencieux.

La publication est une étape comme les autres : une cible make publish, appelée par la pipeline, qui envoie au registre le fichier déjà construit et son empreinte. Le fichier de pipeline orchestre, la cible fait le travail, et vous pouvez la lire sans connaître la syntaxe du moteur.

REGISTRE := http://localhost:3000/api/packages/apprenant/generic/inventaire
.PHONY: publish
publish:
@test -f $(ARTEFACT) || { echo "Aucun artefact : lancez d'abord make build"; exit 1; }
@test -n "$$REGISTRE_JETON" || { echo "REGISTRE_JETON absent de l'environnement"; exit 1; }
@test "$${GITHUB_REF_NAME:-v$(VERSION)}" = "v$(VERSION)" || { echo "L'étiquette $$GITHUB_REF_NAME ne correspond pas à la version $(VERSION)"; exit 1; }
curl --fail-with-body --silent --show-error --user "apprenant:$$REGISTRE_JETON" \
--upload-file $(ARTEFACT) $(REGISTRE)/$(VERSION)/inventaire-$(VERSION).pyz
curl --fail-with-body --silent --show-error --user "apprenant:$$REGISTRE_JETON" \
--upload-file $(ARTEFACT).sha256 $(REGISTRE)/$(VERSION)/inventaire-$(VERSION).pyz.sha256
@echo "Publié : inventaire $(VERSION)"

Les trois contrôles du début sont des garde-fous. La cible refuse de publier s'il n'y a aucun artefact (on n'envoie pas un fichier fantôme), si le jeton manque, ou si l'étiquette Git ne correspond pas à la version écrite dans le code : une étiquette v1.0.1 sur un code qui annonce 1.0.0 publierait sous un faux nom.

Le déclencheur est une étiquette de version (un tag Git, un repère nommé posé sur un commit). La pipeline d'intégration ne publie pas à chaque push : elle ne le fait que lorsqu'une étiquette v* est poussée, c'est-à-dire quand une personne décide « cette version existe ». Poser et pousser l'étiquette tient en deux commandes, lancées sur le commit à publier :

Fenêtre de terminal
git tag v1.0.0
git push origin v1.0.0

Côté pipeline, deux ajouts suffisent : le déclencheur tags: ['v*'] et une étape conditionnelle qui publie, placée après la construction.

on:
push:
branches: [main]
tags: ['v*']
pull_request:
jobs:
integrer:
runs-on: debian
steps:
- uses: https://data.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: make lint
- run: make test
- run: make build
# Publier ce qui vient d'être construit et testé, seulement pour une
# étiquette de version : jamais de seconde construction.
- if: startsWith(github.ref, 'refs/tags/v')
run: make publish
env:
REGISTRE_JETON: ${{ secrets.REGISTRE_JETON }}

Le persist-credentials: false du checkout, vu dans la leçon précédente, empêche l'action de laisser le jeton Git de la pipeline dans le dépôt cloné. Lisez l'ordre : lint, tests, construction, puis publication. L'artefact publié est donc celui qui vient de passer les tests dans le même job, et l'étape de publication n'appelle pas make build une seconde fois. L'action checkout est épinglée par son SHA de commit, avec la version en commentaire, pour qu'un tag déplacé ne change pas ce que la pipeline exécute.

Pour écrire dans le registre, la pipeline s'authentifie. Le réflexe à éviter est d'y mettre le mot de passe d'une personne, ou un jeton qui ouvre tous les droits. Le lab crée un jeton dédié (un mot de passe généré, propre à cet usage), limité à la portée write:package. La portée est la liste des droits que le jeton accorde : celui-ci peut publier des paquets et rien d'autre.

Ce jeton est stocké comme secret du dépôt (une valeur que la forge garde chiffrée et masque dans les journaux) sous le nom REGISTRE_JETON, jamais dans le dépôt. La pipeline le lit par ${{ secrets.REGISTRE_JETON }} et le passe à l'étape sous forme de variable d'environnement : make publish y lit $REGISTRE_JETON. Si le jeton fuit, on le révoque sans toucher à aucun autre droit. Dans Forgejo, les jetons se gèrent dans les réglages du compte (Applications) ou par l'API /api/v1/users/<compte>/tokens, et les secrets de pipeline dans les réglages du dépôt (Actions, Secrets) ou par l'API /api/v1/repos/<compte>/<dépôt>/actions/secrets/<NOM> ; le lab les a créés par ces routes. Le détail de la gestion des secrets en CI se trouve dans Sécuriser les secrets dans vos pipelines CI/CD.

Publier n'a de sens que si l'on retrouve ce qu'on a publié. Dans le lab, une fois la pipeline de l'étiquette v1.0.0 verte, l'API de la forge liste les fichiers de la version : inventaire-1.0.0.pyz avec son empreinte SHA-256, et inventaire-1.0.0.pyz.sha256 qui contient la même.

inventaire-1.0.0.pyz
sha256 e4dca78a7cd88dc856d018a38fc2e0659c908c29e084dd3ae90ef6d9ea74baba

Pour s'en servir, on télécharge le fichier, puis on recalcule son empreinte avec sha256sum et on la compare à celle publiée. Un fichier tronqué ou corrompu en route ne passe pas ce contrôle. Il a une limite : l'empreinte attendue vient du même registre que le fichier, donc une version supprimée puis republiée dans le registre passerait. D'où le réflexe de noter l'empreinte d'une version validée et de l'imposer ensuite, que la leçon suivante met en œuvre.

Fenêtre de terminal
curl -fsS -O http://localhost:3000/api/packages/apprenant/generic/inventaire/1.0.0/inventaire-1.0.0.pyz
sha256sum inventaire-1.0.0.pyz

Dans le lab, l'empreinte du fichier téléchargé est e4dca78a…, identique à celle publiée. L'artefact se lance alors comme n'importe quel programme, ici en lui donnant un nom d'environnement et un port d'écoute, puis on l'interroge depuis un second terminal :

Fenêtre de terminal
INVENTAIRE_ENV=essai INVENTAIRE_PORT=8099 python3 inventaire-1.0.0.pyz
# dans un second terminal :
curl -s http://localhost:8099/version

Le service répond {"version": "1.0.0", "environnement": "essai"} : il sait quelle version il est, et dans quel environnement il tourne. Notez que le nom de l'environnement vient de la variable, pas du fichier. La leçon Environnements et promotion s'appuie sur cette séparation pour installer le même fichier en recette puis en production.

Reconstruire semble innocent : même source, mêmes commandes, donc même fichier. Le lab montre que non. Le schéma suit la version 1.0.0 : en traits pleins, le chemin normal, construit une fois par la pipeline, stocké et retrouvé avec la même empreinte ; en pointillés, la reconstruction à partir de la même étiquette, qui aboutit à une autre empreinte.

L'étiquette v1.0.0 déclenche la pipeline, qui lance lint et tests puis build et produit inventaire-1.0.0.pyz d'empreinte e4dca78a. L'artefact est publié dans le registre de la forge, puis retrouvé par téléchargement avec la même empreinte. Une reconstruction locale de la même étiquette donne l'empreinte 2f6ab1b5, ce qui n'est plus le même fichier.Pipeline : une seuleconstructionétiquette v1.0.0lint, testbuildinventaire-1.0.0.pyzempreinte e4dca78a…registre de la forgeversion 1.0.0téléchargementsha256sum : e4dca78a…reconstruction localede v1.0.0empreinte 2f6ab1b5…ce n'est plus le même fichier
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Voici les empreintes mesurées le 2026-10-13, lors de deux passages distincts. Dans le premier lab, une construction locale et une construction de la pipeline sur le même commit ; dans le second lab, la version publiée et une reconstruction locale de cette même version. Les quatre valeurs ne viennent donc pas d'un seul passage et ne se comparent que deux à deux, à l'intérieur de chaque lab.

LabConstructionEmpreinte SHA-256 (début)
Premier labLocale (make build)ae59c425…
Premier labPipeline, même commit3d87b1d0…
Second labPubliée par l'étiquette v1.0.0e4dca78a…
Second labReconstruction locale de v1.0.02f6ab1b5…

La reconstruction de v1.0.0 donne 2f6ab1b50b1995972e72eae3946a9a2b37bc62b5be9a0f44f7b68963016449e1, contre e4dca78a… pour la version publiée. Le code est pourtant le même. La cause probable tient au format : un .pyz est une archive zip, et un zip enregistre la date de modification de chaque fichier qu'il contient (ZipInfo.date_time dans le module zipfile de Python). Ces dates sont celles des fichiers sources sur le disque, qui diffèrent entre un dépôt cloné par la pipeline et votre copie locale : octets différents, donc autre empreinte. Le lab n'a pas isolé cette cause octet par octet ; un essai séparé confirme que deux constructions successives des mêmes fichiers donnent la même empreinte, et qu'il suffit de changer la date d'un fichier source pour la changer.

La conséquence compte plus que la cause. Une reconstruction pour la production ne serait pas le fichier qui a passé les tests : même si elle fonctionne, rien ne le prouve, et l'empreinte vérifiée en recette ne correspondrait plus. Voilà le principe build once, deploy many : on construit une fois, et chaque environnement reçoit ce fichier-là. La page Bonnes pratiques CI/CD l'étudie comme invariant, et Les formes de pipelines le compare au motif inverse (reconstruire par environnement).

Il existe des constructions dites reproductibles, où la même source redonne les mêmes octets. Elles demandent un effort particulier (dates normalisées, dépendances figées) et ne remplacent pas la règle ici : tant qu'on ne l'a pas démontré, on stocke le fichier et on le réutilise.

Le cache accélère et peut disparaître ; l'artefact est le produit, il se nomme, se versionne, s'empreinte et se conserve. La page Qu'est-ce qu'une pipeline CI/CD ? détaille cette opposition. Le test le plus simple : supprimez le fichier. Si la pipeline devient plus lente, c'était un cache ; si elle ne peut plus livrer, c'était un artefact. Dans le fil rouge, dist/inventaire-1.0.0.pyz est un artefact publié dans le registre, alors qu'un cache de paquets Python n'a pas à y figurer.

Une empreinte prouve que le fichier n'a pas changé depuis la publication. Elle ne dit pas qui l'a construit, à partir de quel commit ni dans quel environnement. Quand un fichier arrive d'un registre, la question « d'où vient-il vraiment ? » est celle de la provenance.

Une attestation de provenance est un document signé qui relie l'artefact à son commit et à la pipeline qui l'a produit. Ce lab n'en produit pas : il pose le socle, version, empreinte et registre immuable, sur lequel elle s'appuie. Pour voir comment vérifier une provenance et en faire une décision, lisez De la provenance SLSA à la décision.

Le refus HTTP 409 est le message du registre, relevé dans le lab. Les trois messages suivants sont ceux écrits par les contrôles de la cible make publish du Makefile ci-dessus. Le tableau part du symptôme.

SymptômeCauseSolution
Publication refusée, HTTP 409, package file already existsLa version existe déjà dans le registre, qui est immuableNe pas chercher à écraser : corriger le code, incrémenter la version (1.0.1), poser une nouvelle étiquette
Aucun artefact : lancez d'abord make buildmake publish appelé sans dist/inventaire-<version>.pyzLancer make build avant, ou vérifier que l'étape de construction précède la publication dans la pipeline
REGISTRE_JETON absent de l'environnementLe secret n'est pas défini sur le dépôt, ou l'étape ne le passe pas par env:Créer le secret REGISTRE_JETON et vérifier le bloc env: de l'étape de publication
L'étiquette … ne correspond pas à la version …L'étiquette Git et la version écrite dans le code divergentAligner la version dans __init__.py et l'étiquette, puis repousser une étiquette cohérente
Aucune publication après un push sur mainLa publication n'a lieu que sur une étiquette v*, pas sur une branchePousser l'étiquette : git tag v1.0.0 && git push origin v1.0.0
L'empreinte d'une reconstruction diffère de celle publiéeUn .pyz embarque les dates de modification des fichiers sources : deux copies du dépôt donnent en général des octets différentsNe pas reconstruire : télécharger la version publiée et vérifier son empreinte

Le lab vous fait ajouter la cible make publish au dépôt inventaire, étendre la pipeline pour qu'elle publie sur une étiquette v* avec le jeton lu dans REGISTRE_JETON, pousser l'étiquette v1.0.0, puis télécharger la version publiée. La validation porte sur l'état de la forge et de la machine : le Makefile de main doit avoir une cible publish, la pipeline doit se déclencher sur une étiquette v* et recevoir REGISTRE_JETON depuis les secrets, aucun jeton ne doit figurer dans le dépôt, la pipeline de v1.0.0 doit être verte, le registre doit contenir inventaire-1.0.0.pyz et son fichier .sha256, et le fichier téléchargé dans votre répertoire personnel doit avoir la même empreinte que celui du registre. Les commandes que vous tapez ne sont pas évaluées, seul le résultat l'est.

Le lab se lance avec dsoxlab run artefacts, après dsoxlab provision qui prépare la machine virtuelle. L'installation de l'outil est décrite dans DSOXLab.

  • Un artefact est le fichier produit par la pipeline et installé dans les environnements : on le construit une seule fois.
  • La version est un nom pour les humains ; l'empreinte SHA-256 est l'identité du contenu, et seule elle permet de comparer deux fichiers.
  • Un registre refuse de republier une version existante (HTTP 409) : pour corriger, on publie une nouvelle version.
  • On publie sur une étiquette de version, avec un jeton dédié (write:package) gardé en secret et passé par l'environnement.
  • On retrouve l'artefact en le téléchargeant et en recalculant son empreinte avant usage.
  • Reconstruire la même version donne une autre empreinte (mesuré : e4dca78a… publiée contre 2f6ab1b5… reconstruite) : ce n'est plus le fichier testé.
  • Un cache accélère et peut disparaître ; un artefact est le produit et doit se retrouver.
  • L'empreinte garantit l'intégrité, pas l'origine : c'est le rôle de la provenance.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn