mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-09 20:26:33 +00:00
A compose deploy job labelled centos or arch built its stack image from pkgmgr-debian and ran it as infinito_nexus_debian (#794). distros.sh exports INFINITO_DISTRO per iteration and sources load.sh again, but the loader returned early because INFINITO_ENV_LOADED and the .env mtime were unchanged. A full reload would not have helped either: the debian values were already exported, so the restore step kept them as caller intent. load.sh now records the distro it loaded for. Another INFINITO_DISTRO defeats the early return and drops INFINITO_CONTAINER, INFINITO_PARENT_IMAGE and INFINITO_IMAGE, the three keys the env handlers derive from the distro. When the requested distro also differs from the one .env was generated for, the loader sources a set that `python -m cli.meta.env --output` generates for it in a private temp directory and leaves .env untouched, so a local multi-distro run does not switch the checkout's distro. A caller value set for the loaded distro still wins. Verified: tests/unit/python/scripts/meta/env/test_load.py and the new distros.sh case pass. Against main's loader both fail with the symptom of the issue: INFINITO_DISTRO=centos next to infinito_nexus_debian and pkgmgr-debian:stable, and distros.sh handing debian, arch and centos the same debian parent. shellcheck is clean. Consequence: CI jobs labelled arch, ubuntu, fedora or centos now build and deploy that distro, so a failure that shows up there was masked before. feature/store-i18n carries3f973eb137, which resolves the parent image for scripts/image/push.sh on its own, and05e4dbafcb, which inserts the INFINITO_INSTANCE slot check into the block this commit restructures. The merge keeps the slot check ahead of the distro check. Closes #794 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
132 lines
4.3 KiB
Python
132 lines
4.3 KiB
Python
"""The distro-derived keys follow the distro the caller asks for.
|
|
|
|
``scripts/tests/deploy/distros.sh`` exports ``INFINITO_DISTRO`` per iteration
|
|
and sources the loader again. The keys composed from the distro must then name
|
|
that distro, not the one ``.env`` was generated for, and ``.env`` itself must
|
|
stay as it was.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from typing import ClassVar
|
|
|
|
from utils.cache.files import PROJECT_ROOT
|
|
|
|
LOADER_DIR = Path(PROJECT_ROOT) / "scripts" / "meta" / "env"
|
|
SHOWN = (
|
|
"INFINITO_DISTRO",
|
|
"INFINITO_CONTAINER",
|
|
"INFINITO_PARENT_IMAGE",
|
|
"INFINITO_IMAGE",
|
|
)
|
|
SHOW = "printf '%s\\n' " + " ".join(f'"{key}=${{{key}:-}}"' for key in SHOWN)
|
|
|
|
|
|
def clean_env(**overrides: str) -> dict[str, str]:
|
|
env = {
|
|
k: v
|
|
for k, v in os.environ.items()
|
|
if not k.startswith("INFINITO_")
|
|
and k not in ("BASH_ENV", "PYTHONPATH", "VENV", "PYTHON", "VIRTUAL_ENV")
|
|
}
|
|
env["PYTHONPATH"] = str(PROJECT_ROOT)
|
|
env.update(overrides)
|
|
return env
|
|
|
|
|
|
def bash(script: str, cwd: Path) -> dict[str, str]:
|
|
proc = subprocess.run(
|
|
["bash", "-c", f"{script}\n{SHOW}"],
|
|
env=clean_env(),
|
|
cwd=cwd,
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
return dict(line.split("=", 1) for line in proc.stdout.splitlines())
|
|
|
|
|
|
class TestDistroDerivedKeys(unittest.TestCase):
|
|
scratch: ClassVar[Path]
|
|
expected: ClassVar[dict[str, dict[str, str]]]
|
|
|
|
@classmethod
|
|
def setUpClass(cls) -> None:
|
|
cls.scratch = Path(tempfile.mkdtemp())
|
|
cls.expected = {}
|
|
for distro in ("debian", "centos"):
|
|
path = cls.scratch / f"{distro}.env"
|
|
subprocess.run(
|
|
[sys.executable, "-m", "cli.meta.env", "--output", str(path)],
|
|
env=clean_env(INFINITO_DISTRO=distro),
|
|
cwd=PROJECT_ROOT,
|
|
capture_output=True,
|
|
check=True,
|
|
)
|
|
cls.expected[distro] = bash(f'set -a; source "{path}"', cls.scratch)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls) -> None:
|
|
shutil.rmtree(cls.scratch)
|
|
|
|
def run_loader(self, steps: str) -> dict[str, str]:
|
|
"""Source a copy of the loader in a checkout whose ``.env`` is debian's.
|
|
|
|
Args:
|
|
steps: bash run before the keys are shown; ``load`` sources the
|
|
loader.
|
|
|
|
Returns:
|
|
The keys as the steps leave them.
|
|
"""
|
|
checkout = Path(tempfile.mkdtemp())
|
|
self.addCleanup(shutil.rmtree, checkout)
|
|
(checkout / "scripts" / "meta" / "env").mkdir(parents=True)
|
|
for name in ("load.sh", "python.sh"):
|
|
shutil.copy(LOADER_DIR / name, checkout / "scripts" / "meta" / "env")
|
|
dotenv = checkout / ".env"
|
|
shutil.copy(self.scratch / "debian.env", dotenv)
|
|
before = dotenv.read_bytes()
|
|
shown = bash(f"load() {{ source scripts/meta/env/load.sh; }}\n{steps}", checkout)
|
|
self.assertEqual(dotenv.read_bytes(), before, ".env was rewritten")
|
|
return shown
|
|
|
|
def test_the_two_distros_derive_different_keys(self):
|
|
self.assertNotEqual(
|
|
self.expected["debian"]["INFINITO_PARENT_IMAGE"],
|
|
self.expected["centos"]["INFINITO_PARENT_IMAGE"],
|
|
)
|
|
|
|
def test_a_distro_exported_after_the_first_load_gets_its_own_keys(self):
|
|
shown = self.run_loader("load\nexport INFINITO_DISTRO=centos\nload")
|
|
self.assertEqual(shown, self.expected["centos"])
|
|
|
|
def test_a_distro_exported_before_the_first_load_gets_its_own_keys(self):
|
|
shown = self.run_loader("export INFINITO_DISTRO=centos\nload")
|
|
self.assertEqual(shown, self.expected["centos"])
|
|
|
|
def test_switching_back_restores_the_keys_of_the_dotenv(self):
|
|
shown = self.run_loader(
|
|
"load\nexport INFINITO_DISTRO=centos\nload\n"
|
|
"export INFINITO_DISTRO=debian\nload"
|
|
)
|
|
self.assertEqual(shown, self.expected["debian"])
|
|
|
|
def test_a_caller_value_for_the_loaded_distro_still_wins(self):
|
|
shown = self.run_loader(
|
|
"load\nexport INFINITO_PARENT_IMAGE=caller/image:pinned\n"
|
|
"touch -d '+1 minute' .env\nload"
|
|
)
|
|
self.assertEqual(shown["INFINITO_PARENT_IMAGE"], "caller/image:pinned")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|