mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
Naming a dns server replaces the runtime's embedded one rather than adding to it, and that embedded server is the only thing answering compose aliases. The dev container could therefore not look up `tools`, so every check reaching a lane service by its service name reported that nothing answers and skipped itself: the System One suite skipped with 325 usable pairs behind an address it could not resolve, while its service was up and healthy. The embedded resolver goes after the platform one, so platform names resolve where they always did and only what that server does not know falls through. Its address is the one group_vars/all/08_networks.yml already holds, read through the env pipeline rather than written a second time in compose.yml. `read_group_path` only ever read the paths file, so the reader it is built on now takes the file to read and it keeps its own signature. The two docker handlers share a prefix once a second one exists, which is a folder; they move into one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
117 lines
3.7 KiB
Python
117 lines
3.7 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*(?:#.*)?$'
|
|
)
|
|
_ITEM_RE = re.compile(r'^\s+-\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).
|
|
"""
|
|
return read_group_value(_GROUP_PATHS_FILE, key)
|
|
|
|
|
|
def read_group_value(path: str, key: str) -> str:
|
|
"""Plain-string value of ``key`` in a group_vars file.
|
|
|
|
Args:
|
|
path: the group_vars file holding the value.
|
|
key: top-level variable name in it.
|
|
|
|
Returns:
|
|
The literal string value.
|
|
|
|
Raises:
|
|
KeyError: the key is not defined in that file.
|
|
ValueError: the value is not a plain string (e.g. a Jinja template).
|
|
"""
|
|
for line in read_text(path).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 {path} must be a plain string, got: {line.strip()!r}"
|
|
)
|
|
return value
|
|
raise KeyError(f"{key} not defined in {path}")
|
|
|
|
|
|
def read_group_paths(key: str) -> list[str]:
|
|
"""Plain-string list value from the group_vars paths SPOT.
|
|
|
|
Args:
|
|
key: top-level variable name whose value is a YAML block sequence.
|
|
|
|
Returns:
|
|
The literal string entries, in file order.
|
|
|
|
Raises:
|
|
KeyError: the key is not defined in the SPOT or carries no entry.
|
|
"""
|
|
entries: list[str] = []
|
|
collecting = False
|
|
for line in read_text(_GROUP_PATHS_FILE).splitlines():
|
|
if line.startswith(f"{key}:"):
|
|
collecting = True
|
|
continue
|
|
if not collecting:
|
|
continue
|
|
item = _ITEM_RE.match(line)
|
|
if item is None:
|
|
break
|
|
entries.append(item.group("value").strip())
|
|
if not entries:
|
|
raise KeyError(f"{key} not defined as a list in {_GROUP_PATHS_FILE}")
|
|
return entries
|
|
|
|
|
|
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"
|