#!/usr/bin/env python3 import argparse from base64 import b64encode from concurrent.futures import ThreadPoolExecutor, as_completed from hashlib import sha256 from itertools import count import boto3 BLOCKSIZE = 512 * 1024 def create_snapshot(region, description, image): """Create an EBS snapshot""" client = boto3.client('ebs', region_name=region) snapshot = client.start_snapshot(VolumeSize=1, Description=description) snapshot_id = snapshot['SnapshotId'] with open(image, 'rb') as fh: for block in count(): data = fh.read(BLOCKSIZE) if not data: break data = data.ljust(BLOCKSIZE, b'\0') checksum = b64encode(sha256(data).digest()).decode() client.put_snapshot_block(SnapshotId=snapshot_id, BlockIndex=block, BlockData=data, DataLength=BLOCKSIZE, Checksum=checksum, ChecksumAlgorithm='SHA256') client.complete_snapshot(SnapshotId=snapshot_id, ChangedBlocksCount=block) return snapshot_id def import_image(region, name, architecture, image, public): """Import an AMI image""" client = boto3.client('ec2', region_name=region) resource = boto3.resource('ec2', region_name=region) description = '%s (%s)' % (name, architecture) snapshot_id = create_snapshot(region=region, description=description, image=image) client.get_waiter('snapshot_completed').wait(SnapshotIds=[snapshot_id]) image = client.register_image(Architecture=architecture, BlockDeviceMappings=[{ 'DeviceName': '/dev/sda1', 'Ebs': { 'SnapshotId': snapshot_id, 'VolumeType': 'standard', }, }], EnaSupport=True, Name=description, RootDeviceName='/dev/sda1', SriovNetSupport='simple', VirtualizationType='hvm') image_id = image['ImageId'] client.get_waiter('image_available').wait(ImageIds=[image_id]) if public: resource.Image(image_id).modify_attribute(Attribute='launchPermission', OperationType='add', UserGroups=['all']) return image_id # Parse command-line arguments parser = argparse.ArgumentParser(description="Import AWS EC2 image (AMI)") parser.add_argument('--architecture', '-a', default='x86_64', help="CPU architecture") parser.add_argument('--name', '-n', required=True, help="Image name") parser.add_argument('--public', '-p', action='store_true', help="Make image public") parser.add_argument('--region', '-r', action='append', help="AWS region(s)") parser.add_argument('image', help="iPXE disk image") args = parser.parse_args() # Use all regions if none specified if not args.region: args.region = sorted(x['RegionName'] for x in boto3.client('ec2').describe_regions()['Regions']) # Use one thread per region to maximise parallelism with ThreadPoolExecutor(max_workers=len(args.region)) as executor: futures = {executor.submit(import_image, region=region, name=args.name, architecture=args.architecture, image=args.image, public=args.public): region for region in args.region} results = {futures[future]: future.result() for future in as_completed(futures)} # Show created images for region in args.region: print("%s: %s" % (region, results[region]))