mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
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>
99 lines
3.6 KiB
Python
99 lines
3.6 KiB
Python
"""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()
|