From 38bbff5ff76a9985c73ce55cdddded6fbda37b66 Mon Sep 17 00:00:00 2001 From: syoul Date: Thu, 19 Mar 2026 14:18:00 +0100 Subject: [PATCH] first commit: add woodpecker et compose --- .woodpecker.yml | 107 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 62 ++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 .woodpecker.yml create mode 100644 docker-compose.yml diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..723919a --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,107 @@ +when: + - branch: main + event: push + +steps: + + # Etape 1 : Ecriture du .env.deploy depuis les secrets + # NOTE: ne pas utiliser ${VAR} dans commands (bug Woodpecker next), utiliser env | grep + # NOTE: from_secret et volumes incompatibles dans le meme step (bug Woodpecker next) + - name: write-env + image: alpine:3.20 + environment: + DTRACK_DOMAIN: + from_secret: dtrack_domain + commands: + - env | grep -E "^(DTRACK_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)" + + # TEST write-env : valide le contenu du .env.deploy + - name: test-env + image: alpine:3.20 + commands: + - | + if [ ! -f .env.deploy ]; then + echo "FAIL: .env.deploy introuvable dans le workspace" + exit 1 + fi + echo "PASS: .env.deploy present" + - | + for KEY in COMPOSE_PROJECT_NAME DTRACK_DOMAIN; do + VAL=$(grep "^${KEY}=" .env.deploy | cut -d= -f2) + if [ -z "$VAL" ]; then + echo "FAIL: $KEY manquant ou vide dans .env.deploy" + exit 1 + fi + echo "PASS: $KEY = $VAL" + done + + # Etape 2 : Deploiement sur sonic via Docker socket + # L'agent Woodpecker tourne sur sonic — pas de SSH requis + - name: deploy + image: docker:27-cli + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /opt/dtrack:/opt/dtrack + commands: + - cp .env.deploy /opt/dtrack/.env + - chmod 600 /opt/dtrack/.env + - cp docker-compose.yml /opt/dtrack/docker-compose.yml + - cd /opt/dtrack && docker compose pull + - cd /opt/dtrack && docker compose up -d --remove-orphans + - cd /opt/dtrack && docker compose ps + + # TEST deploy : verifie que les conteneurs sont running + - name: test-deploy + image: docker:27-cli + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - | + PROJECT=$(grep '^COMPOSE_PROJECT_NAME=' /opt/dtrack/.env | cut -d= -f2) + for CONTAINER in apiserver frontend; do + STATUS=$(docker inspect --format '{{.State.Status}}' "${PROJECT}-${CONTAINER}" 2>/dev/null || echo "absent") + echo "${PROJECT}-${CONTAINER} : $STATUS" + [ "$STATUS" = "running" ] || { echo "FAIL: ${CONTAINER} non running"; exit 1; } + echo "PASS: ${CONTAINER} running" + done + + # Etape 3 : Healthcheck HTTP sur l'apiserver + - name: healthcheck + image: alpine:3.20 + commands: + - apk add --no-cache --quiet curl + - | + DOMAIN=$(grep '^DTRACK_DOMAIN=' .env.deploy | cut -d= -f2) + if [ -z "$DOMAIN" ]; then + echo "ERREUR: DTRACK_DOMAIN non defini dans .env.deploy" + exit 1 + fi + TARGET="https://$DOMAIN/api/version" + echo "Healthcheck sur $TARGET (max 2 minutes)..." + MAX=12 + i=0 + until [ $i -ge $MAX ]; do + RESPONSE=$(curl -sf "$TARGET" 2>/dev/null) + if [ $? -eq 0 ]; then + echo "PASS: apiserver repond" + echo "version: $RESPONSE" + exit 0 + fi + i=$((i+1)) + echo "Tentative $i/$MAX - retry dans 10s" + sleep 10 + done + echo "ERREUR: apiserver ne repond pas apres 2 minutes" + 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 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c3a8b03 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,62 @@ +# Convention de nommage : user-project-branch (ex: syoul-dtrack-main) +# Permet plusieurs instances en parallele (prod/staging) sans collision +name: ${COMPOSE_PROJECT_NAME:-syoul-dtrack-main} + +services: + apiserver: + image: dependencytrack/apiserver:4.12 + container_name: ${COMPOSE_PROJECT_NAME:-syoul-dtrack-main}-apiserver + restart: always + environment: + # Stockage H2 interne — suffisant pour une team, pas de PostgreSQL requis + ALPINE_DATABASE_MODE: internal + ALPINE_DATA_DIRECTORY: /data + volumes: + - dtrack_data:/data + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:8080/api/version"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 120s # ~60-90s au premier démarrage + networks: + - dtrack-net + - sonic + labels: + # Registrator enregistre le service dans Consul via le reseau "sonic" + # Fabio route /api/* vers l'apiserver (prioritaire sur le catch-all frontend) + - SERVICE_8080_NAME=${SERVICE_8080_APISERVER_NAME:-${COMPOSE_PROJECT_NAME}-apiserver-8080} + - SERVICE_8080_TAGS=${SERVICE_8080_APISERVER_TAGS:-urlprefix-${DTRACK_DOMAIN}/api/*} + - SERVICE_8080_CHECK_TCP=true + # sonic-acme-1 emet le cert TLS detecte automatiquement par Fabio via SNI + - LETSENCRYPT_HOST=${DTRACK_DOMAIN} + + frontend: + image: dependencytrack/frontend:4.12 + container_name: ${COMPOSE_PROJECT_NAME:-syoul-dtrack-main}-frontend + restart: always + environment: + # URL de l'apiserver vue depuis le navigateur (HTTPS public) + API_BASE_URL: https://${DTRACK_DOMAIN} + depends_on: + apiserver: + condition: service_healthy + networks: + - dtrack-net + - sonic + labels: + # Catch-all /* : doit etre enregistre apres /api/* pour que Fabio priorise l'apiserver + - SERVICE_8080_NAME=${SERVICE_8080_FRONTEND_NAME:-${COMPOSE_PROJECT_NAME}-frontend-8080} + - SERVICE_8080_TAGS=${SERVICE_8080_FRONTEND_TAGS:-urlprefix-${DTRACK_DOMAIN}/*} + - SERVICE_8080_CHECK_TCP=true + +volumes: + dtrack_data: + +networks: + dtrack-net: + # Reseau interne isole apiserver <-> frontend + driver: bridge + sonic: + # Reseau externe partage avec Registrator/Consul/Fabio + external: true