@@ -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 ):
@@ -213,7 +213,7 @@ def get_image_url(self):
213213 else :
214214 cloud = "CloudImages"
215215
216- if self .url_old_images and int (self .version ) <= 38 :
216+ if self .url_old_images and int (self .version ) <= 40 :
217217 self .url_versions = self .url_old_images
218218
219219 self .url_images = self .url_versions + "{version}/" + cloud + "/{arch}/images/"
@@ -633,6 +633,89 @@ 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+ return (
667+ metadata .get ("type" ) == "vmimage"
668+ and (not name or metadata .get ("name" , "" ).lower () == name .lower ())
669+ and (not version or str (metadata .get ("version" , "" )) == str (version ))
670+ and (not build or str (metadata .get ("build" , "" )) == str (build ))
671+ and (not compatible_arches or metadata .get ("arch" ) in compatible_arches )
672+ )
673+
674+ # Use Asset.get_all_assets() to find cached assets
675+ for asset_path in asset .Asset .get_all_assets (cache_dirs , sort = False ):
676+ try :
677+ temp_asset = asset .Asset (
678+ name = asset_path ,
679+ asset_hash = checksum ,
680+ algorithm = algorithm ,
681+ cache_dirs = cache_dirs ,
682+ )
683+
684+ try :
685+ metadata = temp_asset .get_metadata ()
686+ if not metadata :
687+ continue
688+ except OSError :
689+ continue
690+
691+ if matches_image_criteria (
692+ metadata , name , version , build , compatible_arches
693+ ):
694+ # pylint: disable-next=W0212
695+ if checksum and not temp_asset ._verify_hash (asset_path ):
696+ LOG .debug ("Hash mismatch for cached image: %s" , asset_path )
697+ continue
698+
699+ LOG .info ("Found matching cached image: %s" , asset_path )
700+ return cls (
701+ name = metadata .get ("name" , name ),
702+ url = asset_path ,
703+ version = metadata .get ("version" , version ),
704+ arch = metadata .get ("arch" , arch ),
705+ build = metadata .get ("build" , build ),
706+ checksum = checksum ,
707+ algorithm = algorithm ,
708+ cache_dir = cache_dirs [0 ],
709+ snapshot_dir = snapshot_dir ,
710+ )
711+
712+ except (OSError , ValueError , KeyError , AttributeError ) as e :
713+ # Skip assets that can't be processed (common errors during metadata processing)
714+ LOG .debug ("Skipping asset %s due to error: %s" , asset_path , e )
715+ continue
716+
717+ return None
718+
636719 @classmethod
637720 # pylint: disable=R0913
638721 def from_parameters (
@@ -739,14 +822,25 @@ def get_best_provider(name=None, version=None, build=None, arch=None):
739822 if name == "fedora" and arch in ("ppc64" , "ppc64le" , "s390x" ):
740823 name = "fedorasecondary"
741824
825+ provider_errors = []
742826 for provider in IMAGE_PROVIDERS :
743827 if name is None or name == provider .name .lower ():
744828 try :
745829 return provider (** provider_args )
746830 except ImageProviderError as e :
747831 LOG .debug (e )
832+ provider_errors .append (f"{ provider .name } : { e } " )
833+ except (HTTPError , OSError ) as e :
834+ LOG .debug (
835+ "Network error while creating provider %s: %s" , provider .name , e
836+ )
837+ # Network errors should raise immediately
838+ raise ImageProviderError (f"Network error: { e } " ) from e
748839
749840 LOG .debug ("Provider for %s not available" , name )
841+ if provider_errors :
842+ error_details = "; " .join (provider_errors )
843+ raise ImageProviderError (f"Providers failed - { error_details } " )
750844 raise AttributeError ("Provider not available" )
751845
752846
0 commit comments