Aller au contenu
English

Publier les preuves

  • Leçon 6
  • 50 min
  • Module 2 : Créer un pipeline CI reproductible
Leçon 6 sur 15 · CI/CD, tests et mises en production sûresPublier les preuves

Ce que vous saurez faire

Une coche verte indique « réussi », mais ne précise ni ce qui a réussi ni quelle image en résulte. Conservez des preuves qui répondent à ces questions plusieurs mois plus tard, pour quelqu’un qui n’était pas là. Rendez ensuite les vérifications obligatoires, pour qu’aucun changement n’atteigne main sans elles.

Avant de commencerCe qu’il faut savoir et avoir sous la mainEnviron 50 minutes

Ce que vous devez savoir

Ce qu’il vous faut sur votre ordinateur

  • Votre dépôt ticket-api-ci avec les deux tâches et une exécution verte sur main, tiré de Construire et tester : exercice guidé.
  • L’interface en ligne de commande GitHub gh, connectée à votre compte GitHub (gh auth login), pour télécharger les artefacts et vérifier une attestation. Les autres étapes fonctionnent aussi dans les pages Web.
  • Docker, pour docker buildx imagetools inspect.
  • Si votre flux de travail ne fonctionne pas, comparez-le au fichier de référence ci.yml à c862935. À la fin de cette leçon, votre fichier est identique. L’instantané end-of-m02 contient tous les fichiers de ce module, mais aucun réglage GitHub : vous créez vous-même l’ensemble de règles.

Exécuté avec GitHub Actions (exécuteurs ubuntu-24.04) dans le dépôt public Nextia-Intelligence/ticket-api-ci, GitHub CLI 2.96.0 et Docker Desktop 4.81.0 sur macOS (Apple silicon). Les étapes pour Windows et Linux n’ont pas été testées.

Les questions auxquelles les preuves doivent répondre

Section intitulée « Les questions auxquelles les preuves doivent répondre »

Dans trois mois, la responsable du service de soutien signale une classification étrange. Amira connaît l’image qui fonctionne en production : elle a son empreinte. Pour trouver la cause, elle doit répondre à quatre questions. Le pipeline doit déjà avoir conservé les réponses :

Question Preuve Où la trouver
Quels tests ont été exécutés sur ce code, et ont-ils réussi? Le rapport de tests junit.xml L’artefact reports de l’exécution
Quel commit et quelle exécution ont produit cette empreinte? build-info.json L’artefact build-info de l’exécution
Puis-je le vérifier sans faire confiance à un fichier? Une attestation de provenance signée Le stockage des attestations de GitHub et le registre
Que dit l’image elle-même? Les étiquettes dans l’image L’image dans GHCR

Dans GitHub Actions, un artefact est un fichier ou dossier conservé par une exécution après la disparition de ses machines virtuelles. Toute personne connectée à GitHub peut télécharger les artefacts d’une exécution d’un dépôt public. GitHub les conserve pendant 90 jours par défaut. Les artefacts de l’exécution de référence expirent 90 jours après l’exécution.

Ajoutez une étape à la fin de la tâche de vérification :

.github/workflows/ci.yml (checks job, the last step)
- name: Keep the test and evaluation reports
if: always()
uses: actions/upload-artifact@v7
with:
name: reports
path: reports/

if: always() exécute aussi l’étape lorsqu’une étape précédente a échoué. Un rapport de tests échoués est le rapport le plus utile. (Le nom indique aussi « évaluation » : le module 4 ajoute le rapport d’évaluation de l’IA au même dossier.)

Dans la première exécution, rouge, la vérification du code a échoué avant le démarrage de pytest. Il n’y avait donc aucun rapport à conserver. L’étape l’a indiqué, et GitHub a affiché un avertissement sur la page de l’exécution :

No files were found with the provided path: reports/. No artifacts will be uploaded.

Cet avertissement est correct, ce n’est pas un deuxième problème : aucun test n’a été exécuté. Dans l’exécution verte, l’étape a téléversé un fichier de 773 octets. Téléchargez-le et regardez son contenu :

Terminal
gh run download 37663364319 --repo Nextia-Intelligence/ticket-api-ci -n reports
head -c 300 junit.xml

La sortie :

<?xml version="1.0" encoding="utf-8"?><testsuites name="pytest tests"><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="25" time="0.418" timestamp="2026-10-07T18:00:12.774920+00:00" hostname="runnervm8df0l">

25 tests, 0 échec, 0 test ignoré, avec la durée et le nom de la machine virtuelle de l’exécuteur. Chaque test a aussi sa propre ligne. Pour votre exécution, omettez --repo … et utilisez son numéro : gh run list l’affiche.

build-info.json : empreinte, commit et exécution dans un seul fichier

Section intitulée « build-info.json : empreinte, commit et exécution dans un seul fichier »

Ajoutez deux étapes à la fin de la tâche de l’image. Elles s’exécutent seulement si l’image a été envoyée :

.github/workflows/ci.yml (image job, the last two steps)
- name: Write build-info.json
if: env.PUSH == 'true'
run: |
mkdir -p evidence
cat > evidence/build-info.json <<JSON
{
"image": "${IMAGE}",
"digest": "${{ steps.push.outputs.digest }}",
"tags": $(jq -c -R -s 'split("\n") | map(select(length > 0))' <<< "${{ steps.meta.outputs.tags }}"),
"commit": "${{ github.sha }}",
"ref": "${{ github.ref_name }}",
"run": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
JSON
cat evidence/build-info.json
{
echo "### Image"
echo "\`${IMAGE}@${{ steps.push.outputs.digest }}\`"
echo "Commit \`${{ github.sha }}\`, tags: ${{ steps.meta.outputs.tags }}" | tr '\n' ' '
} >> "$GITHUB_STEP_SUMMARY"
- name: Keep the build evidence
if: env.PUSH == 'true'
uses: actions/upload-artifact@v7
with:
name: build-info
path: evidence/build-info.json

L’étape écrit un petit fichier JSON. steps.push.outputs.digest est l’empreinte indiquée par l’étape d’envoi. jq transforme la liste d’étiquettes (une par ligne) en liste JSON. github.sha est l’identifiant complet du commit (enregistrement des changements), et la dernière ligne construit l’adresse de cette exécution. L’étape affiche le fichier, donc les journaux (logs) le contiennent aussi :

{
"image": "ghcr.io/usmanahmed99/ticket-api-ci",
"digest": "sha256:6c9bb4ed7c7cffc27f0c5489ba867d4d5db16def1c59c8bcbaeb3d26c45ce2e1",
"tags": ["ghcr.io/usmanahmed99/ticket-api-ci:sha-c862935"],
"commit": "c8629352adc1c51933b0c95cb80b73bf6c97e99f",
"ref": "main",
"run": "https://github.com/Nextia-Intelligence/ticket-api-ci/actions/runs/37663364319"
}

Ce fichier relie le registre au dépôt. Un déploiement en a aussi besoin : au module 3, le flux de travail Deploy télécharge build-info depuis l’exécution CI. Il déploie exactement cette empreinte. Le fichier n’est donc pas seulement un relevé : c’est l’entrée de l’étape suivante.

Les dernières lignes ajoutent trois lignes de Markdown à $GITHUB_STEP_SUMMARY. Un résumé de tâche est du « Markdown personnalisé pour chaque tâche ». GitHub l’affiche « sur la page de résumé d’une exécution de flux de travail » (documentation GitHub). Ici, il affiche l’image, son empreinte et le commit en haut de la page de l’exécution. Le jour du test, un visiteur non connecté voyait le graphique des tâches et les artefacts, mais pas les journaux.

Dans l’étape d’envoi, labels: ${{ steps.meta.outputs.labels }} a écrit des étiquettes dans l’image. Ce sont des paires nom-valeur qui l’accompagnent. Tout le monde peut les lire dans le registre public, sans se connecter :

Terminal
docker buildx imagetools inspect ghcr.io/usmanahmed99/ticket-api-ci:sha-c862935 --format '{{json .Image.Config.Labels}}'

La sortie (mise en forme et abrégée) :

{
"org.opencontainers.image.created": "2026-10-07T18:00:42.131Z",
"org.opencontainers.image.revision": "c8629352adc1c51933b0c95cb80b73bf6c97e99f",
"org.opencontainers.image.source": "https://github.com/Nextia-Intelligence/ticket-api-ci",
"org.opencontainers.image.title": "ticket-api-ci",
"org.opencontainers.image.version": "sha-c862935",
...
}

Les noms viennent de l’Open Container Initiative (OCI), donc de nombreux outils les comprennent. revision désigne le commit. source désigne le dépôt. Sans --format, la même commande affiche deux entrées pour l’étiquette : l’image linux/amd64 et une entrée unknown/unknown. Cette dernière est le relevé de construction propre à BuildKit, comme dans le cours sur Docker.

Une étiquette est utile, mais quiconque peut envoyer une image peut y écrire n’importe quelle étiquette. La prochaine preuve ne peut pas être falsifiée ainsi.

Provenance : un relevé signé de l’origine de l’image

Section intitulée « Provenance : un relevé signé de l’origine de l’image »

La provenance décrit comment un artefact a été construit et à partir de quoi : quelle source, quel flux de travail, quelle exécution. Une attestation est un tel relevé accompagné d’une signature cryptographique, pour vérifier qui l’a produit. Ajoutez les permissions et une étape à la tâche de l’image :

.github/workflows/ci.yml (image job)
permissions:
contents: read
packages: write
id-token: write
attestations: write
.github/workflows/ci.yml (image job, after Push the same build)
- name: Record where the image came from
if: env.PUSH == 'true'
uses: actions/attest-build-provenance@v4
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true

id-token: write permet à la tâche d’obtenir de GitHub un jeton d’identité de courte durée. Le service de signature l’utilise pour donner à la tâche un certificat. Celui-ci indique « c’est le flux de travail ci.yml de ce dépôt, à ce commit, dans cette exécution ». attestations: write permet à la tâche de stocker le résultat. Aucune clé n’est stockée nulle part : l’identité vient de l’exécution elle-même. Le module 3 utilise le même type de jeton pour se connecter à Azure.

Le journal de l’exécution de référence :

Attestation type: Build Provenance
Attestation created for ghcr.io/usmanahmed99/ticket-api-ci@sha256:6c9bb4ed7c7cffc27f0c5489ba867d4d5db16def1c59c8bcbaeb3d26c45ce2e1
Attestation signed using certificate from Public Good Sigstore instance
Attestation signature uploaded to Rekor transparency log
https://search.sigstore.dev?logIndex=3133904536
Attestation uploaded to repository
https://github.com/Nextia-Intelligence/ticket-api-ci/attestations/53659075
Attestation uploaded to registry
ghcr.io/usmanahmed99/ticket-api-ci@sha256:2c0ec56270b121fa5e7be5b05645f29514002c43f1a0c74f27936fda0bdbdb7b

Sigstore l’a signée, un journal public (Rekor) l’a enregistrée, et GitHub l’a stockée à deux endroits. La page de l’attestation est publique :

La page publique de l'attestation 53659075 pour le prédicat https://slsa.dev/provenance/v1. Créée le 7 octobre 2026 à 18 h 01 min 22 s GMT. Commit c8629352adc1c51933b0c95cb80b73bf6c97e99f. Build Summary (résumé de construction) renvoie à l'exécution 37663364319, tentative 1. Workflow File (fichier du flux de travail) .github/workflows/ci.yml@refs/heads/main. Une boîte Verify (vérifier) affiche gh attestation verify --owner usmanahmed99, suivi d'un espace réservé pour un nom de fichier ou une URL. Le résumé du certificat indique Build Trigger (déclencheur de construction) push, Issuer (émetteur) https://token.actions.githubusercontent.com, Runner Environment (environnement de l'exécuteur) github-hosted, Source Repository Digest (empreinte du dépôt source) c8629352, Source Repository Ref (référence du dépôt source) refs/heads/main, Source Repository URI (URI du dépôt source) https://github.com/Nextia-Intelligence/ticket-api-ci et Source Repository Visibility (visibilité du dépôt source) public. Subjects (sujets) : ghcr.io/usmanahmed99/ticket-api-ci avec l'empreinte sha256:6c9bb4ed7c7cffc27f0c5489ba867d4d5db16def1c59c8bcbaeb3d26c45ce2e1.
L'attestation de l'image de référence, telle qu'un visiteur non connecté la voit. Le sujet est l'empreinte; le certificat nomme le commit, le fichier du flux de travail, le déclencheur et l'exécution. L’image est en anglais.

Vérifiez-la maintenant dans le terminal. gh attestation verify télécharge l’attestation, vérifie la signature et compare le certificat à une politique :

Terminal
gh attestation verify oci://ghcr.io/usmanahmed99/ticket-api-ci@sha256:6c9bb4ed7c7cffc27f0c5489ba867d4d5db16def1c59c8bcbaeb3d26c45ce2e1 --owner usmanahmed99

La sortie :

Loaded digest sha256:6c9bb4ed7c7cffc27f0c5489ba867d4d5db16def1c59c8bcbaeb3d26c45ce2e1 for oci://ghcr.io/usmanahmed99/ticket-api-ci@sha256:6c9bb4ed7c7cffc27f0c5489ba867d4d5db16def1c59c8bcbaeb3d26c45ce2e1
Loaded 1 attestation from GitHub API
The following policy criteria will be enforced:
- Predicate type must match:................ https://slsa.dev/provenance/v1
- Source Repository Owner URI must match:... https://github.com/usmanahmed99
- Subject Alternative Name must match regex: (?i)^https://github.com/usmanahmed99/
- OIDC Issuer must match:................... https://token.actions.githubusercontent.com
✓ Verification succeeded!
The following 1 attestation matched the policy criteria
- Attestation #1
- Build repo:..... usmanahmed99/ticket-api-ci
- Build workflow:. .github/workflows/ci.yml@refs/heads/main
- Signer repo:.... usmanahmed99/ticket-api-ci
- Signer workflow: .github/workflows/ci.yml@refs/heads/main

La commande exige une connexion à GitHub (avec n’importe quel compte). Une politique peut être plus stricte. Faites une prédiction : la même image réussit-elle si la politique exige la branche hotfix/1.2.1?

Terminal
gh attestation verify oci://ghcr.io/usmanahmed99/ticket-api-ci@sha256:6c9bb4ed7c7cffc27f0c5489ba867d4d5db16def1c59c8bcbaeb3d26c45ce2e1 --owner usmanahmed99 --source-ref refs/heads/hotfix/1.2.1

La sortie se termine par :

✗ Policy verification failed
Error: expected SourceRepositoryRef to be refs/heads/hotfix/1.2.1, got refs/heads/main

Le code de sortie est 1. Voilà l’utilité d’une attestation : un outil de déploiement peut refuser toute image non construite par un flux de travail donné sur main.

Trois remarques du jour du test. Premièrement, dans un script, demandez du JSON. Sans terminal, la commande n’affichait rien et seul son code de sortie indiquait le résultat. --format json donne le commit et l’exécution, par exemple avec --jq '.[0].verificationResult.signature.certificate | {sourceRepositoryDigest, runInvocationURI}'. Deuxièmement, avec les forfaits GitHub Free, Pro et Team, les attestations fonctionnent seulement dans les dépôts publics. Troisièmement, la page de procédure de GitHub montre maintenant l’action actions/attest@v4. L’action actions/attest-build-provenance@v4 du flux de référence exécutait actions/attest à l’intérieur, comme son journal le montre. Les deux fonctionnent.

Exemple détaillé : retrouver le commit à partir d’une empreinte

Section intitulée « Exemple détaillé : retrouver le commit à partir d’une empreinte »

La vérification de fin de module vous demande de retrouver la source d’une construction. Amira a l’empreinte sha256:6c9bb4ed…. Elle suit les preuves :

Quatre boîtes de gauche à droite, reliées par des flèches. 1, l'image : ghcr.io/usmanahmed99/ticket-api-ci@sha256:6c9bb4ed7c7c; vous commencez ici, avec l'empreinte de l'image en cours d'exécution ou que vous récupérez. 2, build-info.json : empreinte sha256:6c9bb4ed, commit c8629352adc1, référence main, exécution .../runs/37663364319; l'artefact build-info, écrit par la tâche de l'image. 3, l'exécution : exécution CI 37663364319, envoi vers main, c862935, les deux tâches ont réussi, artefact reports avec 25 tests; ce qui a été vérifié, et les journaux complets. 4, le commit : c862935, Fix the two lint findings : raise ... from None, and sort the imports (corriger les deux problèmes de vérification du code : raise ... from None et trier les importations); le code source exact que contient l'image. En dessous, une boîte en pointillés relie directement l'image au commit : un deuxième chemin, signé, l'attestation de provenance (gh attestation verify) : sujet sha256:6c9bb4ed, puis exécution 37663364319, tentative 1, puis empreinte source c8629352adc1 sur refs/heads/main.
Deux chemins d'une empreinte à son commit. Le chemin du haut utilise les relevés du pipeline. Celui du bas est signé : il ne dépend donc pas de la confiance accordée à un fichier.
Description textuelle du diagramme

Commencez par l’empreinte. L’artefact build-info de l’exécution qui l’a envoyée contient la même empreinte, le commit et l’adresse de l’exécution. L’exécution montre ce qui a été vérifié : les deux tâches, les 25 tests dans reports et les journaux. Le commit est le code source exact. L’attestation est un deuxième chemin que vous pouvez vérifier séparément : son sujet est l’empreinte. Son certificat signé nomme l’exécution et le commit. Si les deux chemins donnent le même commit, la trace est complète.

  1. Obtenez l’empreinte de l’image en cours d’exécution ou que vous récupérez :

    Terminal
    docker buildx imagetools inspect ghcr.io/usmanahmed99/ticket-api-ci:sha-c862935

    La sortie commence par Digest: sha256:6c9bb4ed7c7cffc27f0c5489ba867d4d5db16def1c59c8bcbaeb3d26c45ce2e1.

  2. Demandez au relevé signé l’exécution et le commit :

    Terminal
    gh attestation verify oci://ghcr.io/usmanahmed99/ticket-api-ci@sha256:6c9bb4ed7c7cffc27f0c5489ba867d4d5db16def1c59c8bcbaeb3d26c45ce2e1 --owner usmanahmed99 --format json --jq '.[0].verificationResult.signature.certificate | {sourceRepositoryDigest, runInvocationURI}'

    La sortie : {"runInvocationURI":"https://github.com/usmanahmed99/ticket-api-ci/actions/runs/37663364319/attempts/1","sourceRepositoryDigest":"c8629352adc1c51933b0c95cb80b73bf6c97e99f"}.

  3. Téléchargez l’artefact build-info de cette exécution et comparez l’empreinte :

    Terminal
    gh run download 37663364319 --repo Nextia-Intelligence/ticket-api-ci -n build-info
    cat build-info.json

    L’empreinte dans le fichier est sha256:6c9bb4ed…, donc identique. Le commit est c8629352…, le même que dans l’attestation.

  4. Ouvrez l’exécution : 37663364319. Les deux tâches sont vertes, et l’artefact reports contient les 25 tests.

  5. Lisez le commit : c862935, ou git show c862935 dans une copie clonée. C’est la correction de vérification du code de Configurer l’exécuteur.

La trace est complète : empreinte → build-info.json → exécution → commit, et le chemin signé concorde.

Le pipeline vérifie chaque changement, mais rien n’empêche un envoi vers main qui le contourne. Jusqu’ici, Amira envoyait directement vers main. Un ensemble de règles regroupe des règles que GitHub impose aux branches. Une vérification obligatoire doit réussir avant qu’une demande de tirage puisse être fusionnée.

Créez l’ensemble de règles après la première exécution verte, car GitHub propose seulement les noms de vérifications déjà rencontrés. Dans le dépôt de référence, Amira l’a créé à 18 h 02 UTC, une minute après la fin de l’exécution verte. Sur GitHub, ouvrez votre dépôt, puis :

  1. Sélectionnez Settings (paramètres). Sous « Code, planning, and automation » (code, planification et automatisation), sélectionnez Rulesets (ensembles de règles).
  2. Sélectionnez New ruleset (nouvel ensemble de règles) → New branch ruleset (nouvel ensemble de règles de branche).
  3. Nommez-le Protect main (protéger main). Réglez Enforcement status (état d’application) sur Active (actif).
  4. Sous Target branches (branches ciblées), sélectionnez Add a target (ajouter une cible), puis ajoutez la branche par défaut.
  5. Sélectionnez Restrict deletions (restreindre les suppressions) et Block force pushes (bloquer les envois forcés).
  6. Sélectionnez Require a pull request before merging (exiger une demande de tirage avant la fusion). Réglez les approbations obligatoires à 0. (Vous travaillez seul. Le module 3 explique le rôle des réviseurs.)
  7. Sélectionnez Require status checks to pass before merging (exiger la réussite des vérifications d’état avant la fusion), puis Require branches to be up to date before merging (exiger des branches à jour avant la fusion).
  8. Ajoutez les deux vérifications : Lint, test, audit (vérifier le code, tester, auditer) et Build, smoke-test and push the image (construire, tester le bon fonctionnement et envoyer l’image).
  9. Sélectionnez Create (créer).

Ces noms sont les valeurs name: des deux tâches, pas leurs identifiants. Require branches to be up to date (exiger des branches à jour) impose qu’une demande de tirage contienne le dernier main avant la fusion. L’API de GitHub appelle cela « strict ». Sinon, deux demandes de tirage peuvent réussir séparément, mais quand même casser main ensemble.

Vérifiez le résultat dans le terminal. Utilisez votre propre nom de propriétaire et relevez le numéro de l’ensemble de règles dans la première commande :

Terminal
gh api repos/<you>/ticket-api-ci/rulesets
gh api repos/<you>/ticket-api-ci/rulesets/<number>

L’ensemble de règles actuel du dépôt de référence (abrégé) :

{
"name": "Protect main",
"enforcement": "active",
"conditions": { "ref_name": { "include": ["~DEFAULT_BRANCH"] } },
"rules": [
{ "type": "deletion" },
{ "type": "non_fast_forward" },
{ "type": "pull_request", "parameters": { "required_approving_review_count": 0 } },
{ "type": "required_status_checks",
"parameters": {
"strict_required_status_checks_policy": true,
"required_status_checks": [
{ "context": "Lint, test, audit, evaluate" },
{ "context": "Build, smoke-test and push the image" }
] } }
],
"bypass_actors": []
}

Regardez la première vérification : « Lint, test, audit, evaluate » (vérifier le code, tester, auditer, évaluer). Le jour de ce module, elle s’appelait Lint, test, audit. Le module 4 renomme la tâche, et l’ensemble de règles a dû suivre, comme le montre l’erreur fréquente ci-dessous. bypass_actors est vide, donc même le propriétaire ne peut pas contourner les règles.

Désormais, chaque changement atteint main par une demande de tirage. Le changement suivant du dépôt de référence était la demande de tirage no 1, de feature/deploy vers main. Son exécution 37664331105 a construit et testé l’image, sans rien envoyer.

  1. Ajoutez Keep the test and evaluation reports (conserver les rapports de tests et d’évaluation) à la tâche de vérification.
  2. Ajoutez id-token: write et attestations: write aux permissions de la tâche de l’image.
  3. Ajoutez Record where the image came from (enregistrer l’origine de l’image), Write build-info.json (écrire build-info.json) et Keep the build evidence (conserver les preuves de construction).
  4. Comparez votre ci.yml au fichier de référence à c862935. Exécutez actionlint.
  5. Créez un commit et envoyez-le vers main. C’est votre dernier envoi direct.
  6. Ouvrez l’exécution. Vérifiez qu’elle contient les artefacts reports et build-info, ainsi que le résumé de tâche avec l’image.
  7. Téléchargez les deux artefacts avec gh run download.
  8. Vérifiez votre image avec gh attestation verify oci://ghcr.io/<you>/ticket-api-ci@<digest> --owner <you>.
  9. Créez l’ensemble de règles Protect main.
Afficher le résultat attendu

diff entre votre fichier et le fichier de référence n’affiche aucune différence, ou seulement les commentaires. La page de l’exécution liste quatre artefacts : reports (moins de 1 Ko), build-info (moins de 1 Ko) et deux relevés de construction .dockerbuild. build-info.json contient votre image, une empreinte, l’étiquette sha-<7>, votre commit complet, main et l’adresse de votre exécution. gh attestation verify se termine par ✓ Verification succeeded! (vérification réussie) et nomme .github/workflows/ci.yml@refs/heads/main. gh api repos/<you>/ticket-api-ci/rulesets/<number> affiche "enforcement": "active", "strict_required_status_checks_policy": true et les deux noms de vérifications Lint, test, audit et Build, smoke-test and push the image.

La fusion de la demande de tirage (pull request) no 1 dans le dépôt de référence a poussé une autre image : sha256:a8af875a60ca868f018e5d83a60382acac691bbfbb7d33a46fe4cbf9942e513f. Cette demande modifiait seulement les fichiers de flux de travail et de déploiement, pas le code de l’application. Faites d’abord une prédiction : cette empreinte est-elle identique à sha256:6c9bb4ed…? Retrouvez ensuite son exécution et son commit (changement enregistré), par les deux chemins.

Afficher la réponse

L’empreinte est différente, même si le code dans l’image n’a pas changé. Les étiquettes font partie de l’image, et elles ont changé : une nouvelle revision (le commit), une nouvelle heure created (création) et une nouvelle étiquette version. Ici, un commit différent donne toujours une empreinte différente. Le chemin signé :

Terminal
gh attestation verify oci://ghcr.io/usmanahmed99/ticket-api-ci@sha256:a8af875a60ca868f018e5d83a60382acac691bbfbb7d33a46fe4cbf9942e513f --owner usmanahmed99 --format json --jq '.[0].verificationResult.signature.certificate | {sourceRepositoryDigest, runInvocationURI}'

La sortie : {"runInvocationURI":"https://github.com/usmanahmed99/ticket-api-ci/actions/runs/37664536375/attempts/1","sourceRepositoryDigest":"43de57baa70c998494d38ad0adda9a7152cfdb96"}. Le chemin du registre : gh run download 37664536375 --repo Nextia-Intelligence/ticket-api-ci -n build-info donne la même empreinte, l’étiquette sha-43de57b et le commit 43de57baa70c…. Ce commit est le commit de fusion de la demande de tirage no 1, « Merge pull request #1 from usmanahmed99/feature/deploy » (fusionner la demande de tirage no 1 de usmanahmed99/feature/deploy).

Erreur courante

Les deux vérifications sont vertes, mais la demande de tirage ne peut toujours pas être fusionnée

Symptôme : dans le dépôt de référence, la demande de tirage no 3 avait deux vérifications vertes, mais GitHub empêchait la fusion. gh pr view 3 --json mergeStateStatus indiquait BLOCKED (bloqué).

Cause : la demande de tirage renommait la première tâche en Lint, test, audit, evaluate (analyse statique, test, audit, évaluation). L’ensemble de règles exigeait encore une vérification nommée Lint, test, audit (analyse statique, test, audit). Aucune tâche ne signalait plus ce nom. GitHub l’attendait donc indéfiniment. Une vérification obligatoire est un nom. GitHub ne sait pas que le nouveau nom désigne la même tâche.

Solution : mettez à jour l’ensemble de règles : Settings (paramètres) → Rulesets (ensembles de règles) → Protect main (protéger main) → dans les vérifications d’état obligatoires, retirez l’ancien nom et ajoutez le nouveau. La demande de tirage peut alors être fusionnée. Mieux encore : modifiez l’ensemble de règles en même temps que le nom de la tâche. Indiquez-le dans la description de la demande de tirage. Barrière d’évaluation de l’IA : une vérification renommée présente toute l’histoire.

Pour un vrai service d’IA : ne « corrigez » pas une demande de tirage bloquée en désactivant les règles ou en vous ajoutant à la liste d’exemption. Chaque fois que les règles sont désactivées, un changement non vérifié peut atteindre main, puis la production.

Vérifiez votre compréhension

build-info.json et l'attestation de provenance relient tous deux une empreinte à un commit et à une exécution. Pourquoi conserver les deux?

Afficher la réponse

Ils ont des usages différents. build-info.json est un simple fichier que le pipeline lui-même lit. Le flux de travail Deploy du module 3 y prend l’empreinte. Mais ce n’est qu’un fichier : une tâche pourrait y écrire n’importe quoi. L’attestation porte une signature issue d’un certificat validé par le jeton d’identité de GitHub. Un journal public l’enregistre. Une personne ou un outil peut donc la vérifier sans faire confiance aux fichiers du pipeline. Quand les deux donnent le même commit, la trace est solide. Les étiquettes sont un troisième indice, plus faible : quiconque peut pousser une image peut écrire n’importe quelle étiquette.

Vérifiez votre compréhension

Pourquoi l'étape qui téléverse les rapports utilise-t-elle if : always(), tandis que les étapes build-info utilisent if : env.PUSH == 'true'?

Afficher la réponse

Le rapport de test est surtout utile quand les tests échouent. Il faut donc le téléverser même après l’échec d’une étape. always() le permet. (Si l’analyse statique échoue avant pytest, il n’y a aucun rapport, et l’étape affiche seulement un avertissement.) build-info.json décrit une image dans le registre. Sans envoi, comme dans une demande de tirage, il n’y a aucune empreinte à enregistrer. Ces étapes s’exécutent donc seulement quand PUSH vaut true.

Exercice guidé → tâche autonome. Vous avez construit le pipeline avec Amira en trois leçons. Terminez-le maintenant par vous-même et documentez-le, en deux parties.

Partie 1 : le pipeline propre au fournisseur. Votre dépôt ticket-api-ci contient .github/workflows/ci.yml, avec les deux tâches et toutes les étapes de preuves. Il a une exécution verte sur main et l’ensemble de règles Protect main. Faites un autre changement par une demande de tirage : ajoutez une ligne à README.md sur la branche docs/pipeline-badge. Ouvrez ensuite une demande de tirage de docs/pipeline-badge vers main. Montrez qu’elle a exécuté les deux vérifications, n’a rien poussé et a été fusionnée seulement après leur réussite. Montrez ensuite l’exécution déclenchée sur main par la fusion, avec ses preuves.

Partie 2 : un guide de correspondance Azure DevOps. Plusieurs équipes utilisent Azure Pipelines (une composante d’Azure DevOps) plutôt que GitHub Actions. Les idées sont les mêmes, mais les noms diffèrent. Rédigez un tableau qui associe chaque partie de votre pipeline à son nom dans Azure Pipelines. Incluez le fichier, les déclencheurs, les tâches et leur ordre, les étapes, les exécuteurs, le cache et le rapport de test. Incluez aussi les artefacts, les secrets et les variables, les environnements et les approbations, ainsi que la connexion au nuage. Ajoutez un pipeline qui démarre après un autre et les vérifications obligatoires. Vérifiez chaque nom Azure DevOps sur learn.microsoft.com, et inscrivez la date de votre vérification.

Afficher un modèle de réponse

Partie 1. Dans le dépôt de référence, la demande de tirage no 1 sert de modèle. Son exécution 37664331105 a exécuté Lint, test, audit (43 s) et Build, smoke-test and push the image (construire, tester sommairement et pousser l’image). Cette deuxième tâche a pris 32 s. Chaque étape du Dockerfile indiquait CACHED (en cache), sans envoi, donc sans artefact build-info. Après la fusion, l’exécution 37664536375 sur main a poussé sha256:a8af875a… avec l’étiquette sha-43de57b, son build-info et son attestation. La personne qui révise note la partie 1 démontrée si l’exécution de la demande de tirage n’a rien poussé. La fusion doit avoir attendu les deux vérifications obligatoires. L’exécution sur main doit aussi avoir les artefacts reports et build-info, ainsi qu’une attestation vérifiée.

Partie 2. Le cours a vérifié ces noms sur learn.microsoft.com. Les pages : concepts clés, tâches, dépôts GitHub et déclencheurs, agents, agents hébergés par Microsoft, mise en cache, résultats des tests, artefacts de pipeline, commandes de journalisation, groupes de variables, variables secrètes, approbations et vérifications, connexion à Azure, déclencheurs de pipeline, stratégies de branche.

Ce cours (GitHub Actions) Azure Pipelines Note
Fichier de flux de travail .github/workflows/ci.yml Fichier de pipeline YAML, souvent azure-pipelines.yml Un pipeline comporte des phases; « chaque phase contient une ou plusieurs tâches ». Un pipeline à une phase peut directement énumérer jobs:.
on: pull_request et on: push avec branches: Déclencheur pr: et trigger: (déclencheur CI) Les deux énumèrent des branches, par exemple trigger: avec - main.
Tâche, runs-on: ubuntu-24.04 Tâche, pool: avec vmImage: Les exemples de la documentation utilisent vmImage: 'ubuntu-latest'.
needs: checks dependsOn: checks Sans cela, les tâches Azure Pipelines « s’exécutent en parallèle ». Les tâches GitHub sans needs s’exécutent aussi en parallèle.
if: env.PUSH == 'true', if: always() condition:, par exemple succeeded(), failed(), always() Par défaut, une tâche s’exécute seulement si les tâches dont elle dépend ont réussi.
Étape : run: ou uses: pour une action Étape : un script ou une task (tâche prédéfinie) « Une tâche prédéfinie est un script préassemblé qui effectue une action », par exemple PublishTestResults@2.
Exécuteur hébergé par GitHub, une nouvelle machine virtuelle par tâche Microsoft-hosted agent (agent hébergé par Microsoft) « Vous obtenez une nouvelle machine virtuelle pour chaque tâche du pipeline. »
Exécuteur autohébergé Self-hosted agent (agent autohébergé), dans un groupe d’agents Le même avertissement s’applique : il conserve son état entre les tâches.
setup-python avec cache: pip Tâche prédéfinie Cache@2 « Les caches expirent après sept jours sans activité. »
pytest --junitxml + artefact reports PublishTestResults@2 avec testResultsFormat: 'JUnit' Azure DevOps affiche aussi les résultats dans un onglet de tests.
Artefact (upload-artifact, download-artifact) Pipeline artifact (artefact de pipeline) : mots-clés publish / download, ou PublishPipelineArtifact@1 / DownloadPipelineArtifact@2 build-info.json serait un artefact de pipeline.
$GITHUB_STEP_SUMMARY Commande de journalisation ##vso[task.uploadsummary] Elle ajoute du Markdown au résumé de construction.
GITHUB_TOKEN avec permissions: System.AccessToken Le jeton de l’exécution.
Secrets, variables (secrets.*, vars.*) Variable groups (groupes de variables, dans Library) et secret variables (variables secrètes) Une icône de cadenas marque un secret, qui est masqué dans les journaux. Par défaut, les constructions de demandes de tirage provenant de dépôts dérivés ne reçoivent pas les secrets.
Environnements avec personnes approbatrices obligatoires (module 3) Environments (environnements) avec approvals and checks (approbations et vérifications) La vérification qui exige une personne s’appelle Approvals (approbations). Les autres sont Branch control (contrôle des branches) et Exclusive lock (verrou exclusif).
Connexion OIDC avec azure/login et un identifiant fédéré (module 3) service connection (connexion de service) Azure Resource Manager avec workload identity federation (fédération d’identité de charge de travail) « La fédération d’identité de charge de travail élimine le besoin de secrets et de gestion des secrets. »
workflow_run : Deploy démarre après CI (module 3) Pipeline resource trigger (déclencheur de ressource de pipeline) : resources: → pipelines: avec trigger: true Il démarre « quand une version du pipeline source se termine ».
Ensemble de règles avec vérifications obligatoires Pour Azure Repos : branch policies (stratégies de branche) avec Build validation (validation de construction) réglée sur Required (obligatoire). Pour un dépôt GitHub : la vérification d’état du pipeline dans les règles GitHub Les stratégies de branche signifient aussi que « tous les changements doivent passer par des demandes de tirage ».
Mode strict : les branches doivent être à jour Expiration de construction de Build validation : Immediately when main is updated (immédiatement quand main est mis à jour) La construction de la demande de tirage s’exécute de nouveau après un changement sur main.
Attestation de provenance, gh attestation verify Aucune correspondance dans ce guide Consultez la documentation Azure DevOps actuelle avant de la planifier.

La personne qui révise note la partie 2 démontrée si chaque ligne du pipeline de l’apprenant a un nom Azure Pipelines et une date. Chaque nom doit avoir un lien vers la page que l’apprenant a vérifiée. Une supposition sans source est partiellement démontrée.

Vérifiez votre compréhension

Rappel : que reste-t-il après la fin d'une tâche sur un exécuteur hébergé par GitHub, et que perd-on? Donnez un exemple de chaque cas dans ce pipeline.

Afficher la réponse

La machine virtuelle et tout son contenu sont perdus : le code extrait, les paquets installés, l’image ticket-api:candidate et le conteneur actif. Ce que le flux de travail a enregistré hors de la machine virtuelle reste. Cela comprend les caches : les téléchargements pip, 44 Mo, et les couches BuildKit avec type=gha. Les artefacts (reports et build-info) restent aussi. Il en va de même pour l’image dans GHCR, l’attestation, les journaux et le résultat de la tâche. needs: utilise ce résultat pour démarrer la tâche suivante.

Vérifiez votre compréhension

Prédiction : Sam fusionne une demande de tirage qui modifie seulement README.md. L'exécution sur main pousse-t-elle une nouvelle image, et son empreinte est-elle identique à la précédente? Qu'indique alors build-info.json?

Afficher la réponse

Oui, l’exécution sur main exécute les deux tâches et pousse une nouvelle image. Son étiquette contient sha- suivi du commit de fusion. Ses couches viennent du cache, car le Dockerfile ne copie pas README.md. Mais l’empreinte est différente. Les étiquettes font partie de l’image. Elles contiennent le nouveau commit (revision), une nouvelle heure created et la nouvelle étiquette. Le dépôt de référence l’a montré avec la demande de tirage no 1 : aucun changement dans l’application, mais quand même sha256:a8af875a… plutôt que sha256:6c9bb4ed…. build-info.json enregistre la nouvelle empreinte avec le nouveau commit et la nouvelle exécution. C’est correct : chaque empreinte mène à exactement un commit.

Vérifiez votre compréhension

Vérification finale : reproduisez une vérification en échec de votre pipeline sur votre ordinateur. Retrouvez ensuite le commit source de votre image la plus récente sur main. Montrez les commandes et le résultat de chacune.

Afficher la réponse

Reproduire : par exemple, retirez from None dans ticket_api/config.py et exécutez scripts/check.sh (ou ruff check .). L’exécution s’arrête à Lint (analyse statique) avec B904 à ticket_api/config.py:28:13, Found 1 error. (une erreur trouvée) et le code de sortie 1. C’est le même constat que dans la première exécution rouge sur GitHub. Exécutez ensuite git restore ticket_api/config.py. Retracer : docker buildx imagetools inspect ghcr.io/<you>/ticket-api-ci:sha-<7> donne l’empreinte. gh attestation verify oci://ghcr.io/<you>/ticket-api-ci@<digest> --owner <you> réussit et, avec --format json, indique l’exécution et le commit. gh run download <run> -n build-info donne la même empreinte et le même commit. La page de l’exécution montre les deux tâches vertes et l’artefact reports. git show <commit> montre le code. La personne qui révise note le résultat démontré si l’échec a été reproduit localement avec le même code de règle qu’en CI. Les deux chemins doivent aussi indiquer le même commit.

Récapitulatif

  • Les preuves répondent aux questions d’une personne qui n’était pas là. Conservez le rapport de test (reports), le registre de l’empreinte, du commit et de l’exécution (build-info), l’attestation signée et les étiquettes.
  • if: always() conserve le rapport d’une exécution en échec. Les preuves de construction existent seulement si une image a été poussée.
  • gh attestation verify vérifie la signature et une politique. Une politique plus stricte, comme une branche, peut refuser une image.
  • Retracez toute empreinte : build-info.json → exécution → commit, avec l’attestation comme deuxième chemin signé.
  • L’ensemble de règles Protect main exige une demande de tirage et les deux vérifications, avec la branche à jour. Une vérification obligatoire est un nom : si vous renommez une tâche, mettez à jour l’ensemble de règles.

Réinitialisation. Sur votre ordinateur : docker image rm ticket-api:check, puis rm -rf reports evidence build-info.json junit.xml dans le projet. Pour réinitialiser les fichiers du projet, copiez l’instantané end-of-m02 par une demande de tirage, comme dans Utiliser un instantané. Un instantané ne contient aucun paramètre GitHub : recréez l’ensemble de règles Protect main s’il manque. Pour recommencer le module avec un nouveau dépôt, supprimez l’ancien sur GitHub (Settings, au bas de la page). Supprimez ensuite son paquet dans Package settings (paramètres du paquet).

Le module. Vous avez commencé avec des vérifications qui s’exécutaient seulement sur le Mac d’Amira. Maintenant, chaque changement lance l’analyse statique, 25 tests et une vérification des vulnérabilités sur un exécuteur propre. Le pipeline construit ensuite une image, la teste sommairement et la pousse par empreinte. Chaque image a des preuves qui mènent à son commit, et main n’accepte aucun changement sans les vérifications. Le module 3 prend cette empreinte et la promeut, sans modification, en préproduction, puis en production.

Leçon suivante : Séparer la configuration