Move files that share a delimiter-bounded name prefix within a folder (threshold 2) into a subfolder named after the prefix, dropping the prefix from the filename. Covers plugins/filter (docker, generate, has, merge, get/all), env handler subpackages, utils/roles/entity, utils/tests/swarm/write, role SQL/scripts/templates, keycloak/mailu task-includes, shared tasks/utils/load, and CI scripts. Update every import, query_file/script/include_tasks/src/lookup target, compose bind mount, and python -m path to the new locations.
Extend the prefix-grouped-files lint to threshold 2 with dir-level .nocheck and file-head nocheck opt-outs, and skip groups whose shortest member's stem equals the shared prefix. Keep flat only where a loader forbids nesting: WordPress mu-plugins and the docs/requirements namespace. Ansible filter discovery is recursive, so nested filters stay Jinja-resolvable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Consumer lookup plugins now reach the merged config through the Ansible
lookup loader instead of importing get_merged_applications /
get_merged_domains from utils.cache. The merge stays one cached
implementation surfaced via the applications/config/domains/users lookups;
consumers call lookup_loader.get(...).run() (the compose_networks idiom).
- get_merged_applications walks the whole meta/ tree recursively
(meta/**/*.yml to applications.<app>.<path>, addons and any depth) and
folds the raw meta/volumes.yml subtree onto applications.<app>.volumes
after the templar render so the Jinja source: strings stay unrendered.
- 31 consumer lookups rerouted; providers (applications/config/
applications_current_play/users/domains) and the raw-volume accessors
(volume/nginx) keep the cache import behind # nocheck: lookup-cache-import.
- New lint test_no_cache_imports_in_lookups enforces the contract.
- nfs_flavor is now the flavor SPOT (svc-storage-nfs-server + nfs_client_src
resolve via lookup('nfs_flavor')); removed the unused STORAGE_NFS_FLAVOR
group_var. Extracted extract_default_credentials to
utils/cache/schema_credentials.py to keep applications.py under the cap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
container_address + container_service are mode-aware lookups that
build a docker-exec-safe address from `application_id + service_key`.
Two adjustments to align them with how the rest of the project uses
service keys:
* Swarm-mode return value now uses the lookup's `service_key`
argument directly as the swarm service-suffix instead of
`services.<key>.name`. This matches the documented contract in
`roles/sys-svc-compose/files/resolve_container_id.sh` (the helper
composes `<stack>_<SERVICE_KEY>`) and what `docker stack deploy`
names a service after (the compose-yaml service key, not the
compose-side `container_name:` override).
* Compose-mode `_resolve_bare_name` falls back to
`services[entity_name]` when `service_key == 'application'` and
the meta has no top-level `application:` entry. 26 roles use
`application:` as their compose-yaml service key while keying
their meta/services.yml under the entity name (baserow, listmonk,
wordpress, mobilizon, etc.). Without the fallback, every consumer
of `container_address(application_id, 'application')` raised
`service 'application' missing in '<role>' services config` at
task-runtime. The fallback is intentionally narrow (only the
literal 'application' key) so an actual typo in another
service_key still raises clearly.
Unit-test contract update: test_container_address.py replaces
`test_swarm_mode_uses_resolved_service_name` with
`test_swarm_mode_uses_compose_yaml_service_key`, asserting the
resolver receives the service_key ('mattermost') rather than the
`services.<key>.name` value ('custom-mm-name'). Existing
`test_unknown_service_raises` still passes because the fallback only
fires for service_key='application'.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`docker stack deploy` names services `<stack>_<compose-yaml-key>` and
silently ignores the compose `name:` field (that field maps to the
compose-only `container_name`). The lookup was returning
`<stack>_<services.<key>.name>` instead, which only happened to be
correct when the YAML key and the `name:` field matched.
The matomo bootstrap service is the failure case: services.bootstrap.name
is `matomo-bootstrap` but the YAML key (and therefore the swarm
service name) is `bootstrap`. The lookup returned
`matomo_matomo-bootstrap`, the bootstrap-wait task polled an absent
service, and the wait timed out after 7 empty retries -- root cause
of the act-39 swarm bootstrap stall.
- plugins/lookup/container_service.py: swarm branch returns
`<stack>_<service_key>`; compose branch unchanged.
- tests/unit/plugins/lookup/test_container_service.py: flip the two
swarm assertions to the corrected expectations and rename
`test_swarm_mode_uses_resolved_service_name` ->
`test_swarm_mode_ignores_services_name_field` so the contract is
visible from the test name.
Add a sibling lookup `container_service` (with 12 unit tests) that
returns the addressable form of a service for container service ps,
container service logs, and container ps --filter
label=com.docker.swarm.service.name=... It mirrors `container_address`
but emits the swarm service name (<stack>_<service_key>) instead of
the runtime container ID subshell. Compose mode returns the bare
service name.
Make the matomo manager-side ops swarm-aware without pinning matomo
to the manager. 03_manager_ops splits the wait into two mode-specific
blocks: in swarm a container service ps loop on CurrentState, in
compose the existing container inspect. After convergence the swarm
block resolves the hosting node via container service ps Node and the
compose block sets it to inventory_hostname; both set MATOMO_HOST_NODE
via separate set_fact tasks so the undefined matomo_swarm_node_query
under compose never reaches Jinja. A second unconditional task
resolves the local container ID on the hosting node (compose: bare
name; swarm: container ps with the swarm service-name label filter)
into MATOMO_LOCAL_CID. Downstream 04_install_path, 07_patch_database,
08_patch_trusted_hosts and the health wait all delegate_to
MATOMO_HOST_NODE and use MATOMO_LOCAL_CID for container exec, so the
patches and inspects always hit a local container. 05_bootstrap keeps
the compose-only profile run and adds a swarm path that tails the
in-stack matomo_bootstrap service logs for a hex token, registering
the same matomo_bootstrap_run shape downstream. 09_config delegates
to STACK_HOST with run_once so the uri_retry hits the published
matomo port from the manager regardless of where matomo is scheduled.
Drop the now-unused MATOMO_CONTAINER_ADDRESS from vars/main.yml.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>