Stands up the upstream Maykin Objecten API in the stack — own PostGIS DB + redis, an -init that runs setup_configuration from the external config volume (migrate + provision a static token + register the Objecttypen API as a trusted service), and a health-checked web on host :8021. Objecten's setup_configuration registers Objecttypen (api_type orc, api_key auth with the S-18a dev token) so an object can reference its objecttype. Same verbatim-image + seed-config pattern as S-18a. Wired into WAIT_SVCS, CFG_VOLS, the SEED invocations, seed-config.sh, and the CI log-dump. Pinned objects-api 3.4.0 (nearest release to objecttypes-api 3.4.2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
287 lines
13 KiB
YAML
287 lines
13 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
# Supersede stale runs: a new push to the same branch/PR cancels the previous run, so the runner's
|
|
# concurrency slots aren't spent on commits nobody is waiting for (refs #127).
|
|
concurrency:
|
|
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
# Self-hosted runner — see docs/runbooks/ci.md for the runner setup.
|
|
# `uses:` are absolute, tag-pinned URLs (CLAUDE.md §8.7 / §15).
|
|
|
|
# Each job calls a `make` target — the same one developers run locally
|
|
# (`make ci`). The Makefile is the single source of truth; see docs/runbooks/ci.md.
|
|
|
|
jobs:
|
|
lint:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: https://github.com/actions/checkout@v4
|
|
- uses: https://github.com/actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: '10.0.x'
|
|
# Cache the NuGet package store so each .NET job restores from disk, not the network. There are
|
|
# no lock files (so setup-dotnet's built-in cache doesn't apply); key on the project files. @v3
|
|
# avoids the GHES guard that breaks @v4 on Gitea (gitea-actions-gotchas.md); cache is best-effort
|
|
# — a miss just restores from the network. See issue #73.
|
|
- uses: https://github.com/actions/cache@v3
|
|
with:
|
|
path: ~/.nuget/packages
|
|
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj') }}
|
|
restore-keys: |
|
|
nuget-${{ runner.os }}-
|
|
- run: make lint
|
|
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: https://github.com/actions/checkout@v4
|
|
- uses: https://github.com/actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: '10.0.x'
|
|
- uses: https://github.com/actions/cache@v3
|
|
with:
|
|
path: ~/.nuget/packages
|
|
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj') }}
|
|
restore-keys: |
|
|
nuget-${{ runner.os }}-
|
|
- run: make build
|
|
|
|
unit:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: https://github.com/actions/checkout@v4
|
|
- uses: https://github.com/actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: '10.0.x'
|
|
- uses: https://github.com/actions/cache@v3
|
|
with:
|
|
path: ~/.nuget/packages
|
|
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj') }}
|
|
restore-keys: |
|
|
nuget-${{ runner.os }}-
|
|
- run: make unit
|
|
# Job summary (#136): a per-service pass/fail table from the TRX `make unit` wrote.
|
|
- name: Unit test summary
|
|
if: always()
|
|
run: |
|
|
[ -n "${GITHUB_STEP_SUMMARY:-}" ] || exit 0
|
|
python3 infra/trx-summary.py TestResults >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
# Frontend (Nx/Angular) lane: install with pnpm, then Nx lint + test + build.
|
|
frontend:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: https://github.com/actions/checkout@v4
|
|
- uses: https://github.com/pnpm/action-setup@v4
|
|
with:
|
|
version: 11
|
|
- uses: https://github.com/actions/setup-node@v4
|
|
with:
|
|
node-version: '24'
|
|
cache: 'pnpm'
|
|
- run: make frontend
|
|
# Job summary (#136): a per-frontend (app) pass/fail table from the vitest JSON each app wrote.
|
|
- name: Frontend test summary
|
|
if: always()
|
|
run: |
|
|
[ -n "${GITHUB_STEP_SUMMARY:-}" ] || exit 0
|
|
python3 infra/vitest-summary.py test-output >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
mutation:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: https://github.com/actions/checkout@v4
|
|
- uses: https://github.com/actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: '10.0.x'
|
|
- uses: https://github.com/actions/cache@v3
|
|
with:
|
|
path: ~/.nuget/packages
|
|
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj') }}
|
|
restore-keys: |
|
|
nuget-${{ runner.os }}-
|
|
- run: make mutation
|
|
# Job summary (#136): render each service's Stryker Markdown report on the run page (Gitea
|
|
# 1.27 $GITHUB_STEP_SUMMARY). `if: always()` so a ratchet break still reports — and because
|
|
# `make mutation` stops at the first break, the summary also shows exactly where it stopped.
|
|
# Guarded so it no-ops on a runner/server without summary support. Strips the report's UTF-8 BOM.
|
|
- name: Mutation score summary
|
|
if: always()
|
|
run: |
|
|
[ -n "${GITHUB_STEP_SUMMARY:-}" ] || exit 0
|
|
{
|
|
echo "## 🧬 Mutation testing"
|
|
echo
|
|
for svc in acl event-subscriber domain bff; do
|
|
echo "### $svc"
|
|
echo
|
|
report=$(ls services/"$svc"/StrykerOutput/*/reports/mutation-report.md 2>/dev/null | sort | tail -1)
|
|
if [ -n "$report" ]; then
|
|
sed '1s/^\xef\xbb\xbf//' "$report"
|
|
else
|
|
echo "_No report — \`make mutation\` stopped before \`$svc\` (earlier ratchet break)._"
|
|
fi
|
|
echo
|
|
done
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
# Publish the Stryker HTML reports. `if: always()` uploads them even when the
|
|
# ratchet fails — that is exactly when you want to inspect the survivors.
|
|
# `continue-on-error` keeps the upload best-effort: the mutation *gate* is the
|
|
# ratchet (make mutation's exit code), not the report, so a Gitea artifact-backend
|
|
# 500 must not fail the job (gitea-actions-gotchas.md §4). Glob handles Stryker's
|
|
# non-deterministic StrykerOutput/<timestamp>/ dir. Pinned @v3: @v4's bundled
|
|
# @actions/artifact hard-aborts on non-github.com (GHES guard) — see the runbook.
|
|
- uses: https://github.com/actions/upload-artifact@v3
|
|
if: always()
|
|
continue-on-error: true
|
|
with:
|
|
name: acl-mutation-report
|
|
path: services/acl/StrykerOutput/**/reports/mutation-report.html
|
|
if-no-files-found: warn
|
|
- uses: https://github.com/actions/upload-artifact@v3
|
|
if: always()
|
|
continue-on-error: true
|
|
with:
|
|
name: event-subscriber-mutation-report
|
|
path: services/event-subscriber/StrykerOutput/**/reports/mutation-report.html
|
|
if-no-files-found: warn
|
|
- uses: https://github.com/actions/upload-artifact@v3
|
|
if: always()
|
|
continue-on-error: true
|
|
with:
|
|
name: domain-mutation-report
|
|
path: services/domain/StrykerOutput/**/reports/mutation-report.html
|
|
if-no-files-found: warn
|
|
- uses: https://github.com/actions/upload-artifact@v3
|
|
if: always()
|
|
continue-on-error: true
|
|
with:
|
|
name: bff-mutation-report
|
|
path: services/bff/StrykerOutput/**/reports/mutation-report.html
|
|
if-no-files-found: warn
|
|
|
|
# One stage for every check that needs the live stack. Booting OpenZaak once (instead
|
|
# of once per job) is the cheapest layout (issue #58). No setup-dotnet: the ACL test runs
|
|
# in a built image and everything reaches services by container IP. Needs Docker + egress
|
|
# (base images, nuget, selectielijst.openzaak.nl).
|
|
#
|
|
# `needs: [mutation]` is NOT a data dependency — it serialises the two memory-heavy jobs so
|
|
# they never co-schedule now the runner has capacity >1. A concurrent Stryker run + full-stack
|
|
# bring-up + Playwright browser on one host is what OOMs the e2e (commit d5e5fa2, #126). The
|
|
# light .NET/frontend jobs have no `needs`, so they still parallelise up to runner capacity.
|
|
#
|
|
# No `if: ${{ !cancelled() }}` here (removed in #134): on Gitea 1.27 + act_runner 2.0.0, a job
|
|
# gated by a status-function `if` (always()/cancelled()) on top of `needs` routes through the new
|
|
# transitional "Cancelling" state + capability negotiation and never leaves `waiting` — it's never
|
|
# dispatched (gitea-actions-gotchas.md §7). Default `if: success()` dispatches normally. Cost: a
|
|
# failing mutation ratchet now skips verify-stack instead of running it anyway; the fix-and-re-push
|
|
# re-run exercises verify-stack, so we still get the signal.
|
|
verify-stack:
|
|
needs: [mutation]
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: https://github.com/actions/checkout@v4
|
|
# Bring the full stack up + wait for health — this also is the DoD "compose up
|
|
# reaches green health" smoke (it replaces the old compose-smoke job).
|
|
# Each check carries an `id` so the summary step below can report its per-check outcome (#136).
|
|
# A failed check skips the rest (no step `if:`), so the table shows exactly where it stopped.
|
|
- name: Bring up the full stack & wait for health
|
|
id: up
|
|
run: make verify-up
|
|
- name: Observability backplane (Grafana + Tempo + Prometheus datasources)
|
|
id: obs
|
|
run: OBS_TIMEOUT=180 make verify-observability
|
|
- name: Objecttypen API up + token authenticates
|
|
id: objecttypen
|
|
run: OBJECTTYPEN_TIMEOUT=120 make verify-objecttypen
|
|
- name: Objecten API up + token authenticates + trusts Objecttypen
|
|
id: objecten
|
|
run: OBJECTEN_TIMEOUT=120 make verify-objecten
|
|
- name: ACL ↔ OpenZaak integration tests
|
|
id: acl
|
|
run: make verify-acl
|
|
- name: OpenZaak → NRC notification delivery
|
|
id: nrc
|
|
run: make verify-nrc
|
|
- name: OpenZaak → NRC → Event Subscriber → projection-api
|
|
id: projection
|
|
run: make verify-projection
|
|
- name: Domain → Flowable → ACL → OpenZaak
|
|
id: domain
|
|
run: make verify-domain
|
|
- name: BFF → Keycloak + domain + projection
|
|
id: bff
|
|
run: make verify-bff
|
|
- name: Distributed traces reach Tempo (one connected trace across services)
|
|
id: tracing
|
|
run: TRACING_TIMEOUT=120 make verify-tracing
|
|
- name: Golden-signal metrics scraped by Prometheus (/metrics on every service)
|
|
id: metrics
|
|
run: METRICS_TIMEOUT=120 make verify-metrics
|
|
- name: Self-service e2e (Playwright, login → submit → success)
|
|
id: e2e
|
|
run: make verify-e2e
|
|
# Job summary (#136): a pass/fail table of every live-stack check, so a red verify-stack shows
|
|
# which check failed at a glance. `if: always()` (step-level — safe on runner 2.0.0, unlike the
|
|
# job-level status-function `if` of #134) so it renders even after a check fails.
|
|
- name: verify-stack check summary
|
|
if: always()
|
|
env:
|
|
UP: ${{ steps.up.outcome }}
|
|
OBS: ${{ steps.obs.outcome }}
|
|
OBJECTTYPEN: ${{ steps.objecttypen.outcome }}
|
|
OBJECTEN: ${{ steps.objecten.outcome }}
|
|
ACL: ${{ steps.acl.outcome }}
|
|
NRC: ${{ steps.nrc.outcome }}
|
|
PROJECTION: ${{ steps.projection.outcome }}
|
|
DOMAIN: ${{ steps.domain.outcome }}
|
|
BFF: ${{ steps.bff.outcome }}
|
|
TRACING: ${{ steps.tracing.outcome }}
|
|
METRICS: ${{ steps.metrics.outcome }}
|
|
E2E: ${{ steps.e2e.outcome }}
|
|
run: |
|
|
[ -n "${GITHUB_STEP_SUMMARY:-}" ] || exit 0
|
|
icon() { case "$1" in success) echo "✅";; failure) echo "❌";; skipped) echo "⏭️";; cancelled) echo "🚫";; *) echo "❔ ${1:-—}";; esac; }
|
|
{
|
|
echo "## 🔌 verify-stack checks"
|
|
echo
|
|
echo "| Check | Result |"
|
|
echo "| ----- | :----: |"
|
|
echo "| Bring up + health | $(icon "$UP") |"
|
|
echo "| Observability backplane | $(icon "$OBS") |"
|
|
echo "| Objecttypen API + token | $(icon "$OBJECTTYPEN") |"
|
|
echo "| Objecten API + token | $(icon "$OBJECTEN") |"
|
|
echo "| ACL ↔ OpenZaak | $(icon "$ACL") |"
|
|
echo "| OpenZaak → NRC | $(icon "$NRC") |"
|
|
echo "| NRC → Event Subscriber → projection | $(icon "$PROJECTION") |"
|
|
echo "| Domain → Flowable → ACL → OpenZaak | $(icon "$DOMAIN") |"
|
|
echo "| BFF → Keycloak + domain + projection | $(icon "$BFF") |"
|
|
echo "| Distributed traces (Tempo) | $(icon "$TRACING") |"
|
|
echo "| Golden-signal metrics (Prometheus) | $(icon "$METRICS") |"
|
|
echo "| Self-service e2e (Playwright) | $(icon "$E2E") |"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
# Job summary (#136): per-spec Playwright results, from the JSON report run-e2e-check.sh copied
|
|
# out of the e2e container. Turns a red e2e into a one-glance "which spec" instead of a log dive.
|
|
- name: e2e spec summary
|
|
if: always()
|
|
run: |
|
|
[ -n "${GITHUB_STEP_SUMMARY:-}" ] || exit 0
|
|
python3 infra/playwright-summary.py tests/e2e/playwright-report.json >> "$GITHUB_STEP_SUMMARY"
|
|
# Log dump must precede teardown (which removes the containers).
|
|
- name: Dump container logs on failure
|
|
if: failure()
|
|
run: docker compose -f infra/docker-compose.yml logs --no-color --tail=100 oz-init openzaak nrc-init nrc-web nrc-celery nrc-beat flowable-db flowable-rest flowable-init keycloak acl bff domain projection-db event-subscriber projection-api self-service openbaar behandel beheer objecttypen-db objecttypen-redis objecttypen-init objecttypen objecten-db objecten-redis objecten-init objecten tempo prometheus grafana 2>&1 || true
|
|
- name: Tear down
|
|
if: always()
|
|
run: make down
|