mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
fix(images): leave eol roles out of image discovery
The scheduled mirror job has failed every day since 2026-09-25 on the same two
images: quay.io/minio/minio and quay.io/minio/mc answer `unauthorized` to
anonymous pulls ("2/197 failed", run 38352569503). Both belong to
web-app-minio, an eol role outside the lifecycle envelope that default.env
tests, so no CI deploy pulls them.
iter_role_images now skips every role that is_maintained() reports as
unmaintained, the check the version updaters already apply to the same list.
On the tree the discovery yields 187 images instead of 192; the five it drops
belong to the four eol roles web-app-confluence, web-app-jira, web-app-minio
and web-app-phpldapadmin. The mirror sync, resolver and wait read this list,
and their descriptions and mirror.md now say so.
The roles directory is resolved first: is_maintained reads a relative path as
a role name and would keep every role.
475ab9b386 on feature/store-i18n takes the same approach with a lifecycle
reader of its own; main already has is_maintained for it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
789c1a1b63
commit
8a29ba0bfd
6 files changed
+82
-9
No files matched your search
@@ -12,8 +12,9 @@ from utils.docker.image.discovery import iter_role_images
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Emit the mirrors.yml mapping that redirects every role-declared "
|
||||
"image (applications.*.services.*) to its GHCR mirror URI."
|
||||
"Emit the mirrors.yml mapping that redirects every image a "
|
||||
"non-eol role declares (applications.*.services.*) to its GHCR "
|
||||
"mirror URI."
|
||||
),
|
||||
)
|
||||
parser.add_argument("--repo-root", default=".")
|
||||
|
||||
@@ -53,8 +53,8 @@ def _throttle_before_next_copy(
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Mirror every role-declared image and every image the CI runner "
|
||||
"pulls itself to the configured GHCR registry, "
|
||||
"Mirror every image a non-eol role declares and every image the "
|
||||
"CI runner pulls itself to the configured GHCR registry, "
|
||||
"optionally throttled and optionally skipping already-mirrored "
|
||||
"tags."
|
||||
),
|
||||
|
||||
@@ -23,8 +23,8 @@ def _validate_positive_int(value: str) -> int:
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Wait until every role-declared image and every image the CI runner "
|
||||
"pulls itself has been mirrored to the "
|
||||
"Wait until every image a non-eol role declares and every image "
|
||||
"the CI runner pulls itself has been mirrored to the "
|
||||
"configured GHCR registry. Used on fork pull_request runs that depend "
|
||||
"on a pull_request_target mirror job populating the destination first."
|
||||
),
|
||||
|
||||
@@ -32,7 +32,7 @@ Example: `ghcr.io/kevinveenbirkenbach/infinito-nexus-core/mirror/docker.io/nextc
|
||||
|
||||
### Image Discovery 🔍
|
||||
|
||||
`utils/docker/image/discovery.py` discovers all role images via `iter_role_images()`. See [origin.md](origin.md) for the declaration format and supported registries.
|
||||
`utils/docker/image/discovery.py` discovers the role images via `iter_role_images()`. It leaves out every role whose lifecycle is `eol` (`is_maintained()` in `utils/update/base.py`), so neither the mirror nor `mirrors.yml` covers those images. See [origin.md](origin.md) for the declaration format and supported registries.
|
||||
|
||||
### GHCRProvider 🐳
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
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
|
||||
|
||||
SUPPORTED = """---
|
||||
demo:
|
||||
image: libretranslate/libretranslate
|
||||
version: v1.9.6
|
||||
lifecycle: stable
|
||||
helper:
|
||||
image: alpine
|
||||
version: "3.22"
|
||||
"""
|
||||
|
||||
RETIRED = """---
|
||||
demo:
|
||||
image: quay.io/minio/minio
|
||||
version: RELEASE.2025-09-07T16-13-09Z
|
||||
lifecycle: eol
|
||||
client:
|
||||
image: quay.io/minio/mc
|
||||
version: RELEASE.2025-08-13T08-35-41Z
|
||||
"""
|
||||
|
||||
UNSTAGED = """---
|
||||
demo:
|
||||
image: libretranslate/libretranslate
|
||||
version: v1.9.6
|
||||
"""
|
||||
|
||||
|
||||
class TestImageDiscoveryLifecycle(unittest.TestCase):
|
||||
def _images(self, services_yaml: str, *, relative: bool = False) -> set[str]:
|
||||
"""Discover the images of one role written into a temporary repository.
|
||||
|
||||
Args:
|
||||
services_yaml: the role's ``meta/services.yml``.
|
||||
relative: pass the repository root relative to the working directory.
|
||||
"""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
services = Path(tmp) / "roles" / "web-app-demo" / ROLE_FILE_META_SERVICES
|
||||
services.parent.mkdir(parents=True)
|
||||
services.write_text(services_yaml, encoding="utf-8")
|
||||
root = Path(os.path.relpath(tmp)) if relative else Path(tmp)
|
||||
return {ref.service for ref in iter_role_images(root)}
|
||||
|
||||
def test_a_supported_role_yields_every_service(self) -> None:
|
||||
self.assertEqual({"demo", "helper"}, self._images(SUPPORTED))
|
||||
|
||||
def test_a_retired_role_yields_none_of_its_services(self) -> None:
|
||||
self.assertEqual(set(), self._images(RETIRED))
|
||||
|
||||
def test_a_role_that_names_no_stage_is_kept(self) -> None:
|
||||
self.assertEqual({"demo"}, self._images(UNSTAGED))
|
||||
|
||||
def test_a_relative_root_still_leaves_a_retired_role_out(self) -> None:
|
||||
self.assertEqual(set(), self._images(RETIRED, relative=True))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING
|
||||
from utils.cache.yaml import load_yaml_any as _load_yaml_cached
|
||||
from utils.docker.image.pin import pull_reference
|
||||
from utils.roles.mapping import ROLE_FILE_META_SERVICES
|
||||
from utils.update.base import is_maintained
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable
|
||||
@@ -191,14 +192,18 @@ def iter_role_images(repo_root: Path) -> Iterable[ImageRef]:
|
||||
"""
|
||||
Yield all ImageRef entries discovered across all roles in *repo_root*.
|
||||
|
||||
Source: roles/**/meta/services.yml → <entity>.{image,version}.
|
||||
Source: roles/**/meta/services.yml → <entity>.{image,version}. Roles the
|
||||
updaters do not maintain (``is_maintained``) are left out.
|
||||
|
||||
See docs/contributing/artefact/image.md for the full format reference.
|
||||
"""
|
||||
roles_dir = repo_root / "roles"
|
||||
roles_dir = (repo_root / "roles").resolve()
|
||||
|
||||
for services_file in roles_dir.glob(f"**/{ROLE_FILE_META_SERVICES}"):
|
||||
role_name = services_file.parent.parent.name
|
||||
if not is_maintained(roles_dir, role_name):
|
||||
continue
|
||||
|
||||
services = load_yaml(services_file)
|
||||
|
||||
if not isinstance(services, dict):
|
||||
|
||||
Reference in new issue
Block a user