Files
computer-playbook/utils/cache/records.py
T
kevinveenbirkenbachandClaude Opus 5 24d6a999dd fix(cache): read the Ubuntu mirror list through the env parser, not os.environ
`records()` is reached from two directions. The `.env` generator hands the
mirror list in explicitly, so `make dotenv` always resolved it. The
`cache_records` Ansible lookup calls `records()` with no argument, and the
fallback read `os.environ` alone. The deploy container is handed a curated
env and never sources `.env`, so `INFINITO_APT_UBUNTU_MIRRORS` is absent
there: `mirrors()` returned None, `${UBUNTU_PRIMARY}` never resolved, and
`records()` returned the empty string, which failed the svc-cache-package
bootstrap task with the message censored by `no_log`.

`env_setting()` is the repository's existing accessor for exactly this
shape - process environment first, `default.env` behind it - and is already
used for this key in the apt-mirror repository test. The explicit-argument
path is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-29 13:43:26 +02:00

88 lines
2.9 KiB
Python

"""Render the declared repositories as the records Nexus is bootstrapped from.
One format, two readers: the ``.env`` generator writes it to
``INFINITO_CACHE_UPSTREAMS`` for the dev stack, and the ``cache_records``
lookup hands the same string to the role's bootstrap task. Both resolve the
Ubuntu mirror placeholders from ``INFINITO_APT_UBUNTU_MIRRORS``, which the
image build reads too, so the mirror order has a single home. That key is read
through :func:`utils.env.parser.env_setting`, never off the process
environment alone: the ansible lookup runs in a container that was handed a
curated env and never sourced ``.env``, so ``default.env`` has to stay
reachable behind it.
Stdlib only: the ``.env`` generator reaches this before PyYAML exists.
"""
from __future__ import annotations
from utils.cache.hosts import declarations
from utils.env.parser import env_setting
MIRRORS_KEY = "INFINITO_APT_UBUNTU_MIRRORS"
PRIMARY = "${UBUNTU_PRIMARY}"
FALLBACK = "${UBUNTU_FALLBACK}"
PLACEHOLDER = "${"
FIELD = "|"
RECORD = ","
def mirrors(raw: str = "") -> tuple[str, str] | None:
"""Return the primary and fallback Ubuntu archive, without trailing slash.
Args:
raw: the mirror list, or empty to read it from the process
environment with ``default.env`` behind it.
Returns:
None where the list does not carry both, in which case the
placeholders cannot be resolved.
"""
found = [
entry.rstrip("/")
for entry in (raw or env_setting(MIRRORS_KEY)).strip('"').split()
if entry
]
if len(found) < 2:
return None
return found[0], found[1]
def upstream(raw: str, resolved: tuple[str, str] | None) -> str:
"""Return *raw* with the Ubuntu mirror placeholders substituted."""
if resolved is None:
return raw
return raw.replace(PRIMARY, resolved[0]).replace(FALLBACK, resolved[1])
def records(raw_mirrors: str = "") -> str:
"""Return every repository as ``name|flavor|url|suite|depth|max_age``.
Args:
raw_mirrors: the mirror list, or empty to read it from the process
environment with ``default.env`` behind it.
Returns:
The comma-separated records, or the empty string where a placeholder
stays unresolved, so a consumer's own guard reports it rather than
Nexus proxying a host that does not exist.
"""
resolved = mirrors(raw_mirrors)
rendered = []
for repo in declarations().repos:
url = upstream(repo.upstream, resolved)
if PLACEHOLDER in url:
return ""
rendered.append(
FIELD.join(
(
repo.name,
repo.flavor,
url,
repo.distribution,
str(repo.repodata_depth or ""),
repo.content_max_age,
)
)
)
return RECORD.join(rendered)