Aller au contenu principal

CI/CD

Objectifs

  • Estimer son travail
  • Ajouter des tests unitaires en Python
  • Créer une CI/CD pipeline sur GitLab

Rendu

  • Rapport individuel en Markdown à rendre avant le prochain cours
  • Délai: 1 semaine

Tâches

Estimer son travail

  • Estimer le temps nécessaire pour réaliser ce travail.
    • Découper le travail en tâches pour faciliter l'estimation.
  • Une fois terminé, comparer le temps estimé avec le temps réellement passé.
TâcheTemps estiméTemps passéCommentaire
Estimation10m15m...
............
Total2h1h30...

Git

  • Reprendre son projet GitLab du laboratoire précédent (DOP Python).
  • Travailler sur une nouvelle branche feature/04-cicd.
    • Faire une merge request (MR) sur main une fois terminé et demander une revue.
    • Une fois qu'une MR est acceptée, la merge sur main.
  • Séparer son travail en commits cohérents avec des messages de commit clairs et concis.

Tester le backend

  • Ajouter les dépendances de développement poetry add -G dev pytest pytest-cov httpx.
    • Une dépendance de développement est une dépendance qui n'est pas nécessaire en production, par exemple uniquement pour les tests.
    • pytest est le framework de test.
    • pytest-cov permet de générer un rapport de couverture de code.
    • httpx permet de faire des requêtes HTTP dans les tests.
  • Ajouter ou modifier les fichiers suivants (inspirés de cette documentation) :
/backend/backend/main.py
from os import getenv
from sys import modules

from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session

from . import models, schemas
from .database import SessionLocal, engine

if "pytest" not in modules:
models.Base.metadata.create_all(bind=engine)

app = FastAPI(root_path=getenv("ROOT_PATH"))

...
  • Pour lancer les tests : poetry run pytest --cov

GitLab CI/CD

Créer une pipeline sur GitLab CI/CD qui :

  • a les 3 stages :
  • est déclenchée à chaque push sur n'importe quelle branche.
    • le stage deploy n'est exécuté que sur main.
  • Le frontend et le backend doivent être dans des jobs séparés et en parallèle.

Proposition

Beaucoup de changements sur la pipeline vont être testés, une manière d'éviter d'avoir plein de commit est d'en avoir qu'un seul au final (à éviter sur main ou develop) : git commit --amend --all --no-edit && git push --force-with-lease

Commencer par le frontend (commencer le script par cd frontend/) :

  • Le job build-frontend utilise l'image node:lts, exécute npm ci et npm run build.
    • Le résultat du build est gardé dans un artefact pour être utilisé par le job deploy-frontend.
    • Ajouter le cache.
  • Le job deploy-frontend utilise l'image docker avec le service docker:dind, exécute docker build -t ${CI_REGISTRY_IMAGE}/frontend:latest . et docker push ${CI_REGISTRY_IMAGE}/frontend:latest.
Solution .gitlab-ci.yml
build-frontend:
stage: build
image: node:lts
cache:
key:
files:
- frontend/package-lock.json
paths:
- frontend/.npm/
before_script:
- cd frontend/
script:
- npm ci --cache .npm --prefer-offline
- npm run build
artifacts:
paths:
- frontend/dist/

deploy-frontend:
stage: deploy
image: docker
services:
- docker:dind
dependencies:
- build-frontend
variables:
REGISTRY_IMAGE: ${CI_REGISTRY_IMAGE}/frontend
before_script:
- cd frontend/
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY --username $CI_REGISTRY_USER --password-stdin
script:
- docker pull $REGISTRY_IMAGE:latest || true
- docker build --cache-from $REGISTRY_IMAGE:latest -t $REGISTRY_IMAGE:latest .
- docker push $REGISTRY_IMAGE:latest

Puis le backend (similairement au frontend) :

  • Le job build-backend utilise l'image python:3.11, installe Poetry et les dépendances en les cachant pour les prochains jobs.
build-backend:
stage: build
image: python:3.11
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
paths:
- .cache/pip
- backend/.venv/
before_script:
- cd backend/
- pip install poetry
- poetry config virtualenvs.in-project true
script:
- poetry install
  • Le job test-backend reprend le cache du job build-backend et exécute poetry run pytest --cov.
test-backend:
stage: test
image: python:3.11
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
paths:
- .cache/pip
- backend/.venv/
before_script:
- cd backend/
- pip install poetry
- poetry config virtualenvs.in-project true
script:
- poetry run pytest --cov
Solution .gitlab-ci.yml
build-backend:
stage: build
image: python:3.11
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
paths:
- .cache/pip
- backend/.venv/
before_script:
- cd backend/
- pip install poetry
- poetry config virtualenvs.in-project true
script:
- poetry install

test-backend:
stage: test
image: python:3.11
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
paths:
- .cache/pip
- backend/.venv/
before_script:
- cd backend/
- pip install poetry
- poetry config virtualenvs.in-project true
script:
- poetry run pytest --cov --junitxml="rspec.xml" --cov-report term --cov-report xml:coverage.xml
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
artifacts:
paths:
- backend/rspec.xml
reports:
junit: backend/rspec.xml
coverage_report:
coverage_format: cobertura
path: backend/coverage.xml

deploy-backend:
stage: deploy
image: docker
services:
- docker:dind
variables:
REGISTRY_IMAGE: ${CI_REGISTRY_IMAGE}/backend
before_script:
- cd backend/
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY --username $CI_REGISTRY_USER --password-stdin
script:
- docker pull $REGISTRY_IMAGE:latest || true
- docker build --cache-from $REGISTRY_IMAGE:latest -t $REGISTRY_IMAGE:latest .
- docker push $REGISTRY_IMAGE:latest

Gestion des secrets

Le fichier .env est versionné avec un mot de passe non-secret destiné au développement local. En CI/CD, la variable GitLab masquée POSTGRES_PASSWORD est injectée dans l'environnement du runner et prend automatiquement le dessus sur la valeur du .env.

  • Versionner le fichier .env avec toutes les variables, incluant un mot de passe local non-secret :
POSTGRES_USER=postgres
POSTGRES_DB=postgres
POSTGRES_PASSWORD=postgres
  • Créer une variable CI/CD masquée POSTGRES_PASSWORD dans les paramètres du projet GitLab (Settings > CI/CD > Variables).
    • Activer l'option Masked pour que la valeur n'apparaisse jamais dans les logs.
    • La variable d'environnement du runner override celle du .env — aucune modification du fichier n'est nécessaire en pipeline.
  • Dans le compose.yml, référencer les variables depuis .env plutôt que de les coder en dur :
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}

Développement local

Le .env versionné fonctionne tel quel pour le développement local et Docker Compose — aucune configuration supplémentaire n'est nécessaire.

  • Pour Docker Compose, le .env est chargé automatiquement.
  • Pour lancer le backend en local (sans Docker), utiliser make dev-backend-dotenv qui charge explicitement le .env via python-dotenv :
make dev-backend-dotenv

Annexe - Validation complète sans merge dans main

Pour exécuter le pipeline complet sans merge dans main, vous devez modifier votre .gitlab-ci.yml pour déclencher les jobs sur chaque push de branche.

Modifications à faire dans votre pipeline

  1. Activer la création de pipeline sur push.
  2. Mettre vos jobs build-*, test-* et deploy-* en exécution sur push.
  3. Supprimer (ou adapter) les règles trop restrictives (par ex. only: main ou rules MR-only).

Snippet 1 - workflow

workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "push"
when: always
- if: $CI_PIPELINE_SOURCE == "web"
when: always

Snippet 2 - Règle type pour un job

Appliquez cette logique à tous les jobs du pipeline complet (build-*, test-*, deploy-*).

build-backend:
stage: build
# ...
rules:
- if: $CI_PIPELINE_SOURCE == "push"
when: on_success
- when: never

Snippet 3 - Exemple deploy

deploy-backend:
stage: deploy
# ...
rules:
- if: $CI_PIPELINE_SOURCE == "push"
when: on_success
- when: never

Vérification attendue

  • Faire un commit puis git push sur une branche feature/*.
  • Vérifier que les stages build, test, puis deploy s'exécutent.
  • Vérifier la publication des images dans Deploy > Container Registry.

Références