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
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
Déja dans le pipeline
CI de base
CI de base (modifié)
Ajout spécial (staging / main)
Artefacts (upload / download)
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
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 tourne sur Ubuntu 24.04
On lance un Service Container pour PostgreSQL,
indispensable pour les tests
Elle garantit que l'application Spring Boot ne tentera pas de démarrer avant que PostgreSQL ne soit totalement prêt à accepter des connexions
Est une bonne pratique pour conditionner les étapes suivantes (ex: on ne pousse l'image Docker qu'avec un tag v*)
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
avec -Djacoco.minimum.coverage=0.80 on vérifie que la couverture atteint les 80%
Un artifact est produit à la fin de chaque build
jacoco-xml : 3.5 KB
- 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
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
Spring Boot peut mettre plusieurs secondes à démarrer. Ce script évite que les tests ne s'exécutent trop tôt et échouent inutilement
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
env:
IMAGE_NAME: flashcards
RELEASE_TAG: ${{ github.ref_name }}
#!/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} .
- 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
Le workflow est segmenté pour optimiser le temps d'exécution et la sécurité selon le contexte :
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
Les badges permettent de suivre l'état du projet, en un coup d'œil
[](https://sonarcloud.io/summary/new_code?id=projetSonarCloud)
[](https://sonarcloud.io/summary/new_code?id=projetSonarCloud)
[](https://sonarcloud.io/summary/new_code?id=projetSonarCloud)
[](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
