1
0
mirror of https://github.com/quay/quay.git synced 2026-01-26 06:21:37 +03:00
Files
quay/workers/repomirrorworker/manifest_utils.py
Marcus Kok 5d57130da6 feat(mirror): add architecture-filtered mirroring support (PROJQUAY-10257) (#4921)
* feat(mirror): add architecture-filtered mirroring support (PROJQUAY-10257)

When architecture_filter is set on a mirror config, copy only the
specified architectures instead of using the --all flag. This preserves
the original manifest list digest for OpenShift compatibility by pushing
the original manifest bytes directly after copying the filtered
architecture manifests.

Key changes:
- Add inspect_raw() and copy_by_digest() methods to SkopeoMirror
- Create manifest_utils.py for manifest list parsing and filtering
- Modify perform_mirror() to use architecture filtering when configured
- Add comprehensive unit tests for the new functionality

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(mirror): default media_type to OCI index when None (PROJQUAY-10257)

Prevent InvalidHeader error when get_manifest_media_type() returns None
by defaulting to OCI_IMAGE_INDEX_CONTENT_TYPE in the Content-Type header
of the manifest push request.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 09:28:05 -05:00

74 lines
2.3 KiB
Python

"""Utilities for parsing and filtering manifest lists during repository mirroring."""
import json
import logging
from typing import Optional
from image.docker.schema2 import DOCKER_SCHEMA2_MANIFESTLIST_CONTENT_TYPE
from image.oci import OCI_IMAGE_INDEX_CONTENT_TYPE
logger = logging.getLogger(__name__)
MANIFEST_LIST_MEDIA_TYPES = {
DOCKER_SCHEMA2_MANIFESTLIST_CONTENT_TYPE,
OCI_IMAGE_INDEX_CONTENT_TYPE,
}
def is_manifest_list(manifest_bytes: str) -> bool:
"""Check if manifest JSON represents a manifest list/index."""
try:
parsed = json.loads(manifest_bytes)
media_type = parsed.get("mediaType", "")
if media_type in MANIFEST_LIST_MEDIA_TYPES:
return True
# OCI index may not have mediaType but has manifests array
if "manifests" in parsed and isinstance(parsed["manifests"], list):
return True
return False
except (json.JSONDecodeError, TypeError):
return False
def get_manifest_media_type(manifest_bytes: str) -> Optional[str]:
"""Extract media type from manifest JSON."""
try:
return json.loads(manifest_bytes).get("mediaType")
except (json.JSONDecodeError, TypeError):
return None
def filter_manifests_by_architecture(manifest_bytes: str, architectures: list[str]) -> list[dict]:
"""
Filter manifest list entries to only include specified architectures.
Returns list of manifest entries with digest, size, and platform info.
"""
try:
parsed = json.loads(manifest_bytes)
except json.JSONDecodeError:
return []
filtered = []
for manifest in parsed.get("manifests", []):
platform = manifest.get("platform", {})
arch = platform.get("architecture", "")
if arch in architectures:
filtered.append(manifest)
logger.debug("Including manifest %s for arch %s", manifest.get("digest"), arch)
return filtered
def get_available_architectures(manifest_bytes: str) -> list[str]:
"""Get all architectures present in a manifest list."""
try:
parsed = json.loads(manifest_bytes)
except json.JSONDecodeError:
return []
return [
m.get("platform", {}).get("architecture")
for m in parsed.get("manifests", [])
if m.get("platform", {}).get("architecture")
]