mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-09 12:17:13 +00:00
feat(images): check declared architectures against the registry, and make a pin say why
Nothing held a role's architectures to reality: test_image_reachable proves a tag exists, test_arm64_images proves four named roles publish arm64, and the closure lint only proves the declarations do not intersect to nothing. An amd64-only bump became a scheduling failure on an arm64 row rather than a red test. A new external test sweeps every pinned image and judges a role by the intersection over its services, because a role runs only where all of its images run; judging per image would demand arm64 from an amd64-only role's nginx and call a narrowing stale because nginx publishes arm64 while the application does not. A role declaring nothing must cover every architecture the matrix rotates; a role that narrows must narrow to exactly its capability, which is what retires its TODO by itself. An unread image downgrades the stale verdict to a warning, since the image a probe could not read may be the one the narrowing exists for. The probe reads the mirror first and caches on disk, because the sweep is ~160 manifests and Docker Hub counts its allowance per IP, shared. A new lint requires every architectures declaration to carry a nocheck marker and a Reason. bigbluebutton and opentalk keep their pins with measured reasons; decidim, pretix and taiga lose theirs, because each builds its own image and their upstream Dockerfiles are arch-neutral. Their source builds are not converted yet, so until that lands the matrix may hand them an arm64 row that fails at FROM. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
03ed2f1ca1
commit
0bb437d9ce
14 files changed
+653
-27
No files matched your search
@@ -0,0 +1,98 @@
|
||||
"""The reduction from published platforms to where a role can be placed.
|
||||
|
||||
The intersection is the whole point: a role pins an application image beside
|
||||
generic sidecars, and judging per image gets it wrong in both directions. It
|
||||
would demand arm64 from an amd64-only role's nginx, and it would call a
|
||||
narrowing stale because nginx publishes arm64 while the application does not.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from utils.docker.image.architectures import role_capability, runnable_architectures
|
||||
|
||||
BOTH = {"linux/amd64", "linux/arm64"}
|
||||
AMD64 = {"linux/amd64"}
|
||||
ARM64 = {"linux/arm64"}
|
||||
|
||||
|
||||
class TestRunnableArchitectures(unittest.TestCase):
|
||||
def test_a_multi_platform_index_offers_both(self) -> None:
|
||||
self.assertEqual({"amd64", "arm64"}, runnable_architectures(BOTH))
|
||||
|
||||
def test_a_foreign_os_carries_no_row(self) -> None:
|
||||
self.assertEqual(
|
||||
{"amd64"}, runnable_architectures({"linux/amd64", "windows/amd64"})
|
||||
)
|
||||
|
||||
def test_an_unrotated_architecture_is_dropped(self) -> None:
|
||||
self.assertEqual(
|
||||
{"amd64"},
|
||||
runnable_architectures({"linux/amd64", "linux/ppc64le", "linux/s390x"}),
|
||||
)
|
||||
|
||||
def test_an_empty_or_missing_set_offers_nothing(self) -> None:
|
||||
for platforms in (set(), None):
|
||||
with self.subTest(platforms=platforms):
|
||||
self.assertEqual(set(), runnable_architectures(platforms))
|
||||
|
||||
|
||||
class TestRoleCapability(unittest.TestCase):
|
||||
def test_one_amd64_only_image_pins_the_whole_role(self) -> None:
|
||||
capability, blame, unread = role_capability({"app": AMD64, "nginx": BOTH})
|
||||
|
||||
self.assertEqual({"amd64"}, capability)
|
||||
self.assertEqual({"arm64": ["app"]}, blame)
|
||||
self.assertEqual([], unread)
|
||||
|
||||
def test_a_role_whose_images_all_publish_both_runs_on_both(self) -> None:
|
||||
capability, blame, _unread = role_capability({"app": BOTH, "nginx": BOTH})
|
||||
|
||||
self.assertEqual({"amd64", "arm64"}, capability)
|
||||
self.assertEqual({}, blame)
|
||||
|
||||
def test_images_pinned_to_opposite_architectures_run_nowhere(self) -> None:
|
||||
capability, blame, _unread = role_capability({"app": AMD64, "sidecar": ARM64})
|
||||
|
||||
self.assertEqual(set(), capability)
|
||||
self.assertEqual({"amd64": ["sidecar"], "arm64": ["app"]}, blame)
|
||||
|
||||
def test_blame_names_every_image_lacking_the_architecture(self) -> None:
|
||||
_capability, blame, _unread = role_capability(
|
||||
{"worker": AMD64, "app": AMD64, "nginx": BOTH}
|
||||
)
|
||||
|
||||
self.assertEqual(["app", "worker"], blame["arm64"])
|
||||
|
||||
def test_an_unreadable_image_is_reported_and_not_intersected(self) -> None:
|
||||
capability, blame, unread = role_capability({"app": BOTH, "unreadable": None})
|
||||
|
||||
self.assertEqual(
|
||||
{"amd64", "arm64"},
|
||||
capability,
|
||||
"a throttled probe must understate, not narrow the role to nothing",
|
||||
)
|
||||
self.assertEqual({}, blame)
|
||||
self.assertEqual(
|
||||
["unreadable"],
|
||||
unread,
|
||||
"without this the skipped image reads as one that runs everywhere, "
|
||||
"and a narrowing it alone justifies looks unnecessary",
|
||||
)
|
||||
|
||||
def test_a_role_nobody_could_read_has_no_capability(self) -> None:
|
||||
capability, blame, unread = role_capability({"app": None, "nginx": None})
|
||||
|
||||
self.assertIsNone(
|
||||
capability, "None is the skip signal, distinct from an empty set"
|
||||
)
|
||||
self.assertEqual({}, blame)
|
||||
self.assertEqual(["app", "nginx"], unread)
|
||||
|
||||
def test_a_role_without_images_has_no_capability(self) -> None:
|
||||
self.assertEqual((None, {}, []), role_capability({}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user