Files
TechradarDev/.woodpecker.yml
syoul 9fb80ef59f
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
fix: sbom-generate - inclure node_modules pour SBOM complet
Retirer l'exclusion des node_modules permet a Syft de scanner
les paquets reellement installes (transitifs inclus).
Seuls les artefacts de build sont exclus (build/, .next/, out/).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 20:42:49 +01:00

159 lines
6.3 KiB
YAML

when:
- branch:
- main
- stand-alone
event: push
steps:
# Etape 0 : Validation syntaxique du docker-compose
# Les vars CI (CI_REPO_OWNER, CI_COMMIT_BRANCH) sont injectees automatiquement par Woodpecker
- name: validate
image: docker:27-cli
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
RADAR_DOMAIN: validate.example.com
commands:
- |
export COMPOSE_PROJECT_NAME=$(printf '%s-%s-%s' "$CI_REPO_OWNER" "$CI_REPO_NAME" "$CI_COMMIT_BRANCH" | tr 'A-Z/' 'a-z-')
docker compose -f docker-compose.business.yml config --quiet
- echo "docker-compose.business.yml valide"
# Etape 1 : Build de l'application statique
- name: build
image: node:20-alpine
environment:
NODE_ENV: production
HUSKY: 0
HUSKY_SKIP_INSTALL: 1
commands:
- apk add --no-cache git python3
- npm ci --legacy-peer-deps
- cd radar-app && npm ci --legacy-peer-deps --include=dev && npm run build:icons && cd ..
- node scripts/generate-team-visualization-data.js
- npm run build
- ls -la build/ | head -10
# Etape 2a : Ecriture du .env depuis les secrets
# NOTE: from_secret et volumes: incompatibles dans le meme step (bug Woodpecker next)
- name: write-env
image: alpine:3.20
environment:
RADAR_DOMAIN:
from_secret: radar_domain
commands:
- env | grep -E "^(RADAR_DOMAIN)=" > .env.deploy
# COMPOSE_PROJECT_NAME : convention user-project-branch, genere depuis les vars CI
- OWNER=$(echo "$CI_REPO_OWNER" | tr 'A-Z' 'a-z') && REPO=$(echo "$CI_REPO_NAME" | tr 'A-Z' 'a-z') && BRANCH=$(echo "$CI_COMMIT_BRANCH" | tr 'A-Z/' 'a-z-') && echo "COMPOSE_PROJECT_NAME=$OWNER-$REPO-$BRANCH" >> .env.deploy
- echo "Fichier .env.deploy cree ($(wc -c < .env.deploy) octets)"
# Etape 2b : Deploiement sur sonic via Docker socket
# NOTE: from_secret et volumes: incompatibles — pas de secrets ici
- name: deploy
image: docker:27-cli
volumes:
- /var/run/docker.sock:/var/run/docker.sock
commands:
- docker compose --env-file .env.deploy -f docker-compose.business.yml build --no-cache
- docker compose --env-file .env.deploy -f docker-compose.business.yml up -d --remove-orphans
- docker compose --env-file .env.deploy -f docker-compose.business.yml ps
- |
DOMAIN=$(grep '^RADAR_DOMAIN=' .env.deploy | cut -d= -f2)
# --- Certificat TLS (acme.sh via sonic-acme-1) ---
# acme.sh est idempotent : skip si cert valide, renouvelle si proche expiration
# exit 0 = emis/renouvele, exit 2 = skip (domaine inchange), autres = erreur
ACME_EXIT=0
docker exec sonic-acme-1 /app/acme.sh \
--home /etc/acme.sh \
--issue -d "$DOMAIN" \
--webroot /usr/share/nginx/html \
--server letsencrypt \
--accountemail support+acme@asycn.io || ACME_EXIT=$?
if [ "$ACME_EXIT" -ne 0 ] && [ "$ACME_EXIT" -ne 2 ]; then
echo "ERREUR: acme.sh a echoue (exit $ACME_EXIT)"
exit 1
fi
docker exec sonic-acme-1 cp /etc/acme.sh/$DOMAIN/fullchain.cer /host/certs/$DOMAIN-cert.pem
docker exec sonic-acme-1 cp /etc/acme.sh/$DOMAIN/$DOMAIN.key /host/certs/$DOMAIN-key.pem
echo "Cert TLS: /host/certs/$DOMAIN-cert.pem OK (acme exit $ACME_EXIT)"
# Etape 3a : Generation SBOM (Syft) — inventaire des dependances npm du workspace
# Scan du repertoire source (pas de docker socket = pas de bug volumes/CI-vars)
- name: sbom-generate
image: alpine:3.20
commands:
- apk add --no-cache curl
- curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin latest
- mkdir -p .reports
- syft dir:. --exclude './build' --exclude './radar-app/.next' --exclude './radar-app/out' -o cyclonedx-json --file .reports/sbom-radar.cyclonedx.json
- echo "SBOM genere $(wc -c < .reports/sbom-radar.cyclonedx.json) octets"
# Etape 3b : Scan CVE (Trivy) depuis le SBOM Syft
# Cache /home/syoul/trivy-cache evite ~200Mo de telechargement des DB CVE a chaque build
# Prerequis sur sonic : mkdir -p /home/syoul/trivy-cache
- name: sbom-scan
image: aquasec/trivy:latest
volumes:
- /home/syoul/trivy-cache:/root/.cache/trivy
commands:
- trivy sbom --format json --output .reports/trivy-radar.json .reports/sbom-radar.cyclonedx.json
- echo "Scan CVE termine"
# Etape 3c : Publication SBOM vers Dependency-Track (dtrack.syoul.fr)
# NOTE: from_secret et volumes: incompatibles — pas de volumes ici
- name: sbom-publish
image: alpine/curl:latest
environment:
DTRACK_TOKEN:
from_secret: dependency_track_token
commands:
- |
VERSION=$(date +%Y-%m-%d)-$(echo "$CI_COMMIT_SHA" | cut -c1-8)
HTTP=$(curl -s -o /tmp/dtrack-response.txt -w "%{http_code}" -X POST "https://dtrack.syoul.fr/api/v1/bom" \
-H "X-Api-Key: $DTRACK_TOKEN" \
-F "autoCreate=true" \
-F "projectName=techradardev-app" \
-F "projectVersion=$VERSION" \
-F "bom=@.reports/sbom-radar.cyclonedx.json")
echo "HTTP $HTTP : $(cat /tmp/dtrack-response.txt)"
[ "$HTTP" -ge 200 ] && [ "$HTTP" -lt 300 ] || exit 1
# Etape 4 : Healthcheck post-deploiement
- name: healthcheck
image: alpine:3.20
commands:
- apk add --no-cache --quiet curl
- |
DOMAIN=$(grep '^RADAR_DOMAIN=' .env.deploy | cut -d= -f2)
if [ -z "$DOMAIN" ]; then
echo "ERREUR: RADAR_DOMAIN non defini dans .env.deploy"
exit 1
fi
TARGET="https://$DOMAIN"
echo "Healthcheck sur $TARGET (max 100s)..."
MAX=20
i=0
until [ $i -ge $MAX ]; do
CODE=$(curl -sSo /dev/null -w "%{http_code}" "$TARGET" 2>/dev/null)
echo "Tentative $((i+1))/$MAX - HTTP $CODE"
if [ "$CODE" = "200" ] || [ "$CODE" = "301" ] || [ "$CODE" = "302" ]; then
echo "Radar repond sur $TARGET"
exit 0
fi
i=$((i+1))
sleep 5
done
echo "ERREUR: Radar ne repond pas apres 100 secondes"
exit 1
# Notification en cas d'echec
- name: notify-failure
image: alpine:3.20
commands:
- 'echo "ECHEC pipeline #$CI_BUILD_NUMBER sur commit $CI_COMMIT_SHA"'
- 'echo "Branche: $CI_COMMIT_BRANCH"'
when:
- status: failure