Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bin/generate_setup_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"objects_host": "objects.internal:8000",
"hc_brp_host": "hc-brp.internal:5010",
"openafval_host": "openafval.internal:8000",
"ssd_host": "ssd.internal",
"clamav_host": "clamav",
"domain": "localhost:9000",
}
Expand All @@ -62,6 +63,7 @@
"objects_host": "localhost:8004",
"hc_brp_host": "localhost:5010",
"openafval_host": "localhost:8339",
"ssd_host": "localhost:5020",
"clamav_host": "localhost",
"domain": "localhost:8000",
}
Expand Down
3 changes: 3 additions & 0 deletions bin/stack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ COMPOSE=(
-f docker/docker-compose.open-zaak.yml
-f docker/docker-compose.objects-apis.yml
-f docker/docker-compose.hc-brp-mock.yml
-f docker/docker-compose.ssd-mock.yml
-f docker/docker-compose.openklant.yml
-f docker/docker-compose.openafval.yml
-f docker/docker-compose.observability.yml
Expand Down Expand Up @@ -117,6 +118,7 @@ print_satellite_urls() {
Open Klant admin http://localhost:8338/admin/ (admin / admin)
Open Afval admin http://localhost:8339/admin/
Haal Centraal BRP mock http://localhost:5010/
SSD mock http://localhost:5020/
Grafana (observability) http://localhost:3000/
Prometheus http://localhost:9090/
Loki http://localhost:3100/ready
Expand Down Expand Up @@ -210,6 +212,7 @@ case "$cmd" in
objecttypes-web \
objects-web \
personen-mock \
ssd-mock \
openklant-web \
openafval-web \
loki \
Expand Down
41 changes: 41 additions & 0 deletions docker/docker-compose.ssd-mock.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
services:
ssd-mock:
image: nginx:alpine
volumes:
# Relative paths resolve against the repo root (bin/stack.sh's
# `--project-directory .`), not this file's own directory -- see
# docs/installation/docker-compose.rst's docker-compose.keycloak.yml
# note for the one exception.
- ./docker/ssd-mock/nginx.conf:/etc/nginx/conf.d/default.conf:ro
# Reuse the fixtures the unit tests already use, so the two can't
# drift apart.
- ./src/open_inwoner/ssd/tests/files/jaaropgave_response.xml:/usr/share/nginx/html/jaaropgave_response.xml:ro
- ./src/open_inwoner/ssd/tests/files/uitkering_response_basic.xml:/usr/share/nginx/html/uitkering_response_basic.xml:ro
ports:
- '5020:80'
healthcheck:
test:
[
'CMD',
'wget',
'-q',
'-O',
'/dev/null',
'http://localhost/JaarOpgaveClient-v0400',
]
interval: 5s
timeout: 5s
retries: 30
start_period: 5s
networks:
open-inwoner-dev:
aliases:
- ssd.local
- ssd.internal

networks:
# Created once via `bin/ensure_dev_network.sh`, not by this (or any other)
# compose file, so bringing up several stacks together never races over who
# "owns" the network. See docs/installation/docker-compose.rst.
open-inwoner-dev:
external: true
16 changes: 16 additions & 0 deletions docker/setup_configuration/data.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,22 @@ kvk_config:
api_root: https://api.kvk.nl/test/api/
api_key: l7xx1f2691f2520d487b902f4e0b57a0b197

# SOAP services (currently just the SSD GWS mock)
soap_services_config_enable: true
soap_services_config:
services:
- identifier: ssd-mock
url: http://ssd.internal/

# SSD (Mijn uitkeringen), backed by the nginx-based mock in
# docker/docker-compose.ssd-mock.yml
ssd_config_enable: true
ssd_config:
service_identifier: ssd-mock
applicatie_naam: Open Inwoner
bedrijfs_naam: Open Inwoner
gemeentecode: '0000'

# KlantenSysteem (consolidated OpenKlant/eSuite and OpenKlant2 configuration)
klantensysteem_config_enable: true
klantensysteem_config:
Expand Down
16 changes: 16 additions & 0 deletions docker/setup_configuration/data.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,22 @@ kvk_config:
api_root: https://api.kvk.nl/test/api/
api_key: l7xx1f2691f2520d487b902f4e0b57a0b197

# SOAP services (currently just the SSD GWS mock)
soap_services_config_enable: true
soap_services_config:
services:
- identifier: ssd-mock
url: http://{{ ssd_host }}/

# SSD (Mijn uitkeringen), backed by the nginx-based mock in
# docker/docker-compose.ssd-mock.yml
ssd_config_enable: true
ssd_config:
service_identifier: ssd-mock
applicatie_naam: Open Inwoner
bedrijfs_naam: Open Inwoner
gemeentecode: '0000'

# KlantenSysteem (consolidated OpenKlant/eSuite and OpenKlant2 configuration)
klantensysteem_config_enable: true
klantensysteem_config:
Expand Down
30 changes: 30 additions & 0 deletions docker/ssd-mock/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Mock for the SSD SOAP service (open_inwoner.ssd / open_inwoner.soap).
# Doesn't parse SOAP bodies at all -- it dispatches purely on the URL path
# each operation posts to (see SSDBaseClient.templated_request() in
# src/open_inwoner/ssd/client.py), which is enough to exercise "Mijn
# uitkeringen" end to end. Response bodies are the same fixtures the
# Python unit tests use (src/open_inwoner/ssd/tests/files/), mounted
# read-only by docker/docker-compose.ssd-mock.yml, so there's one copy to
# keep in sync.
server {
listen 80 default_server;
server_name _;

default_type text/xml;

# SSDBaseClient always POSTs (client.py), but nginx's static-file handler
# only serves GET/HEAD and 405s otherwise. Standard workaround: redirect
# the 405 back through the same URI, which re-enters without the method
# check.
error_page 405 = $uri;

# JaaropgaveClient.get_reports()
location = /JaarOpgaveClient-v0400 {
alias /usr/share/nginx/html/jaaropgave_response.xml;
}

# UitkeringClient.get_reports()
location = /UitkeringsSpecificatieClient-v0600 {
alias /usr/share/nginx/html/uitkering_response_basic.xml;
}
}
14 changes: 9 additions & 5 deletions docs/installation/docker-compose.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ shared acceptance environments:
Objecttypes API
- ``docker/docker-compose.hc-brp-mock.yml`` -- the Haal Centraal BRP
``personen-mock``
- ``docker/docker-compose.ssd-mock.yml`` -- an nginx-based mock of the SSD
SOAP service backing the "Mijn uitkeringen" pages (see
``open_inwoner.ssd``)
- ``docker/docker-compose.openklant.yml`` -- Open Klant (Klantinteracties and
Contactmomenten APIs)
- ``docker/docker-compose.openafval.yml`` -- `Open Afval
Expand All @@ -128,11 +131,11 @@ shared acceptance environments:
All of these attach to the same external ``open-inwoner-dev`` network as the
main app stack (see `Getting started`_, step 3) and publish internal DNS
aliases such as ``keycloak.internal``, ``openzaak.internal``,
``openklant.internal``, ``objects.internal``, ``objecttypes.internal`` and
``openafval.internal``. ``docker/setup_configuration/data.yaml`` -- which
``web-init`` applies on its first run (see below) -- already points its
ZGW/OpenKlant/Objects/Haal Centraal/Open Afval service configuration at those
aliases (Keycloak's OIDC endpoints are the exception, see below), so once
``openklant.internal``, ``objects.internal``, ``objecttypes.internal``,
``openafval.internal`` and ``ssd.internal``. ``docker/setup_configuration/data.yaml``
-- which ``web-init`` applies on its first run (see below) -- already points
its ZGW/OpenKlant/Objects/Haal Centraal/Open Afval/SSD service configuration at
those aliases (Keycloak's OIDC endpoints are the exception, see below), so once
those stacks are up the app is pre-configured to talk to them without any
manual admin work.

Expand Down Expand Up @@ -211,6 +214,7 @@ Objects API admin http://localhost:8004/admin/ admin / admin
Open Klant admin http://localhost:8338/admin/ admin / admin [1]_
Open Afval admin http://localhost:8339/admin/ admin / admin [1]_
Haal Centraal BRP mock http://localhost:5010/ --
SSD mock http://localhost:5020/ --
Grafana http://localhost:3000/ --
Prometheus http://localhost:9090/ --
Loki (readiness) http://localhost:3100/ready --
Expand Down
2 changes: 2 additions & 0 deletions src/open_inwoner/conf/app/setup_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
"open_inwoner.configurations.bootstrap.saml.DigiDSAMLConfigurationStep",
"open_inwoner.configurations.bootstrap.saml.eHerkenningSAMLConfigurationStep",
"zgw_consumers.contrib.setup_configuration.steps.ServiceConfigurationStep",
"open_inwoner.configurations.bootstrap.soap.SoapServiceConfigurationStep",
"open_inwoner.configurations.bootstrap.zgw.OpenZaakConfigurationStep",
"open_inwoner.configurations.bootstrap.openklant.KlantenSysteemConfigurationStep",
"open_inwoner.configurations.bootstrap.haalcentraal.HaalCentraalConfigurationStep",
"open_inwoner.configurations.bootstrap.mijn_afval.MijnAfvalConfigurationStep",
"open_inwoner.configurations.bootstrap.kvk.KvKConfigurationStep",
"open_inwoner.configurations.bootstrap.ssd.SSDConfigurationStep",
"open_inwoner.configurations.bootstrap.default_users.UserConfigurationStep",
"django_setup_configuration.contrib.sites.steps.SitesConfigurationStep",
"open_inwoner.configurations.bootstrap.cms.CMSPagesConfigurationStep",
Expand Down
40 changes: 40 additions & 0 deletions src/open_inwoner/configurations/bootstrap/soap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from django_setup_configuration import ConfigurationModel, DjangoModelRef
from django_setup_configuration.configuration import BaseConfigurationStep

from open_inwoner.soap.models import SoapService


class SoapServiceConfig(ConfigurationModel):
"""A single SOAP service, e.g. the SSD GWS endpoint."""

identifier: str = DjangoModelRef(SoapService, "slug", examples=["ssd-mock"])

class Meta:
django_model_refs = {
SoapService: ["url"],
}


class SoapServicesConfigurationModel(ConfigurationModel):
services: list[SoapServiceConfig]


class SoapServiceConfigurationStep(BaseConfigurationStep):
"""
Configure one or more `SoapService` instances (SOAP/WS-* endpoints, e.g.
the SSD GWS service), analogous to zgw_consumers' ServiceConfigurationStep
for REST/ZGW services. Client/server certificates aren't settable here --
attach those in the admin for a real (mutual-TLS) connection.
"""

verbose_name = "SOAP service configuration"
enable_setting = "soap_services_config_enable"
namespace = "soap_services_config"
config_model = SoapServicesConfigurationModel

def execute(self, model: SoapServicesConfigurationModel) -> None:
for service in model.services:
SoapService.objects.update_or_create(
slug=service.identifier,
defaults={"url": service.url},
)
67 changes: 67 additions & 0 deletions src/open_inwoner/configurations/bootstrap/ssd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from django.core.exceptions import ValidationError

from django_setup_configuration import ConfigurationModel, DjangoModelRef
from django_setup_configuration.configuration import BaseConfigurationStep
from django_setup_configuration.exceptions import ConfigurationRunFailed

from open_inwoner.configurations.bootstrap.utils import get_soap_service
from open_inwoner.soap.models import SoapService
from open_inwoner.ssd.models import SSDConfig


class SSDConfigurationModel(ConfigurationModel):
"""Configuration for the SSD SOAP service backing "Mijn uitkeringen"."""

service_identifier: str = DjangoModelRef(SSDConfig, "service")

class Meta:
django_model_refs = {
SSDConfig: [
"applicatie_naam",
"bedrijfs_naam",
"gemeentecode",
],
}


class SSDConfigurationStep(BaseConfigurationStep):
"""
Configures the SSD SOAP service used to retrieve yearly/monthly benefits
reports for "Mijn uitkeringen".

Only the fields present in the YAML source are applied: re-running this
step, or running it with a YAML source that only lists a handful of
fields, does not reset the fields it omits back to their defaults.
"""

verbose_name = "SSD configuration"
enable_setting = "ssd_config_enable"
namespace = "ssd_config"
config_model = SSDConfigurationModel

def execute(self, model: SSDConfigurationModel) -> None:
config = SSDConfig.get_solo()

try:
service = get_soap_service(model.service_identifier)
except SoapService.DoesNotExist as exc:
raise ConfigurationRunFailed(
"Unable to retrieve SoapService with identifier "
f"`{model.service_identifier}`. Try first configuring the "
"`soap_services_config` step."
) from exc

config.service = service

for field, value in model.model_dump(
exclude={"service_identifier"}, exclude_unset=True
).items():
setattr(config, field, value)

try:
config.full_clean()
config.save()
except ValidationError as exc:
raise ConfigurationRunFailed(
f"Something went wrong while saving SSDConfig: {exc}"
) from exc
13 changes: 13 additions & 0 deletions src/open_inwoner/configurations/bootstrap/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from zgw_consumers.models import Service

from open_inwoner.soap.models import SoapService


def get_service(slug: str) -> Service:
"""
Expand All @@ -10,3 +12,14 @@ def get_service(slug: str) -> Service:
return Service.objects.get(slug=slug)
except Service.DoesNotExist as exc:
raise Service.DoesNotExist(f"{str(exc)} (identifier = {slug})") from exc


def get_soap_service(slug: str) -> SoapService:
"""
Try to find a SoapService and re-raise DoesNotExist with the identifier to
make debugging easier.
"""
try:
return SoapService.objects.get(slug=slug)
except SoapService.DoesNotExist as exc:
raise SoapService.DoesNotExist(f"{str(exc)} (identifier = {slug})") from exc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
soap_services_config_enable: true
soap_services_config:
services:
- identifier: ssd-service
url: http://ssd.internal/
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ssd_config_enable: true
ssd_config:
service_identifier: ssd-service
applicatie_naam: Open Inwoner
bedrijfs_naam: Open Inwoner
gemeentecode: '0000'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ssd_config_enable: true
ssd_config:
service_identifier: ssd-service
Loading