| #!/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])) |