ci: rassemble la CI dans un orchestrateur unique et retire le doublon Sonar
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é.
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
self-hosted-runner:
|
||||||
|
labels:
|
||||||
|
- eni-g3
|
||||||
@@ -6,42 +6,20 @@ name: Airflow
|
|||||||
# Docker, dans son propre environnement (cf. etl/airflow/Dockerfile).
|
# Docker, dans son propre environnement (cf. etl/airflow/Dockerfile).
|
||||||
#
|
#
|
||||||
# Piège : l'image COPY les fichiers de dépendances et le code de ml/ et de apps/backend/. Une
|
# Piège : l'image COPY les fichiers de dépendances et le code de ml/ et de apps/backend/. Une
|
||||||
# modification de l'un ou de l'autre peut donc casser sa construction, d'où ces chemins dans
|
# modification de l'un ou de l'autre peut donc casser sa construction : le filtre `airflow` de
|
||||||
# les déclencheurs, alors même que ce workflow ne teste ni le modèle ni l'API.
|
# ci.yml, qui appelle ce workflow, inclut ces chemins alors qu'il ne teste ni le modèle ni l'API.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_call:
|
||||||
paths:
|
|
||||||
- "etl/airflow/**"
|
|
||||||
- "ml/pyproject.toml"
|
|
||||||
- "ml/uv.lock"
|
|
||||||
- "ml/enervision_ml/**"
|
|
||||||
- "apps/backend/pyproject.toml"
|
|
||||||
- "apps/backend/uv.lock"
|
|
||||||
- "apps/backend/app/**"
|
|
||||||
- ".github/workflows/airflow.yml"
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- "etl/airflow/**"
|
|
||||||
- "ml/pyproject.toml"
|
|
||||||
- "ml/uv.lock"
|
|
||||||
- "ml/enervision_ml/**"
|
|
||||||
- "apps/backend/pyproject.toml"
|
|
||||||
- "apps/backend/uv.lock"
|
|
||||||
- "apps/backend/app/**"
|
|
||||||
- ".github/workflows/airflow.yml"
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: airflow-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
verification:
|
verification:
|
||||||
name: Lint et intégrité des DAGs
|
name: Lint et intégrité des DAGs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: etl/airflow
|
working-directory: etl/airflow
|
||||||
@@ -50,17 +28,19 @@ jobs:
|
|||||||
- name: Récupère le dépôt
|
- name: Récupère le dépôt
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
# Action tierce, épinglée sur le commit du tag (règle Sonar githubactions:S7637).
|
||||||
- name: Installe uv
|
- name: Installe uv
|
||||||
uses: astral-sh/setup-uv@v7
|
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
|
||||||
with:
|
with:
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: etl/airflow/uv.lock
|
cache-dependency-glob: etl/airflow/uv.lock
|
||||||
|
prune-cache: false
|
||||||
|
|
||||||
- name: Installe l'interpréteur déclaré par .python-version
|
- name: Installe l'interpréteur déclaré par .python-version
|
||||||
run: uv python install
|
run: uv python install
|
||||||
|
|
||||||
- name: Synchronise les dépendances sans dévier du verrou
|
- name: Synchronise les dépendances sur le verrou
|
||||||
run: uv sync --all-groups --frozen
|
run: uv sync --all-groups --locked
|
||||||
|
|
||||||
- name: Vérifie le formatage
|
- name: Vérifie le formatage
|
||||||
run: uv run ruff format --check .
|
run: uv run ruff format --check .
|
||||||
@@ -76,6 +56,7 @@ jobs:
|
|||||||
image:
|
image:
|
||||||
name: Construction de l'image
|
name: Construction de l'image
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 25
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Récupère le dépôt
|
- name: Récupère le dépôt
|
||||||
@@ -93,7 +74,6 @@ jobs:
|
|||||||
|
|
||||||
# `--help` sort par argparse avant `get_settings()` : ni base ni secret requis, et
|
# `--help` sort par argparse avant `get_settings()` : ni base ni secret requis, et
|
||||||
# l'import des modules prouve que l'environnement /opt/backend est complet.
|
# l'import des modules prouve que l'environnement /opt/backend est complet.
|
||||||
# Les commandes des DAGs `alertes`, historique et API Mock sont couvertes.
|
|
||||||
- name: Vérifie que les quatre commandes backend s'importent sans réseau
|
- name: Vérifie que les quatre commandes backend s'importent sans réseau
|
||||||
run: >
|
run: >
|
||||||
docker run --rm --network none enervision-airflow:ci
|
docker run --rm --network none enervision-airflow:ci
|
||||||
@@ -101,4 +81,4 @@ jobs:
|
|||||||
&& env -u VIRTUAL_ENV uv run --no-sync python -m app.detection.internal_alerts --help
|
&& env -u VIRTUAL_ENV uv run --no-sync python -m app.detection.internal_alerts --help
|
||||||
&& env -u VIRTUAL_ENV uv run --no-sync python -m app.cli generate-recommendations --help
|
&& env -u VIRTUAL_ENV uv run --no-sync python -m app.cli generate-recommendations --help
|
||||||
&& env -u VIRTUAL_ENV uv run --no-sync python -m app.etl.historical_import --help
|
&& env -u VIRTUAL_ENV uv run --no-sync python -m app.etl.historical_import --help
|
||||||
&& env -u VIRTUAL_ENV uv run --no-sync python -m app.etl.mock_api_import --help"
|
&& env -u VIRTUAL_ENV uv run --no-sync python -m app.etl.mock_api_import --help"
|
||||||
|
|||||||
@@ -2,28 +2,20 @@ name: Backend
|
|||||||
|
|
||||||
# Piège : la version de Python vient de apps/backend/.python-version, et elle doit rester
|
# 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.
|
# 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:
|
on:
|
||||||
push:
|
workflow_call:
|
||||||
paths:
|
|
||||||
- "apps/backend/**"
|
|
||||||
- ".github/workflows/backend.yml"
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- "apps/backend/**"
|
|
||||||
- ".github/workflows/backend.yml"
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: backend-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
verification:
|
verification:
|
||||||
name: Lint, typage et tests
|
name: Lint, typage et tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: apps/backend
|
working-directory: apps/backend
|
||||||
@@ -32,17 +24,20 @@ jobs:
|
|||||||
- name: Récupère le dépôt
|
- name: Récupère le dépôt
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
# Action tierce, épinglée sur le commit du tag (règle Sonar githubactions:S7637).
|
||||||
- name: Installe uv
|
- name: Installe uv
|
||||||
uses: astral-sh/setup-uv@v7
|
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
|
||||||
with:
|
with:
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: apps/backend/uv.lock
|
cache-dependency-glob: apps/backend/uv.lock
|
||||||
|
prune-cache: false
|
||||||
|
|
||||||
- name: Installe l'interpréteur déclaré par .python-version
|
- name: Installe l'interpréteur déclaré par .python-version
|
||||||
run: uv python install
|
run: uv python install
|
||||||
|
|
||||||
- name: Synchronise les dépendances sans dévier du verrou
|
# `--locked` et non `--frozen` : un verrou qui ne suit plus pyproject.toml doit casser ici.
|
||||||
run: uv sync --all-groups --frozen
|
- name: Synchronise les dépendances sur le verrou
|
||||||
|
run: uv sync --all-groups --locked
|
||||||
|
|
||||||
- name: Vérifie le formatage
|
- name: Vérifie le formatage
|
||||||
run: uv run ruff format --check .
|
run: uv run ruff format --check .
|
||||||
@@ -55,14 +50,21 @@ jobs:
|
|||||||
|
|
||||||
# Le marqueur `integration` est exclu par défaut, donc aucune base n'est nécessaire ici.
|
# Le marqueur `integration` est exclu par défaut, donc aucune base n'est nécessaire ici.
|
||||||
- name: Tests et couverture
|
- name: Tests et couverture
|
||||||
run: uv run pytest --cov-fail-under=85
|
run: uv run pytest --cov-fail-under=85 --cov-report=xml
|
||||||
|
|
||||||
# Piège : l'image est celle de docker-compose.yml, pas une image `postgres` nue. La première
|
- name: Verse la couverture pour Sonar
|
||||||
# migration (`5353c0e4f094`) échoue volontairement si l'extension TimescaleDB manque, et un
|
uses: actions/upload-artifact@v7
|
||||||
# écart d'image entre la CI et le poste rendrait ce job vert sur une base qui n'est pas la nôtre.
|
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:
|
integration:
|
||||||
name: Tests exigeant une base
|
name: Tests exigeant une base
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: apps/backend
|
working-directory: apps/backend
|
||||||
@@ -93,16 +95,17 @@ jobs:
|
|||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Installe uv
|
- name: Installe uv
|
||||||
uses: astral-sh/setup-uv@v7
|
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
|
||||||
with:
|
with:
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: apps/backend/uv.lock
|
cache-dependency-glob: apps/backend/uv.lock
|
||||||
|
prune-cache: false
|
||||||
|
|
||||||
- name: Installe l'interpréteur déclaré par .python-version
|
- name: Installe l'interpréteur déclaré par .python-version
|
||||||
run: uv python install
|
run: uv python install
|
||||||
|
|
||||||
- name: Synchronise les dépendances sans dévier du verrou
|
- name: Synchronise les dépendances sur le verrou
|
||||||
run: uv sync --all-groups --frozen
|
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
|
# 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.
|
# pas monté ici, et sans lui `alembic upgrade head` s'arrête sur la garde de la révision 1.
|
||||||
@@ -112,14 +115,15 @@ jobs:
|
|||||||
- name: Applique les migrations
|
- name: Applique les migrations
|
||||||
run: uv run alembic upgrade head
|
run: uv run alembic upgrade head
|
||||||
|
|
||||||
# `-m` en ligne de commande écrase celui d'`addopts`. La couverture est désactivée : ce job
|
# Couverture désactivée : ce job ne joue qu'une partie de la suite, son taux n'aurait
|
||||||
# ne joue qu'une partie de la suite, son taux n'aurait aucun sens face au seuil de 85 %.
|
# aucun sens face au seuil de 85 %.
|
||||||
- name: Tests d'intégration
|
- name: Tests d'intégration
|
||||||
run: uv run pytest -m integration --no-cov
|
run: uv run pytest -m integration --no-cov
|
||||||
|
|
||||||
security-audit:
|
security-audit:
|
||||||
name: Audit des dépendances
|
name: Audit des dépendances
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: apps/backend
|
working-directory: apps/backend
|
||||||
@@ -129,21 +133,23 @@ jobs:
|
|||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Installe uv
|
- name: Installe uv
|
||||||
uses: astral-sh/setup-uv@v7
|
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
|
||||||
with:
|
with:
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: apps/backend/uv.lock
|
cache-dependency-glob: apps/backend/uv.lock
|
||||||
|
prune-cache: false
|
||||||
|
|
||||||
# L'audit porte sur le verrou, pas sur l'environnement : sinon pip-audit auditerait
|
# 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.
|
# aussi les paquets que son propre `--with` injecte, hors dépendances du projet.
|
||||||
- name: Audite les dépendances livrées
|
- name: Audite les dépendances livrées
|
||||||
# Piège : sans `shell: bash`, un échec de `uv export` serait masqué par le pipe.
|
# Piège : sans `shell: bash`, un échec de `uv export` serait masqué par le pipe.
|
||||||
shell: bash
|
shell: bash
|
||||||
run: uv export --frozen --no-dev --no-emit-project --no-hashes | uvx pip-audit --requirement /dev/stdin --no-deps
|
run: uv export --locked --no-dev --no-emit-project --no-hashes | uvx pip-audit --requirement /dev/stdin --no-deps
|
||||||
|
|
||||||
sast:
|
sast:
|
||||||
name: Analyse statique de sécurité
|
name: Analyse statique de sécurité
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: apps/backend
|
working-directory: apps/backend
|
||||||
@@ -155,7 +161,9 @@ jobs:
|
|||||||
# Pourquoi : pas de cache ici. uvx n'installe pas le projet, le verrou n'alimente donc
|
# 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.
|
# aucune clé de cache ; la seule roue téléchargée est celle de Bandit.
|
||||||
- name: Installe uv
|
- name: Installe uv
|
||||||
uses: astral-sh/setup-uv@v7
|
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
|
# 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.
|
# secrets factices et des `assert` que Bandit signalerait sans qu'aucun n'atteigne la prod.
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
# Pourquoi : un seul point d'entrée pour toute la CI (ADR 0014) - workflow CI. Chaque composant
|
||||||
|
# ne tourne que si ses fichiers changent, Sonar reprend les couvertures déjà produites au lieu de
|
||||||
|
# tout rejouer, et le déploiement ne part que d'un commit dont la CI est verte.
|
||||||
|
# Piège : le seul check à exiger dans les règles de branche est « CI ok ». Un job sauté par son
|
||||||
|
# filtre ne publie pas les checks de son workflow, qui resteraient en attente s'ils étaient exigés.
|
||||||
|
# Piège : sur un push vers dev ou main, tous les filtres valent vrai. paths-filter comparerait
|
||||||
|
# sinon à la base de fusion avec main, et Sonar n'analyserait qu'une partie de la branche.
|
||||||
|
# Piège : pas d'annulation des runs de push. Un run coupé en plein `make stack-up` laisserait la
|
||||||
|
# stack à moitié redémarrée ; le groupe par SHA évite aussi de mettre `dev` en file derrière lui.
|
||||||
|
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches: [dev, main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ci-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
changes:
|
||||||
|
name: Périmètre modifié
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
outputs:
|
||||||
|
backend: ${{ github.event_name != 'pull_request' || steps.filtre.outputs.ci == 'true' || steps.filtre.outputs.backend == 'true' }}
|
||||||
|
frontend: ${{ github.event_name != 'pull_request' || steps.filtre.outputs.ci == 'true' || steps.filtre.outputs.frontend == 'true' }}
|
||||||
|
ml: ${{ github.event_name != 'pull_request' || steps.filtre.outputs.ci == 'true' || steps.filtre.outputs.ml == 'true' }}
|
||||||
|
airflow: ${{ github.event_name != 'pull_request' || steps.filtre.outputs.ci == 'true' || steps.filtre.outputs.airflow == 'true' }}
|
||||||
|
terraform: ${{ github.event_name != 'pull_request' || steps.filtre.outputs.ci == 'true' || steps.filtre.outputs.terraform == 'true' }}
|
||||||
|
compose: ${{ github.event_name != 'pull_request' || steps.filtre.outputs.ci == 'true' || steps.filtre.outputs.compose == 'true' }}
|
||||||
|
workflows: ${{ github.event_name != 'pull_request' || steps.filtre.outputs.workflows == 'true' }}
|
||||||
|
sonar: ${{ github.event_name != 'pull_request' || steps.filtre.outputs.ci == 'true' || steps.filtre.outputs.sonar == 'true' }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Sur une PR, la liste des fichiers vient de l'API : ni checkout ni historique requis.
|
||||||
|
- name: Calcule le périmètre de la PR
|
||||||
|
id: filtre
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
ci:
|
||||||
|
- ".github/workflows/ci.yml"
|
||||||
|
backend:
|
||||||
|
- "apps/backend/**"
|
||||||
|
- ".github/workflows/backend.yml"
|
||||||
|
frontend:
|
||||||
|
- "apps/frontend/**"
|
||||||
|
- ".github/workflows/frontend.yml"
|
||||||
|
ml:
|
||||||
|
- "ml/**"
|
||||||
|
- "apps/backend/alembic/**"
|
||||||
|
- "apps/backend/app/models/**"
|
||||||
|
- "apps/backend/tests/test_chaine_ml_api.py"
|
||||||
|
- "apps/backend/pyproject.toml"
|
||||||
|
- "apps/backend/uv.lock"
|
||||||
|
- ".github/workflows/ml.yml"
|
||||||
|
airflow:
|
||||||
|
- "etl/airflow/**"
|
||||||
|
- "ml/pyproject.toml"
|
||||||
|
- "ml/uv.lock"
|
||||||
|
- "ml/enervision_ml/**"
|
||||||
|
- "apps/backend/pyproject.toml"
|
||||||
|
- "apps/backend/uv.lock"
|
||||||
|
- "apps/backend/app/**"
|
||||||
|
- ".github/workflows/airflow.yml"
|
||||||
|
terraform:
|
||||||
|
- "infra/terraform/**"
|
||||||
|
- ".github/workflows/infra.yml"
|
||||||
|
compose:
|
||||||
|
- "docker-compose*.yml"
|
||||||
|
- ".env.example"
|
||||||
|
- ".github/workflows/infra.yml"
|
||||||
|
workflows:
|
||||||
|
- ".github/**"
|
||||||
|
sonar:
|
||||||
|
- "apps/backend/**"
|
||||||
|
- "apps/frontend/**"
|
||||||
|
- "ml/**"
|
||||||
|
- "etl/airflow/**"
|
||||||
|
- "sonar-project.properties"
|
||||||
|
|
||||||
|
backend:
|
||||||
|
name: Backend
|
||||||
|
needs: changes
|
||||||
|
if: needs.changes.outputs.backend == 'true'
|
||||||
|
uses: ./.github/workflows/backend.yml
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
name: Frontend
|
||||||
|
needs: changes
|
||||||
|
if: needs.changes.outputs.frontend == 'true'
|
||||||
|
uses: ./.github/workflows/frontend.yml
|
||||||
|
|
||||||
|
ml:
|
||||||
|
name: ML
|
||||||
|
needs: changes
|
||||||
|
if: needs.changes.outputs.ml == 'true'
|
||||||
|
uses: ./.github/workflows/ml.yml
|
||||||
|
|
||||||
|
airflow:
|
||||||
|
name: Airflow
|
||||||
|
needs: changes
|
||||||
|
if: needs.changes.outputs.airflow == 'true'
|
||||||
|
uses: ./.github/workflows/airflow.yml
|
||||||
|
|
||||||
|
infra:
|
||||||
|
name: Infra
|
||||||
|
needs: changes
|
||||||
|
if: >-
|
||||||
|
needs.changes.outputs.terraform == 'true'
|
||||||
|
|| needs.changes.outputs.compose == 'true'
|
||||||
|
|| needs.changes.outputs.workflows == 'true'
|
||||||
|
uses: ./.github/workflows/infra.yml
|
||||||
|
with:
|
||||||
|
terraform: ${{ needs.changes.outputs.terraform == 'true' }}
|
||||||
|
compose: ${{ needs.changes.outputs.compose == 'true' }}
|
||||||
|
workflows: ${{ needs.changes.outputs.workflows == 'true' }}
|
||||||
|
|
||||||
|
# Ni dependabot[bot] ni une PR de fork ne reçoivent SONAR_TOKEN : le scan échouerait sans rien
|
||||||
|
# analyser. Tests et couverture restent joués par leurs jobs.
|
||||||
|
sonar:
|
||||||
|
name: SonarQube
|
||||||
|
needs: [changes, backend, frontend, ml]
|
||||||
|
if: >-
|
||||||
|
always() && !cancelled()
|
||||||
|
&& !contains(needs.*.result, 'failure')
|
||||||
|
&& needs.changes.outputs.sonar == 'true'
|
||||||
|
&& github.actor != 'dependabot[bot]'
|
||||||
|
&& (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Récupère le dépôt
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# Un téléchargement par rapport : backend et ML nomment tous deux le leur `coverage.xml`.
|
||||||
|
- name: Couverture du backend
|
||||||
|
if: needs.backend.result == 'success'
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: backend-coverage
|
||||||
|
path: apps/backend
|
||||||
|
|
||||||
|
- name: Couverture du pipeline ML
|
||||||
|
if: needs.ml.result == 'success'
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: ml-coverage
|
||||||
|
path: ml
|
||||||
|
|
||||||
|
- name: Couverture du frontend
|
||||||
|
if: needs.frontend.result == 'success'
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: frontend-coverage
|
||||||
|
path: apps/frontend/coverage/frontend
|
||||||
|
|
||||||
|
# Action tierce, épinglée sur le commit du tag (règle Sonar githubactions:S7637).
|
||||||
|
- name: Analyse SonarQube
|
||||||
|
uses: SonarSource/sonarqube-scan-action@ba9859eae8dd6bd29e412f25ddbbef3d032000f4 # v8.2.2
|
||||||
|
env:
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
|
||||||
|
ci-ok:
|
||||||
|
name: CI ok
|
||||||
|
needs: [changes, backend, frontend, ml, airflow, infra, sonar]
|
||||||
|
if: always()
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Refuse si un job a échoué ou a été annulé
|
||||||
|
env:
|
||||||
|
RESULTATS: ${{ toJSON(needs.*.result) }}
|
||||||
|
run: |
|
||||||
|
echo "$RESULTATS"
|
||||||
|
if grep -qE '"(failure|cancelled)"' <<<"$RESULTATS"; then
|
||||||
|
echo "::error::Au moins un job de la CI a échoué ou a été annulé."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ jobs:
|
|||||||
# `apps/backend`, comme dans son Dockerfile. Les `uv run` suivants portent `--frozen
|
# `apps/backend`, comme dans son Dockerfile. Les `uv run` suivants portent `--frozen
|
||||||
# --no-sync` pour ne rien résoudre ni reconstruire (règle S8544).
|
# --no-sync` pour ne rien résoudre ni reconstruire (règle S8544).
|
||||||
- name: Synchronise les dépendances sans dévier du verrou
|
- name: Synchronise les dépendances sans dévier du verrou
|
||||||
run: uv sync --frozen --no-dev --no-install-project --no-build
|
run: uv sync --locked --no-dev --no-install-project --no-build
|
||||||
working-directory: apps/backend
|
working-directory: apps/backend
|
||||||
|
|
||||||
- name: Active TimescaleDB sur la base du scan
|
- name: Active TimescaleDB sur la base du scan
|
||||||
@@ -299,9 +299,11 @@ jobs:
|
|||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
if [ -f zap-out/zap-report.md ]; then
|
if [ -f zap-out/zap-report.md ]; then
|
||||||
awk '/^## Alert Detail/{exit} {print}' zap-out/zap-report.md >> "$GITHUB_STEP_SUMMARY"
|
{
|
||||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
awk '/^## Alert Detail/{exit} {print}' zap-out/zap-report.md
|
||||||
echo "Rapport complet (HTML/JSON/Markdown) dans l'artefact \`zap-report\`." >> "$GITHUB_STEP_SUMMARY"
|
echo ""
|
||||||
|
echo "Rapport complet (HTML/JSON/Markdown) dans l'artefact \`zap-report\`."
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
else
|
else
|
||||||
echo "Aucun rapport ZAP produit, voir le journal du job." >> "$GITHUB_STEP_SUMMARY"
|
echo "Aucun rapport ZAP produit, voir le journal du job." >> "$GITHUB_STEP_SUMMARY"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,64 +1,70 @@
|
|||||||
name: Frontend
|
name: Frontend
|
||||||
|
|
||||||
|
# Pourquoi : aucun déclencheur propre. ci.yml appelle ce workflow quand le frontend change, et
|
||||||
|
# Sonar y reprend la couverture versée par le job `verification` (ADR 0014).
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_call:
|
||||||
paths:
|
|
||||||
- "apps/frontend/**"
|
|
||||||
- ".github/workflows/frontend.yml"
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- "apps/frontend/**"
|
|
||||||
- ".github/workflows/frontend.yml"
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
# Un seul `npm ci` pour la construction et les tests : un job de plus ne ferait que le rejouer.
|
||||||
|
verification:
|
||||||
|
name: Construction et tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: apps/frontend
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- name: Récupère le dépôt
|
||||||
- uses: actions/setup-node@v7
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- name: Installe Node
|
||||||
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: 26
|
node-version: 26
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: apps/frontend/package-lock.json
|
cache-dependency-path: apps/frontend/package-lock.json
|
||||||
|
|
||||||
- run: npm ci
|
- name: Installe les dépendances
|
||||||
working-directory: apps/frontend
|
run: npm ci
|
||||||
- run: npm run build
|
|
||||||
working-directory: apps/frontend
|
|
||||||
|
|
||||||
security-audit:
|
- name: Construit l'application
|
||||||
name: Audit des dépendances
|
run: npm run build
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
- uses: actions/setup-node@v7
|
|
||||||
with:
|
|
||||||
node-version: 26
|
|
||||||
# Seuil high : une vulnérabilité moderate de devDependency ne doit pas bloquer une livraison.
|
|
||||||
- run: npm audit --audit-level=high --package-lock-only
|
|
||||||
working-directory: apps/frontend
|
|
||||||
|
|
||||||
test:
|
# Piège : `npm test --watch=false` garde l'option pour npm, `ng test` ne la reçoit jamais.
|
||||||
needs: build
|
# La couverture lcov vient d'angular.json (`coverage: true`).
|
||||||
runs-on: ubuntu-latest
|
- name: Tests et couverture
|
||||||
steps:
|
run: npm run test:ci
|
||||||
- uses: actions/checkout@v7
|
|
||||||
- uses: actions/setup-node@v7
|
- name: Verse la couverture pour Sonar
|
||||||
with:
|
|
||||||
node-version: 26
|
|
||||||
cache: npm
|
|
||||||
cache-dependency-path: apps/frontend/package-lock.json
|
|
||||||
- name : Installation des dépendances (Front)
|
|
||||||
run: npm ci
|
|
||||||
working-directory: apps/frontend
|
|
||||||
- name : Lancement des tests et génénration du rapport de couverture (Front)
|
|
||||||
run: npm test --watch=false --code-coverage --coverageReporters=lcov
|
|
||||||
working-directory: apps/frontend
|
|
||||||
- name: Upload coverage
|
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: frontend-coverage
|
name: frontend-coverage
|
||||||
path: apps/frontend/coverage/frontend/lcov.info
|
path: apps/frontend/coverage/frontend/lcov.info
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
security-audit:
|
||||||
|
name: Audit des dépendances
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: apps/frontend
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Récupère le dépôt
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- name: Installe Node
|
||||||
|
uses: actions/setup-node@v7
|
||||||
|
with:
|
||||||
|
node-version: 26
|
||||||
|
|
||||||
|
# Seuil high : une vulnérabilité moderate de devDependency ne doit pas bloquer une livraison.
|
||||||
|
- name: Audite le verrou
|
||||||
|
run: npm audit --audit-level=high --package-lock-only
|
||||||
|
|||||||
+55
-18
@@ -1,38 +1,40 @@
|
|||||||
name: Infra
|
name: Infra
|
||||||
|
|
||||||
# Pourquoi : le Terraform du dépôt est resté cassé sans que rien ne le dise, faute de job qui le
|
# Pourquoi : rien de ce qui décrit l'infrastructure ne s'exécute avant le déploiement. Terraform est
|
||||||
# joue. Ce workflow n'applique rien : il vérifie le formatage et la validité de chaque racine.
|
# resté cassé sans que rien ne le dise, faute de job qui le joue : ce workflow n'applique rien, il
|
||||||
# Piège : la boucle parcourt `environments/*`, pour qu'une racine ajoutée soit couverte sans
|
# vérifie le Terraform, les fichiers Compose et les workflows eux-mêmes - jobs terraform, compose,
|
||||||
# toucher à ce fichier.
|
# workflows. ci.yml choisit par ses entrées ceux qui tournent (ADR 0014).
|
||||||
|
# Piège : la boucle Terraform parcourt `environments/*`, pour qu'une racine ajoutée soit couverte
|
||||||
|
# sans toucher à ce fichier.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_call:
|
||||||
paths:
|
inputs:
|
||||||
- "infra/terraform/**"
|
terraform:
|
||||||
- ".github/workflows/infra.yml"
|
type: boolean
|
||||||
pull_request:
|
default: false
|
||||||
paths:
|
compose:
|
||||||
- "infra/terraform/**"
|
type: boolean
|
||||||
- ".github/workflows/infra.yml"
|
default: false
|
||||||
|
workflows:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: infra-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
terraform:
|
terraform:
|
||||||
name: Formatage et validation Terraform
|
name: Formatage et validation Terraform
|
||||||
|
if: inputs.terraform
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Récupère le dépôt
|
- name: Récupère le dépôt
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
# Action tierce, donc epinglee sur un SHA de commit et pas sur un tag mobile : un tag se
|
# Action tierce, épinglée sur le commit du tag (règle Sonar githubactions:S7637).
|
||||||
# redeplace, et ce workflow tourne avec les droits du depot (regle Sonar githubactions:S7637).
|
|
||||||
- name: Installe Terraform
|
- name: Installe Terraform
|
||||||
uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
|
uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
|
||||||
with:
|
with:
|
||||||
@@ -50,3 +52,38 @@ jobs:
|
|||||||
terraform -chdir="${racine}" validate
|
terraform -chdir="${racine}" validate
|
||||||
echo "::endgroup::"
|
echo "::endgroup::"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
compose:
|
||||||
|
name: Validation des fichiers Compose
|
||||||
|
if: inputs.compose
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Récupère le dépôt
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
# Compose interpole tout le fichier : les `:?` exigent une valeur, pas un vrai secret.
|
||||||
|
- name: Prépare un .env d'exemple
|
||||||
|
run: cp .env.example .env
|
||||||
|
|
||||||
|
- name: Valide la stack de développement
|
||||||
|
run: docker compose config --quiet
|
||||||
|
|
||||||
|
- name: Valide la stack déployée, profils compris
|
||||||
|
run: docker compose -f docker-compose.yml -f docker-compose.prod.yml --profile acme config --quiet
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
name: Analyse des workflows
|
||||||
|
if: inputs.workflows
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Récupère le dépôt
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
# Image épinglée par tag, comme les images des fichiers Compose. Elle embarque shellcheck,
|
||||||
|
# qui analyse aussi les blocs `run:`.
|
||||||
|
- name: actionlint
|
||||||
|
run: docker run --rm -v "$PWD:/repo" --workdir /repo rhysd/actionlint:1.7.12 -color
|
||||||
|
|||||||
+35
-48
@@ -2,46 +2,20 @@ name: ML
|
|||||||
|
|
||||||
# Piège : la version de Python vient de ml/.python-version, et doit rester en 3.14 (cf.
|
# Piège : la version de Python vient de ml/.python-version, et doit rester en 3.14 (cf.
|
||||||
# .github/workflows/backend.yml, même contrainte).
|
# .github/workflows/backend.yml, même contrainte).
|
||||||
|
# Pourquoi : aucun déclencheur propre. ci.yml l'appelle aussi quand les migrations ou les modèles
|
||||||
|
# du backend changent, dont dépend le job `integration` (ADR 0014).
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_call:
|
||||||
paths:
|
|
||||||
- "ml/**"
|
|
||||||
- ".github/workflows/ml.yml"
|
|
||||||
# Le job `integration` monte son schema avec les migrations du backend et joue le test de
|
|
||||||
# chaine qui vit dans ses tests : sans ces chemins, une migration modifiee ne declencherait
|
|
||||||
# rien et le schema deriverait du SQL du pipeline sans que rien ne casse. Meme raisonnement
|
|
||||||
# que le filtre d'airflow.yml, qui inclut deja des chemins de ml/ et de apps/backend/.
|
|
||||||
- "apps/backend/alembic/**"
|
|
||||||
- "apps/backend/app/models/**"
|
|
||||||
- "apps/backend/tests/test_chaine_ml_api.py"
|
|
||||||
- "apps/backend/pyproject.toml"
|
|
||||||
- "apps/backend/uv.lock"
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- "ml/**"
|
|
||||||
- ".github/workflows/ml.yml"
|
|
||||||
# Le job `integration` monte son schema avec les migrations du backend et joue le test de
|
|
||||||
# chaine qui vit dans ses tests : sans ces chemins, une migration modifiee ne declencherait
|
|
||||||
# rien et le schema deriverait du SQL du pipeline sans que rien ne casse. Meme raisonnement
|
|
||||||
# que le filtre d'airflow.yml, qui inclut deja des chemins de ml/ et de apps/backend/.
|
|
||||||
- "apps/backend/alembic/**"
|
|
||||||
- "apps/backend/app/models/**"
|
|
||||||
- "apps/backend/tests/test_chaine_ml_api.py"
|
|
||||||
- "apps/backend/pyproject.toml"
|
|
||||||
- "apps/backend/uv.lock"
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ml-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
verification:
|
verification:
|
||||||
name: Lint, typage et tests
|
name: Lint, typage et tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ml
|
working-directory: ml
|
||||||
@@ -50,17 +24,19 @@ jobs:
|
|||||||
- name: Récupère le dépôt
|
- name: Récupère le dépôt
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
# Action tierce, épinglée sur le commit du tag (règle Sonar githubactions:S7637).
|
||||||
- name: Installe uv
|
- name: Installe uv
|
||||||
uses: astral-sh/setup-uv@v7
|
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
|
||||||
with:
|
with:
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: ml/uv.lock
|
cache-dependency-glob: ml/uv.lock
|
||||||
|
prune-cache: false
|
||||||
|
|
||||||
- name: Installe l'interpréteur déclaré par .python-version
|
- name: Installe l'interpréteur déclaré par .python-version
|
||||||
run: uv python install
|
run: uv python install
|
||||||
|
|
||||||
- name: Synchronise les dépendances sans dévier du verrou
|
- name: Synchronise les dépendances sur le verrou
|
||||||
run: uv sync --all-groups --frozen
|
run: uv sync --all-groups --locked
|
||||||
|
|
||||||
- name: Vérifie le formatage
|
- name: Vérifie le formatage
|
||||||
run: uv run ruff format --check .
|
run: uv run ruff format --check .
|
||||||
@@ -71,17 +47,24 @@ jobs:
|
|||||||
- name: Typage
|
- name: Typage
|
||||||
run: uv run mypy enervision_ml tests
|
run: uv run mypy enervision_ml tests
|
||||||
|
|
||||||
# Les tests exigeant une base portent le marqueur `integration`, ecarte par defaut et
|
# Les tests exigeant une base portent le marqueur `integration`, écarté par défaut et
|
||||||
# joue par le job `integration` ci-dessous.
|
# joué par le job `integration` ci-dessous.
|
||||||
- name: Tests
|
- name: Tests et couverture
|
||||||
run: uv run pytest
|
run: uv run pytest --cov-report=xml
|
||||||
|
|
||||||
# Le seul job du depot qui dispose a la fois des deux environnements uv et d'une base. Piege :
|
- name: Verse la couverture pour Sonar
|
||||||
# le schema de la base ML est celui du backend (apps/backend/alembic, proprietaire du schema).
|
uses: actions/upload-artifact@v7
|
||||||
# Le reconstruire ici a la main rendrait ce job vert sur une base qui n'est pas la notre.
|
with:
|
||||||
|
name: ml-coverage
|
||||||
|
path: ml/coverage.xml
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
# Piège : le schéma de la base ML est celui du backend (apps/backend/alembic, propriétaire du
|
||||||
|
# schéma). Le reconstruire ici à la main rendrait ce job vert sur une base qui n'est pas la nôtre.
|
||||||
integration:
|
integration:
|
||||||
name: ML - DB et chaîne ML - DB - API
|
name: ML - DB et chaîne ML - DB - API
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 20
|
||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
@@ -112,26 +95,27 @@ jobs:
|
|||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Installe uv
|
- name: Installe uv
|
||||||
uses: astral-sh/setup-uv@v7
|
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
|
||||||
with:
|
with:
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: |
|
cache-dependency-glob: |
|
||||||
ml/uv.lock
|
ml/uv.lock
|
||||||
apps/backend/uv.lock
|
apps/backend/uv.lock
|
||||||
|
prune-cache: false
|
||||||
|
|
||||||
- name: Installe l'interpréteur déclaré par .python-version
|
- name: Installe l'interpréteur déclaré par .python-version
|
||||||
working-directory: ml
|
working-directory: ml
|
||||||
run: uv python install
|
run: uv python install
|
||||||
|
|
||||||
- name: Synchronise le pipeline ML sans dévier du verrou
|
- name: Synchronise le pipeline ML sur le verrou
|
||||||
working-directory: ml
|
working-directory: ml
|
||||||
run: uv sync --all-groups --frozen
|
run: uv sync --all-groups --locked
|
||||||
|
|
||||||
# Le backend est installé ici parce qu'il porte les migrations, seule source du schéma, et
|
# Le backend est installé ici parce qu'il porte les migrations, seule source du schéma, et
|
||||||
# le test de chaîne, qui interroge l'API.
|
# le test de chaîne, qui interroge l'API.
|
||||||
- name: Synchronise le backend sans dévier du verrou
|
- name: Synchronise le backend sur le verrou
|
||||||
working-directory: apps/backend
|
working-directory: apps/backend
|
||||||
run: uv sync --all-groups --frozen
|
run: uv sync --all-groups --locked
|
||||||
|
|
||||||
# db/init/110-test-database.sql n'est pas monté ici, et sans l'extension la première
|
# db/init/110-test-database.sql n'est pas monté ici, et sans l'extension la première
|
||||||
# révision Alembic refuse de s'appliquer.
|
# révision Alembic refuse de s'appliquer.
|
||||||
@@ -142,8 +126,8 @@ jobs:
|
|||||||
working-directory: apps/backend
|
working-directory: apps/backend
|
||||||
run: uv run alembic upgrade head
|
run: uv run alembic upgrade head
|
||||||
|
|
||||||
# `-m` en ligne de commande écrase celui d'addopts. Couverture désactivée : ce job ne joue
|
# Couverture désactivée : ce job ne joue qu'une partie de la suite, son taux n'aurait pas
|
||||||
# qu'une partie de la suite, son taux n'aurait pas de sens (même raison que backend.yml).
|
# de sens (même raison que backend.yml).
|
||||||
- name: Tests ML exigeant une base
|
- name: Tests ML exigeant une base
|
||||||
working-directory: ml
|
working-directory: ml
|
||||||
run: uv run pytest -m integration --no-cov
|
run: uv run pytest -m integration --no-cov
|
||||||
@@ -159,6 +143,7 @@ jobs:
|
|||||||
sast:
|
sast:
|
||||||
name: Analyse statique de sécurité
|
name: Analyse statique de sécurité
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ml
|
working-directory: ml
|
||||||
@@ -170,7 +155,9 @@ jobs:
|
|||||||
# Pourquoi : pas de cache ici. uvx n'installe pas le projet, le verrou n'alimente donc
|
# 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.
|
# aucune clé de cache ; la seule roue téléchargée est celle de Bandit.
|
||||||
- name: Installe uv
|
- name: Installe uv
|
||||||
uses: astral-sh/setup-uv@v7
|
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
|
||||||
|
with:
|
||||||
|
enable-cache: false
|
||||||
|
|
||||||
- name: Analyse le code livré (bloquant à partir de MEDIUM)
|
- name: Analyse le code livré (bloquant à partir de MEDIUM)
|
||||||
run: uvx bandit==1.9.4 --recursive enervision_ml --severity-level medium --confidence-level medium
|
run: uvx bandit==1.9.4 --recursive enervision_ml --severity-level medium --confidence-level medium
|
||||||
|
|||||||
@@ -1,172 +0,0 @@
|
|||||||
name: SonarQube
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- "apps/frontend/**"
|
|
||||||
- "apps/backend/**"
|
|
||||||
- "ml/**"
|
|
||||||
- "etl/airflow/**"
|
|
||||||
- ".github/workflows/sonarqube.yml"
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- "apps/frontend/**"
|
|
||||||
- "apps/backend/**"
|
|
||||||
- "ml/**"
|
|
||||||
- "etl/airflow/**"
|
|
||||||
- ".github/workflows/sonarqube.yml"
|
|
||||||
|
|
||||||
|
|
||||||
# Build l'ensemble du projet, puis lance les tests
|
|
||||||
# Génère les rapports de couverture, puis lance l'analyse SonarQube
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-front:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
- uses: actions/setup-node@v7
|
|
||||||
with:
|
|
||||||
node-version: 26
|
|
||||||
cache: npm
|
|
||||||
cache-dependency-path: apps/frontend/package-lock.json
|
|
||||||
|
|
||||||
- run: npm ci
|
|
||||||
working-directory: apps/frontend
|
|
||||||
- run: npm run build
|
|
||||||
working-directory: apps/frontend
|
|
||||||
|
|
||||||
test-front:
|
|
||||||
needs: build-front
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
- uses: actions/setup-node@v7
|
|
||||||
with:
|
|
||||||
node-version: 26
|
|
||||||
cache: npm
|
|
||||||
cache-dependency-path: apps/frontend/package-lock.json
|
|
||||||
|
|
||||||
- name : Installation des dépendances (Front)
|
|
||||||
run: npm ci
|
|
||||||
working-directory: apps/frontend
|
|
||||||
|
|
||||||
- name : Lancement des tests et génénration du rapport de couverture (Front)
|
|
||||||
run: npm test --watch=false --code-coverage --coverageReporters=lcov
|
|
||||||
working-directory: apps/frontend
|
|
||||||
|
|
||||||
- name: Upload coverage
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: frontend-coverage
|
|
||||||
path: apps/frontend/coverage/frontend/lcov.info
|
|
||||||
|
|
||||||
build-back:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
- name: Installe uv
|
|
||||||
uses: astral-sh/setup-uv@v7
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
cache-dependency-glob: apps/backend/uv.lock
|
|
||||||
- name: Installe l'interpréteur déclaré par .python-version
|
|
||||||
run: uv python install
|
|
||||||
working-directory: apps/backend
|
|
||||||
|
|
||||||
- name: Synchronise les dépendances sans dévier du verrou
|
|
||||||
run: uv sync --all-groups --frozen
|
|
||||||
working-directory: apps/backend
|
|
||||||
|
|
||||||
- name: Vérifie le formatage
|
|
||||||
run: uv run ruff format --check .
|
|
||||||
working-directory: apps/backend
|
|
||||||
|
|
||||||
- name: Analyse statique
|
|
||||||
run: uv run ruff check --output-format=github .
|
|
||||||
working-directory: apps/backend
|
|
||||||
|
|
||||||
- name: Typage
|
|
||||||
run: uv run mypy app
|
|
||||||
working-directory: apps/backend
|
|
||||||
|
|
||||||
|
|
||||||
test-back:
|
|
||||||
needs: build-back
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
- name: Installe uv
|
|
||||||
uses: astral-sh/setup-uv@v7
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
cache-dependency-glob: apps/backend/uv.lock
|
|
||||||
|
|
||||||
- name : Lancement des tests et génénration du rapport de couverture (Back)
|
|
||||||
run: uv run pytest --cov-fail-under=85 --cov-report=xml
|
|
||||||
working-directory: apps/backend
|
|
||||||
|
|
||||||
- name: Upload coverage
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: backend-coverage
|
|
||||||
path: apps/backend/coverage.xml
|
|
||||||
|
|
||||||
test-ml:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
- name: Installe uv
|
|
||||||
uses: astral-sh/setup-uv@v7
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
cache-dependency-glob: ml/uv.lock
|
|
||||||
|
|
||||||
- name: Installe l'interpréteur déclaré par .python-version
|
|
||||||
run: uv python install
|
|
||||||
working-directory: ml
|
|
||||||
|
|
||||||
- name: Synchronise les dépendances sans dévier du verrou
|
|
||||||
run: uv sync --all-groups --frozen
|
|
||||||
working-directory: ml
|
|
||||||
|
|
||||||
- name: Lancement des tests et génération du rapport de couverture (ML)
|
|
||||||
run: uv run pytest --cov-report=xml
|
|
||||||
working-directory: ml
|
|
||||||
|
|
||||||
- name: Upload coverage
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: ml-coverage
|
|
||||||
path: ml/coverage.xml
|
|
||||||
|
|
||||||
sonarqube:
|
|
||||||
needs: [build-front, build-back, test-front, test-back, test-ml]
|
|
||||||
name: SonarQube
|
|
||||||
# Pourquoi : GitHub ne fournit pas les secrets aux workflows lancés par dependabot[bot].
|
|
||||||
# Sans SONAR_TOKEN le scan échoue sans rien analyser ; build et tests restent joués.
|
|
||||||
if: github.actor != 'dependabot[bot]'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Téléchargement du rapport de couverture (Front)
|
|
||||||
uses: actions/download-artifact@v8
|
|
||||||
with:
|
|
||||||
name: frontend-coverage
|
|
||||||
path: apps/frontend/coverage/frontend
|
|
||||||
- name: Téléchargement du rapport de couverture (Back)
|
|
||||||
uses: actions/download-artifact@v8
|
|
||||||
with:
|
|
||||||
name: backend-coverage
|
|
||||||
path: apps/backend
|
|
||||||
- name: Téléchargement du rapport de couverture (ML)
|
|
||||||
uses: actions/download-artifact@v8
|
|
||||||
with:
|
|
||||||
name: ml-coverage
|
|
||||||
path: ml
|
|
||||||
- name: SonarQube Scan
|
|
||||||
uses: SonarSource/sonarqube-scan-action@v8
|
|
||||||
env:
|
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
sonar.projectKey=ProjetPiscine_EnerVision
|
|
||||||
sonar.organization=groupe3-ener-vision
|
|
||||||
sonar.sourceEncoding=UTF-8
|
|
||||||
|
|
||||||
# Dossier contenant le code source
|
|
||||||
sonar.sources=apps/frontend/src,apps/backend/app
|
|
||||||
sonar.tests=apps/backend/tests
|
|
||||||
|
|
||||||
# Liste des fichiers et dossiers à exclure de l'analyse
|
|
||||||
# Liste des fichiers et dossiers à exclure de l'analyse
|
|
||||||
sonar.exclusions=**/node_modules/**,**/dist/**,**/*.spec.js,**/*.test.js,github,db,ml,docker-compose.yml,**/**/Dockerfile,**/**/proxy.conf.json,**/**/package.json,**/**/angular.json
|
|
||||||
|
|
||||||
# Chemin vers le rapport de couverture de code
|
|
||||||
# Fichier généré par Vitest
|
|
||||||
# Chemin vers le rapport de couverture de code
|
|
||||||
# Fichier généré par Vitest
|
|
||||||
sonar.javascript.lcov.reportPaths=apps/frontend/coverage/frontend/lcov.info
|
|
||||||
sonar.python.coverage.reportPaths=apps/backend/cov.info
|
|
||||||
@@ -2,18 +2,16 @@ sonar.projectKey=ProjetPiscine_EnerVision
|
|||||||
sonar.organization=groupe3-ener-vision
|
sonar.organization=groupe3-ener-vision
|
||||||
sonar.sourceEncoding=UTF-8
|
sonar.sourceEncoding=UTF-8
|
||||||
|
|
||||||
# Dossier contenant le code source
|
|
||||||
sonar.sources=apps/frontend/src,apps/backend,ml,etl/airflow
|
sonar.sources=apps/frontend/src,apps/backend,ml,etl/airflow
|
||||||
# Dossier contenant les tests
|
|
||||||
sonar.tests=apps/frontend/src,apps/backend/tests,ml/tests,etl/airflow/tests
|
sonar.tests=apps/frontend/src,apps/backend/tests,ml/tests,etl/airflow/tests
|
||||||
sonar.test.inclusions=**/*.spec.ts,**/*.test.ts,**/*test_*.py,**/*test.py
|
sonar.test.inclusions=**/*.spec.ts,**/*.test.ts,**/test_*.py,**/*_test.py
|
||||||
|
|
||||||
# Liste des fichiers et dossiers à exclure de l'analyse
|
# Piège : un motif sans `**` (`alembic`, `tests`) ne vise que la racine du dépôt. Les dossiers de
|
||||||
sonar.exclusions=.pytest_cache,.venv,.airflow_home,alembic,tests,ml/data/**,ml/models/**,ml/mlruns/**,ml/mlartifacts/**,**/*/node_modules/**,**/*/dist/**,**/*/build/**,**/*.spec.ts,**/*.test.ts,**/*test_*.py,**/*test.py,**/*.spec.ts
|
# tests restent donc exclus des sources ici, sans quoi leurs fixtures comptent en code non couvert.
|
||||||
|
sonar.exclusions=**/.pytest_cache/**,**/.venv/**,**/.airflow_home/**,**/node_modules/**,**/dist/**,**/build/**,**/htmlcov/**,**/coverage/**,**/tests/**,**/alembic/**,ml/data/**,ml/models/**,ml/mlruns/**,ml/mlartifacts/**,**/*.spec.ts,**/*.test.ts,**/test_*.py,**/*_test.py
|
||||||
|
|
||||||
# Chemin vers le rapport de couverture de code
|
# Rapports versés par les jobs de backend.yml, ml.yml et frontend.yml, repris par le job sonar de ci.yml.
|
||||||
# Fichier généré par Pytest
|
|
||||||
sonar.python.coverage.reportPaths=apps/backend/coverage.xml,ml/coverage.xml
|
sonar.python.coverage.reportPaths=apps/backend/coverage.xml,ml/coverage.xml
|
||||||
# Les DAGs n'ont pas de couverture mesurable : leurs tests ne font que les charger (DagBag)
|
|
||||||
sonar.coverage.exclusions=etl/airflow/**
|
|
||||||
sonar.javascript.lcov.reportPaths=apps/frontend/coverage/frontend/lcov.info
|
sonar.javascript.lcov.reportPaths=apps/frontend/coverage/frontend/lcov.info
|
||||||
|
# Les DAGs n'ont pas de couverture mesurable : leurs tests ne font que les charger (DagBag).
|
||||||
|
sonar.coverage.exclusions=etl/airflow/**
|
||||||
|
|||||||
Reference in New Issue
Block a user