Files
computer-playbook/docs/contributing/design/role/services/base.md
T
kevinveenbirkenbach 4187237ed0 docs(req-21): rename stale oidc/oauth2 service-gate refs to sso
Three docs still referenced the pre-collapse service keys:

* docs/contributing/artefact/files/role/playwright.specs.js.md — biber
  and administrator auth-chain diagrams listed
  `skipUnlessServiceEnabled('oidc' | 'oauth2' | 'ldap')`; the unified gate
  is `'sso' | 'ldap'`.
* docs/contributing/design/role/services/base.md — the `lookup('service',
  ...)` example block carried the now-invalid `'oidc'` key; replaced
  with `'sso'`.
* docs/requirements/019-playwright-meta-services-parity.md — the
  prometheus, matomo, and mailu rows still spelled
  `safeSkipUnlessEnabled("oauth2"|"oidc")` and
  `isServiceEnabled("oauth2")`; all rewritten to `"sso"`.
2026-05-28 09:43:52 +02:00

7.8 KiB

Service Management 🧱

This page describes 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 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 meta/services.yml, where the file root IS the services map keyed by <entity_name> (no compose: and no services: wrapper). See layout.md for the full per-role meta layout.

Role-Local Service Metadata 🏷️

Service providers are self-describing. The provider role owns:

  • enabled
  • shared
  • optional provides
  • optional canonical

Example (file root of meta/services.yml):

keycloak:
  enabled: false
  shared: true
  provides: oidc

Canonical aliases are also role-local:

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 services
  • derives deploy type and loader bucket from the role name
  • resolves provides: and canonical:
  • validates and applies run_after: from meta/services.yml.<primary_entity>

Load Order 🧮

main.yml is the single loader entry point for all shared services.

It runs from 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: declared on the provider role's primary entity in meta/services.yml (i.e. services.<primary_entity>.run_after).

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.

main.yml:

  • queries the ordered discovered service list
  • checks lookup('service', service_key).required
  • skips roles already protected by run_once_*
  • loads services through 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 services.<feature>.enabled flags. It does not load provider roles.

Lookup Plugins 🔎

service 🧷

File: service.py

Examples:

lookup('service', 'matomo')
lookup('service', 'sso')
lookup('service', 'web-svc-cdn')

Returns:

  • id
  • role
  • enabled
  • shared
  • required is True when some deployed app has the service with both enabled: true AND shared: true, directly or transitively through its own enabled service dependencies. This is the flag consumers should gate on when deciding whether to load, integrate with, or configure the service. A plain enabled flag is not enough on its own, because a service that is enabled but never shared with another app is not actually contractually required by anyone.

Behavior:

  • accepts either a service key or a provider role name
  • resolves aliases through canonical
  • resolves provider roles through discovered primary service keys
  • computes required transitively from enabled shared services

service_registry 📚

File: 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: 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:

  • svc-db-mariadb provides mariadb
  • svc-db-postgres provides postgres

Applications express database choice directly via the file root of meta/services.yml:

mariadb:
  enabled: true
  shared: true

or:

postgres:
  enabled: true
  shared: false

The lookup('database', ...) API is the convenience accessor for database connection values; it resolves the active direct database service from the role-local keys above.

File Purpose
service_registry.py Service discovery, provides, canonical, bucket detection, run_after ordering
service_registry.py lookup Exposes the discovered registry and ordered provider list to Ansible
service.py Resolves service flags and transitive need
applications_current_play.py Builds the current play app graph with shared service deps
main.yml Single shared-service loader entry point
load_service.yml Per-service load helper used by the central service loader
01_constructor.yml Calls the service loader during constructor
load_app.yml Run-once role loader
test_service_registry.py Unit tests for discovery, buckets, and run_after ordering
test_service.py Unit tests for lookup('service', ...)
test_resolvable.py Integration checks for discovered service resolution
test_canonical.py Canonical alias consistency checks
test_transitive_dependencies.py Integration coverage for transitive dependency resolution