Introduce docs/contributing/design/iam/ with a docs README, common.md for cross-cutting principles, and per-provider pages oidc.md and ldap.md. The OIDC page documents the OIDC.* variable tree, claim attribute SPOT (preferred_username), and the rule that app-specific Keycloak protocol mappers belong in per-client scopes, not the shared default.json.j2. The LDAP page documents the LDAP.* variable tree, attribute SPOT, and the paired-identifier rule with OIDC. Move services.md from docs/contributing/code/ to docs/contributing/design/ so the design/ folder is the SPOT for cross-cutting platform conventions. Update the two architecture.md cross-links and the requirement doc. Cross-link the new guides from 13_oidc.yml, 14_ldap.yml, and the web-app-keycloak and svc-db-openldap role READMEs. Fix nearby README compliance issues per readme_md.md (svc-db-openldap H1, missing Credits section in web-app-jira, Credits formatting drift). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
7.6 KiB
Service Management
This page is the SPOT for how shared services are declared, discovered, ordered, loaded, and injected.
What Is a Service?
A service is a reusable dependency that an application enables via
compose.services.<service_key>.
Examples:
web-svc-cdnprovides the primary service keycdnweb-app-keycloakprovidesoidcsvc-db-mariadbprovidesmariadb
Each service entry lives in the provider role's own
config/main.yml under compose.services.<entity_name>.
Role-Local Service Metadata
Service providers are self-describing. The provider role owns:
enabledshared- optional
provides - optional
canonical
Example:
compose:
services:
keycloak:
enabled: false
shared: true
provides: oidc
Canonical aliases are also role-local:
compose:
services:
cdn:
enabled: false
shared: true
css:
enabled: true
shared: true
canonical: cdn
javascript:
enabled: true
shared: true
canonical: cdn
Rules:
- The primary service entry is the role entity name returned by
get_entity_name. provides:is only used when the public service name differs from the entity name.canonical:is only used on alias entries that resolve back to the primary service key.frontendvs.backendis derived from the role name prefix, not stored in config.
Service Discovery
Service discovery is built from role configs, not from a central registry file.
Primary implementation files:
The discovery layer:
- scans role configs from
roles - discovers provider entries from
compose.services - derives deploy type and loader bucket from the role name
- resolves
provides:andcanonical: - validates and applies
run_after:frommeta/main.yml
Load Order
roles/sys-utils-service-loader/tasks/main.yml
is the single loader SPOT for all shared services.
It runs from tasks/stages/01_constructor.yml
before the normal application stage.
Global bucket order:
universalworkstationserverweb-svcweb-app
Within the same bucket, ordering is refined by run_after: in the provider role's
meta/main.yml.
Rules:
run_after:entries are role names, not service keys.- Cross-type
run_after:is invalid and fails hard. - Later-bucket dependencies are invalid and fail hard.
- Cross-type service dependencies do not need
run_after:because constructor-stage loading already brings backend services up before normal app deployment.
Loading vs Injection
Service loading and frontend injection are separate mechanisms.
Loading
Loading decides whether the provider role is deployed at all.
roles/sys-utils-service-loader/tasks/main.yml:
- queries the ordered discovered service list
- checks
lookup('service', service_key).needed - skips roles already protected by
run_once_* - loads services through
tasks/utils/load_app.yml
Frontend service probe/load helper:
Injection
Injection decides whether a deployed app gets extra nginx integration such as dashboard, logout, CSS, or JavaScript hooks.
This stays in:
Injection still reads the current app's compose.services.<feature>.enabled flags.
It does not load provider roles.
Lookup Plugins
service
File:
plugins/lookup/service.py
Examples:
lookup('service', 'matomo')
lookup('service', 'oidc')
lookup('service', 'web-svc-cdn')
Returns:
idroleenabledsharedneeded
Behavior:
- accepts either a service key or a provider role name
- resolves aliases through
canonical - resolves provider roles through discovered primary service keys
- computes
neededtransitively from enabled shared services
service_registry
File:
plugins/lookup/service_registry.py
Examples:
query('service_registry') | first
query('service_registry', 'ordered') | first
Modes:
- default: full discovered registry mapping
ordered: ordered primary service entries for the service loader
applications_current_play
File:
plugins/lookup/applications_current_play.py
Builds the current-play application set including:
- group-selected roles
- transitive shared service dependencies
- meta dependencies
Database Services
Relational databases are regular services now:
svc-db-mariadbprovidesmariadbsvc-db-postgresprovidespostgres
Applications express database choice directly via:
compose:
services:
mariadb:
enabled: true
shared: true
or:
compose:
services:
postgres:
enabled: true
shared: false
The lookup('database', ...) API remains as the convenience accessor for database
connection values, but it now resolves the active direct database service from those
role-local keys instead of compose.services.database.type.
Related Files
| File | Purpose |
|---|---|
utils/service_registry.py |
Service discovery, provides, canonical, bucket detection, run_after ordering |
plugins/lookup/service_registry.py |
Exposes the discovered registry and ordered provider list to Ansible |
plugins/lookup/service.py |
Resolves service flags and transitive need |
plugins/lookup/applications_current_play.py |
Builds the current play app graph with shared service deps |
roles/sys-utils-service-loader/tasks/main.yml |
Single shared-service loader SPOT |
roles/sys-utils-service-loader/tasks/load_service.yml |
Per-service load helper used by the central service loader |
tasks/stages/01_constructor.yml |
Calls the service loader during constructor |
tasks/utils/load_app.yml |
Run-once role loader |
tests/unit/utils/test_service_registry.py |
Unit tests for discovery, buckets, and run_after ordering |
tests/unit/plugins/lookup/test_service.py |
Unit tests for lookup('service', ...) |
tests/integration/test_services_resolvable.py |
Integration checks for discovered service resolution |
tests/integration/test_services_canonical.py |
Canonical alias consistency checks |
tests/integration/test_service_transitive_dependencies.py |
Integration coverage for transitive dependency resolution |