mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
feat(mirror): let a role declare the tags it derives from its version
Four libretranslate jobs in run 37173067619 failed with failed to solve: ghcr.io/…/mirror/docker.io/libretranslate/libretranslate:v1.9.6-cuda: not found svc-ai-libretranslate-engine and web-svc-libretranslate append '-cuda' to the version in vars/main.yml when the gpu flag is set. iter_role_images is the only enumeration the mirror pipeline sees and it reads image and version alone, so the derived tag was never a candidate for mirroring and resolved against a registry that has no such tag. Upstream does publish it: Docker Hub answers 200 for v1.9.6-cuda, the mirror 404. The suffixes are now declared next to the version they extend, and discovery yields a ref per suffix. A suffix rather than a full tag keeps the version number in one place when it is bumped. The gpu flag is static: it is set in meta/services.yml, no inventory overrides it, and resource_filter reads configuration rather than hardware. Every host therefore takes the cuda path today, which is why all four jobs failed rather than some. Rejected: mirror_policy: skip, which has no users in this repository and would route these two images around the mirror to Docker Hub permanently, reintroducing the rate limit the mirror exists to avoid. Also rejected: baking '-cuda' into version, which fixes the mirror but removes the GPU switch. The test pins both halves; without the discovery change its two variant cases fail and its two base cases still pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
12c1eabb6c
commit
d6d7ff8c9e
4 files changed
+82
-9
No files matched your search
@@ -16,6 +16,8 @@ libretranslate-engine:
|
||||
shared: true
|
||||
image: libretranslate/libretranslate
|
||||
version: v1.9.6
|
||||
version_variants:
|
||||
- "-cuda"
|
||||
custom: true
|
||||
min_storage: 402MB
|
||||
load_only: []
|
||||
|
||||
@@ -16,6 +16,8 @@ libretranslate:
|
||||
shared: true
|
||||
image: libretranslate/libretranslate
|
||||
version: v1.9.6
|
||||
version_variants:
|
||||
- "-cuda"
|
||||
custom: true
|
||||
min_storage: 402MB
|
||||
load_only: []
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from utils.docker.image.discovery import iter_role_images
|
||||
from utils.roles.mapping import ROLE_FILE_META_SERVICES
|
||||
|
||||
BASE = """---
|
||||
engine:
|
||||
image: libretranslate/libretranslate
|
||||
version: v1.9.6
|
||||
"""
|
||||
|
||||
WITH_VARIANTS = """---
|
||||
engine:
|
||||
image: libretranslate/libretranslate
|
||||
version: v1.9.6
|
||||
version_variants:
|
||||
- "-cuda"
|
||||
"""
|
||||
|
||||
|
||||
class TestImageDiscoveryVersionVariants(unittest.TestCase):
|
||||
def _versions(self, services_yaml: str) -> set[str]:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
services = Path(tmp) / "roles" / "svc-demo" / ROLE_FILE_META_SERVICES
|
||||
services.parent.mkdir(parents=True)
|
||||
services.write_text(services_yaml, encoding="utf-8")
|
||||
return {ref.version for ref in iter_role_images(Path(tmp))}
|
||||
|
||||
def test_a_role_without_variants_yields_only_the_declared_version(self) -> None:
|
||||
self.assertEqual({"v1.9.6"}, self._versions(BASE))
|
||||
|
||||
def test_each_variant_suffix_yields_its_own_ref(self) -> None:
|
||||
self.assertEqual({"v1.9.6", "v1.9.6-cuda"}, self._versions(WITH_VARIANTS))
|
||||
|
||||
def test_the_variant_ref_carries_the_derived_pull_source(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
services = Path(tmp) / "roles" / "svc-demo" / ROLE_FILE_META_SERVICES
|
||||
services.parent.mkdir(parents=True)
|
||||
services.write_text(WITH_VARIANTS, encoding="utf-8")
|
||||
sources = {ref.source for ref in iter_role_images(Path(tmp))}
|
||||
|
||||
self.assertIn("docker.io/libretranslate/libretranslate:v1.9.6-cuda", sources)
|
||||
|
||||
def test_blank_and_non_list_variants_are_ignored(self) -> None:
|
||||
for declaration in (' version_variants: "-cuda"\n', " version_variants:\n - ' '\n"):
|
||||
with self.subTest(declaration=declaration):
|
||||
self.assertEqual({"v1.9.6"}, self._versions(BASE + declaration))
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
unittest.main()
|
||||
@@ -192,6 +192,11 @@ def iter_role_images(repo_root: Path) -> Iterable[ImageRef]:
|
||||
|
||||
Source: ``roles/**/meta/services.yml`` -> ``<entity>.{image,version}``.
|
||||
|
||||
A role whose vars derive a further tag from ``version`` lists the suffixes
|
||||
under ``<entity>.version_variants``; each yields its own ref, because this
|
||||
function is the only enumeration the mirror sees and a tag it never emits
|
||||
can never be mirrored.
|
||||
|
||||
See docs/contributing/artefact/image.md for the full format reference.
|
||||
"""
|
||||
roles_dir = repo_root / "roles"
|
||||
@@ -216,12 +221,21 @@ def iter_role_images(repo_root: Path) -> Iterable[ImageRef]:
|
||||
if not is_mirrorable_image(image):
|
||||
continue
|
||||
|
||||
yield ImageRef(
|
||||
role=role_name,
|
||||
service=str(service_name),
|
||||
name=canonical_image_name(image),
|
||||
version=version,
|
||||
source=image_source(image, version),
|
||||
registry=_detect_registry(image),
|
||||
source_file=ROLE_FILE_META_SERVICES,
|
||||
)
|
||||
variants = service.get("version_variants")
|
||||
suffixes = [""] + [
|
||||
str(suffix)
|
||||
for suffix in (variants if isinstance(variants, list) else [])
|
||||
if str(suffix).strip()
|
||||
]
|
||||
|
||||
for suffix in suffixes:
|
||||
tag = version + suffix
|
||||
yield ImageRef(
|
||||
role=role_name,
|
||||
service=str(service_name),
|
||||
name=canonical_image_name(image),
|
||||
version=tag,
|
||||
source=image_source(image, tag),
|
||||
registry=_detect_registry(image),
|
||||
source_file=ROLE_FILE_META_SERVICES,
|
||||
)
|
||||
Reference in new issue
Block a user