# 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é). ```bash # ❌ 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 : ```yaml # ✅ 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). ```bash # ❌ 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. ```bash # ❌ 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. ```yaml # ❌ 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. ```yaml # ❌ 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/` est obligatoire.** Mettre à jour l'IP à chaque déploiement : ```bash # 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. ```bash # ✅ 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 && docker rm ` 2. Registrator désenregistre automatiquement à l'arrêt 3. Supprimer les entrées manuelles orphelines si besoin : ```bash curl -X PUT -H "X-Consul-Token: $CTOK" \ http://localhost:8500/v1/agent/service/deregister/ ``` --- ## 11. Syft : image distroless incompatible avec Woodpecker `anchore/syft:latest` est distroless → `exec: "/bin/sh": no such file or directory`. ```yaml # ❌ 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 : ```bash # ❌ -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 ```