OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

ci-local.sh

5 KB
#!/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