Files
computer-playbook/plugins
kevinveenbirkenbach 5bbd0ff307 feat(plugins/depends_on): mode-aware lookup + migrate every *compose*.yml.j2 to it
The compose/swarm split on `depends_on:` shape (map form with
`condition:` vs. list form, per `docker stack deploy` constraint)
was being hand-coded as a `{% if DEPLOYMENT_MODE == 'swarm' %}` gate
around every block. Repetitive, easy to forget, and templates that
did remember it had to repeat the same six-line pattern per service.

The new `depends_on` lookup encapsulates the shape switch:

    {{ lookup('depends_on', {SVC: 'service_healthy', …}) }}     # mapping
    {{ lookup('depends_on', [SVC_A, SVC_B]) }}                  # all default

- Compose -> map form with explicit `condition:` per entry.
- Swarm   -> list form (just names; swarm ignores depends_on
  ordering semantically but accepts the syntax).
- Mapping `None` value uses the default condition (`service_started`,
  matching Docker Compose's behaviour when no condition is given).
- Plain list of names uses the default for every entry.

The output is shaped so the call site reads naturally at the
service-member column:

      some_service:
        image: …
        {{ lookup('depends_on', {DB: 'service_healthy'}) }}     # at col 4
        volumes: …

Concretely the lookup emits line 1 WITHOUT leading whitespace and
lines 2+ with the `indent` kwarg (default 4) plus the YAML-relative
nested indent. Jinja substitution applies the template's leading
spaces to line 1 only, so the column-4 call placement makes
`depends_on:` land at column 4 and the children at 6 / 8. Lines 2+
carry their own indent because Jinja does not propagate template-side
leading whitespace to continuation lines.

Plugin + tests:

- plugins/lookup/depends_on.py: ~190 lines. Mapping / list / empty
  input handling, default_condition kwarg, condition validation
  against the closed set `{service_started, service_healthy,
  service_completed_successfully}`, DEPLOYMENT_MODE resolution via
  templar with `mode=` test-only override, indent kwarg semantics
  documented in the module docstring.
- tests/unit/plugins/lookup/test_depends_on.py: 27 cases covering
  TestInputNormalisation (mapping / list / empty / typo / single
  string rejection / non-string sequence rejection / unsupported
  type rejection / arity errors), TestModeBehaviour (compose / swarm
  / unknown-falls-to-compose / missing-DEPLOYMENT_MODE-falls-to-compose
  / mode-kwarg override), TestOutputFormatting (line-1 unindented
  exact-string assertion, indent=0 edge case, indent=6 edge case,
  insertion-order stability for diff-stable output).

Lint:

- tests/lint/ansible/jinja/test_compose_template_uses_depends_on_lookup.py:
  rule key `compose-depends-on-must-use-lookup`. Flags three patterns
  in any `*compose*.yml.j2` under `roles/`:
    1. literal `depends_on:` keyword line
    2. flow-form `depends_on: [a, b]`
    3. literal `condition: service_*` line
  The pre-existing `lookup('container_depends_on', application_id)`
  is intentionally not flagged: its call form contains no literal
  `depends_on:` token in source. Scan target broadened to
  `name.endswith('.yml.j2') and 'compose' in name` so
  `compose.override.yml.j2` and `compose-inits.yml.j2` are covered
  alongside `compose.yml.j2` and `*.compose.yml.j2`.

Migration (43 depends_on blocks across 17 templates):

- erpnext (6): configurator + backend + websocket + scheduler +
  queue-short + queue-long + frontend chains.
- mailu (8): imap + smtp + oletools + antispam + antivirus + webdav +
  fetchmail + webmail.
- zammad (4): rails + 3 worker shapes.
- shopware (4): web + worker + scheduler + opensearch, init-gated.
- jitsi (3): three single-key dependencies on JITSI_PROSODY_CONTAINER.
- kix (3): backend + frontend + proxy.
- bigbluebutton/compose.override.yml.j2 (2): bbb-web + etherpad,
  inside the existing BBB_COLLABORA_ENABLED gate.
- moodle (2): nginx + cron, both depending on moodle.
- opentalk (2): application + recorder.
- taiga (2): events + gateway.
- matrix (1): loop-driven via `{% set deps = {} %} +
  __setitem__` escape hatch over MATRIX_BRIDGES.
- matomo (1): bootstrap service.
- nextcloud (1): whiteboard sidecar.
- openproject (1): proxy.
- funkwhale (1): front.
- magento (1): nginx.
- flowise (1): postgres + redis.

Pre-existing `lookup('container_depends_on', application_id)` calls
(erpnext, nextcloud, moodle, zammad, taiga, matrix, etc.) are left
untouched -- that lookup auto-detects DB / redis from the application
config and emits its own block; it is the app-aware counterpart to
this structural lookup.

Every call placement repositioned to column 4 (standard
compose-service-member indent) so the new line-1-unindented semantic
renders correctly. Eight templates needed the column-4 reposition
(via a mechanical one-shot script that was deleted after).

`make test` green; new lint + 27 unit tests green; independent
re-grep across all `*compose*.yml.j2` finds zero residual raw
`depends_on:` / `condition:` lines.
2026-06-14 19:08:45 +02:00
..

Ansible Plugins (plugins/) 🧩

This document is the central reference for plugin types and plugin placement in Infinito.Nexus.

Current Project Layout 🗂️

plugins/
├── action/   # custom action plugins
├── filter/   # custom Jinja2 filters
└── lookup/   # custom lookup plugins

Configured plugin paths in this repository are defined in ansible.cfg:

  • action_plugins = ./plugins/action
  • filter_plugins = ./plugins/filter
  • lookup_plugins = ./plugins/lookup

All Common Ansible Plugin Types 📚

The table below summarizes common Ansible plugin types and their conventional directory names.

Emoji Plugin Type Conventional Directory Purpose
⚙️ action action_plugins/ or plugins/action/ Wrap/extend module execution behavior at task runtime.
🔐 become become_plugins/ Implement privilege escalation methods.
💾 cache cache_plugins/ Persist gathered facts and cached data between runs.
📣 callback callback_plugins/ React to play/task events (logging, reporting, notifications).
🖧 cliconf cliconf_plugins/ Define CLI command transport behavior (mainly network platforms).
🔌 connection connection_plugins/ Control how Ansible connects to targets (ssh, local, etc.).
🔍 filter filter_plugins/ or plugins/filter/ Add Jinja2 filters for data transformation.
🌐 httpapi httpapi_plugins/ API transport layer over HTTP(S), mainly for network devices.
🧭 inventory inventory_plugins/ Build dynamic inventories from APIs, services, or files.
👉 lookup lookup_plugins/ or plugins/lookup/ Resolve dynamic values during playbook processing.
🛰️ netconf netconf_plugins/ NETCONF transport integration for network automation.
🐚 shell shell_plugins/ Adjust shell command wrapping/execution behavior.
🧠 strategy strategy_plugins/ Customize task scheduling/execution strategy.
✅ test test_plugins/ Add Jinja2 tests used in conditions and templates.
📦 vars vars_plugins/ Load variables from custom sources.

Which Plugin Type to Use 🧭

action ⚙️

Use when task execution behavior itself must be changed or wrapped.

Examples:

  • centralized retry logic (uri_retry.py),
  • argument normalization before module dispatch,
  • pre/post task orchestration.

filter 🔍

Use for pure data transformation in Jinja2 expressions.

Examples:

  • mapping/normalizing dictionaries,
  • deriving computed fields for templates,
  • deterministic formatting helpers.

lookup 👉

Use when a value must be dynamically resolved at runtime.

Examples:

  • computing IDs from role metadata,
  • resolving paths/version data,
  • reading structured project data for templates/tasks.

callback 📣

Use to hook into execution events and shape output/reporting.

inventory 🧭

Use when host/group data should come from a dynamic source.

strategy 🧠

Use to alter how tasks are scheduled/executed across hosts.

vars 📦

Use to load variables from custom backends.

connection, become, shell 🔌🔐🐚

Use to customize low-level execution and privilege mechanics.

cache 💾

Use to persist facts/results across runs for performance and consistency.

cliconf, httpapi, netconf 🖧🌐🛰️

Use in network automation contexts requiring protocol-specific transport behavior.

test ✅

Use to add custom Jinja2 test operators for conditional logic.

Best Practices 🧪

  • Keep plugin interfaces explicit and stable.
  • Keep plugin logic focused; move shared logic to utils/.
  • Add unit tests for plugin behavior and integration tests for end-to-end flows.
  • Prefer deterministic outputs and avoid hidden global state.
  • Document any plugin-specific task arguments in the plugin docstring and README.

References 🔗