Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions delivery/ci/publish_mcp_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,18 +348,20 @@ def smoke(self, image, runtime_sha, manifest):
"""
require(re.fullmatch(re.escape(IMAGE) + r"@sha256:[0-9a-f]{64}", image), "image_identity")
bounded_command(attestation_command("oci://" + image, runtime_sha), seconds=90)
images = platform_images(image)
with tempfile.TemporaryDirectory(prefix="v8std-anonymous-") as directory:
env = anonymous_environment(directory)
for platform in ("linux/amd64", "linux/arm64"):
bounded_command(["docker", "pull", "--platform", platform, image], env=env, seconds=180)
platform_image = images[platform]
bounded_command(["docker", "pull", "--platform", platform, platform_image], env=env, seconds=180)
name = "v8std-ci-default-" + uuid.uuid4().hex
primary = None
try:
bounded_command(["docker", "run", "-d", "--name", name, "--label", "pro.v8std.ci=" + name,
"--platform", platform, "--read-only", "--cap-drop", "ALL", "--security-opt", "no-new-privileges",
"--init", "--pids-limit", "128", "--memory", "1024m", "--cpus", "2",
"--tmpfs", "/tmp:size=16m", "--tmpfs", "/var/lib/v8std-mcp:rw,size=256m,uid=10001,gid=10001,mode=0700",
"-p", "127.0.0.1::8000", image, "--transport", "streamable-http", "--host", "0.0.0.0",
"-p", "127.0.0.1::8000", platform_image, "--transport", "streamable-http", "--host", "0.0.0.0",
"--port", "8000", "--refresh-seconds", "0"], env=env, seconds=30)
inspected = json.loads(bounded_command(["docker", "inspect", name], env=env))[0]
require(inspected["Config"]["Labels"].get("org.opencontainers.image.revision") == runtime_sha,
Expand Down Expand Up @@ -402,11 +404,12 @@ def local_smoke(self, image, runtime_sha, site_image, site_sha, manifest):
links share the unchanged Compose v8std.localhost address.
"""
root = Path(__file__).resolve().parents[2]
runtime_images, site_images = platform_images(image), platform_images(site_image)
with tempfile.TemporaryDirectory(prefix="v8std-pair-proof-") as directory:
for platform in ("linux/amd64", "linux/arm64"):
project = "v8std-ci-pair-" + uuid.uuid4().hex[:16]
env = {**anonymous_environment(directory), "DOCKER_DEFAULT_PLATFORM": platform,
"V8STD_SITE_IMAGE": site_image, "V8STD_MCP_IMAGE": image,
"V8STD_SITE_IMAGE": site_images[platform], "V8STD_MCP_IMAGE": runtime_images[platform],
"V8STD_SITE_PORT": "18765", "V8STD_MCP_PORT": "18766",
"V8STD_SITE_PREFIX": "/", "V8STD_MCP_SITE_URL": "http://v8std.localhost:18765/"}
compose = ["docker", "compose", "-p", project, "-f", str(root / "delivery/local/compose.yaml"), "--profile", "mcp"]
Expand Down Expand Up @@ -797,6 +800,37 @@ def registry_manifest(image, reference):
return raw


def platform_images(image):
"""Select children of the verified index without reusing its local digest.

Docker's classic image store cannot pull two architectures under the same
index digest ("cannot overwrite digest"). Child digests remain bound to the
signed index by its checked content hash and are distinct local references.
"""
namespace, separator, digest = image.partition("@")
require(separator and namespace in {IMAGE, SITE_IMAGE}
and re.fullmatch(r"sha256:[0-9a-f]{64}", digest), "image_identity")
raw = registry_manifest(namespace, digest)
require(raw is not None, "published_image_required")
members = strict_json(raw).get("manifests")
require(isinstance(members, list), "platform_descriptor")
result = {}
for architecture in ("amd64", "arm64"):
selected = [item for item in members if isinstance(item, dict)
and isinstance(item.get("platform"), dict)
and item["platform"].get("os") == "linux"
and item["platform"].get("architecture") == architecture
and item["platform"].get("variant", "") in ({"", "v8"} if architecture == "arm64" else {""})]
require(len(selected) == 1, "platform_descriptor")
child = selected[0]
require(child.get("mediaType") in {
"application/vnd.oci.image.manifest.v1+json", "application/vnd.docker.distribution.manifest.v2+json"}
and isinstance(child.get("digest"), str) and re.fullmatch(r"sha256:[0-9a-f]{64}", child["digest"]),
"platform_descriptor")
result["linux/" + architecture] = namespace + "@" + child["digest"]
return result


def registry_tags():
status, raw = registry_request(IMAGE, "tags/list?n=1000")
if status == 404:
Expand Down
58 changes: 56 additions & 2 deletions tests/test_mcp_publication.py
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,33 @@ class TransportBoundaryTests(unittest.TestCase):
def setUp(self):
self.p = importlib.import_module("delivery.ci.publish_mcp_artifacts")

@staticmethod
def platform_references(image):
namespace = image.split("@")[0]
return {"linux/amd64": namespace + "@sha256:" + "1" * 64,
"linux/arm64": namespace + "@sha256:" + "2" * 64}

def test_platform_images_select_children_bound_to_exact_index_digest(self):
members = [{"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:" + digit * 64,
"platform": {"os": "linux", "architecture": arch}}
for arch, digit in (("amd64", "1"), ("arm64", "2"))]
members[1]["platform"]["variant"] = "v8"
valid = {"schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": members}
def resolve(value, digest=None):
raw = fixture.json_bytes(value)
reference = self.p.IMAGE + "@sha256:" + (digest or self.p.hashlib.sha256(raw).hexdigest())
with patch.object(self.p, "registry_request", return_value=(200, raw)):
return self.p.platform_images(reference)
self.assertEqual(resolve(valid), self.platform_references(self.p.IMAGE))
with self.assertRaisesRegex(self.p.PublicationError, "registry_digest"):
resolve(valid, "a" * 64)
for replacement in (None, members[:1], members + [members[0]],
[members[0], {**members[1], "digest": "invalid"}],
[members[0], {**members[1], "mediaType": "invalid"}]):
with self.subTest(members=replacement), self.assertRaisesRegex(self.p.PublicationError, "platform_descriptor"):
resolve({**valid, "manifests": replacement})

def test_registry_without_tagged_images_has_no_reusable_runtime(self):
with patch.object(self.p, "registry_request", return_value=(200, b'{"name":"zeegin/v8std-mcp","tags":null}')):
self.assertEqual(self.p.registry_tags(), [])
Expand Down Expand Up @@ -778,8 +805,8 @@ def command(argv, **kwargs):
project = argv[3]
if "pull" == argv[-1]:
projects.append(project)
self.assertEqual(env["V8STD_MCP_IMAGE"], self.p.IMAGE + "@sha256:" + "a" * 64)
self.assertEqual(env["V8STD_SITE_IMAGE"], self.p.SITE_IMAGE + "@sha256:" + "c" * 64)
self.assertEqual(env["V8STD_MCP_IMAGE"], self.platform_references(self.p.IMAGE)[platform])
self.assertEqual(env["V8STD_SITE_IMAGE"], self.platform_references(self.p.SITE_IMAGE)[platform])
effects.append((platform, "pull"))
elif "up" in argv:
self.assertIn("--no-build", argv)
Expand Down Expand Up @@ -814,6 +841,7 @@ def smoke(url, record, deadline):
now[0] += 390
raise ValueError("not ready")
with patch.dict(os.environ, {}, clear=True), patch.object(self.p, "bounded_command", side_effect=command), \
patch.object(self.p, "platform_images", side_effect=self.platform_references), \
patch.object(self.p, "runtime_smoke", side_effect=smoke), \
patch.object(self.p.time, "monotonic", side_effect=lambda: now[0]):
if name == "valid":
Expand Down Expand Up @@ -995,6 +1023,7 @@ def command(argv, **kwargs):
return b"{}"
with patch.dict(os.environ, {"DOCKER_AUTH_CONFIG": "synthetic", "DOCKER_CONTEXT": "synthetic-remote",
"REGISTRY_AUTH_FILE": "/synthetic/auth"}, clear=True), \
patch.object(self.p, "platform_images", side_effect=self.platform_references), \
patch.object(self.p, "bounded_command", side_effect=command):
with self.assertRaisesRegex(self.p.PublicationError, "command_timeout") as raised:
self.p.CITransport().smoke(self.p.IMAGE + "@sha256:" + "a" * 64, "b" * 40, {})
Expand All @@ -1006,6 +1035,31 @@ def command(argv, **kwargs):
self.assertFalse(name in kwargs["env"], "anonymous environment retains key: " + name)
self.assertFalse(Path(kwargs["env"]["DOCKER_CONFIG"]).exists())

def test_default_source_uses_distinct_child_digests_for_pull_and_run(self):
pulled, running = {}, {}
image = self.p.IMAGE + "@sha256:" + "a" * 64
def command(argv, **kwargs):
if argv[:2] == ["docker", "pull"]:
platform = argv[3]
self.assertNotIn(argv[-1], pulled.values())
pulled[platform] = argv[-1]
elif argv[:2] == ["docker", "run"]:
platform = argv[argv.index("--platform") + 1]
self.assertEqual(pulled[platform], self.platform_references(image)[platform])
self.assertIn(pulled[platform], argv)
running[argv[argv.index("--name") + 1]] = platform
elif argv[:2] == ["docker", "inspect"]:
self.assertIn(argv[-1], running)
return json.dumps([{"Config": {"Labels": {"org.opencontainers.image.revision": "b" * 40}},
"NetworkSettings": {"Ports": {"8000/tcp": [{"HostPort": "12345"}]}}}]).encode()
return b""
with patch.object(self.p, "platform_images", side_effect=self.platform_references), \
patch.object(self.p, "bounded_command", side_effect=command), \
patch.object(self.p, "runtime_smoke") as smoke:
self.p.CITransport().smoke(image, "b" * 40, fixture.snapshot_fixture()[1])
self.assertEqual(set(pulled), {"linux/amd64", "linux/arm64"})
self.assertEqual(smoke.call_count, 2)

def test_release_exact_queue_identity_capacity_failure_and_malformed_platform_fail_closed(self):
context = dict(event="push", repository="zeegin/v8std", ref="refs/heads/main",
sha="c" * 40, main_sha="c" * 40, run_id=1, run_number=2, attempt=1,
Expand Down