Files
prestashop-test/docs-bonne-pratiqueCI/woodpecker-tips.md
syoul 4e02e59614 docs: ajout dossier docs-bonne-pratiqueCI
Récap complet de l'infra CI sonic/openus : Gitea, Woodpecker server/agent,
Dependency-Track, tips Woodpecker next, checklist nouvelle app.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 13:36:22 +01:00

7.2 KiB

Woodpecker next — Bugs connus et règles de la stack

Tous les problèmes rencontrés en production et leurs fixes. Source : docs-sonic/retrospective-problemes-2026-03-17.md


Règles à appliquer systématiquement

Règle Raison
Jamais ${VAR} dans commands: — toujours $VAR Woodpecker next substitue ${VAR} au parse YAML, avant exécution. Vaut pour les secrets, vars CI, et vars shell locales.
from_secret et volumes: dans des steps séparés Bug Woodpecker next : les secrets sont vides si le step a aussi des volumes:.
Chemins absolus après un cd Un cd dans commands: persiste pour toutes les lignes suivantes du même step.
ACME_EXIT=0; cmd || ACME_EXIT=$? pour capturer les codes non-zéro set -e est actif dans les blocs - |. cmd; VAR=$? tue le script si cmd retourne != 0.

1. ${VAR} substitué au parse YAML

Symptôme : valeurs vides dans les commandes, ou docker exec "-db" (nom coupé).

# ❌
echo "${PS_DOMAIN}"
docker exec "${PROJECT}-db"

# ✅
env | grep '^PS_DOMAIN=' | cut -d= -f2
docker exec "$PROJECT-db"

2. from_secret + volumes: incompatibles dans le même step

Symptôme : secrets vides (DTRACK_TOKEN vide, etc.) quand le step a aussi un volumes:.

Fix : séparer en deux steps :

  • step avec volumes: : lit depuis des fichiers copiés dans le workspace ou dans un chemin monté
  • step avec from_secret : pas de volumes:

Exemple de découpage :

# ✅ Step secrets uniquement (pas de volumes)
- name: write-env
  environment:
    MON_SECRET:
      from_secret: mon_secret
  commands:
    - echo "MON_SECRET=$MON_SECRET" > .env.deploy

# ✅ Step deploy avec volumes (lit depuis .env.deploy)
- name: deploy
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock
    - /opt/monapp:/opt/monapp
  commands:
    - cp .env.deploy /opt/monapp/.env

3. set -e et capture de codes de retour non-zéro

Symptôme : pipeline échoue sur une commande qui retourne un code non-zéro attendu (ex : acme.sh exit 2 = cert déjà valide).

# ❌ fragile : set -e tue le script si exit != 0
cmd ; EXIT=$?

# ✅ correct
EXIT=0
cmd || EXIT=$?
if [ "$EXIT" -ne 0 ] && [ "$EXIT" -ne 2 ]; then  # 2 = cas acceptable
  exit 1
fi

4. YAML cassé par variables shell multi-lignes

Symptôme : yaml: line XX: could not find expected ':'.

Cause : une variable assignée sur plusieurs lignes dans un - | est interprétée comme YAML.

# ❌ casse le YAML
ROUTES="route add ... \
        route add ..."

# ✅
ROUTES=$(printf 'route add %s %s/* http://%s:80/\nroute add %s %s:443/* http://%s:80/' \
  "$SERVICE" "$DOMAIN" "$IP" "$SERVICE" "$DOMAIN" "$IP")

5. CWD modifié par cd dans les commandes précédentes

Symptôme : cat: can't open '.env.deploy': No such file or directory alors que le fichier existe.

Cause : un cd /opt/monapp dans une ligne précédente change le CWD pour tout le step.

# ❌
commands:
  - cd /opt/monapp && docker compose up -d
  - PASS=$(grep '^DB_PASSWORD=' .env.deploy | cut -d= -f2)  # cherche dans /opt/monapp/

# ✅ copier avant le cd, puis chemin absolu
commands:
  - cp .env.deploy /opt/monapp/.env
  - cd /opt/monapp && docker compose up -d
  - PASS=$(grep '^DB_PASSWORD=' /opt/monapp/.env | cut -d= -f2)

6. SERVICE_80_CHECK_TCP : valeur "true" obligatoire

Registrator ignore les valeurs vides. Avec PS_SSL_ENABLED_EVERYWHERE=1, le service redirige tout en 302 → un check HTTP voit "failing". Utiliser TCP.

# ❌ ignoré par Registrator
- SERVICE_80_CHECK_TCP=

# ✅
- SERVICE_80_CHECK_TCP=true

7. Routes Fabio : toujours /*

Fabio utilise le glob matcher. / matche uniquement le chemin exact. Sans /*, tous les sous-chemins tombent sur le catch-all nginx → 404.

# ❌ ne route que /
route add monservice mondomaine.fr/ http://172.22.0.x:80/

# ✅ route tout
route add monservice mondomaine.fr/* http://172.22.0.x:80/
route add monservice mondomaine.fr:443/* http://172.22.0.x:80/

8. Consul catalog seul insuffisant pour Fabio

Testé en production : Fabio ne détecte pas les services via le catalog Consul même quand ils sont healthy avec les bons tags urlprefix-*.

Le KV fabio/config/<projet> est obligatoire. Mettre à jour l'IP à chaque déploiement :

# Dans le step deploy, après docker compose up :
IP=$(docker inspect "$PROJECT-app" \
  --format '{{(index .NetworkSettings.Networks "sonic").IPAddress}}')
ROUTES=$(printf 'route add %s %s/* http://%s:80/\nroute add %s %s:443/* http://%s:80/' \
  "$PROJECT" "$DOMAIN" "$IP" "$PROJECT" "$DOMAIN" "$IP")
docker exec sonic-consul env CONSUL_HTTP_TOKEN="$CTOK" consul kv put "fabio/config/$PROJECT" "$ROUTES"

Utiliser une sous-clé fabio/config/$PROJECT (pas fabio/config) pour ne pas écraser les routes des autres projets.


9. acme.sh : toujours --home /etc/acme.sh

Sans ce flag, acme.sh écrit dans /root/.acme.sh dans le container → non persistant.

# ✅ pattern complet (idempotent)
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=$?
[ "$ACME_EXIT" -ne 0 ] && [ "$ACME_EXIT" -ne 2 ] && exit 1
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

acme-companion (LETSENCRYPT_HOST) ne couvre que les containers sur le réseau nginx-proxy. Pour les services Fabio, acme.sh via docker exec est la seule méthode qui fonctionne.


10. Instances Consul stale après renommage de container

Changer COMPOSE_PROJECT_NAME → nouveaux containers, nouvelle IP, mais les anciennes registrations Consul restent actives → Fabio peut router vers des IPs mortes.

Fix :

  1. docker stop <ancien-container> && docker rm <ancien-container>
  2. Registrator désenregistre automatiquement à l'arrêt
  3. Supprimer les entrées manuelles orphelines si besoin :
    curl -X PUT -H "X-Consul-Token: $CTOK" \
      http://localhost:8500/v1/agent/service/deregister/<service-id>
    

11. Syft : image distroless incompatible avec Woodpecker

anchore/syft:latest est distroless → exec: "/bin/sh": no such file or directory.

# ❌
image: anchore/syft:latest

# ✅
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
  - syft mon-image:tag -o cyclonedx-json --file .reports/sbom.cyclonedx.json

12. Dependency-Track : token sans permissions → HTTP 403

Le token de l'équipe Automation doit avoir les permissions :

  • BOM_UPLOAD
  • PROJECT_CREATION

Administration > Access Management > Teams > Automation > Permissions.

Pour debugger une réponse HTTP :

# ❌ -f masque le body de l'erreur
curl -sf -X POST ...

# ✅ capture HTTP code + body
HTTP=$(curl -s -o /tmp/resp.txt -w "%{http_code}" -X POST ...)
echo "HTTP $HTTP : $(cat /tmp/resp.txt)"
[ "$HTTP" -ge 200 ] && [ "$HTTP" -lt 300 ] || exit 1