Files
computer-playbook/utils/applications
kevinveenbirkenbachandClaude Opus 4.7 a8a117c86e refactor(yaml): route every YAML touchpoint through utils.cache.yaml + lint
New lint guard
==============
tests/lint/repository/test_no_direct_yaml_calls.py AST-walks the
production tree (utils/, cli/, plugins/, filter_plugins/,
lookup_plugins/, roles/, scripts/, tasks/) and flags any direct
`yaml.safe_load` / `yaml.safe_dump` / `yaml.load` / `yaml.dump` /
`yaml.safe_load_all` / `yaml.dump_all` call. Aliased imports are
tracked: `import yaml as Y` ⇒ `Y.safe_load(...)` is flagged;
`from yaml import safe_load` ⇒ bare `safe_load(...)` is flagged.

Per-line opt-out: `# noqa: direct-yaml` on the call's physical line
(or any line spanned by a multi-line call). No central exemption
list — every legitimate exception lives next to its call with a
human-readable rationale in the surrounding comment.

Cache module additions
======================
* `dump_yaml_str(data)` — symmetric to `safe_dump(data)` with no file
  path. Used for stdout output and filter return values where the
  cache cannot help but the API needs to stay consistent.
* `load_yaml_str(text)` — symmetric to `safe_load(text)`. Used when
  YAML comes from an HTTP body, a mutated buffer, or a mock-patched
  source whose content does not correspond to a stable on-disk file.

Migrated production call sites (33 → 0 direct-yaml violations)
==============================================================
* utils/{entity_name_utils, valid_deploy_id, invokable, service_registry,
  domains/list, applications/{config, in_group_deps},
  docker/image/discovery, meta/{port_bands, scan},
  roles/{dependency_resolver, meta_lookup, resource_validation}}
* cli/{build/{graph, inventory/full/command}, create/inventory/{host_vars,
  mirror_overrides}, fix/vars_main_files, meta/{applications/in_group_deps,
  callorder}, mirror/resolver, validate/inventory}
* plugins/{filter/{compose_volumes, get_all_application_ids, get_role,
  invokable_paths, role_path_by_app_id}, lookup/applications_current_play}
* roles/{svc-db-postgres/filter_plugins/split_postgres_connections,
  web-app-bigbluebutton/filter_plugins/compose_mods,
  web-app-dashboard/lookup_plugins/docker_cards}

Behaviour adjustments
=====================
* utils/roles/dependency_resolver — `safe_load_all` (multi-doc) →
  single-doc `load_yaml_any` wrapped in a list. Ansible task files are
  single-doc by convention; the downstream multi-doc loop stays intact
  for forward compatibility.
* roles/sys-svc-compose-ca/files/compose_ca.py — KEEPS direct
  yaml.safe_load / yaml.safe_dump (annotated with noqa). The script is
  copied to the target host via `ansible.builtin.copy` and the target
  has no `utils/` package on PYTHONPATH.

noqa-annotated direct-yaml call sites (with rationale)
======================================================
* utils/cache/yaml.py — IS the cache; would recurse.
* utils/handler/yaml.py — vault-aware Loader subclass that wraps
  \!vault scalars in VaultScalar; cache-by-path collapses the wrapping.
* cli/encrypt/inventory/__main__.py — vault-aware Dumper subclass that
  emits \!vault literal blocks; the cached dump_yaml writes plain YAML
  which is the wrong output here.
* roles/sys-svc-compose-ca/files/compose_ca.py — runtime-deployed.
* tasks/utils/migrate_meta_layout.py — once-off migration script.

Validation
==========
* Unit tests: 1573 passed.
* Lint:       56 passed (incl. new test_no_direct_yaml_calls).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:10:14 +02:00
..

Application Utilities (utils/applications/) 🧩

This package contains shared Python helpers that operate on the central applications configuration dictionary.

Purpose 🎯

Modules in utils/applications/ MUST provide reusable helpers for reading, resolving, and validating application configuration. Helpers MUST stay independent of Ansible plugin entrypoints so the same code can be imported from filter plugins, lookup plugins, and library modules.

Example Import ✍️

from utils.applications import config

value = config.get(
    applications,
    application_id,
    "features.oidc",
    strict=False,
    default=False,
)

Design Guidelines 📐

  • Helpers SHOULD be focused, composable, and free of side effects during import.
  • Helpers MUST NOT depend on Ansible plugin loader behavior at import time.
  • Helpers SHOULD raise typed exceptions (e.g. AppConfigKeyError) so callers can react to missing keys in strict mode.
  • Public functions SHOULD use concise names (e.g. get) and rely on module-qualified access at call sites (config.get(...)) for clarity.

What Does Not Belong Here 🚫

  • Standalone executable modules MUST live in library/.
  • Ansible plugin entrypoints MUST live under plugins/*.
  • Cross-cutting helpers that are not application-specific SHOULD live in the parent utils/ package instead.