feat(infra): subscribe the projection to the objecten kanaal (refs #153)
Completes the re-source (ADR-0030): the Event Subscriber's abonnement moves from `zaken` to `objecten`, in both the local stack's `nrc-subscribe` and the CI projection check. The OpenZaak → NRC check keeps its own `zaken` abonnement — OpenZaak still publishes, nothing in the product listens. - register-abonnement.py subscribes to `objecten`, and now treats the kanaal as part of "already current" — an abonnement left from before this slice points at the right callback but the wrong kanaal, and would never have been replaced on IP alone. - run-projection-check.sh opens its zaak *through the ACL* instead of straight against OpenZaak, because the ACL is what writes the register record the projection is now derived from. A zaak created behind the ACL's back produces no row — which is the re-source working. - The acceptance scenario is restated in register terms and gains the approval case: the same row moving INGEDIEND → INGESCHREVEN is now one registration's record being updated, not two unrelated ZGW events.
This commit is contained in:
@@ -2,10 +2,10 @@
|
||||
"""Local-stack bootstrap (S-B04, #110, ADR-0020) — register the NRC abonnement.
|
||||
|
||||
Runs as the `nrc-subscribe` init container of infra/docker-compose.local.yml. Registers an
|
||||
abonnement on the `zaken` kanaal pointing at the event-subscriber's /notifications callback, so
|
||||
OpenZaak's notifications (zaak create + status set) reach the projection — without this the openbaar
|
||||
(public) register stays empty. This is what infra/verify-notification-driver.py does for CI (minus
|
||||
the test zaak it also creates).
|
||||
abonnement on the `objecten` kanaal pointing at the event-subscriber's /notifications callback, so
|
||||
the register writes the ACL makes (INGEDIEND on submit, INGESCHREVEN on approval) reach the
|
||||
projection — without this the openbaar (public) register stays empty. Since S-19b-2 the projection
|
||||
is sourced from the register in Objecten, not from ZGW zaak events (ADR-0030).
|
||||
|
||||
The callback host is the event-subscriber's resolved **container IP**, not `event-subscriber`, because
|
||||
NRC validates callbackUrl with Django's URLValidator (a single-label host is rejected — same reason the
|
||||
@@ -22,6 +22,8 @@ SINK_PORT = os.environ.get("SINK_PORT", "8080")
|
||||
SINK_AUTH = os.environ.get("SINK_AUTH", "Bearer big-reference-notifications")
|
||||
CID = os.environ.get("OZ_CLIENT_ID", "big-reference-seed")
|
||||
SECRET = os.environ.get("OZ_SECRET", "insecure-dev-secret-change-me")
|
||||
# The projection is sourced from the register in Objecten, not from ZGW zaak events (S-19b-2).
|
||||
KANAAL = "objecten"
|
||||
|
||||
|
||||
def token():
|
||||
@@ -60,7 +62,10 @@ def main():
|
||||
status, body = call("GET", f"{NRC}/api/v1/abonnement")
|
||||
for ab in (body or []) if status == 200 else []:
|
||||
if str(ab.get("callbackUrl", "")).endswith("/notifications"):
|
||||
if ab.get("callbackUrl") == callback:
|
||||
# The kanaal is part of "current": an abonnement left over from before S-19b-2 points at
|
||||
# the right callback but listens on `zaken`, and would never be replaced on IP alone.
|
||||
kanalen = [k.get("naam") for k in ab.get("kanalen", [])]
|
||||
if ab.get("callbackUrl") == callback and kanalen == [KANAAL]:
|
||||
print(f"abonnement already current: {ab['url']}")
|
||||
return
|
||||
call("DELETE", ab["url"])
|
||||
@@ -68,7 +73,7 @@ def main():
|
||||
|
||||
status, ab = call("POST", f"{NRC}/api/v1/abonnement", {
|
||||
"callbackUrl": callback, "auth": SINK_AUTH,
|
||||
"kanalen": [{"naam": "zaken", "filters": {}}]})
|
||||
"kanalen": [{"naam": KANAAL, "filters": {}}]})
|
||||
if status != 201:
|
||||
sys.exit(f"create abonnement -> {status}: {json.dumps(ab)}")
|
||||
print(f"abonnement registered: {ab['url']} -> {callback}")
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Verify the end-to-end read-projection path (S-06) against an ALREADY-RUNNING full stack:
|
||||
# OpenZaak → NRC → Event Subscriber → projection → projection-api. Seeds a published BIG
|
||||
# zaaktype (idempotent), registers an abonnement on the `zaken` kanaal pointing at the real
|
||||
# Event Subscriber's /notifications callback (with the bearer it enforces), creates a zaak,
|
||||
# and asserts projection-api serves a row for that zaak with status INGEDIEND.
|
||||
# Verify the end-to-end read-projection path (S-06, re-sourced by S-19b-2) against an ALREADY-RUNNING
|
||||
# full stack: ACL → Objecten → NRC → Event Subscriber → projection → projection-api. Seeds a
|
||||
# published BIG zaaktype (idempotent), registers an abonnement on the `objecten` kanaal pointing at
|
||||
# the real Event Subscriber's /notifications callback (with the bearer it enforces), opens a zaak
|
||||
# *through the ACL*, and asserts projection-api serves a row for it with status INGEDIEND.
|
||||
#
|
||||
# The zaak is opened through the ACL, not straight against OpenZaak: since ADR-0030 the projection is
|
||||
# derived from the RegisterRecord in Objecten, and the ACL is what writes that record (INGEDIEND on
|
||||
# submit). A zaak created behind the ACL's back produces no register write and so no projection row —
|
||||
# which is the point of the re-source.
|
||||
#
|
||||
# All in-network, reaching services by container IP — single-label hosts aren't URL-valid and
|
||||
# the runner can't reach published ports (gitea-actions-gotchas.md §5/§6). Reuses the
|
||||
# notification driver to register the abonnement + create the zaak. Does NOT manage the stack
|
||||
# lifecycle (the caller owns bring-up + teardown). Plain docker primitives only. See ADR-0007/0008.
|
||||
# the runner can't reach published ports (gitea-actions-gotchas.md §5/§6). Does NOT manage the stack
|
||||
# lifecycle (the caller owns bring-up + teardown). Plain docker primitives only. See ADR-0007/0008/0030.
|
||||
set -euo pipefail
|
||||
|
||||
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -24,11 +28,13 @@ oz="$(docker ps -q --filter 'name=[-_]openzaak[-_]' | head -1)"
|
||||
nrc="$(docker ps -q --filter 'name=nrc-web' | head -1)"
|
||||
es="$(docker ps -q --filter 'name=event-subscriber' | head -1)"
|
||||
proj="$(docker ps -q --filter 'name=projection-api' | head -1)"
|
||||
acl="$(docker ps -q --filter 'name=[-_]acl[-_]' | head -1)"
|
||||
[ -n "$oz" ] && [ -n "$nrc" ] || { echo "ERROR: OpenZaak and/or NRC not running — bring the stack up first" >&2; exit 1; }
|
||||
[ -n "$es" ] && [ -n "$proj" ] || { echo "ERROR: event-subscriber and/or projection-api not running — bring the stack up first" >&2; exit 1; }
|
||||
[ -n "$acl" ] || { echo "ERROR: acl not running — bring the stack up first" >&2; exit 1; }
|
||||
net="$(docker inspect -f '{{range $k,$_ := .NetworkSettings.Networks}}{{$k}}{{"\n"}}{{end}}' "$oz" | head -1)"
|
||||
oz_ip="$(ip "$oz")"; nrc_ip="$(ip "$nrc")"; es_ip="$(ip "$es")"; proj_ip="$(ip "$proj")"
|
||||
echo ">> network=$net openzaak=$oz_ip nrc=$nrc_ip event-subscriber=$es_ip projection-api=$proj_ip"
|
||||
oz_ip="$(ip "$oz")"; nrc_ip="$(ip "$nrc")"; es_ip="$(ip "$es")"; proj_ip="$(ip "$proj")"; acl_ip="$(ip "$acl")"
|
||||
echo ">> network=$net openzaak=$oz_ip nrc=$nrc_ip event-subscriber=$es_ip projection-api=$proj_ip acl=$acl_ip"
|
||||
|
||||
echo ">> seeding a published BIG zaaktype (idempotent)"
|
||||
sid="$(docker create --network "$net" -e "OZ_BASE=http://$oz_ip:8000" -e OZ_PUBLISH=1 \
|
||||
@@ -37,19 +43,26 @@ docker cp "$here/openzaak/seed_catalogus.py" "$sid:/seed.py" >/dev/null
|
||||
docker start -a "$sid"
|
||||
docker rm -f "$sid" >/dev/null
|
||||
|
||||
echo ">> registering abonnement at the Event Subscriber + creating a zaak"
|
||||
echo ">> registering the event-subscriber abonnement on the objecten kanaal"
|
||||
docker rm -f rr-pverify >/dev/null 2>&1 || true
|
||||
# The same script the local stack uses (ADR-0020), so both paths register the identical abonnement.
|
||||
drv="$(docker create --network "$net" --name rr-pverify \
|
||||
-e "OZ_BASE=http://$oz_ip:8000" -e "NRC_BASE=http://$nrc_ip:8000" \
|
||||
-e "SINK_CALLBACK=http://$es_ip:8080/notifications" -e "SINK_AUTH=$WEBHOOK_AUTH" \
|
||||
python:3-slim python /driver.py)"
|
||||
docker cp "$here/verify-notification-driver.py" "$drv:/driver.py" >/dev/null
|
||||
-e "NRC_BASE=http://$nrc_ip:8000" \
|
||||
-e "SINK_HOST=$es_ip" -e "SINK_PORT=8080" -e "SINK_AUTH=$WEBHOOK_AUTH" \
|
||||
python:3-slim python /subscribe.py)"
|
||||
docker cp "$here/local/register-abonnement.py" "$drv:/subscribe.py" >/dev/null
|
||||
docker start -a "$drv"
|
||||
zaak_url="$(docker logs rr-pverify 2>/dev/null | sed -n 's/^ZAAK_CREATED //p' | head -1)"
|
||||
docker rm -f rr-pverify >/dev/null
|
||||
[ -n "$zaak_url" ] || { echo "ERROR: driver did not create a zaak" >&2; exit 1; }
|
||||
|
||||
echo ">> opening a zaak through the ACL (which writes the INGEDIEND register record)"
|
||||
reference="PROJ-$(date +%s)"
|
||||
zaak_url="$(docker run --rm --network "$net" curlimages/curl:latest \
|
||||
-fsS -X POST "http://$acl_ip:8080/zaken" -H 'Content-Type: application/json' \
|
||||
-d "{\"bsn\":\"123456782\",\"reference\":\"$reference\"}" \
|
||||
| sed -n 's/.*"zaakUrl":"\([^"]*\)".*/\1/p')"
|
||||
[ -n "$zaak_url" ] || { echo "ERROR: the ACL did not open a zaak" >&2; exit 1; }
|
||||
zaak_uuid="${zaak_url##*/}"
|
||||
echo ">> zaak created: $zaak_url"
|
||||
echo ">> zaak created: $zaak_url (reference $reference)"
|
||||
|
||||
echo ">> polling projection-api for the projected row (status INGEDIEND)"
|
||||
for _ in $(seq 1 30); do
|
||||
@@ -63,6 +76,8 @@ for _ in $(seq 1 30); do
|
||||
sleep 2
|
||||
done
|
||||
echo "FAIL — projection-api never served an INGEDIEND row for zaak $zaak_uuid" >&2
|
||||
echo " The chain is ACL → Objecten → NRC → event-subscriber → projection (ADR-0030)." >&2
|
||||
echo "--- event-subscriber log ---" >&2; docker logs "$es" 2>&1 | tail -10 >&2
|
||||
echo "--- projection-api log ---" >&2; docker logs "$proj" 2>&1 | tail -10 >&2
|
||||
echo "--- acl log ---" >&2; docker logs "$acl" 2>&1 | tail -10 >&2
|
||||
exit 1
|
||||
|
||||
Reference in New Issue
Block a user