Skip to content

chore(demo): zet de berichtenbox op de huidige main van de proeftuin … #1108

chore(demo): zet de berichtenbox op de huidige main van de proeftuin …

chore(demo): zet de berichtenbox op de huidige main van de proeftuin … #1108

Workflow file for this run

# Deploy naar ZAD (Operations Manager) via RijksICTGilde/zad-actions.
#
# DRIE losse ZAD-projecten (deployments zijn geïsoleerd; cross-project bereik je via de
# https-ingress-URLs `<component>-<deployment>-<projectid>.rig.prd1.gn2.quattro.rijksapps.nl`):
#
# uitvraag (mpfb-8wh) componenten: redis + uitvraag in de deployments
# `test`/`pr-<n>`, plus logius-fsc{pg,mgr,ctl,outway,inway,
# txlog} (FSC-peer van de uitvraag-organisatie) in de EIGEN,
# preview-loze deployment `fsc-logius` — zie
# demo/environment/logius/
# externe-stubs (mpfpsm-lcl) componenten: profiel + notificatie (WireMock-stubs)
# magazijnen (mpfm-w3h) componenten: magazijna + magazijnb in de deployments
# `test`/`pr-<n>`, plus magazijna-fsc{pg,mgr,ctl,inway,
# txlog} (FSC-provider-peer) in de EIGEN, preview-loze
# deployment `fsc-magazijna` — zie
# demo/environment/magazijn-a/
#
# Redis (sessiecache) draait per project intern (centrale ZAD-Redis v7 mist RediSearch);
# interne calls zijn http: redis via redis://…:6379. Het LDV-logboek draait op de
# PostgreSQL die het ZAD-platform per component levert (service `postgresql-database`,
# niet in de component-payloads hieronder — het platform provisioneert 'm zelf); de
# interne JDBC-verbinding daarheen is plaintext, waarvoor
# FBS_LDV_UNSAFE_ALLOW_PLAINTEXT_ENDPOINT aan blijft staan (rationale + voorwaarden in de
# KDoc van OutboundTlsValidator.requireHttps).
#
# HARDE VOORWAARDE voor plaintext-LDV met echte persoonsgegevens: het ZAD-project levert
# pod-to-pod transport-encryptie (mesh-mTLS), niet enkel netwerk-isolatie — de BSN in het
# LDV-dataSubjectId gaat anders onversleuteld over de draad (BIO 13.2.1 / AVG art. 32). De
# applicatie kan dit niet verifiëren; bevestig het per project in het deployment-runbook
# vóór je de override aanzet. Zonder die bevestiging: alleen synthetische test-data.
#
# App-config staat NIET hier (de deploy-action draagt geen env). Configureer de runtime-env
# éénmalig per component op de `test`-deployment van elk project in Operations Manager;
# previews erven via `clone-from: test`. Cross-project-URLs templaten op de ZAD-systeemvar
# `$DEPLOYMENT_NAME` (door ZAD geïnjecteerd + geëxpandeerd), zodat een preview met ZIJN EIGEN
# pr-<n>-buren praat — het doel-project-id staat vast in de URL. Bv. op uitvraag:
# MAGAZIJN_A_URL=https://magazijna-$DEPLOYMENT_NAME-mpfm-w3h.rig... (en magazijnb)
# PROFIEL_SERVICE_URL=https://profiel-$DEPLOYMENT_NAME-mpfpsm-lcl.rig...
# En op magazijnen: AANMELD_URL → uitvraag-webhook (POST /api/v1/aanmeldingen),
# NOTIFICATIE_URL → notificatie-stub (POST /events).
#
# GESTAPELDE PR'S (base != main) deployen niet — zie het `branches`-filter hieronder. Hun
# wijzigingen belanden alsnog in een preview zodra ze de PR bereiken die wél op main mikt: die
# krijgt dan een `synchronize` en rolt zijn eigen pr-<n> opnieuw uit. Wat zo'n PR aan controles
# houdt: test, detekt-gate en de pin-controles — die workflows draaien zichzelf op elke PR
# BEHALVE die naar main (`branches-ignore: [main]`), en worden voor een PR naar main juist hier
# aangeroepen. CodeQL en de fuzz-check draaien er NIET: codeql.yml filtert op main en
# cflite_pr.yml draait uitsluitend als aangeroepen workflow vanuit deze. Die dekking komt dus pas
# bij de PR naar main. Een gestapelde PR is inhoudelijk getoetst maar niet security-gescand; weeg
# dat mee bij het reviewen ervan.
#
# OPRUIMEN STAAT IN cleanup-preview.yml: het `branches`-filter hieronder mag niet op teardown
# drukken (rationale daar). De omgekeerde beweging — GitHub's automatische retarget náár main bij
# het mergen van de parent — is veilig: de checks zijn dan afwezig i.p.v. groen, en `strict: true`
# op main dwingt sowieso een verse run af.
#
# TIMEOUT-MINUTES OP ELKE JOB: GitHub's default is 360 minuten. Een job die hangt (netwerkstall,
# vastgelopen Testcontainer, een herintroduceerde wachtlus) houdt dan zes uur een runner bezet
# voordat iemand het merkt. De waarden hieronder staan op ~3× de waargenomen maximumduur, dus ruim
# genoeg voor een trage runner maar kort genoeg om verspilling te begrenzen. Jobs die een andere
# workflow aanroepen kunnen de sleutel niet dragen; die staat daar in de aangeroepen workflow.
#
# Repo-secrets (Settings → Secrets and variables → Actions):
# ZAD_API_KEY_UITVRAAG — API-key project mpfb-8wh
# ZAD_API_KEY_PROFIEL — API-key project mpfpsm-lcl (externe-stubs)
# ZAD_API_KEY_MAGAZIJNEN — API-key project mpfm-w3h
# (GH_ADMIN_TOKEN hoort bij het opruimen en staat beschreven in cleanup-preview.yml.)
name: Deploy ZAD
on:
# `branches` filtert op de DOELbranch: alleen PR's naar main bouwen en deployen. Een gestapelde
# PR (base = een feature-branch) draait deze workflow dus helemaal niet — geen build, geen
# preview, en daarmee ook niets te gaten. Bewust hier en niet als `if:` per job: een job die via
# `if:` overslaat rapporteert `skipped`, en dat telt als succes voor branch protection. De drie
# `deploy-preview-*` zijn required contexts op main, dus zouden gestapelde PR's groene vinkjes
# verzamelen voor deploys die nooit gedraaid hebben. GitHub herricht een gestapelde PR
# automatisch naar main zodra de parent merget, zónder nieuw event — die stale vinkjes zouden
# dan blijven staan. Zonder run zijn de checks afwezig, wat de merge wél blokkeert.
#
# Let op de bewuste asymmetrie met test.yml/detekt.yml/pin-consistency.yml: die dragen het
# complement (`branches-ignore: [main]`), want een gestapelde PR verdient wél inhoudelijke
# toetsing. Alleen de deploy valt weg. Samen dekken de twee filters elke PR precies één keer.
pull_request:
branches: [main]
# Geen `closed`: het sluiten van een PR heeft hier niets te doen sinds het opruimen in
# cleanup-preview.yml staat. Elke job zou eraan moeten ontsnappen met een
# `action != 'closed'`-clausule, en de run zelf zou een lege, volledig overgeslagen run zijn.
#
# Wel `ready_for_review`: een draft-PR rolt niet uit (zie `changes`), dus het moment dat hij uit
# draft komt moet de uitrol aftrappen, ook zonder nieuwe commit. Geen `converted_to_draft`: een
# preview die er al staat, mag blijven tot de PR sluit.
types: [opened, synchronize, reopened, ready_for_review]
push:
branches: [main]
# Least-privilege default voor GITHUB_TOKEN: read-only op workflow-niveau; elke job die schrijft
# declareert zijn eigen scopes.
permissions: read-all
# GEEN workflow-brede concurrency. De toetsende checks draaien sinds #173 als jobs ín deze
# workflow; een workflow-brede groep met `cancel-in-progress: false` zou een opvolgende push laten
# wachten tot de vorige deploy klaar is én de achterhaalde testrun helemaal uit laten draaien —
# precies de verspilling die we wegnemen. Met `cancel-in-progress: true` zou een lopende ZAD-deploy
# halverwege afgebroken kunnen worden, wat een deployment in een inconsistente staat achterlaat.
# Beide eisen tegelijk kan alleen per job:
# * build/toetsing → eigen groep met cancel-in-progress: true (achterhaald werk stoppen);
# * deploy → eigen groep PER PROJECT + doel-deployment met cancel-in-progress: false
# (nooit halverwege afbreken; per project apart zodat de drie parallelle jobs elkaar niet
# blokkeren, wat met één gedeelde groep wél zou gebeuren). De drie legs van de opruim-matrix in
# cleanup-preview.yml zitten in dezelfde groepen; de opruim-job voor de GitHub-kant deelt de
# uitvraag-groep en draait daarom ná die matrix, zodat die twee teardowns nooit samen in de
# wachtrij van één groep staan (GitHub annuleert dan de eerst-wachtende).
env:
REGISTRY: ghcr.io
PROJECT_UITVRAAG: mpfb-8wh
PROJECT_EXTERNE_STUBS: mpfpsm-lcl
PROJECT_MAGAZIJNEN: mpfm-w3h
# Publiek infra-image (niet door de cleanup verwijderd). Pin in sync met compose.yaml
# (zie pin-consistency.yml).
#
# Redis draait op ZAD zónder persistence: zet op de redis-component de runtime-env
# `REDIS_ARGS=--save "" --appendonly no` (in Operations Manager, net als de overige
# runtime-env hierboven). Het restricted SecurityContext (willekeurige non-root UID)
# mag niet in de image-datadir `/data` schrijven, dus RDB-snapshots falen met
# "Permission denied". Kan uit: de sessiecache is ephemeral en rekent nergens op
# persistence (idempotente RediSearch-bootstrap, TTL-entries, crash-safe vangnet-lock).
REDIS_IMAGE: redis/redis-stack-server:7.4.0-v3
# Verse preview-provisioning (`clone-from: test` maakt nieuwe volumes + ingress aan) duurt
# structureel langer dan het bijwerken van een bestaande deployment. De action-default van
# 300s is daarvoor te krap: uitvraag en externe-stubs time-outen er reproduceerbaar op
# ("Task did not complete within 300s"), terwijl de test-deploys (bestaande deployment) in
# <100s klaar zijn en de default houden. Gelijkgetrokken met de gate-timeout (900s) zodat
# er één marge-getal in deze workflow rondgaat.
# Let op: dit is de wachttijd van de runner op de OM-task. De melding "timed out waiting for
# application to be created" komt daarentegen van OM zelf (Argo-Application-wait) en wordt
# NIET door deze waarde beïnvloed — die deploys landen alsnog gezond; opnieuw draaien volstaat.
PREVIEW_TASK_TIMEOUT: '900'
# De comment wordt door .github/scripts/preview-comment.sh geplaatst en niet door de
# deploy-action; het waarom staat in de kop van dat script. cleanup-preview.yml zoekt de comment
# op dezelfde tekst, dus drift tussen plaatsen en opruimen laat hem stil achter op een gesloten
# PR. test-preview-comment.sh bewaakt dat de drie plekken gelijk blijven.
COMMENT_HEADER: '## 🚀 Preview Deployment'
# De cross-domain-regels die de demo-console bij de sessiecache en bij de vier Toxiproxy-admin-API's
# laten. Elke regel staat op projectniveau zónder peer-deployment; die vult elke deployment zelf
# in, want een regel waarvan de peer-deployment open blijft wordt bij het genereren overgeslagen.
# Opruimen hoeft niet: die ingevulde regels staan in de projectspec onder de deployment en
# verdwijnen met hem.
#
# Eén regel opent één poort op één component bij één peer, dus elke hop is een eigen regel. De
# console is altijd de aanroepende kant, dus het magazijnen-project draagt de outbound-kant van
# álle regels en is per definitie de vereniging van de twee lijsten erboven —
# test-cross-domain-preview.sh bewaakt dat.
CROSS_DOMAIN_REGELS_UITVRAAG: >-
democonsole-naar-redis
democonsole-naar-toxiproxy-aanmeld
democonsole-naar-toxiproxy-redis
CROSS_DOMAIN_REGELS_EXTERNE_STUBS: >-
democonsole-naar-toxiproxy-profiel
democonsole-naar-toxiproxy-notificatie
CROSS_DOMAIN_REGELS_MAGAZIJNEN: >-
democonsole-naar-redis
democonsole-naar-toxiproxy-aanmeld
democonsole-naar-toxiproxy-redis
democonsole-naar-toxiproxy-profiel
democonsole-naar-toxiproxy-notificatie
# Toxiproxy op de amd64-child in plaats van op de manifest list. Die lijst draagt vier entries met
# drie unieke digests — `linux/arm/v6` staat er twee keer in met dezelfde digest — en de
# Quay-pull-through-cache van ZAD schendt daarop een unique constraint en geeft HTTP 500. Een
# child is een gewoon manifest zonder kinderen, dus daar struikelt hij niet over.
#
# De tag staat er alleen bij zodat pin-consistency.yml deze regel aan compose.yaml kan binden; de
# digest bepaalt wat er getrokken wordt. Bij een versiebump moeten ze samen bewegen:
#
# TOKEN=$(curl -s "https://ghcr.io/token?scope=repository:shopify/toxiproxy:pull" | jq -r .token)
# curl -s -H "Authorization: Bearer $TOKEN" \
# -H "Accept: application/vnd.oci.image.index.v1+json" \
# "https://ghcr.io/v2/shopify/toxiproxy/manifests/<versie>" |
# jq -r '.manifests[] | select(.platform.architecture=="amd64") | .digest'
#
# Alleen amd64: ZAD draait daarop, en compose houdt de multi-arch-tag voor wie op arm werkt.
# Verdwijnt de dubbele child upstream, of komt de Quay-dedupe (PROJQUAY-10068) in een release, dan
# kan de digest hier weg en volstaat de tag.
TOXIPROXY_IMAGE: 'ghcr.io/shopify/toxiproxy:2.12.0@sha256:a3e244375123dad8849091bcc59775e188624d3f602db01901f9af855682fef8'
jobs:
# Bepaalt in één keer wát er voor deze revisie moet draaien. Levert vier onafhankelijke
# uitkomsten: `run` (code-checks), `deploy` (bouwen en uitrollen), `demo-only` (test-scope)
# en `fuzz` (fuzz-ronde). Voor een bot- of draft-PR valt alléén het uitrollen af — tests, detekt
# en fuzz draaien juist wél, want ook die PR's horen getoetst te worden.
#
# De build-, gate- en deploy-preview-jobs hangen aan `deploy`; een via `if` overgeslagen job ín
# deze workflow telt als 'skipped' = succes voor de required checks, dus de merge blijft
# mogelijk. Op een push naar main staat alles aan.
#
# Drie gevallen rollen niet uit:
# - Draft-PR's: bij veel gelijktijdige PR's bekijkt niemand alle previews, dus die komt pas bij
# `ready_for_review`. Die overgeslagen checks laten geen merge door: GitHub merget geen draft,
# en de run van `ready_for_review` vervangt ze op dezelfde commit.
# - Wijzigingen die de uitgerolde applicatie niet raken (documentatie, repo-meta, bruno, en de
# niet-uitgerolde delen van demo/). Welke paden dat precies zijn staat in
# .github/scripts/wijzigingsfilter.sh.
# - PR's van een bot: `zad-actions` weigert die zelf (`skip-bot-prs`, default true), dus de
# preview komt er toch niet. Zonder deze uitsluiting bouwden en pushten we er wél images voor
# die nooit een pod bereiken.
changes:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
run: ${{ steps.filter.outputs.run }}
deploy: ${{ steps.filter.outputs.deploy }}
demo-only: ${{ steps.filter.outputs.demo-only }}
fuzz: ${{ steps.filter.outputs.fuzz }}
# Alleen lezen, en expliciet in plaats van de `read-all` van de workflow: deze job checkt de
# PR uit en voert er een script uit. De uitkomst stuurt de build- en deploy-jobs aan, dus de
# PR bepaalt hier mede wat er daarna draait. Dat is aanvaardbaar omdat de trigger
# `pull_request` is (geen `pull_request_target`): een fork krijgt een read-only token en komt
# niet aan de secrets van de deploy-jobs.
permissions:
contents: read
pull-requests: read
steps:
# De beoordeling zelf staat in .github/scripts/wijzigingsfilter.sh; test.yml en detekt.yml
# draaien op een gestapelde PR hetzelfde script. cflite_pr.yml deelt het script ook, maar
# heeft alleen `workflow_call` als trigger en draait daar dus niet.
# Een gestruikelde checkout laat het script ontbreken of staat het er juist compleet — dat
# laatste kan ook, want checkout kan ná het uitpakken nog falen. Beide gevallen zijn afgedekt:
# de stap hieronder valt bij elke onvolledige uitkomst terug op alles draaien.
# `continue-on-error` houdt de job daarbij groen, want bij een rode `changes` blijven de
# outputs leeg: `build*` slaat dan over op de impliciete success(), `gate` op een `deploy` die
# niet 'true' is, en de previews op een `gate` die niet 'success' is. Alle drie rapporteren
# 'skipped', en dat telt als succes in de required checks — de uitrol verdwijnt stil.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
continue-on-error: true
with:
persist-credentials: false
sparse-checkout: .github/scripts
# Eén plek waar de gewijzigde bestanden opgehaald en beoordeeld worden. De aangeroepen
# workflows stelden dezelfde vraag elk apart; dat kostte per PR vier runner-allocaties
# voor hetzelfde antwoord. Zij krijgen de uitkomst nu als input en slaan hun eigen
# detectie over.
- name: Bepaal wat er moet draaien
id: filter
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EVENT: ${{ github.event_name }}
REPO: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
PR_AUTHOR_TYPE: ${{ github.event.pull_request.user.type }}
PR_DRAFT: ${{ github.event.pull_request.draft }}
run: |
set -euo pipefail
# Op sleutelnaam filteren, niet op vorm: alleen deze vier sleutels met true/false horen
# in $GITHUB_OUTPUT. Een regel in een andere vorm laat de runner het hele outputbestand
# weigeren ("Invalid format"); de stap faalt, alle outputs blijven leeg en de uitrol-jobs
# slaan stil over. Het valideren en publiceren zit daarom in één script met eigen
# fixtures — inline stond het vier keer en kon geen enkele test het gedrag rood krijgen.
.github/scripts/publiceer-uitkomsten.sh
# Afgeleide waarden op één plek: image-tag + lowercase GHCR-owner (GHCR eist lowercase).
#
# De PR-tag draagt de head-sha en is dus UNIEK per commit — dat is de kern van de
# preview-uitrol, geen cosmetica. De deploy zet de image-referentie in het door Operations
# Manager gerenderde k8s-manifest, en Argo CD rolt alleen uit bij een verschil in dát
# manifest. Met een vaste tag `pr-<n>` blijft het manifest bij elke volgende commit
# byte-identiek: de deploy-check wordt groen terwijl de pod op de image-laag van de eerste
# build blijft draaien. `imagePullPolicy: Always` vangt dat niet af — dat werkt pas bij een
# herstart, en die komt niet vanzelf. Op main speelde het niet: `main-<sha7>` wijzigt al
# per commit.
#
# Elke build krijgt zo zijn eigen tag; `cleanup-preview-images` in cleanup-preview.yml ruimt ze
# bij PR-sluiten allemaal op — op prefix, plus de kale `pr-<n>` uit de tijd vóór de
# per-commit-tags.
meta:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
tag: ${{ steps.m.outputs.tag }}
owner: ${{ steps.m.outputs.owner }}
proeftuin_image: ${{ steps.proeftuin.outputs.image }}
componenten-uitvraag: ${{ steps.componenten.outputs.uitvraag }}
componenten-externe-stubs: ${{ steps.componenten.outputs.externe-stubs }}
componenten-magazijnen: ${{ steps.componenten.outputs.magazijnen }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# De berichtenbox van de proeftuin (MinBZK/moza-poc), als component in ons eigen project. Zo
# bepalen wij welke versie er onder een demo hangt, in plaats van het uitrolritme van een
# ander project te volgen. De referentie hoort in de deploy en niet alleen in de projectspec:
# OM's upsert-deployment neemt bij het aanmaken alleen de meegegeven componenten over en
# kopieert die van de kloonbron niet, dus zonder dit heeft alleen `test` een berichtenbox en
# geen enkele preview.
#
# Uit compose.yaml en niet uit een eigen env-regel hier; het waarom staat bij die regel.
#
# De repo-variabele `PROEFTUIN_IMAGE` overschrijft die pin voor elke uitrol die daarna draait.
# Bedoeld om de demo tijdelijk op nog niet gemergd werk van de proeftuin te zetten, of op een
# bevroren release-tag: variabele zetten, deploy opnieuw draaien, en terugzetten door hem weg
# te halen. Een variabele en geen commit, want dit hoort niet in de geschiedenis van de repo
# thuis; hij geldt wél voor `test` én elke preview, vandaar de notice hieronder.
- id: proeftuin
env:
PROEFTUIN_IMAGE: ${{ vars.PROEFTUIN_IMAGE }}
run: |
set -euo pipefail
# Eerst toewijzen, dan pas schrijven: `echo "image=$(...)"` zou bij een falend script een
# lege waarde wegschrijven en de deploy een component zonder image laten zetten.
image=$(.github/scripts/proeftuin-image.sh)
# Zichtbaar bovenaan de run en niet alleen in het stap-log: een demo die op een andere
# berichtenbox draait dan de repo pint, verklaart anders gedrag dat niemand terugvindt.
if [ -n "${PROEFTUIN_IMAGE:-}" ]; then
echo "::notice title=Berichtenbox overschreven::De repo-variabele PROEFTUIN_IMAGE zet de berichtenbox op $image in plaats van de pin in compose.yaml."
fi
echo "image=$image" >> "$GITHUB_OUTPUT"
- id: m
# Variabele waarden via env i.p.v. inline interpolatie (defense-in-depth injectie).
env:
EVENT: ${{ github.event_name }}
ACTIE: ${{ github.event.action }}
PR: ${{ github.event.number }}
# Op een pull_request-event is GITHUB_SHA de (efemere) merge-commit; de head-sha is
# wat in de PR-tijdlijn staat en waar een reviewer op kan matchen.
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
if [ "$EVENT" = "pull_request" ]; then
tag="pr-${PR}-${HEAD_SHA::7}"
# Een herdraai bouwt uit refs/pull/<n>/merge, dus met de main van dát moment. Is main
# inmiddels verder, dan levert dezelfde head-sha andere inhoud op en zou de tag
# verschuiven — precies de bevriezing die deze tagvorm moet voorkomen. Vanaf poging 2
# dus een eigen tag. Poging 1 (het normale geval) blijft kaal en leesbaar.
if [ "$GITHUB_RUN_ATTEMPT" -gt 1 ]; then
tag="${tag}-r${GITHUB_RUN_ATTEMPT}"
fi
# Hetzelfde bij `ready_for_review`: een PR die al een preview had, naar draft ging en
# zonder nieuwe commit terugkomt, bouwt tegen een nieuwere main onder dezelfde head-sha.
# Die preview blijft op draft staan, dus zonder eigen tag rolt er niets uit.
if [ "$ACTIE" = "ready_for_review" ]; then
tag="${tag}-k${GITHUB_RUN_NUMBER}"
fi
echo "tag=$tag" >> "$GITHUB_OUTPUT"
else
echo "tag=main-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
fi
echo "owner=${GITHUB_REPOSITORY_OWNER,,}" >> "$GITHUB_OUTPUT"
# De componenten van elke preview op één plek. `preview-klaarzetten` maakt een nieuwe preview
# aan met deze lijst en de deploy rolt dezelfde lijst uit. Operations Manager neemt bij het
# aanmaken alleen de meegegeven componenten over, dus een component die alleen in de deploy
# staat, zou de preview bij het aanmaken missen.
#
# De profiel-stub draait het demo-image: dat layert op dezelfde gedeelde mappings en voegt de
# voorkeuren van de demo-persona's toe. Zonder die voorkeuren geeft de catch-all voor élke
# ontvanger hetzelfde ene magazijn terug, en valt er van de federatie niets te zien. De
# Toxiproxy's staan in dezelfde deployment als hun upstream, zodat de proxy die de console
# aanmaakt naar de stub van díe preview wijst en niet naar die van `test`.
- id: componenten
env:
OWNER: ${{ steps.m.outputs.owner }}
TAG: ${{ steps.m.outputs.tag }}
BERICHTENBOX: ${{ steps.proeftuin.outputs.image }}
run: |
set -euo pipefail
eigen() { printf '%s/%s/%s:%s' "$REGISTRY" "$OWNER" "$1" "$TAG"; }
uitvraag=$(jq -nc --arg redis "$REDIS_IMAGE" --arg toxiproxy "$TOXIPROXY_IMAGE" \
--arg uitvraag "$(eigen fbs-berichtenuitvraag)" '[
{name: "redis", image: $redis},
{name: "toxiproxy-aanmeld", image: $toxiproxy},
{name: "toxiproxy-redis", image: $toxiproxy},
{name: "uitvraag", image: $uitvraag}
]')
stubs=$(jq -nc --arg toxiproxy "$TOXIPROXY_IMAGE" \
--arg profiel "$(eigen fbs-demo-profiel)" --arg notificatie "$(eigen fbs-externe-stubs)" '[
{name: "profiel", image: $profiel},
{name: "notificatie", image: $notificatie},
{name: "toxiproxy-profiel", image: $toxiproxy},
{name: "toxiproxy-notificatie", image: $toxiproxy}
]')
magazijnen=$(jq -nc --arg magazijn "$(eigen fbs-berichtenmagazijn)" \
--arg console "$(eigen fbs-demo-console)" --arg simulator "$(eigen fbs-magazijn-simulator)" \
--arg personas "$(eigen fbs-demo-personas)" --arg berichtenbox "$BERICHTENBOX" '[
{name: "magazijna", image: $magazijn},
{name: "magazijnb", image: $magazijn},
{name: "democonsole", image: $console},
{name: "magazijnsimulator", image: $simulator},
{name: "demopersonas", image: $personas},
{name: "proeftuin", image: $berichtenbox}
]')
{
echo "uitvraag=$uitvraag"
echo "externe-stubs=$stubs"
echo "magazijnen=$magazijnen"
} >> "$GITHUB_OUTPUT"
# Bouw + push de service-images met jib (geen Dockerfile). Niet wanneer `changes` de keten heeft
# uitgesloten: zo'n revisie deployt toch niet, dus twee volledige Maven/jib-builds + push kosten
# dan alleen rekentijd, netwerk en een ghcr-versie die nooit een pod bereikt.
build:
if: github.event_name == 'push' || needs.changes.outputs.deploy == 'true'
needs: [meta, changes]
runs-on: ubuntu-latest
timeout-minutes: 20
# Een opvolgende push maakt deze build achterhaald: de preview rolt straks toch de nieuwere
# commit uit, dus doorbouwen levert alleen runnertijd en een ghcr-versie op die nooit een pod
# bereikt. Per matrix-service een eigen groep, anders blokkeren de twee parallelle builds van
# dezelfde run elkaar.
concurrency:
group: build-${{ matrix.service }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
packages: write
strategy:
matrix:
service: [berichtenuitvraag, berichtenmagazijn]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: temurin
# Expliciet aan, ook al verifieert de action Temurin sinds v6 standaard: een wissel naar
# een distributie zonder handtekeningondersteuning faalt hiermee hard, in plaats van de
# controle stil te laten wegvallen. Draait alleen wanneer de JDK daadwerkelijk gedownload
# wordt; komt hij uit de toolcache van de runner, dan valt er niets te verifiëren.
verify-signature: true
java-version: '21'
# Cache de Maven-dependency-tree tussen runs; elke PR-push bouwt anders de
# volledige boom opnieuw (~/.m2 redownload) — onnodig netwerk/energie/CI-tijd.
cache: maven
- name: Build + push image (jib)
# Variabele waarden via env i.p.v. inline interpolatie (defense-in-depth injectie).
# Het GHCR-token gaat via de MicroProfile-env-vorm QUARKUS_CONTAINER_IMAGE_PASSWORD
# i.p.v. een -D-flag, zodat het secret niet in de proceslijst (ps) of build-output
# belandt. Quarkus mapt die env-var op quarkus.container-image.password.
env:
SERVICE: ${{ matrix.service }}
GROUP: ${{ needs.meta.outputs.owner }}
TAG: ${{ needs.meta.outputs.tag }}
GHCR_USER: ${{ github.actor }}
QUARKUS_CONTAINER_IMAGE_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
run: |
./mvnw -B package -DskipTests \
-pl "services/$SERVICE" -am \
-Dquarkus.container-image.build=true \
-Dquarkus.container-image.push=true \
-Dquarkus.container-image.registry="$REGISTRY" \
-Dquarkus.container-image.group="$GROUP" \
-Dquarkus.container-image.tag="$TAG" \
-Dquarkus.container-image.username="$GHCR_USER"
# Externe-stubs: één WireMock-image met de profiel- én notificatie-mappings erin gebakken
# (plain docker, geen jib). Wordt als twee losse componenten gedeployd (profiel + notificatie).
#
# BEWUST GEEN paths-filter, hoewel de inhoud zelden wijzigt: de deploy-jobs verwachten een
# image op de tag van déze run, en die tag draagt per commit de head-sha — precies wat de
# preview-uitrol laat werken. Overslaan bij een ongewijzigde stub-map zou een
# tag-hergebruikstrategie vergen die daarmee botst. De prijs is een herbouw per commit; de
# opbrengst van een filter weegt daar niet tegenop zolang de per-commit-tag de basis is.
build-externe-stubs:
if: github.event_name == 'push' || needs.changes.outputs.deploy == 'true'
needs: [meta, changes]
runs-on: ubuntu-latest
timeout-minutes: 15
concurrency:
group: build-externe-stubs-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Build + push externe-stubs
env:
OWNER: ${{ needs.meta.outputs.owner }}
TAG: ${{ needs.meta.outputs.tag }}
GHCR_USER: ${{ github.actor }}
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "$GHCR_TOKEN" | docker login "$REGISTRY" -u "$GHCR_USER" --password-stdin
IMG="$REGISTRY/$OWNER/fbs-externe-stubs:$TAG"
docker build -t "$IMG" wiremock/externe-stubs
docker push "$IMG"
# Dezelfde mappings plus de voorkeuren van de demo-persona's; zie
# wiremock/demo-profiel/Dockerfile voor waarom die niet in het gedeelde image passen.
# Eén job, want het demo-image layert op de mappings van het gedeelde image: los
# bouwen zou ze uit elkaar kunnen laten lopen zonder dat iets dat merkt.
#
# De vier ondernemers met hun volledige fan-out komen uit het generatiescript. Lokaal
# bind-mount compose die map; hier moeten ze het image in, anders kent elke persona op de
# gedeelde omgeving alleen de twee echte magazijnen. Geen SIMULATOR_URL: de
# ondernemer-stubs dragen alleen OIN's, geen adressen. Ook geen eigen aantal — de default
# van het script (98) is hetzelfde getal als waarmee de twee ZAD-attachments
# (magazijn-simulator-set, magazijnen-register) gegenereerd zijn, en een profiel dat naar
# een niet-bestaand magazijn wijst wordt door de uitvraag stil overgeslagen.
python3 demo/genereer-magazijnen.py
mkdir -p wiremock/demo-profiel/generated
# Eerst leegmaken: hernoemt iemand een ondernemer, dan zou de oude naam in een werkkopie
# blijven staan en als extra mapping met dezelfde voorrang meegaan. In CI is de checkout
# schoon, maar dezelfde regels draaien met de hand.
rm -f wiremock/demo-profiel/generated/*.json
cp demo/generated/profiel/ondernemer-*.json wiremock/demo-profiel/generated/
# Hardop falen in plaats van terugvallen op een fan-out van twee: dat is precies de
# stille faalwijze die deze stap opheft.
gegenereerd=$(find demo/generated/profiel -name 'ondernemer-*.json' | wc -l)
gekopieerd=$(find wiremock/demo-profiel/generated -name 'ondernemer-*.json' | wc -l)
if [ "$gegenereerd" -eq 0 ] || [ "$gegenereerd" -ne "$gekopieerd" ]; then
echo "::error::$gegenereerd ondernemer-stubs gegenereerd, $gekopieerd in de build-context beland."
exit 1
fi
DEMO_IMG="$REGISTRY/$OWNER/fbs-demo-profiel:$TAG"
docker build -t "$DEMO_IMG" -f wiremock/demo-profiel/Dockerfile wiremock
# Het artefact zelf toetsen, niet de bestanden ernaartoe: dit is de enige plek waar de
# voorrang tussen de handgeschreven persona's (5) en de gegenereerde ondernemers (1)
# werkelijk uitkomt. Landelijk Concern is de grootste van de vier en dus de scherpste
# controle.
docker run -d --rm --name profiel-controle -p 8089:8080 "$DEMO_IMG"
for _ in $(seq 30); do
curl -sf http://localhost:8089/__admin/mappings >/dev/null && break
sleep 1
done
scopes=$(curl -sf -X POST http://localhost:8089/api/profielservice/v1/partij \
-H 'Content-Type: application/json' \
-d '{"identificatieType":"KVK","identificatieNummer":"90000003"}' \
| jq '.voorkeuren[0].scopes | length' || echo 0)
docker rm -f profiel-controle >/dev/null || true
if [ "$scopes" != "100" ]; then
echo "::error::Landelijk Concern geeft $scopes organisaties in plaats van 100; de gegenereerde stubs zitten niet in het image of verliezen op voorrang."
exit 1
fi
echo "Fan-out geverifieerd: Landelijk Concern kent $scopes organisaties."
docker push "$DEMO_IMG"
# De contract-bootstrap draait op ZAD als component náást de manager van zijn eigen peer: de
# interne manager-API heeft daar geen route en de tenant-baseline-NetworkPolicy laat alleen
# verkeer binnen hetzelfde deployment toe. Eén image, twee rollen — welke, leest het entrypoint
# uit FSC_ROL in de component-env.
build-contract-bootstrap:
if: github.event_name == 'push' || needs.changes.outputs.run == 'true'
needs: [meta, changes]
runs-on: ubuntu-latest
timeout-minutes: 15
concurrency:
group: build-contract-bootstrap-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Build + push contract-bootstrap
env:
OWNER: ${{ needs.meta.outputs.owner }}
TAG: ${{ needs.meta.outputs.tag }}
GHCR_USER: ${{ github.actor }}
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "$GHCR_TOKEN" | docker login "$REGISTRY" -u "$GHCR_USER" --password-stdin
IMG="$REGISTRY/$OWNER/fbs-fsc-contract-bootstrap:$TAG"
# Context is demo/environment/ en niet de contracts-map: de scripts leunen op ../../lib.
docker build -f demo/environment/federatie/contracts/Dockerfile -t "$IMG" demo/environment
docker push "$IMG"
# De drie demo-modules met een eigen image: het bedieningspaneel, de personadienst en de
# magazijn-simulator. Alle drie componenten van `test` in het magazijnen-project, dus ze rollen
# mee met de rest van de keten,
# previews inbegrepen. De job hangt aan `run` en niet aan `deploy`: `run` is de ruimere uitkomst
# van de twee, dus een PR die de uitrol niet raakt bouwt de images tóch en laat een kapotte
# jib-configuratie vallen vóór de merge. Omdat `deploy=true` altijd `run=true` impliceert, is deze
# job er ook wanneer deploy-*-magazijnen hem nodig heeft.
#
# Eén job en één Maven-aanroep voor alle drie: ze delen hun afhankelijkheden, dus een tweede runner
# zou vooral dezelfde boom nog een keer ophalen.
build-demo-images:
if: github.event_name == 'push' || needs.changes.outputs.run == 'true'
needs: [meta, changes]
runs-on: ubuntu-latest
timeout-minutes: 20
concurrency:
group: build-demo-images-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: temurin
verify-signature: true
java-version: '21'
cache: maven
- name: Build + push image (jib)
env:
GROUP: ${{ needs.meta.outputs.owner }}
TAG: ${{ needs.meta.outputs.tag }}
GHCR_USER: ${{ github.actor }}
QUARKUS_CONTAINER_IMAGE_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
run: |
./mvnw -B package -DskipTests \
-pl demo/demo-console,demo/demo-personas,demo/magazijn-simulator -am \
-Dquarkus.container-image.build=true \
-Dquarkus.container-image.push=true \
-Dquarkus.container-image.registry="$REGISTRY" \
-Dquarkus.container-image.group="$GROUP" \
-Dquarkus.container-image.tag="$TAG" \
-Dquarkus.container-image.username="$GHCR_USER"
# De vier toetsende workflows draaien als aangeroepen (`workflow_call`) job ín deze workflow,
# zodat de deploy er met `needs:` aan kan hangen. Vóór #173 draaiden ze zelfstandig en wachtte
# een `gate`-job er in een poll-lus op: ~461 s per run een runner die niets deed dan `sleep 15`.
# De eerste drie (test, detekt, pin-consistency) dragen daarnaast hun eigen `pull_request`-trigger
# op `branches-ignore: [main]` — het complement van de `branches: [main]` hierboven — zodat een
# gestapelde PR ze zelfstandig draait en een PR naar main ze precies één keer draait, hier.
# `cflite_pr.yml` heeft géén eigen trigger en draait uitsluitend als aangeroepen workflow.
#
# LET OP: door de aanroep krijgen de check-runs het aanroeper-job-voorvoegsel: `checks-test / test`
# in plaats van `test`. Branch protection op main pint op die namen; wijzig je hier een job-id,
# dan moet de required context mee. Een required context die nooit meer verschijnt, blokkeert
# elke merge.
#
# De aanroeper-jobs staan bewust ALTIJD aan; het overslaan gebeurt binnen de aangeroepen
# workflow. Een overgeslagen aanroeper-job draait die workflow namelijk nooit, waardoor de
# geneste check-run (`checks-test / test`) niet bestaat — en een required context die niet
# verschijnt blokkeert de merge voorgoed. Anders dan een overgeslagen job ín deze workflow,
# die wél als 'skipped' (= succes) doortelt.
#
# `!cancelled()` zodat een mislukte detectie de checks niet meesleept: de uitkomst is dan
# leeg, en op een lege uitkomst zet de aangeroepen workflow zijn eigen detectie weer aan.
# De uitkomst is daarmee fail-safe — hij wordt opnieuw bepaald, niet aangenomen.
checks-test:
needs: changes
# `!` mag geen plain scalar beginnen (YAML-tagindicator), vandaar `${{ }}`; een blokscalar
# (`>-`, zoals bij `checks-fuzz`) werkt net zo goed.
if: ${{ !cancelled() }}
uses: ./.github/workflows/test.yml
with:
wijzigingen-relevant: ${{ needs.changes.outputs.run }}
demo-only: ${{ needs.changes.outputs.demo-only }}
# De aangeroepen workflow mag nooit meer rechten hebben dan de aanroeper-job toekent;
# pull-requests: write is voor de JaCoCo-coverage-comment.
permissions:
contents: read
pull-requests: write
checks-detekt:
needs: changes
if: ${{ !cancelled() }}
uses: ./.github/workflows/detekt.yml
with:
wijzigingen-relevant: ${{ needs.changes.outputs.run }}
permissions:
contents: read
pull-requests: read
security-events: write
checks-pins:
uses: ./.github/workflows/pin-consistency.yml
# Alleen lezen: deze checks oordelen over de pins en schrijven niets terug. Een achterlopende
# berichtenbox-pin komt als eigen PR langs (proeftuin-pin.yml), niet als melding op déze PR.
permissions:
contents: read
# ClusterFuzzLite fuzzt alleen PR's: op een push naar main is er geen basis om code-change-fuzzing
# tegen af te zetten. Deze job wordt daar dus overgeslagen, en de gate telt dat als OK.
checks-fuzz:
needs: changes
if: >-
!cancelled()
&& github.event_name == 'pull_request'
uses: ./.github/workflows/cflite_pr.yml
with:
fuzz-relevant: ${{ needs.changes.outputs.fuzz }}
permissions: read-all
# Kwaliteitspoort vóór élke ZAD-deploy: geen cluster-capaciteit (en geen review-ruis van een
# "geslaagde" preview) verspillen aan een revisie die de tests/kwaliteitsgates niet haalt.
#
# De poort zelf toetst niets — dat doen de checks-jobs hierboven. Hij aggregeert alleen hun
# resultaat op één plek, zodat de "overgeslagen telt als OK"-regel één keer bestaat in plaats van
# als `always() && ...`-formule op elk van de zes deploy-jobs, met zes kansen op drift.
#
# CodeQL en architecture bewust NIET in de set: CodeQL mag traag of tijdelijk rood zijn (bv.
# extractor-lag op een nieuwe Kotlin-versie) zonder de preview te blokkeren, en architecture
# triggert alleen op docs/architecture-wijzigingen.
gate:
# `!cancelled()` i.p.v. de impliciete success(): een overgeslagen `checks-fuzz` (push naar main)
# zou deze job anders meeslepen in 'skipped' en de deploy stilzwijgend blokkeren. Bij een
# geannuleerde run hoeft de poort juist niet meer te oordelen.
# Niet wanneer `changes` de keten heeft uitgesloten (documentatie/repo-meta of bot-PR) — dan valt er niets
# te bewaken.
if: >-
!cancelled()
&& (github.event_name == 'push' || needs.changes.outputs.deploy == 'true')
needs: [changes, checks-test, checks-detekt, checks-pins, checks-fuzz]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Beoordeel de verplichte checks
env:
# Naam=resultaat per verplichte check. De namen zijn die van de check-runs zoals ze in
# de PR verschijnen, niet de job-id's — dit is wat een lezer van het logboek zoekt.
# De fuzz-check hoort er alleen bij op een pull_request; op een push naar main is hij
# per definitie afwezig en zou hij elke run een 'overgeslagen'-waarschuwing opleveren.
RESULTATEN: |
test=${{ needs.checks-test.result }}
detekt-gate=${{ needs.checks-detekt.result }}
pin-consistency=${{ needs.checks-pins.result }}
${{ github.event_name == 'pull_request' && format('PR={0}', needs.checks-fuzz.result) || '' }}
run: |
set -euo pipefail
geblokkeerd=0
while IFS='=' read -r naam resultaat; do
[ -n "$naam" ] || continue
case "$resultaat" in
success)
echo "Verplichte check '$naam' geslaagd."
;;
skipped)
# Legitiem wanneer de check zichzelf heeft overgeslagen via zijn eigen
# relevantie-filter: de fuzz-`PR` bij een niet-fuzz-relevante wijziging of op een
# push naar main, of een docs-only wijziging. (Een docs-only PR bereikt deze poort
# niet eens — dan is de deploy zelf al overgeslagen.) Luid loggen zodat een
# onbedoelde overslag auditbaar is i.p.v. stil als 'groen' doortelt.
echo "::warning::Verplichte check '$naam' is overgeslagen — als OK geteld (check overgeslagen via eigen relevantie-filter)."
;;
*)
echo "::error::Verplichte check '$naam' eindigde als '$resultaat' — deploy geblokkeerd."
geblokkeerd=1
;;
esac
done <<< "$RESULTATEN"
if [ "$geblokkeerd" -ne 0 ]; then
exit 1
fi
echo "Alle verplichte checks groen — deploy vrijgegeven."
# Zet de drie preview-deployments en hun cross-domain-regels klaar in de projectbestanden, zonder
# uit te rollen; de deploy-jobs hieronder rollen daarna uit wat er dan staat. Waarom dat nodig is,
# staat in .github/scripts/preview-klaarzetten.sh. Kort: Operations Manager slaat een regel
# waarvan de peer-deployment bij het renderen nog niet bestaat stil over en rendert hem daarna niet
# opnieuw, en de console van de magazijnen haalt zonder zijn regels de sync-wacht niet.
#
# Dezelfde projecten als de opruim-matrix in cleanup-preview.yml: die ruimt op wat deze klaarzet.
# test-preview-klaarzetten.sh bewaakt dat de twee gelijk blijven.
#
# Geen `deploy-preview-`-voorvoegsel: `uitrol-poort` telt op dat voorvoegsel de uitrol-jobs per
# as, en dit is er geen. De poort beoordeelt hem apart.
preview-klaarzetten:
# Zelfde reden als bij deploy-preview-uitvraag: de impliciete success() struikelt over
# het transitief overgeslagen `checks-fuzz` onder `gate`.
if: >-
!cancelled()
&& needs.meta.result == 'success'
&& needs.build.result == 'success'
&& needs.build-externe-stubs.result == 'success'
&& needs.build-demo-images.result == 'success'
&& needs.gate.result == 'success'
&& github.event_name == 'pull_request'
&& needs.changes.outputs.deploy == 'true'
# Ook na de builds, hoewel hier niets uitgerold wordt: herverwerkt iets anders het project
# voordat de deploy draait, dan rolt dat de klaargezette preview alsnog uit, en dan moeten de
# images er staan.
needs: [meta, build, build-externe-stubs, build-demo-images, gate, changes]
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
# Een falende leg laat de deploys hoe dan ook overslaan; de andere twee afmaken zet wel alle
# meldingen in één run.
fail-fast: false
matrix:
include:
- naam: uitvraag
project: mpfb-8wh
key: ZAD_API_KEY_UITVRAAG
richting: inbound
regelsleutel: CROSS_DOMAIN_REGELS_UITVRAAG
- naam: externe-stubs
project: mpfpsm-lcl
key: ZAD_API_KEY_PROFIEL
richting: inbound
regelsleutel: CROSS_DOMAIN_REGELS_EXTERNE_STUBS
- naam: magazijnen
project: mpfm-w3h
key: ZAD_API_KEY_MAGAZIJNEN
richting: outbound
regelsleutel: CROSS_DOMAIN_REGELS_MAGAZIJNEN
# Dezelfde groep als de deploy en de cleanup van dit project: Operations Manager vergrendelt op
# project, en van twee taken tegelijk eindigt er één als `superseded`.
concurrency:
group: zad-${{ matrix.naam }}-pr-${{ github.event.pull_request.number }}
cancel-in-progress: false
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Zet de deployment en zijn netwerkregels klaar
env:
ZAD_API_KEY: ${{ secrets[matrix.key] }}
PROJECT: ${{ matrix.project }}
DEPLOYMENT: pr-${{ github.event.pull_request.number }}
COMPONENTEN: ${{ needs.meta.outputs[format('componenten-{0}', matrix.naam)] }}
RICHTING: ${{ matrix.richting }}
REGELS: ${{ env[matrix.regelsleutel] }}
run: |
# Bewust ongequote: REGELS draagt meerdere regelnamen, gescheiden door spaties, en die
# moeten als losse argumenten aankomen. De waarde komt uit het env-blok bovenaan en is
# daarmee een vaste, spatie-vrije lijst.
# shellcheck disable=SC2086
.github/scripts/preview-klaarzetten.sh "$PROJECT" "$DEPLOYMENT" test "$COMPONENTEN" "$RICHTING" $REGELS
# PR-preview: één job PER project. Uitvraag en externe stubs draaien parallel, de magazijnen erna
# (het waarom staat bij die job); een fout in de ene slaat de andere niet af, dus alle fouten staan
# in één run. Config geërfd van `test`. wait-for-ready staat UIT: de
# zad-action doet geen k8s-readinessprobe maar curlt vanaf de runner ELKE component-URL op
# één health-endpoint (input `health-endpoint`, default '/'). De services bieden nu wél
# /q/health/ready (smallrye-health), maar deze deployments mengen niet-HTTP-componenten
# (redis) die op dat pad nooit een geaccepteerde status geven → de wachtlus zou timen out. Aanzetten
# kan pas met een per-component health-endpoint of een Quarkus-only deployment; tot dan 'false'.
deploy-preview-uitvraag:
# Expliciete statusfunctie i.p.v. de impliciete success(), om dezelfde reden als bij
# deploy-test-*: die kijkt transitief door naar de needs van `gate`, en `checks-fuzz` is
# 'skipped' zodra de PR niets fuzz-relevants raakt. Die overgeslagen grootouder in de
# needs-keten sloeg de previews over terwijl `gate` zelf groen was — een preview die stil
# uitblijft terwijl de required check als 'skipped' meetelt.
#
# GitHub documenteert de impliciete vorm als "alle directe needs geslaagd"; dat de evaluatie
# transitief doorwerkt is hier waargenomen, niet gedocumenteerd. Vereenvoudig dit dus niet
# terug naar `github.event_name == 'pull_request' && needs.changes.outputs.deploy == 'true'`,
# ook al lijkt dat gelijkwaardig.
if: >-
!cancelled()
&& needs.meta.result == 'success'
&& needs.build.result == 'success'
&& needs.build-externe-stubs.result == 'success'
&& needs.gate.result == 'success'
&& needs.preview-klaarzetten.result == 'success'
&& github.event_name == 'pull_request'
&& needs.changes.outputs.deploy == 'true'
needs: [meta, build, build-externe-stubs, gate, changes, preview-klaarzetten]
runs-on: ubuntu-latest
timeout-minutes: 20
# De URL's gaan naar `preview-afronding`, die de PR-comment samenstelt: één comment met de demo
# én deze uitvraag erin.
outputs:
urls: ${{ steps.deploy.outputs.urls }}
# Eén ZAD-taak tegelijk op deze deployment; een nieuwe push wacht op de lopende i.p.v. die af
# te breken — een halverwege afgebroken ZAD-deploy laat de deployment in een inconsistente
# staat achter. De cleanup-job in cleanup-preview.yml draagt voor hetzelfde project + dezelfde
# PR dezelfde groepsnaam (concurrency-groepen zijn repo-breed), zodat een PR-close een lopende
# deploy niet onder handen wegtrekt.
concurrency:
group: zad-uitvraag-pr-${{ github.event.pull_request.number }}
cancel-in-progress: false
permissions:
contents: read
# De GitHub-environment (één per PR) hangt aan déze job; de preview-URL komt uit de
# uitvraag-deploy (de frontend). De andere projecten dragen geen environment.
environment:
name: pr-${{ github.event.pull_request.number }}
url: ${{ steps.deploy.outputs.url }}
steps:
# De inbound-regels voor de console staan al in het projectbestand (`preview-klaarzetten`), dus
# deze uitrol rendert ze meteen mee.
- name: Deploy uitvraag-project
id: deploy
uses: RijksICTGilde/zad-actions/deploy@4ac541a57542a22bde95b28f3ace4ee3123acd7a # v4
with:
api-key: ${{ secrets.ZAD_API_KEY_UITVRAAG }}
project-id: ${{ env.PROJECT_UITVRAAG }}
deployment-name: pr-${{ github.event.pull_request.number }}
clone-from: test
wait-for-ready: 'false'
task-timeout: ${{ env.PREVIEW_TASK_TIMEOUT }}
components: ${{ needs.meta.outputs.componenten-uitvraag }}
deploy-preview-externe-stubs:
# Zelfde reden als bij deploy-preview-uitvraag: de impliciete success() struikelt over
# het transitief overgeslagen `checks-fuzz` onder `gate`.
if: >-
!cancelled()
&& needs.meta.result == 'success'
&& needs.build.result == 'success'
&& needs.build-externe-stubs.result == 'success'
&& needs.gate.result == 'success'
&& needs.preview-klaarzetten.result == 'success'
&& github.event_name == 'pull_request'
&& needs.changes.outputs.deploy == 'true'
needs: [meta, build, build-externe-stubs, gate, changes, preview-klaarzetten]
runs-on: ubuntu-latest
timeout-minutes: 20
concurrency:
group: zad-externe-stubs-pr-${{ github.event.pull_request.number }}
cancel-in-progress: false
permissions:
contents: read
steps:
# profiel + notificatie = losse componenten (eigen subdomein per stub); wat erin draait staat
# bij de componentenlijst in `meta`. De inbound-regels voor de Toxiproxy's staan al in het
# projectbestand (`preview-klaarzetten`), dus deze uitrol rendert ze meteen mee.
- name: Deploy externe-stubs-project
uses: RijksICTGilde/zad-actions/deploy@4ac541a57542a22bde95b28f3ace4ee3123acd7a # v4
with:
api-key: ${{ secrets.ZAD_API_KEY_PROFIEL }}
project-id: ${{ env.PROJECT_EXTERNE_STUBS }}
deployment-name: pr-${{ github.event.pull_request.number }}
clone-from: test
wait-for-ready: 'false'
task-timeout: ${{ env.PREVIEW_TASK_TIMEOUT }}
components: ${{ needs.meta.outputs.componenten-externe-stubs }}
deploy-preview-magazijnen:
# Zelfde reden als bij deploy-preview-uitvraag: de impliciete success() struikelt over
# het transitief overgeslagen `checks-fuzz` onder `gate`.
if: >-
!cancelled()
&& needs.meta.result == 'success'
&& needs.build.result == 'success'
&& needs.build-externe-stubs.result == 'success'
&& needs.gate.result == 'success'
&& needs.build-demo-images.result == 'success'
&& needs.preview-klaarzetten.result == 'success'
&& needs.deploy-preview-uitvraag.result == 'success'
&& needs.deploy-preview-externe-stubs.result == 'success'
&& github.event_name == 'pull_request'
&& needs.changes.outputs.deploy == 'true'
# Ná de twee andere preview-deploys. De console in dit project wordt pas gezond als hij bij de
# sessiecache en de Toxiproxy's in die projecten kan, en de inbound-policies daarvoor staan pas
# in het cluster als die deploys ze hebben uitgerold. Parallel zou het afhangen van wie er
# eerst is, en Operations Manager wacht 300 s op een gezonde sync voordat hij het opgeeft.
needs:
- meta
- build
- build-externe-stubs
- build-demo-images
- gate
- changes
- preview-klaarzetten
- deploy-preview-uitvraag
- deploy-preview-externe-stubs
runs-on: ubuntu-latest
timeout-minutes: 20
concurrency:
group: zad-magazijnen-pr-${{ github.event.pull_request.number }}
cancel-in-progress: false
permissions:
contents: read
# Naar `preview-afronding`, dat de comment samenstelt uit de URL's van deze deploy en die van
# de uitvraag.
outputs:
urls: ${{ steps.deploy.outputs.urls }}
steps:
- name: Deploy magazijnen-project
id: deploy
uses: RijksICTGilde/zad-actions/deploy@4ac541a57542a22bde95b28f3ace4ee3123acd7a # v4
with:
api-key: ${{ secrets.ZAD_API_KEY_MAGAZIJNEN }}
project-id: ${{ env.PROJECT_MAGAZIJNEN }}
deployment-name: pr-${{ github.event.pull_request.number }}
clone-from: test
wait-for-ready: 'false'
task-timeout: ${{ env.PREVIEW_TASK_TIMEOUT }}
components: ${{ needs.meta.outputs.componenten-magazijnen }}
# De comment met de preview-URL's, in een eigen job omdat hij de URL's van twee deploys nodig
# heeft.
#
# De job draagt geen `deploy-preview-`-voorvoegsel: `uitrol-poort` telt op dat voorvoegsel de
# uitrol-jobs per as, en dit is er geen. De poort beoordeelt hem apart.
preview-afronding:
# Zelfde reden als bij deploy-preview-uitvraag: de impliciete success() struikelt over
# het transitief overgeslagen `checks-fuzz` onder `gate`.
if: >-
!cancelled()
&& needs.deploy-preview-uitvraag.result == 'success'
&& needs.deploy-preview-externe-stubs.result == 'success'
&& needs.deploy-preview-magazijnen.result == 'success'
&& github.event_name == 'pull_request'
&& needs.changes.outputs.deploy == 'true'
needs:
- changes
- deploy-preview-uitvraag
- deploy-preview-externe-stubs
- deploy-preview-magazijnen
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
# Voor de preview-comment onderaan deze job.
pull-requests: write
steps:
# Voor preview-comment.sh uit .github/scripts/.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# De comment met de preview-URL's staat met de demo vooraan: dit project draagt de
# bedieningsconsole, de berichtenbox en de magazijnen, en dat is voor een lezer van de PR de
# ingang. Deze job draait ná alle drie de deploys, dus pas hier zijn de URL's van beide
# kanten bekend.
#
# Het derde project (profiel, notificatie en hun twee Toxiproxy's) blijft eruit: daar zit geen
# scherm dat je opent, alleen stubs waar de demo doorheen praat. De grens loopt per project en
# niet per component — binnen een project gaat de hele `urls`-map mee.
#
# Twee gevolgen van het samenstellen in een eigen job, allebei aanvaard. Faalt een van de drie
# preview-deploys, dan wordt deze job overgeslagen en komt er géén comment, ook niet met de
# URL's die er wél zijn — maar dan is de preview zelf incompleet en is de run rood. En faalt
# deze stap, dan is de job rood en blokkeert `uitrol-poort` de merge terwijl de preview
# draait; dat is de bedoelde prijs voor "een uitgebleven comment mag niet stil zijn".
- name: Plaats de preview-URL's op de PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
COMMENT_HEADER: ${{ env.COMMENT_HEADER }}
PR_NUMMER: ${{ github.event.pull_request.number }}
URLS_DEMO: ${{ needs.deploy-preview-magazijnen.outputs.urls }}
URLS_UITVRAAG: ${{ needs.deploy-preview-uitvraag.outputs.urls }}
run: >-
.github/scripts/preview-comment.sh "$PR_NUMMER"
"Demo=$URLS_DEMO"
"Berichtenuitvraag=$URLS_UITVRAAG"
# Push naar main → per project de `test`-deployment (de baseline waarvan previews klonen;
# env/secrets configureer je daar in Operations Manager). Per project een eigen parallelle job.
deploy-test-uitvraag:
# Expliciete statusfunctie i.p.v. de impliciete success(): die kijkt niet alleen naar de vier
# directe needs hieronder, maar transitief door naar hún needs — en `checks-fuzz` (PR-only) is
# bij een push naar main altijd 'skipped', wat de impliciete success() over de hele keten laat
# falen. `gate` ontsnapt daar zelf al aan met `!cancelled()`, maar dat beschermt alleen `gate`
# zelf, niet de jobs die van `gate` afhangen. Vandaar hier dezelfde `!cancelled()`-vorm, plus
# expliciete per-need result-checks zodat een échte build-/gate-fout deze job wél overslaat.
if: >-
!cancelled()
&& needs.meta.result == 'success'
&& needs.build.result == 'success'
&& needs.build-externe-stubs.result == 'success'
&& needs.build-contract-bootstrap.result == 'success'
&& needs.gate.result == 'success'
&& github.event_name == 'push'
&& github.ref == 'refs/heads/main'
needs: [meta, build, build-externe-stubs, build-contract-bootstrap, gate]
runs-on: ubuntu-latest
timeout-minutes: 20
# Eén ZAD-taak tegelijk op de `test`-deployment van dit project; twee snel opeenvolgende merges
# naar main mogen elkaars deploy niet halverwege afbreken.
concurrency:
group: zad-uitvraag-test
cancel-in-progress: false
permissions:
contents: read
environment:
name: test
steps:
- name: Deploy uitvraag-project (test)
uses: RijksICTGilde/zad-actions/deploy@4ac541a57542a22bde95b28f3ace4ee3123acd7a # v4
with:
api-key: ${{ secrets.ZAD_API_KEY_UITVRAAG }}
project-id: ${{ env.PROJECT_UITVRAAG }}
deployment-name: test
components: |
[
{"name": "redis", "image": "${{ env.REDIS_IMAGE }}"},
{"name": "toxiproxy-aanmeld", "image": "${{ env.TOXIPROXY_IMAGE }}"},
{"name": "toxiproxy-redis", "image": "${{ env.TOXIPROXY_IMAGE }}"},
{"name": "uitvraag", "image": "${{ env.REGISTRY }}/${{ needs.meta.outputs.owner }}/fbs-berichtenuitvraag:${{ needs.meta.outputs.tag }}"}
]
# De FSC-peer draait in een EIGEN deployment `fsc-logius` binnen hetzelfde project, niet in
# `test`. Previews klonen met `clone-from: test`; een gekloonde peer zou zich met dezelfde
# federatie-OIN opnieuw aanmelden bij de gedeelde directory (en zonder de UI-only
# cert-attachments direct crashloopen). Wat niet in `test` staat, kan niet meegekloond worden.
# Deze stap doet alleen tag-updates; de eenmalige creatie (env/ports) loopt via
# demo/environment/logius/deploy/zad/upsert-peer.sh.
- name: Deploy uitvraag-project (fsc-logius — FSC-peer)
uses: RijksICTGilde/zad-actions/deploy@4ac541a57542a22bde95b28f3ace4ee3123acd7a # v4
with:
api-key: ${{ secrets.ZAD_API_KEY_UITVRAAG }}
project-id: ${{ env.PROJECT_UITVRAAG }}
deployment-name: fsc-logius
components: |
[
{"name": "logius-fscpg", "image": "docker.io/library/postgres:17"},
{"name": "logius-fscmgr", "image": "ghcr.io/minbzk/moza-fsc-testnet-manager-migrate:v2.5.2"},
{"name": "logius-fscctl", "image": "ghcr.io/minbzk/moza-fsc-testnet-controller-migrate:v2.5.2"},
{"name": "logius-fscoutway", "image": "docker.io/federatedserviceconnectivity/outway:v2.5.2"},
{"name": "logius-fscinway", "image": "docker.io/federatedserviceconnectivity/inway:v2.5.2"},
{"name": "logius-fsctxlog", "image": "ghcr.io/minbzk/moza-fsc-testnet-txlog-migrate:v2.5.2"},
{"name": "logius-fscbootstrap", "image": "${{ env.REGISTRY }}/${{ needs.meta.outputs.owner }}/fbs-fsc-contract-bootstrap:${{ needs.meta.outputs.tag }}"}
]
deploy-test-externe-stubs:
# Zelfde reden als deploy-test-uitvraag hierboven: impliciete success() over de needs-keten
# wordt vergiftigd door het op push altijd-geskipte `checks-fuzz`, ook al is `gate` zelf groen.
if: >-
!cancelled()
&& needs.meta.result == 'success'
&& needs.build.result == 'success'
&& needs.build-externe-stubs.result == 'success'
&& needs.gate.result == 'success'
&& github.event_name == 'push'
&& github.ref == 'refs/heads/main'
needs: [meta, build, build-externe-stubs, gate]
runs-on: ubuntu-latest
timeout-minutes: 20
concurrency:
group: zad-externe-stubs-test
cancel-in-progress: false
permissions:
contents: read
steps:
- name: Deploy externe-stubs-project (test)
uses: RijksICTGilde/zad-actions/deploy@4ac541a57542a22bde95b28f3ace4ee3123acd7a # v4
with:
api-key: ${{ secrets.ZAD_API_KEY_PROFIEL }}
project-id: ${{ env.PROJECT_EXTERNE_STUBS }}
deployment-name: test
components: |
[
{"name": "profiel", "image": "${{ env.REGISTRY }}/${{ needs.meta.outputs.owner }}/fbs-demo-profiel:${{ needs.meta.outputs.tag }}"},
{"name": "notificatie", "image": "${{ env.REGISTRY }}/${{ needs.meta.outputs.owner }}/fbs-externe-stubs:${{ needs.meta.outputs.tag }}"},
{"name": "toxiproxy-profiel", "image": "${{ env.TOXIPROXY_IMAGE }}"},
{"name": "toxiproxy-notificatie", "image": "${{ env.TOXIPROXY_IMAGE }}"}
]
deploy-test-magazijnen:
# Zelfde reden als deploy-test-uitvraag hierboven: impliciete success() over de needs-keten
# wordt vergiftigd door het op push altijd-geskipte `checks-fuzz`, ook al is `gate` zelf groen.
if: >-
!cancelled()
&& needs.meta.result == 'success'
&& needs.build.result == 'success'
&& needs.build-externe-stubs.result == 'success'
&& needs.build-contract-bootstrap.result == 'success'
&& needs.build-demo-images.result == 'success'
&& needs.gate.result == 'success'
&& github.event_name == 'push'
&& github.ref == 'refs/heads/main'
needs: [meta, build, build-externe-stubs, build-contract-bootstrap, build-demo-images, gate]
runs-on: ubuntu-latest
timeout-minutes: 20
# Dekt ook de tweede stap (deployment `fsc-magazijna`) — die hoort bij hetzelfde project.
concurrency:
group: zad-magazijnen-test
cancel-in-progress: false
permissions:
contents: read
steps:
- name: Deploy magazijnen-project (test)
uses: RijksICTGilde/zad-actions/deploy@4ac541a57542a22bde95b28f3ace4ee3123acd7a # v4
with:
api-key: ${{ secrets.ZAD_API_KEY_MAGAZIJNEN }}
project-id: ${{ env.PROJECT_MAGAZIJNEN }}
deployment-name: test
components: |
[
{"name": "magazijna", "image": "${{ env.REGISTRY }}/${{ needs.meta.outputs.owner }}/fbs-berichtenmagazijn:${{ needs.meta.outputs.tag }}"},
{"name": "magazijnb", "image": "${{ env.REGISTRY }}/${{ needs.meta.outputs.owner }}/fbs-berichtenmagazijn:${{ needs.meta.outputs.tag }}"},
{"name": "democonsole", "image": "${{ env.REGISTRY }}/${{ needs.meta.outputs.owner }}/fbs-demo-console:${{ needs.meta.outputs.tag }}"},
{"name": "magazijnsimulator", "image": "${{ env.REGISTRY }}/${{ needs.meta.outputs.owner }}/fbs-magazijn-simulator:${{ needs.meta.outputs.tag }}"},
{"name": "demopersonas", "image": "${{ env.REGISTRY }}/${{ needs.meta.outputs.owner }}/fbs-demo-personas:${{ needs.meta.outputs.tag }}"},
{"name": "proeftuin", "image": "${{ needs.meta.outputs.proeftuin_image }}"}
]
# De FSC-peer draait in een EIGEN deployment `fsc-magazijna` binnen hetzelfde project, niet in
# `test`. Previews klonen met `clone-from: test`; een gekloonde peer zou zich met dezelfde
# federatie-OIN opnieuw aanmelden bij de gedeelde directory (en zonder de UI-only
# cert-attachments direct crashloopen). Wat niet in `test` staat, kan niet meegekloond worden.
# Deze stap doet alleen tag-updates; de eenmalige creatie (env/ports) loopt via
# demo/environment/magazijn-a/deploy/zad/upsert-peer.sh.
- name: Deploy magazijnen-project (fsc-magazijna — FSC-peer)
uses: RijksICTGilde/zad-actions/deploy@4ac541a57542a22bde95b28f3ace4ee3123acd7a # v4
with:
api-key: ${{ secrets.ZAD_API_KEY_MAGAZIJNEN }}
project-id: ${{ env.PROJECT_MAGAZIJNEN }}
deployment-name: fsc-magazijna
components: |
[
{"name": "magazijna-fscpg", "image": "docker.io/library/postgres:17"},
{"name": "magazijna-fscmgr", "image": "ghcr.io/minbzk/moza-fsc-testnet-manager-migrate:v2.5.2"},
{"name": "magazijna-fscctl", "image": "ghcr.io/minbzk/moza-fsc-testnet-controller-migrate:v2.5.2"},
{"name": "magazijna-fscinway", "image": "docker.io/federatedserviceconnectivity/inway:v2.5.2"},
{"name": "magazijna-fsctxlog", "image": "ghcr.io/minbzk/moza-fsc-testnet-txlog-migrate:v2.5.2"},
{"name": "magazijna-fscbootstrap", "image": "${{ env.REGISTRY }}/${{ needs.meta.outputs.owner }}/fbs-fsc-contract-bootstrap:${{ needs.meta.outputs.tag }}"}
]
# De check die niet stil kan overslaan — bedoeld als required context op main, naast (niet in
# plaats van) de bestaande. `gate` en de uitrol-jobs mogen zichzelf legitiem overslaan; deze job
# bewijst dat een uitgebleven uitrol een besluit van de detectie was en geen storing.
#
# Het oordeel staat in .github/scripts/uitrol-poort.sh, met unittests ernaast. Die draaien via
# ci-scripts.yml óók op een gestapelde PR, waar deze workflow niet komt — als inline `run:`-blok
# zou de logica hier pas ná de merge naar main voor het eerst uitgevoerd worden.
uitrol-poort:
needs:
- changes
- gate
- build
- build-externe-stubs
- build-contract-bootstrap
- build-demo-images
- deploy-preview-uitvraag
- deploy-preview-externe-stubs
- deploy-preview-magazijnen
- preview-klaarzetten
- preview-afronding
- deploy-test-uitvraag
- deploy-test-externe-stubs
- deploy-test-magazijnen
# `always()` i.p.v. de impliciete success(), die deze job zou overslaan zodra een need faalt —
# precies wat hij moet beoordelen. Ook niet `!cancelled()`: `cancelled()` is run-breed, dus
# bij een handmatig afgebroken run zou deze check op 'skipped' (= succes) komen te staan en
# daarmee certificeren dat er is uitgerold terwijl er niets gedraaid heeft. Prijs: zo'n
# afbreking start hier alsnog een runner.
if: ${{ always() }}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
env:
# `cancelled()` mag alleen in een `if:` staan, niet in een env-expressie; de stap hieronder
# zet deze waarde om zodra de run is afgebroken.
CANCELLED: 'false'
steps:
# De stappen dragen hun eigen `always()`: zonder die conditie draait een stap niet meer zodra
# de run is afgebroken, en dan blijft alleen de stap hieronder over — de job eindigt groen en
# certificeert een uitrol die nooit gedraaid heeft.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
if: ${{ always() }}
with:
persist-credentials: false
sparse-checkout: .github/scripts
# Een annulering is niet uit de job-resultaten af te leiden: `gate` en de uitrol-jobs dragen
# zelf `!cancelled()`, dus bij een afbreking vóór hun start rapporteren ze 'skipped' — niet
# te onderscheiden van de legitieme "niets uit te rollen"-uitkomst.
- name: Merk een afgebroken run
if: ${{ cancelled() }}
run: echo "CANCELLED=true" >> "$GITHUB_ENV"
- name: Beoordeel de uitrol
if: ${{ always() }}
env:
EVENT: ${{ github.event_name }}
REF: ${{ github.ref }}
CHANGES: ${{ needs.changes.result }}
DEPLOY: ${{ needs.changes.outputs.deploy }}
GATE: ${{ needs.gate.result }}
# De bouw- en uitrol-resultaten komen uit het needs-object zelf in plaats van uit negen
# overgetypte expressies: dat is één plek die synchroon moet blijven met de needs-lijst
# hierboven. Dat die lijst zélf compleet is, bewaakt de kruiscontrole in
# test-uitrol-poort.sh.
NEEDS: ${{ toJSON(needs) }}
run: .github/scripts/uitrol-poort.sh