Files
computer-playbook/docs/contributing/design/services.md
T
kevinveenbirkenbachandClaude Opus 4.7 0b25c6646f docs(iam): add OIDC/LDAP design guide, move services.md to design/
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>
2026-04-19 17:18:41 +02:00

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-cdn provides the primary service key cdn
  • web-app-keycloak provides oidc
  • svc-db-mariadb provides mariadb

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:

  • enabled
  • shared
  • 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.
  • frontend vs. backend is 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: and canonical:
  • validates and applies run_after: from meta/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:

  1. universal
  2. workstation
  3. server
  4. web-svc
  5. web-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:

  • id
  • role
  • enabled
  • shared
  • needed

Behavior:

  • accepts either a service key or a provider role name
  • resolves aliases through canonical
  • resolves provider roles through discovered primary service keys
  • computes needed transitively 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-mariadb provides mariadb
  • svc-db-postgres provides postgres

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.

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