Maximize Page
Tech & DevOps HubEspace Tech & DevOps: Explorez le monde du Dev, du Cloud et des outils DevOps à travers nos articles et discussions Explore the world of development, the cloud and DevOps tools

Mise en place d'un pipeline CI/CD (4/4) - La branche main (final)

Date de l'article:12-12-2025
Github-Actions SonarCloud JaCoCo
Finalisation du pipeline de production avec smoke tests, health check API, push conditionnel de l'image Docker et badges SonarCloud. On détaille les conditions d'exécution spécifiques à main et les différences avec les pipelines develop/staging

L'objectif est, de garantir que la branche [main] hérite des étapes de base de la CI, d'ajouter des tests "post-build" et les vérifications de santé de l'API, d'intégrer et publier les Quality Gates via SonarCloud (Free Plan), de gérer le packaging et la publication des images sur Docker Hub lors d'un push avec tag, ainsi que de définir les conditions du pipeline. Enfin nous ajoutons le badge SonarCloud


Stratégie multi-branches, rôle pour "main"
La branche [main]: Environnement de production et de livraison
→ Est protégé par [develop] et [staging] avec plusieurs “gates” successives (qualité, sécurité, performance, déploiement)
→ Rapide et léger sur develop, plus complet (et plus cher) sur staging, très complet mais moins fréquement mis à jour sur main

Cette séparation rend chaque pipeline plus lisible, plus facile à maintenir et limite les effets de bord entre branches.
     L'idée est de garder un pipeline axé qualité sur develop, et de durcir les contrôles et la partie déploiement sur staging et en production


Mise à jour de la branche main
Checkout code
Set up JDK 17
Cache Maven dependencies
Build Package
Run Unit Tests + JaCoCo
Build Docker Image

Mise en place et étapes du pipeline
LEGEND:

Déja dans le pipeline
 CI de base
 CI de base (modifié)
 Ajout spécial (staging / main)
 Artefacts (upload / download)

[main] reprend donc 100% des étapes de base (develop/staging)
Certaines sont à ajouter, d'autres à adapter:
Checkout code
Set up JDK 17
Check formatting (Spotless)
Initialize CodeQL
Run Tests Maven clean verify
Upload artifact
Perform CodeQL analysis
Scan filesystem with Trivy

Amélioration du pipeline de production
Le workflow est défini dans: (ancien main.yml, renomé en cd-prod.yml)
name: Prod Build, Analyze and Deploy Flashcards App
on:
  push:
    branches: [ main ]
    tags:
      - 'v*.*.*'
  pull_request:
    branches: [ main ]

  permissions:
    contents: write
    actions: write
    security-events: write

Voir le pipeline complet: cd-prod


Le pipeline est déclenché sur les push et pull requests vers la branche main
Il ne crée de release QUE sur le push d'un tag (actuellement v1.1.0)

On lui donne ensuite les permissions:
contents: write permet au workflow de modifier le code
actions: write donne le droit de gérer les exécutions de workflows
security-events: write est requis pour CodeQL
jobs:
  build:
    runs-on: ubuntu-24.04
    services:
      postgres:
        image: postgres:16
        ports:
          - 5432:5432
        env:
          POSTGRES_DB: flashcardsdb
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 10
    env:
      IMAGE_NAME: flashcards
      RELEASE_TAG: ${{ github.ref_name }}
      IS_RELEASE: ${{ startsWith(github.ref, 'refs/tags/v') }}
Le job "build"

Le job tourne sur Ubuntu 24.04


On lance un Service Container pour PostgreSQL,
     indispensable pour les tests


L'option "--health-cmd pg_isready" est cruciale

Elle garantit que l'application Spring Boot ne tentera pas de démarrer avant que PostgreSQL ne soit totalement prêt à accepter des connexions


Dans env, la définition de "IS_RELEASE" via "startsWith"

Est une bonne pratique pour conditionner les étapes suivantes (ex: on ne pousse l'image Docker qu'avec un tag v*)

On ajoute des étapes de base (develop/staging)
steps:
- name: Checkout repository
	uses: actions/checkout@v6.0.1
	with:
	  fetch-depth: 0

- name: Set up JDK 17
	uses: actions/setup-java@v5
	with:
	  distribution: temurin
	  java-version: '17'
	  cache: maven

- name: Validate formatting with Spotless
	run: ./mvnw -q spotless:check

- name: Initialize CodeQL
	uses: github/codeql-action/init@v4.32.2
	with:
	  languages: java

- name: Wait for PostgreSQL
	run: |
	  until pg_isready -h localhost -p 5432 -U postgres; do
		echo "Waiting for PostgreSQL..."
		sleep 2
	  done

- name: Run Tests + JaCoCo
	run: ./mvnw -B clean verify jacoco:report -Djacoco.minimum.coverage=0.80

- name: Upload JaCoCo XML
	uses: actions/upload-artifact@v6
	with:
	  name: jacoco-xml
	  path: target/site/jacoco/jacoco.xml

- name: SonarCloud analysis
	if: github.ref == 'refs/heads/main' || github.event_name == 'pull_request'
	env:
	  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
	  SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
	run: >
	  ./mvnw -B sonar:sonar
	  -Dsonar.host.url=https://sonarcloud.io
	  -Dsonar.organization=val7304
	  -Dsonar.projectKey=${{ secrets.SONAR_PROJECT_KEY }}
	  -Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
	  -Dsonar.qualitygate.wait=true

- name: Perform CodeQL analysis
	uses: github/codeql-action/analyze@v4.32.2

- name: Scan dependencies with Trivy
	uses: aquasecurity/trivy-action@v0.28
	with:
	  scan-type: fs
	  scan-ref: .
	  format: table
	  severity: HIGH,CRITICAL

- name: Compute short SHA
	id: vars
	run: echo "IMAGE_TAG=$(git rev-parse --short HEAD)" >> $GITHUB_ENV

- name: Build Docker image
	run: ci-scripts/docker-build.sh "$IMAGE_NAME" "$IMAGE_TAG"

- name: Scan Docker image with Trivy
	uses: aquasecurity/trivy-action@v0.28
	with:
	  scan-type: image
	  image-ref: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
	  format: table
	  severity: HIGH,CRITICAL
	  exit-code: 1
	  ignore-unfixed: true
	  scanners: vuln

Les étapes:
Checkout code
Set up JDK 17

On ajoute (CI de base)
Validate formatting with Spotless
Initialize CodeQL
Prépare l'analyse statique de sécurité (SAST) de GitHub via CodeQL. Détecte les vulnérabilités logiques (injections SQL, failles XSS) avant la compilation

Wait for PostgreSQL

→ On fait tourner les tests dans une étape:
Run Tests + JaCoCo

avec -Djacoco.minimum.coverage=0.80 on vérifie que la couverture atteint les 80%

La commande verify inclus tous les tests, dont:
Checkstyle + SpotBugs
si c'est ok:
Upload JaCoCo (XML)

Un artifact est produit à la fin de chaque build
jacoco-xml : 3.5 KB


main only:
SonarCloud analysis
Envoie les métriques de qualité et les rapports JaCoCo à SonarCloud. Il applique une "Quality Gate": si le code est jugé "non sain", le pipeline s'arrête

On ajoute (CI base)
Perform CodeQL analysis
Scan dependencies with Trivy

main only:
Compute short SHA
Génère un identifiant unique à 7 caractères basé sur le commit Git actuel. Cela permet un versioning précis de l'image produite
Build Docker image
Scan Docker image (Trivy)
Analyse l'image Docker finale (OS, packages)
Bloque le pipeline si l'image de production contient des failles critiques

Spécificités du Pipeline
Le job est déja très fonctionnal mais, nous allons l'améliorer en ajoutant 2 étapes supplémentaire;
  1. Ajout d'un "Smoke-test", qui validera l'image Docker avant le log in et push sur Docker Hub
  2. La modification de l'étape du "push" de Docker, avec un tag, une vrai release
1. Ajout d'un Smoke Tests + Health Check API (only main)
- name: Run container for smoke tests
  run: |
    docker run --pull=never -d --name flashcards-smoke \
	  --add-host=host.docker.internal:host-gateway \
	  -p 8080:8080 \
	  -e SPRING_PROFILES_ACTIVE=prod \
	  -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/flashcardsdb \
	  -e SPRING_DATASOURCE_USERNAME=postgres \
	  -e SPRING_DATASOURCE_PASSWORD=${{ secrets.POSTGRES_PASSWORD }} \
	   $IMAGE_NAME:$IMAGE_TAG

- name: Wait for Spring Boot health endpoint
  run: |
    for i in {1..20}; do
	  if curl -sf http://localhost:8080/actuator/health > /dev/null; then
	    echo "Spring Boot is healthy"
	    exit 0
	  fi
	  sleep 3
    done
    docker logs flashcards-smoke
    exit 1

- name: Smoke Test - Categories endpoint
  run: |
    for i in {1..3}; do
	  if curl -sf http://localhost:8080/api/categories > /dev/null; then
	    echo "API /categories OK"
	    exit 0
	  fi
	  sleep 5
    done
    echo "API smoke test FAILED"
    exit 1

- name: Stop smoke test container
  if: always()
  run: |
    docker stop flashcards-smoke || true
    docker rm flashcards-smoke || true

Les étapes:
Run container for smoke tests

Démarre l'application Dockerisée dans un environnement isolé, mais connecté aux services

L'option --add-host=host.docker.internal:host-gateway est cruciale! Elle permet à l'application, dans Docker, de communiquer avec la base de données qui tourne sur le runner GitHub


Wait for Spring Boot health endpoint

Spring Boot peut mettre plusieurs secondes à démarrer. Ce script évite que les tests ne s'exécutent trop tôt et échouent inutilement


Smoke Test - Categories endpoint

Une fois l'application "en vie", on vérifie que l'accès aux données fonctionne, en faisant un "ping" sur les catégories.
L'Objectif est de vérifier que la connexion à la base de données est effective et que le mapping est correct

Stop smoke test container

Nettoyage de l'environnement: Il est essentiel de ne pas laisser de conteneurs orphelins sur le runner


2. Modification du "push" de Docker
Utilisation de ${{ github.ref_name }}, à la place de v${{ github.run_number }}
env:
  IMAGE_NAME: flashcards
  RELEASE_TAG: ${{ github.ref_name }}
  • ${{ github.ref_name }} représente le nom de la branche ou du tag Git
  • ${{ github.run_number }} est le numéro séquentiel unique pour chaque exécution du workflow
1. Modification du script "docker-build.sh", à adapter comme suit:
#!/bin/bash
set -euo pipefail

IMAGE_NAME=${1:-flashcards}
IMAGE_TAG=${2:-latest}

echo "== Building Docker image: ${IMAGE_NAME}:${IMAGE_TAG} =="
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
${{ github.ref_name }} en combinaison avec "Compute short SHA" défini la variable "IMAGE_TAG" qui est transmise, en tant qu'argument, au script "docker-build"
2. Modification de l'étape du "push" de Docker (à la fin du pipeline)
- name: Log in to Docker Hub
  if: env.IS_RELEASE == 'true'
  run: echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

- name: Push image to Docker Hub
  if: env.IS_RELEASE == 'true'
  run: |
    docker tag $IMAGE_NAME:$IMAGE_TAG ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:$IMAGE_TAG
    docker tag $IMAGE_NAME:$IMAGE_TAG ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:${RELEASE_TAG}
    docker tag $IMAGE_NAME:$IMAGE_TAG ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:latest

    docker push ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:$IMAGE_TAG
    docker push ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:${RELEASE_TAG}
    docker push ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:latest

Les Conditions du Pipeline

Le workflow est segmenté pour optimiser le temps d'exécution et la sécurité selon le contexte :

À chaque Push (toutes branches): On valide l'intégrité technique
Le pipeline démarre une instance réelle de la base de données (PostgreSQL), exécute les tests unitaires et d'intégration avec JaCoCo pour mesurer la couverture, puis, vérifie la viabilité de l'app via un "smoke test" Docker
Sur la branche [main] et via les Pull Requests: On ajoute une couche de qualité
Le pipeline déclenche l'analyse SonarCloud pour valider le "Quality Gate"
Cela garantit que seul un code propre et sécurisé est fusionné
[main] only - Lors de la création d'un Tag (v*.*.*) - le mode "Release"
En plus de toutes les étapes précédentes, le pipeline active les conditions de déploiement: connexion sécurisée et publication de l'image sur Docker Hub
C'est la garantie que chaque version publiée a passé 100% des tests de sécurité et de performance

L'utilisation de la variable d'environnement "IS_RELEASE" pour stocker le résultat d'une condition complexe est une "Best Practice"
Cela rend le YAML beaucoup plus lisible (DRY - "Don't Repeat Yourself") et facilite la maintenance


Ajout des badges SonarCloud
Dans la première partie, nous avions ajouté les badges Docker
Dans develop et staging, nous avions ajouté les badges CI pour chaque branche ainsi que les badges Checkstyles et Spotbug
Nous complétons à présent avec l'ajout de la Quality Gate de SonarCloud


Les badges permettent de suivre l'état du projet, en un coup d'œil

Exemple d'intégration dans un README:
[![SonarQube Cloud](https://sonarcloud.io/images/project_badges/sonarcloud-light.svg)](https://sonarcloud.io/summary/new_code?id=projetSonarCloud)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=projetSonarCloud&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=projetSonarCloud)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=projetSonarCloud&metric=coverage)](https://sonarcloud.io/summary/new_code?id=projetSonarCloud)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=projetSonarCloud&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=projetSonarCloud)

Vous pouvez aussi choisir vos propre badge et choisir le métriques que vous désirez afficher:

Depuis votre projet (project/information?id=votreID)
SonarCloud → Projet information: Badges

Badges SonarCloud
Dans l'article suivant, nous verrons comment pinnés les Actions Github par SHA ainsi que la mise en place et l'utilisation de dependabot
Naviguez entre les articlesBrowse through the articles

Laissez-moi un commentaire

En postant un commentaire anonyme, vous adhérez automatiquement aux conditions d'utilisation du site.