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