meta/variants.yml π§¬ΒΆ
Per-role matrix-deploy variant overrides. Each list entry declares one variant of the roleβs effective configuration; the development deploy CLI materialises one inventory folder per entry so every variant can be exercised against a real host.
For general documentation rules such as links, writing style, RFC 2119 keywords, and Sphinx behavior, see documentation.md.
For how the file is consumed at runtime (folder-per-round model, --variant / --full-cycle, cleanup behaviour), see variants.md.
Placement πΒΆ
The file MUST live at
roles/<application_id>/meta/variants.yml.It MUST NOT be named
meta/inventory.yml. The Ansible language server auto-applies the inventory schema to that filename, and the variant list does not satisfy it.A role MAY omit the file entirely. The loader then exposes exactly one variant equal to the assembled per-role meta payload (
meta/services.yml+meta/server.yml+meta/rbac.yml+meta/volumes.yml+apply_schema()βdmeta/secrets.yml) unchanged.
File Format πΒΆ
The top-level node MUST be a YAML list. A non-list root is a hard error.
Each list entry MUST be either:
the empty mapping
{}(the canonical no-override entry), ora YAML mapping that mirrors the assembled application payload, so it can override anything reachable under
applications.<app>.<root>(see layout.md). Every root is the name of ameta/topic βservices,server,rbac,volumes,secrets,addons,csp,domains,networks,info,usersβ because the entry is baked intohost_varsraw, which makes its top-level key the inventory path. A key nometa/<topic>.ymlproduces lands where nothing reads it and the override is silently discarded;TestVariantTopLevelKeysin test_layout.py rejects it.
The literal
nullis normalised to{}so a bare-list item stays valid.Scalars at entry level (numbers, strings, lists) are rejected.
Variants are addressed by their zero-based index in the list.
Entry Semantics π§©ΒΆ
A variant is the roleβs assembled per-role meta payload (the same payload applications.<app> exposes; see layout.md) deep-merged with the matching list entry. The deep-merge follows the same rules as the applications lookup: dictionaries merge recursively, scalars and lists are replaced, and the entry has precedence.
Entry
{}produces the unchanged assembled payload.An entry with overrides produces a derived shape (for example WordPress Multisite domains).
Variant 0 is the canonical baseline. The first entry SHOULD manually enumerate every dynamic service-key declared in
meta/services.ymland pin each one toenabled: true, shared: true. The literal-true pins document the roleβs βall dynamics onβ maximum-footprint deploy shape that every non-baseline variant either re-affirms or explicitly disables (see test_non_baseline_explicit_disables.py).{}is permitted only whenmeta/services.ymldeclares no dynamic-enabled service-key, so there is nothing for the baseline to pin (for example pure matrix-driver roles such assvc-bkp-volume-2-local, whose every service-key originates invariants.yml).
Credentials Interaction πΒΆ
A variant entry MAY toggle services.<key>.enabled+shared to pull additional shared providers into the roundβs closure (for example services.ldap.enabled: true to add svc-db-openldap).
The credential generator participates in this overlay so the providers a variant enables receive their schema-defined credentials in the inventory before deploy time. See Credentials Generation in the design doc for the full CI/CD vs standalone flow and the involved CLI flags.
Example πΒΆ
# roles/web-app-wordpress/meta/variants.yml
# variant 0: canonical Single-Site deploy with every dynamic flag on.
- services:
dashboard:
enabled: true
shared: true
matomo:
enabled: true
shared: true
oauth2:
enabled: true
shared: true
# variant 1: Multisite deploy across blog/shop/news subdomains.
- server:
domains:
canonical:
- "blog.{{ DOMAIN_PRIMARY }}"
- "shop.{{ DOMAIN_PRIMARY }}"
- "news.{{ DOMAIN_PRIMARY }}"
services:
dashboard:
enabled: true
shared: true
matomo:
enabled: true
shared: true
oauth2:
enabled: true
shared: true
wordpress:
multisite:
enabled: true
This declares two variants. Variant 0 pins every dynamic service-key from meta/services.yml to enabled: true, shared: true so the maximum-footprint baseline is explicit. Variant 1 keeps the same dynamic flags on and additionally flips Multisite on against three canonical domains.
Adding A Variant βΒΆ
Edit
roles/<application_id>/meta/variants.yml(create the file if absent).Make sure variant 0 pins every dynamic service-key from
meta/services.ymltoenabled: true, shared: true.Append a list entry for the new variant. For every dynamic service-key variant 0 enables, the new entry MUST either keep it on (re-pin
enabled: true, shared: true) or explicitly disable it (enabled: false, shared: false). Add the variant-specific overrides that justify the new entry.If the new variant relies on cleanup steps that the standard inter-round entity purge does not cover, extend the roleβs purge handling. The matrix wrapper invokes the standard purge between rounds for every app whose variant changed.
Add or extend the deep-merge edge-case tests in test_variants.py when the new variant exercises behaviour beyond the existing fixtures (for example list replacement vs. nested scalar override).
What Not To Do π«ΒΆ
You MUST NOT put per-environment overrides into
inventories/<env>/default.ymlfor cases that belong to a single role; use this file instead. The environment inventory keeps only cross-cutting environment knobs.You MUST NOT introduce conditionals or templating tricks at the variant-list level. The deep-merge is straight YAML; complex shape decisions belong inside the role itself.
You MUST NOT name the file
meta/inventory.ymlfor any reason. See the placement rule.