Files
computer-playbook/utils/paths.py
T
kevinveenbirkenbachandClaude Opus 4.8 ab3accba3a refactor(paths): derive var-lib subpaths everywhere from the paths SPOT
Add DIR_BACKUPS to utils.paths and INFINITO_DIR_BACKUPS /
INFINITO_DIR_SECRETS env handlers, derive the recover-console constants
(including NFS_EXPORT_STATE) from utils.paths and the nfs export SPOT,
and switch every remaining self-built /var/lib/infinito subpath - the
secrets recover targets, the swarm extras backup source, the users
token-store candidate chain, the CI recover drill, the swarm NFS
diagnostics and the playwright reports dir - to those constants. Tests
consume the env constants directly; narration comments in the touched
files are dropped per the comment lint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:15:27 +02:00

71 lines
2.4 KiB
Python

"""Filesystem SPOT for the Python side, read from group_vars/all/05_paths.yml.
``group_vars/all/05_paths.yml`` is the single source of truth for host paths.
The env layer (utils/env/handlers, exported by scripts/meta/env/load.sh) derives
the ``INFINITO_*`` env keys from it; a set env wins so callers can override.
When a tool runs outside that layer, e.g. a bare ``subprocess`` that does not
inherit the env build, the value is read straight from the group_vars SPOT, so
there is still no hardcoded literal default.
The SPOT is parsed with a plain-string line parser instead of PyYAML: the env
build runs during bootstrap (make install-system-python) before any Python
dependency exists, so this module must stay stdlib-only. Only plain
``KEY: "value"`` entries can be read this way; a Jinja expression or a missing
key is a hard error.
"""
from __future__ import annotations
import os
import re
from pathlib import Path
from utils import PROJECT_ROOT
from utils.cache.files import read_text
_GROUP_PATHS_FILE = str(PROJECT_ROOT / "group_vars" / "all" / "05_paths.yml")
_ENTRY_RE = re.compile(
r'^(?P<key>[A-Za-z_][A-Za-z0-9_]*):\s*"?(?P<value>[^"#]*)"?\s*(?:#.*)?$'
)
def read_group_path(key: str) -> str:
"""Plain-string path value from the group_vars paths SPOT.
Args:
key: top-level variable name in group_vars/all/05_paths.yml.
Returns:
The literal string value.
Raises:
KeyError: the key is not defined in the SPOT.
ValueError: the value is not a plain string (e.g. a Jinja template).
"""
for line in read_text(_GROUP_PATHS_FILE).splitlines():
match = _ENTRY_RE.match(line.strip())
if not match or match.group("key") != key:
continue
value = match.group("value").strip()
if not value or "{{" in value:
raise ValueError(
f"{key} in {_GROUP_PATHS_FILE} must be a plain string, "
f"got: {line.strip()!r}"
)
return value
raise KeyError(f"{key} not defined in {_GROUP_PATHS_FILE}")
def _dir_var_lib() -> str:
env = os.environ.get("INFINITO_DIR_VAR_LIB")
if env:
return env
return read_group_path("DIR_VAR_LIB")
DIR_VAR_LIB = Path(_dir_var_lib())
DIR_BACKUPS = DIR_VAR_LIB / "backup"
DIR_SECRETS = DIR_VAR_LIB / "secrets"
FILE_TOKENS = DIR_SECRETS / "tokens.yml"
FILE_DATABASE_SECRETS = DIR_SECRETS / "databases.csv"