Commit Graph
51 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 4.7 c6f4b7fb4b feat(web-app-fediwall): integrate Fediwall as multi-wall public timeline aggregator
New Ansible role rendering one Fediwall instance with one or more
configurable walls under https://fediwall.<DOMAIN_PRIMARY>/<slug>/. Each
wall's `wall-config.json` is forwarded VERBATIM from
meta/services.yml.fediwall.walls[*].config — the YAML there is the
single source of truth, the role's template applies no defaults. Two
matrix-deploy variants ship: variant 0 inherits the services.yml
default (one wall, polls all active Mastodon-API-compatible siblings via
the new `lookup('fediwall_active', ...)` plugin); variant 1 overrides
with two explicitly configured walls (default + mastodon-only) so the
per-wall server scoping is observable end-to-end.

End-to-end Playwright spec drives biber's first OIDC login on Mastodon
and ldapauth-mediated form login on Friendica through the same web UI a
human would use — biber's local accounts come into existence lazily on
first SSO login, exactly as in production. The cross-Fediverse test is
data-driven against each wall's `wall-config.json.servers`: a sibling's
post is visible iff its host is listed there.

Cross-cutting fixes that surfaced during the iteration ship alongside:

- utils/cache + utils/templating: type-preserving render path for
  single-Jinja-expression scalars in `lookup('config', ...)`.
  `_templar_render_best_effort` always coerced its output to `str`, so a
  list-returning lookup like `{{ lookup('fediwall_active', 'domains') }}`
  collapsed to its Python repr. `_render_scalar` now detects the
  `{{ ... }}`-only shape and routes it through the new
  `_templar_render_preserve_type` helper, which returns templar's
  native value (list/dict/etc.) instead of stringifying.

- web-app-friendica/tasks/04_addons.yml: force a `disable` + `enable`
  cycle for enabled addons. Friendica's `addon enable` only fires the
  `_install` hooks on the disabled→enabled transition, so an
  already-enabled-but-broken state (the addon shows up in `list
  enabled` but its `_install`-registered hooks are missing from the
  `hook` DB table) silently rejected every ldapauth login at runtime.
  Cycling through disable+enable reliably re-registers the hooks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 19:50:05 +02:00
kevinveenbirkenbachandClaude Opus 4.7 623035618c refactor(cache): route every yaml call in tests/ through utils.cache.yaml
Stops every direct `yaml.safe_load`/`safe_load_all`/`safe_dump`/`dump`
call across the test corpus from re-parsing files the production cache
already holds. Each migrated test loads role meta, group_vars, inventory
fixtures, etc. through the shared cache, so a single `make test` run
parses every YAML file at most once across all suites.

Cache module changes (utils/cache/yaml.py):
  - Unify single- and multi-doc cache: one `_CACHE: Dict[sig, list]`
    holding the `safe_load_all` payload. Single-doc helpers
    (load_yaml / load_yaml_any) unwrap docs[0] and raise on >1
    document, preserving the old strict semantics. A file consumed
    both as single-doc and multi-doc is now parsed exactly once.
  - Add load_yaml_all (path-cached) and load_yaml_all_str (string,
    no cache) so multi-doc consumers no longer need a direct
    `yaml.safe_load_all` plus a separate file read.
  - dump_yaml now accepts any safe-dumpable structure (dict, list,
    scalar) so test fixtures writing list-of-tasks YAML can use it.

Lint guard (tests/lint/repository/test_no_direct_yaml_calls.py):
  - Extend the scanned tree to include `tests/` so future test code
    is forced through the cache, with the existing
    `# noqa: direct-yaml` per-call opt-out for genuine raw-yaml
    fixture needs.

Test corpus:
  - 89 test files migrated to load_yaml / load_yaml_any /
    load_yaml_str / load_yaml_all / load_yaml_all_str / dump_yaml /
    dump_yaml_str. No `# noqa` shortcuts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 12:16:20 +02:00
kevinveenbirkenbachandClaude Opus 4.7 b9353118d1 fix(meta): close req-008 sweep gaps — lost suppressions, silent test breakage, 1 prod bug
The req-008/009/010 refactor's mechanical migration left several gaps
the test suite could not catch because the test suite itself was
silently broken in spots. Audit-and-fix pass closes those.

1. Production bug: backup discovery filter
==========================================
roles/sys-ctl-bkp-docker-2-loc/filter_plugins/find_dock_val_by_bkp_entr.py
still read `app.compose.services` from the materialised application
payload. After req-008 the file root IS the services map (no
`compose.services` envelope), so the filter walked an empty dict and
silently returned `[]` for every backup discovery call. Rerouted to
the new `app.services` path. Test fixtures updated to match.

2. External test broken by source_file mismatch
===============================================
tests/external/docker/test_image_versions.py:61 filtered for
`ref.source_file == "config/main.yml"`, but
utils/docker/image/discovery emits `meta/services.yml` since req-008.
The filter rejected every candidate → `entries == []` →
`self.assertTrue(entries)` failed. Switched the filter to the new path.

3. Five lint/integration tests silently iterating empty dicts
=============================================================
The following tests still read `cfg.get("compose").get("services")`
(the legacy compose-wrapper path). After req-008 every meta/services.yml
file root IS the services map, so these tests have been iterating
empty dicts and quietly passing without checking anything:

* tests/lint/ansible/test_service_shared_consistency.py:72
* tests/integration/backups/test_enabled.py:33
* tests/integration/backups/test_no_stop_required.py:35
* tests/integration/docker/test_services_image_version_valid.py:28
* tests/integration/oauth2_oidc/test_mutual_exclusive.py:26

All five rerouted to the file-root convention. test_service_shared_consistency
immediately surfaced 24 missing `# noqa: shared` markers (see below).

4. Lost suppression markers (51 total)
======================================
The migration script (tasks/utils/migrate_meta_layout.py) used
ruamel.yaml round-trip which preserved most comments but lost
trailing-comment markers on inline list items (a known ruamel edge
case). Restored verbatim from main:

* 13 `# nocheck: url` markers on meta/server.yml CSP-whitelist entries
  (web-app-bluesky, web-app-flowise, web-app-matomo, web-svc-onlyoffice).
  These cover CDN root URLs that legitimately return 404 for the bare
  domain (Google Fonts, Matomo CDN, BlueSky API endpoint).
* 1 `# nocheck: docker-version` on web-app-moodle/meta/services.yml
  (LTS pin for OIDC compatibility).
* 20 `# noqa: shared` markers on meta/services.yml — only the markers
  that are still NEEDED. Initial restoration mirrored all 24 markers
  from main, but 4 of them turned out redundant against the current
  shape: bigbluebutton/postgres, bookwyrm/postgres, discourse/postgres,
  magento/mariadb already declare `shared:` (true or false), so the
  lint rule does not fire and the marker is dead weight. Skipped them.
* 13 `# noqa: email` markers on meta/services.yml — every covered
  `email:` block has `{enabled: false, shared: false}`, exactly the
  pattern the email-integration lint expects to suppress.

5. Test-fixture cleanups for backwards-compat clarity
=====================================================
Several unit tests still wrapped synthetic application payloads in
`{"compose": {"services": {...}}}`. Harmless because the production
code paths under test were already migrated, but misleading for future
contributors. Reshaped to match the post-req-008 file-root
convention:

* tests/unit/plugins/filter/test_node_autosize.py
* tests/unit/plugins/filter/test_docker_service_enabled.py
* tests/unit/utils/manager/test_inventory_manager.py (5 occurrences)
* tests/unit/cli/deploy/development/test_inventory.py
* tests/unit/roles/sys-ctl-bkp-docker-2-loc/filter_plugins/test_find_dock_val_by_bkp_entr.py

Validation
==========
* Unit:        1572 passed.
* Lint:        56 passed.
* Integration: 161 passed (1 skipped).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 15:25:38 +02:00
kevinveenbirkenbachandClaude Opus 4.7 b3de2a4d7c refactor(meta): land req-011 meta/info.yml + utils/cache cleanup
req-011 — descriptive role-level metadata
  * Move logo / homepage / video / display out of
    meta/main.yml.galaxy_info into a new project-owned
    roles/<role>/meta/info.yml (file-root convention, no `info:` wrapper).
  * 78 role meta/main.yml files migrated via ruamel round-trip
    (key-order / quoting / inline-comments preserved). Roles with none
    of the four fields are left untouched (no empty info.yml created).
  * web-app-dashboard/lookup_plugins/docker_cards.py reads logo +
    display from meta/info.yml; description / galaxy_tags continue to
    come from galaxy_info (Galaxy-spec fields).
  * docker_cards.py now routes ALL its file reads through the
    process-wide caches: load_yaml_any for vars/main.yml,
    meta/main.yml, meta/info.yml; read_text from utils.cache.files for
    README.md. The orphan `import yaml` is gone.

New utils/cache/files (renamed from tests/utils/fs)
  * The process-wide filesystem cache (read_text, iter_project_files,
    iter_project_files_with_content, _all_project_files) is not
    test-specific; it now lives under utils/cache/files.py next to the
    other process-wide caches. 34 consumer imports rewritten across
    tests/ and docs/. The empty tests/utils/ package is removed.
  * New unit suite tests/unit/utils/cache/test_files.py — 12 cases
    covering read_text, iter_project_files (skip-dirs, extensions,
    exclude_tests, exclude_dirs, walk-cache invariants), the combined
    iterator, non-UTF-8 silent skip, and _reset.

utils/cache redundancy cleanup
  * applications.py + users.py + cli/create/inventory/services_disabler.py
    drop their direct yaml.safe_load calls and their private
    _load_yaml_mapping/_load_yaml_variant_list copies; everything routes
    through utils.cache.yaml.load_yaml / load_yaml_any. The dead
    utils.cache.base._load_yaml_mapping is removed.
  * users.py:_load_store_users (tokens.yml read) likewise migrated.
  * _load_yaml_variant_list moves out of base.py to applications.py
    (its only consumer) and is renamed _load_variants_overrides — also
    routes through the cached loader now.
  * utils.cache.yaml._reset_cache_for_tests is renamed _reset (kept as
    alias) for parity with the per-domain reset helpers.
  * utils.cache.files grows a _reset() that clears its two LRU caches.
  * utils.cache.__init__._reset_cache_for_tests now orchestrates ALL
    six caches (applications, users, domains, base, yaml, files) so
    tests get clean state across cache layers — needed because
    applications/users now share the yaml cache and would otherwise
    serve stale parses across reset boundaries.
  * utils.cache.files imports PROJECT_ROOT from base instead of
    redefining it.
  * tests/unit/utils/cache/test_data.py + test_base.py: obsolete
    TestLoadYamlMapping / TestLoadYamlVariantList classes deleted
    (covered by test_yaml.py + test_variants.py).
  * README.md updated for the new module list (files, yaml) and the
    six-module orchestrator.

Galaxy schema lint
  * tests/lint/repository/test_role_meta_main_galaxy_schema.py: split
    galaxy_info field requirements into hard-required {author,
    description, license} and soft-recommended {min_ansible_version,
    platforms}. Soft-recommended -> emit a GitHub Actions ::warning::
    annotation via utils.annotations.message.warning (titled
    "meta/main.yml galaxy_info" with file= pointing at the offender),
    no test failure. Rationale: min_ansible_version + platforms are
    only enforced by Galaxy when *publishing*, which Infinito.Nexus
    does not do.
  * Galaxy lint goes green (was 226 offenders before req-011 +
    license_url/repository/documentation strip; was 62 before this
    relaxation; now 0 hard offenders, 62 soft warnings).
  * roles/svc-opt-keyboard-color/meta/main.yml: backfill `license`
    (only role missing the hard-required field).

New lint
  * tests/lint/repository/test_role_meta_info.py — enforces meta/info.yml
    file-root convention, the four-field allowlist (logo, homepage,
    video, display), and shape (logo.class string, homepage/video
    non-empty strings, display bool). Also fails if any
    meta/main.yml.galaxy_info still carries one of the four migrated
    fields.

Docs
  * docs/contributing/design/services/layout.md: new section
    "Descriptive Role-Level Metadata — meta/info.yml" with allowed-
    fields table, optionality rule, materialised path, and consumer
    note. File-Layout table grows the meta/info.yml row;
    meta/main.yml row gets the explicit "no logo/homepage/video/display"
    note. req-011 added to header and Related Pages.
  * utils/cache/README.md: files.py + yaml.py rows added; six-module
    reset orchestrator documented.

Validation
  * Unit:        1573 passed.
  * Lint:        55 passed.
  * Integration: 161 passed (1 skipped) via pre-commit hook.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 12:27:17 +02:00
kevinveenbirkenbachandClaude Opus 4.7 ed04dad5d5 refactor(meta): land req-008/009/010 role-meta layout/networks/ports/run_after migration
Single atomic landing for three back-to-back role-meta refactor requirements
(see docs/requirements/008-role-meta-layout.md, 009-per-role-networks-and-ports.md,
010-role-meta-runafter-lifecycle-migration.md).

req-008 — role meta layout
  * Move every role's metadata under meta/<topic>.yml with file-root
    convention (no wrapping key matching the basename):
      - schema/main.yml + config/main.yml.credentials → meta/schema.yml
      - users/main.yml                                → meta/users.yml
      - config/main.yml.server                        → meta/server.yml
      - config/main.yml.rbac                          → meta/rbac.yml
      - config/main.yml.compose.services + every other top-level key
        of config/main.yml inlined under <entity>.<key>
                                                      → meta/services.yml
      - config/main.yml.compose.volumes               → meta/volumes.yml
  * Drop the legacy schema/, users/, config/ directories from every role.
  * meta/schema.yml: support nested credential keys, optional `default:`
    field (Jinja literal, not rendered/validated/regenerated), implicit
    `algorithm: plain`.
  * Materialised paths drop the `compose.` prefix:
      compose.services.<e>.<…> → services.<e>.<…>
      compose.volumes.<k>      → volumes.<k>
      <former-top-level>.<…>   → services.<entity_name>.<former-top-level>.<…>
  * Update every Python consumer (utils/cache, utils/manager, utils/applications,
    plugins/lookup, plugins/filter, cli/*) plus every Jinja template, vars/*.yml
    and task file to the new paths.
  * No legacy fallback. Repository-wide grep for `compose.services.` /
    `compose.volumes.` / `schema/main.yml` / `users/main.yml` / `config/main.yml`
    is empty outside docs/requirements/ and tasks/utils/migrate_meta_layout.py.
  * Add tests/lint/repository/test_role_meta_layout.py guarding against the
    old paths returning, and an explicit lint guard that meta/variants.yml
    files do not carry a `compose:` wrapper.

req-009 — per-role networks/ports
  * Move group_vars/all/08_networks.yml.defaults_networks.local.<role>.{subnet,
    dns_resolver} into roles/<role>/meta/server.yml.networks.local.{subnet,
    dns_resolver}.
  * Delete group_vars/all/09_ports.yml entirely; ports now live on the
    service entity that exposes them at
    roles/<role>/meta/services.yml.<entity>.ports.{inter,local,public}.
  * Rename + int-cast every former top-level `services.<entity>.port`
    field to `services.<entity>.ports.inter` (single int).
  * Always-category-keyed maps for ports.local / ports.public; relay
    spans use {start, end} integers.
  * Add PORT_BANDS map to group_vars/all/08_networks.yml — single source
    of truth for the per-<scope>.<category> band ranges, consumed by both
    the new CLI helpers and the lint test.
  * New CLI helpers (cli meta ports suggest, cli meta networks suggest)
    walk the live role tree gap-first then increment-fallback, with
    deterministic output and non-zero exit on capacity overflow.
  * cli create role consumes both helpers when scaffolding a new role.
  * Lint extensions: bare-int port shape rejection, host-bound port
    collision check (single ints + relay span integers, inter excluded),
    band-membership check with documented allow-list for the legacy
    web-app-bigbluebutton.bigbluebutton.local.http: 48087 entry.

req-010 — run_after / lifecycle migration
  * Move galaxy_info.run_after and galaxy_info.lifecycle out of
    meta/main.yml into meta/services.yml.<primary_entity>.{run_after,
    lifecycle}; <primary_entity> is the value returned by
    get_entity_name(role_name).
  * For multi-entity roles whose primary entity has no compose service
    (web-app-bluesky → bluesky, web-app-matrix → matrix, desk/dev/drv/
    gen roles), create a dedicated metadata-only top-level entry under
    <primary_entity>.
  * Introduce utils/roles/meta_lookup.py with get_role_run_after /
    get_role_lifecycle that resolve <primary_entity> once and read from
    meta/services.yml; degrade gracefully when meta/services.yml is
    absent OR the field is absent; raise MetaServicesShapeError when the
    file exists but is malformed.
  * Update every consumer (utils/service_registry, utils/invokable,
    cli/meta/applications/resolution/run_after/__main__, cli/meta/roles/
    lifecycle_filter/__main__, cli/meta/applications/resolution/combined,
    cli/build/{tree,graph,role_include}, cli/deploy/development/{common,
    init,deps,deploy}, plugins/filter/canonical_domains_map, etc.) to
    use the helper.
  * Lint guards: no run_after/lifecycle in meta/main.yml; lifecycle
    values restricted to {planned,pre-alpha,alpha,beta,stable,deprecated};
    run_after/lifecycle never on a non-primary entity; empty run_after
    lists rejected.

Also folded into this change set
  * services_disabler now writes `services.<svc>.{enabled,shared}` into
    host_vars instead of the old `compose.services.<svc>.{...}` path; the
    discover/find pair walks meta/services.yml and only treats entries
    carrying enabled/shared/provides as provider declarations (so
    metadata-only entries from req-010 do not create spurious provider
    rows).
  * service_registry skips `run_after` deps whose target was filtered
    out via SERVICES_DISABLED instead of failing the whole load.
  * cli/mirror/resolver and cli/create/inventory/mirror_overrides emit/
    consume the new applications.<role>.services.<svc>.image shape
    (mirror.md updated to match).
  * utils/domains/list.build_applications_from_roles now reads
    meta/server.yml directly (it had been silently returning {} since
    config/main.yml stopped existing).
  * utils/roles/resource_validation.filter_roles_by_min_storage reads
    meta/services.yml.<entity>.min_storage instead of the legacy
    compose.services path.
  * web-app-bluesky pre-clones the social-app build context BEFORE
    sys-svc-compose so the build context is on disk by the time
    sys-stk-front-proxy/tasks/main.yml flushes the docker build handler.
  * 4 meta/variants.yml files (svc-ai-ollama, web-app-phpmyadmin,
    web-app-wordpress, web-app-keycloak) lose their `compose: services:`
    wrapper so deep-merge over meta/services.yml lands on the new
    file-root shape.

Validation
  * Unit tests:        1571 passed.
  * Lint tests:        50 passed.
  * Integration tests: 161 passed (1 skipped).
  * Full-cycle fresh-purged-apps deploy succeeded for the 8-app validation
    set: web-app-{nextcloud,wordpress,yourls,bigbluebutton,bluesky,gitea,
    discourse,mattermost}.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:58:26 +02:00
kevinveenbirkenbachandClaude Opus 4.7 9c35909752 refactor(utils/cache): split data.py into base/applications/users/domains
The 934-line `utils/cache/data.py` monolith mixed cross-cutting helpers
(constants, yaml-loaders, deep-merge, signatures, render machinery)
with three independent domain caches (applications, users, domains).
Per the KISS/SPOT/DRY guidelines, split it along domain boundaries so
each file has a single, well-scoped responsibility:

  utils/cache/__init__.py     — package; owns _reset_cache_for_tests()
                                orchestrator
  utils/cache/base.py         — cross-cutting helpers: constants,
                                yaml-loaders, _deep_merge, cache-key +
                                content-fingerprint signatures, the
                                cross-domain re-entry guard, and the
                                templar-render machinery (the only
                                ansible-coupled symbol, lazy-imports
                                its dependency)
  utils/cache/applications.py — variants + defaults +
                                get_merged_applications. Strictly
                                ansible-free at import time so the
                                GitHub Actions runner-host CLI path
                                (cli.deploy.development.init →
                                plan_dev_inventory_matrix →
                                get_variants) keeps working without
                                ansible installed
  utils/cache/users.py        — user defs, token store hydration,
                                alias materialization,
                                get_user_defaults, get_merged_users
  utils/cache/domains.py      — get_merged_domains, derived from the
                                merged applications view
  utils/cache/data.py         — DELETED. Façade is gone; the package
                                exports nothing the domain modules
                                don't already

Backward-compat: the facade was deleted (per operator request) and all
36+ consumers — 18 plugins, 2 CLI entry points, 9 test files, 4 doc
files, plus a role-internal lookup_plugin — were rewired to import
directly from the owning domain module. `_reset_cache_for_tests` is
now exposed at `from utils.cache import _reset_cache_for_tests`.

Tests added (per-module, focused):
  tests/unit/utils/cache/test_base.py            (31 cases)
  tests/unit/utils/cache/test_applications.py    ( 9 cases)
  tests/unit/utils/cache/test_users_module.py    (21 cases; suffix
                                                  disambiguates from
                                                  the existing
                                                  tests/unit/plugins/
                                                  lookup/test_users.py)
  tests/unit/utils/cache/test_domains.py         ( 6 cases)

The ansible-block invariants from CI runs 24934007615 and 24935979190
are now pinned via subprocess isolation: each ansible-less import test
spawns a fresh `python3 -c '…'` with a meta_path filter denying every
`ansible*` import and asserts the import + invocation succeed in that
clean process. The earlier in-process sys.modules-manipulation shims
fragmented module identity and broke under Python 3.13 inside the
infinito container; subprocess isolation has neither problem.

Doc-style audit (per docs/contributing/documentation.md):
  - Removed em-dash from utils/cache/README.md (split into two
    sentences)
  - Updated stale `utils.runtime_data` reference in
    docs/contributing/design/matrix-deploy.md
  - Removed two SPOT self-declarations
    (docs/agents/files/group_vars/applications.md and
    docs/contributing/artefact/files/plugins/lookup/users.md) per
    the documentation rule that SPOT status is conferred by
    cross-links, not by the page asserting it about itself

Empirical proof:
  - All four domain modules import without ansible (subprocess
    isolation tests)
  - get_variants(roles_dir=ROLES_DIR) returns 99 apps in the
    ansible-blocked subprocess
  - 142 cache tests + 1085 wider-suite tests pass; the only
    remaining failures (test_host_vars free-function fixture bug,
    test_runtime_performance timing flake) are pre-existing and
    unrelated to this refactor (verified via `git stash`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 00:27:57 +02:00
kevinveenbirkenbach 12dba01156 refactor(utils): consolidate cache modules into utils/cache/ package
Both in-process cache layers — the cached YAML loader and the cached
runtime-data getters — now live next to each other under
`utils/cache/`. The package gains a README that documents the
"orthogonal layers, no third cache" rule so future contributors hit
the right primitive on the first look.

Moves:
- `utils/yaml_cache.py` -> `utils/cache/yaml.py`
- `utils/runtime_data.py` -> `utils/cache/data.py`
- `tests/unit/utils/test_yaml_cache.py` -> `tests/unit/utils/cache/test_yaml.py`
- `tests/unit/utils/test_runtime_data.py` -> `tests/unit/utils/cache/test_data.py`
- `tests/unit/utils/test_variants.py` -> `tests/unit/utils/cache/test_variants.py`

New:
- `utils/cache/__init__.py` (empty per project convention)
- `utils/cache/README.md` describing the two layers, when to reach for
  which, lifetime, test-only helpers, and design guidelines (RFC 2119
  keywords, emoji-after-headline, no self-SPOT declaration, no
  slash-in-link-text).
- `tests/unit/utils/cache/__init__.py`

Mechanical updates:
- All imports across cli/, plugins/lookup/, tests/, and roles/ rewritten
  from `utils.yaml_cache` -> `utils.cache.yaml` and
  `utils.runtime_data` -> `utils.cache.data` (40+ files).
- `REPO_ROOT` parents-index in the moved tests bumped from `parents[3]`
  to `parents[4]` to reflect the deeper nesting.
- Six broken markdown links in docs/ updated to the new module paths
  (test_markdown_links lint test stays green).
- Two test files that used the `from utils import runtime_data` form
  now use `from utils.cache import data as runtime_data` so the local
  alias remains.

Tests: 848 passed across unit + lint suites (1 pre-existing collection
error in test_host_vars.py unrelated to this change).
2026-04-25 17:17:14 +02:00
kevinveenbirkenbachandClaude Opus 4.7 d009a35116 feat(rbac): hierarchical /roles/<app>/<role> + service-gated Playwright + Discourse round-trip
Implements requirements 005, 006, and 007.

005: per-application Keycloak `group-ldap-mapper`s anchor each app's
role groups under `/roles/<application_id>/<role_name>` (and, for
tenant-aware apps, `/roles/<application_id>/<tenant>/<role_name>`).
The flat-CN LDAP layout keeps each CN globally unique so Keycloak's
group-tree resolver never collides; per-app mappers use the LDAP
`description` as the visible Keycloak group name to keep the path
free of redundant prefixes. Migration purges legacy pre-005 entries
and forces a Keycloak `logout-all` so stale tokens drop pre-005
group paths.

006: per-service `<SERVICE>_SERVICE_ENABLED` env flags and the
`service-gating.js` helper let role specs `skipUnlessServiceEnabled`
cleanly when a shared service is missing or `SERVICES_DISABLED` is
set; `MODE_CI` markers (`INFINITO_MAKE_DEPLOY`, `GITHUB_ACTIONS`,
`ACT`) propagate through the deploy CLI into the test stage.

007: WordPress->Discourse round-trip Playwright scenario gated on the
`web-app-discourse` deployment.

WordPress Playwright runs 5/5 green on the single-site flow; the
multisite + Discourse scenarios are correctly skipped when their
service prerequisites are absent. The `rbac_group_path` lookup is the
single sanctioned producer for OIDC group paths and is wired into
every consumer (yourls, lam, minio, openwebui, pgadmin, phpldapadmin,
phpmyadmin, prometheus, xwiki).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 08:51:01 +02:00
kevinveenbirkenbachandClaude Opus 4.7 f722e676ca refactor(service-lookup): rename 'needed' flag to 'required'
Rename the third flag returned by lookup('service', ...) from 'needed'
to 'required' across code, tests and design docs. Same semantics: True
when some deployed app has the service with both enabled: true AND
shared: true, directly or transitively through its own enabled service
dependencies.

The new name reads as a contract ("this service is required by a real
consumer") rather than a suggestion ("something needs it"), which
matches the stricter-than-enabled semantic the flag already carries.

Touchpoints:
- plugins/lookup/service.py: _is_service_needed → _is_service_required,
  any_needed var, dict key in _compute_flags, docstring rationale.
- roles/sys-utils-service-loader/tasks/main.yml: caller switched to
  .required.
- roles/web-app-wordpress/config/main.yml + tasks/plugins/wp-discourse.yml:
  wp-discourse plugin gate uses lookup('service', 'discourse').required;
  compose.services.discourse declared as shared-service dependency so
  the service loader auto-loads web-app-discourse when inventory sets
  enabled: true. The legacy include_tasks: utils/load_app.yml in
  plugins/wp-discourse.yml is removed — the service loader now handles
  it.
- tests/unit/plugins/lookup/test_service.py: 25 assertions and 6 test
  method names renamed (test_needed_* → test_required_*,
  test_transitive_needed_resolved,
  test_direct_provider_is_not_needed_without_shared_flag).
- tests/integration/services/test_transitive_dependencies.py: one
  assertion renamed.
- docs/contributing/design/services/base.md: Lookup-Plugins section
  replaces the 'needed' bullet with an expanded 'required' entry that
  documents when consumers should gate on it and why plain enabled is
  not enough.

Also moves wordpress-app-specific keys (title, max_upload_size,
plugins) under compose.services.wordpress to match the convention
the other role keys already follow, and updates vars/main.yml lookup
paths accordingly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 00:06:47 +02:00
kevinveenbirkenbachandClaude Opus 4.7 e6eb5115bf test(utils/fs): extend cache coverage + invert filters/test_usage scan
Second-wave rollout of tests/utils/fs:

- 6 tests switched to the shared content cache via read_text() (replacing
  raw open()/Path.read_text()): roles/includes/test_existence,
  roles/dependencies/test_unnecessary, vars/test_facts_not_overridden,
  services/test_sys_requires_id, lint/ansible/roles/
  test_service_core_first_task_run_once, lint/ansible/roles/
  test_compose_resource_limits.

- 4 unit-lookup tests moved their fixture root from <repo>/.tmp_test_* to
  tempfile.TemporaryDirectory() so transient files land in /tmp (matching
  the AGENTS.md rule). Manual recursive-delete teardowns removed —
  TemporaryDirectory.cleanup() covers that path safely.

- filters/test_usage.py: algorithmic inversion. Old loop was
  O(N_filters × M_files × 4_regex); new scan walks files once, applies two
  master-alternation regexes (bare `| name` and `{% filter name %}`) plus
  one combined regex over all Python callables, and records hits per filter
  via the capture group. The two dropped Jinja patterns were strict
  subsets of the bare pattern, so the hit set is identical — only the
  complexity changes (3.6s → 0.2s, ~18x).

- docs/agents/action/testing.md: new "Filesystem Scans — Use the Shared
  Cache" section codifies the rule so future agent-written tests route
  through tests.utils.fs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 22:19:06 +02:00
kevinveenbirkenbach b2914b7cd1 Merge branch 'main' into fix/users-applications.yml 2026-04-23 18:14:17 +02:00
kevinveenbirkenbachandClaude Opus 4.7 e26ce8a630 test(email-lookup): isolate token store from container filesystem
setUp/tearDown patches `runtime_data._load_store_users` to return `{}`
so `_hydrate_users_tokens` can't pull a real `no-reply` token out of
`/var/lib/infinito/secrets/tokens.yml` (or any DIR_SECRETS / DIR_VAR_LIB
candidate that happens to exist in the test container). Without this,
`test_returns_plugin_defaults_when_no_vars` failed inside the CI
container with `password='395d6871414ffcbfbc3253db32328001'` because
`email._compute('password')` reads `users['no-reply'].tokens['web-app-mailu']`
and a prior deploy had persisted that token to disk. Patches the loader
rather than env vars because `_resolve_tokens_file` iterates four
candidate sources and any one of them existing reintroduces the leak.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 10:17:18 +02:00
Alejandro Roman 58aa904129 feat(prometheus-comm): fix last comments and fix new issue in matrix 2026-04-23 00:31:24 +04:00
Alejandro Roman 9a37c3f8bc feat(prometheus-comm): fix lint 2026-04-23 00:31:23 +04:00
Alejandro Roman 0c02f19f87 feat(prometheus-comm): fix matrix issue + find mattemost doesnt support metrics for free plan, deffered to a mattemrost future task 2026-04-23 00:31:23 +04:00
Alejandro Roman 5ada2e930c feat(prometheus-comm): Gitea fixed and avoid deffered 2026-04-23 00:31:22 +04:00
Alejandro Roman faf02d26a4 feat(prometheus-comm): fix gitea issues to have prometheus 2026-04-23 00:31:22 +04:00
Alejandro Roman e3a1797ba9 feat(prometheus-comm): fix previous issues + add comms correctly and avoid deffered logic for this PR - WIP - some tests still fails 2026-04-23 00:31:21 +04:00
Alejandro Roman 682eedb58f feat(prometheus-comm): fix lats issues + additionals I found 2026-04-23 00:31:20 +04:00
Alejandro Roman 6bc5512806 feat(prometheus-comm): fix CI fails tests 2026-04-23 00:31:18 +04:00
Alejandro Roman 48a99a2bb9 feat(prometheus-comm): move logic to each role and remove old files 2026-04-23 00:31:17 +04:00
Alejandro Roman e73dc1412b feat(prometheus-comm): fix Python lint issues 2026-04-23 00:31:15 +04:00
Alejandro Roman 6388984dbe feat(prometheus-comm): remove defaults and refactor code 2026-04-23 00:31:14 +04:00
kevinveenbirkenbach 591983af7d fix(email-lookup): render Jinja in computed defaults
email._compute('domain') returned variables['DOMAIN_PRIMARY'] verbatim. When
DOMAIN_PRIMARY held an unrendered template (e.g. the group_vars default
"{{ lookup('env','DOMAIN') | default('infinito.localhost', true) }}"), the raw
expression leaked into rendered templates. In discourse's containers/discourse.yml
the literal "{{ ..." opened a YAML flow mapping and made Ruby Psych fail with
"did not find expected ',' or '}' while parsing a flow mapping", aborting
`./launcher rebuild discourse` in CI.

Route every _compute() result through the same _render() helper that already
covered SYSTEM_EMAIL_* overrides, and add a regression test that exercises a
templated DOMAIN_PRIMARY default.
2026-04-22 22:22:57 +02:00
kevinveenbirkenbachandClaude Opus 4.7 247d8ccdeb fix(lookup:oidc_flavor): resolve via merged applications, not raw overrides
lookup('oidc_flavor') was reading applications[APP].compose.services
straight off variables["applications"], which in Ansible is the
pre-merge override slice — role defaults from config/main.yml (like
compose.services.ldap.enabled: true) are not yet visible there. As a
result ldap.enabled resolved to the lookup's default False on every
invocation and the Nextcloud flavor silently fell through to
"sociallogin" even when LDAP was enabled.

Switch the lookup to get_merged_applications(), matching the path
lookup('config') already uses, so role-level defaults and inventory
overrides are both visible. Tests now patch that merge function rather
than inject raw variables.

Verified live against the reuse-kept stack: lookup('oidc_flavor')
resolves to 'oidc_login', plugins.oidc_login.enabled=True and
plugins.sociallogin.enabled=False.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:35:58 +02:00
kevinveenbirkenbachandClaude Opus 4.7 9124b9b6dd refactor(utils): rename runtime_lookup_data to runtime_data
Shorten the module name now that it backs more than just lookup plugins
(validator CLI, tests, dashboard role). Updates all 26 import sites and
adds a unit test covering the pure helpers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 00:46:35 +02:00
kevinveenbirkenbachandClaude Opus 4.7 2dbb2aaa1a feat(email): introduce lookup('email') plugin, migrate all consumers
Moves the SYSTEM_EMAIL_* resolution logic out of group_vars/all/03_email.yml
into a dedicated Ansible lookup plugin, so every role template reads outgoing
mail settings via lookup('email') (global) or lookup('email', application_id)
(per-app). Per-app compose.services.email is merged on top of the resolved
defaults; SYSTEM_EMAIL_* variables remain usable as an inventory escape hatch.

Also splits the email docs: administrator-facing configuration stays under
docs/administration/configuration/, plugin contract and extension rules move
to docs/contributing/design/services/email.md. services.md is relocated to
services/base.md so the services/ subtree can host per-service design pages.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 22:47:40 +02:00
kevinveenbirkenbachandClaude Opus 4.7 64a520714c fix(canonical-domains-map): register infra roles with explicit canonical domain
Roles outside the web-*/svc-db-* auto-default families (notably
svc-prx-openresty) declare an explicit `server.domains.canonical` and
must register in the global domains map so `lookup('tls', ...)` can
resolve them. Previously the prefix filter silently dropped them,
causing `application_id 'svc-prx-openresty' not found in domains
mapping` once the legacy `domains` set_fact / `CURRENT_PLAY_DOMAINS`
injection was removed.

Auto-default domain generation (entity_name.DOMAIN_PRIMARY) still
applies only to web-*/svc-db-*. Defensive type checks keep malformed
non-auto-default configs from raising, matching prior behavior.

Tests updated to cover the svc-prx-openresty case and renamed to
reflect the new auto-default-prefix semantics.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 19:08:19 +02:00
kevinveenbirkenbachandClaude Opus 4.7 22841d34b2 fix(lookups): route tls/cert/compose/docker_cards through get_merged_domains
Commit 9b5de4e47 removed the `domains` set_fact in 01_constructor.yml
and migrated only the `domain` lookup to get_merged_domains, leaving
`tls`, `cert`, `compose_file_args`, and the dashboard `docker_cards`
lookup still requiring variables['domains']. That caused
`tls_common: required variable 'domains' is missing` during the OIDC
merge task (first lookup('tls', ...) call at defaults_oidc render).

Also updates sys-svc-compose tasks/handlers so the CA-trust when-clauses
use `lookup('domains') | has_domain(application_id)` instead of the
removed top-level variable. Unit tests for the four lookups now patch
get_merged_domains to read from variables['domains'] so they stay
hermetic without touching the real roles/ tree.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 17:46:35 +02:00
kevinveenbirkenbach f12b3ecc7c Merge branch 'feature/remove_legacy_get_app_conf' into fix/users-applications.yml 2026-04-21 16:13:02 +02:00
kevinveenbirkenbachandClaude Opus 4.7 029a5cbfc6 fix(lookup): move oidc_flavor to global plugins/lookup
Role-local lookup_plugins/ only autoloads when the role executes, but
oidc_flavor is called at config-merge time from
roles/web-app-nextcloud/config/main.yml during the 01_constructor stage.
Ansible could not resolve the lookup and failed the merge.

Relocate the plugin to plugins/lookup/ (the path declared in
ansible.cfg) so it is discovered before role execution, and move the
unit tests alongside it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 15:58:58 +02:00
kevinveenbirkenbach 9b5de4e477 refactor(runtime): replace DictRenderer and domains fact with cached lookups
Removes the placeholder interpreter (utils/dict_renderer.py) in favor of
standard {{ lookup('config', ...) }} / {{ lookup('domain', ...) }} calls
inside role configs, and eliminates the `domains` set_fact stage in
tasks/stages/01_constructor.yml by introducing a cached get_merged_domains
helper plus a new `domains` lookup plugin (full map + by-app subtree).

The `domain` lookup no longer depends on a pre-computed `domains` variable;
it delegates to get_merged_domains, which respects inventory-level overrides
from both `applications:` (authoritative canonical source) and `domains:`
(direct override map). Caching mirrors the applications pipeline — keyed on
(roles_dir, variables signature, override fingerprint) with a re-entry guard.

Replaced `{{ domains[app_id] }}` / `{{ domains }}` usages across web-app-matrix,
-bluesky, -mastodon, -peertube, -keycloak, test-e2e-playwright, and
group_vars. Added two performance tests for lookup('domains') mirroring the
applications cache assertions (≥50x warm-to-hot ratio, 120s cold budget).
2026-04-21 13:29:30 +02:00
kevinveenbirkenbach 17b6d755fd Merge branch 'main' into fix/users-applications.yml
Resolve conflicts by migrating lookup plugins to utils.applications.config
(get_app_conf -> get) while preserving branch's runtime applications/users
lookups. Notable changes:

- plugins/lookup/{config,nginx,service}.py, roles/web-app-dashboard/
  lookup_plugins/docker_cards.py: switch to utils.applications.config.get,
  keep utils.runtime_lookup_data.get_merged_applications/_users.
- roles/web-app-matrix/docs/Installation.md: use lookup('config', ...)
  syntax with markdown link formatting.
- scripts/tests/e2e/rerun-spec.sh: take main's single-source image tag
  derivation from roles/test-e2e-playwright/defaults/main.yml.
- tests/integration/test_get_app_conf_paths.py -> test_config_lookup_paths.py:
  migrate scan pattern to lookup('config', ...) and skip docs/ directory.
- tests/unit/plugins/lookup/test_config_lookup.py: union of new imports.
- tests/unit/plugins/lookup/test_cert.py: reset runtime caches in
  setUp/tearDown to avoid id()-based fingerprint cross-test pollution.
- Add __init__.py in 7 test directories left behind after prior cleanup.
- Delete dead duplicate group_vars/all/{04_users,05_applications}.yml
  (untracked on disk; content long migrated to roles/*/{config,users}/main.yml).
2026-04-21 02:34:06 +02:00
kevinveenbirkenbachandClaude Opus 4.7 34d6121293 refactor(utils): move config_utils to utils.applications.config, rename resolve_app_config -> get
Consolidates application-config helpers under utils/applications/ with a
concise public API (config.get). Updates all plugin, role, and test
imports; removes the legacy get_app_conf filter and its tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 18:32:30 +02:00
kevinveenbirkenbachandClaude Opus 4.7 918c49add1 perf(lookups): cache applications_current_play + tag templar inputs
Speed up per-play lookups that previously re-read YAML and re-templated
against a cold Templar on every invocation:

- applications_current_play: cache the rendered applications map keyed
  on the current play's identity so the 28+ plugin install calls reuse
  one resolved result.
- runtime_lookup_data / load_app: memoize the raw applications load and
  reuse it from the templar path instead of rereading group_vars.
- templating: merge caller-supplied variables onto templar's existing
  available_variables (keeping ansible_facts / hostvars visible) and
  tag the input with TrustedAsTemplate so Ansible 2.19+ actually
  renders {{ ... }} instead of returning it unchanged.

Tests: add integration perf test + unit regression guarding the
available_variables merge restores prev state after the call.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 00:22:06 +02:00
kevinveenbirkenbachandClaude Opus 4.6 e2016cd320 fix(users-applications): unify runtime lookup of users/applications
Lookups for 'service', 'service_registry', and 'applications_current_play'
always re-derive the merged applications mapping from inventory+role defaults
instead of trusting the passed kwargs, which could be shadowed or left
unrendered by nested template evaluations.

Dashboard/keycloak tests now consume the administrator password via
lookup('users','administrator').password so the rendered Playwright .env
matches the realm user provisioned by Ansible. Remove the hardcoded
DASHBOARD_KEYCLOAK_TEST_LOGIN_PASSWORD / KEYCLOAK_LOCAL_REALM_ADMIN_PASSWORD
overrides that previously masked the mismatch.

run_one.yml: drop users/applications from the template vars block. Those
entries created a shadowing context that broke nested lookup('users', ...)
inside lookup('template', ...).

utils/runtime_lookup_data: decrypt Ansible 2.19 EncryptedString values at
the lookup boundary and try templar rendering before falling back to
aggregated defaults when override mappings arrive as placeholders.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 21:21:09 +02:00
kevinveenbirkenbach 262e1b43fc Merge branch 'main' into fix/users-applications.yml
# Conflicts:
#	docs/contributing/design/services.md
#	plugins/lookup/applications_current_play.py
#	plugins/lookup/service.py
#	roles/sys-stk-backend/lookup_plugins/service_should_load.py
#	roles/sys-stk-backend/tasks/_load_service.yml
#	roles/sys-stk-backend/vars/main.yml
#	roles/sys-svc-compose/templates/base.yml.j2
#	roles/sys-svc-mail/tasks/01_core.yml
#	roles/sys-utils-service-loader/tasks/load_frontend_service.yml
#	tests/unit/plugins/lookup/test_applications_current_play.py
#	tests/unit/plugins/lookup/test_service.py
#	tests/unit/roles/sys-stk-backend/lookup_plugins/test_service_should_load.py
2026-04-15 23:59:54 +02:00
kevinveenbirkenbachandClaude Opus 4.6 b70a783423 refactor(lookups): migrate to runtime applications/users lookups
Replaces inline `applications`/`users` merging with dedicated lookup
plugins and runtime hydration. Introduces `add_csp_hash` filter,
integrates sort into `find_dock_val_by_bkp_entr`, splits the Keycloak
one-shot realm import into `05a_realm_import_run.yml`, and fixes the
Mastodon administrator task YAML. Removes obsolete `csp_hashes` filter,
`sys-token-store` Jinja filter, and empty CLI setup modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 23:44:19 +02:00
kevinveenbirkenbachandClaude Sonnet 4.6 1bcb7cfbb0 Merge branch 'main' into feature/service-refactoring
Resolve conflicts:
- docs/agents/action/iteration.md: keep both new bullets (SERVICES_DISABLED
  proposal + make test requirement)
- docs/contributing/design/services.md: deleted (renamed to
  docs/contributing/code/services.md in this branch)
- group_vars/all/20_services.yml: keep deletion from this branch (req-001)
- tests/lint/ansible/roles/test_service_core_first_task_run_once.py:
  use build_service_registry_from_roles_dir (req-001) with repo_root()
  from main for robust path resolution

Fix broken relative links caused by rename and deletion of files:
- docs/contributing/design/architecture.md: services.md → ../code/services.md
- docs/contributing/design/exceptions.md: load_frontend_service.yml →
  load_service.yml
- docs/requirements/001-service-registry-refactoring.md: fix paths for
  unit.md, integration.md, principles.md, documentation.md, AGENTS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 19:58:51 +02:00
kevinveenbirkenbachandClaude Sonnet 4.6 be6a310d59 refactor(services): implement role-local service registry (req-001)
- Delete group_vars/all/20_services.yml; all service metadata now lives
  in each role's config/main.yml under compose.services.<name>
- Rename generic 'database' service key to explicit 'mariadb'/'postgres'
  across all app roles; remove legacy type: field
- Add utils/service_registry.py and plugins/lookup/service_registry.py
  as the single source of truth for service discovery
- Add utils/database_service.py to centralize DB service resolution
- Delete service_load_enabled filter and service_should_load lookup;
  replaced by service lookup plugin
- Update sys-utils-service-loader to run in constructor stage and use
  generic load_service.yml; remove load_frontend_service.yml
- Add _service_loader_preloading guard to sys-svc-compose,
  sys-svc-webserver-core and sys-ctl-alm-compose to prevent
  health-checks/alarms from firing during preload phase
- Replace manual load_app.yml calls for shared DB/Mailu with assert-
  based preload validation in sys-svc-rdbms and sys-svc-mail
- Update networks.yml.j2 templates to resolve LDAP/AI networks via
  service_registry instead of hardcoded role names
- Migrate all tests to new service discovery API; delete obsolete tests
- Update docs/contributing/code/services.md to reflect role-local model

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 19:33:20 +02:00
Environment TestandClaude Sonnet 4.6 aa33256cde feat(mirror): split resolver output, move images to defaults, add lookup plugin
- resolver: output both applications: (config/main.yml compose images) and
  images: (defaults/main.yml images) keyed by source_file
- image_discovery: scan defaults/main.yml instead of vars/main.yml for images:
- mirror_overrides: write mirror URLs to applications.*.compose.services.*
  (mirror_policy-aware) and images_overrides.*.* (always merged, if_missing)
- roles/sys-ctl-hlth-csp, roles/test-e2e-playwright: move images: from
  vars/main.yml to defaults/main.yml for correct Ansible variable precedence
- plugins/lookup/image.py: new Ansible lookup plugin that prefers
  images_overrides over role-local images defaults
- docs: add artefact/image.md and artefact/mirror.md as SPOTs; add
  code/role/image.md contributor guide; link from ci.md
- tests: update test_util, test_mirror_overrides; add test_resolver,
  test_publish (404/401 coverage), test_image (lookup plugin)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:17:42 +02:00
Environment TestandClaude Sonnet 4.6 9ea2ad6ff1 refactor(docker): introduce utils/docker/image_discovery as single SPOT for image enumeration
- Move iter_role_images + all helpers + ImageRef from cli/mirror/util.py and
  cli/mirror/model.py into utils/docker/image_discovery.py; add source_file field
  to ImageRef to distinguish config/main.yml vs vars/main.yml entries
- Delete cli/mirror/util.py; update all callers to import from utils.docker.image_discovery
- Move utils into subpackages: utils/docker/ (image_ref, paths_utils, hadolint) and
  utils/roles/ (dependency_resolver, resource_validation); update all imports
- Reorganise tests into subpackages: tests/lint/docker/dockerfile/, tests/integration/docker/,
  tests/unit/utils/docker/, tests/unit/utils/roles/; fix parents[N] offsets after relocation
- tests/lint/docker/test_image_versions.py now uses iter_role_images (same SPOT as mirror)
- Add vars/main.yml images.<name>.{image,version} support for non-application roles
  (test-e2e-playwright, sys-ctl-hlth-csp); eliminate hardcoded duplicates in defaults/tasks
- Add web-app-matrix matrix-chatgpt-bot and web-app-mini-qr mini-qr entries in config/main.yml;
  replace hardcoded image strings in vars/templates with lookup('config', ...) references
- Fix sync/__main__.py log line to use correct registry prefix for non-Docker-Hub images
- Update docs/contributing/code/role/dockerfile.md with image declaration rules and SPOT reference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 19:32:31 +02:00
kevinveenbirkenbach 5db1ff8e7f fix(services): resolve transitive shared asset dependencies 2026-04-01 23:01:09 +02:00
kevinveenbirkenbach c3fb04e91a refactor: share application dependency resolution 2026-04-01 15:13:04 +02:00
kevinveenbirkenbach 00193b4a23 Rename the canonical services map to SERVICE_REGISTRY 2026-03-31 21:43:29 +02:00
kevinveenbirkenbach ef6b645cd7 Add uri_probe action plugin for short reachability checks 2026-03-31 20:35:57 +02:00
kevinveenbirkenbachandClaude Sonnet 4.6 84323aa471 refactor(cdn): replace cdn_paths/cdn_urls filter plugins with cdn lookup plugin
Eliminates scope-dependent CDN variable bugs by introducing a `cdn` lookup
plugin that resolves paths and URLs at call-time using an explicit application_id
argument. Deletes the old cdn_paths.py and cdn_urls.py filter plugins and their
tests; adds a new unit test for the lookup plugin's _cdn_paths and _to_url_tree
helpers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 15:10:59 +02:00
kevinveenbirkenbachandClaude Sonnet 4.6 96649d8c9b refactor(services): centralize frontend service loading with generic lookup plugin
Introduces a SPOT for all frontend service loading in sys-front-inj-all/tasks/01_services.yml,
driven by a new generic `service` lookup plugin that resolves services by key or role name
and returns {id, role, enabled, shared, needed}. Needed = enabled AND shared (direct or
transitive). The canonical key→role mapping moves to group_vars/all/20_services.yml
(previously scattered hardcoded rules). Removes per-role service loads from bigbluebutton,
nextcloud, and dashboard. Renames 01_dependencies.yml→01_services.yml and
08_services.yml→08_daemons.yml. Adds flush_handlers after each service load.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 08:27:21 +02:00
kevinveenbirkenbach 189267496d Rename module_utils package to utils 2026-03-21 02:05:39 +01:00
kevinveenbirkenbach de555f6bc3 add get_url_retry plugin and migrate get_url usages 2026-03-19 01:58:11 +01:00