1919import json
2020import os
2121import pathlib
22- import platform
2322import tarfile
2423import tempfile
2524from typing import Optional
@@ -479,65 +478,6 @@ def find_file_matching_pattern_in_image_layers(
479478 authenticator .authenticate (repository = repository )
480479 # Don't fail here - authentication may fail but public containers can still be accessed
481480
482- def _normalize_arch (a : object ) -> Optional [str ]:
483- if not a :
484- return None
485- s = str (a ).lower ()
486- if s in {"amd64" , "x86_64" }:
487- return "amd64"
488- if s in {"arm64" , "aarch64" }:
489- return "arm64"
490- return None
491-
492- def _preferred_arch () -> str :
493- return _normalize_arch (platform .machine ()) or "amd64"
494-
495- def _pick_platform_manifest_digest (index_manifest : dict ) -> Optional [str ]:
496- manifests = index_manifest .get ("manifests" )
497- if not isinstance (manifests , list ) or not manifests :
498- return None
499-
500- pref = _preferred_arch ()
501- # Prefer linux + preferred arch, then linux/amd64, then linux/arm64, then first digest.
502- best_score = 10_000
503- best_digest : Optional [str ] = None
504-
505- for m in manifests :
506- if not isinstance (m , dict ):
507- continue
508- plat = m .get ("platform" ) or {}
509- if not isinstance (plat , dict ):
510- continue
511- os_name = str (plat .get ("os" ) or "" ).lower ()
512- arch = _normalize_arch (plat .get ("architecture" ))
513- digest = m .get ("digest" )
514- if not (isinstance (digest , str ) and digest .startswith ("sha256:" )):
515- continue
516- if os_name != "linux" or not arch :
517- continue
518-
519- score = 100
520- if arch == pref :
521- score = 0
522- elif arch == "amd64" :
523- score = 10
524- elif arch == "arm64" :
525- score = 20
526-
527- if score < best_score :
528- best_score = score
529- best_digest = digest
530-
531- if best_digest :
532- return best_digest
533-
534- for m in manifests :
535- if isinstance (m , dict ):
536- d = m .get ("digest" )
537- if isinstance (d , str ) and d .startswith ("sha256:" ):
538- return d
539- return None
540-
541481 # Get top-level manifest and digest (tag may resolve to multi-arch index).
542482 top_manifest , top_digest = authenticator .get_manifest_and_digest (
543483 repository , reference
@@ -554,13 +494,30 @@ def _pick_platform_manifest_digest(index_manifest: dict) -> Optional[str]:
554494 if isinstance (top_manifest , dict ) and isinstance (
555495 top_manifest .get ("manifests" ), list
556496 ):
557- platform_digest = _pick_platform_manifest_digest (top_manifest )
558- if platform_digest :
559- resolved , _ = authenticator .get_manifest_and_digest (
560- repository , platform_digest
561- )
562- if resolved :
563- manifest = resolved
497+ # Prefer registry's default platform resolver by requesting a single-image manifest.
498+ single_accept = (
499+ "application/vnd.oci.image.manifest.v1+json, "
500+ "application/vnd.docker.distribution.manifest.v2+json"
501+ )
502+ resolved , _ = authenticator .get_manifest_and_digest (
503+ repository , reference , accept = single_accept
504+ )
505+ if resolved :
506+ manifest = resolved
507+
508+ # If a registry still returns an index (ignoring Accept), fall back to the
509+ # first digest entry for layer inspection. This does NOT affect recorded digests.
510+ if isinstance (manifest , dict ) and isinstance (manifest .get ("manifests" ), list ):
511+ for m in manifest .get ("manifests" ) or []:
512+ if isinstance (m , dict ):
513+ d = m .get ("digest" )
514+ if isinstance (d , str ) and d .startswith ("sha256:" ):
515+ resolved2 , _ = authenticator .get_manifest_and_digest (
516+ repository , d , accept = single_accept
517+ )
518+ if resolved2 :
519+ manifest = resolved2
520+ break
564521
565522 # Check cache with digest validation (always validates digest)
566523 # For pattern searches, use pattern-based cache key (not resolved path)
0 commit comments