Files
computer-playbook/plugins/lookup/volume.py
T
kevinveenbirkenbachandClaude Opus 4.8 238bc285c7 refactor(lookups): reach merged config via the lookup-loader SPOT
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>
2026-06-28 18:35:06 +02:00

96 lines
3.7 KiB
Python

"""Lookup ``volume``: read the canonical ``meta/volumes.yml`` registry.
Two call forms:
``lookup('volume', application_id, name)`` — return the normalised dict
for a single semantic entry. The returned dict always carries:
- ``name`` the EFFECTIVE docker volume name
(entry's ``name:`` if set, otherwise the YAML key)
- ``semantic_name`` the YAML key from ``meta/volumes.yml``
- ``docker_name`` the explicit ``name:`` field (alias for ``name``
during the dict-of-dicts migration), ``''`` if absent
- ``path`` the legacy ``path:`` field, ``''`` if absent
- ``type`` one of ``volume`` | ``bind`` | ``config`` | ``secret``
| ``tmpfs``, defaults to ``volume``
- ``source`` for binds / configs / secrets, ``''`` if absent
- ``nfs`` NFS opt-in (bool or dict), ``None`` if absent
- ``mounts`` the raw mounts list, ``[]`` if absent
``lookup('volume', application_id)`` — return the WHOLE canonical
registry as a dict keyed by semantic name. Useful for tasks that loop
over every declared volume (e.g. NFS subdir pre-creation). Returns
``{}`` for roles with no meta/volumes.yml.
"""
from __future__ import annotations
from typing import Any
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
# nocheck: lookup-cache-import
from utils.cache.applications import get_application_defaults, get_canonical_volumes
class LookupModule(LookupBase):
def run(
self,
terms: list[Any] | None,
variables: dict[str, Any] | None = None,
**kwargs: Any,
) -> list[dict[str, Any]]:
if not terms or len(terms) not in (1, 2):
raise AnsibleError(
"volume lookup requires 1 (application_id) or 2 "
"(application_id, name) terms"
)
application_id = str(terms[0]).strip()
if not application_id:
raise AnsibleError("volume lookup: application_id must be non-empty")
canonical = get_canonical_volumes(application_id)
if not canonical:
# The canonical-volumes registry fills lazily as a side effect of
# building the application defaults; force it on a miss so a lookup
# that runs before any consumer built this role (e.g. nfs_prep's
# pre-create loop) does not see an empty registry and silently skip
# the subdir. Guarded so the populated hot path skips the deepcopy.
get_application_defaults()
canonical = get_canonical_volumes(application_id)
if len(terms) == 1:
return [dict(canonical)]
name = str(terms[1]).strip()
if not name:
raise AnsibleError("volume lookup: name must be non-empty")
if not canonical:
raise AnsibleError(
f"volume lookup: no canonical meta/volumes.yml entries for "
f"role {application_id!r}"
)
entry = canonical.get(name)
if not isinstance(entry, dict):
raise AnsibleError(
f"volume lookup: no entry named {name!r} in canonical "
f"meta/volumes.yml for role {application_id!r}"
)
docker_name = entry.get("name", "")
return [
{
"name": entry.get("name") or name,
"semantic_name": name,
"docker_name": docker_name,
"path": entry.get("path", ""),
"type": entry.get("type", "volume"),
"source": entry.get("source", ""),
"nfs": entry.get("nfs", None),
"mounts": entry.get("mounts", []),
}
]