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

249 lines
7.2 KiB
Markdown

# 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/<projet>` 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 <ancien-container> && docker rm <ancien-container>`
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/<service-id>
```
---
## 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
```