docs(credentials): name the path the code actually reads

Eight prose references still pointed at the pre-rename root, and one of them
did more than mislead a reader: the addons lint told an author to reach a
secret through lookup('config', application_id, 'credentials.<name>') at the
exact moment it rejected their literal, so the guidance produced the next
defect. The check itself only tests whether the value is templated, so no
enforcement changes here.

variants.md gains the reason rather than a corrected list. A variant entry is
baked into host_vars raw, which makes its top-level key the inventory path, so
a key no meta topic produces is silently discarded. The previous list drifted
precisely because it stated the set without stating why it was the set.

design/variants.md was wrong twice over: the old root, and an argument order in
lookup('config', 'credentials', '<provider>') that could never have resolved.
The erpnext key was checked against svc-db-mariadb/vars/main.yml rather than
prefixed on faith.

Left alone on purpose: test_credentials_roundtrip and the layout lint name
meta/schema.yml and schema/main.yml as history and as the legacy directory the
lint forbids. Both are correct.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-08-14 02:02:27 +02:00
1 parent cf0c7f2a40
commit c541e7c22f
8 files changed
+8 -8

No files matched your search

@@ -15,7 +15,7 @@ For how the file is consumed at runtime (folder-per-round model, `--variant` / `
- 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), or
- a YAML mapping that mirrors the assembled application payload, so it can override anything reachable under `applications.<app>.{server,rbac,services,volumes,credentials}` (see [layout.md](../../../design/role/services/layout.md)).
- a YAML mapping that mirrors the assembled application payload, so it can override anything reachable under `applications.<app>.<root>` (see [layout.md](../../../design/role/services/layout.md)). Every root is the name of a `meta/` topic — `services`, `server`, `rbac`, `volumes`, `secrets`, `addons`, `csp`, `domains`, `networks`, `info`, `users` — because the entry is baked into `host_vars` raw, which makes its top-level key the inventory path. A key no `meta/<topic>.yml` produces lands where nothing reads it and the override is silently discarded; `TestVariantTopLevelKeys` in [test_layout.py](../../../../../tests/lint/ansible/roles/meta/test_layout.py) rejects it.
- The literal `null` is 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.
+1 -1
View File
@@ -34,7 +34,7 @@ The dev-deploy CLI threads the variant index through three layers so a variant's
2. The provision CLI parses the JSON and forwards the per-app index as `--variant <N>` to each `infinito administration inventory credentials` subprocess call.
3. `InventoryManager` resolves the root role's config via `get_variants(roles_dir)[app_id][N]` so `resolve_schema_includes_recursive` sees the variant-merged `services` map and discovers every shared provider the variant enables.
The resulting snippets cover every shared provider in the variant's closure, so the deploy stage finds the required `applications.<provider>.credentials` block in the inventory and `lookup('config', 'credentials', '<provider>')` succeeds.
The resulting snippets cover every shared provider in the variant's closure, so the deploy stage finds the required `applications.<provider>.secrets.credentials` block in the inventory and `lookup('config', '<provider>', 'secrets.credentials.<key>')` succeeds.
The variant only affects inventory generation; the Ansible deploy itself reads `applications.<app>` overrides from the variant-resolved inventory and MUST NOT consult variant data at runtime.
+1 -1
View File
@@ -204,7 +204,7 @@ DB numbers (0 / 1 / 2) are stable for v1; if `svc-db-redis` later partitions ten
### Central-service reuse (Decision #7)
- [x] When `services.mariadb.shared=true` (V1 + matching `group_names`), Frappe connects to the central `svc-db-mariadb` via the in-stack `mariadb` network alias using svc-db-mariadb's `credentials.root_password` for `bench new-site`; when `shared=false` (V2/V3) `sys-svc-rdbms` templates a per-role MariaDB container and bench uses the consumer's per-role db password as root.
- [x] When `services.mariadb.shared=true` (V1 + matching `group_names`), Frappe connects to the central `svc-db-mariadb` via the in-stack `mariadb` network alias using svc-db-mariadb's `secrets.credentials.root_password` for `bench new-site`; when `shared=false` (V2/V3) `sys-svc-rdbms` templates a per-role MariaDB container and bench uses the consumer's per-role db password as root.
- [x] Frappe's three Redis logical roles share one instance via DB-number split (`cache=0`, `queue=1`, `socketio=2`) using the in-compose `redis` alias.
- [x] When `services.mariadb.enabled` (always true for ERPNext) is unsatisfiable, `sys-stk-full` fails the deploy at the database-readiness gate before bench-bootstrap runs.
@@ -29,7 +29,7 @@ NODE_TLS_REJECT_UNAUTHORIZED=0
OIDC_ISSUER_URL={{ OIDC.CLIENT.ISSUER_URL | dotenv_quote }}
# nocheck: persona-block-rationale — Akaunting has no OIDC adapter; meta/services.yml pins `sso.flavor: oauth2`, so the proxy only fronts Akaunting's own Laravel login. The single seeded account signs in with `services.akaunting.setup_admin_email` (the administrator's e-mail) and `credentials.setup_admin_password` (vars/main.yml AKAUNTING_ADMIN_PASSWORD) — neither is the Keycloak username/password the shared admin helper types (see TODO.md).
# nocheck: persona-block-rationale — Akaunting has no OIDC adapter; meta/services.yml pins `sso.flavor: oauth2`, so the proxy only fronts Akaunting's own Laravel login. The single seeded account signs in with `services.akaunting.setup_admin_email` (the administrator's e-mail) and `secrets.credentials.setup_admin_password` (vars/main.yml AKAUNTING_ADMIN_PASSWORD) — neither is the Keycloak username/password the shared admin helper types (see TODO.md).
PERSONA_ADMINISTRATOR_BLOCKED=true
# nocheck: persona-block-rationale — meta/services.yml `sso.oauth2.allowed_groups` lists only the web-app-akaunting administrator RBAC group, so biber is rejected at the oauth2-proxy; and without OIDC auto-provisioning no Akaunting account exists for biber behind it either (see TODO.md).
@@ -21,7 +21,7 @@ MATOMO_SERVICE_ENABLED={{ lookup('config', application_id, 'services.matomo.enab
NODE_TLS_REJECT_UNAUTHORIZED=0
# nocheck: persona-block-rationale — the wiki's bureaucrat/sysop is a local account created by maintenance/run.php createAndPromote with credentials.administrator_password (vars/main.yml MEDIAWIKI_ADMINISTRATOR_PASSWORD, tasks/04_admin.yml), not the Keycloak secret ADMIN_PASSWORD carries; $wgPluggableAuth_EnableLocalLogin is false so no native form accepts it, and $wgOpenIDConnect_UseEmailNameAsUserName lands the Keycloak identity on a separate e-mail-named wiki account with no sysop rights (see README "Persona contract opt-outs").
# nocheck: persona-block-rationale — the wiki's bureaucrat/sysop is a local account created by maintenance/run.php createAndPromote with secrets.credentials.administrator_password (vars/main.yml MEDIAWIKI_ADMINISTRATOR_PASSWORD, tasks/04_admin.yml), not the Keycloak secret ADMIN_PASSWORD carries; $wgPluggableAuth_EnableLocalLogin is false so no native form accepts it, and $wgOpenIDConnect_UseEmailNameAsUserName lands the Keycloak identity on a separate e-mail-named wiki account with no sysop rights (see README "Persona contract opt-outs").
PERSONA_ADMINISTRATOR_BLOCKED=true
# nocheck: persona-block-rationale — $wgPluggableAuth_EnableAutoLogin is true (vars/main.yml, rendered by templates/oidc.php.j2), so every anonymous request is bounced straight back into the IdP; the verified unauthenticated landing after in-app logout that the persona contract requires can never be observed on this role (see README "Persona contract opt-outs").
@@ -14,7 +14,7 @@ BIBER_USERNAME={{ lookup('users', 'biber').username | dotenv_quote }}
BIBER_PASSWORD={{ lookup('users', 'biber').password | dotenv_quote }}
CANONICAL_DOMAIN={{ lookup('domain', application_id) | dotenv_quote }}
# nocheck: persona-block-rationale — Moodle's site administrator is created by install_database.php with credentials.user_password (tasks/01_manager_ops.yml), a role-local secret this env never renders; ADMIN_PASSWORD above is the Keycloak secret. In the SSO variant tasks/04_oidc.yml replaces the login page with an alternateloginurl bounce and flips every mdl_user row to auth='oidc', so no form accepts either secret, and the sso-off variants in meta/variants.yml have no OIDC chain to substitute — one env file is rendered per role, not per variant (see README "Persona contract opt-outs").
# nocheck: persona-block-rationale — Moodle's site administrator is created by install_database.php with secrets.credentials.user_password (tasks/01_manager_ops.yml), a role-local secret this env never renders; ADMIN_PASSWORD above is the Keycloak secret. In the SSO variant tasks/04_oidc.yml replaces the login page with an alternateloginurl bounce and flips every mdl_user row to auth='oidc', so no form accepts either secret, and the sso-off variants in meta/variants.yml have no OIDC chain to substitute — one env file is rendered per role, not per variant (see README "Persona contract opt-outs").
PERSONA_ADMINISTRATOR_BLOCKED=true
MATOMO_BASE_URL={{ lookup('tls', 'web-app-matomo', 'url.base') | dotenv_quote }}
@@ -52,7 +52,7 @@ OIDC_ISSUER_URL={{ OIDC.CLIENT.ISSUER_URL | dotenv_quote }}
PEERTUBE_BASE_URL={{ (lookup('tls', 'web-app-peertube', 'url.base') if (lookup('config', application_id, 'services.peertube.enabled') | bool) else '') | dotenv_quote }}
# nocheck: persona-block-rationale — the native Nextcloud administrator signs in with the role-local credentials.administrator_password (meta/schema.yml:11, NEXTCLOUD_DIRECT_LOGIN_PASSWORD above), not with the Keycloak secret, so this role renders LOGIN_USERNAME / LOGIN_PASSWORD instead of the ADMIN_USERNAME / ADMIN_PASSWORD pair the shared helper reads (roles/test-e2e-playwright/files/personas/admin.js:29-30) and the helper has nothing to type; the admin chain is driven by files/playwright/test-login-admin-native.js and test-login-admin-oidc.js (see README "Persona contract opt-outs").
# nocheck: persona-block-rationale — the native Nextcloud administrator signs in with the role-local secrets.credentials.administrator_password (meta/secrets.yml, NEXTCLOUD_DIRECT_LOGIN_PASSWORD above), not with the Keycloak secret, so this role renders LOGIN_USERNAME / LOGIN_PASSWORD instead of the ADMIN_USERNAME / ADMIN_PASSWORD pair the shared helper reads (roles/test-e2e-playwright/files/personas/admin.js:29-30) and the helper has nothing to type; the admin chain is driven by files/playwright/test-login-admin-native.js and test-login-admin-oidc.js (see README "Persona contract opt-outs").
PERSONA_ADMINISTRATOR_BLOCKED=true
# nocheck: persona-block-rationale — Nextcloud's login surface has three shapes depending on the variant (native / oidc_login / sociallogin, files/playwright/_shared.js:19-23) and the #firstrunwizard modal intercepts pointer events on the user menu the generic logout must click (files/playwright/_shared.js:160-175), so biber's chain is driven by files/playwright/test-login-biber-oidc.js and test-login-biber-ldap.js (see README "Persona contract opt-outs").
@@ -222,7 +222,7 @@ class TestAddonsSchema(unittest.TestCase):
errors.append(
f"{prefix} config.{key} inlines a literal secret "
f"{value!r}; use lookup('config', application_id, "
f"'credentials.<name>') instead (or mark "
f"'secrets.credentials.<name>') instead (or mark "
f"'# nocheck: addon-secret' for a non-secret literal). "
f"({rel})"
)