Files
maxime.killinger 7de55c5655
🚀 Docker Build and Push / build-and-push (15) (push) Successful in 5m29s
🚀 Docker Build and Push / build-and-push (16) (push) Successful in 6m10s
🚀 Docker Build and Push / build-and-push (17) (push) Successful in 5m20s
🚀 Docker Build and Push / build-and-push (18) (push) Successful in 5m21s
PG18 de retour, mises à jour nocturnes, tests de reprise et documentation
Image
- Étape `base` commune : tous les paquets sont mis à niveau (PostgreSQL à la
  dernière mineure, pgvector, Debian). L'image de base de VectorChord restait
  figée à la version de sa publication.
- En-têtes du serveur épinglés sur la version du serveur installé. PG18 avait
  été retiré parce que TimescaleDB, compilé avec des en-têtes 18.6, était chargé
  par un serveur 18.3 (`undefined symbol: palloc0_mul`).
- Sonde de santé avec l'utilisateur et la base du conteneur : en `postgres`,
  elle écrivait une erreur FATAL au journal dès que POSTGRES_USER était défini.
- Label `base.name` corrigé : il reprenait la version du paquet au lieu de la
  version majeure.

CI
- PG18 ajouté à la matrice.
- Exécution nocturne : apt est interrogé dans l'image publiée ; elle n'est
  reconstruite et republiée que s'il lui reste des mises à jour, sinon elle est
  seulement testée. Lancement manuel possible.
- `docker-build.yml` supprimé : l'ancien workflow à actions externes tournait
  encore à chaque push, en double de `docker.yml`.
- `paths-ignore` : un push de documentation seule ne lance pas la CI.
- Labels `version`, `url` et `fr.killinger.apt-refresh`.

Tests
- `tests.sh`, la même commande en local et en CI : utilisateur et base autres
  que `postgres`, version majeure du serveur, présence des cinq extensions,
  redémarrage, et reprise d'une base créée par l'image publiée.

Documentation
- README en vitrine, CLAUDE.md, docs/architecture.md,
  docs/exploitation/publication.md, six décisions, docs/a-faire.md.
2026-10-02 17:55:42 +02:00

92 lines
3.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# Tests de l'image, en local comme en CI.
#
# ./tests.sh IMAGE démarrage, extensions, tests SQL, redémarrage
# ./tests.sh IMAGE PRECEDENTE en plus : reprise d'une base créée par l'image PRECEDENTE
#
# Tout tourne dans des conteneurs et un volume jetables, sans port publié, supprimés à la fin.
set -euo pipefail
cd "$(dirname "$0")"
IMAGE="${1:?usage : tests.sh IMAGE [IMAGE_PRECEDENTE]}"
PRECEDENTE="${2:-}"
ID="ptvtest$$"
# Utilisateur et base autres que `postgres`, comme chez les consommateurs de l'image : les
# extensions doivent être créées dans POSTGRES_DB, par POSTGRES_USER.
OPTIONS=(-e POSTGRES_USER=test -e POSTGRES_PASSWORD=test -e POSTGRES_DB=test
--memory=768m --memory-swap=768m) # le runner de CI est limité en mémoire
nettoyer() {
docker rm -f -v "$ID" "$ID-reprise" >/dev/null 2>&1 || true
docker volume rm "$ID-donnees" >/dev/null 2>&1 || true
}
trap nettoyer EXIT
sql() { docker exec -i "$1" psql -X -q -v ON_ERROR_STOP=1 -U test -d test "${@:2}"; }
# Trois sondes, de la plus exigeante à la moins exigeante.
# Premier démarrage : le serveur temporaire de l'initialisation répond déjà aux requêtes, avant
# que les extensions soient créées ; il faut aussi la fin de l'initialisation dans le journal.
# Le journal est lu en entier avant la recherche : `grep -q` branché sur `docker logs` le coupe
# dès la première correspondance, ce que `pipefail` compte comme un échec.
initialise() {
local journal
journal=$(docker logs "$1" 2>&1)
grep -q "PostgreSQL init process complete" <<<"$journal" && repond "$1"
}
repond() { sql "$1" -Atc 'select 1' >/dev/null 2>&1; }
# Sans requête : après un changement de version de TimescaleDB, toute requête échoue tant que
# l'extension n'est pas mise à niveau (tests/reprise-2-controle.sql).
accepte() { docker exec "$1" pg_isready -q -U test -d test; }
attendre() { # attendre SONDE CONTENEUR
for _ in $(seq 1 90); do
"$1" "$2" && return 0
sleep 1
done
docker logs "$2" 2>&1 | tail -30 >&2
echo "ÉCHEC : $2 ne démarre pas" >&2
return 1
}
echo "== Démarrage de $IMAGE"
docker run -d --name "$ID" "${OPTIONS[@]}" "$IMAGE" >/dev/null
attendre initialise "$ID"
# L'étiquette pgN doit contenir un serveur N : PG_MAJOR vient de l'image de base.
attendue=$(docker exec "$ID" printenv PG_MAJOR)
servie=$(sql "$ID" -Atc "select current_setting('server_version_num')::int / 10000")
[[ "$servie" == "$attendue" ]] || { echo "ÉCHEC : serveur $servie, image pg$attendue" >&2; exit 1; }
for fichier in tests/test-*.sql; do
echo "== $fichier"
sql "$ID" < "$fichier"
done
echo "== Redémarrage sur des données existantes"
docker restart "$ID" >/dev/null
attendre repond "$ID"
sql "$ID" < tests/test-1-config.sql
if [[ -n "$PRECEDENTE" ]]; then
echo "== Reprise d'une base créée par $PRECEDENTE"
# Le volume se monte là où l'image déclare le sien : /var/lib/postgresql/data jusqu'à
# PostgreSQL 17, /var/lib/postgresql à partir de 18.
montage=$(docker image inspect -f '{{range $chemin, $_ := .Config.Volumes}}{{$chemin}}{{end}}' "$IMAGE")
docker volume create "$ID-donnees" >/dev/null
docker run -d --name "$ID-reprise" "${OPTIONS[@]}" -v "$ID-donnees:$montage" "$PRECEDENTE" >/dev/null
attendre initialise "$ID-reprise"
sql "$ID-reprise" < tests/reprise-1-semis.sql
docker stop "$ID-reprise" >/dev/null
docker rm "$ID-reprise" >/dev/null
docker run -d --name "$ID-reprise" "${OPTIONS[@]}" -v "$ID-donnees:$montage" "$IMAGE" >/dev/null
attendre accepte "$ID-reprise"
sql "$ID-reprise" < tests/reprise-2-controle.sql
fi
echo "== Versions"
sql "$ID" -At -c "select version()" \
-c "select string_agg(extname || ' ' || extversion, ', ' order by extname) from pg_extension"
echo "Tests réussis : $IMAGE"