0fbca06d3d
Cartographie complète (liste_taches/coherence_taches), briefs tacheN + gates validation_tacheN, design tâche 2 (docs/design/tache2/), specs/plans jalon 1-2 et tâche 1.9/2 (Phase 1, Phase 2, SJ-0→3). Validations consignées (1.9 ✅, 2-8 🟡). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
212 lines
12 KiB
Markdown
212 lines
12 KiB
Markdown
# 20 — Docker Compose : inventaire, flux et pseudo-shell
|
|
|
|
> Axe D + livrables §4.1/§4.2. Conçu pour cocher chaque case de `validation_tache2.md §6` (« Focus Docker Compose »). Gestion **par SSH sur la machine cible** via `server/ssh/client.ts` et templates versionnés ; pas de moteur parallèle.
|
|
|
|
---
|
|
|
|
## 1. Méthode retenue (MVP)
|
|
|
|
- **Gestion par SSH** sur la machine cible, réutilisant `runScriptSudo` / `runPlain` et la table `executions`, le WebSocket terminal, `rawLogPath`, `reportPath`, statut `ok|warning|error`. **Pas de second système de jobs Docker.**
|
|
- Variante `docker context` over SSH : **citée comme alternative opérateur**, pas le moteur MVP.
|
|
- **Découverte des stacks** depuis des **racines déclarées par machine** (`composeRoots`, ex. `/opt/stacks`, `/srv/docker`), scan limité en profondeur (`composeScanDepth`, défaut 4), puis validation UI.
|
|
- **Détection par labels Compose** (`com.docker.compose.project`, `com.docker.compose.service`, et si présent `com.docker.compose.project.working_dir`) = **complément** pour retrouver les stacks actifs, pas l'unique source de vérité.
|
|
- Cycle de vie d'un stack : `candidate` (juste détecté) → `enabled` (validé par l'utilisateur) → actions autorisées. `pull`, `up`, `down`, `prune` **uniquement sur un stack `enabled`/validé**.
|
|
|
|
Sources citées : `docker compose pull` https://docs.docker.com/reference/cli/docker/compose/pull/ · `up` https://docs.docker.com/reference/cli/docker/compose/up/ · `config` https://docs.docker.com/reference/cli/docker/compose/config/ · `ps` https://docs.docker.com/reference/cli/docker/compose/ps/ · `images` https://docs.docker.com/reference/cli/docker/compose/images/ · `down` https://docs.docker.com/reference/cli/docker/compose/down/ · `image inspect` https://docs.docker.com/reference/cli/docker/image/inspect/ · `image prune` https://docs.docker.com/reference/cli/docker/image/prune/
|
|
|
|
---
|
|
|
|
## 2. Inventaire des templates Docker
|
|
|
|
| Template | Action (`ActionType`) | Rôle | Effet disque | Destructif ? | Validation UI |
|
|
|---|---|---|---|---|---|
|
|
| `docker/scan-compose.sh.tpl` | `docker_scan` | Scanne les racines déclarées, trouve les fichiers compose, valide chaque candidat. **Passif.** | non | non | non |
|
|
| `docker/inspect-compose.sh.tpl` | `docker_inspect_current` | État actuel sans changement : config images, ps, images, inspect. **Passif.** | non | non | non |
|
|
| `docker/pull-check.sh.tpl` | `docker_pull_check` | `docker compose pull` (télécharge sans démarrer), compare ID/digest/labels avant-après. **Écrit sur le disque Docker** (pas un scan pur). | oui (cache images) | non (pas applicatif) | non (mais non passif) |
|
|
| `docker/apply-compose.sh.tpl` | `docker_compose_apply` | `docker compose up -d --remove-orphans` après validation. Recapture ps/images/inspect. | oui | oui (recrée conteneurs) | **oui, explicite** |
|
|
| `docker/prune-images.sh.tpl` | `docker_prune_images` | `docker image prune -f` (safe) ; mode agressif `-a -f --filter "until=168h"`. | oui | oui (agressif) | **oui pour agressif** |
|
|
| `docker/down-compose.sh.tpl` | `docker_compose_down` | `docker compose down`. **Action séparée et destructive**, hors chemin de mise à jour normal. | oui | oui | **oui, forte** |
|
|
|
|
> `--volumes` et `--rmi` sur `down` : **interdits au MVP** (ou protégés par une validation forte distincte). Le chemin de mise à jour normal n'utilise jamais `down` : `up -d` recrée les conteneurs quand l'image ou la config change, en préservant les volumes montés.
|
|
|
|
Tous les templates : `LC_ALL=C`, marqueurs `===SU:DOCKER_*===`, sortie parsable, log brut archivé.
|
|
|
|
---
|
|
|
|
## 3. Flux de mise à jour Docker (formalisé)
|
|
|
|
1. `docker_scan` — découverte des stacks candidats (racines déclarées + labels actifs).
|
|
2. `docker_inspect_current` — état actuel des services, conteneurs, images.
|
|
3. `docker_pull_check` — téléchargement des images candidates **sans démarrage de conteneurs**.
|
|
4. Comparaison déterministe — image ref, image ID, repo digest, labels OCI (`org.opencontainers.image.version`, `revision`, `source`, `created`) si présents.
|
|
5. Proposition UI/Hermes — liste des stacks/services avec update dispo, erreurs de pull, inconnues.
|
|
6. `docker_compose_apply` après validation utilisateur — `docker compose up -d --remove-orphans`.
|
|
7. Vérification après application — conteneurs recréés, état `running/exited`, health si dispo, erreurs.
|
|
8. `docker_prune_images` après succès ou action séparée — images supprimées, espace récupéré, erreurs.
|
|
|
|
**Points clés validés** :
|
|
- `docker compose pull` télécharge mais **ne démarre pas** les conteneurs → bon pré-check applicatif, pas un scan sans effet.
|
|
- `docker compose up -d` recrée les conteneurs quand l'image/config a changé, **en préservant les volumes** → `down` inutile pour une MAJ normale.
|
|
- `docker image prune -f` = images *dangling* (sûr) ; `docker image prune -a` = **toutes** les images non référencées par un conteneur (destructif).
|
|
|
|
---
|
|
|
|
## 4. Pseudo-shell des templates
|
|
|
|
### 4.1 `docker/scan-compose.sh.tpl`
|
|
|
|
```sh
|
|
#!/bin/sh
|
|
export LC_ALL=C
|
|
echo "===SU:DOCKER_SCAN==="
|
|
# {{composeRoots}} rendu en liste shell-safe par le backend (une racine par ligne).
|
|
ROOTS="{{composeRoots}}"
|
|
DEPTH="{{composeScanDepth}}"
|
|
for root in $ROOTS; do
|
|
[ -d "$root" ] || continue
|
|
find "$root" -maxdepth "$DEPTH" -type f \
|
|
\( -name 'compose.yaml' -o -name 'compose.yml' \
|
|
-o -name 'docker-compose.yaml' -o -name 'docker-compose.yml' \) \
|
|
-not -path '*/.git/*' -not -path '*/node_modules/*' \
|
|
-not -path '*/backup/*' -not -path '*/old/*' -not -path '*/archive/*' \
|
|
2>/dev/null | while IFS= read -r f; do
|
|
dir=$(dirname "$f")
|
|
# Valide le candidat ; n'applique rien.
|
|
if docker compose -f "$f" config --quiet >/dev/null 2>&1; then
|
|
echo "STACK_OK\tdir=$dir\tfile=$f"
|
|
else
|
|
echo "STACK_INVALID\tdir=$dir\tfile=$f"
|
|
fi
|
|
done
|
|
done
|
|
echo "===SU:DOCKER_LABELS==="
|
|
# Complément : stacks actifs détectés par labels.
|
|
docker ps --format '{{ "{{.ID}}" }}' 2>/dev/null | while read -r id; do
|
|
proj=$(docker inspect --format '{{ "{{index .Config.Labels \"com.docker.compose.project\"}}" }}' "$id" 2>/dev/null)
|
|
wd=$(docker inspect --format '{{ "{{index .Config.Labels \"com.docker.compose.project.working_dir\"}}" }}' "$id" 2>/dev/null)
|
|
[ -n "$proj" ] && echo "ACTIVE\tproject=$proj\tworking_dir=$wd"
|
|
done
|
|
echo "===SU:EXIT=0==="
|
|
```
|
|
|
|
> Note de rendu : les `{{ }}` Docker Go-template sont échappés ici pour ne pas être interprétés par Mustache (le moteur de rendu réel utilisera des délimiteurs Mustache personnalisés ou un échappement, à fixer en implémentation). Seules `composeRoots`/`composeScanDepth` sont des variables Mustache.
|
|
|
|
### 4.2 `docker/inspect-compose.sh.tpl`
|
|
|
|
```sh
|
|
#!/bin/sh
|
|
export LC_ALL=C
|
|
cd "{{stackDir}}" || { echo "===SU:DOCKER_ERR===\ncompose_not_found"; echo "===SU:EXIT=2==="; exit 2; }
|
|
echo "===SU:DOCKER_CONFIG_IMAGES==="
|
|
docker compose config --images 2>&1
|
|
echo "===SU:DOCKER_PS==="
|
|
docker compose ps --format json 2>&1
|
|
echo "===SU:DOCKER_IMAGES==="
|
|
docker compose images --format json 2>&1
|
|
echo "===SU:DOCKER_INSPECT==="
|
|
# Pour chaque image utilisée : Id, RepoDigests, labels OCI.
|
|
docker compose config --images 2>/dev/null | while IFS= read -r img; do
|
|
docker image inspect "$img" \
|
|
--format '{{ "IMG\t{{.Id}}\t{{join .RepoDigests \",\"}}\t{{index .Config.Labels \"org.opencontainers.image.version\"}}\t{{index .Config.Labels \"org.opencontainers.image.source\"}}" }}' 2>/dev/null \
|
|
|| echo "IMG_MISSING\t$img"
|
|
done
|
|
echo "===SU:EXIT=0==="
|
|
```
|
|
|
|
### 4.3 `docker/pull-check.sh.tpl`
|
|
|
|
```sh
|
|
#!/bin/sh
|
|
export LC_ALL=C
|
|
cd "{{stackDir}}" || { echo "compose_not_found"; echo "===SU:EXIT=2==="; exit 2; }
|
|
echo "===SU:DOCKER_INSPECT_BEFORE==="
|
|
docker compose config --images 2>/dev/null | while IFS= read -r img; do
|
|
id=$(docker image inspect "$img" --format '{{ "{{.Id}}" }}' 2>/dev/null || echo "")
|
|
dg=$(docker image inspect "$img" --format '{{ "{{join .RepoDigests \",\"}}" }}' 2>/dev/null || echo "")
|
|
echo "BEFORE\t$img\t$id\t$dg"
|
|
done
|
|
echo "===SU:DOCKER_PULL==="
|
|
# Télécharge les images candidates SANS démarrer de conteneurs.
|
|
docker compose pull --policy always --ignore-buildable 2>&1
|
|
CODE=$?
|
|
echo "===SU:DOCKER_INSPECT_AFTER==="
|
|
docker compose config --images 2>/dev/null | while IFS= read -r img; do
|
|
id=$(docker image inspect "$img" --format '{{ "{{.Id}}" }}' 2>/dev/null || echo "")
|
|
dg=$(docker image inspect "$img" --format '{{ "{{join .RepoDigests \",\"}}" }}' 2>/dev/null || echo "")
|
|
ver=$(docker image inspect "$img" --format '{{ "{{index .Config.Labels \"org.opencontainers.image.version\"}}" }}' 2>/dev/null || echo "")
|
|
echo "AFTER\t$img\t$id\t$dg\t$ver"
|
|
done
|
|
echo "===SU:EXIT=${CODE}==="
|
|
```
|
|
|
|
Backend : compare `BEFORE`/`AFTER` par `image ref`. Si `id`/`digest` change → `updates_available`. Erreurs `pull_failed`/`registry_auth_failed` nettoyées (jamais d'URL/token sensible vers UI/MCP).
|
|
|
|
### 4.4 `docker/apply-compose.sh.tpl`
|
|
|
|
```sh
|
|
#!/bin/sh
|
|
export LC_ALL=C
|
|
cd "{{stackDir}}" || { echo "compose_not_found"; echo "===SU:EXIT=2==="; exit 2; }
|
|
echo "===SU:DOCKER_APPLY==="
|
|
docker compose up -d --remove-orphans 2>&1
|
|
CODE=$?
|
|
echo "===SU:DOCKER_PS_AFTER==="
|
|
docker compose ps --format json 2>&1
|
|
echo "===SU:DOCKER_INSPECT_AFTER==="
|
|
docker compose config --images 2>/dev/null | while IFS= read -r img; do
|
|
docker image inspect "$img" --format '{{ "IMG\t{{.Id}}\t{{join .RepoDigests \",\"}}" }}' 2>/dev/null || echo "IMG_MISSING\t$img"
|
|
done
|
|
echo "===SU:EXIT=${CODE}==="
|
|
```
|
|
|
|
### 4.5 `docker/prune-images.sh.tpl`
|
|
|
|
```sh
|
|
#!/bin/sh
|
|
export LC_ALL=C
|
|
echo "===SU:DOCKER_PRUNE==="
|
|
{{#aggressive}}
|
|
# Mode agressif : nécessite validation UI explicite distincte.
|
|
docker image prune -a -f --filter "until=168h" 2>&1
|
|
{{/aggressive}}
|
|
{{^aggressive}}
|
|
# Mode sûr par défaut : dangling images uniquement.
|
|
docker image prune -f 2>&1
|
|
{{/aggressive}}
|
|
CODE=$?
|
|
echo "===SU:EXIT=${CODE}==="
|
|
```
|
|
|
|
Le backend parse `Total reclaimed space` et `deleted` pour `bytesReclaimed` et la liste d'images supprimées.
|
|
|
|
### 4.6 `docker/down-compose.sh.tpl`
|
|
|
|
```sh
|
|
#!/bin/sh
|
|
export LC_ALL=C
|
|
cd "{{stackDir}}" || { echo "compose_not_found"; echo "===SU:EXIT=2==="; exit 2; }
|
|
echo "===SU:DOCKER_DOWN==="
|
|
# --volumes et --rmi INTERDITS au MVP. down simple uniquement.
|
|
docker compose down 2>&1
|
|
CODE=$?
|
|
echo "===SU:EXIT=${CODE}==="
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Réduction Hermes (Docker)
|
|
|
|
Seules ces lignes (+ le JSON canonique) sont transmises : `Pulling`, `Digest`, `Status`, `Downloaded newer image`, `Recreating`, `Started`, `Error`, `deleted`, `Total reclaimed space`. Le log brut complet reste archivé (`raw_artifacts` / `rawLogPath`).
|
|
|
|
---
|
|
|
|
## 6. Insertion dans la webapp existante
|
|
|
|
- **Config machine** : nouveaux champs `dockerEnabled`, `composeRoots[]`, `composeScanDepth`, `composeStacks[]` — **optionnels** ou dans un endpoint dédié ; `MachineView` n'est pas cassé (les champs sont ajoutés en option). Stockés dans `docker_settings` / `docker_compose_roots` / `docker_compose_stacks` (`tache1.9.md`).
|
|
- **Refresh/snapshot** : le refresh machine peut produire un snapshot combiné `apt` + `docker`, **ou** un refresh Docker séparé pour éviter de lancer `docker_pull_check` automatiquement (recommandé : pull-check séparé car non-passif).
|
|
- **Actions** : extension progressive de `ActionType` (`docker_scan`, `docker_pull_check`, `docker_compose_apply`, `docker_prune_images`, `docker_compose_down`) avec filtrage d'autorisation conservé sur `POST /api/machines/:id/actions`.
|
|
- **Executions/rapports** : réutilisation de la table `executions`, du WebSocket terminal, de `rawLogPath`/`reportPath`/statut. Pas de second système.
|
|
- **UI machine** : compteur Docker séparé du compteur APT (ex. stacks avec updates) ; vue détail par stack/service ; boutons d'action validés (`Pull/check`, `Appliquer`, `Prune`, `Down`).
|
|
- **Validation utilisateur** : `docker_compose_apply`, `docker_prune_images` agressif et `docker_compose_down` passent par une confirmation UI explicite (via `action_requests`). Hermes propose, ne déclenche jamais.
|
|
- **Secrets** : credentials registry (`~/.docker/config.json`, helpers, tokens) **jamais lus ni renvoyés** ; erreurs nettoyées si elles exposent une URL sensible (voir `70-securite.md`).
|