Files
StefanandClaude Opus 5 e0212cfa2c
Docker-Image bauen / build (push) Successful in 1m7s
Tests von 55 auf 12 Sekunden, Abhängigkeiten nur noch einmal installieren
Der Testlauf war zu langsam. Gemessen statt geraten: 0,68 s Vorbereitung
pro Test, bei 81 Tests praktisch die ganze Laufzeit.

Ursache war bcrypt. Pro Test wird ein Hash gebildet und geprüft, und das
ist absichtlich langsam - genau das soll es im Betrieb sein. Der Aufwand
ist jetzt über BCRYPT_ROUNDS einstellbar (Standard bleibt 12) und in den
Tests auf 4 gesetzt: dort geht es um die Ablauflogik, nicht um die Stärke
des Hashes.

Der Dekompressionsbomben-Test erzeugte ein Bild mit 400 Megapixeln, allein
dafür 4,8 s. Jetzt wird stattdessen die Grenze heruntergesetzt und ein
kleines Bild verwendet - dieselbe Codestelle, ohne die Wartezeit.

Ausserdem installierten Test- und Anwendungs-Image dieselben
Abhängigkeiten zweimal. Dockerfile.test setzt nun auf dem gebauten
Anwendungs-Image auf, der Testschritt kommt entsprechend danach.

Dabei zwei Fallen, die beide auffielen, weil die Tests plötzlich wieder
langsam waren:
- Das Basis-Image bringt seinen eigenen Stand von app/ mit. Ohne erneutes
  Kopieren prüfen die Tests den Code des Basis-Images - ist es veraltet,
  läuft alles gegen alten Code und meldet Erfolg.
- Der ENTRYPOINT des Anwendungs-Images startet eine Datenbank-Migration.
  Für Tests weder nötig noch erwünscht, darum geleert.

Ganze Kette lokal durchgespielt: bauen, testen, Image prüfen - 22 s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-30 14:45:24 +02:00

157 lines
6.3 KiB
YAML

name: Docker-Image bauen
# Baut das Image, sobald ein Versions-Tag (v1.2.3) gepusht wird, und legt es
# in der eingebauten Container-Registry von Gitea ab. Bewusst NICHT bei
# jedem Push auf main: so bleibt das Zusammenführen von Branches folgenlos
# und ein Release ist ein bewusster Schritt.
on:
push:
tags:
- 'v*'
workflow_dispatch:
env:
REGISTRY: gitea.boing86.myds.me
IMAGE_NAME: docker/kleiderboerse
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Quellcode auschecken
uses: actions/checkout@v4
- name: Version bestimmen
id: meta
env:
# Bewusst über die Umgebung statt direkt als ${{ ... }} im Skript:
# Ausdrücke werden vor der Shell eingesetzt, und Tag-Namen dürfen
# Zeichen wie " oder $ enthalten. Als Variable kann daraus kein
# zusätzlicher Befehl werden.
REF_TYPE: ${{ github.ref_type }}
REF_NAME: ${{ github.ref_name }}
run: |
set -eu
if [ "$REF_TYPE" = "tag" ]; then
VERSION="$REF_NAME"
VERSION="${VERSION#v}" # v1.2.3 -> 1.2.3
IS_RELEASE=true
else
VERSION="manual-$(date +%Y%m%d-%H%M%S)"
IS_RELEASE=false
fi
# Die Registry behandelt Namen case-insensitiv -> konsequent klein.
VERSION="$(echo "$VERSION" | tr '[:upper:]' '[:lower:]')"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "is_release=$IS_RELEASE" >> "$GITHUB_OUTPUT"
# Nur bei echten Tags eine Version in die Fusszeile schreiben; bei
# manuellen Läufen bleibt sie leer und es erscheint der Commit.
if [ "$IS_RELEASE" = "true" ]; then
echo "tag_version=$VERSION" >> "$GITHUB_OUTPUT"
else
echo "tag_version=" >> "$GITHUB_OUTPUT"
fi
echo "Version: $VERSION (Release: $IS_RELEASE)"
- name: Image bauen
run: |
set -eu
docker build \
--tag "$REGISTRY/$IMAGE_NAME:${{ steps.meta.outputs.version }}" \
--build-arg "APP_VERSION=${{ steps.meta.outputs.tag_version }}" \
--build-arg "APP_REVISION=${{ github.sha }}" \
--label "org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}" \
--label "org.opencontainers.image.revision=${{ github.sha }}" \
--label "org.opencontainers.image.version=${{ steps.meta.outputs.version }}" \
.
# Nach dem Bauen: das Test-Image setzt auf dem eben gebauten auf und
# spart sich damit ein zweites Mal Abhängigkeiten installieren. Den
# Quellcode kopiert es trotzdem frisch hinein, sonst prüfte es den
# Stand des Basis-Images.
- name: Tests
run: |
set -eu
docker build -f Dockerfile.test \
--build-arg "BASIS=$REGISTRY/$IMAGE_NAME:${{ steps.meta.outputs.version }}" \
-t kleiderboerse-tests .
docker run --rm kleiderboerse-tests
# Prüft das fertige Image, bevor irgendetwas veröffentlicht wird.
# Geprüft wird im Container selbst (kein Port-Mapping nötig, damit der
# Test unabhängig vom Netzwerk-Setup des Runners funktioniert).
- name: Image testen
run: |
set -eu
BILD="$REGISTRY/$IMAGE_NAME:${{ steps.meta.outputs.version }}"
CID=$(docker run -d -e ADMIN_PASSWORD=pruef -e SECRET_KEY=pruef "$BILD")
trap 'docker rm -f "$CID" >/dev/null 2>&1 || true' EXIT
for i in $(seq 1 30); do
# Einzeilig: ein mehrzeiliger String bräche den YAML-Block auf,
# weil dessen Fortsetzung links vom Einzug stünde.
if docker exec "$CID" python -c "import urllib.request as u; u.urlopen('http://localhost:8000/health')" 2>/dev/null; then
break
fi
if [ "$i" = "30" ]; then
echo "Anwendung im Container nicht erreichbar geworden:"
docker logs "$CID"
exit 1
fi
sleep 1
done
# Stammdaten: ohne Kategorien und Grössen lässt sich nichts
# erfassen. Untergrenzen bewusst unter dem Ist-Stand, damit der
# Test nicht bei jeder Ergänzung anschlägt.
# -i ist zwingend: ohne angehängtes stdin bekommt "python -" ein
# leeres Programm, tut nichts und meldet Erfolg. Der Test könnte
# dann nie fehlschlagen.
docker exec -i "$CID" python - < scripts/image-pruefen.py
# Das CSS muss im Image liegen - es entsteht in einer eigenen
# Build-Stufe und würde bei einem Fehler dort still fehlen.
docker exec "$CID" test -s /app/app/static/style.css
echo "style.css vorhanden."
- name: An Registry anmelden
env:
# Optional: eigenes Token/Konto als Repo-Secret hinterlegen, falls
# der automatische GITEA_TOKEN nicht in die Registry schreiben darf.
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
AUTO_TOKEN: ${{ secrets.GITEA_TOKEN }}
AKTEUR: ${{ github.actor }}
run: |
set -eu
TOKEN="${REGISTRY_TOKEN:-$AUTO_TOKEN}"
BENUTZER="${REGISTRY_USER:-$AKTEUR}"
if [ -z "$TOKEN" ]; then
echo "Kein Token verfügbar: REGISTRY_TOKEN als Repo-Secret hinterlegen."
exit 1
fi
echo "Anmeldung an $REGISTRY als $BENUTZER"
# --password-stdin: so steht das Token weder in der Prozessliste
# noch im Protokoll.
echo "$TOKEN" | docker login "$REGISTRY" -u "$BENUTZER" --password-stdin
- name: Image veröffentlichen
run: |
set -eu
docker push "$REGISTRY/$IMAGE_NAME:${{ steps.meta.outputs.version }}"
# "latest" nur für echte Versions-Tags, nicht für manuelle Läufe.
if [ "${{ steps.meta.outputs.is_release }}" = "true" ]; then
docker tag "$REGISTRY/$IMAGE_NAME:${{ steps.meta.outputs.version }}" \
"$REGISTRY/$IMAGE_NAME:latest"
docker push "$REGISTRY/$IMAGE_NAME:latest"
fi
- name: Abmelden
if: always()
run: docker logout "$REGISTRY" || true