Commit Graph
664 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 4.7 cc8c5502e6 test(config): move test_prometheus_config.py into integration/config/
Group the prometheus integration test next to the other config-scoped
tests under tests/integration/config/, drop the redundant _config
suffix, and bump the __file__-rooted path computations by one .parent
to compensate for the new depth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 12:19:23 +02:00
kevinveenbirkenbachandClaude Opus 4.7 d14e2214bc test(rbac): add static guard for rbac_group_path role declarations
Catch the runtime "role 'X' is not declared under
applications[Y].rbac.roles" failure of the rbac_group_path lookup at CI
time by cross-checking every callsite under roles/ against each
application's meta/rbac.yml. Move both rbac-scoped guards into a
dedicated oauth2_oidc/rbac/ subdirectory with its own README and drop
the now-redundant rbac prefix from the test filenames.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 12:14:30 +02:00
kevinveenbirkenbach 837d0c5335 test(suppression): unify # noqa / # nocheck marker grammar
Introduce a single grammar for per-line, per-block, and per-file
suppression markers across the test suite, replacing seven ad-hoc
syntaxes that had drifted apart.

Helper
  utils/annotations/suppress.py exposes is_suppressed_at,
  is_suppressed_in_head, is_suppressed_anywhere, line_has_rule, and
  suppressed_line_numbers. Markers may use either `noqa` or `nocheck`
  (case-insensitive synonyms), live inside `#`, `//`, `{# ... #}`, or
  `<\!-- ... -->` comments, and combine multiple kebab-case rules
  comma-separated. 17 unit tests cover the grammar in isolation.

Refactored consumers
  - tests/external/repository/test_urls_reachable.py (rule: url)
  - tests/external/docker/test_image_versions.py via
    utils/docker/version_updater.py (rule: docker-version)
  - tests/lint/repository/test_no_direct_yaml_calls.py (rule:
    direct-yaml)
  - tests/lint/ansible/test_service_shared_consistency.py (rule:
    shared)
  - tests/lint/ansible/roles/test_web_app_email_integration.py (rule:
    email)
  - tests/lint/repository/test_python_file_size.py (rule: file-size)
  - tests/lint/ansible/test_run_once_tags.py +
    tests/integration/roles/run_once/test_schema.py (rules: run-once,
    run-once-suffix)

Marker migration
  12 role tasks/main.yml files: `# run_once_<key>: deactivated`
  -> `# nocheck: run-once`. roles/web-app-suitecrm/meta/services.yml
  drops a malformed inline `noqa: intentional legacy pin` from a TODO
  comment so the new parser does not pick it up as a stray rule.

Caching wins
  Test files that previously loaded YAML / read files via raw
  yaml.safe_load + Path.read_text now route through utils.cache.yaml
  (load_yaml_any / load_yaml_str) and utils.cache.files.read_text:
    - test_service_shared_consistency.py
    - test_web_app_email_integration.py
    - tests/integration/roles/run_once/test_schema.py
    - utils/docker/version_updater.py
  This both speeds up repeated reads and stays consistent with the
  direct-yaml lint policy.

Documentation
  New SPOT page at docs/contributing/actions/testing/suppression.md
  with grammar, position semantics, the catalog of rules, examples,
  and an "adding a new rule" checklist. lint.md and external.md drop
  their per-rule tables and link to the new page. All touched docs
  conform to docs/contributing/documentation.md (no em-dash thought
  breaks, no SPOT self-labels, RFC-2119 keywords used consistently).

Settings sort tooling
  scripts/lint/sort_claude_settings.sh wraps utils/claude/settings_sort.py
  and is wired into scripts/lint/autoformat.sh. The tooling fixes the
  ASCII-ascending requirement enforced by
  tests/lint/repository/test_claude_settings_sorted.py; the test's
  failure message now points operators at the script. .claude/settings.json
  is committed in sorted form.
2026-04-30 11:54:46 +02:00
kevinveenbirkenbachandClaude Opus 4.7 5dadbdf88b test(playwright-env-flags): accept | string | lower shape alongside ternary
`tests/integration/roles/test_playwright_env_service_flags.py` enforces
that every `<SERVICE>_SERVICE_ENABLED=...` line in a role's
`templates/playwright.env.j2` resolves to the literal `"true"` or
`"false"` strings demanded by requirement 006. The heuristic previously
recognised only two shapes:

  1. A ternary that ends with `'true'`/`'false'` literals.
  2. The bare strings `true` / `false`.

Commit cac52b74a introduced a third equivalent form,
`{{ <bool> | string | lower }}`, which Jinja renders to "true"/"false"
when the operand is a Python boolean. The old regex did not match it,
so the test went red even though the render-output guarantee still
held.

Add a third recognised shape via a `\|\s*string\s*\|\s*lower\b` regex
and update the inline doc to spell out all three. Both forms remain
permitted in role templates; the compact one stays valid alongside the
explicit ternary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 23:56:57 +02:00
kevinveenbirkenbachandClaude Opus 4.7 b65e818d87 test(playwright-env-flags): accept | string | lower shape alongside ternary
`tests/integration/roles/test_playwright_env_service_flags.py` enforces
that every `<SERVICE>_SERVICE_ENABLED=...` line in a role's
`templates/playwright.env.j2` resolves to the literal `"true"` or
`"false"` strings demanded by requirement 006. The heuristic previously
recognised only two shapes:

  1. A ternary that ends with `'true'`/`'false'` literals.
  2. The bare strings `true` / `false`.

Commit cac52b74a introduced a third equivalent form,
`{{ <bool> | string | lower }}`, which Jinja renders to "true"/"false"
when the operand is a Python boolean. The old regex did not match it,
so the test went red even though the render-output guarantee still
held.

Add a third recognised shape via a `\|\s*string\s*\|\s*lower\b` regex
and update the inline doc to spell out all three. Both forms remain
permitted in role templates; the compact one stays valid alongside the
explicit ternary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 23:37:38 +02:00
kevinveenbirkenbachandClaude Opus 4.7 f7eb65a682 fix(web-app-mig,web-app-sphinx)+test: container_port pointed at non-existent flat .port key, add wildcard-path validator
Both `roles/web-app-mig/vars/main.yml` and
`roles/web-app-sphinx/vars/main.yml` resolved `container_port` via
`lookup('config', application_id, 'services.' ~ entity_name ~ '.port')`,
but neither role's `meta/services.yml` declares a flat `port` key —
they declare `services.<entity>.ports.{inter, local.http}`. Deploy on
arch crashed at compose-template render time with
`The lookup plugin 'config' failed: Key 'port' not found in dict`
(GHA universal-deploy run 25028370090 jobs 73306260785 + 73306260788).
Match the convention used by web-app-listmonk / -gitea / -shopware
and read `services.<entity>.ports.inter` instead.

The existing `tests/integration/lookups/test_config_paths.py`
literal-path scanner only captures the first quoted segment of a
`lookup('config', ...)` call, so `~`-concatenated paths (where a
Jinja variable expands inside the dotted path) were validated only
as wildcard prefixes. That contract is too loose: any role with a
top-level `services` dict satisfied
`'services.' ~ entity_name ~ '.port'`, even when the resolved path
did not exist for the specific role.

Add a `test_wildcard_paths` method that:

- captures the entire path expression in any
  `lookup('config', <app>, <expr>)` whose `<expr>` contains `~`,
- parses the expression token by token, treating quoted strings as
  literals and any bareword between `~` markers as `*`,
- joins the parts and splits on `.` into a wildcard path
  (for example `'credentials.' ~ X ~ '_jwt_secret'` becomes
  `credentials.*_jwt_secret`),
- resolves the role from the file's `roles/<role>/...` path when
  the lookup's app argument is a Jinja variable,
- validates the wildcard path through the same fallback chain as
  the literal-path test (application defaults -> users defaults
  -> credentials in defaults / schema -> images presence), with
  per-segment fnmatch globbing so partial-segment wildcards like
  `*_jwt_secret` resolve correctly.

Verified: reverting the two role fixes makes the new test report
`'services.*.port' has no match in application defaults / schema for
role 'web-app-mig'` (and the same for sphinx) with file/line refs.
Restoring the fixes turns it green again.

Also drop one extra `..` segment from three relative links in
`docs/contributing/design/inventory.md` so the markdown lint check
passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 12:54:10 +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
kevinveenbirkenbachandClaude Opus 4.7 2f5e2b4154 fix(oauth2-proxy): normalize allowed_groups slash + tighten config SPOT
The yourls Playwright admin-SSO spec was failing at /oauth2/callback with
HTTP 403 + `[AuthFailure] Invalid authentication via OAuth2: unauthorized`
because oauth2-proxy literal-matches `allowed_groups` against the JWT
groups claim, but the two sides emit different shapes:
- Keycloak's group-membership-mapper emits `/roles/<app>/<role>` (leading
  slash, `full.path=true` is the realm-import default).
- The `rbac_group_path` lookup, by historical contract pinned in
  tests/unit/plugins/lookup/test_rbac_group_path.py, returns the path
  WITHOUT a leading slash.

Bridge the contract gap inside the oauth2-proxy template (the only consumer
that does literal equality) so the rest of the codebase keeps the existing
no-leading-slash contract. A new static guard test pins the normalisation
filter chain so future refactors get a fast signal instead of an HTTP 403
discovered via a multi-hour Playwright run.

While the file was open, also tighten its SPOT layout:

- Cache the `compose.services.oauth2.allowed_groups` lookup in a Jinja
  `set` so the same lookup isn't called twice in the same render.
- Lift the hardcoded internal port `4180` into
  `compose.services.oauth2-proxy.port`. container.yml.j2 (right side of
  the port mapping) and oauth2-proxy-keycloak.cfg.j2 (`http_address`)
  now read it via `lookup('config')`; both MUST stay in sync, that is
  exactly what a single SPOT delivers.
- Move `configuration_file` from the role config root into
  `compose.services.oauth2-proxy.configuration_file` so it lives next to
  the other oauth2-proxy service settings; both consumers (tasks/main.yml
  template destination and container.yml.j2 volume mount) updated.
- Drop unused top-level `allowed_roles` — no consumer in the repo, only
  effective restriction is `compose.services.oauth2.allowed_groups` in
  the per-app configs.

Empirical proof of the slash-normalize fix:
- iter 2 (pre-fix): yourls spec `admin sso login to admin panel, then
  logout` fails with 60s timeout polling for /admin/, oauth2-proxy logs
  show `[AuthFailure] ... unauthorized` 403 at /oauth2/callback.
- iter 3 (post-fix): same spec passes in 890ms, sibling biber-deny spec
  passes in 384ms, deploy PLAY RECAP ok=1530 failed=0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:00:55 +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
kevinveenbirkenbach 43e18d1ee2 Merge branch 'main' into feature/rbac-hierarchy-and-service-gates 2026-04-25 09:38:27 +02:00
kevinveenbirkenbachandCopilot Autofix powered by AI 22b05f49de Potential fix for code scanning alert no. 277: Explicit returns mixed with implicit (fall through) returns
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-04-25 09:38:27 +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 b56554a6f3 test(lookups): assert every defined lookup plugin is actually used
Symmetric to tests/integration/filters/test_usage.py: enumerates every
plugins/lookup/<name>.py that declares a LookupModule class and fails
if any of them is never referenced in production code. Three invocation
styles count as usage:

- lookup('<name>', ...) — canonical Jinja/YAML form.
- query('<name>', ...) — list-returning variant.
- from plugins.lookup.<name> import ... — direct Python import, used
  by utils/runtime_data.py which embeds several LookupModule classes
  as regular helpers instead of dispatching through Ansible.

Matches under tests/ alone count as "only used in tests" and still fail
the build. An unused lookup is a maintenance hazard: it stays in the
search path, rots silently, and confuses plugin browsing.

Uses the inverted-scan approach from the filter-usage test (one pass
over all matching project files, a single master alternation regex
covering every defined lookup name), so cost scales with M_files not
N_lookups × M_files. Walk and file contents are served from
tests.utils.fs process-level caches per the testing.md rule.

All 27 currently-defined lookups pass — no migration backlog.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 23:37:28 +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
kevinveenbirkenbachandClaude Opus 4.7 9c8cc1ddcb test(utils/fs): cache project file walks + contents across test run
Adds tests/utils/fs.py with process-level @lru_cache for both the
project-wide os.walk and per-file read_text(), then migrates 26 tests
(24 modified + 2 new) that previously re-walked/re-read the tree
independently to share one cached pass.

Headline win: tests/integration/jinja/test_variable_usage.py drops from
~26s to ~1.3s (≈19x), jinja cluster as a whole ~14x. Other migrated
tests benefit from the shared content cache when the same file is read
by multiple scans in one pytest run.

Non-caching logic changes (kept intentionally):
- test_variable_usage.py: added `if varname not in content: continue`
  fast-path before the line loop (pure short-circuit, identical result).
- test_test_file_naming.py: exempt tests/utils/ from the test_*.py
  prefix rule so the new shared helpers don't trip the lint.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 20:29:54 +02:00
kevinveenbirkenbach b2914b7cd1 Merge branch 'main' into fix/users-applications.yml 2026-04-23 18:14:17 +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 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 15505b2547 feat(prometheus-comm): following best practices 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 acc407a9b0 feat(prometheus-comm): add filters for comms apps taht are not enabled yet + fix tests issues 2026-04-23 00:31:20 +04:00
Alejandro Roman eacd2b9696 feat(prometheus-comm): fix latest comments 2026-04-23 00:31:19 +04:00
Alejandro Roman e5b26818d2 feat(prometheus-comm): fix tests issues 2026-04-23 00:31:19 +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
Alejandro Roman 3f82fb1ac8 feat(prometheus-comm): make sure to move logic into separate temapltes and fix issues in code 2026-04-23 00:31:14 +04:00
Alejandro Roman a4c46aed65 feat(prometheus-comm): fix issues, make test working and ensure communication works correctly as expected 2026-04-23 00:31:13 +04:00
Alejandro Roman 82c40e6415 feat(prometheus-comm): Add all the logic to handle metrics and health int oall apps 2026-04-23 00:31:12 +04:00
Alejandro Roman b117c9ed3c fix(prometheus-intergration): add missing test 2026-04-23 00:31:12 +04:00
Alejandro Roman 6daa486e72 fix(prometheus-intergration): solve make test issues 2026-04-23 00:31:12 +04:00
Alejandro Roman b61359c85c feat(prometheus-intergration): Add all the logic need it for all apps with prometheus, missin playwright still 2026-04-23 00:31:11 +04: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
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
kevinveenbirkenbachandClaude Opus 4.7 d244129a76 refactor(tests/integration/roles/includes): merge role- and task-scope existence checks
Fold test_include_import_exist.py (include_role / import_role target
existence) and test_tasks_existence.py (include_tasks / import_tasks
file existence) into a single test_existence.py. The two tests
previously shared setUp, YAML scan, and recursive collector shape
almost verbatim; the merged file keeps one filesystem walk and one
recursive collector parameterised over the directive keys and the
value field (name vs. file), then exposes two test methods so role
and task existence still fail independently.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 12:49:04 +02:00
kevinveenbirkenbachandClaude Opus 4.7 709f6f73dd refactor(tests/integration): promote remaining top-level tests into topical clusters
Introduce seven new top-level clusters under tests/integration/:
  cli/         - CLI subcommand --help smoke tests
  config/      - uniform rules over roles/*/config/main.yml (non-empty,
                 CSP shape, universal-logout bool)
  lookups/     - custom lookup-plugin behavior (config-path validation,
                 runtime caching perf)
  vars/        - cross-role vars/defaults hygiene (uppercase uniqueness,
                 fact override prohibition)
  inventory/   - utils/manager/inventory integration
  spots/       - role-specific spot checks (dashboard OIDC, keycloak
                 realm import); home for future test_<role>_<aspect>.py
  playbooks/   - playbook-level wiring (invokable-paths filter ->
                 tasks/stages + tasks/groups/*-roles.yml)

Under tests/integration/roles/ add two sub-clusters:
  when/     - block-level when: hygiene (guards, duplication, block size)
  includes/ - include_tasks/import_tasks/include_role/import_role target
              existence (roles-scope and whole-repo-scope, kept separate
              for now)

Move 14 previously unclustered tests into these clusters, adjust path
depths accordingly (from +1 to +2 levels for most, walker-based tests
unchanged), strip stale file-path header comments, and extend
tests/integration/roles/README.md to document the new sub-clusters.
No behavior change; every new cluster passes locally.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 12:41:30 +02:00
kevinveenbirkenbachandClaude Opus 4.7 02da33b0a5 refactor(tests): reorganize into topical sub-clusters and consolidate linting
Under tests/integration/roles/ introduce four sub-clusters (meta/,
dependencies/, applications/, run_once/) plus nested dependencies/run_after/
and applications/id/. Fold tests/integration/application_id/ into
roles/applications/id/ and tests/integration/run_once/ into roles/run_once/.
Pull four application-related tests out of tests/integration/ into
roles/applications/, and test_run_after_references.py into the run_after/
sub-cluster.

Consolidate tests/integration/linting/ into tests/lint/{ansible,filesystem,
repository}/, so repository-wide lint checks now run under make test-lint
rather than make test-integration.

Each new cluster ships a README.md and __init__.py; cross-referencing
READMEs (categories, ports, services, roles, applications) are updated to
point at the new paths. Path depths inside moved tests and one hardcoded
ignore path in test_domain.py are adjusted accordingly. No behavior change;
all 32 lint tests and every moved cluster pass locally.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 12:22:33 +02:00
kevinveenbirkenbachandClaude Opus 4.7 84a68c43cf feat(tests/integration): add networks cluster and compose-role coverage test
Move the existing networks test into a dedicated tests/integration/networks/
cluster (with __init__.py and README.md) and add a new integration test that
enforces: every role which defines an application_id in vars/main.yml and
ships a templates/compose.yml.j2 MUST have a dedicated subnet entry under
defaults_networks.local in group_vars/all/08_networks.yml. Close the coverage
gap by registering /28 subnets for web-app-dashboard, web-app-fider,
web-app-jenkins, web-app-mattermost, web-app-mediawiki, web-app-odoo, and
svc-prx-openresty.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 11:30:22 +02:00
kevinveenbirkenbachandClaude Opus 4.7 bc9d10f085 refactor(tests/integration): reorganize tests into topical cluster subdirs
Move flat tests/integration/test_*.py files into thematic subfolders
(action_plugins, application_id, categories, compose, domains, filters,
handlers, jinja, linting, oauth2_oidc, passwords, ports, roles, run_once,
services, workflows), strip redundant prefixes from filenames, and
adjust path-depth references by +1 level. Add __init__.py and a scope-
focused README.md per cluster so the directory purpose is discoverable
without duplicating the integration.md SPOT. Update design/services.md
to point at the new test locations.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 11:04:35 +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
kevinveenbirkenbach 50e4cb8422 chore(group_vars/all): renumber files to close 04/05 gap
- Rename 06..19 → 04..17 so the sequence is dense and gap-free.
- Update hard-coded references in cli/, tests/, and PR template.
- Add README.md describing scope, load order, and naming rules
  per docs/contributing/documentation.md standards.
2026-04-19 15:55:52 +02:00