Chaque workflow se déclenchait sur push (toutes branches) et sur pull_request : chaque commit de PR jouait tout deux fois. sonarqube.yml reconstruisait et retestait front, back et ML en parallèle des workflows qui le faisaient déjà, et son test backend tournait sans uv sync. ci.yml devient le seul point d'entrée (pull_request, push sur dev et main) : - paths-filter choisit les composants à jouer sur une PR, tout est rejoué sur dev et main ; - backend, frontend, ml, airflow et infra passent en workflow_call ; - le job sonar reprend les couvertures versées par ces jobs au lieu de tout rejouer ; - « CI ok » agrège le résultat, seul check à exiger dans les règles de branche. Au passage : - npm run test:ci au lieu de npm test --watch=false, option que npm gardait pour lui ; - uv sync --locked au lieu de --frozen, pour qu'un verrou périmé casse la CI ; - setup-uv et sonarqube-scan-action épinglés sur un SHA (règle S7637), timeout sur chaque job ; - frontend : un seul npm ci pour la construction et les tests ; - infra : validation des fichiers Compose et actionlint sur les workflows ; - exclusions Sonar en globs, doublon apps/frontend/sonar-project.properties supprimé.
177 lines
6.1 KiB
YAML
177 lines
6.1 KiB
YAML
name: Backend
|
|
|
|
# Piège : la version de Python vient de apps/backend/.python-version, et elle doit rester
|
|
# en 3.14. Le code utilise le PEP 758, qu'un interpréteur 3.13 refuse de compiler.
|
|
# Pourquoi : aucun déclencheur propre. ci.yml appelle ce workflow quand le backend change, et
|
|
# Sonar y reprend la couverture versée par le job `verification` (ADR 0014).
|
|
|
|
on:
|
|
workflow_call:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
verification:
|
|
name: Lint, typage et tests
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
defaults:
|
|
run:
|
|
working-directory: apps/backend
|
|
|
|
steps:
|
|
- name: Récupère le dépôt
|
|
uses: actions/checkout@v7
|
|
|
|
# Action tierce, épinglée sur le commit du tag (règle Sonar githubactions:S7637).
|
|
- name: Installe uv
|
|
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
|
|
with:
|
|
enable-cache: true
|
|
cache-dependency-glob: apps/backend/uv.lock
|
|
prune-cache: false
|
|
|
|
- name: Installe l'interpréteur déclaré par .python-version
|
|
run: uv python install
|
|
|
|
# `--locked` et non `--frozen` : un verrou qui ne suit plus pyproject.toml doit casser ici.
|
|
- name: Synchronise les dépendances sur le verrou
|
|
run: uv sync --all-groups --locked
|
|
|
|
- name: Vérifie le formatage
|
|
run: uv run ruff format --check .
|
|
|
|
- name: Analyse statique
|
|
run: uv run ruff check --output-format=github .
|
|
|
|
- name: Typage
|
|
run: uv run mypy app
|
|
|
|
# Le marqueur `integration` est exclu par défaut, donc aucune base n'est nécessaire ici.
|
|
- name: Tests et couverture
|
|
run: uv run pytest --cov-fail-under=85 --cov-report=xml
|
|
|
|
- name: Verse la couverture pour Sonar
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: backend-coverage
|
|
path: apps/backend/coverage.xml
|
|
if-no-files-found: error
|
|
|
|
# Piège : même image que docker-compose.yml, pas un `postgres` nu. La première migration refuse
|
|
# de s'appliquer sans TimescaleDB, et une autre image testerait une base qui n'est pas la nôtre.
|
|
integration:
|
|
name: Tests exigeant une base
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
defaults:
|
|
run:
|
|
working-directory: apps/backend
|
|
|
|
services:
|
|
db:
|
|
image: timescale/timescaledb-ha:pg17
|
|
env:
|
|
POSTGRES_USER: enervision
|
|
POSTGRES_PASSWORD: change_me
|
|
POSTGRES_DB: enervision_test
|
|
ports:
|
|
- "5433:5432"
|
|
options: >-
|
|
--health-cmd "pg_isready -U enervision -d enervision_test"
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 12
|
|
--health-start-period 40s
|
|
|
|
env:
|
|
DATABASE_URL: postgresql+asyncpg://enervision:change_me@localhost:5433/enervision_test
|
|
APP_SECRET_KEY: secret-de-test-assez-long-pour-le-validateur
|
|
PGPASSWORD: change_me
|
|
|
|
steps:
|
|
- name: Récupère le dépôt
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Installe uv
|
|
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
|
|
with:
|
|
enable-cache: true
|
|
cache-dependency-glob: apps/backend/uv.lock
|
|
prune-cache: false
|
|
|
|
- name: Installe l'interpréteur déclaré par .python-version
|
|
run: uv python install
|
|
|
|
- name: Synchronise les dépendances sur le verrou
|
|
run: uv sync --all-groups --locked
|
|
|
|
# Sur le poste, c'est db/init/110-test-database.sql qui pose l'extension. Ce fichier n'est
|
|
# pas monté ici, et sans lui `alembic upgrade head` s'arrête sur la garde de la révision 1.
|
|
- name: Active TimescaleDB sur la base de test
|
|
run: psql -h localhost -p 5433 -U enervision -d enervision_test -c "CREATE EXTENSION IF NOT EXISTS timescaledb"
|
|
|
|
- name: Applique les migrations
|
|
run: uv run alembic upgrade head
|
|
|
|
# Couverture désactivée : ce job ne joue qu'une partie de la suite, son taux n'aurait
|
|
# aucun sens face au seuil de 85 %.
|
|
- name: Tests d'intégration
|
|
run: uv run pytest -m integration --no-cov
|
|
|
|
security-audit:
|
|
name: Audit des dépendances
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
defaults:
|
|
run:
|
|
working-directory: apps/backend
|
|
|
|
steps:
|
|
- name: Récupère le dépôt
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Installe uv
|
|
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
|
|
with:
|
|
enable-cache: true
|
|
cache-dependency-glob: apps/backend/uv.lock
|
|
prune-cache: false
|
|
|
|
# L'audit porte sur le verrou, pas sur l'environnement : sinon pip-audit auditerait
|
|
# aussi les paquets que son propre `--with` injecte, hors dépendances du projet.
|
|
- name: Audite les dépendances livrées
|
|
# Piège : sans `shell: bash`, un échec de `uv export` serait masqué par le pipe.
|
|
shell: bash
|
|
run: uv export --locked --no-dev --no-emit-project --no-hashes | uvx pip-audit --requirement /dev/stdin --no-deps
|
|
|
|
sast:
|
|
name: Analyse statique de sécurité
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
defaults:
|
|
run:
|
|
working-directory: apps/backend
|
|
|
|
steps:
|
|
- name: Récupère le dépôt
|
|
uses: actions/checkout@v7
|
|
|
|
# Pourquoi : pas de cache ici. uvx n'installe pas le projet, le verrou n'alimente donc
|
|
# aucune clé de cache ; la seule roue téléchargée est celle de Bandit.
|
|
- name: Installe uv
|
|
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
|
|
with:
|
|
enable-cache: false
|
|
|
|
# Pourquoi : le périmètre est `app`, le code livré. Les tests emploient légitimement des
|
|
# secrets factices et des `assert` que Bandit signalerait sans qu'aucun n'atteigne la prod.
|
|
- name: Analyse le code livré (bloquant à partir de MEDIUM)
|
|
run: uvx bandit==1.9.4 --recursive app --severity-level medium --confidence-level medium
|
|
|
|
# Piège : sans cette seconde passe, un constat LOW disparaîtrait du journal sans trace.
|
|
- name: Rapport complet, tous niveaux
|
|
continue-on-error: true
|
|
run: uvx bandit==1.9.4 --recursive app
|