Quelltext des aktuellen Standes
#!/usr/bin/env bash
#
# Fährt dieselbe Prüfmatrix wie .github/workflows/tests.yml auf dem eigenen
# Rechner – PHP 8.2 gegen MariaDB 10.11 und PHP 8.3 gegen MySQL 8.0, jeweils
# inklusive Migrations-Smoke-Test gegen eine echte, leere Datenbank.
#
# Gedacht als Rückfallebene, wenn die Läufe bei GitHub nicht zur Verfügung
# stehen. Voraussetzung ist ein laufender Docker-Daemon; sonst wird nichts
# benötigt – weder PHP noch eine Datenbank auf dem Host.
#
# bin/ci-local.sh # ganze Matrix
# bin/ci-local.sh 8.2 # nur den Zweig mit PHP 8.2
# bin/ci-local.sh --list # Zweige auflisten
#
# Das Arbeitsverzeichnis wird in den Container kopiert, nicht eingehängt: Ein
# `composer install` im Container darf das `vendor/` des Hosts nicht ersetzen,
# das zu einer anderen PHP-Version gehören kann.
#
# Rückgabewert 0 nur, wenn jeder Zweig durchgelaufen ist.
set -euo pipefail
WURZEL="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
KENNUNG="opennit-ci-$$"
# Zweige der Matrix: PHP-Version, Datenbank-Image, Bezeichnung.
ZWEIGE=(
"8.3|mysql:8.0|MySQL 8.0 · PHP 8.3"
"8.2|mariadb:10.11|MariaDB 10.11 · PHP 8.2"
)
DB_NAME='opennit_test'
DB_USER='root'
DB_PASS='root'
rot() { printf '\033[31m%s\033[0m\n' "$*"; }
gruen(){ printf '\033[32m%s\033[0m\n' "$*"; }
info() { printf '\033[1m%s\033[0m\n' "$*"; }
# Raeumt Container und Netze dieses Laufs ab. Darf den Rueckgabewert des
# Skripts nicht veraendern – deshalb ist jeder Schritt fehlertolerant.
aufraeumen() {
local stand=$?
if docker info >/dev/null 2>&1; then
for c in $(docker ps -aq --filter "name=^${KENNUNG}" 2>/dev/null || true); do
docker rm -f "$c" >/dev/null 2>&1 || true
done
for n in $(docker network ls -q --filter "name=^${KENNUNG}" 2>/dev/null || true); do
docker network rm "$n" >/dev/null 2>&1 || true
done
fi
return $stand
}
trap aufraeumen EXIT INT TERM
if [[ "${1:-}" == "--list" ]]; then
for z in "${ZWEIGE[@]}"; do echo "${z##*|}"; done
exit 0
fi
if ! docker info >/dev/null 2>&1; then
rot "Kein erreichbarer Docker-Daemon. Docker starten und erneut versuchen."
exit 2
fi
# Optional auf einen PHP-Zweig einschränken.
if [[ -n "${1:-}" ]]; then
GEFILTERT=()
for z in "${ZWEIGE[@]}"; do
[[ "${z%%|*}" == "$1" ]] && GEFILTERT+=("$z")
done
if [[ ${#GEFILTERT[@]} -eq 0 ]]; then
rot "Kein Zweig mit PHP $1. Verfügbar: $(for z in "${ZWEIGE[@]}"; do printf '%s ' "${z%%|*}"; done)"
exit 2
fi
ZWEIGE=("${GEFILTERT[@]}")
fi
# Ein Abbild je PHP-Version, damit die Erweiterungen nicht bei jedem Lauf neu
# gebaut werden. Die Liste ist deckungsgleich mit composer.json und dem Workflow.
abbild_bauen() {
local php="$1" tag="opennit-ci-php:${php}"
if docker image inspect "$tag" >/dev/null 2>&1; then
return 0
fi
info " Abbild $tag wird einmalig gebaut …"
docker build -q -t "$tag" - >/dev/null <<DOCKERFILE
FROM php:${php}-cli
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN chmod +x /usr/local/bin/install-php-extensions \\
&& install-php-extensions curl dom fileinfo gd json mbstring openssl pdo pdo_mysql zip \\
&& apt-get update && apt-get install -y --no-install-recommends git unzip default-mysql-client \\
&& rm -rf /var/lib/apt/lists/*
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
DOCKERFILE
}
zweig_fahren() {
local php="$1" dbimage="$2" label="$3"
local netz="${KENNUNG}-net-${php//./}"
local dbc="${KENNUNG}-db-${php//./}"
info "── ${label} ─────────────────────────────────"
abbild_bauen "$php"
docker network create "$netz" >/dev/null
docker run -d --name "$dbc" --network "$netz" \
-e MYSQL_ROOT_PASSWORD="$DB_PASS" -e MYSQL_DATABASE="$DB_NAME" \
"$dbimage" >/dev/null
# Auf die Datenbank warten. Kommt sie nicht hoch, bricht der Zweig ab –
# ein Lauf ohne Datenbank pruefte den Smoke-Test nicht und waere trotzdem gruen.
local bereit=0
for _ in $(seq 1 60); do
if docker exec "$dbc" mysqladmin ping -h 127.0.0.1 -u"$DB_USER" -p"$DB_PASS" --silent >/dev/null 2>&1; then
bereit=1; break
fi
sleep 2
done
if [[ $bereit -ne 1 ]]; then
rot " Datenbank ($dbimage) kam nicht hoch."
docker logs --tail 30 "$dbc" || true
return 1
fi
docker run --rm --network "$netz" \
-v "${WURZEL}:/src:ro" \
-e DB_HOST="$dbc" -e DB_PORT=3306 -e DB_DATABASE="$DB_NAME" \
-e DB_USERNAME="$DB_USER" -e DB_PASSWORD="$DB_PASS" \
-e OPENNIT_MIGRATION_SMOKE=1 \
-e COMPOSER_ALLOW_SUPERUSER=1 \
"opennit-ci-php:${php}" \
bash -euc '
# Kopie statt Einhaengen: composer install darf das vendor/ des Hosts nicht anfassen.
mkdir -p /app && cp -a /src/. /app/ && cd /app
rm -rf vendor
composer install --no-interaction --no-progress --prefer-dist
composer audit --no-interaction
php -v | head -1
php tests/run.php
'
}
FEHLER=0
for z in "${ZWEIGE[@]}"; do
IFS='|' read -r php dbimage label <<<"$z"
if zweig_fahren "$php" "$dbimage" "$label"; then
gruen " ✓ ${label}"
else
rot " ✗ ${label}"
FEHLER=1
fi
aufraeumen
done
echo
if [[ $FEHLER -eq 0 ]]; then
gruen "Alle Zweige der Matrix bestanden."
else
rot "Mindestens ein Zweig ist fehlgeschlagen."
fi
exit $FEHLER