GitHub Actions essentials: workflow, job et step

1. Vue d’ensemble

GitHub Actions est un outil CI/CD intégré à GitHub qui vous permet d’automatiser les builds, tests et déploiements sans avoir besoin d’un serveur CI dédié. Cet article explique trois concepts clés : workflow, job et step, avec des exemples concrets pour un projet Spring Boot utilisant Maven.

Après sa lecture, vous saurez écrire un fichier workflow qui exécute les tests automatiquement à chaque push de code, comprendre comment les jobs s’exécutent en parallèle ou séquentiellement, et déboguer quand un workflow échoue.

2. Qu’est-ce qu’un workflow?

Un workflow est un fichier YAML situé dans le dossier .github/workflows/ de votre dépôt. Chaque workflow définit quand exécuter (trigger) et ce qu’il faut faire (jobs).

# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
      - name: Build with Maven
        run: mvn -B package

Explication :

  • name au début du fichier est le nom affiché sur l’onglet Actions de GitHub.
  • on déclare le trigger : ici le workflow s’exécute lors d’un push ou d’une pull_request sur la branche main.
  • jobs.build est un job unique nommé build, exécuté sur la machine virtuelle ubuntu-latest.

Remarque : Le fichier doit être situé dans le bon chemin .github/workflows/*.yml, sinon GitHub ne reconnaîtra pas le workflow.

3. Job : unité d’exécution indépendante

Un workflow peut avoir plusieurs jobs. Chaque job s’exécute sur son propre runner (machine virtuelle), par défaut les jobs s’exécutent en parallèle à moins que vous ne déclariez needs.

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: mvn -B test

  deploy:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - run: echo "Deploying application..."

Explication :

  • test et deploy sont deux jobs distincts, chaque job a son propre environnement runner sain, sans partage de fichiers système entre eux.
  • needs: test force deploy à attendre que test réussisse avant de s’exécuter — créant une relation séquentielle.
  • Sans needs, GitHub Actions exécutera test et deploy en même temps.

3.1. Partager des données entre jobs

Puisque chaque job a son propre runner, pour transférer des fichiers d’un job à un autre, vous devez utiliser artifacts.

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: mvn -B package
      - uses: actions/upload-artifact@v4
        with:
          name: app-jar
          path: target/*.jar

  deploy:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: app-jar

Explication :

  • upload-artifact enregistre le fichier .jar du job build dans le stockage temporaire de GitHub.
  • download-artifact dans le job deploy récupère ce fichier sur le nouveau runner.
  • C’est l’unique moyen de transférer des données entre jobs (les variables d’environnement ordinaires ne fonctionnent pas).

4. Step : plus petite unité d’action

Une step est une action spécifique dans un job, exécutée séquentiellement de haut en bas. Il existe deux types de steps principaux : utiliser uses (appeler une action prédéfinie) ou utiliser run (exécuter une commande shell).

steps:
  - name: Checkout code
    uses: actions/checkout@v4

  - name: Cache Maven dependencies
    uses: actions/cache@v4
    with:
      path: ~/.m2/repository
      key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}

  - name: Run tests
    run: mvn -B test

  - name: Print result
    run: echo "Test step finished with status ${{ job.status }}"

Explication :

  • La step Checkout code utilise l’action prédéfinie actions/checkout pour récupérer le code source sur le runner.
  • La step Cache Maven dependencies met en cache le répertoire ~/.m2/repository, ce qui accélère les builds suivants en évitant de retélécharger les dépendances.
  • La key du cache est basée sur le hash de pom.xml — quand pom.xml change, l’ancien cache est rejeté et un nouveau cache est créé.
  • La dernière step utilise run pour exécuter une commande shell ordinaire, et peut référencer des contextes comme ${{ job.status }}.

5. Variables d’environnement et secrets

Vous avez souvent besoin de transmettre des configurations sensibles (clés API, tokens) au workflow sans les coder en dur dans le fichier YAML.

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      SPRING_PROFILES_ACTIVE: prod
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to server
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        run: |
          echo "Deploying with profile $SPRING_PROFILES_ACTIVE"
          curl -H "Authorization: Bearer $DEPLOY_TOKEN" https://api.example.com/deploy

Explication :

  • env au niveau du job s’applique à toutes les steps de ce job (SPRING_PROFILES_ACTIVE).
  • env au niveau de la step s’applique uniquement à cette step (DEPLOY_TOKEN).
  • secrets.DEPLOY_TOKEN est récupéré depuis Settings → Secrets and variables → Actions du dépôt, la valeur réelle n’apparaît jamais dans les logs.

Remarque : Ne jamais afficher directement la valeur d’un secret avec echo $DEPLOY_TOKEN dans les logs — GitHub le masquera automatiquement, mais il est préférable de ne pas afficher les secrets.

6. Matrix : exécuter plusieurs versions en même temps

Quand vous devez tester sur plusieurs versions de Java ou d’OS, matrix vous évite d’écrire des jobs en répétition.

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        java: [17, 21]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: ${{ matrix.java }}
          distribution: 'temurin'
      - run: mvn -B test

Explication :

  • strategy.matrix.java déclare une liste de valeurs [17, 21].
  • GitHub Actions crée automatiquement 2 jobs en parallèle, chaque job s’exécutant avec une version Java correspondante.
  • Cette approche économise les efforts d’écriture YAML répétitif, idéale quand vous devez garantir que le code fonctionne bien dans plusieurs environnements.

7. Tester le workflow avec des tests simples

Le workflow n’est pas testé directement avec JUnit, mais vous devez vous assurer que l’étape mvn test du workflow exécute réellement la suite de tests de votre projet. Voici un exemple de test simple pour OrderService que la CI exécutera :

class OrderServiceTest {

    private final OrderService orderService = new OrderService();

    @Test
    void shouldCalculateTotalPriceCorrectly() {
        Order order = new Order("ORD-001", List.of(
                new OrderItem("Keyboard", 2, BigDecimal.valueOf(50)),
                new OrderItem("Mouse", 1, BigDecimal.valueOf(20))
        ));

        BigDecimal total = orderService.calculateTotal(order);

        assertThat(total).isEqualByComparingTo(BigDecimal.valueOf(120));
    }
}

Explication :

  • Ce test se trouve dans src/test/java et sera exécuté par mvn -B test dans la step du workflow automatiquement.
  • Si le test échoue, la step Run tests signale une erreur (code de sortie différent de 0), le job s’arrête et les jobs dépendants (needs) ne s’exécutent pas.
  • C’est pourquoi la CI est utile : le code erroné est bloqué avant sa fusion dans main.

Résultat lors de l’exécution sur l’interface GitHub Actions (onglet Actions) :

✓ Extraction du code
✓ Configuration de JDK 21
✓ Build avec Maven
✓ Exécution des tests (shouldCalculateTotalPriceCorrectly RÉUSSI)

8. Erreurs courantes

  • Mauvaise indentation YAML : GitHub Actions utilise YAML, une mauvaise indentation d’un seul espace fait qu’un job ou une step n’est pas reconnu, ce qui génère généralement une erreur « Invalid workflow file ».
  • Oublier runs-on : chaque job doit obligatoirement avoir runs-on, son absence fait échouer le workflow lors de l’analyse.
  • Utiliser une variable d’environnement d’un job dans un autre : env et les variables de sortie ne sont pas automatiquement transmises entre jobs, il faut utiliser outputs ou artifact.
  • Cache key ne change pas quand les dépendances changent : si la key du cache est trop générale (par exemple juste maven-cache), vous utiliserez accidentellement un ancien cache contenant des dépendances obsolètes.
  • Exécuter un workflow lourd sur chaque push : sans limiter paths ou branches, chaque petit commit (modification de README) déclenche toute la pipeline build + test, consommant des minutes d’exécution Actions (avec limite gratuite).

Remarque : Si votre projet n’a besoin que d’un build simple et n’a pas plusieurs environnements, n’utilisez pas matrix ou artifact — ajouter une complexité inutile rendra le workflow difficile à maintenir.

9. Conclusion

Un workflow est un fichier YAML qui définit toute la pipeline, un job est un bloc d’exécution indépendant s’exécutant sur son propre runner, et une step est chaque action spécifique dans un job. Comprendre ces trois niveaux vous aide à organiser votre pipeline CI/CD clairement : quels jobs s’exécutent en parallèle, quels jobs doivent s’attendre mutuellement via needs, et quand utiliser artifact pour partager des données.

Au départ, gardez votre workflow simple (un job, quelques steps de base : checkout, setup Java, test). À mesure que votre projet grandit, ajoutez matrix, cache, et séparez build/test/deploy en jobs distincts pour faciliter la gestion et le débogage.

github-actionsci-cddevopsjavaautomation