Build and deploy / test-python (push) Successful in 13m44s
Build and deploy / test-js (push) Successful in 1m27s
Build and deploy / lint-python (push) Successful in 4m32s
Build and deploy / lint-js (push) Successful in 3m5s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 4m3s
Les hooks pre-commit locaux (deja tous verts, voir le commit Phase 3) ne protegent que la machine du committeur, jamais un push direct ou une PR mergee depuis ailleurs sans passer par ces hooks. Ajoute donc en CI : - lint-python (ruff check/format, mypy --strict, vulture, bandit, import-linter) et lint-js (eslint, stylelint) : bloquants des maintenant, memes commandes que .pre-commit-config.yaml, deja verts en local donc aucune raison d'attendre. djlint volontairement exclu (49 H021 deja en backlog assume, a ajouter ici une fois ce lot traite). build-and-push en depend desormais (needs), en plus des tests deja en place. - sonarqube : scan contre l'instance self-hebergee (sonar.forgebase.fr) a chaque push. continue-on-error: true au niveau du job (non-bloquant pendant cette premiere periode, le temps de trier le rapport deja analyse en Phase 3 - code smells/vulnerabilites), et absent du needs de build-and-push : un echec ici n'affecte jamais le reste du pipeline, meme si l'infra n'est pas encore prete (echec attendu tant que SONAR_TOKEN n'est pas configure cote Gitea). Secret a ajouter dans Gitea (Parametres du depot > Actions > Secrets) : SONAR_TOKEN (jeton d'analyse genere sur sonar.forgebase.fr, jamais le mot de passe admin). djLint (H021, styles inline) volontairement saute pour ce commit - meme backlog assume que le commit Phase 3, aucun rapport avec ce changement. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
213 lines
9.6 KiB
YAML
213 lines
9.6 KiB
YAML
name: Build and deploy
|
|
|
|
on:
|
|
push:
|
|
branches: [main, dev]
|
|
|
|
# Les jobs "test-*"/"lint-*"/"sonarqube" tournent sur CHAQUE push (main et
|
|
# dev) : jusqu'ici aucune étape de CI n'exécutait la suite de tests ni les
|
|
# outils qualité, rien n'empêchait un commit cassé d'atteindre la
|
|
# production (voir l'audit qualité de la Phase 0 du plan). "build-and-
|
|
# push"/"deploy", eux, restent réservés à main (via le filtre "if" sur
|
|
# gitea.ref) — un push sur dev ne doit jamais redéployer la prod, seulement
|
|
# faire tourner tests/lint/sonar.
|
|
#
|
|
# lint-python/lint-js rejouent EXACTEMENT les hooks pre-commit locaux
|
|
# (.pre-commit-config.yaml) mais bloquants ici dès le départ (déjà tous
|
|
# verts en local, voir le commit "Phase 3 : hardening qualite de code") —
|
|
# les hooks pre-commit ne protègent que la machine du committeur, jamais
|
|
# un push direct ou une PR mergée depuis ailleurs. djlint EXCLU
|
|
# volontairement (49 H021 "styles inline" déjà en backlog assumé, voir
|
|
# CODE_QUALITY.md) — à ajouter ici quand ce lot sera traité. sonarqube,
|
|
# lui, reste NON-BLOQUANT (continue-on-error) pendant cette première
|
|
# période — voir CODE_QUALITY.md pour la trajectoire vers un mode
|
|
# bloquant une fois le rapport trié (code smells, vulnerabilites) plutôt
|
|
# que de bloquer tout de suite sur des centaines de signalements pas
|
|
# encore triés.
|
|
|
|
# Secrets à configurer dans Gitea (Paramètres du dépôt > Actions > Secrets) :
|
|
# REGISTRY_HOST adresse du registre d'images (ex: gitea.exemple.com)
|
|
# REGISTRY_IMAGE chemin complet de l'image (ex: gitea.exemple.com/mon-compte/forge-engine)
|
|
# REGISTRY_USER utilisateur pour "docker login" sur ce registre
|
|
# REGISTRY_TOKEN jeton d'accès (scope package:write) pour ce registre
|
|
# DEPLOY_HOST adresse du serveur de production
|
|
# DEPLOY_USER utilisateur SSH sur ce serveur
|
|
# DEPLOY_SSH_KEY clé privée SSH (au format PEM) autorisée sur ce serveur
|
|
# DEPLOY_PATH dossier sur le serveur où vit docker-compose.prod.yml (ex: /home/deploy/forge-engine)
|
|
# SONAR_TOKEN jeton d'analyse SonarQube (Mon compte > Security > Generate Token
|
|
# sur sonar.forgebase.fr) — jamais le mot de passe admin.
|
|
#
|
|
# Utilise directement docker/ssh/scp en ligne de commande plutôt que des
|
|
# actions du marketplace, pour ne pas dépendre de la disponibilité de
|
|
# github.com / du miroir gitea.com depuis le runner self-hosted.
|
|
|
|
jobs:
|
|
# Deux essais précédents ont échoué :
|
|
# 1. docker run -v "$PWD":/app ... imbriqué depuis le runner — sur un
|
|
# runner qui exécute déjà le job dans son propre conteneur (Docker-
|
|
# outside-of-Docker), "$PWD" ne correspond à aucun chemin que le démon
|
|
# Docker de l'HÔTE peut monter : /app se retrouvait vide.
|
|
# 2. jobs.test.container: image: python:3.13-slim — actions/checkout@v4
|
|
# (une action Node.js) a alors besoin de Node dans CE conteneur pour
|
|
# s'exécuter, qui ne l'a pas ("command not found", voir
|
|
# nektos/act#107) : `container:` remplace tout l'environnement des
|
|
# steps, pas seulement celui des commandes qu'on y lance soi-même.
|
|
# Solution : le job tourne sur le runner PAR DÉFAUT (checkout fonctionne,
|
|
# Node y est déjà disponible), et les tests s'exécutent PENDANT un
|
|
# `docker build` (Dockerfile passé par stdin, jamais commité) — le
|
|
# transfert du contexte de build au démon Docker ne dépend JAMAIS d'un
|
|
# chemin hôte/montage de volume (protocole API, un tar envoyé tel quel),
|
|
# donc insensible au problème Docker-outside-of-Docker du point 1.
|
|
test-python:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Tests Python (pytest, exécutés PENDANT le build d'une image jetable)
|
|
run: |
|
|
# .dockerignore (à la racine, pensé pour l'image de PROD) exclut
|
|
# tests/ du contexte de build — COPY . . ne l'aurait donc jamais
|
|
# inclus, quel que soit le Dockerfile utilisé (.dockerignore
|
|
# s'applique au contexte entier, pas à un Dockerfile en
|
|
# particulier). Neutralisé ici SANS RISQUE : ce checkout est
|
|
# propre à ce job, jetable, jamais repoussé vers le dépôt.
|
|
mv .dockerignore .dockerignore.disabled-for-ci
|
|
docker build -f - -t forge-test-python:${{ gitea.sha }} . <<'DOCKERFILE'
|
|
FROM python:3.13-slim
|
|
WORKDIR /app
|
|
COPY requirements.txt requirements-dev.txt ./
|
|
RUN pip install --no-cache-dir -r requirements-dev.txt
|
|
COPY . .
|
|
RUN python -m pytest tests/ -q
|
|
DOCKERFILE
|
|
|
|
test-js:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Tests JS (node:test — logique pure de static/js/play/, et l'assistant déclencheurs de static/js/scenes/+static/js/triggers/, voir le plan de modularisation)
|
|
run: |
|
|
docker build -f - -t forge-test-js:${{ gitea.sha }} . <<'DOCKERFILE'
|
|
FROM node:20-slim
|
|
WORKDIR /app
|
|
COPY static/js/play/ static/js/play/
|
|
COPY static/js/scenes/ static/js/scenes/
|
|
COPY static/js/triggers/ static/js/triggers/
|
|
RUN node --test static/js/play/__tests__/*.test.js static/js/scenes/__tests__/*.test.js
|
|
DOCKERFILE
|
|
|
|
lint-python:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Qualite Python (ruff/mypy/vulture/bandit/import-linter — memes commandes que .pre-commit-config.yaml)
|
|
run: |
|
|
# Meme neutralisation de .dockerignore que test-python ci-dessus :
|
|
# ruff/mypy typent aussi tests/ (voir le plan de typage strict),
|
|
# exclu par defaut de l'image de PROD.
|
|
mv .dockerignore .dockerignore.disabled-for-ci
|
|
docker build -f - -t forge-lint-python:${{ gitea.sha }} . <<'DOCKERFILE'
|
|
FROM python:3.13-slim
|
|
WORKDIR /app
|
|
COPY requirements.txt requirements-dev.txt ./
|
|
RUN pip install --no-cache-dir -r requirements-dev.txt
|
|
COPY . .
|
|
RUN ruff check .
|
|
RUN ruff format --check .
|
|
RUN mypy .
|
|
RUN vulture
|
|
RUN bandit -c pyproject.toml -r ai auth core db filters publish routes screens scripts app.py build_css.py
|
|
RUN lint-imports
|
|
DOCKERFILE
|
|
|
|
lint-js:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Qualite JS/CSS (eslint/stylelint — memes commandes que .pre-commit-config.yaml, via les scripts npm de package.json)
|
|
run: |
|
|
docker build -f - -t forge-lint-js:${{ gitea.sha }} . <<'DOCKERFILE'
|
|
FROM node:20-slim
|
|
WORKDIR /app
|
|
COPY package.json package-lock.json ./
|
|
RUN npm ci
|
|
COPY . .
|
|
RUN npm run lint:js
|
|
RUN npm run lint:css
|
|
DOCKERFILE
|
|
|
|
sonarqube:
|
|
runs-on: ubuntu-latest
|
|
# Non-bloquant pendant cette premiere periode (voir le bloc de
|
|
# commentaires en tete de fichier) — un echec ici n'empeche jamais
|
|
# build-and-push/deploy, contrairement a lint-python/lint-js.
|
|
continue-on-error: true
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Analyse SonarQube (rapport seul, non-bloquant)
|
|
run: |
|
|
# Meme neutralisation de .dockerignore que test-python : sonar-
|
|
# project.properties couvre aussi tests/ (sonar.tests).
|
|
mv .dockerignore .dockerignore.disabled-for-ci
|
|
docker build -f - -t forge-sonar:${{ gitea.sha }} . <<'DOCKERFILE'
|
|
FROM sonarsource/sonar-scanner-cli:latest
|
|
WORKDIR /usr/src
|
|
COPY . .
|
|
DOCKERFILE
|
|
docker run --rm forge-sonar:${{ gitea.sha }} \
|
|
-Dsonar.host.url=https://sonar.forgebase.fr \
|
|
-Dsonar.token=${{ secrets.SONAR_TOKEN }}
|
|
|
|
build-and-push:
|
|
needs: [test-python, test-js, lint-python, lint-js]
|
|
if: gitea.ref == 'refs/heads/main'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Connexion au registre d'images
|
|
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "${{ secrets.REGISTRY_HOST }}" -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
|
|
|
- name: Build de l'image
|
|
run: |
|
|
docker build \
|
|
-t "${{ secrets.REGISTRY_IMAGE }}:${{ gitea.sha }}" \
|
|
-t "${{ secrets.REGISTRY_IMAGE }}:latest" \
|
|
.
|
|
|
|
- name: Publication de l'image
|
|
run: |
|
|
docker push "${{ secrets.REGISTRY_IMAGE }}:${{ gitea.sha }}"
|
|
docker push "${{ secrets.REGISTRY_IMAGE }}:latest"
|
|
|
|
deploy:
|
|
needs: build-and-push
|
|
if: gitea.ref == 'refs/heads/main'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Préparation de la clé SSH
|
|
run: |
|
|
mkdir -p ~/.ssh
|
|
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key
|
|
chmod 600 ~/.ssh/deploy_key
|
|
ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts
|
|
|
|
- name: Envoi du docker-compose.prod.yml sur le serveur
|
|
run: |
|
|
scp -i ~/.ssh/deploy_key docker-compose.prod.yml \
|
|
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PATH }}/docker-compose.prod.yml"
|
|
|
|
- name: Pull et redémarrage sur le serveur
|
|
run: |
|
|
ssh -i ~/.ssh/deploy_key "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" '
|
|
set -e
|
|
cd "${{ secrets.DEPLOY_PATH }}"
|
|
echo "REGISTRY_IMAGE=${{ secrets.REGISTRY_IMAGE }}" > .env
|
|
echo "IMAGE_TAG=${{ gitea.sha }}" >> .env
|
|
docker login "${{ secrets.REGISTRY_HOST }}" -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
|
|
docker compose -f docker-compose.prod.yml pull
|
|
docker compose -f docker-compose.prod.yml up -d
|
|
docker image prune -f
|
|
'
|