55from typing import Any , Dict , List , Optional
66
77import boto3
8+ import botocore .exceptions
89from mypy_boto3_ec2 .client import EC2Client
910from mypy_boto3_ssm import SSMClient
1011
@@ -418,6 +419,7 @@ def create(self) -> Dict[str, _ImageInfo]:
418419 )
419420
420421 images : Dict [str , _ImageInfo ] = dict ()
422+ missing_regions : List [str ] = []
421423 for region in self .image_regions :
422424 ec2client_region : EC2Client = boto3 .client ("ec2" , region_name = region )
423425 image_info : Optional [_ImageInfo ] = self ._get (ec2client_region )
@@ -435,53 +437,10 @@ def create(self) -> Dict[str, _ImageInfo]:
435437 )
436438 images [region ] = image_info
437439 else :
438- logger .info (
439- f"creating image with name '{ self .image_name } ' in "
440- f"region { ec2client_region .meta .region_name } ..."
441- )
442-
443- register_image_kwargs = dict (
444- Name = self .image_name ,
445- Description = self .conf .get ("description" , "" ),
446- Architecture = self ._ctx .conf ["source" ]["architecture" ],
447- RootDeviceName = self .conf ["root_device_name" ],
448- BlockDeviceMappings = [
449- {
450- "Ebs" : {
451- "SnapshotId" : snapshot_ids [region ],
452- "VolumeType" : self .conf ["root_device_volume_type" ],
453- "VolumeSize" : self .conf ["root_device_volume_size" ],
454- },
455- "DeviceName" : self .conf ["root_device_name" ],
456- },
457- # TODO: make those ephemeral block device mappings configurable
458- {"VirtualName" : "ephemeral0" , "DeviceName" : "/dev/sdb" },
459- {"VirtualName" : "ephemeral1" , "DeviceName" : "/dev/sdc" },
460- ],
461- EnaSupport = True ,
462- SriovNetSupport = "simple" ,
463- VirtualizationType = "hvm" ,
464- BootMode = self .conf ["boot_mode" ],
465- )
466-
467- if self .conf ["tpm_support" ]:
468- register_image_kwargs ["TpmSupport" ] = self .conf ["tpm_support" ]
469-
470- if self .conf ["imds_support" ]:
471- register_image_kwargs ["ImdsSupport" ] = self .conf ["imds_support" ]
472-
473- if self .conf ["uefi_data" ]:
474- with open (self .conf ["uefi_data" ], "r" ) as f :
475- uefi_data = f .read ()
476- register_image_kwargs ["UefiData" ] = uefi_data
477-
478- if self .conf ["billing_products" ]:
479- register_image_kwargs ["BillingProducts" ] = self .conf ["billing_products" ]
480-
481- resp = ec2client_region .register_image (** register_image_kwargs )
482- ec2client_region .create_tags (Resources = [resp ["ImageId" ]], Tags = self ._tags )
483- images [region ] = _ImageInfo (resp ["ImageId" ], snapshot_ids [region ])
484-
440+ if image := self ._register_image (snapshot_ids [region ], ec2client_region ):
441+ images [region ] = image
442+ else :
443+ missing_regions .append (region )
485444 # wait for the images
486445 logger .info (f"Waiting for { len (images )} images to be ready the regions ..." )
487446 for region , image_info in images .items ():
@@ -500,8 +459,80 @@ def create(self) -> Dict[str, _ImageInfo]:
500459 if self .conf ["share" ]:
501460 self ._share (self .conf ["share" ], images )
502461
462+ if missing_regions :
463+ logger .error ("Failed to publish images to all regions" , extra = {"missing_regions" : missing_regions })
464+ raise exceptions .IncompleteImageSetException ("Incomplete image set published" )
465+
503466 return images
504467
468+ def _register_image (self , snapshot_id : str , ec2client : EC2Client ) -> Optional [_ImageInfo ]:
469+ """
470+ Register snapshot_id in region configured for ec2client_region
471+
472+ :param snapshot_id: snapshot id to use for image registration
473+ :type snapshot_id: str
474+ :param ec2client: EC2Client for the region to register image to
475+ :type ec2client: EC2Client
476+ :return: _ImageInfo containing the ImageId SnapshotId pair
477+ :rtype: _ImageInfo
478+ """
479+ logger .info (f"creating image with name '{ self .image_name } ' in " f"region { ec2client .meta .region_name } ..." )
480+
481+ register_image_kwargs = dict (
482+ Name = self .image_name ,
483+ Description = self .conf .get ("description" , "" ),
484+ Architecture = self ._ctx .conf ["source" ]["architecture" ],
485+ RootDeviceName = self .conf ["root_device_name" ],
486+ BlockDeviceMappings = [
487+ {
488+ "Ebs" : {
489+ "SnapshotId" : snapshot_id ,
490+ "VolumeType" : self .conf ["root_device_volume_type" ],
491+ "VolumeSize" : self .conf ["root_device_volume_size" ],
492+ },
493+ "DeviceName" : self .conf ["root_device_name" ],
494+ },
495+ # TODO: make those ephemeral block device mappings configurable
496+ {"VirtualName" : "ephemeral0" , "DeviceName" : "/dev/sdb" },
497+ {"VirtualName" : "ephemeral1" , "DeviceName" : "/dev/sdc" },
498+ ],
499+ EnaSupport = True ,
500+ SriovNetSupport = "simple" ,
501+ VirtualizationType = "hvm" ,
502+ BootMode = self .conf ["boot_mode" ],
503+ )
504+
505+ if self .conf ["tpm_support" ]:
506+ register_image_kwargs ["TpmSupport" ] = self .conf ["tpm_support" ]
507+
508+ if self .conf ["imds_support" ]:
509+ register_image_kwargs ["ImdsSupport" ] = self .conf ["imds_support" ]
510+
511+ if self .conf ["uefi_data" ]:
512+ with open (self .conf ["uefi_data" ], "r" ) as f :
513+ uefi_data = f .read ()
514+ register_image_kwargs ["UefiData" ] = uefi_data
515+
516+ if self .conf ["billing_products" ]:
517+ register_image_kwargs ["BillingProducts" ] = self .conf ["billing_products" ]
518+
519+ try :
520+ resp = ec2client .register_image (** register_image_kwargs )
521+ except botocore .exceptions .ClientError as e :
522+ if e .response .get ("Error" , {}).get ("Code" , None ) == "OperationNotPermitted" :
523+ logger .exception (
524+ "Unable to register image" ,
525+ extra = {
526+ "registration_options" : register_image_kwargs ,
527+ "region" : ec2client .meta .region_name ,
528+ },
529+ )
530+ return None
531+ raise e
532+
533+ ec2client .create_tags (Resources = [resp ["ImageId" ]], Tags = self ._tags )
534+ return _ImageInfo (resp ["ImageId" ], snapshot_id )
535+
505536 def publish (self ) -> None :
506537 """
507538 Handle all publication steps
0 commit comments