Compare commits
2 Commits
feat/2-cat
...
feat/10-op
| Author | SHA1 | Date | |
|---|---|---|---|
| e1883c2d0e | |||
| ae2169b6ee |
9
Makefile
9
Makefile
@@ -21,7 +21,7 @@ export DOCKER_HOST := unix://$(PODMAN_SOCK)
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: ci lint build unit smoke down changelog openzaak-up openzaak-smoke openzaak-seed openzaak-down help
|
.PHONY: ci lint build unit smoke down changelog openzaak-up openzaak-smoke openzaak-down help
|
||||||
|
|
||||||
## ci: run the full pipeline — lint, build, unit, smoke (mirrors Gitea Actions)
|
## ci: run the full pipeline — lint, build, unit, smoke (mirrors Gitea Actions)
|
||||||
ci: lint build unit smoke
|
ci: lint build unit smoke
|
||||||
@@ -71,13 +71,6 @@ openzaak-smoke:
|
|||||||
echo "GET /zaken/api/v1/ -> $$root (expect 200)"; test "$$root" = "200"; \
|
echo "GET /zaken/api/v1/ -> $$root (expect 200)"; test "$$root" = "200"; \
|
||||||
echo "OpenZaak smoke OK"'
|
echo "OpenZaak smoke OK"'
|
||||||
|
|
||||||
## openzaak-seed: bring OpenZaak up and seed the BIG catalogus (idempotent)
|
|
||||||
openzaak-seed: openzaak-up
|
|
||||||
@bash -c 'for i in $$(seq 1 50); do \
|
|
||||||
c=$$(curl -s -o /dev/null -w "%{http_code}" $(OZ_BASE)/catalogi/api/v1/ || true); \
|
|
||||||
[ "$$c" = "200" ] && break; sleep 3; done; echo "OpenZaak ready ($$c)"'
|
|
||||||
python3 infra/openzaak/seed_catalogus.py
|
|
||||||
|
|
||||||
## openzaak-down: stop and remove the OpenZaak stack (wipes data)
|
## openzaak-down: stop and remove the OpenZaak stack (wipes data)
|
||||||
openzaak-down:
|
openzaak-down:
|
||||||
docker compose -f $(OZ_COMPOSE) down --volumes
|
docker compose -f $(OZ_COMPOSE) down --volumes
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
# ADR-0002: BIG catalogus design and OpenZaak seeding
|
|
||||||
|
|
||||||
- **Status:** Accepted
|
|
||||||
- **Date:** 2026-06-03
|
|
||||||
- **Deciders:** Respellion engineering
|
|
||||||
- **Relates to:** S-01 (#2)
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
S-01 needs a reproducible `BIG` catalogus in OpenZaak with a **lean** `BIG-registratie`
|
|
||||||
zaaktype (only schema-mandatory fields) plus a `bsn` eigenschap, and a JWT client that
|
|
||||||
can list zaaktypen. We had to decide *how* to provision this idempotently at startup.
|
|
||||||
|
|
||||||
Findings from the OpenZaak image (`openzaak/open-zaak:latest`):
|
|
||||||
- `setup_configuration` (run by the init container) is declarative and idempotent, with
|
|
||||||
steps for **JWT secrets** and **applicaties** (`vng_api_common_credentials`,
|
|
||||||
`vng_api_common_applicaties`) — but **no step for catalogi/zaaktypen**.
|
|
||||||
- Catalogus/zaaktype/eigenschap can only be created through the **ZTC REST API**.
|
|
||||||
- Publishing a zaaktype requires ≥1 roltype, ≥1 resultaattype and ≥2 statustypen.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
1. **Provision the JWT client declaratively** via `infra/openzaak/setup_configuration/data.yaml`:
|
|
||||||
a `JWTSecret` (`big-reference-seed` / dev secret) and an `Applicatie` with
|
|
||||||
`heeft_alle_autorisaties: true`. Idempotent, runs in the init container.
|
|
||||||
2. **Seed the catalogus/zaaktype/eigenschap via the ZTC API** with an idempotent,
|
|
||||||
stdlib-only script (`infra/openzaak/seed_catalogus.py`, `make openzaak-seed`). It mints
|
|
||||||
a ZGW JWT from the provisioned client and matches existing objects (by `domein` /
|
|
||||||
`identificatie` / `naam`, querying `status=alles` so concepts are seen) before creating.
|
|
||||||
3. **Keep the zaaktype a CONCEPT (not published).** Publishing pulls in roltypen,
|
|
||||||
statustypen and resultaattypen, which go beyond "schema-mandatory"; those arrive with
|
|
||||||
the workflow/zaak slices that actually need a published type. Listing uses `status=alles`.
|
|
||||||
4. **Disable outbound notifications** (`NOTIFICATIONS_DISABLED=true`) until Open Notificaties
|
|
||||||
(NRC) lands in S-01-c — otherwise every ZTC write 500s trying to notify.
|
|
||||||
5. **Fixed dev values:** RSIN `517439943` (elfproef-valid test value); the JWT secret is
|
|
||||||
dev-only and documented as such.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
- **Reproducible & version-robust:** the API-driven seed doesn't depend on fixture PKs or
|
|
||||||
a catalogi `setup_configuration` step that may change between versions.
|
|
||||||
- **Teaches the pattern:** the seed talks to OpenZaak exactly the way the ACL will later —
|
|
||||||
through the documented ZGW API, with a JWT (ADR-0001).
|
|
||||||
- The seed is a script, but a **data loader is explicitly anticipated** (PRD §8); it lives
|
|
||||||
under `infra/openzaak/`, not as ad-hoc tooling.
|
|
||||||
- **Follow-ups:** re-enable notifications when NRC is up (S-01-c); publish the zaaktype (add
|
|
||||||
the related types) when a slice needs to create real zaken; pin the OpenZaak image tag.
|
|
||||||
|
|
||||||
## Alternatives considered
|
|
||||||
|
|
||||||
- **Fully declarative in `data.yaml`** — rejected: no catalogi/zaaktype step exists.
|
|
||||||
- **Django `loaddata` fixture** — rejected: brittle, tied to model PKs and the exact image
|
|
||||||
version; bypasses the API the rest of the system uses.
|
|
||||||
@@ -9,32 +9,9 @@ migrations, the OpenZaak API, and a celery worker.
|
|||||||
```bash
|
```bash
|
||||||
make openzaak-up # start the stack (first run pulls images + migrates: 1-3 min)
|
make openzaak-up # start the stack (first run pulls images + migrates: 1-3 min)
|
||||||
make openzaak-smoke # start + assert it's up with auth enforced (403/302/200)
|
make openzaak-smoke # start + assert it's up with auth enforced (403/302/200)
|
||||||
make openzaak-seed # start + seed the BIG catalogus (idempotent)
|
|
||||||
make openzaak-down # stop and wipe data
|
make openzaak-down # stop and wipe data
|
||||||
```
|
```
|
||||||
|
|
||||||
## Seed the BIG catalogus
|
|
||||||
|
|
||||||
`make openzaak-seed` brings the stack up and runs `infra/openzaak/seed_catalogus.py`,
|
|
||||||
which creates (idempotently, via the ZTC API):
|
|
||||||
|
|
||||||
- catalogus **BIG**
|
|
||||||
- a lean **BIG-REGISTRATIE** zaaktype (concept; only schema-mandatory fields)
|
|
||||||
- a **bsn** eigenschap on it
|
|
||||||
|
|
||||||
then confirms the JWT client can list it. See **ADR-0002** for the design (why the
|
|
||||||
zaaktype stays a concept, why notifications are disabled, why the API not a fixture).
|
|
||||||
|
|
||||||
**JWT client** (provisioned declaratively by `setup_configuration/data.yaml`, **dev only**):
|
|
||||||
|
|
||||||
| | |
|
|
||||||
|---|---|
|
|
||||||
| client_id | `big-reference-seed` |
|
|
||||||
| secret | `insecure-dev-secret-change-me` |
|
|
||||||
| authorizations | `heeft_alle_autorisaties` (all) |
|
|
||||||
|
|
||||||
The seed mints a ZGW JWT (HS256) from these and calls `/catalogi/api/v1/...`.
|
|
||||||
|
|
||||||
`make openzaak-smoke` polls until the API responds, then asserts:
|
`make openzaak-smoke` polls until the API responds, then asserts:
|
||||||
|
|
||||||
| Check | Expected |
|
| Check | Expected |
|
||||||
@@ -66,9 +43,8 @@ The Makefile auto-points `DOCKER_HOST` at the Podman socket when it exists, so t
|
|||||||
- **Not in `make ci`.** The OpenZaak smoke is a separate, heavier check (large image
|
- **Not in `make ci`.** The OpenZaak smoke is a separate, heavier check (large image
|
||||||
pull + migrations); it is intentionally kept out of `make ci` so the core gate
|
pull + migrations); it is intentionally kept out of `make ci` so the core gate
|
||||||
stays fast. Run `make openzaak-smoke` when you touch the OpenZaak stack.
|
stays fast. Run `make openzaak-smoke` when you touch the OpenZaak stack.
|
||||||
- **Notifications disabled.** `NOTIFICATIONS_DISABLED=true` until Open Notificaties
|
- **No catalogus/zaaktypen yet.** Seeding the `BIG` catalogus + `BIG-registratie`
|
||||||
(NRC) lands in S-01-c — otherwise ZTC writes 500 trying to notify. Re-enable then.
|
zaaktype and a JWT client is the next slice (**S-01-b**); authenticated zaaktype
|
||||||
- **Zaaktype is a concept**, not published (publishing needs roltypen/statustypen/
|
listing isn't testable until then.
|
||||||
resultaattypen — beyond the lean seed). List with `?status=alles`.
|
|
||||||
- **Image tag.** Currently `openzaak/open-zaak:latest` via `${OPENZAAK_TAG}`; pin to
|
- **Image tag.** Currently `openzaak/open-zaak:latest` via `${OPENZAAK_TAG}`; pin to
|
||||||
a known-good tag (ADR-0002 follow-up).
|
a known-good tag as part of the catalogus-design ADR.
|
||||||
|
|||||||
@@ -44,18 +44,11 @@ services:
|
|||||||
CELERY_BROKER_URL: redis://oz-redis:6379/1
|
CELERY_BROKER_URL: redis://oz-redis:6379/1
|
||||||
CELERY_RESULT_BACKEND: redis://oz-redis:6379/1
|
CELERY_RESULT_BACKEND: redis://oz-redis:6379/1
|
||||||
DISABLE_2FA: "true"
|
DISABLE_2FA: "true"
|
||||||
# Notifications go to Open Notificaties (NRC), which arrives in S-01-c.
|
|
||||||
# Until then, disable outbound notifications so writes don't 500.
|
|
||||||
NOTIFICATIONS_DISABLED: "true"
|
|
||||||
OPENZAAK_SUPERUSER_USERNAME: admin
|
OPENZAAK_SUPERUSER_USERNAME: admin
|
||||||
DJANGO_SUPERUSER_PASSWORD: admin
|
DJANGO_SUPERUSER_PASSWORD: admin
|
||||||
OPENZAAK_SUPERUSER_EMAIL: admin@localhost
|
OPENZAAK_SUPERUSER_EMAIL: admin@localhost
|
||||||
RUN_SETUP_CONFIG: "true"
|
RUN_SETUP_CONFIG: "false"
|
||||||
command: /setup_configuration.sh
|
command: /setup_configuration.sh
|
||||||
volumes:
|
|
||||||
# :z relabels for SELinux; the dir/file must be world-readable for the
|
|
||||||
# container user (rootless Podman uid mapping). See docs/runbooks/openzaak.md.
|
|
||||||
- ./setup_configuration:/app/setup_configuration:ro,z
|
|
||||||
depends_on:
|
depends_on:
|
||||||
oz-db:
|
oz-db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|||||||
@@ -1,137 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Idempotent seed of the BIG catalogus into OpenZaak via the ZTC API.
|
|
||||||
|
|
||||||
Creates (if absent):
|
|
||||||
- catalogus "BIG"
|
|
||||||
- a lean "BIG-registratie" zaaktype (only schema-mandatory fields)
|
|
||||||
- a "bsn" eigenschap on that zaaktype
|
|
||||||
- then publishes the zaaktype.
|
|
||||||
|
|
||||||
Auth uses the JWT client provisioned by setup_configuration (see ADR-0002).
|
|
||||||
Stdlib only — no pip deps. Re-running is safe (matches existing by identifier).
|
|
||||||
"""
|
|
||||||
import base64, hashlib, hmac, json, os, sys, time, urllib.error, urllib.request
|
|
||||||
|
|
||||||
BASE = os.environ.get("OZ_BASE", "http://localhost:8000")
|
|
||||||
CLIENT_ID = os.environ.get("OZ_CLIENT_ID", "big-reference-seed")
|
|
||||||
SECRET = os.environ.get("OZ_SECRET", "insecure-dev-secret-change-me")
|
|
||||||
ZTC = f"{BASE}/catalogi/api/v1"
|
|
||||||
RSIN = "517439943" # elfproef-valid test RSIN
|
|
||||||
|
|
||||||
|
|
||||||
def token():
|
|
||||||
b64 = lambda b: base64.urlsafe_b64encode(b).rstrip(b"=")
|
|
||||||
hdr = {"alg": "HS256", "typ": "JWT"}
|
|
||||||
pl = {"iss": CLIENT_ID, "iat": int(time.time()), "client_id": CLIENT_ID,
|
|
||||||
"user_id": "seed", "user_representation": "seed"}
|
|
||||||
seg = b64(json.dumps(hdr, separators=(",", ":")).encode()) + b"." + \
|
|
||||||
b64(json.dumps(pl, separators=(",", ":")).encode())
|
|
||||||
sig = b64(hmac.new(SECRET.encode(), seg, hashlib.sha256).digest())
|
|
||||||
return (seg + b"." + sig).decode()
|
|
||||||
|
|
||||||
|
|
||||||
def api(method, path, body=None):
|
|
||||||
url = path if path.startswith("http") else f"{ZTC}{path}"
|
|
||||||
data = json.dumps(body).encode() if body is not None else None
|
|
||||||
req = urllib.request.Request(url, data=data, method=method, headers={
|
|
||||||
"Authorization": "Bearer " + token(),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Accept": "application/json",
|
|
||||||
})
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(req, timeout=30) as r:
|
|
||||||
return r.status, json.loads(r.read() or "null")
|
|
||||||
except urllib.error.HTTPError as e:
|
|
||||||
return e.code, json.loads(e.read() or "null")
|
|
||||||
|
|
||||||
|
|
||||||
def find(path):
|
|
||||||
status, body = api("GET", path)
|
|
||||||
if status != 200:
|
|
||||||
sys.exit(f"GET {path} -> {status}: {body}")
|
|
||||||
return body.get("results", [])
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# 1. Catalogus
|
|
||||||
existing = [c for c in find(f"/catalogussen?domein=BIG") if c.get("domein") == "BIG"]
|
|
||||||
if existing:
|
|
||||||
cat = existing[0]
|
|
||||||
print(f"skip catalogus BIG ({cat['url']})")
|
|
||||||
else:
|
|
||||||
st, cat = api("POST", "/catalogussen", {
|
|
||||||
"domein": "BIG", "rsin": RSIN,
|
|
||||||
"contactpersoonBeheerNaam": "BIG Beheer",
|
|
||||||
})
|
|
||||||
if st != 201:
|
|
||||||
sys.exit(f"create catalogus -> {st}: {cat}")
|
|
||||||
print(f"create catalogus BIG ({cat['url']})")
|
|
||||||
|
|
||||||
# 2. Zaaktype (concept)
|
|
||||||
# status=alles so concept zaaktypen are matched too (else we'd duplicate).
|
|
||||||
zts = [z for z in find(f"/zaaktypen?catalogus={cat['url']}&status=alles")
|
|
||||||
if z.get("identificatie") == "BIG-REGISTRATIE"]
|
|
||||||
if zts:
|
|
||||||
zt = zts[0]
|
|
||||||
print(f"skip zaaktype BIG-REGISTRATIE ({zt['url']}) concept={zt.get('concept')}")
|
|
||||||
else:
|
|
||||||
st, zt = api("POST", "/zaaktypen", {
|
|
||||||
"identificatie": "BIG-REGISTRATIE",
|
|
||||||
"omschrijving": "BIG-registratie",
|
|
||||||
"vertrouwelijkheidaanduiding": "openbaar",
|
|
||||||
"doel": "Registratie van een zorgprofessional in het BIG-register",
|
|
||||||
"aanleiding": "Aanvraag tot registratie",
|
|
||||||
"indicatieInternOfExtern": "extern",
|
|
||||||
"handelingInitiator": "indienen",
|
|
||||||
"onderwerp": "BIG-registratie",
|
|
||||||
"handelingBehandelaar": "behandelen",
|
|
||||||
"doorlooptijd": "P30D",
|
|
||||||
"opschortingEnAanhoudingMogelijk": False,
|
|
||||||
"verlengingMogelijk": False,
|
|
||||||
"publicatieIndicatie": False,
|
|
||||||
"productenOfDiensten": [],
|
|
||||||
"referentieproces": {"naam": "BIG-registratie"},
|
|
||||||
"catalogus": cat["url"],
|
|
||||||
"besluittypen": [],
|
|
||||||
"gerelateerdeZaaktypen": [],
|
|
||||||
"beginGeldigheid": "2026-01-01",
|
|
||||||
"versiedatum": "2026-01-01",
|
|
||||||
"verantwoordelijke": RSIN,
|
|
||||||
})
|
|
||||||
if st != 201:
|
|
||||||
sys.exit(f"create zaaktype -> {st}: {json.dumps(zt, indent=2)}")
|
|
||||||
print(f"create zaaktype BIG-REGISTRATIE ({zt['url']})")
|
|
||||||
|
|
||||||
# 3. bsn eigenschap (only addable while concept)
|
|
||||||
eigs = [e for e in find(f"/eigenschappen?zaaktype={zt['url']}&status=alles")
|
|
||||||
if e.get("naam") == "bsn"]
|
|
||||||
if eigs:
|
|
||||||
print("skip eigenschap bsn")
|
|
||||||
elif zt.get("concept", True):
|
|
||||||
st, eig = api("POST", "/eigenschappen", {
|
|
||||||
"naam": "bsn",
|
|
||||||
"definitie": "Burgerservicenummer van de zorgprofessional",
|
|
||||||
"zaaktype": zt["url"],
|
|
||||||
"specificatie": {"groep": "aanvrager", "formaat": "tekst",
|
|
||||||
"lengte": "9", "kardinaliteit": "1", "waardenverzameling": []},
|
|
||||||
})
|
|
||||||
if st != 201:
|
|
||||||
sys.exit(f"create eigenschap -> {st}: {json.dumps(eig, indent=2)}")
|
|
||||||
print("create eigenschap bsn")
|
|
||||||
else:
|
|
||||||
print("warn zaaktype already published; cannot add bsn eigenschap")
|
|
||||||
|
|
||||||
# Intentionally NOT published. Publishing requires roltypen, resultaattypen
|
|
||||||
# and statustypen, which go beyond the "lean / schema-mandatory" zaaktype this
|
|
||||||
# slice asks for; they arrive with the workflow/zaak slices. See ADR-0002.
|
|
||||||
|
|
||||||
# 4. Verify the JWT client can list the zaaktype (concepts included).
|
|
||||||
zaaktypen = find(f"/zaaktypen?catalogus={cat['url']}&status=alles")
|
|
||||||
names = [z.get("identificatie") for z in zaaktypen]
|
|
||||||
print(f"zaaktypen in BIG: {names}")
|
|
||||||
assert "BIG-REGISTRATIE" in names, "BIG-REGISTRATIE not listed"
|
|
||||||
print("OK — BIG catalogus seeded (BIG-REGISTRATIE concept + bsn eigenschap)")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# OpenZaak setup_configuration (idempotent, declarative).
|
|
||||||
# Provisions the JWT client the seed + ACL use to call OpenZaak's APIs.
|
|
||||||
# Dev-only credentials — not for production.
|
|
||||||
#
|
|
||||||
# Steps come from vng_api_common.contrib.setup_configuration (see ADR-0002).
|
|
||||||
|
|
||||||
vng_api_common_credentials_config_enable: true
|
|
||||||
vng_api_common_credentials:
|
|
||||||
items:
|
|
||||||
- identifier: big-reference-seed
|
|
||||||
secret: insecure-dev-secret-change-me
|
|
||||||
|
|
||||||
vng_api_common_applicaties_config_enable: true
|
|
||||||
vng_api_common_applicaties:
|
|
||||||
items:
|
|
||||||
# uuid must be given explicitly as a string (the step's auto-default is a
|
|
||||||
# UUID object that fails its own validation).
|
|
||||||
- uuid: "11111111-1111-4111-8111-111111111111"
|
|
||||||
client_ids:
|
|
||||||
- big-reference-seed
|
|
||||||
label: BIG reference seed client
|
|
||||||
heeft_alle_autorisaties: true
|
|
||||||
Reference in New Issue
Block a user