Aller au contenu
Actions composites et workflows réutilisables

Actions composites et workflows réutilisables

300 Concevoir ⏱ 1 h 10 github-actionsci-cd

À la fin, vous saurez

  • Choisir entre ancre YAML, action composite et workflow réutilisable selon ce qu'il faut factoriser
  • Écrire une action composite avec entrées et sorties, et l'appeler depuis le dépôt
  • Transformer un workflow en workflow réutilisable, avec entrées typées, sorties et permissions
  • Prévoir ce qui traverse, ou non, la frontière d'un workflow réutilisable : variables, secrets, contexte, permissions
  • Organiser un dépôt central de workflows versionnés pour plusieurs équipes

Prérequis

Testé avec actionlint 1.7.12 actions/runner 2.337.0 zizmor 1.30.1 , vérifié le 1 octobre 2026

Pourquoi

Après six leçons, le workflow de Signalements répète les mêmes trois étapes dans deux jobs (installer Python, activer le cache, installer les dépendances), et la construction d'image fait cent lignes que toute autre application voudrait réutiliser. Copier, c'est simple. C'est aussi le début d'une dérive que l'on peut observer chez Lyneko : sept applications déploient sur Kapsule avec le même enchaînement (construire l'image, la publier sur le registre Scaleway, écrire l'étiquette pour Argo CD), chacune avec sa propre copie du workflow. Au 1er octobre 2026, ces sept copies ne sont déjà plus identiques : deux n'ont pas de bloc permissions au niveau du workflow, cinq publient une étiquette latest (pour au moins une image) et deux non, une seule vérifie les demandes de fusion, deux variantes différentes de la commande qui écrit l'étiquette déployée coexistent (leçon 8), et six référencent encore actions/checkout@v4, trois versions majeures en retard, quand la septième vient de passer à une empreinte. Aucun de ces écarts n'a été décidé : chacun est un oubli, ou une correction, faite dans une seule copie.

Factoriser répond à ce problème, à condition de le faire avec le bon outil. GitHub Actions en propose trois, qui ne factorisent pas la même chose, et qui n'ont pas les mêmes frontières. Mal choisi, l'outil rend le pipeline plus difficile à lire qu'avant, ou fait circuler des secrets là où ils n'ont rien à faire.

Les concepts

Trois niveaux de réutilisation

Ancre YAMLAction compositeWorkflow réutilisable
Factoriseun fragment de YAMLune suite d'étapesun ou plusieurs jobs
Portéeun seul fichiern'importe quel dépôtn'importe quel dépôt (selon visibilité)
S'appelle depuisle même fichier (*nom)une étape : uses:un job : jobs.<id>.uses:
Choisit son runnernonnon (celui du job appelant)oui (runs-on dans ses jobs)
Services, matricenonnonoui
Secretsceux du fichierseulement ceux passés en entréesecrets: explicites ou inherit
Journauxnormauxregroupés sous une étapeun job par job appelé, préfixé par l'appelant

Les ancres YAML (&nom pour définir, *nom pour réutiliser) sont acceptées dans les workflows depuis septembre 2025. Elles évitent de répéter un bloc d'env ou d'options de service dans un même fichier, et rien de plus : ce n'est que du YAML, développé avant toute interprétation par GitHub.

Une action composite est un fichier action.yml qui déclare des entrées, des sorties, et une suite d'étapes. Elle s'insère dans un job comme une seule étape, et ses étapes s'exécutent sur le runner du job, dans son espace de travail. C'est l'outil pour « préparer l'environnement », « publier un rapport », « se connecter au registre ».

Un workflow réutilisable est un workflow ordinaire dont le déclencheur est workflow_call. Un job d'un autre workflow l'appelle, et ses jobs s'insèrent dans le run appelant, avec leurs propres runners, matrices et services. C'est l'outil pour « construire et publier une image », « déployer sur Kapsule » : une chaîne complète, que des équipes différentes appellent avec des paramètres différents.

Ce qui traverse la frontière

La source de la plupart des surprises est ce qui passe, ou ne passe pas, entre l'appelant et l'appelé.

Pour une action composite :

  • les entrées sont des chaînes, lues par ${{ inputs.nom }} ;
  • les variables d'environnement du job appelant sont visibles par ses étapes (même processus de runner, même job) ;
  • les secrets ne sont pas accessibles par le contexte secrets : il faut les passer en entrée ;
  • chaque étape run: doit déclarer son shell, il n'y a pas de valeur par défaut héritée du workflow ;
  • les sorties se déclarent avec une valeur explicite, tirée des sorties de ses étapes.

Pour un workflow réutilisable :

  • les entrées sont typées (string, boolean, number seulement) ;
  • les variables env du workflow appelant ne sont pas transmises ; inversement, celles de l'appelé ne remontent pas. Seules les sorties traversent ;
  • les secrets ne passent que s'ils sont transmis : un par un (secrets: { NOM: ${{ secrets.NOM }} }) ou en bloc (secrets: inherit, réservé aux workflows de la même organisation) ;
  • le contexte github est celui de l'appelant : github.event_name, github.ref, github.repository décrivent le run qui appelle, pas le dépôt qui héberge le workflow ;
  • les permissions du GITHUB_TOKEN sont fixées par le job appelant ; l'appelé peut seulement les réduire, jamais les élargir ;
  • jusqu'à 10 niveaux d'imbrication, et 50 workflows réutilisables distincts par fichier appelant, arbre compris (ces limites étaient de 4 et 20 jusqu'en novembre 2025).

Où vivent les briques

Une action ou un workflow réutilisable peut vivre dans le dépôt qui l'utilise, ou dans un autre dépôt :

uses: ./.github/actions/preparer-python                                  # même dépôt, après checkout
uses: $/.github/actions/preparer-python                                  # même dépôt, au commit en cours
uses: lyneko-team/workflows/.github/workflows/image.yml@<empreinte>     # autre dépôt, version épinglée

La forme ./ désigne un chemin dans l'espace de travail : elle exige que le dépôt ait été récupéré par checkout avant l'étape, et elle utilise ce qui a été récupéré. La forme $/, apparue en juillet 2026, désigne le même dépôt, au commit exact du workflow en cours d'exécution, sans checkout. Elle règle un problème de cohérence important pour les dépôts centraux : un workflow réutilisable appelé à une empreinte précise, qui utilise ses propres actions avec $/, utilise les actions de cette empreinte, et pas celles de la branche par défaut ou de l'espace de travail. Elle exige un runner en version 2.336.0 ou plus récente.

Pour un dépôt privé, l'accès se règle dans les réglages Actions du dépôt qui héberge les briques : par défaut, ses workflows et actions ne sont pas accessibles depuis les autres dépôts. Un dépôt appelant public ne peut utiliser que des briques publiques.

En pratique

Une action composite pour préparer Python

Créez .github/actions/preparer-python/action.yml :

name: Préparer Python
description: >-
  Installe une version de Python, active le cache de pip et installe les
  dépendances de développement de Signalements.
inputs:
  python-version:
    description: "Version de Python, entre guillemets (\"3.14\")"
    required: true
  prerelease:
    description: "Accepter une préversion si aucune version finale n'existe"
    required: false
    default: "false"
  dependances:
    description: "Fichier de dépendances à installer"
    required: false
    default: requirements-dev.txt
outputs:
  python-version:
    description: "Version exacte installée"
    value: ${{ steps.python.outputs.python-version }}
runs:
  using: composite
  steps:
    - id: python
      uses: actions/setup-python@v7
      with:
        python-version: ${{ inputs.python-version }}
        allow-prereleases: ${{ inputs.prerelease }}
        cache: pip
        cache-dependency-path: requirements*.txt

    - name: Installer les dépendances
      shell: bash
      env:
        DEPENDANCES: ${{ inputs.dependances }}
      run: pip install --disable-pip-version-check -r "$DEPENDANCES"

    - name: Afficher l'environnement
      shell: bash
      run: |
        python --version
        pip --version

Les points à noter :

  • runs.using: composite fait de ce fichier une action composite. Une action peut aussi être écrite en JavaScript ou fournie comme image Docker : c'est le sujet de la leçon suivante.
  • Les entrées sont des chaînes, même prerelease : "false" et non false. setup-python les interprète.
  • shell: bash sur chaque étape run:. Le bloc defaults du workflow appelant ne s'applique pas à l'intérieur d'une action ; sans shell, GitHub refuse l'action au moment de l'exécuter.
  • L'entrée passe par env:, comme on l'a appris à la leçon 3 : le nom de fichier vient de l'appelant, il ne doit pas être interpolé dans le script.
  • La sortie reprend celle de setup-python, pour que l'appelant puisse afficher ou comparer la version exacte (3.14.8, par exemple).

Dans ci.yml, les deux jobs qui préparaient Python l'appellent désormais :

      - name: Préparer Python
        uses: ./.github/actions/preparer-python
        with:
          python-version: ${{ env.PYTHON_VERSION }}

et, dans la matrice d'intégration :

      - uses: ./.github/actions/preparer-python
        with:
          python-version: ${{ matrix.python }}
          prerelease: ${{ matrix.experimental }}

Dix lignes de moins, et surtout une seule définition de la préparation : la prochaine montée de version de setup-python se fera à un endroit.

actionlint lit les actions locales et vérifie les entrées qu'on leur passe :

$ actionlint
.github/workflows/ci.yml:125:11: input "pre-release" is not defined in action "Préparer Python" defined at "./.github/actions/preparer-python". available inputs are "dependances", "prerelease", "python-version" [action]
    |
125 |           pre-release: ${{ matrix.experimental }}
    |           ^~~~~~~~~~~~

En revanche, il ne vérifie pas le contenu de action.yml lui-même : dans le même essai, une étape de l'action privée de son shell est passée inaperçue. Relisez les actions composites avec soin, et testez-les dans un workflow avant de les partager.

./ ou $/ : quand les outils divergent

zizmor recommande désormais la nouvelle syntaxe pour toute référence au même dépôt :

$ uvx zizmor --offline .github/workflows/
help[self-repository]: use GitHub's dedicated self-repository syntax
  --> .github/workflows/ci.yml:58:15
   |
57 |       - name: Préparer Python
   |         --------------------- this step
58 |         uses: ./.github/actions/preparer-python
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use '$/...' instead of './...'
   |
   = note: audit confidence → High
   = note: this finding has an auto-fix
   = help: audit documentation → https://docs.zizmor.sh/audits/#self-repository

Mais actionlint 1.7.12, publié en mars 2026, ne la connaît pas encore :

$ actionlint .github/workflows/ci.yml
.github/workflows/ci.yml:58:15: specifying action "$/.github/actions/preparer-python" in invalid format because ref is missing. available formats are "{owner}/{repo}@{ref}" or "{owner}/{repo}/{path}@{ref}" [action]
   |
58 |         uses: $/.github/actions/preparer-python
   |               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Les deux outils ont raison sur leur terrain : $/ est la forme recommandée par GitHub, et actionlint n'a pas encore été mis à jour. Pour Signalements, dont les actions locales sont toujours utilisées après un checkout du même commit, ./ et $/ désignent le même code ; on garde ./ tant qu'actionlint ne valide pas $/, plutôt que de désactiver la vérification des actions. Pour un dépôt central de workflows, où la cohérence entre un workflow réutilisable et ses actions est essentielle, $/ vaut d'accepter une exception documentée dans la configuration d'actionlint.

Un workflow réutilisable pour l'image

Le workflow d'image de la leçon 6 est un bon candidat : il ne dépend presque pas de Signalements, et toute application conteneurisée voudrait le même. On le déplace dans .github/workflows/image-multiarch.yml en remplaçant ses déclencheurs par workflow_call :

name: Image multi-architecture (réutilisable)

on:
  workflow_call:
    inputs:
      image:
        description: "Nom de l'image, sans étiquette ; par défaut ghcr.io/<propriétaire>/<dépôt>"
        type: string
        default: ""
      plateformes:
        description: "Liste JSON des plateformes"
        type: string
        default: '["linux/amd64", "linux/arm64"]'
      publier:
        description: "Publier et assembler l'index (faux pour une demande de fusion)"
        type: boolean
        default: true
    outputs:
      empreinte:
        description: "Empreinte de l'index publié"
        value: ${{ jobs.assembler.outputs.empreinte }}

permissions:
  contents: read

defaults:
  run:
    shell: bash

jobs:
  construire:
    name: Construire (${{ matrix.plateforme }})
    runs-on: ${{ case(matrix.plateforme == 'linux/arm64', 'ubuntu-24.04-arm', 'ubuntu-24.04') }}
    # ...
    strategy:
      fail-fast: true
      matrix:
        plateforme: ${{ fromJSON(inputs.plateformes) }}
    steps:
      # ...
      - name: Préparer les noms
        env:
          PLATEFORME: ${{ matrix.plateforme }}
          IMAGE_DEMANDEE: ${{ inputs.image }}
        run: |
          image=${IMAGE_DEMANDEE:-ghcr.io/$GITHUB_REPOSITORY}
          echo "IMAGE=${image,,}" >> "$GITHUB_ENV"
          echo "PAIRE=${PLATEFORME//\//-}" >> "$GITHUB_ENV"
      # ... les conditions github.event_name deviennent inputs.publier

  assembler:
    name: Assembler l'index multi-architecture
    needs: construire
    if: inputs.publier
    outputs:
      empreinte: ${{ steps.index.outputs.empreinte }}
    # ... inchangé, avec la même préparation du nom de l'image

Le reste des étapes est celui de la leçon 6. Les changements portent tous sur la frontière :

  • Trois entrées typées remplacent ce que le workflow lisait dans l'événement. publier remplace github.event_name != 'pull_request' : le workflow réutilisable ne doit pas décider seul s'il publie, c'est à l'appelant de le dire. plateformes est une chaîne JSON, puisque les entrées ne peuvent pas être des listes, convertie par fromJSON pour la matrice.
  • Le runner est choisi d'après la plateforme avec case, au lieu d'une liste d'associations.
  • La sortie empreinte remonte celle du job assembler, qui doit lui-même la déclarer en sortie de job : une sortie de workflow réutilisable ne peut lire que des sorties de jobs.
  • Le nom de l'image a une valeur par défaut calculée à partir du dépôt appelant : GITHUB_REPOSITORY est celui du run, donc de l'appelant.

Le workflow image.yml ne fait plus qu'appeler :

name: Image

on:
  push:
    branches: [main]
    tags: ["v*"]
  pull_request:

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
  contents: read

jobs:
  image:
    uses: ./.github/workflows/image-multiarch.yml
    permissions:
      contents: read
      packages: write
      id-token: write
      attestations: write
      artifact-metadata: write
    with:
      publier: ${{ github.event_name != 'pull_request' }}

  afficher:
    needs: image
    if: github.event_name != 'pull_request'
    runs-on: ubuntu-24.04
    timeout-minutes: 2
    steps:
      - env:
          EMPREINTE: ${{ needs.image.outputs.empreinte }}
        run: |
          echo "Index publié : $EMPREINTE"
  • Le job appelant n'a ni runs-on ni steps : seulement uses, with, secrets, permissions, et quelques autres clés (needs, if, strategy, concurrency, name, cache-mode).
  • Les permissions sont accordées par l'appelant, au plus juste de ce que l'appelé déclare. Si le job appelant ne précisait rien, l'appelé recevrait les permissions par défaut du dépôt ; s'il accordait moins que ce que l'appelé demande, le run échouerait à la validation.
  • La sortie se lit par needs.image.outputs.empreinte, comme une sortie de job ordinaire.
  • La concurrence reste chez l'appelant. La documentation met en garde contre un piège : dans un workflow réutilisable, github.workflow vaut le nom du workflow appelant ; un même groupe ${{ github.workflow }} avec cancel-in-progress: true des deux côtés ferait annuler l'appelant par l'appelé.

actionlint vérifie aussi cette frontière, en lisant le workflow appelé :

$ actionlint .github/workflows/image.yml
.github/workflows/image.yml:26:7: input "publie" is not defined in "./.github/workflows/image-multiarch.yml" reusable workflow. defined inputs are "image", "plateformes", "publier" [workflow-call]
   |
26 |       publie: ${{ github.event_name != 'pull_request' }}
   |       ^~~~~~~
.github/workflows/image.yml:35:26: property "digest" is not defined in object type {empreinte: string} [expression]
   |
35 |           EMPREINTE: ${{ needs.image.outputs.digest }}
   |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~

Et au passage, en écrivant ce workflow, deux pièges de la leçon 2 ont été reproduits par mégarde, et attrapés par actionlint avant toute poussée : une condition if: !inputs.publier sans ${{ }}, et un echo "Index publié : $EMPREINTE" non protégé, que la typographie française transforme en clé YAML.

Les ancres pour le reste

Les répétitions qui restent dans un même fichier relèvent des ancres. Par exemple, des variables d'environnement communes à deux jobs :

jobs:
  a:
    runs-on: ubuntu-24.04
    env: &commun
      PYTHONDONTWRITEBYTECODE: "1"
      PIP_DISABLE_PIP_VERSION_CHECK: "1"
    steps:
      - run: env | grep -E '^(PYTHON|PIP)_' | sort
  b:
    runs-on: ubuntu-24.04
    env: *commun
    steps:
      - run: env | grep -E '^(PYTHON|PIP)_' | sort

actionlint les accepte, et un analyseur YAML ordinaire montre ce que GitHub reçoit après développement :

$ python3 -c 'import yaml,json;print(json.dumps(yaml.safe_load(open(".github/workflows/ancres.yml"))["jobs"]["b"]["env"]))'
{"PYTHONDONTWRITEBYTECODE": "1", "PIP_DISABLE_PIP_VERSION_CHECK": "1"}

Restez sobre : une ancre définie dans le premier job et utilisée trois écrans plus bas oblige le lecteur à faire des allers-retours. Au-delà de quelques lignes, une action composite est plus lisible.

Sous le capot

Une action composite est résolue par le runner pendant Set up job : il télécharge le dépôt de l'action (ou lit le chemin local au moment de l'étape), lit action.yml, et exécute ses étapes comme des sous-étapes, chacune avec son propre contexte steps, isolé de celui du job. C'est pourquoi les sorties doivent être déclarées explicitement : une étape interne n'est pas visible du job. Dans l'interface, la composite apparaît comme une étape, dont le journal regroupe ceux de ses sous-étapes.

Un workflow réutilisable est résolu par GitHub, avant tout runner : au moment de créer le run, le service lit le fichier appelé à la référence demandée, vérifie les entrées, les secrets et les permissions, et insère ses jobs dans le graphe du run, sous le nom <job appelant> / <job appelé>. Une erreur de validation (entrée inconnue, permission insuffisante, workflow inaccessible) empêche donc le run de démarrer. Les runners hébergés sont attribués et facturés dans le contexte de l'appelant.

La référence au workflow appelé fait partie de l'identité du job. Elle apparaît dans le contexte job.workflow_ref (ajouté en septembre 2026) et, surtout, dans la revendication job_workflow_ref du jeton OIDC (leçon 10) : un fournisseur de nuage ou une attestation peut exiger que le jeton vienne d'un workflow réutilisable précis, à une version précise. C'est ce qui permet d'atteindre le niveau 3 de construction de SLSA avec les attestations de GitHub : la construction se fait dans un workflow que les équipes appelantes ne peuvent pas modifier, et l'attestation le prouve.

Pièges courants

Can't find 'action.yml', 'action.yaml' or 'Dockerfile' under '...'. Did you forget to run actions/checkout before running your local action? Le message dit tout : une action locale ./... est utilisée avant checkout, ou dans un job qui récupère seulement une partie du dépôt (sparse-checkout). Récupérez le dépôt d'abord, ou passez à $/.

Une variable d'environnement est vide dans le workflow réutilisable. Elle était définie dans l'env du workflow appelant, qui n'est pas transmis. Passez-la en entrée, ou utilisez une variable de configuration (vars).

Un secret est vide dans le workflow réutilisable. Il n'a pas été transmis. secrets: inherit le ferait, mais transmet tous les secrets ; préférez la liste explicite.

Le run ne démarre pas, avec une erreur de la forme Error calling workflow '...'. The nested job 'construire' is requesting 'packages: write', but is only allowed 'packages: read'. Le job appelant n'accorde pas une permission que l'appelé déclare. Les permissions descendent, elles ne montent jamais. (Le message est produit par l'analyseur de workflows, dont le code est publié avec celui de l'agent.)

github.event_name ne vaut pas ce qu'on attend dans le workflow appelé. Il vaut l'événement de l'appelant : push, pull_request... et jamais workflow_call. Ne faites pas dépendre le comportement du workflow réutilisable de l'événement : passez une entrée.

Les entrées booléennes d'une action composite. Une action reçoit des chaînes : if: inputs.prerelease est vrai pour "false" (une chaîne non vide). Comparez explicitement : if: inputs.prerelease == 'true'. Les workflows réutilisables, eux, ont de vrais booléens.

Une modification du workflow central casse toutes les équipes à la fois. C'est la contrepartie de la factorisation. Voir En production.

Sécurité

Une brique réutilisée est du code exécuté avec vos droits. Une action composite tierce s'exécute avec le jeton du job et toutes les variables d'environnement du job ; un workflow réutilisable tiers reçoit les secrets qu'on lui transmet. Les règles d'épinglage de la leçon 11 s'appliquent aux deux : on appelle propriétaire/dépôt/.github/workflows/x.yml@<empreinte complète>, pas @main.

secrets: inherit est un raccourci dangereux. Il transmet tous les secrets du dépôt et de l'organisation accessibles à l'appelant, y compris ceux dont le workflow appelé n'a aucun besoin. zizmor le signale avec l'audit secrets-inherit. La liste explicite documente aussi ce que le workflow consomme.

Le dépôt central devient un point de contrôle. Quiconque peut pousser sur la branche suivie par les appelants peut modifier le pipeline de toutes les applications. Protégez-le comme du code de production : relecture obligatoire, fichier CODEOWNERS désignant l'équipe plateforme, étiquettes de version immuables (immutable releases, disponibles depuis octobre 2025), et appels épinglés par empreinte.

Les permissions ne montent pas. C'est une protection précieuse : un workflow réutilisable ne peut pas s'accorder contents: write si l'appelant ne l'a pas donné. Accordez dans le job appelant exactement ce que l'appelé déclare, et pas write-all par facilité.

En production

Un dépôt de workflows pour l'organisation. Pour les sept applications de Lyneko qui déploient de la même façon, la cible est un dépôt lyneko-team/workflows, qui contiendrait par exemple image-scaleway.yml (construire, publier sur le registre Scaleway, attester) et deployer-argocd.yml (écrire l'étiquette pour Argo CD), et leurs actions composites internes référencées en $/. Chaque application garde un workflow de quelques lignes, qui appelle ces briques avec son nom d'image et son chemin de chart.

Versionner comme une bibliothèque. Les appelants épinglent une empreinte, avec la version en commentaire (@<empreinte> # v2.3.0) ; Dependabot leur propose les nouvelles versions (leçon 11). Les changements incompatibles (une entrée renommée, une permission ajoutée) passent par une nouvelle version majeure, annoncée, avec une période où les deux coexistent. Un workflow central qui suit @main dans toutes les applications transforme chaque commit de l'équipe plateforme en mise en production simultanée de tous les pipelines.

Ne pas tout factoriser. Un workflow réutilisable trop paramétrable (quinze entrées, des conditions sur chacune) devient plus difficile à comprendre que les copies qu'il remplace. Factorisez les chaînes réellement identiques ; laissez dans chaque dépôt ce qui lui est propre (les tests, la matrice), et préférez deux workflows simples à un seul qui fait tout.

Les modèles de workflow. Pour amorcer un nouveau dépôt, un dépôt .github de l'organisation peut publier des modèles (workflow templates), proposés dans l'onglet Actions des nouveaux dépôts. Un modèle est copié, donc il dérive comme toute copie : il sert à démarrer, pas à maintenir. Les appels à des workflows réutilisables, eux, restent à jour.

Exercices

1. Pour chacun de ces besoins, choisissez entre ancre YAML, action composite et workflow réutilisable : (a) les mêmes options --health-* pour deux services d'un même job ; (b) « se connecter au registre Scaleway et configurer Buildx », dans dix dépôts ; (c) « construire, analyser avec Trivy, publier et déployer en recette », dans dix dépôts ; (d) une étape de résumé de tests utilisée dans trois jobs du même workflow.

Solution

(a) Ancre YAML : un fragment répété dans un fichier. (b) Action composite : deux ou trois étapes, insérées dans des jobs qui feront autre chose ensuite. (c) Workflow réutilisable : plusieurs jobs, des runners, probablement des environnements et des secrets propres. (d) Action composite locale, dans le dépôt : trois jobs ne partagent pas de YAML par ancre de façon lisible, et c'est une suite d'étapes.

2. Ce workflow appelant ne fonctionne pas comme prévu : le workflow réutilisable affiche une région vide. Expliquez et corrigez.

env:
  REGION: fr-par
jobs:
  deployer:
    uses: ./.github/workflows/deployer.yml
Solution

L'env du workflow appelant n'est pas transmis au workflow appelé. Deux corrections : déclarer une entrée region dans deployer.yml (on.workflow_call.inputs.region) et la passer avec with: { region: fr-par } ; ou, si la région est commune à toute l'organisation, la définir comme variable de configuration et la lire dans l'appelé par vars.REGION.

3. Ajoutez à l'action preparer-python une entrée booléenne verbeux qui, quand elle vaut vrai, affiche la liste des paquets installés (pip list). Attention au type des entrées.

Solution
inputs:
  verbeux:
    description: "Afficher les paquets installés"
    required: false
    default: "false"
runs:
  using: composite
  steps:
    # ... étapes existantes
    - name: Paquets installés
      if: inputs.verbeux == 'true'
      shell: bash
      run: pip list

La comparaison explicite à 'true' est indispensable : les entrées d'une action sont des chaînes, et if: inputs.verbeux serait vrai pour la chaîne "false".

4. Votre organisation crée lyneko-team/workflows. Rédigez les quatre règles que vous imposeriez à ce dépôt et à ses appelants.

Solution
  1. Relecture obligatoire et CODEOWNERS pour l'équipe plateforme sur tout le dépôt ; pas de poussée directe sur main.
  2. Versions sémantiques publiées en versions immuables ; un changement incompatible change la version majeure et s'accompagne d'une note de migration.
  3. Les appelants épinglent l'empreinte complète, avec la version en commentaire, et laissent Dependabot proposer les mises à jour.
  4. Les workflows du dépôt déclarent des permissions minimales job par job, n'utilisent jamais secrets: inherit, et référencent leurs propres actions par $/ pour rester cohérents à l'empreinte appelée.

5. Dans le workflow réutilisable d'image, que vaut github.repository quand il est appelé depuis le dépôt lyneko-team/pricer, alors qu'il est hébergé dans lyneko-team/workflows ? Quelle conséquence pour le nom d'image par défaut ? Et que vaut job.workflow_ref ?

Solution

github.repository vaut lyneko-team/pricer : le contexte github est celui de l'appelant. Le nom d'image par défaut est donc ghcr.io/lyneko-team/pricer, ce qui est le comportement voulu. job.workflow_ref, lui, désigne le fichier qui définit le job en cours : lyneko-team/workflows/.github/workflows/image-multiarch.yml@<référence appelée>. C'est cette information, reprise dans le jeton OIDC, qui permet de prouver où la construction a eu lieu.

Récapitulatif

  • Ancre YAML pour un fragment dans un fichier, action composite pour une suite d'étapes, workflow réutilisable pour des jobs complets.
  • Une composite reçoit des chaînes, voit l'env du job mais pas les secrets, et exige shell sur chaque run.
  • Un workflow réutilisable reçoit des entrées typées, pas l'env de l'appelant, les secrets transmis explicitement, le contexte github de l'appelant, et des permissions qu'il peut seulement réduire.
  • ./ utilise l'espace de travail (après checkout) ; $/ utilise le même dépôt au commit en cours, sans checkout. actionlint 1.7.12 ne connaît pas encore $/.
  • Un dépôt central de workflows se protège et se versionne comme une bibliothèque ; les appelants épinglent une empreinte.
  • secrets: inherit transmet tout : préférez la liste explicite.

Pour aller plus loin

Voir ma constellation →

Sources