@@ -119,7 +119,7 @@ def _feed_html_parser(self, url, parser):
119119 data = u .read ()
120120 parser .feed (astring .to_text (data , self .HTML_ENCODING ))
121121 except HTTPError as exc :
122- raise ImageProviderError (f"Cannot open { url } " ) from exc
122+ raise ImageProviderError (f"Network error: Cannot open { url } " ) from exc
123123
124124 @staticmethod
125125 def get_best_version (versions ):
@@ -633,6 +633,109 @@ def _take_snapshot(self):
633633 process .run (cmd )
634634 return new_image
635635
636+ @classmethod
637+ def _find_cached_image ( # pylint: disable=too-many-locals
638+ cls ,
639+ cache_dirs ,
640+ name = None ,
641+ version = None ,
642+ build = None ,
643+ arch = None ,
644+ checksum = None ,
645+ algorithm = None ,
646+ snapshot_dir = None ,
647+ ):
648+ """
649+ Find a cached image using asset.py enhanced built-in functionality.
650+
651+ This version uses the enhanced Asset.get_metadata() method which supports
652+ both normal asset names and direct file paths, maximizing utilization of
653+ existing asset.py caching features.
654+ """
655+
656+ # pylint: disable-next=invalid-name
657+ ARCH_COMPATIBILITY = {
658+ "x86_64" : ["x86_64" , "amd64" , "64" ],
659+ "amd64" : ["x86_64" , "amd64" , "64" ],
660+ "aarch64" : ["aarch64" , "arm64" ],
661+ "arm64" : ["aarch64" , "arm64" ],
662+ }
663+ compatible_arches = ARCH_COMPATIBILITY .get (arch , [arch ]) if arch else []
664+
665+ def matches_image_criteria (metadata , name , version , build , compatible_arches ):
666+ """
667+ Check if metadata matches the specified image criteria.
668+
669+ :param metadata: Asset metadata dictionary
670+ :param name: Expected image name
671+ :param version: Expected image version
672+ :param build: Expected image build
673+ :param compatible_arches: List of compatible architectures
674+ :return: True if metadata matches criteria, False otherwise
675+ """
676+
677+ if metadata .get ("type" ) != "vmimage" :
678+ return False
679+
680+ if name and metadata .get ("name" , "" ).lower () != name .lower ():
681+ return False
682+
683+ if version and str (metadata .get ("version" , "" )) != str (version ):
684+ return False
685+
686+ if build and str (metadata .get ("build" , "" )) != str (build ):
687+ return False
688+
689+ if compatible_arches and metadata .get ("arch" ) not in compatible_arches :
690+ return False
691+
692+ return True
693+
694+ # Use Asset.get_all_assets() to find cached assets
695+ for asset_path in asset .Asset .get_all_assets (cache_dirs , sort = False ):
696+ try :
697+ temp_asset = asset .Asset (
698+ name = asset_path ,
699+ asset_hash = checksum ,
700+ algorithm = algorithm ,
701+ cache_dirs = cache_dirs ,
702+ )
703+
704+ try :
705+ metadata = temp_asset .get_metadata ()
706+ if not metadata :
707+ continue
708+ except OSError :
709+ continue
710+
711+ if matches_image_criteria (
712+ metadata , name , version , build , compatible_arches
713+ ):
714+ # pylint: disable-next=W0212
715+ if checksum and not temp_asset ._verify_hash (asset_path ):
716+ LOG .debug ("Hash mismatch for cached image: %s" , asset_path )
717+ continue
718+
719+ LOG .info ("Found matching cached image: %s" , asset_path )
720+ return cls (
721+ name = metadata .get ("name" , name ),
722+ url = asset_path ,
723+ version = metadata .get ("version" , version ),
724+ arch = metadata .get ("arch" , arch ),
725+ build = metadata .get ("build" , build ),
726+ checksum = checksum ,
727+ algorithm = algorithm ,
728+ cache_dir = cache_dirs [0 ],
729+ snapshot_dir = snapshot_dir ,
730+ )
731+
732+ except (OSError , ValueError , KeyError , AttributeError ) as e :
733+ # Skip assets that can't be processed (common errors during metadata processing)
734+ LOG .debug ("Skipping asset %s due to error: %s" , asset_path , e )
735+ continue
736+
737+ return None
738+
636739 @classmethod
637740 # pylint: disable=R0913
638741 def from_parameters (
@@ -739,14 +842,25 @@ def get_best_provider(name=None, version=None, build=None, arch=None):
739842 if name == "fedora" and arch in ("ppc64" , "ppc64le" , "s390x" ):
740843 name = "fedorasecondary"
741844
845+ provider_errors = []
742846 for provider in IMAGE_PROVIDERS :
743847 if name is None or name == provider .name .lower ():
744848 try :
745849 return provider (** provider_args )
746850 except ImageProviderError as e :
747851 LOG .debug (e )
852+ provider_errors .append (f"{ provider .name } : { e } " )
853+ except (HTTPError , OSError ) as e :
854+ LOG .debug (
855+ "Network error while creating provider %s: %s" , provider .name , e
856+ )
857+ # Network errors should raise immediately
858+ raise ImageProviderError (f"Network error: { e } " ) from e
748859
749860 LOG .debug ("Provider for %s not available" , name )
861+ if provider_errors :
862+ error_details = "; " .join (provider_errors )
863+ raise ImageProviderError (f"Providers failed - { error_details } " )
750864 raise AttributeError ("Provider not available" )
751865
752866
0 commit comments