From fa775497eea448c016848ac62e23992f5e7aa027 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Mon, 28 Sep 2026 14:47:40 -0400 Subject: [PATCH] =?UTF-8?q?fix(deploy):=20404=20de=20r=C3=A9tention=20sur?= =?UTF-8?q?=20les=20images=20OCI=20index=20(buildx)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le push réussissait puis le script sortait en 22 : curl: (22) The requested URL returned error: 404 Write-Error: ... code d'erreur: 22 Deux causes cumulées, visibles seulement une fois MAX_VERSIONS dépassé. 1. Accept trop restrictif `docker build` via buildx produit un OCI image index (application/vnd.oci.image.index.v1+json), pas un manifest v2 plat. delete_by_tag demandait le digest avec `Accept: application/vnd.docker.distribution.manifest.v2+json` : le registre répond 404 — et non 406 — quand l'Accept ne couvre pas le type stocké. Le HEAD échouait donc sur TOUS les tags, y compris celui venait d'être poussé. On liste maintenant les quatre media types possibles. 2. La rétention pouvait tuer un déploiement réussi Sous `set -euo pipefail`, le curl de delete_by_tag n'était protégé par aucun `|| true` : son code de sortie 22 se propageait et tuait le script, transformant un push réussi en échec. Le ménage des anciennes versions est du best-effort et ne doit jamais faire échouer le push. Symptôme d'origine : la rétention ne s'est jamais déclenchée tant que le nombre de tags semver est resté <= MAX_VERSIONS (5). Au 7e tag, elle a commencé à tourner et a fait échouer le script. Vérifié : deploy-img.ps1 publie 1.0.20, `latest` aligné sur le digest de l'image locale (sha256:25555d9ee4bf…), rétention purgée sans erreur. --- docker/deploy-img.sh | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/docker/deploy-img.sh b/docker/deploy-img.sh index 2d7f942..656bf0f 100644 --- a/docker/deploy-img.sh +++ b/docker/deploy-img.sh @@ -27,7 +27,14 @@ CURL="curl -fsSL" JQ_BIN="${JQ_BIN:-jq}" # possibilité de surcharger via env # Headers pour obtenir le digest (manifest v2) -ACCEPT_MANIFEST='application/vnd.docker.distribution.manifest.v2+json' +# L'Accept doit lister TOUS les media types possibles : `docker build` via +# buildx produit un OCI image index (application/vnd.oci.image.index.v1+json), +# pas un manifest v2 plat. Un registre répond 404 — pas 406 — quand l'Accept +# ne couvre pas le type stocké, ce qui faisait échouer la rétention. +ACCEPT_MANIFEST='application/vnd.oci.image.index.v1+json' +ACCEPT_MANIFEST="${ACCEPT_MANIFEST},application/vnd.docker.distribution.manifest.list.v2+json" +ACCEPT_MANIFEST="${ACCEPT_MANIFEST},application/vnd.oci.image.manifest.v1+json" +ACCEPT_MANIFEST="${ACCEPT_MANIFEST},application/vnd.docker.distribution.manifest.v2+json" # Auth du registre. Ordre de priorité : # 1. variables déjà exports (DOCKER_USERNAME / DOCKER_PASSWORD) @@ -235,19 +242,25 @@ delete_by_tag() { # Supprime un manifest par son tag en récupérant le digest via HEAD local tag="$1" local digest - # On demande le digest via HEAD + Accept manifest v2 - digest="$(curl -fsSI ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} -H "Accept: ${ACCEPT_MANIFEST}" "http://${REGISTRY}/v2/${IMAGE_NAME}/manifests/${tag}" \ + # La rétention est du ménage best-effort : elle ne doit JAMAIS faire échouer + # un push qui a réussi. D'où les `|| true` (et le `set +e` local) — sans eux, + # le moindre curl en erreur (404 sur un tag déjà purgé, registre momentanément + # indisponible) faisait sortir le script avec le code curl sous `set -e`. + # On demande le digest via HEAD + Accept (index OCI + manifest v2). + set +e + digest="$(curl -fsSI ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} -H "Accept: ${ACCEPT_MANIFEST}" "http://${REGISTRY}/v2/${IMAGE_NAME}/manifests/${tag}" 2>/dev/null \ | tr -d '\r' \ | awk -F': ' 'tolower($1)=="docker-content-digest"{print $2}' \ | tail -n1)" + set -e if [[ -z "${digest}" ]]; then - warn "Digest introuvable pour ${tag} (manifest v2 absent ?). Skip." + warn "Digest introuvable pour ${tag} (manifest absent ?). Skip." return 0 fi info "Suppression manifest ${tag} (digest: ${digest})…" # La delete API ne renvoie rien en cas de succès (204) - if ! curl -fsS ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} -X DELETE "http://${REGISTRY}/v2/${IMAGE_NAME}/manifests/${digest}" >/dev/null; then + if ! curl -fsS ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} -X DELETE "http://${REGISTRY}/v2/${IMAGE_NAME}/manifests/${digest}" >/dev/null 2>&1; then warn "Échec suppression manifest pour ${tag} (digest: ${digest})." fi }