release: fix RC-04 single-platform archive export

This commit is contained in:
2026-09-20 14:14:49 +08:00
parent 56b9565742
commit 9e9a22fcec
3 changed files with 182 additions and 104 deletions
+7 -22
View File
@@ -1743,29 +1743,14 @@ def inspect_oci_archive(path: Path) -> dict[str, Any]:
"application/vnd.oci.image.index.v1+json",
"application/vnd.docker.distribution.manifest.list.v2+json",
}:
nested = _require_mapping(load_json_bytes(manifest_data, label="nested OCI index"), "nested OCI index")
nested_descriptors = _require_list(nested.get("manifests"), "nested OCI index manifests")
matches = []
for candidate in nested_descriptors:
platform = _require_mapping(
_require_mapping(candidate, "nested OCI descriptor").get("platform"),
"nested OCI descriptor platform",
)
if platform.get("os") == "linux" and platform.get("architecture") == "amd64":
matches.append(candidate)
if len(matches) != 1:
raise ReleaseValidationError("OCI archive does not contain exactly one linux/amd64 image")
descriptor, manifest_data = _verify_oci_descriptor(
archive, matches[0], label="OCI linux/amd64 manifest descriptor"
raise ReleaseValidationError(
"OCI top-level descriptor must reference one image manifest directly, not an image index"
)
assert manifest_data is not None
media_type = descriptor.get("mediaType")
else:
platform = descriptor.get("platform")
if isinstance(platform, dict) and (
platform.get("os") != "linux" or platform.get("architecture") != "amd64"
):
raise ReleaseValidationError("OCI descriptor platform is not linux/amd64")
platform = descriptor.get("platform")
if isinstance(platform, dict) and (
platform.get("os") != "linux" or platform.get("architecture") != "amd64"
):
raise ReleaseValidationError("OCI descriptor platform is not linux/amd64")
if media_type not in {
"application/vnd.oci.image.manifest.v1+json",
"application/vnd.docker.distribution.manifest.v2+json",
+78 -82
View File
@@ -897,6 +897,64 @@ def validate_release_inputs(
}
def _image_build_command(
*,
spec: Mapping[str, Any],
commit: str,
tag: str,
builder_name: str,
source_date_epoch: int,
dockerfile: str | Path,
oci_output: str | Path,
docker_output: str | Path,
) -> list[str]:
"""Return the single-platform build command shared by plan and execution.
``BUILDKIT_MULTI_PLATFORM=1`` is intentionally absent. Even with one
explicit target platform, that BuildKit option changes the result into a
manifest list, which the required Docker archive exporter rejects.
"""
role = spec["role"]
image_ref = f"{spec['name']}:{commit}"
return [
PINNED_EXECUTABLES["docker"],
"--host",
LOCAL_DOCKER_HOST,
"buildx",
"build",
"--builder",
builder_name,
"--platform",
TARGET["platform"],
"--file",
str(dockerfile),
"--tag",
image_ref,
"--label",
f"org.opencontainers.image.revision={commit}",
"--label",
f"org.opencontainers.image.version={tag}",
"--label",
f"org.opencontainers.image.title={spec['name']}",
"--label",
f"io.yuqei.release.role={role}",
"--build-arg",
f"SOURCE_DATE_EPOCH={source_date_epoch}",
"--build-arg",
f"{spec['base_image']['build_arg']}={spec['base_image']['transport_reference']}",
"--pull",
"--no-cache",
"--provenance=false",
"--sbom=false",
"--output",
f"type=oci,dest={oci_output}",
"--output",
f"type=docker,dest={docker_output}",
".",
]
def create_build_plan(
*,
repo: Path,
@@ -980,47 +1038,16 @@ def create_build_plan(
"scan": scan_path,
},
"commands": [
[
PINNED_EXECUTABLES["docker"],
"--host",
LOCAL_DOCKER_HOST,
"buildx",
"build",
"--builder",
builder_name,
"--platform",
TARGET["platform"],
"--file",
spec["dockerfile"],
"--tag",
image_ref,
"--label",
f"org.opencontainers.image.revision={commit}",
"--label",
f"org.opencontainers.image.version={tag}",
"--label",
f"org.opencontainers.image.title={spec['name']}",
"--label",
f"io.yuqei.release.role={role}",
"--build-arg",
f"SOURCE_DATE_EPOCH={source_date_epoch}",
"--build-arg",
"BUILDKIT_MULTI_PLATFORM=1",
"--build-arg",
(
f"{spec['base_image']['build_arg']}="
f"{spec['base_image']['transport_reference']}"
),
"--pull",
"--no-cache",
"--provenance=false",
"--sbom=false",
"--output",
f"type=oci,dest=<output-dir>/{oci_path}",
"--output",
f"type=docker,dest=<output-dir>/{docker_path}",
".",
],
_image_build_command(
spec=spec,
commit=commit,
tag=tag,
builder_name=builder_name,
source_date_epoch=source_date_epoch,
dockerfile=spec["dockerfile"],
oci_output=f"<output-dir>/{oci_path}",
docker_output=f"<output-dir>/{docker_path}",
),
[
PINNED_EXECUTABLES["syft"],
"scan",
@@ -1370,47 +1397,16 @@ def execute_build(
scan_path.parent.mkdir(parents=True, exist_ok=True)
runner(
[
PINNED_EXECUTABLES["docker"],
"--host",
LOCAL_DOCKER_HOST,
"buildx",
"build",
"--builder",
builder_name,
"--platform",
TARGET["platform"],
"--file",
str(dockerfile),
"--tag",
image_ref,
"--label",
f"org.opencontainers.image.revision={commit}",
"--label",
f"org.opencontainers.image.version={tag}",
"--label",
f"org.opencontainers.image.title={spec['name']}",
"--label",
f"io.yuqei.release.role={role}",
"--build-arg",
f"SOURCE_DATE_EPOCH={source_date_epoch}",
"--build-arg",
"BUILDKIT_MULTI_PLATFORM=1",
"--build-arg",
(
f"{spec['base_image']['build_arg']}="
f"{spec['base_image']['transport_reference']}"
),
"--pull",
"--no-cache",
"--provenance=false",
"--sbom=false",
"--output",
f"type=oci,dest={oci_path}",
"--output",
f"type=docker,dest={docker_path}",
".",
],
_image_build_command(
spec=spec,
commit=commit,
tag=tag,
builder_name=builder_name,
source_date_epoch=source_date_epoch,
dockerfile=dockerfile,
oci_output=oci_path,
docker_output=docker_path,
),
cwd=source_root,
timeout_seconds=COMMAND_TIMEOUTS_SECONDS["image_build"],
)
@@ -288,6 +288,52 @@ def _write_oci(
return common.inspect_oci_archive(path)
def _wrap_oci_in_nested_index(path: Path, platforms: list[dict[str, str]]) -> None:
with tarfile.open(path, "r") as archive:
entries: dict[str, bytes] = {}
for member in archive.getmembers():
if not member.isfile():
continue
source = archive.extractfile(member)
assert source is not None
entries[member.name] = source.read()
outer_index = json.loads(entries["index.json"])
image_descriptor = outer_index["manifests"][0]
nested = json.dumps(
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{**image_descriptor, "platform": platform} for platform in platforms
],
},
sort_keys=True,
separators=(",", ":"),
).encode()
nested_digest = hashlib.sha256(nested).hexdigest()
entries[f"blobs/sha256/{nested_digest}"] = nested
entries["index.json"] = json.dumps(
{
"schemaVersion": 2,
"manifests": [
{
"mediaType": "application/vnd.oci.image.index.v1+json",
"digest": f"sha256:{nested_digest}",
"size": len(nested),
}
],
},
sort_keys=True,
separators=(",", ":"),
).encode()
with tarfile.open(path, "w") as archive:
for name, data in sorted(entries.items()):
info = tarfile.TarInfo(name)
info.mode = 0o644
info.size = len(data)
archive.addfile(info, BytesIO(data))
def _write_docker_archive(
path: Path,
role: str,
@@ -815,12 +861,38 @@ def test_plan_mode_validates_commit_tag_manifest_archive_and_orders_four_images(
assert plan["compose_gate"]["execution_engine"] == "self_bound_release_runner_builtin_v1"
assert not (tmp_path / "never-created").exists()
for image in plan["images"]:
build_command = image["commands"][0]
flattened = " ".join(" ".join(command) for command in image["commands"])
assert "linux/amd64" in flattened
assert "spdx-json" in flattened
assert "HIGH,CRITICAL" in flattened
assert "type=oci" in flattened and "type=docker" in flattened
assert "--builder fixture-builder" in flattened
assert build_command.count("--platform") == 1
platform_index = build_command.index("--platform")
assert build_command[platform_index + 1] == "linux/amd64"
output_indexes = [
index for index, token in enumerate(build_command) if token == "--output"
]
assert [build_command[index + 1].split(",", 1)[0] for index in output_indexes] == [
"type=oci",
"type=docker",
]
assert "--provenance=false" in build_command
assert "--sbom=false" in build_command
build_arg_indexes = [
index for index, token in enumerate(build_command) if token == "--build-arg"
]
assert [build_command[index + 1] for index in build_arg_indexes] == [
f"SOURCE_DATE_EPOCH={source['epoch']}",
(
f"{image['base_image']['build_arg']}="
f"{image['base_image']['transport_reference']}"
),
]
# Forcing BuildKit's multi-platform result shape makes the Docker
# archive exporter reject even a one-target result as a manifest list.
assert all("BUILDKIT_MULTI_PLATFORM" not in token for token in build_command)
assert common.TRIVY_DB_REPOSITORY_TRANSPORT["reference"] in flattened
trivy_command = image["commands"][2]
assert trivy_command[trivy_command.index("--input") + 1].endswith(".docker.tar")
@@ -1582,6 +1654,31 @@ def test_docker_archive_rejects_layer_content_not_bound_to_config(tmp_path: Path
common.inspect_docker_archive(path, expected_repo_tag=image_ref)
@pytest.mark.parametrize(
"platforms",
[
[{"os": "linux", "architecture": "amd64"}],
[
{"os": "linux", "architecture": "amd64"},
{"os": "linux", "architecture": "arm64"},
],
],
ids=["single-item-index", "multi-platform-index"],
)
def test_oci_archive_rejects_nested_image_index(
tmp_path: Path, platforms: list[dict[str, str]]
) -> None:
path = tmp_path / "nested-index.oci.tar"
_write_oci(path, "contract-api", "yuqei/contract-api", "a" * 40, "v2-test")
_wrap_oci_in_nested_index(path, platforms)
with pytest.raises(
common.ReleaseValidationError,
match="must reference one image manifest directly",
):
common.inspect_oci_archive(path)
def test_oci_archive_rejects_descriptor_valid_layer_not_bound_to_config_diff_id(
tmp_path: Path,
) -> None: