mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
The package-cache frontend terminates TLS for package traffic and signs with its own CA: probed from inside the test container, pypi.org arrives as subject CN=pypi.org issued by CN=Infinito Package-Cache Frontend CA. package-frontend-ca.sh installs that CA into the OS store, which is why apt, pip, curl and urllib work — but requests reads certifi, which has never heard of it, so test-external failed its reachability precondition with CERTIFICATE_VERIFY_FAILED. Upgrading certifi does not help; the issuer is not a public root. load.sh is where the runner puts the venv on PATH, so it is also where the interpreter carrying the pip certifi is chosen; pointing REQUESTS_CA_BUNDLE and SSL_CERT_FILE at the store the stack deliberately fills keeps one trust decision instead of two. A caller-set value still wins. The bundle location was about to become a fourth copy: dev-nix and the network diagnose CLI each carried their own candidate list, and the two had already drifted apart in their fourth entry. All of them now read group_vars CA_BUNDLE_CANDIDATES — Ansible directly, the shell through a generated env key, python through read_group_paths. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
101 lines
3.3 KiB
Python
101 lines
3.3 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).
|
|
"""
|
|
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 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"
|