feat(infra): register RegisterRecord objecttype + published version in Objecttypen (refs #141)
The Objecttypen setup_configuration (3.4.2) can only provision tokens, with no declarative objecttype step, so a registerrecord-init compose one-shot creates the RegisterRecord objecttype + a published v1 over the API once Objecttypen is healthy (idempotent; ADR-0020 self-seed pattern). The schema (infra/objecttypen-registerrecord/registerrecord.schema.json) is public-safe — id, status, reference — mirroring the BFF's OpenbaarEntry, no bsn/naam; dataClassification 'open'. S-19 writes records against it on approval. Config (schema + register.py) is streamed into rr-registerrecord-config by seed-config.sh (main) / bind-mounted (local), same dual pattern as the setup_configuration volumes. Wired into SEED, CFG_VOLS, and the CI log-dump. Verified locally: registerrecord-init registered RegisterRecord v1 published, make verify-registerrecord green, and a re-run is a no-op. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -285,7 +285,7 @@ jobs:
|
|||||||
# Log dump must precede teardown (which removes the containers).
|
# Log dump must precede teardown (which removes the containers).
|
||||||
- name: Dump container logs on failure
|
- name: Dump container logs on failure
|
||||||
if: 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
|
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 registerrecord-init tempo prometheus grafana 2>&1 || true
|
||||||
- name: Tear down
|
- name: Tear down
|
||||||
if: always()
|
if: always()
|
||||||
run: make down
|
run: make down
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ WAIT_SVCS := openzaak nrc-web acl bff domain event-subscriber projection-api se
|
|||||||
# volumes are `external`, so compose won't remove them — CFG_VOLS lists them for
|
# volumes are `external`, so compose won't remove them — CFG_VOLS lists them for
|
||||||
# explicit teardown. See docs/runbooks/gitea-actions-gotchas.md.
|
# explicit teardown. See docs/runbooks/gitea-actions-gotchas.md.
|
||||||
SEED := bash infra/seed-config.sh
|
SEED := bash infra/seed-config.sh
|
||||||
CFG_VOLS := rr-oz-config rr-nrc-config rr-kc-realms rr-fl-bpmn rr-objecttypen-config rr-objecten-config
|
CFG_VOLS := rr-oz-config rr-nrc-config rr-kc-realms rr-fl-bpmn rr-objecttypen-config rr-objecten-config rr-registerrecord-config
|
||||||
# Local-only stack: same services but config is bind-mounted (no seed step), so a
|
# Local-only stack: same services but config is bind-mounted (no seed step), so a
|
||||||
# plain `docker compose -f infra/docker-compose.local.yml up` works on any local
|
# plain `docker compose -f infra/docker-compose.local.yml up` works on any local
|
||||||
# engine. This is the no-make / Windows-friendly path. See that file's header.
|
# engine. This is the no-make / Windows-friendly path. See that file's header.
|
||||||
@@ -94,14 +94,14 @@ mutation:
|
|||||||
# podman-compose, and needing no `--wait` flag or host port access. The one-shots
|
# podman-compose, and needing no `--wait` flag or host port access. The one-shots
|
||||||
# (oz-init, flowable-init) aren't polled; they just need to have run.
|
# (oz-init, flowable-init) aren't polled; they just need to have run.
|
||||||
smoke:
|
smoke:
|
||||||
$(SEED) oz nrc kc fl objecttypen objecten
|
$(SEED) oz nrc kc fl objecttypen objecten registerrecord
|
||||||
docker compose -f $(COMPOSE) up -d --build
|
docker compose -f $(COMPOSE) up -d --build
|
||||||
bash -c 'WAIT_TIMEOUT=420 bash infra/wait-healthy.sh $(WAIT_SVCS); rc=$$?; docker compose -f $(COMPOSE) down --volumes; docker volume rm -f $(CFG_VOLS) >/dev/null 2>&1; exit $$rc'
|
bash -c 'WAIT_TIMEOUT=420 bash infra/wait-healthy.sh $(WAIT_SVCS); rc=$$?; docker compose -f $(COMPOSE) down --volumes; docker volume rm -f $(CFG_VOLS) >/dev/null 2>&1; exit $$rc'
|
||||||
|
|
||||||
## up: seed config volumes and start the full stack (use instead of bare
|
## up: seed config volumes and start the full stack (use instead of bare
|
||||||
## `docker compose up`, which can't self-seed the external config volumes)
|
## `docker compose up`, which can't self-seed the external config volumes)
|
||||||
up:
|
up:
|
||||||
$(SEED) oz nrc kc fl objecttypen objecten
|
$(SEED) oz nrc kc fl objecttypen objecten registerrecord
|
||||||
docker compose -f $(COMPOSE) up -d --build
|
docker compose -f $(COMPOSE) up -d --build
|
||||||
|
|
||||||
## down: stop and remove the local stack (incl. the external config volumes)
|
## down: stop and remove the local stack (incl. the external config volumes)
|
||||||
@@ -139,7 +139,7 @@ changelog:
|
|||||||
## verify-up: bring the FULL stack up and wait for health (CI verify-stack step 1;
|
## verify-up: bring the FULL stack up and wait for health (CI verify-stack step 1;
|
||||||
## subsumes the old compose-smoke health gate — the DoD "up reaches green" check).
|
## subsumes the old compose-smoke health gate — the DoD "up reaches green" check).
|
||||||
verify-up:
|
verify-up:
|
||||||
$(SEED) oz nrc kc fl objecttypen objecten
|
$(SEED) oz nrc kc fl objecttypen objecten registerrecord
|
||||||
docker compose -f $(COMPOSE) up -d --build
|
docker compose -f $(COMPOSE) up -d --build
|
||||||
WAIT_TIMEOUT=420 bash infra/wait-healthy.sh $(WAIT_SVCS)
|
WAIT_TIMEOUT=420 bash infra/wait-healthy.sh $(WAIT_SVCS)
|
||||||
|
|
||||||
@@ -205,7 +205,7 @@ verify-registerrecord:
|
|||||||
## tear down (always). For fast single-concern local iteration use `integration`
|
## tear down (always). For fast single-concern local iteration use `integration`
|
||||||
## (oz-only) or `verify-notifications` (oz+nrc) instead.
|
## (oz-only) or `verify-notifications` (oz+nrc) instead.
|
||||||
verify:
|
verify:
|
||||||
$(SEED) oz nrc kc fl objecttypen objecten
|
$(SEED) oz nrc kc fl objecttypen objecten registerrecord
|
||||||
docker compose -f $(COMPOSE) up -d --build
|
docker compose -f $(COMPOSE) up -d --build
|
||||||
@bash -c 'set -e; rc=0; \
|
@bash -c 'set -e; rc=0; \
|
||||||
WAIT_TIMEOUT=420 bash infra/wait-healthy.sh $(WAIT_SVCS) \
|
WAIT_TIMEOUT=420 bash infra/wait-healthy.sh $(WAIT_SVCS) \
|
||||||
|
|||||||
@@ -624,6 +624,21 @@ services:
|
|||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
networks: [cg]
|
networks: [cg]
|
||||||
|
|
||||||
|
# ── RegisterRecord objecttype (S-18c) — API-seeded one-shot (local variant) ─
|
||||||
|
registerrecord-init:
|
||||||
|
image: docker.io/library/python:3-slim
|
||||||
|
environment:
|
||||||
|
OBJECTTYPEN: http://objecttypen:8000
|
||||||
|
OBJECTTYPEN_TOKEN: ${OBJECTTYPEN_TOKEN:-0123456789abcdef0123456789abcdef01234567}
|
||||||
|
SCHEMA: /config/registerrecord.schema.json
|
||||||
|
command: python /config/register.py
|
||||||
|
volumes:
|
||||||
|
- ./objecttypen-registerrecord:/config:ro,z
|
||||||
|
depends_on:
|
||||||
|
objecttypen:
|
||||||
|
condition: service_healthy
|
||||||
|
networks: [cg]
|
||||||
|
|
||||||
# ── Objecten API (S-18b) — bind-mounted config (local variant) ─────────────
|
# ── Objecten API (S-18b) — bind-mounted config (local variant) ─────────────
|
||||||
objecten-db:
|
objecten-db:
|
||||||
image: docker.io/postgis/postgis:17-3.5
|
image: docker.io/postgis/postgis:17-3.5
|
||||||
|
|||||||
@@ -640,6 +640,25 @@ services:
|
|||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
networks: [cg]
|
networks: [cg]
|
||||||
|
|
||||||
|
# ── RegisterRecord objecttype (S-18c) — API-seeded one-shot ────────────────
|
||||||
|
# The Objecttypen setup_configuration (3.4.2) can only provision tokens — no declarative objecttype
|
||||||
|
# step — so this one-shot creates the RegisterRecord objecttype + a published version over the API
|
||||||
|
# once Objecttypen is healthy (idempotent; ADR-0020 self-seed, ADR-0027 schema). The schema + script
|
||||||
|
# are streamed into the external config volume by infra/seed-config.sh, like the *-init volumes.
|
||||||
|
registerrecord-init:
|
||||||
|
image: docker.io/library/python:3-slim
|
||||||
|
environment:
|
||||||
|
OBJECTTYPEN: http://objecttypen:8000
|
||||||
|
OBJECTTYPEN_TOKEN: ${OBJECTTYPEN_TOKEN:-0123456789abcdef0123456789abcdef01234567}
|
||||||
|
SCHEMA: /config/registerrecord.schema.json
|
||||||
|
command: python /config/register.py
|
||||||
|
volumes:
|
||||||
|
- registerrecord-config:/config:ro
|
||||||
|
depends_on:
|
||||||
|
objecttypen:
|
||||||
|
condition: service_healthy
|
||||||
|
networks: [cg]
|
||||||
|
|
||||||
# ── Objecten API (S-18b) — upstream Maykin image, verbatim ─────────────────
|
# ── Objecten API (S-18b) — upstream Maykin image, verbatim ─────────────────
|
||||||
# The authoritative object store. Same shape as Objecttypen (own DB + redis, an `-init` that runs
|
# The authoritative object store. Same shape as Objecttypen (own DB + redis, an `-init` that runs
|
||||||
# setup_configuration from the external config volume, a health-checked web). Two differences: the
|
# setup_configuration from the external config volume, a health-checked web). Two differences: the
|
||||||
@@ -782,6 +801,9 @@ volumes:
|
|||||||
objecttypen-config:
|
objecttypen-config:
|
||||||
external: true
|
external: true
|
||||||
name: rr-objecttypen-config
|
name: rr-objecttypen-config
|
||||||
|
registerrecord-config:
|
||||||
|
external: true
|
||||||
|
name: rr-registerrecord-config
|
||||||
objecten-config:
|
objecten-config:
|
||||||
external: true
|
external: true
|
||||||
name: rr-objecten-config
|
name: rr-objecten-config
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""S-18c (#141): register the RegisterRecord objecttype + a published version in the Objecttypen API.
|
||||||
|
|
||||||
|
Run by the `registerrecord-init` compose one-shot once Objecttypen is healthy. The Objecttypen API's
|
||||||
|
setup_configuration (3.4.2) can only provision tokens — it has no declarative objecttype step — so
|
||||||
|
the objecttype is created over the API here (the ADR-0020 self-seed pattern), idempotently: if a
|
||||||
|
"RegisterRecord" objecttype with a published version already exists, it's a no-op. Stdlib only.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
BASE = os.environ.get("OBJECTTYPEN", "http://objecttypen:8000").rstrip("/")
|
||||||
|
TOKEN = os.environ["OBJECTTYPEN_TOKEN"]
|
||||||
|
SCHEMA_PATH = os.environ.get("SCHEMA", "/config/registerrecord.schema.json")
|
||||||
|
NAME = "RegisterRecord"
|
||||||
|
|
||||||
|
|
||||||
|
def api(method, path, body=None):
|
||||||
|
data = json.dumps(body).encode() if body is not None else None
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"{BASE}{path}", data=data, method=method,
|
||||||
|
headers={"Authorization": f"Token {TOKEN}", "Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(req, timeout=15) as r:
|
||||||
|
return json.load(r) if r.length != 0 else {}
|
||||||
|
|
||||||
|
|
||||||
|
def wait_ready():
|
||||||
|
"""Objecttypen depends_on health already, but tolerate a slow first request."""
|
||||||
|
for _ in range(20):
|
||||||
|
try:
|
||||||
|
api("GET", "/api/v2/objecttypes")
|
||||||
|
return
|
||||||
|
except (urllib.error.URLError, ConnectionError, TimeoutError):
|
||||||
|
time.sleep(3)
|
||||||
|
api("GET", "/api/v2/objecttypes") # last try, let it raise
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
schema = json.load(open(SCHEMA_PATH))
|
||||||
|
wait_ready()
|
||||||
|
|
||||||
|
existing = next(
|
||||||
|
(o for o in api("GET", "/api/v2/objecttypes").get("results", []) if o.get("name") == NAME),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if existing and existing.get("versions"):
|
||||||
|
print(f"RegisterRecord already registered ({len(existing['versions'])} version(s)) — no-op")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
ot = existing or api("POST", "/api/v2/objecttypes", {
|
||||||
|
"name": NAME,
|
||||||
|
"namePlural": "RegisterRecords",
|
||||||
|
"description": schema.get("description", ""),
|
||||||
|
"dataClassification": "open", # public-safe: the openbaar register may show it
|
||||||
|
})
|
||||||
|
uuid = ot["uuid"]
|
||||||
|
ver = api("POST", f"/api/v2/objecttypes/{uuid}/versions", {
|
||||||
|
"status": "published",
|
||||||
|
"jsonSchema": schema,
|
||||||
|
})
|
||||||
|
print(f"registered RegisterRecord {uuid} v{ver.get('version')} ({ver.get('status')})")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"title": "RegisterRecord",
|
||||||
|
"description": "Public-safe register entry shown in the openbaar (public) register. Mirrors the BFF's OpenbaarEntry (services/bff/Bff.Api/DownstreamClients.cs) — deliberately NO bsn or naam. S-19 (#20) writes records against this schema in the Objecten API on approval. See ADR-0027.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["id", "status"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Zaak id — the register entry's stable primary key (the projection key)."
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["INGEDIEND", "INGESCHREVEN"],
|
||||||
|
"description": "Registration lifecycle status (RegistrationStatus)."
|
||||||
|
},
|
||||||
|
"reference": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"description": "Citizen-facing zaak identificatie shown publicly (ADR-0012)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
# subcommand. Fixed-name `external` volumes keep the names deterministic across
|
# subcommand. Fixed-name `external` volumes keep the names deterministic across
|
||||||
# both runtimes. See docs/runbooks/gitea-actions-gotchas.md.
|
# both runtimes. See docs/runbooks/gitea-actions-gotchas.md.
|
||||||
#
|
#
|
||||||
# Usage: seed-config.sh <key> [<key> ...] where key ∈ { oz, nrc, kc, fl, objecttypen, objecten }
|
# Usage: seed-config.sh <key> [<key> ...] where key ∈ { oz, nrc, kc, fl, objecttypen, objecten, registerrecord }
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
@@ -33,7 +33,7 @@ populate() { # volume source(file or dir/.)
|
|||||||
echo " seeded $vol"
|
echo " seeded $vol"
|
||||||
}
|
}
|
||||||
|
|
||||||
[ "$#" -gt 0 ] || { echo "usage: seed-config.sh <oz|nrc|kc|fl|objecttypen|objecten> ..." >&2; exit 2; }
|
[ "$#" -gt 0 ] || { echo "usage: seed-config.sh <oz|nrc|kc|fl|objecttypen|objecten|registerrecord> ..." >&2; exit 2; }
|
||||||
|
|
||||||
# The registratie process (BPMN) and its diploma-eligibility DMN are deployed as SEPARATE Flowable
|
# The registratie process (BPMN) and its diploma-eligibility DMN are deployed as SEPARATE Flowable
|
||||||
# deployments — the process engine and the DMN engine each own theirs (S-13, ADR-0016). flowable-rest
|
# deployments — the process engine and the DMN engine each own theirs (S-13, ADR-0016). flowable-rest
|
||||||
@@ -51,6 +51,7 @@ for key in "$@"; do
|
|||||||
kc) populate rr-kc-realms "$here/keycloak/realms/." ;;
|
kc) populate rr-kc-realms "$here/keycloak/realms/." ;;
|
||||||
objecttypen) populate rr-objecttypen-config "$here/objecttypen/setup_configuration/." ;;
|
objecttypen) populate rr-objecttypen-config "$here/objecttypen/setup_configuration/." ;;
|
||||||
objecten) populate rr-objecten-config "$here/objecten/setup_configuration/." ;;
|
objecten) populate rr-objecten-config "$here/objecten/setup_configuration/." ;;
|
||||||
|
registerrecord) populate rr-registerrecord-config "$here/objecttypen-registerrecord/." ;;
|
||||||
fl) d="$(mktemp -d)"; stage_flowable_workflows "$d"; populate rr-fl-bpmn "$d/." ;;
|
fl) d="$(mktemp -d)"; stage_flowable_workflows "$d"; populate rr-fl-bpmn "$d/." ;;
|
||||||
*) echo "unknown seed key: $key" >&2; exit 2 ;;
|
*) echo "unknown seed key: $key" >&2; exit 2 ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
Reference in New Issue
Block a user