mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
refactor(env/spot): forbid .get() defaults + cache namespace rename
Three new lint tests close gaps in the INFINITO_* SPOT contract:
* tests/lint/filesystem/env/test_default_env_keys_handled.py
Every default.env key must be wired through a handler.
* tests/lint/filesystem/env/test_used_keys_declared.py
Every INFINITO_* in .sh/.py must live in env/{default,ci}.env or
a handler; per-line `# nocheck:` suppresses runtime-only refs.
* tests/lint/filesystem/python/test_no_infinito_get_defaults.py
Forbid ANY default in `.get("INFINITO_*", ...)` -- including the
empty-string fallback (which silently masks an unset key). The
nocheck check walks every physical line the call spans so an
autoformat-split call still honours the marker.
Cache namespace flattens to a single prefix:
INFINITO_PACKAGE_CACHE_* -> INFINITO_CACHE_PACKAGE_*
INFINITO_REGISTRY_CACHE_* -> INFINITO_CACHE_REGISTRY_*
The five previously driver-injected paths (APT_LIST, NPMRC,
PIP_CONF, REGISTRY_PROXY_CONF, FRONTEND_CA_FILE) move into
env/default.env and passthrough.STATIC_KEYS; the
`cache_env_overrides()` helper and its two call sites are gone
along with the test-fixture override for FRONTEND_CA_DIR.
SPOT-strict rewrites:
- 19 sites: `something.get("INFINITO_X", "")` -> subscript
`something["INFINITO_X"]` (loud KeyError when env loader did
not run; all keys live in env/default.env).
- 2 handler-internal bootstrap reads suppressed with
`# nocheck: handler-bootstrap-read`.
- 30 runtime-only / driver-injected / test-fixture references
annotated with `# nocheck: <reason>` per-line.
- Unit tests that previously removed SPOT-guaranteed keys via
`{k: v ... if k \!= KEY}` or `clear=True` switch to setting
the key to "" explicitly, matching the new contract.
Side fixes:
- INFINITO_DNS_DOMAIN + INFINITO_DNS_HOSTS_FILE move from
shell-side defaults in common.sh to env/default.env and
passthrough.STATIC_KEYS.
- dns_systemd_is_operational() drops the dead
INFINITO_DNS_FORCE_HOSTS_FALLBACK override branch (the
variable was never set anywhere).
- INFINITO_WORKER_FETCH switches from `os.environ.get(KEY, "1")`
to `os.environ[KEY]`.
- scripts/tests/code/wrapper.sh exec args move into a bash
array so `# nocheck:` on INFINITO_TEST_TYPE can live on its
own element line (SC2215 fix).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
22cf6f38f1
commit
eb4bcd23a8
53 files changed
+666
-267
No files matched your search
@@ -37,13 +37,13 @@ jobs:
|
||||
# post-prune in-run re-pulls: large enough to fit one deploy's
|
||||
# in-use image set (rpardini) plus pip/npm/apt artefacts (Nexus),
|
||||
# leaving ~62g headroom for Docker layer storage on /mnt.
|
||||
INFINITO_REGISTRY_CACHE_MAX_SIZE: 8g
|
||||
INFINITO_REGISTRY_CACHE_HOST_PATH: /mnt/cache/registry/mirror
|
||||
INFINITO_REGISTRY_CACHE_CA_HOST_PATH: /mnt/cache/registry/ca
|
||||
INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX: 4g
|
||||
INFINITO_PACKAGE_CACHE_HEAP: 1g
|
||||
INFINITO_PACKAGE_CACHE_DIRECT_MEM: 1g
|
||||
INFINITO_PACKAGE_CACHE_HOST_PATH: /mnt/cache/package/data
|
||||
INFINITO_CACHE_REGISTRY_MAX_SIZE: 8g
|
||||
INFINITO_CACHE_REGISTRY_HOST_PATH: /mnt/cache/registry/mirror
|
||||
INFINITO_CACHE_REGISTRY_CA_HOST_PATH: /mnt/cache/registry/ca
|
||||
INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX: 4g
|
||||
INFINITO_CACHE_PACKAGE_HEAP: 1g
|
||||
INFINITO_CACHE_PACKAGE_DIRECT_MEM: 1g
|
||||
INFINITO_CACHE_PACKAGE_HOST_PATH: /mnt/cache/package/data
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -69,13 +69,13 @@ jobs:
|
||||
-e INFINITO_PULL_POLICY=never \
|
||||
-e INFINITO_NO_BUILD=0 \
|
||||
-e NIX_CONFIG \
|
||||
-e INFINITO_REGISTRY_CACHE_MAX_SIZE \
|
||||
-e INFINITO_REGISTRY_CACHE_HOST_PATH \
|
||||
-e INFINITO_REGISTRY_CACHE_CA_HOST_PATH \
|
||||
-e INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX \
|
||||
-e INFINITO_PACKAGE_CACHE_HEAP \
|
||||
-e INFINITO_PACKAGE_CACHE_DIRECT_MEM \
|
||||
-e INFINITO_PACKAGE_CACHE_HOST_PATH \
|
||||
-e INFINITO_CACHE_REGISTRY_MAX_SIZE \
|
||||
-e INFINITO_CACHE_REGISTRY_HOST_PATH \
|
||||
-e INFINITO_CACHE_REGISTRY_CA_HOST_PATH \
|
||||
-e INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX \
|
||||
-e INFINITO_CACHE_PACKAGE_HEAP \
|
||||
-e INFINITO_CACHE_PACKAGE_DIRECT_MEM \
|
||||
-e INFINITO_CACHE_PACKAGE_HOST_PATH \
|
||||
--network host \
|
||||
-v "${HOST_WORKDIR}:${HOST_WORKDIR}" \
|
||||
-v "/mnt/docker:/var/lib/docker" \
|
||||
|
||||
@@ -377,9 +377,7 @@ def main() -> int:
|
||||
help="Container mode: run, start, stop, exec, remove.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--image", default=os.environ.get("INFINITO_IMAGE", "infinito:latest")
|
||||
)
|
||||
parser.add_argument("--image", default=os.environ.get("INFINITO_IMAGE"))
|
||||
parser.add_argument("--build", action="store_true")
|
||||
parser.add_argument("--rebuild", action="store_true")
|
||||
parser.add_argument("--no-cache", action="store_true")
|
||||
|
||||
@@ -30,32 +30,9 @@ def compose_file_args() -> list[str]:
|
||||
return out
|
||||
|
||||
|
||||
def cache_env_overrides() -> dict[str, str]:
|
||||
"""Env vars consumed strictly by compose/cache.override.yml."""
|
||||
from .profile import Profile
|
||||
|
||||
if not Profile().registry_cache_active():
|
||||
return {}
|
||||
|
||||
ca_dir = os.environ.get("INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR", "").strip()
|
||||
if not ca_dir:
|
||||
raise SystemExit(
|
||||
"INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR is not set. Run "
|
||||
"'make dotenv' (or source scripts/meta/env/load.sh) before "
|
||||
"invoking cli.administration.deploy.development."
|
||||
)
|
||||
return {
|
||||
"INFINITO_REGISTRY_CACHE_PROXY_CONF": "./compose/registry-cache/proxy.conf",
|
||||
"INFINITO_PACKAGE_CACHE_PIP_CONF": "./compose/package-cache/pip.conf",
|
||||
"INFINITO_PACKAGE_CACHE_NPMRC": "./compose/package-cache/npmrc",
|
||||
"INFINITO_PACKAGE_CACHE_APT_LIST": "./compose/package-cache/apt.list",
|
||||
"INFINITO_PACKAGE_CACHE_FRONTEND_CA_FILE": f"{ca_dir}/ca.crt",
|
||||
}
|
||||
|
||||
|
||||
def resolve_distro() -> str:
|
||||
"""Return INFINITO_DISTRO; raise SystemExit if missing or invalid."""
|
||||
distro = os.environ.get("INFINITO_DISTRO", "").strip()
|
||||
distro = os.environ["INFINITO_DISTRO"].strip()
|
||||
if not distro:
|
||||
raise SystemExit(
|
||||
"INFINITO_DISTRO is not set. Run 'make dotenv' (or source scripts/meta/env/load.sh) "
|
||||
@@ -72,7 +49,7 @@ def resolve_distro() -> str:
|
||||
|
||||
def resolve_container() -> str:
|
||||
"""Return INFINITO_CONTAINER; raise SystemExit if unset."""
|
||||
container = os.environ.get("INFINITO_CONTAINER", "").strip()
|
||||
container = os.environ["INFINITO_CONTAINER"].strip()
|
||||
if not container:
|
||||
raise SystemExit(
|
||||
"INFINITO_CONTAINER is not set. Run 'make dotenv' (or source scripts/meta/env/load.sh) "
|
||||
|
||||
@@ -5,7 +5,7 @@ import subprocess
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .common import cache_env_overrides, compose_file_args
|
||||
from .common import compose_file_args
|
||||
from .coredns import CoreDNSCorefileRenderer
|
||||
from .network import detect_outer_network_mtu
|
||||
from .proc import run_streaming
|
||||
@@ -42,7 +42,6 @@ class Compose:
|
||||
check=True,
|
||||
)
|
||||
env["INFINITO_IMAGE"] = result.stdout.strip()
|
||||
env.update(cache_env_overrides())
|
||||
return env
|
||||
|
||||
def run(
|
||||
@@ -192,7 +191,7 @@ class Compose:
|
||||
print(">>> env:", {k: env.get(k) for k in keys})
|
||||
print(">>> NIX_CONFIG:", "<set>" if env.get("NIX_CONFIG") else "<empty>")
|
||||
|
||||
no_build = env.get("INFINITO_NO_BUILD", "0") == "1"
|
||||
no_build = env["INFINITO_NO_BUILD"] == "1"
|
||||
# Compose env-file precedence: later files override earlier ones.
|
||||
args = ["--env-file", "env/ci.env"]
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
def _env_variant() -> int | None:
|
||||
raw = os.environ.get("INFINITO_VARIANT", "").strip()
|
||||
raw = os.environ["INFINITO_VARIANT"].strip()
|
||||
if not raw:
|
||||
return None
|
||||
try:
|
||||
@@ -35,7 +35,7 @@ def _env_variant() -> int | None:
|
||||
|
||||
|
||||
def _env_full_cycle() -> bool:
|
||||
return os.environ.get("INFINITO_FULL_CYCLE", "").strip().lower() == "true"
|
||||
return os.environ["INFINITO_FULL_CYCLE"].strip().lower() == "true"
|
||||
|
||||
|
||||
def _run_deploy(
|
||||
@@ -84,7 +84,7 @@ def _run_deploy(
|
||||
"PY_COLORS": "1",
|
||||
"TERM": "xterm-256color",
|
||||
}
|
||||
services_disabled = os.environ.get("INFINITO_SERVICES_DISABLED", "")
|
||||
services_disabled = os.environ["INFINITO_SERVICES_DISABLED"]
|
||||
if services_disabled:
|
||||
extra_env["INFINITO_SERVICES_DISABLED"] = services_disabled
|
||||
|
||||
@@ -216,7 +216,7 @@ def handler(args: argparse.Namespace) -> int:
|
||||
|
||||
# Remove any app IDs that were disabled via INFINITO_SERVICES_DISABLED so the
|
||||
# deploy list stays consistent with the inventory created by init.
|
||||
raw_disabled = os.environ.get("INFINITO_SERVICES_DISABLED", "").strip()
|
||||
raw_disabled = os.environ["INFINITO_SERVICES_DISABLED"].strip()
|
||||
disabled_app_ids: set[str] = set()
|
||||
if raw_disabled:
|
||||
services = parse_services_disabled(raw_disabled)
|
||||
|
||||
@@ -7,7 +7,7 @@ from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from . import PROJECT_ROOT
|
||||
from .common import cache_env_overrides, compose_file_args, resolve_distro
|
||||
from .common import compose_file_args, resolve_distro
|
||||
from .profile import Profile
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -20,7 +20,6 @@ CI_DOCKER_ROOT_STR = str(CI_DOCKER_ROOT)
|
||||
def _base_env(*, distro: str) -> dict[str, str]:
|
||||
env = dict(os.environ)
|
||||
env["INFINITO_DISTRO"] = distro
|
||||
env.update(cache_env_overrides())
|
||||
return env
|
||||
|
||||
|
||||
@@ -45,7 +44,7 @@ def _cleanup_docker_root() -> None:
|
||||
print(f">>> Not on GitHub - No bind volumes will be deleted: {docker_root}")
|
||||
return
|
||||
|
||||
docker_root_env = os.environ.get("INFINITO_DOCKER_VOLUME", "").strip().rstrip("/")
|
||||
docker_root_env = os.environ["INFINITO_DOCKER_VOLUME"].strip().rstrip("/")
|
||||
|
||||
if docker_root_env and docker_root_env != CI_DOCKER_ROOT_STR:
|
||||
raise RuntimeError(
|
||||
|
||||
@@ -51,7 +51,7 @@ def handler(args: argparse.Namespace) -> int:
|
||||
raise SystemExit("exec requires a command (e.g. exec -- sh -lc 'whoami')")
|
||||
|
||||
extra_env: dict[str, str] = {}
|
||||
services_disabled = os.environ.get("INFINITO_SERVICES_DISABLED", "")
|
||||
services_disabled = os.environ["INFINITO_SERVICES_DISABLED"]
|
||||
if services_disabled:
|
||||
extra_env["INFINITO_SERVICES_DISABLED"] = services_disabled
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ def add_parser(sub: argparse._SubParsersAction) -> None:
|
||||
|
||||
|
||||
def _env_variant() -> int | None:
|
||||
raw = os.environ.get("INFINITO_VARIANT", "").strip()
|
||||
raw = os.environ["INFINITO_VARIANT"].strip()
|
||||
if not raw:
|
||||
return None
|
||||
try:
|
||||
@@ -117,7 +117,7 @@ def handler(args: argparse.Namespace) -> int:
|
||||
# the inventory step strip it back out — leaving the round's
|
||||
# include list inconsistent with what the inventory actually
|
||||
# contains.
|
||||
raw_disabled = os.environ.get("INFINITO_SERVICES_DISABLED", "").strip()
|
||||
raw_disabled = os.environ["INFINITO_SERVICES_DISABLED"].strip()
|
||||
disabled_app_ids: set[str] = set()
|
||||
if raw_disabled:
|
||||
services = parse_services_disabled(raw_disabled)
|
||||
@@ -159,7 +159,7 @@ def handler(args: argparse.Namespace) -> int:
|
||||
raise SystemExit(f"--variant: {exc}") from exc
|
||||
|
||||
runtime = os.environ.get("RUNTIME") or detect_runtime()
|
||||
services_disabled = os.environ.get("INFINITO_SERVICES_DISABLED", "")
|
||||
services_disabled = os.environ["INFINITO_SERVICES_DISABLED"]
|
||||
for _round_index, inv_dir, round_variants, include_R, _purge_set in plan:
|
||||
round_include = tuple(
|
||||
role for role in include_R if role not in disabled_app_ids
|
||||
|
||||
@@ -19,7 +19,7 @@ def detect_outer_network_mtu(
|
||||
daemon_config_path: Path = DOCKER_DAEMON_CONFIG_PATH,
|
||||
) -> str | None:
|
||||
current_env = os.environ if env is None else env
|
||||
explicit_mtu = current_env.get("INFINITO_OUTER_NETWORK_MTU", "").strip()
|
||||
explicit_mtu = current_env["INFINITO_OUTER_NETWORK_MTU"].strip()
|
||||
if explicit_mtu:
|
||||
return explicit_mtu
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ def add_parser(sub: argparse._SubParsersAction) -> None:
|
||||
|
||||
def _maybe_build_missing() -> int:
|
||||
# CI: never build (image is pulled). Local: build if missing.
|
||||
if os.environ.get("INFINITO_NO_BUILD", "0") == "1":
|
||||
if os.environ["INFINITO_NO_BUILD"] == "1":
|
||||
return 0
|
||||
|
||||
build_args = argparse.Namespace(
|
||||
|
||||
@@ -235,7 +235,7 @@ def apply_services_disabled_from_env(
|
||||
inventory_file: Path | None = None,
|
||||
) -> None:
|
||||
"""Read INFINITO_SERVICES_DISABLED from the environment and apply to host_vars and inventory."""
|
||||
raw = os.environ.get("INFINITO_SERVICES_DISABLED", "").strip()
|
||||
raw = os.environ["INFINITO_SERVICES_DISABLED"].strip()
|
||||
if not raw:
|
||||
return
|
||||
services = parse_services_disabled(raw)
|
||||
@@ -326,7 +326,7 @@ def assert_services_disabled_inventory_consistency_from_env(
|
||||
roles_dir: Path,
|
||||
) -> None:
|
||||
"""Fail fast when INFINITO_SERVICES_DISABLED conflicts with the existing inventory."""
|
||||
raw = os.environ.get("INFINITO_SERVICES_DISABLED", "").strip()
|
||||
raw = os.environ["INFINITO_SERVICES_DISABLED"].strip()
|
||||
if not raw:
|
||||
return
|
||||
|
||||
|
||||
+2
-1
@@ -65,7 +65,8 @@ services:
|
||||
volumes:
|
||||
- /sys/fs/cgroup:/sys/fs/cgroup:rw
|
||||
- .:/opt/src/infinito
|
||||
- ${INFINITO_DOCKER_VOLUME:?Run 'make dotenv' to generate the .env single source of truth}:${INFINITO_DOCKER_MOUNT:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
- ${INFINITO_DOCKER_VOLUME:?Run 'make dotenv' to generate the .env single source of truth}:${INFINITO_DOCKER_MOUNT:?Run 'make dotenv' to generate the .env
|
||||
single source of truth}
|
||||
- infinito:/var/lib/infinito/
|
||||
- letsencrypt:/etc/letsencrypt/
|
||||
|
||||
|
||||
+27
-27
@@ -9,17 +9,17 @@ services:
|
||||
environment:
|
||||
ENABLE_MANIFEST_CACHE: "true"
|
||||
AUTH_REGISTRIES: ""
|
||||
CACHE_MAX_SIZE: "${INFINITO_REGISTRY_CACHE_MAX_SIZE:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
CACHE_MAX_SIZE: "${INFINITO_CACHE_REGISTRY_MAX_SIZE:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
# Avoid 502s on hosts with broken IPv6 outbound.
|
||||
DISABLE_IPV6: "true"
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ${INFINITO_REGISTRY_CACHE_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
source: ${INFINITO_CACHE_REGISTRY_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
target: /docker_mirror_cache
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${INFINITO_REGISTRY_CACHE_CA_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
source: ${INFINITO_CACHE_REGISTRY_CA_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
target: /ca
|
||||
bind:
|
||||
create_host_path: true
|
||||
@@ -39,19 +39,19 @@ services:
|
||||
# Run as root: fresh bind-mount is root:root and Nexus aborts otherwise.
|
||||
user: "0:0"
|
||||
environment:
|
||||
INSTALL4J_ADD_VM_PARAMS: "-Xms${INFINITO_PACKAGE_CACHE_HEAP:?Run 'make dotenv' to generate the .env single source of truth} -Xmx${INFINITO_PACKAGE_CACHE_HEAP:?Run
|
||||
'make dotenv' to generate the .env single source of truth} -XX:MaxDirectMemorySize=${INFINITO_PACKAGE_CACHE_DIRECT_MEM:?Run 'make dotenv' to generate the
|
||||
INSTALL4J_ADD_VM_PARAMS: "-Xms${INFINITO_CACHE_PACKAGE_HEAP:?Run 'make dotenv' to generate the .env single source of truth} -Xmx${INFINITO_CACHE_PACKAGE_HEAP:?Run
|
||||
'make dotenv' to generate the .env single source of truth} -XX:MaxDirectMemorySize=${INFINITO_CACHE_PACKAGE_DIRECT_MEM:?Run 'make dotenv' to generate the
|
||||
.env single source of truth}"
|
||||
# Random-password mode leaves no readable secret for the bootstrap.
|
||||
NEXUS_SECURITY_RANDOMPASSWORD: "false"
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ${INFINITO_PACKAGE_CACHE_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
source: ${INFINITO_CACHE_PACKAGE_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
target: /nexus-data
|
||||
bind:
|
||||
create_host_path: true
|
||||
ports:
|
||||
- "${INFINITO_BIND_IP:?Run 'make dotenv' to generate the .env single source of truth}:${INFINITO_PACKAGE_CACHE_PORT:?Run 'make dotenv' to generate the .env
|
||||
- "${INFINITO_BIND_IP:?Run 'make dotenv' to generate the .env single source of truth}:${INFINITO_CACHE_PACKAGE_PORT:?Run 'make dotenv' to generate the .env
|
||||
single source of truth}:8081"
|
||||
healthcheck:
|
||||
test:
|
||||
@@ -71,13 +71,13 @@ services:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ${INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
source: ${INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
target: /etc/nginx/ca
|
||||
read_only: true
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${INFINITO_PACKAGE_CACHE_FRONTEND_CERTS_DIR:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
source: ${INFINITO_CACHE_PACKAGE_FRONTEND_CERTS_DIR:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
target: /etc/nginx/certs
|
||||
read_only: true
|
||||
bind:
|
||||
@@ -85,7 +85,7 @@ services:
|
||||
- ./compose/package-cache-frontend/upstreams.conf:/etc/nginx/conf.d/upstreams.conf:ro
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: ${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
ipv4_address: ${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD-SHELL
|
||||
@@ -108,32 +108,32 @@ services:
|
||||
condition: service_healthy
|
||||
required: false
|
||||
environment:
|
||||
INFINITO_PACKAGE_CACHE_FRONTEND_IP: ${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
INFINITO_CACHE_PACKAGE_FRONTEND_IP: ${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
extra_hosts:
|
||||
- "pypi.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "files.pythonhosted.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "registry.npmjs.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "rubygems.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "index.rubygems.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "repo.packagist.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "proxy.golang.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "dl-cdn.alpinelinux.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "raw.githubusercontent.com:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "codeload.github.com:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "pypi.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "files.pythonhosted.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "registry.npmjs.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "rubygems.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "index.rubygems.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "repo.packagist.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "proxy.golang.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "dl-cdn.alpinelinux.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "raw.githubusercontent.com:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
- "codeload.github.com:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}"
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ${INFINITO_REGISTRY_CACHE_CA_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
source: ${INFINITO_CACHE_REGISTRY_CA_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth}
|
||||
target: /opt/registry-cache-ca
|
||||
read_only: true
|
||||
bind:
|
||||
create_host_path: true
|
||||
- ${INFINITO_REGISTRY_CACHE_PROXY_CONF:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make
|
||||
- ${INFINITO_CACHE_REGISTRY_PROXY_CONF:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make
|
||||
dotenv')}:/etc/systemd/system/docker.service.d/registry-cache-proxy.conf:ro
|
||||
- ./scripts/docker/cache/registry-ca.sh:/usr/local/bin/registry-cache-ca.sh:ro
|
||||
- ${INFINITO_PACKAGE_CACHE_PIP_CONF:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make dotenv')}:/etc/pip.conf:ro
|
||||
- ${INFINITO_PACKAGE_CACHE_NPMRC:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make dotenv')}:/root/.npmrc:ro
|
||||
- ${INFINITO_PACKAGE_CACHE_APT_LIST:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make
|
||||
- ${INFINITO_CACHE_PACKAGE_PIP_CONF:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make dotenv')}:/etc/pip.conf:ro
|
||||
- ${INFINITO_CACHE_PACKAGE_NPMRC:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make dotenv')}:/root/.npmrc:ro
|
||||
- ${INFINITO_CACHE_PACKAGE_APT_LIST:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make
|
||||
dotenv')}:/etc/apt/sources.list.d/package-cache.list:ro
|
||||
- ${INFINITO_PACKAGE_CACHE_FRONTEND_CA_FILE:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make
|
||||
- ${INFINITO_CACHE_PACKAGE_FRONTEND_CA_FILE:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make
|
||||
dotenv')}:/opt/package-frontend-ca.crt:ro
|
||||
- ./scripts/docker/cache/package-frontend-ca.sh:/usr/local/bin/package-frontend-ca.sh:ro
|
||||
@@ -66,24 +66,24 @@ The env-var contracts each service expects strictly via `${VAR:?…}` (consumed
|
||||
|
||||
| Variable | Purpose |
|
||||
|---|---|
|
||||
| `INFINITO_REGISTRY_CACHE_HOST_PATH` | Host path bind-mounted into `registry-cache` for blob/manifest persistence. |
|
||||
| `INFINITO_REGISTRY_CACHE_CA_HOST_PATH` | Host path holding the proxy MITM CA bundle. Mounted writable into `registry-cache`, read-only into `infinito`. |
|
||||
| `INFINITO_REGISTRY_CACHE_MAX_SIZE` | Maximum on-disk size. Computed by [infinito_registry_cache_max_size.py](../../../../utils/env/handlers/infinito_registry_cache_max_size.py) as half the free disk at the cache path, floor `1g`, fallback `2g`. |
|
||||
| `INFINITO_REGISTRY_CACHE_PROXY_CONF` | Bind source for the systemd drop-in inside `infinito`. Set by the dev tooling to the real `proxy.conf` under the `cache` profile. Driver-injected; not produced by `make dotenv`. |
|
||||
| `INFINITO_PACKAGE_CACHE_HOST_PATH` | Host path bind-mounted at `/nexus-data`. |
|
||||
| `INFINITO_PACKAGE_CACHE_HEAP` | JVM heap (`-Xms`/`-Xmx`). Computed as half free RAM, capped `2g`, floor `1g`. |
|
||||
| `INFINITO_PACKAGE_CACHE_DIRECT_MEM` | `MaxDirectMemorySize`. Mirrors `INFINITO_PACKAGE_CACHE_HEAP`. |
|
||||
| `INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX` | Soft quota for the default blobstore. Computed as half free disk at the cache path, floor `2g`. |
|
||||
| `INFINITO_PACKAGE_CACHE_MAX_AGE_MIN` | Cache freshness window in minutes, applied to every Nexus proxy repo (`contentMaxAge`/`metadataMaxAge`/negative-cache TTL). Stays strictly below the 7-day `Valid-Until` window Debian / Ubuntu generate in their apt `Release` files, so `apt-get update` never aborts with "Release file ... is expired". |
|
||||
| `INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD` | Target value for the rotated Nexus admin password. Default is a stable per-host hash. |
|
||||
| `INFINITO_PACKAGE_CACHE_PORT` | Host-side port mapped to Nexus REST/UI. Bound to `${INFINITO_BIND_IP}` only. |
|
||||
| `INFINITO_PACKAGE_CACHE_PIP_CONF` | Bind source for `/etc/pip.conf`. Driver-injected by `make up` under the `cache` profile. |
|
||||
| `INFINITO_PACKAGE_CACHE_NPMRC` | Bind source for `/root/.npmrc`. Driver-injected. |
|
||||
| `INFINITO_PACKAGE_CACHE_APT_LIST` | Bind source for `/etc/apt/sources.list.d/package-cache.list`. Driver-injected. |
|
||||
| `INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR` | Host directory for the frontend CA. |
|
||||
| `INFINITO_PACKAGE_CACHE_FRONTEND_CERTS_DIR` | Host directory for per-hostname leaf certs. |
|
||||
| `INFINITO_PACKAGE_CACHE_FRONTEND_IP` | Static IPv4 address for the frontend on the compose default network. |
|
||||
| `INFINITO_PACKAGE_CACHE_FRONTEND_CA_FILE` | Bind source for `/opt/package-frontend-ca.crt` inside the runner. Driver-injected. |
|
||||
| `INFINITO_CACHE_REGISTRY_HOST_PATH` | Host path bind-mounted into `registry-cache` for blob/manifest persistence. |
|
||||
| `INFINITO_CACHE_REGISTRY_CA_HOST_PATH` | Host path holding the proxy MITM CA bundle. Mounted writable into `registry-cache`, read-only into `infinito`. |
|
||||
| `INFINITO_CACHE_REGISTRY_MAX_SIZE` | Maximum on-disk size. Computed by [infinito_registry_cache_max_size.py](../../../../utils/env/handlers/infinito_registry_cache_max_size.py) as half the free disk at the cache path, floor `1g`, fallback `2g`. |
|
||||
| `INFINITO_CACHE_REGISTRY_PROXY_CONF` | Bind source for the systemd drop-in inside `infinito`. Set by the dev tooling to the real `proxy.conf` under the `cache` profile. Driver-injected; not produced by `make dotenv`. |
|
||||
| `INFINITO_CACHE_PACKAGE_HOST_PATH` | Host path bind-mounted at `/nexus-data`. |
|
||||
| `INFINITO_CACHE_PACKAGE_HEAP` | JVM heap (`-Xms`/`-Xmx`). Computed as half free RAM, capped `2g`, floor `1g`. |
|
||||
| `INFINITO_CACHE_PACKAGE_DIRECT_MEM` | `MaxDirectMemorySize`. Mirrors `INFINITO_CACHE_PACKAGE_HEAP`. |
|
||||
| `INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX` | Soft quota for the default blobstore. Computed as half free disk at the cache path, floor `2g`. |
|
||||
| `INFINITO_CACHE_PACKAGE_MAX_AGE_MIN` | Cache freshness window in minutes, applied to every Nexus proxy repo (`contentMaxAge`/`metadataMaxAge`/negative-cache TTL). Stays strictly below the 7-day `Valid-Until` window Debian / Ubuntu generate in their apt `Release` files, so `apt-get update` never aborts with "Release file ... is expired". |
|
||||
| `INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD` | Target value for the rotated Nexus admin password. Default is a stable per-host hash. |
|
||||
| `INFINITO_CACHE_PACKAGE_PORT` | Host-side port mapped to Nexus REST/UI. Bound to `${INFINITO_BIND_IP}` only. |
|
||||
| `INFINITO_CACHE_PACKAGE_PIP_CONF` | Bind source for `/etc/pip.conf`. Driver-injected by `make up` under the `cache` profile. |
|
||||
| `INFINITO_CACHE_PACKAGE_NPMRC` | Bind source for `/root/.npmrc`. Driver-injected. |
|
||||
| `INFINITO_CACHE_PACKAGE_APT_LIST` | Bind source for `/etc/apt/sources.list.d/package-cache.list`. Driver-injected. |
|
||||
| `INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR` | Host directory for the frontend CA. |
|
||||
| `INFINITO_CACHE_PACKAGE_FRONTEND_CERTS_DIR` | Host directory for per-hostname leaf certs. |
|
||||
| `INFINITO_CACHE_PACKAGE_FRONTEND_IP` | Static IPv4 address for the frontend on the compose default network. |
|
||||
| `INFINITO_CACHE_PACKAGE_FRONTEND_CA_FILE` | Bind source for `/opt/package-frontend-ca.crt` inside the runner. Driver-injected. |
|
||||
|
||||
### Networking
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ The `cache` decision is exposed via `Profile.registry_cache_active()` in [profil
|
||||
- The runner's `infinito` service receives:
|
||||
- bind-mounts for the registry-cache CA, the package-cache client snippets (`pip.conf`, `npmrc`, `apt.list`), and the frontend CA file
|
||||
- `extra_hosts` entries DNS-hijacking the HTTPS upstream hostnames to the frontend's static IP
|
||||
- `INFINITO_PACKAGE_CACHE_FRONTEND_IP` env var for the inner compose wrapper
|
||||
- `INFINITO_CACHE_PACKAGE_FRONTEND_IP` env var for the inner compose wrapper
|
||||
- Cert generation, Nexus repo bootstrap, and runner trust-store install run from [compose.py](../../../cli/administration/deploy/development/compose.py).
|
||||
|
||||
When inactive, the override is omitted: cache services do not exist, the runner has no cache mounts or DNS-hijack, package managers go direct to upstream.
|
||||
@@ -80,17 +80,17 @@ The runner's `compose` wrapper at [roles/sys-svc-compose/files/compose.py](../..
|
||||
| `compose.yml` | always |
|
||||
| `compose.override.yml` | when present (role provides it) |
|
||||
| `compose.ca.override.yml` | when present (TLS self-signed CA-inject runs) |
|
||||
| `compose.cache.override.yml` | generated on the fly when `INFINITO_PACKAGE_CACHE_FRONTEND_IP` is set; emits `build.extra_hosts` for every service that has a `build:` key |
|
||||
| `compose.cache.override.yml` | generated on the fly when `INFINITO_CACHE_PACKAGE_FRONTEND_IP` is set; emits `build.extra_hosts` for every service that has a `build:` key |
|
||||
|
||||
[pull.py](../../../roles/sys-svc-compose/files/pull.py) delegates to the same wrapper so `pull` and `build --pull` operations see the identical `-f` set.
|
||||
|
||||
## Environment Variables 🌳
|
||||
|
||||
The host-side env-vars (`INFINITO_REGISTRY_CACHE_*`, `INFINITO_PACKAGE_CACHE_*`) are declared in [env/default.env](../../../env/default.env) and the dynamic ones (cache sizes from `df`/`/proc/meminfo`, sha256 admin password) are computed by [utils/env/builder.py](../../../utils/env/builder.py). `make dotenv` writes them into `.env`, which Compose auto-loads and which `BASH_ENV` makes available to every Makefile recipe through [scripts/meta/env/load.sh](../../../scripts/meta/env/load.sh). See [variables.md](variables.md).
|
||||
The host-side env-vars (`INFINITO_CACHE_REGISTRY_*`, `INFINITO_CACHE_PACKAGE_*`) are declared in [env/default.env](../../../env/default.env) and the dynamic ones (cache sizes from `df`/`/proc/meminfo`, sha256 admin password) are computed by [utils/env/builder.py](../../../utils/env/builder.py). `make dotenv` writes them into `.env`, which Compose auto-loads and which `BASH_ENV` makes available to every Makefile recipe through [scripts/meta/env/load.sh](../../../scripts/meta/env/load.sh). See [variables.md](variables.md).
|
||||
|
||||
Per-variable defaults and purposes are in [compose.yml.md](../artefact/files/compose.yml.md) under the cache section.
|
||||
|
||||
`INFINITO_PACKAGE_CACHE_MAX_AGE_MIN` (default `8640` = 6 days) controls how long every Nexus proxy repo holds an upstream response before revalidating. The bootstrap helper applies it as `contentMaxAge`, `metadataMaxAge`, and `negativeCache.timeToLive`. The default is intentionally one day below the 7-day `Valid-Until` window Debian and Ubuntu generate in their apt `Release` files, so Nexus always refreshes the manifest before `apt-get update` would see it expired. Raise it for slow-moving upstreams when staleness is preferable to upstream load; lower it further for fast-moving feeds (`apt-debian-security`, branch-pinned tarballs) when staleness becomes visible.
|
||||
`INFINITO_CACHE_PACKAGE_MAX_AGE_MIN` (default `8640` = 6 days) controls how long every Nexus proxy repo holds an upstream response before revalidating. The bootstrap helper applies it as `contentMaxAge`, `metadataMaxAge`, and `negativeCache.timeToLive`. The default is intentionally one day below the 7-day `Valid-Until` window Debian and Ubuntu generate in their apt `Release` files, so Nexus always refreshes the manifest before `apt-get update` would see it expired. Raise it for slow-moving upstreams when staleness is preferable to upstream load; lower it further for fast-moving feeds (`apt-debian-security`, branch-pinned tarballs) when staleness becomes visible.
|
||||
|
||||
## Operations 🛠️
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Already-set env vars take precedence over `default.env` defaults.
|
||||
|
||||
A handful of variables are documented next to their consumer because they cannot have a static default:
|
||||
|
||||
- **Driver-injected** by `make up` and the dev CLI (cache override paths like `INFINITO_REGISTRY_CACHE_PROXY_CONF`, the package-cache config paths, the frontend CA file).
|
||||
- **Driver-injected** by `make up` and the dev CLI (cache override paths like `INFINITO_CACHE_REGISTRY_PROXY_CONF`, the package-cache config paths, the frontend CA file).
|
||||
See [common.py](../../../cli/administration/deploy/development/common.py) and [compose.yml.md](../artefact/files/compose.yml.md).
|
||||
- **Computed at container start** for `INFINITO_PATH`, set by [entry.sh](../../../scripts/docker/entry.sh).
|
||||
- **Persisted on the deploy target** for `INFINITO_VERSION`, written by the [sys-version](../../../roles/sys-version/) role.
|
||||
|
||||
@@ -93,10 +93,10 @@ Therefore:
|
||||
### Env defaults
|
||||
|
||||
- [ ] `scripts/meta/env/cache/package.sh` exists and exports
|
||||
`INFINITO_PACKAGE_CACHE_HOST_PATH`, `INFINITO_PACKAGE_CACHE_HEAP`,
|
||||
`INFINITO_PACKAGE_CACHE_DIRECT_MEM`,
|
||||
`INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX`, and
|
||||
`INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD`.
|
||||
`INFINITO_CACHE_PACKAGE_HOST_PATH`, `INFINITO_CACHE_PACKAGE_HEAP`,
|
||||
`INFINITO_CACHE_PACKAGE_DIRECT_MEM`,
|
||||
`INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX`, and
|
||||
`INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD`.
|
||||
- [ ] `scripts/meta/env/cache/package.sh` is sourced by
|
||||
`scripts/meta/env/all.sh`.
|
||||
- [ ] `compose/cache.override.yml` consumes every package-cache env
|
||||
@@ -109,11 +109,11 @@ Therefore:
|
||||
(repeated runs MUST NOT error on already-existing repos), and is
|
||||
invoked once on first start under the `cache` profile.
|
||||
- [ ] On first start, the auto-generated Nexus admin password is
|
||||
rotated to the value of `INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD`.
|
||||
rotated to the value of `INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD`.
|
||||
- [ ] On first start, the proxy repositories listed in
|
||||
[cache.md](../contributing/environment/cache.md) are created.
|
||||
- [ ] The blobstore `default` is created with quota
|
||||
`INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX`.
|
||||
`INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX`.
|
||||
|
||||
### Client wiring
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ These commits are not per-role fixes but address the underlying environment so s
|
||||
| --- | --- | --- |
|
||||
| `c6affc96f` | New entity-keyed purge primitive `scripts/container/purge/entity/network.sh` removes the per-entity default Docker network when `compose down` left it behind with the wrong / empty `com.docker.compose.network` label. `scripts/container/purge/apps.sh` runs the primitive per entity and then issues a single bare `docker network prune -f` after the entity loop. | C9b (gitea), C12 (matrix); any future role hitting the same orphan-network signature. |
|
||||
| `6d61e3ca4` | Source-able helper `scripts/tests/deploy/local/utils/cache-retry.sh` wraps the deploy command in each local deploy entrypoint (`fresh-purged-app.sh`, `fresh-kept-app.sh`, `fresh-kept-all.sh`, `reuse-kept-app.sh`, `reuse-kept-all.sh`). Detects the stale-apt signature `Release file ... is expired` / `Valid-Until ... expired` in the wrapped command's output, runs `make cache-clean` + `docker builder prune -af` + `docker image prune -af`, then re-runs the command exactly once. | Defensive fallback for "apt Release file ... is expired" Docker-build failures during local iteration. |
|
||||
| `e58f71987` | Default `INFINITO_PACKAGE_CACHE_MAX_AGE_MIN` lowered 129600 → 8640 (90 days → 6 days). Nexus now re-validates every cached `Release` against upstream before apt would see it expired (Debian/Ubuntu emit `Valid-Until` 7 days from generation). Applies to every Nexus proxy repo via `contentMaxAge` / `metadataMaxAge` / `negativeCache.timeToLive`. | Prevents the underlying stale-apt failure from reaching the deploy in the first place; the cache-retry helper from `6d61e3ca4` is the secondary defence. |
|
||||
| `e58f71987` | Default `INFINITO_CACHE_PACKAGE_MAX_AGE_MIN` lowered 129600 → 8640 (90 days → 6 days). Nexus now re-validates every cached `Release` against upstream before apt would see it expired (Debian/Ubuntu emit `Valid-Until` 7 days from generation). Applies to every Nexus proxy repo via `contentMaxAge` / `metadataMaxAge` / `negativeCache.timeToLive`. | Prevents the underlying stale-apt failure from reaching the deploy in the first place; the cache-retry helper from `6d61e3ca4` is the secondary defence. |
|
||||
|
||||
## CI Run 25890032686 follow-up snapshot
|
||||
|
||||
|
||||
Vendored
+48
-27
@@ -22,6 +22,48 @@ INFINITO_BIND_IP=127.0.0.1
|
||||
# Comma-separated inventory bundles consumed by 'make deploy-bundles' / 'make redeploy-bundles'.
|
||||
INFINITO_BUNDLES=
|
||||
|
||||
# Bind-mounted apt sources.list fragment for the package-cache frontend.
|
||||
INFINITO_CACHE_PACKAGE_APT_LIST=./compose/package-cache/apt.list
|
||||
|
||||
# Host directory holding the package-cache frontend CA materials.
|
||||
INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR=/var/cache/infinito/core/cache/package/frontend/ca
|
||||
|
||||
# CA certificate file produced under INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR.
|
||||
INFINITO_CACHE_PACKAGE_FRONTEND_CA_FILE=/var/cache/infinito/core/cache/package/frontend/ca/ca.crt
|
||||
|
||||
# Host directory holding the package-cache frontend TLS certificates.
|
||||
INFINITO_CACHE_PACKAGE_FRONTEND_CERTS_DIR=/var/cache/infinito/core/cache/package/frontend/certs
|
||||
|
||||
# Init image used by the package-cache frontend cert-gen wrapper to run cert-gen.sh.
|
||||
INFINITO_CACHE_PACKAGE_FRONTEND_INIT_IMAGE=alpine:3.20
|
||||
|
||||
# Static IPv4 address the package-cache frontend takes on the docker bridge.
|
||||
INFINITO_CACHE_PACKAGE_FRONTEND_IP=172.30.0.4
|
||||
|
||||
# Host directory backing the Nexus 3 package-cache blobstore.
|
||||
INFINITO_CACHE_PACKAGE_HOST_PATH=/var/cache/infinito/core/cache/package/data
|
||||
|
||||
# Cache freshness window (minutes) Nexus tolerates before re-validating upstream. 8640 = 6 days (below the 7-day Debian Valid-Until).
|
||||
INFINITO_CACHE_PACKAGE_MAX_AGE_MIN=8640
|
||||
|
||||
# Bind-mounted .npmrc fragment for the package-cache frontend.
|
||||
INFINITO_CACHE_PACKAGE_NPMRC=./compose/package-cache/npmrc
|
||||
|
||||
# Bind-mounted pip.conf fragment for the package-cache frontend.
|
||||
INFINITO_CACHE_PACKAGE_PIP_CONF=./compose/package-cache/pip.conf
|
||||
|
||||
# Host port the Nexus 3 package cache binds to on BIND_IP.
|
||||
INFINITO_CACHE_PACKAGE_PORT=8081
|
||||
|
||||
# Host directory holding the registry-cache CA materials.
|
||||
INFINITO_CACHE_REGISTRY_CA_HOST_PATH=/var/cache/infinito/core/cache/registry/ca
|
||||
|
||||
# Host directory backing the docker registry-mirror cache (bind mount).
|
||||
INFINITO_CACHE_REGISTRY_HOST_PATH=/var/cache/infinito/core/cache/registry/mirror
|
||||
|
||||
# Bind-mounted registry-cache proxy configuration consumed by the cache container.
|
||||
INFINITO_CACHE_REGISTRY_PROXY_CONF=./compose/registry-cache/proxy.conf
|
||||
|
||||
# One-shot command 'make exec INFINITO_CMD=...' runs inside the dev container (empty -> interactive shell).
|
||||
INFINITO_CMD=
|
||||
|
||||
@@ -49,6 +91,12 @@ INFINITO_DISTRO=debian
|
||||
# Space-separated list of distros the build/test matrix iterates over.
|
||||
INFINITO_DISTROS="arch debian ubuntu fedora centos"
|
||||
|
||||
# Domain the host-side DNS-fallback / dnsmasq config wires up (consumed by scripts/system/network/dns/).
|
||||
INFINITO_DNS_DOMAIN=infinito.example
|
||||
|
||||
# Path to the hosts file the DNS-fallback installer writes the BEGIN/END block into.
|
||||
INFINITO_DNS_HOSTS_FILE=/etc/hosts
|
||||
|
||||
# Mount point inside the infinito container where INFINITO_DOCKER_VOLUME lands.
|
||||
INFINITO_DOCKER_MOUNT=/var/lib/docker
|
||||
|
||||
@@ -88,36 +136,9 @@ INFINITO_NO_BUILD=1
|
||||
# MTU of the compose stack's bridge network; 1500 matches most hosts.
|
||||
INFINITO_OUTER_NETWORK_MTU=1500
|
||||
|
||||
# Host directory holding the package-cache frontend CA materials.
|
||||
INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR=/var/cache/infinito/core/cache/package/frontend/ca
|
||||
|
||||
# Host directory holding the package-cache frontend TLS certificates.
|
||||
INFINITO_PACKAGE_CACHE_FRONTEND_CERTS_DIR=/var/cache/infinito/core/cache/package/frontend/certs
|
||||
|
||||
# Init image used by the package-cache frontend cert-gen wrapper to run cert-gen.sh.
|
||||
INFINITO_PACKAGE_CACHE_FRONTEND_INIT_IMAGE=alpine:3.20
|
||||
|
||||
# Static IPv4 address the package-cache frontend takes on the docker bridge.
|
||||
INFINITO_PACKAGE_CACHE_FRONTEND_IP=172.30.0.4
|
||||
|
||||
# Host directory backing the Nexus 3 package-cache blobstore.
|
||||
INFINITO_PACKAGE_CACHE_HOST_PATH=/var/cache/infinito/core/cache/package/data
|
||||
|
||||
# Cache freshness window (minutes) Nexus tolerates before re-validating upstream. 8640 = 6 days (below the 7-day Debian Valid-Until).
|
||||
INFINITO_PACKAGE_CACHE_MAX_AGE_MIN=8640
|
||||
|
||||
# Host port the Nexus 3 package cache binds to on BIND_IP.
|
||||
INFINITO_PACKAGE_CACHE_PORT=8081
|
||||
|
||||
# Docker pull policy compose applies; generator overrides to `always` on GHA.
|
||||
INFINITO_PULL_POLICY=never
|
||||
|
||||
# Host directory holding the registry-cache CA materials.
|
||||
INFINITO_REGISTRY_CACHE_CA_HOST_PATH=/var/cache/infinito/core/cache/registry/ca
|
||||
|
||||
# Host directory backing the docker registry-mirror cache (bind mount).
|
||||
INFINITO_REGISTRY_CACHE_HOST_PATH=/var/cache/infinito/core/cache/registry/mirror
|
||||
|
||||
# True when running under nektos/act locally; generator overrides via runtime detection.
|
||||
INFINITO_RUNNING_ON_ACT=false
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ _CACHE_HTTP_HOSTNAMES = (
|
||||
|
||||
def generate_cache_override(project_dir: Path, base_compose: Path) -> Path | None:
|
||||
"""Emit transient build.extra_hosts override when cache profile active."""
|
||||
cache_ip = (os.environ.get("INFINITO_PACKAGE_CACHE_FRONTEND_IP") or "").strip()
|
||||
cache_ip = (os.environ.get("INFINITO_CACHE_PACKAGE_FRONTEND_IP") or "").strip()
|
||||
if not cache_ip or not base_compose.is_file():
|
||||
return None
|
||||
|
||||
|
||||
@@ -447,7 +447,7 @@ def main() -> int:
|
||||
return 2
|
||||
|
||||
if cmd == "run":
|
||||
no_ca = os.environ.get("INFINITO_CONTAINER_NO_CA", "").lower() in {
|
||||
no_ca = os.environ["INFINITO_CONTAINER_NO_CA"].lower() in {
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
|
||||
+6
-6
@@ -5,8 +5,8 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR:?Source scripts/meta/env/load.sh first}"
|
||||
: "${INFINITO_PACKAGE_CACHE_FRONTEND_CERTS_DIR:?Source scripts/meta/env/load.sh first}"
|
||||
: "${INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR:?Source scripts/meta/env/load.sh first}"
|
||||
: "${INFINITO_CACHE_PACKAGE_FRONTEND_CERTS_DIR:?Source scripts/meta/env/load.sh first}"
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||
INNER_SCRIPT="${REPO_ROOT}/compose/package-cache-frontend/cert-gen.sh"
|
||||
@@ -16,12 +16,12 @@ if [[ ! -r "${INNER_SCRIPT}" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
: "${INFINITO_PACKAGE_CACHE_FRONTEND_INIT_IMAGE:?Source scripts/meta/env/load.sh first}"
|
||||
ALPINE_IMAGE="${INFINITO_PACKAGE_CACHE_FRONTEND_INIT_IMAGE}"
|
||||
: "${INFINITO_CACHE_PACKAGE_FRONTEND_INIT_IMAGE:?Source scripts/meta/env/load.sh first}"
|
||||
ALPINE_IMAGE="${INFINITO_CACHE_PACKAGE_FRONTEND_INIT_IMAGE}"
|
||||
|
||||
exec docker run --rm \
|
||||
-v "${INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR}:/ca" \
|
||||
-v "${INFINITO_PACKAGE_CACHE_FRONTEND_CERTS_DIR}:/certs" \
|
||||
-v "${INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR}:/ca" \
|
||||
-v "${INFINITO_CACHE_PACKAGE_FRONTEND_CERTS_DIR}:/certs" \
|
||||
-v "${INNER_SCRIPT}:/work/cert-gen.sh:ro" \
|
||||
--entrypoint /bin/sh \
|
||||
"${ALPINE_IMAGE}" \
|
||||
|
||||
Vendored
+12
-12
@@ -5,12 +5,12 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${INFINITO_PACKAGE_CACHE_HOST_PATH:?Source scripts/meta/env/load.sh first}"
|
||||
: "${INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD:?Source scripts/meta/env/load.sh first}"
|
||||
: "${INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX:?Source scripts/meta/env/load.sh first}"
|
||||
: "${INFINITO_PACKAGE_CACHE_MAX_AGE_MIN:?Source scripts/meta/env/load.sh first}"
|
||||
: "${INFINITO_CACHE_PACKAGE_HOST_PATH:?Source scripts/meta/env/load.sh first}"
|
||||
: "${INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD:?Source scripts/meta/env/load.sh first}"
|
||||
: "${INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX:?Source scripts/meta/env/load.sh first}"
|
||||
: "${INFINITO_CACHE_PACKAGE_MAX_AGE_MIN:?Source scripts/meta/env/load.sh first}"
|
||||
|
||||
CACHE_MAX_AGE_MIN="${INFINITO_PACKAGE_CACHE_MAX_AGE_MIN}"
|
||||
CACHE_MAX_AGE_MIN="${INFINITO_CACHE_PACKAGE_MAX_AGE_MIN}"
|
||||
|
||||
# REST goes via `docker exec curl` to bypass sandbox network isolation.
|
||||
PKGCACHE_CONTAINER="infinito-package-cache"
|
||||
@@ -39,7 +39,7 @@ rotate_admin_password() {
|
||||
ADMIN_USER="admin"
|
||||
if docker exec "${PKGCACHE_CONTAINER}" test -f "${BOOTSTRAP_DONE_FILE}"; then
|
||||
log "Already bootstrapped; using stored admin password"
|
||||
ADMIN_PASS="${INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD}"
|
||||
ADMIN_PASS="${INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -47,24 +47,24 @@ rotate_admin_password() {
|
||||
if ! nexus_curl -fsS -u "admin:${NEXUS_DEFAULT_PASSWORD}" \
|
||||
-H "Content-Type: text/plain" \
|
||||
-X PUT "${NEXUS_REST}/v1/security/users/admin/change-password" \
|
||||
--data-binary "${INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD}"; then
|
||||
--data-binary "${INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD}"; then
|
||||
# Recover from a partially-completed previous bootstrap.
|
||||
if nexus_curl -fsS -o /dev/null -u "admin:${INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD}" \
|
||||
if nexus_curl -fsS -o /dev/null -u "admin:${INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD}" \
|
||||
"${NEXUS_REST}/v1/repositories" >/dev/null 2>&1; then
|
||||
log "Default rotation rejected but configured password authenticates; assuming previous bootstrap, marking done"
|
||||
else
|
||||
log "Cannot authenticate with default '${NEXUS_DEFAULT_PASSWORD}' nor with configured INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD."
|
||||
log "Wipe ${INFINITO_PACKAGE_CACHE_HOST_PATH} (e.g. via 'make cache-clean') and retry."
|
||||
log "Cannot authenticate with default '${NEXUS_DEFAULT_PASSWORD}' nor with configured INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD."
|
||||
log "Wipe ${INFINITO_CACHE_PACKAGE_HOST_PATH} (e.g. via 'make cache-clean') and retry."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
docker exec "${PKGCACHE_CONTAINER}" touch "${BOOTSTRAP_DONE_FILE}"
|
||||
ADMIN_PASS="${INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD}"
|
||||
ADMIN_PASS="${INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD}"
|
||||
}
|
||||
|
||||
ensure_blobstore() {
|
||||
# Nexus quota expects MB; convert from "Ng".
|
||||
local quota_gb="${INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX%g}"
|
||||
local quota_gb="${INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX%g}"
|
||||
local quota_mb=$((quota_gb * 1024))
|
||||
local payload
|
||||
payload="$(printf '{"name":"default","path":"default","softQuota":{"type":"spaceUsedQuota","limit":%d}}' "${quota_mb}")"
|
||||
|
||||
@@ -10,7 +10,7 @@ fi
|
||||
# Compute dynamically if not provided from outside
|
||||
echo "[docker-infinito] before pkgmgr path"
|
||||
INFINITO_PATH="$(pkgmgr path infinito)"
|
||||
echo "[docker-infinito] after pkgmgr path: ${INFINITO_PATH}"
|
||||
echo "[docker-infinito] after pkgmgr path: ${INFINITO_PATH}" # nocheck: container-bootstrap
|
||||
|
||||
INFINITO_SRC_DIR="/opt/src/infinito"
|
||||
export INFINITO_PATH
|
||||
@@ -18,13 +18,13 @@ export INFINITO_SRC_DIR
|
||||
|
||||
run_local_build() {
|
||||
echo "[docker-infinito] Build enabled (INFINITO_COMPILE=1)"
|
||||
echo "[docker-infinito] Using ${INFINITO_PATH} as working directory"
|
||||
echo "[docker-infinito] Using ${INFINITO_PATH} as working directory" # nocheck: container-bootstrap
|
||||
|
||||
mkdir -p "${INFINITO_PATH}"
|
||||
cd "${INFINITO_PATH}"
|
||||
mkdir -p "${INFINITO_PATH}" # nocheck: container-bootstrap
|
||||
cd "${INFINITO_PATH}" # nocheck: container-bootstrap
|
||||
|
||||
echo "[docker-infinito] Copy ${INFINITO_SRC_DIR} to ${INFINITO_PATH}..."
|
||||
rsync -a --delete --chown=root:root --exclude='.git' "${INFINITO_SRC_DIR}/" "${INFINITO_PATH}/"
|
||||
echo "[docker-infinito] Copy ${INFINITO_SRC_DIR} to ${INFINITO_PATH}..." # nocheck: container-bootstrap
|
||||
rsync -a --delete --chown=root:root --exclude='.git' "${INFINITO_SRC_DIR}/" "${INFINITO_PATH}/" # nocheck: container-bootstrap
|
||||
|
||||
echo "[docker-infinito] Reinstall via 'make install'..."
|
||||
make install
|
||||
@@ -51,6 +51,6 @@ if [[ $# -eq 0 ]]; then
|
||||
echo "[docker-infinito] No arguments provided. Showing infinito help..."
|
||||
exec infinito --help
|
||||
else
|
||||
cd "${INFINITO_PATH}"
|
||||
cd "${INFINITO_PATH}" # nocheck: container-bootstrap
|
||||
exec "$@"
|
||||
fi
|
||||
Vendored
+2
-2
@@ -30,10 +30,10 @@
|
||||
# short-circuit here when we see it, so subprocesses skip the load
|
||||
# entirely during generation.
|
||||
|
||||
if [[ "${INFINITO_ENV_LOADED:-}" == "1" ]]; then
|
||||
if [[ "${INFINITO_ENV_LOADED:-}" == "1" ]]; then # nocheck: env-loader-internal-guard
|
||||
return 0
|
||||
fi
|
||||
if [[ "${INFINITO_ENV_GENERATING:-}" == "1" ]]; then
|
||||
if [[ "${INFINITO_ENV_GENERATING:-}" == "1" ]]; then # nocheck: env-loader-internal-guard
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -37,9 +37,9 @@ if docker ps --format '{{.Names}}' | grep -qE '^infinito-(registry|package)-cach
|
||||
fi
|
||||
|
||||
PATHS=(
|
||||
"${INFINITO_REGISTRY_CACHE_HOST_PATH}"
|
||||
"${INFINITO_REGISTRY_CACHE_CA_HOST_PATH}"
|
||||
"${INFINITO_PACKAGE_CACHE_HOST_PATH}"
|
||||
"${INFINITO_CACHE_REGISTRY_HOST_PATH}"
|
||||
"${INFINITO_CACHE_REGISTRY_CA_HOST_PATH}"
|
||||
"${INFINITO_CACHE_PACKAGE_HOST_PATH}"
|
||||
)
|
||||
|
||||
# Remove each cache path individually (keeps the parent /var/cache/
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
DNS_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DNS_PROJECT_ROOT="$(cd "${DNS_SCRIPT_DIR}/../../../.." && pwd)"
|
||||
DNS_DOMAIN="${INFINITO_DNS_DOMAIN:-infinito.example}" # nocheck: host-dns-override -- run with sudo outside load.sh context; tweak the host's /etc/hosts only
|
||||
DNS_HOSTS_FILE="${INFINITO_DNS_HOSTS_FILE:-/etc/hosts}" # nocheck: host-dns-override -- run with sudo outside load.sh context; tweak the host's /etc/hosts only
|
||||
DNS_DOMAIN="${INFINITO_DNS_DOMAIN}"
|
||||
DNS_HOSTS_FILE="${INFINITO_DNS_HOSTS_FILE}"
|
||||
DNS_HOSTS_BLOCK_BEGIN="# BEGIN infinito-dns-fallback"
|
||||
DNS_HOSTS_BLOCK_END="# END infinito-dns-fallback"
|
||||
DNS_HOSTS_GENERATOR="${DNS_PROJECT_ROOT}/cli/meta/domains/__main__.py"
|
||||
@@ -19,9 +19,6 @@ DNS_NM_DNSMASQ_CONF="${DNS_NM_DNSMASQ_DIR}/${DNS_DOMAIN}.conf"
|
||||
DNS_SYS_DNSMASQ_CONF="/etc/dnsmasq.d/${DNS_DOMAIN}.conf"
|
||||
|
||||
dns_systemd_is_operational() {
|
||||
if [[ "${INFINITO_DNS_FORCE_HOSTS_FALLBACK:-0}" == "1" ]]; then # nocheck: host-dns-override -- run with sudo outside load.sh context
|
||||
return 1
|
||||
fi
|
||||
command -v systemctl >/dev/null 2>&1 && [[ -d /run/systemd/system ]]
|
||||
}
|
||||
|
||||
@@ -87,8 +84,8 @@ dns_read_generated_hosts_fallback_entries() {
|
||||
}
|
||||
|
||||
dns_read_hosts_fallback_entries() {
|
||||
if [[ -n "${INFINITO_DNS_HOSTS:-}" ]]; then
|
||||
dns_read_hosts_fallback_entries_from_raw "${INFINITO_DNS_HOSTS}"
|
||||
if [[ -n "${INFINITO_DNS_HOSTS:-}" ]]; then # nocheck: infinito-dns-hosts-optional
|
||||
dns_read_hosts_fallback_entries_from_raw "${INFINITO_DNS_HOSTS}" # nocheck: infinito-dns-hosts-optional
|
||||
return
|
||||
fi
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
: "${INFINITO_TEST_PATTERN:?INFINITO_TEST_PATTERN must be set}"
|
||||
: "${INFINITO_TEST_TYPE:?INFINITO_TEST_TYPE must be set}"
|
||||
: "${INFINITO_TEST_TYPE:?INFINITO_TEST_TYPE must be set}" # nocheck: makefile-supplied
|
||||
|
||||
NIX_CONFIG_EFFECTIVE="$(
|
||||
printf "%s\n%s\n" \
|
||||
@@ -25,4 +25,4 @@ if [ -n "${PYTHON:-}" ]; then
|
||||
fi
|
||||
|
||||
make setup
|
||||
"${PYTHON:-python3}" -m unittest discover -s "tests/${INFINITO_TEST_TYPE}" -t . -p "${INFINITO_TEST_PATTERN}"
|
||||
"${PYTHON:-python3}" -m unittest discover -s "tests/${INFINITO_TEST_TYPE}" -t . -p "${INFINITO_TEST_PATTERN}" # nocheck: makefile-supplied
|
||||
@@ -19,7 +19,7 @@ source scripts/meta/env/load.sh
|
||||
|
||||
: "${INFINITO_TEST_PATTERN:?INFINITO_TEST_PATTERN must be set}"
|
||||
: "${INFINITO_TEST_RUNNER:?INFINITO_TEST_RUNNER must be set}"
|
||||
: "${INFINITO_TEST_TYPE:?INFINITO_TEST_TYPE must be set}"
|
||||
: "${INFINITO_TEST_TYPE:?INFINITO_TEST_TYPE must be set}" # nocheck: makefile-supplied
|
||||
|
||||
RUN_SCRIPT_IN_CONTAINER="${INFINITO_SRC_DIR}/scripts/tests/code/run.sh"
|
||||
|
||||
@@ -28,7 +28,7 @@ docker)
|
||||
# INFINITO_DISTRO is sourced (with a `debian` default) from
|
||||
# scripts/meta/env/load.sh above, so no explicit check here.
|
||||
echo "============================================================"
|
||||
echo ">>> Running ${INFINITO_TEST_TYPE^^} tests in ${INFINITO_DISTRO} container (compose stack)"
|
||||
echo ">>> Running ${INFINITO_TEST_TYPE^^} tests in ${INFINITO_DISTRO} container (compose stack)" # nocheck: makefile-supplied
|
||||
echo "============================================================"
|
||||
|
||||
# Auto-bring-up: if the `infinito` container is not running, kick off
|
||||
@@ -48,23 +48,30 @@ docker)
|
||||
# every INFINITO_* key from the bind-mounted .env into the test
|
||||
# runner's environment (notably INFINITO_WORKER_FETCH, otherwise the
|
||||
# external URL probe collapses to 1 worker).
|
||||
# Per-invocation env passed through docker exec. Built as a bash
|
||||
# array so the inline `# nocheck:` markers between elements stay
|
||||
# syntactically valid (a backslash-continued line cannot host an
|
||||
# inline comment).
|
||||
exec_env_args=(
|
||||
-e ACT="${ACT:-}"
|
||||
-e BASH_ENV="${INFINITO_SRC_DIR}/scripts/meta/env/load.sh"
|
||||
-e GITHUB_ACTIONS="${GITHUB_ACTIONS:-}"
|
||||
-e GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-}"
|
||||
-e GITHUB_REPOSITORY_OWNER="${GITHUB_REPOSITORY_OWNER:-}"
|
||||
-e GITHUB_SHA="${GITHUB_SHA:-}"
|
||||
-e INFINITO_TEST_PATTERN="${INFINITO_TEST_PATTERN}"
|
||||
-e INFINITO_TEST_TYPE="${INFINITO_TEST_TYPE}" # nocheck: makefile-supplied
|
||||
)
|
||||
INFINITO_DISTRO="${INFINITO_DISTRO}" \
|
||||
docker compose --env-file env/ci.env --env-file .env --profile ci exec -T \
|
||||
-e ACT="${ACT:-}" \
|
||||
-e BASH_ENV="${INFINITO_SRC_DIR}/scripts/meta/env/load.sh" \
|
||||
-e GITHUB_ACTIONS="${GITHUB_ACTIONS:-}" \
|
||||
-e GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-}" \
|
||||
-e GITHUB_REPOSITORY_OWNER="${GITHUB_REPOSITORY_OWNER:-}" \
|
||||
-e GITHUB_SHA="${GITHUB_SHA:-}" \
|
||||
-e INFINITO_TEST_PATTERN="${INFINITO_TEST_PATTERN}" \
|
||||
-e INFINITO_TEST_TYPE="${INFINITO_TEST_TYPE}" \
|
||||
"${exec_env_args[@]}" \
|
||||
--workdir "${INFINITO_SRC_DIR}" \
|
||||
infinito \
|
||||
bash --login "${RUN_SCRIPT_IN_CONTAINER}"
|
||||
;;
|
||||
host)
|
||||
echo "============================================================"
|
||||
echo ">>> Running ${INFINITO_TEST_TYPE^^} tests on host"
|
||||
echo ">>> Running ${INFINITO_TEST_TYPE^^} tests on host" # nocheck: makefile-supplied
|
||||
echo "============================================================"
|
||||
exec bash --login "${RUN_SCRIPT}"
|
||||
;;
|
||||
|
||||
@@ -18,7 +18,7 @@ _cache_retry_nexus_invalidate() {
|
||||
local rest="http://127.0.0.1:8081/service/rest/v1/repositories/${repo}/invalidate-cache"
|
||||
local code
|
||||
code="$(docker exec infinito-package-cache curl -sS -o /dev/null -w '%{http_code}' \
|
||||
-u "admin:${INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD}" \
|
||||
-u "admin:${INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD}" \
|
||||
-X POST "${rest}" 2>/dev/null || echo 000)"
|
||||
if [[ "${code}" =~ ^2[0-9][0-9]$ ]]; then
|
||||
echo "[cache-retry] invalidate-cache OK (${code}): ${repo}"
|
||||
@@ -30,7 +30,7 @@ _cache_retry_nexus_invalidate() {
|
||||
_cache_retry_list_apt_proxy_repos() {
|
||||
local json
|
||||
json="$(docker exec infinito-package-cache curl -sS \
|
||||
-u "admin:${INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD}" \
|
||||
-u "admin:${INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD}" \
|
||||
"http://127.0.0.1:8081/service/rest/v1/repositories" 2>/dev/null)" || return 1
|
||||
[[ -z "${json}" ]] && return 1
|
||||
printf '%s' "${json}" |
|
||||
|
||||
@@ -16,7 +16,7 @@ set -euo pipefail
|
||||
cd /opt/src/infinito
|
||||
|
||||
: "${INFINITO_INVENTORY_FILE:?INFINITO_INVENTORY_FILE must be set}"
|
||||
: "${INFINITO_INVENTORY_PASSWORD_FILE:?INFINITO_INVENTORY_PASSWORD_FILE must be set}"
|
||||
: "${INFINITO_INVENTORY_PASSWORD_FILE:?INFINITO_INVENTORY_PASSWORD_FILE must be set}" # nocheck: driver-injected-per-invocation
|
||||
: "${INFINITO_LIMIT_HOST:?INFINITO_LIMIT_HOST must be set}"
|
||||
: "${INFINITO_DEBUG:?INFINITO_DEBUG must be set}"
|
||||
|
||||
@@ -29,7 +29,7 @@ cmd=(infinito administration deploy dedicated "${INFINITO_INVENTORY_FILE}"
|
||||
-l "${INFINITO_LIMIT_HOST}"
|
||||
--diff
|
||||
-vv
|
||||
--password-file "${INFINITO_INVENTORY_PASSWORD_FILE}"
|
||||
--password-file "${INFINITO_INVENTORY_PASSWORD_FILE}" # nocheck: driver-injected-per-invocation
|
||||
-e ASYNC_ENABLED=false
|
||||
-e SYS_SERVICE_ALL_ENABLED=false
|
||||
-e SYS_SERVICE_DEFAULT_STATE=started
|
||||
|
||||
@@ -213,9 +213,9 @@ probe_caches() {
|
||||
# Ansible-driven runner-side traffic alone does NOT exercise.
|
||||
probe_did_inner_build() {
|
||||
local frontend_ip
|
||||
frontend_ip="${INFINITO_PACKAGE_CACHE_FRONTEND_IP:-}"
|
||||
frontend_ip="${INFINITO_CACHE_PACKAGE_FRONTEND_IP:-}"
|
||||
if [[ -z "${frontend_ip}" ]]; then
|
||||
echo "[probe-did] INFINITO_PACKAGE_CACHE_FRONTEND_IP unset; skipping DiD probe"
|
||||
echo "[probe-did] INFINITO_CACHE_PACKAGE_FRONTEND_IP unset; skipping DiD probe"
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ _WARNING_STATUS_CODES = {
|
||||
}
|
||||
_REQUEST_TIMEOUT_SECONDS = 10
|
||||
_USER_AGENT = "infinito-nexus-url-reachability-check"
|
||||
_MAX_WORKERS = int(os.environ.get("INFINITO_WORKER_FETCH", "1"))
|
||||
_MAX_WORKERS = int(os.environ["INFINITO_WORKER_FETCH"])
|
||||
# Hard ceiling for the whole probe loop. After this elapses, any probe
|
||||
# still in flight is marked as a Timeout warning so the test never hangs
|
||||
# indefinitely on a slow / trickling server. 5h30m matches the longest
|
||||
|
||||
+14
-14
@@ -85,20 +85,20 @@ EXPECTED_BASELINE_KEYS = frozenset(
|
||||
"PIP",
|
||||
"PYTHONPATH",
|
||||
"INVENTORY_VARS_FILE",
|
||||
"INFINITO_REGISTRY_CACHE_HOST_PATH",
|
||||
"INFINITO_REGISTRY_CACHE_CA_HOST_PATH",
|
||||
"INFINITO_REGISTRY_CACHE_MAX_SIZE",
|
||||
"INFINITO_PACKAGE_CACHE_HOST_PATH",
|
||||
"INFINITO_PACKAGE_CACHE_PORT",
|
||||
"INFINITO_PACKAGE_CACHE_HEAP",
|
||||
"INFINITO_PACKAGE_CACHE_DIRECT_MEM",
|
||||
"INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX",
|
||||
"INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD",
|
||||
"INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR",
|
||||
"INFINITO_PACKAGE_CACHE_FRONTEND_CERTS_DIR",
|
||||
"INFINITO_PACKAGE_CACHE_FRONTEND_IP",
|
||||
"INFINITO_PACKAGE_CACHE_FRONTEND_INIT_IMAGE",
|
||||
"INFINITO_PACKAGE_CACHE_MAX_AGE_MIN",
|
||||
"INFINITO_CACHE_REGISTRY_HOST_PATH",
|
||||
"INFINITO_CACHE_REGISTRY_CA_HOST_PATH",
|
||||
"INFINITO_CACHE_REGISTRY_MAX_SIZE",
|
||||
"INFINITO_CACHE_PACKAGE_HOST_PATH",
|
||||
"INFINITO_CACHE_PACKAGE_PORT",
|
||||
"INFINITO_CACHE_PACKAGE_HEAP",
|
||||
"INFINITO_CACHE_PACKAGE_DIRECT_MEM",
|
||||
"INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX",
|
||||
"INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD",
|
||||
"INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR",
|
||||
"INFINITO_CACHE_PACKAGE_FRONTEND_CERTS_DIR",
|
||||
"INFINITO_CACHE_PACKAGE_FRONTEND_IP",
|
||||
"INFINITO_CACHE_PACKAGE_FRONTEND_INIT_IMAGE",
|
||||
"INFINITO_CACHE_PACKAGE_MAX_AGE_MIN",
|
||||
"INFINITO_IMAGE_TAG",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
"""Every key in env/default.env must be wired through a handler builder.
|
||||
|
||||
env/default.env is the SPOT for static defaults. The handler builders
|
||||
under ``utils/env/handlers/`` are responsible for applying each entry
|
||||
as a ``setdefault`` on the EnvBuilder. If a key lives in default.env
|
||||
but no handler references it, the generated ``.env`` will silently
|
||||
miss the default and downstream consumers crash with an unbound
|
||||
variable.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import unittest
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from utils.cache.files import read_text
|
||||
|
||||
from . import PROJECT_ROOT
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
_KEY_RE = re.compile(r"^\s*(?P<key>INFINITO_[A-Z0-9_]+)\s*=")
|
||||
_HANDLER_LITERAL_RE = re.compile(r"[\"'](?P<key>INFINITO_[A-Z0-9_]+)[\"']")
|
||||
|
||||
|
||||
def _default_env_keys(path: Path) -> set[str]:
|
||||
return {
|
||||
match.group("key")
|
||||
for line in read_text(str(path)).splitlines()
|
||||
if (match := _KEY_RE.match(line)) is not None
|
||||
}
|
||||
|
||||
|
||||
def _handler_referenced_keys(handlers_dir: Path) -> set[str]:
|
||||
keys: set[str] = set()
|
||||
for module in sorted(handlers_dir.glob("*.py")):
|
||||
if module.name == "__init__.py":
|
||||
continue
|
||||
text = read_text(str(module))
|
||||
for match in _HANDLER_LITERAL_RE.finditer(text):
|
||||
keys.add(match.group("key"))
|
||||
return keys
|
||||
|
||||
|
||||
class TestDefaultEnvKeysHandled(unittest.TestCase):
|
||||
def test_every_default_env_key_has_a_handler(self) -> None:
|
||||
default_env_path = PROJECT_ROOT / "env" / "default.env"
|
||||
handlers_dir = PROJECT_ROOT / "utils" / "env" / "handlers"
|
||||
|
||||
self.assertTrue(default_env_path.is_file(), "env/default.env not found")
|
||||
self.assertTrue(handlers_dir.is_dir(), "utils/env/handlers/ not found")
|
||||
|
||||
declared = _default_env_keys(default_env_path)
|
||||
self.assertTrue(declared, "env/default.env has no INFINITO_* entries")
|
||||
|
||||
referenced = _handler_referenced_keys(handlers_dir)
|
||||
missing = sorted(declared - referenced)
|
||||
if not missing:
|
||||
return
|
||||
|
||||
lines = [
|
||||
f"INFINITO_* keys in env/default.env are not wired through any handler "
|
||||
f"({len(missing)} unhandled):",
|
||||
"",
|
||||
"env/default.env is the SPOT for static defaults; each key MUST be "
|
||||
"applied via setdefault by a handler in utils/env/handlers/ "
|
||||
"(typically the static-passthrough handler). Add the key to the "
|
||||
"handler's STATIC_KEYS / KEY constant so `make dotenv` materialises "
|
||||
"the default into the generated .env.",
|
||||
"",
|
||||
"Unhandled keys:",
|
||||
]
|
||||
lines.extend(f" {key}" for key in missing)
|
||||
self.fail("\n".join(lines))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,168 @@
|
||||
"""Every INFINITO_* used in `.sh` / `.py` must be declared as a default.
|
||||
|
||||
Defaults live in ``env/default.env`` (the SPOT for static values) and
|
||||
in the handler modules under ``utils/env/handlers/`` (which apply
|
||||
dynamic defaults via ``setdefault``). Together they form the registry
|
||||
of valid INFINITO_* keys.
|
||||
|
||||
This test scans every shell and Python source file for INFINITO_*
|
||||
references and fails when a referenced key has no corresponding entry
|
||||
in either the static file or a handler module. Such a key would reach
|
||||
the consumer unset, and a bare read would crash with KeyError / set -u.
|
||||
|
||||
Allowed reference shapes (sampled, not exhaustive):
|
||||
|
||||
* shell: ``${INFINITO_VAR}``, ``${INFINITO_VAR:?msg}``, ``${INFINITO_VAR:-}``
|
||||
* python: ``os.environ["INFINITO_VAR"]``, ``os.environ.get("INFINITO_VAR")``,
|
||||
``env.get("INFINITO_VAR")``, etc.
|
||||
|
||||
Suppress per line with a same-line ``# nocheck: <reason>`` marker. Use
|
||||
it only for keys that legitimately stay registry-less (e.g. pure
|
||||
bootstrap one-shots).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import unittest
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from utils.cache.files import iter_non_ignored_files, read_text
|
||||
|
||||
from . import PROJECT_ROOT
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
_SH_REF_RE = re.compile(r"\$\{?(?P<key>INFINITO_[A-Z0-9_]+)")
|
||||
_PY_LITERAL_RE = re.compile(r"[\"'](?P<key>INFINITO_[A-Z0-9_]+)[\"']")
|
||||
_DEFAULT_ENV_KEY_RE = re.compile(r"^\s*(?P<key>INFINITO_[A-Z0-9_]+)\s*=")
|
||||
_HANDLER_LITERAL_RE = re.compile(r"[\"'](?P<key>INFINITO_[A-Z0-9_]+)[\"']")
|
||||
_NOCHECK_RE = re.compile(r"#\s*nocheck\b")
|
||||
|
||||
# These trees define the registry / document it; references in them
|
||||
# describe the rule, they do not consume the keys.
|
||||
_EXCLUDED_PREFIXES = (
|
||||
"tests/lint/",
|
||||
"utils/env/handlers/",
|
||||
"env/",
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _Reference:
|
||||
file: str
|
||||
line_no: int
|
||||
key: str
|
||||
|
||||
|
||||
def _registered_keys() -> set[str]:
|
||||
keys: set[str] = set()
|
||||
env_dir = PROJECT_ROOT / "env"
|
||||
for env_file in sorted(env_dir.glob("*.env")):
|
||||
for line in read_text(str(env_file)).splitlines():
|
||||
match = _DEFAULT_ENV_KEY_RE.match(line)
|
||||
if match is not None:
|
||||
keys.add(match.group("key"))
|
||||
|
||||
handlers_dir = PROJECT_ROOT / "utils" / "env" / "handlers"
|
||||
for module in sorted(handlers_dir.glob("*.py")):
|
||||
if module.name == "__init__.py":
|
||||
continue
|
||||
text = read_text(str(module))
|
||||
for match in _HANDLER_LITERAL_RE.finditer(text):
|
||||
keys.add(match.group("key"))
|
||||
|
||||
return keys
|
||||
|
||||
|
||||
def _strip_comment(line: str) -> str:
|
||||
"""Drop a trailing `# ...` comment so INFINITO_* names mentioned only in
|
||||
prose do not register as code references. Crude: a `#` inside a quoted
|
||||
string also truncates, which is fine since INFINITO_* references inside
|
||||
quoted strings stay intact in the kept prefix."""
|
||||
idx = line.find("#")
|
||||
return line if idx < 0 else line[:idx]
|
||||
|
||||
|
||||
def _scan_with(path: Path, pattern: re.Pattern[str]) -> list[_Reference]:
|
||||
rel = path.relative_to(PROJECT_ROOT).as_posix()
|
||||
out: list[_Reference] = []
|
||||
for idx, line in enumerate(read_text(str(path)).splitlines(), start=1):
|
||||
if _NOCHECK_RE.search(line):
|
||||
continue
|
||||
code = _strip_comment(line)
|
||||
out.extend(_Reference(rel, idx, m.group("key")) for m in pattern.finditer(code))
|
||||
return out
|
||||
|
||||
|
||||
def _scan_shell(path: Path) -> list[_Reference]:
|
||||
return _scan_with(path, _SH_REF_RE)
|
||||
|
||||
|
||||
def _scan_python(path: Path) -> list[_Reference]:
|
||||
return _scan_with(path, _PY_LITERAL_RE)
|
||||
|
||||
|
||||
def _scan_targets() -> tuple[list[Path], list[Path]]:
|
||||
sh: list[Path] = []
|
||||
py: list[Path] = []
|
||||
root_str = str(PROJECT_ROOT)
|
||||
for absolute in iter_non_ignored_files(root=root_str):
|
||||
rel = absolute.removeprefix(root_str + "/")
|
||||
if any(rel.startswith(prefix) for prefix in _EXCLUDED_PREFIXES):
|
||||
continue
|
||||
if rel.endswith(".sh"):
|
||||
sh.append(PROJECT_ROOT / rel)
|
||||
elif rel.endswith(".py"):
|
||||
py.append(PROJECT_ROOT / rel)
|
||||
return sh, py
|
||||
|
||||
|
||||
class TestUsedKeysDeclared(unittest.TestCase):
|
||||
def test_every_referenced_infinito_key_is_registered(self) -> None:
|
||||
registered = _registered_keys()
|
||||
self.assertTrue(registered, "no INFINITO_* keys found in the registry")
|
||||
|
||||
shell_files, python_files = _scan_targets()
|
||||
self.assertTrue(shell_files or python_files, "no .sh / .py files to scan")
|
||||
|
||||
violations: list[_Reference] = []
|
||||
for path in shell_files:
|
||||
violations.extend(
|
||||
ref for ref in _scan_shell(path) if ref.key not in registered
|
||||
)
|
||||
for path in python_files:
|
||||
violations.extend(
|
||||
ref for ref in _scan_python(path) if ref.key not in registered
|
||||
)
|
||||
|
||||
if not violations:
|
||||
return
|
||||
|
||||
unique_keys = sorted({v.key for v in violations})
|
||||
grouped: dict[str, list[_Reference]] = {}
|
||||
for v in violations:
|
||||
grouped.setdefault(v.key, []).append(v)
|
||||
|
||||
lines = [
|
||||
f"INFINITO_* keys referenced in code without a registry entry "
|
||||
f"({len(unique_keys)} unknown key(s) across {len(violations)} use site(s)):",
|
||||
"",
|
||||
"Add a default to env/default.env (and to the static-passthrough handler's "
|
||||
"STATIC_KEYS) for static values, or write a dedicated handler under "
|
||||
"utils/env/handlers/ that sets the key via eb.setdefault. "
|
||||
"Suppress per line with `# nocheck: <reason>` when the key legitimately "
|
||||
"stays registry-less.",
|
||||
"",
|
||||
"Unregistered keys:",
|
||||
]
|
||||
for key in unique_keys:
|
||||
lines.append(f" {key}:")
|
||||
lines.extend(f" {ref.file}:{ref.line_no}" for ref in grouped[key])
|
||||
self.fail("\n".join(lines))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,144 @@
|
||||
"""Lint guard: `.py` files MUST NOT declare INFINITO_* defaults in `.get(KEY, default)`.
|
||||
|
||||
The single source of truth for INFINITO_* defaults is ``env/default.env``
|
||||
(materialised into ``.env`` by ``make dotenv`` and applied as
|
||||
``setdefault`` by the handler builders in ``utils/env/handlers/``).
|
||||
Hardcoded fallbacks in Python contradict that contract -- if the
|
||||
default.env value drifts, the Python copy silently keeps the old one.
|
||||
|
||||
Allowed:
|
||||
|
||||
* Bare read: ``os.environ.get("INFINITO_VAR")`` (returns ``None`` when unset)
|
||||
* Required-loud: ``os.environ["INFINITO_VAR"]`` (raises ``KeyError`` when unset)
|
||||
|
||||
Forbidden:
|
||||
|
||||
* Any default at all, including the empty string:
|
||||
``os.environ.get("INFINITO_VAR", "")``,
|
||||
``os.environ.get("INFINITO_VAR", "1")``,
|
||||
``env.get("INFINITO_VAR", DEFAULT)``, etc.
|
||||
|
||||
Suppress per line with a same-line ``# nocheck: <reason>`` marker.
|
||||
Use it only when the call genuinely cannot consume ``env/default.env``
|
||||
(e.g. bootstrap code that runs before the env loader).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import re
|
||||
import unittest
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from utils.cache.files import iter_non_ignored_files, read_text
|
||||
|
||||
from . import PROJECT_ROOT
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
_INFINITO_KEY_RE = re.compile(r"^INFINITO_[A-Z0-9_]+$")
|
||||
_NOCHECK_RE = re.compile(r"#\s*nocheck\b")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _Violation:
|
||||
file: str
|
||||
line_no: int
|
||||
key: str
|
||||
default_repr: str
|
||||
|
||||
|
||||
def _scan_file(path: Path) -> list[_Violation]:
|
||||
rel = path.relative_to(PROJECT_ROOT).as_posix()
|
||||
try:
|
||||
text = read_text(str(path))
|
||||
tree = ast.parse(text, filename=rel)
|
||||
except (OSError, UnicodeDecodeError, SyntaxError):
|
||||
return []
|
||||
|
||||
raw_lines = text.splitlines()
|
||||
violations: list[_Violation] = []
|
||||
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, ast.Call):
|
||||
continue
|
||||
if not (isinstance(node.func, ast.Attribute) and node.func.attr == "get"):
|
||||
continue
|
||||
if len(node.args) < 2:
|
||||
continue
|
||||
key_node = node.args[0]
|
||||
if not (isinstance(key_node, ast.Constant) and isinstance(key_node.value, str)):
|
||||
continue
|
||||
if not _INFINITO_KEY_RE.match(key_node.value):
|
||||
continue
|
||||
default_node = node.args[1]
|
||||
# Allow per-line `# nocheck:` suppression on any physical line the
|
||||
# call spans (autoformat may have split a one-liner across the
|
||||
# opening paren, the args, and the closing paren).
|
||||
line_no = node.lineno
|
||||
end_line_no = getattr(node, "end_lineno", line_no) or line_no
|
||||
if any(
|
||||
1 <= n <= len(raw_lines) and _NOCHECK_RE.search(raw_lines[n - 1])
|
||||
for n in range(line_no, end_line_no + 1)
|
||||
):
|
||||
continue
|
||||
violations.append(
|
||||
_Violation(
|
||||
rel,
|
||||
line_no,
|
||||
key_node.value,
|
||||
ast.unparse(default_node),
|
||||
)
|
||||
)
|
||||
|
||||
return violations
|
||||
|
||||
|
||||
def _scan_targets() -> list[Path]:
|
||||
return [
|
||||
PROJECT_ROOT / rel
|
||||
for rel in iter_non_ignored_files(root=str(PROJECT_ROOT))
|
||||
if rel.endswith(".py")
|
||||
]
|
||||
|
||||
|
||||
class TestPythonNoInfinitoGetDefaults(unittest.TestCase):
|
||||
def test_python_files_dont_declare_infinito_get_defaults(self) -> None:
|
||||
targets = _scan_targets()
|
||||
self.assertTrue(targets, "no .py files found to scan")
|
||||
|
||||
violations: list[_Violation] = []
|
||||
for path in targets:
|
||||
violations.extend(_scan_file(path))
|
||||
|
||||
if not violations:
|
||||
return
|
||||
|
||||
grouped: dict[str, list[_Violation]] = {}
|
||||
for v in violations:
|
||||
grouped.setdefault(v.file, []).append(v)
|
||||
lines = [
|
||||
f"INFINITO_* defaults declared in `.get(KEY, default)` "
|
||||
f"({len(violations)} violations across {len(grouped)} file(s)):",
|
||||
"",
|
||||
"INFINITO_* defaults belong in env/default.env (SPOT); the "
|
||||
"handler builders in utils/env/handlers/ then apply them via "
|
||||
"setdefault. Use `os.environ.get(KEY)` (None when unset) or "
|
||||
"`os.environ[KEY]` (loud KeyError) instead -- the empty-string "
|
||||
"fallback is also forbidden because it silently masks an unset "
|
||||
"key. Suppress per line with `# nocheck: <reason>`.",
|
||||
"",
|
||||
"Offenders:",
|
||||
]
|
||||
for f, vs in sorted(grouped.items()):
|
||||
lines.append(f" {f}:")
|
||||
lines.extend(
|
||||
f" line {v.line_no}: {v.key} default={v.default_repr}" for v in vs
|
||||
)
|
||||
self.fail("\n".join(lines))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -144,7 +144,6 @@ class TestComposeUpRetries(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(env["INFINITO_DISTRO"], "arch")
|
||||
self.assertNotIn("COMPOSE_PROFILES", env)
|
||||
self.assertNotIn("INFINITO_REGISTRY_CACHE_PROXY_CONF", env)
|
||||
|
||||
@patch.dict(
|
||||
os.environ,
|
||||
@@ -153,8 +152,6 @@ class TestComposeUpRetries(unittest.TestCase):
|
||||
"GITHUB_ACTIONS": "",
|
||||
"INFINITO_RUNNING_ON_GITHUB": "",
|
||||
"CI": "",
|
||||
# Tests bypass BASH_ENV; set explicitly so cache_env_overrides() passes.
|
||||
"INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR": "/var/cache/infinito/test/ca",
|
||||
},
|
||||
clear=False,
|
||||
)
|
||||
@@ -189,11 +186,13 @@ class TestComposeUpRetries(unittest.TestCase):
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
env["INFINITO_REGISTRY_CACHE_PROXY_CONF"],
|
||||
env["INFINITO_CACHE_REGISTRY_PROXY_CONF"], # nocheck: test-fixture
|
||||
"./compose/registry-cache/proxy.conf",
|
||||
)
|
||||
self.assertTrue(
|
||||
env["INFINITO_PACKAGE_CACHE_FRONTEND_CA_FILE"].endswith("/ca.crt")
|
||||
env["INFINITO_CACHE_PACKAGE_FRONTEND_CA_FILE"].endswith(
|
||||
"/ca.crt"
|
||||
) # nocheck: test-fixture
|
||||
)
|
||||
|
||||
@patch.dict(
|
||||
@@ -267,8 +266,6 @@ class TestComposeUpRetries(unittest.TestCase):
|
||||
"CI": "",
|
||||
"GITHUB_ACTIONS": "",
|
||||
"INFINITO_RUNNING_ON_GITHUB": "",
|
||||
# Tests bypass BASH_ENV; set explicitly so cache_env_overrides() passes.
|
||||
"INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR": "/var/cache/infinito/test/ca",
|
||||
},
|
||||
clear=False,
|
||||
)
|
||||
|
||||
@@ -48,7 +48,7 @@ class TestDevelopmentExec(unittest.TestCase):
|
||||
compose = unittest.mock.Mock()
|
||||
compose.exec.return_value.returncode = 0
|
||||
|
||||
env = {k: v for k, v in os.environ.items() if k != "INFINITO_SERVICES_DISABLED"}
|
||||
env = {**os.environ, "INFINITO_SERVICES_DISABLED": ""}
|
||||
with (
|
||||
unittest.mock.patch(
|
||||
"cli.administration.deploy.development.exec.make_compose",
|
||||
@@ -71,7 +71,7 @@ class TestDevelopmentExec(unittest.TestCase):
|
||||
compose = unittest.mock.Mock()
|
||||
compose.exec.return_value.returncode = 0
|
||||
|
||||
env = {k: v for k, v in os.environ.items() if k != "INFINITO_SERVICES_DISABLED"}
|
||||
env = {**os.environ, "INFINITO_SERVICES_DISABLED": ""}
|
||||
with (
|
||||
unittest.mock.patch(
|
||||
"cli.administration.deploy.development.exec.make_compose",
|
||||
@@ -103,7 +103,7 @@ class TestDevelopmentExec(unittest.TestCase):
|
||||
compose = unittest.mock.Mock()
|
||||
compose.exec.return_value.returncode = 0
|
||||
|
||||
env = {k: v for k, v in os.environ.items() if k != "INFINITO_SERVICES_DISABLED"}
|
||||
env = {**os.environ, "INFINITO_SERVICES_DISABLED": ""}
|
||||
with (
|
||||
unittest.mock.patch(
|
||||
"cli.administration.deploy.development.exec.make_compose",
|
||||
|
||||
@@ -12,7 +12,7 @@ class TestDetectOuterNetworkMtu(unittest.TestCase):
|
||||
def test_prefers_explicit_outer_network_mtu_env(self) -> None:
|
||||
self.assertEqual(detect_outer_network_mtu(), "1300")
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
@patch.dict(os.environ, {"INFINITO_OUTER_NETWORK_MTU": ""}, clear=True)
|
||||
@patch("cli.administration.deploy.development.network.Path.exists", autospec=True)
|
||||
@patch(
|
||||
"cli.administration.deploy.development.network.Path.read_text", autospec=True
|
||||
@@ -27,7 +27,7 @@ class TestDetectOuterNetworkMtu(unittest.TestCase):
|
||||
|
||||
self.assertEqual(detect_outer_network_mtu(), "1400")
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
@patch.dict(os.environ, {"INFINITO_OUTER_NETWORK_MTU": ""}, clear=True)
|
||||
@patch("cli.administration.deploy.development.network.Path.exists", autospec=True)
|
||||
@patch(
|
||||
"cli.administration.deploy.development.network.Path.read_text", autospec=True
|
||||
|
||||
@@ -400,7 +400,7 @@ class TestApplyServicesDisabledFromEnv(unittest.TestCase):
|
||||
|
||||
def test_no_op_when_env_not_set(self):
|
||||
self._write({"applications": {}})
|
||||
env = {k: v for k, v in os.environ.items() if k != "INFINITO_SERVICES_DISABLED"}
|
||||
env = {**os.environ, "INFINITO_SERVICES_DISABLED": ""}
|
||||
with unittest.mock.patch.dict(os.environ, env, clear=True):
|
||||
apply_services_disabled_from_env(self.host_vars, roles_dir=self.roles_dir)
|
||||
self.assertEqual(self._read(), {"applications": {}})
|
||||
|
||||
@@ -20,7 +20,7 @@ def _resolved_repo_root() -> Path:
|
||||
(used by some CI fixtures); otherwise the canonical ``PROJECT_ROOT``
|
||||
constant from the package ``__init__.py`` is returned.
|
||||
"""
|
||||
env = os.environ.get("INFINITO_REPO_ROOT")
|
||||
env = os.environ.get("INFINITO_REPO_ROOT") # nocheck: test-fixture
|
||||
if env:
|
||||
return Path(env).expanduser().resolve()
|
||||
return PROJECT_ROOT
|
||||
|
||||
@@ -42,7 +42,7 @@ class TestComposeCaInject(unittest.TestCase):
|
||||
"!",
|
||||
"grep",
|
||||
"-qF",
|
||||
"INFINITO_TAIGA_AUTH_CONFIG",
|
||||
"INFINITO_TAIGA_AUTH_CONFIG", # nocheck: test-fixture
|
||||
"/taiga-back/settings/config.py",
|
||||
";",
|
||||
"then",
|
||||
@@ -501,7 +501,7 @@ class TestComposeCaInject(unittest.TestCase):
|
||||
"!",
|
||||
"grep",
|
||||
"-qF",
|
||||
"INFINITO_TAIGA_AUTH_CONFIG",
|
||||
"INFINITO_TAIGA_AUTH_CONFIG", # nocheck: test-fixture
|
||||
"/taiga-back/settings/config.py",
|
||||
";",
|
||||
"then",
|
||||
|
||||
@@ -31,7 +31,7 @@ class TestInfinitoComposeWrapper(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Disable the wrapper's cache-override branch; tests use mocked is_file.
|
||||
self._env_patch = patch.dict(
|
||||
os.environ, {"INFINITO_PACKAGE_CACHE_FRONTEND_IP": ""}, clear=False
|
||||
os.environ, {"INFINITO_CACHE_PACKAGE_FRONTEND_IP": ""}, clear=False
|
||||
)
|
||||
self._env_patch.start()
|
||||
|
||||
|
||||
+3
-1
@@ -20,7 +20,9 @@ COMMENT = (
|
||||
def apply(eb: EnvBuilder, ctx: BuildContext) -> None:
|
||||
if not ctx.on_gha:
|
||||
return
|
||||
repo_name = os.environ.get("INFINITO_IMAGE_REPOSITORY", "").strip()
|
||||
repo_name = os.environ.get(
|
||||
"INFINITO_IMAGE_REPOSITORY", ""
|
||||
).strip() # nocheck: handler-bootstrap-read
|
||||
if not repo_name:
|
||||
repo_name = run_helper(
|
||||
["bash", "scripts/meta/resolve/repository/name.sh"],
|
||||
|
||||
+3
-1
@@ -27,7 +27,9 @@ HOST_VARS_FILE_COMMENT = (
|
||||
|
||||
|
||||
def apply(eb: EnvBuilder, ctx: BuildContext) -> None:
|
||||
inventory_dir = os.environ.get("INFINITO_INVENTORY_DIR", "").strip()
|
||||
inventory_dir = os.environ.get(
|
||||
"INFINITO_INVENTORY_DIR", ""
|
||||
).strip() # nocheck: handler-bootstrap-read
|
||||
if not inventory_dir:
|
||||
inventory_dir = run_helper(
|
||||
["bash", "scripts/inventory/resolve.sh"],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD: stable per-host Nexus admin
|
||||
"""INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD: stable per-host Nexus admin
|
||||
password: sha256(host_path:hostname), truncated to 32 hex chars."""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -12,7 +12,7 @@ from utils.env.runtime import hostname
|
||||
if TYPE_CHECKING:
|
||||
from utils.env.builder import BuildContext, EnvBuilder
|
||||
|
||||
KEY = "INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD"
|
||||
KEY = "INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD"
|
||||
COMMENT = (
|
||||
"Stable per-host Nexus admin password: sha256(host_path:hostname), "
|
||||
"truncated to 32 hex chars."
|
||||
@@ -24,6 +24,6 @@ def apply(eb: EnvBuilder, ctx: BuildContext) -> None:
|
||||
if existing_pw:
|
||||
eb.set(KEY, existing_pw, comment=COMMENT)
|
||||
return
|
||||
seed = f"{eb.get('INFINITO_PACKAGE_CACHE_HOST_PATH')}:{hostname()}"
|
||||
seed = f"{eb.get('INFINITO_CACHE_PACKAGE_HOST_PATH')}:{hostname()}"
|
||||
digest = hashlib.sha256(seed.encode("utf-8")).hexdigest()[:32]
|
||||
eb.set(KEY, digest, comment=COMMENT)
|
||||
@@ -1,5 +1,5 @@
|
||||
"""INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX: Nexus blobstore quota (half of
|
||||
free disk at INFINITO_PACKAGE_CACHE_HOST_PATH, floor 2g)."""
|
||||
"""INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX: Nexus blobstore quota (half of
|
||||
free disk at INFINITO_CACHE_PACKAGE_HOST_PATH, floor 2g)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -11,10 +11,10 @@ from utils.env.runtime import df_avail_gb
|
||||
if TYPE_CHECKING:
|
||||
from utils.env.builder import BuildContext, EnvBuilder
|
||||
|
||||
KEY = "INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX"
|
||||
KEY = "INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX"
|
||||
COMMENT = (
|
||||
"Nexus blobstore quota (half of free disk at "
|
||||
"INFINITO_PACKAGE_CACHE_HOST_PATH, floor 2g)."
|
||||
"INFINITO_CACHE_PACKAGE_HOST_PATH, floor 2g)."
|
||||
)
|
||||
|
||||
|
||||
@@ -23,5 +23,5 @@ def apply(eb: EnvBuilder, ctx: BuildContext) -> None:
|
||||
if existing_blob:
|
||||
eb.set(KEY, existing_blob, comment=COMMENT)
|
||||
return
|
||||
avail = df_avail_gb(eb.get("INFINITO_PACKAGE_CACHE_HOST_PATH")) or 4
|
||||
avail = df_avail_gb(eb.get("INFINITO_CACHE_PACKAGE_HOST_PATH")) or 4
|
||||
eb.set(KEY, f"{max(avail // 2, 2)}g", comment=COMMENT)
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
"""INFINITO_PACKAGE_CACHE_DIRECT_MEM: Nexus direct-memory budget; tied to
|
||||
INFINITO_PACKAGE_CACHE_HEAP."""
|
||||
"""INFINITO_CACHE_PACKAGE_DIRECT_MEM: Nexus direct-memory budget; tied to
|
||||
INFINITO_CACHE_PACKAGE_HEAP."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -8,9 +8,9 @@ from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from utils.env.builder import BuildContext, EnvBuilder
|
||||
|
||||
KEY = "INFINITO_PACKAGE_CACHE_DIRECT_MEM"
|
||||
COMMENT = "Nexus direct-memory budget; tied to INFINITO_PACKAGE_CACHE_HEAP."
|
||||
KEY = "INFINITO_CACHE_PACKAGE_DIRECT_MEM"
|
||||
COMMENT = "Nexus direct-memory budget; tied to INFINITO_CACHE_PACKAGE_HEAP."
|
||||
|
||||
|
||||
def apply(eb: EnvBuilder, ctx: BuildContext) -> None:
|
||||
eb.setdefault(KEY, eb.get("INFINITO_PACKAGE_CACHE_HEAP"), comment=COMMENT)
|
||||
eb.setdefault(KEY, eb.get("INFINITO_CACHE_PACKAGE_HEAP"), comment=COMMENT)
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
"""INFINITO_PACKAGE_CACHE_HEAP: Nexus JVM heap size (half of MemAvailable,
|
||||
"""INFINITO_CACHE_PACKAGE_HEAP: Nexus JVM heap size (half of MemAvailable,
|
||||
cap 2g, floor 1g)."""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -11,7 +11,7 @@ from utils.env.runtime import mem_available_mb
|
||||
if TYPE_CHECKING:
|
||||
from utils.env.builder import BuildContext, EnvBuilder
|
||||
|
||||
KEY = "INFINITO_PACKAGE_CACHE_HEAP"
|
||||
KEY = "INFINITO_CACHE_PACKAGE_HEAP"
|
||||
COMMENT = "Nexus JVM heap size (half of MemAvailable, cap 2g, floor 1g)."
|
||||
|
||||
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
"""INFINITO_REGISTRY_CACHE_MAX_SIZE: registry-cache soft cap (half of
|
||||
free disk at INFINITO_REGISTRY_CACHE_HOST_PATH, floor 1g)."""
|
||||
"""INFINITO_CACHE_REGISTRY_MAX_SIZE: registry-cache soft cap (half of
|
||||
free disk at INFINITO_CACHE_REGISTRY_HOST_PATH, floor 1g)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -11,15 +11,15 @@ from utils.env.runtime import df_avail_gb
|
||||
if TYPE_CHECKING:
|
||||
from utils.env.builder import BuildContext, EnvBuilder
|
||||
|
||||
KEY = "INFINITO_REGISTRY_CACHE_MAX_SIZE"
|
||||
KEY = "INFINITO_CACHE_REGISTRY_MAX_SIZE"
|
||||
COMMENT = (
|
||||
"Registry-cache soft cap (half of free disk at "
|
||||
"INFINITO_REGISTRY_CACHE_HOST_PATH, floor 1g)."
|
||||
"INFINITO_CACHE_REGISTRY_HOST_PATH, floor 1g)."
|
||||
)
|
||||
|
||||
|
||||
def apply(eb: EnvBuilder, ctx: BuildContext) -> None:
|
||||
host_path = eb.get("INFINITO_REGISTRY_CACHE_HOST_PATH")
|
||||
host_path = eb.get("INFINITO_CACHE_REGISTRY_HOST_PATH")
|
||||
if not host_path:
|
||||
return
|
||||
existing = os.environ.get(KEY, "").strip()
|
||||
|
||||
Vendored
+16
-9
@@ -15,6 +15,8 @@ STATIC_KEYS = (
|
||||
"INFINITO_SUBNET",
|
||||
"INFINITO_GATEWAY",
|
||||
"INFINITO_DISTRO",
|
||||
"INFINITO_DNS_DOMAIN",
|
||||
"INFINITO_DNS_HOSTS_FILE",
|
||||
"INFINITO_CONTAINER",
|
||||
"INFINITO_CONTAINER_NO_CA",
|
||||
"INFINITO_RUNNING_ON_ACT",
|
||||
@@ -50,15 +52,20 @@ STATIC_KEYS = (
|
||||
"INFINITO_VENV_NAME",
|
||||
"PYTHONPATH",
|
||||
"INVENTORY_VARS_FILE",
|
||||
"INFINITO_REGISTRY_CACHE_HOST_PATH",
|
||||
"INFINITO_REGISTRY_CACHE_CA_HOST_PATH",
|
||||
"INFINITO_PACKAGE_CACHE_HOST_PATH",
|
||||
"INFINITO_PACKAGE_CACHE_PORT",
|
||||
"INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR",
|
||||
"INFINITO_PACKAGE_CACHE_FRONTEND_CERTS_DIR",
|
||||
"INFINITO_PACKAGE_CACHE_FRONTEND_IP",
|
||||
"INFINITO_PACKAGE_CACHE_FRONTEND_INIT_IMAGE",
|
||||
"INFINITO_PACKAGE_CACHE_MAX_AGE_MIN",
|
||||
"INFINITO_CACHE_REGISTRY_HOST_PATH",
|
||||
"INFINITO_CACHE_REGISTRY_CA_HOST_PATH",
|
||||
"INFINITO_CACHE_PACKAGE_HOST_PATH",
|
||||
"INFINITO_CACHE_PACKAGE_PORT",
|
||||
"INFINITO_CACHE_PACKAGE_APT_LIST",
|
||||
"INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR",
|
||||
"INFINITO_CACHE_PACKAGE_FRONTEND_CA_FILE",
|
||||
"INFINITO_CACHE_PACKAGE_FRONTEND_CERTS_DIR",
|
||||
"INFINITO_CACHE_PACKAGE_FRONTEND_IP",
|
||||
"INFINITO_CACHE_PACKAGE_FRONTEND_INIT_IMAGE",
|
||||
"INFINITO_CACHE_PACKAGE_MAX_AGE_MIN",
|
||||
"INFINITO_CACHE_PACKAGE_NPMRC",
|
||||
"INFINITO_CACHE_PACKAGE_PIP_CONF",
|
||||
"INFINITO_CACHE_REGISTRY_PROXY_CONF",
|
||||
"INFINITO_IMAGE_TAG",
|
||||
)
|
||||
|
||||
|
||||
@@ -67,4 +67,4 @@ def latest_semver(tags: list[str], depth: int, flavor: str = "") -> str | None:
|
||||
|
||||
|
||||
def resolve_max_fetch_workers() -> int:
|
||||
return int(os.environ.get("INFINITO_WORKER_FETCH", "1"))
|
||||
return int(os.environ["INFINITO_WORKER_FETCH"])
|
||||
Reference in new issue
Block a user