Merge pull request #216 from DreadPirateShawn/ImplementCopyImage

AMI: Implement copy_image.
This commit is contained in:
Steve Pulec 2014-10-05 10:55:48 -04:00
commit f0724d458e
3 changed files with 91 additions and 11 deletions

View File

@ -593,19 +593,33 @@ class TagBackend(object):
class Ami(TaggedEC2Instance):
def __init__(self, ami_id, instance, name, description):
def __init__(self, ami_id, instance=None, source_ami=None, name=None, description=None):
self.id = ami_id
self.state = "available"
self.instance = instance
self.instance_id = instance.id
self.virtualization_type = instance.virtualization_type
self.architecture = instance.architecture
self.kernel_id = instance.kernel
self.platform = instance.platform
if instance:
self.instance = instance
self.instance_id = instance.id
self.virtualization_type = instance.virtualization_type
self.architecture = instance.architecture
self.kernel_id = instance.kernel
self.platform = instance.platform
self.name = name
self.description = description
elif source_ami:
"""
http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/CopyingAMIs.html
"We don't copy launch permissions, user-defined tags, or Amazon S3 bucket permissions from the source AMI to the new AMI."
~ 2014.09.29
"""
self.virtualization_type = source_ami.virtualization_type
self.architecture = source_ami.architecture
self.kernel_id = source_ami.kernel_id
self.platform = source_ami.platform
self.name = name if name else source_ami.name
self.description = description if description else source_ami.description
self.name = name
self.description = description
self.launch_permission_groups = set()
# AWS auto-creates these, we should reflect the same.
@ -637,11 +651,18 @@ class AmiBackend(object):
self.amis = {}
super(AmiBackend, self).__init__()
def create_image(self, instance_id, name, description):
def create_image(self, instance_id, name=None, description=None):
# TODO: check that instance exists and pull info from it.
ami_id = random_ami_id()
instance = self.get_instance(instance_id)
ami = Ami(ami_id, instance, name, description)
ami = Ami(ami_id, instance=instance, source_ami=None, name=name, description=description)
self.amis[ami_id] = ami
return ami
def copy_image(self, source_image_id, source_region, name=None, description=None):
source_ami = ec2_backends[source_region].describe_images(ami_ids=[source_image_id])[0]
ami_id = random_ami_id()
ami = Ami(ami_id, instance=None, source_ami=source_ami, name=name, description=description)
self.amis[ami_id] = ami
return ami

View File

@ -19,6 +19,15 @@ class AmisResponse(BaseResponse):
template = Template(CREATE_IMAGE_RESPONSE)
return template.render(image=image)
def copy_image(self):
source_image_id = self.querystring.get('SourceImageId')[0]
source_region = self.querystring.get('SourceRegion')[0]
name = self.querystring.get('Name')[0] if self.querystring.get('Name') else None
description = self.querystring.get('Description')[0] if self.querystring.get('Description') else None
image = ec2_backend.copy_image(source_image_id, source_region, name, description)
template = Template(COPY_IMAGE_RESPONSE)
return template.render(image=image)
def deregister_image(self):
ami_id = self.querystring.get('ImageId')[0]
success = ec2_backend.deregister_image(ami_id)
@ -61,6 +70,11 @@ CREATE_IMAGE_RESPONSE = """<CreateImageResponse xmlns="http://ec2.amazonaws.com/
<imageId>{{ image.id }}</imageId>
</CreateImageResponse>"""
COPY_IMAGE_RESPONSE = """<CopyImageResponse xmlns="http://ec2.amazonaws.com/doc/2013-07-15/">
<requestId>60bc441d-fa2c-494d-b155-5d6a3EXAMPLE</requestId>
<imageId>{{ image.id }}</imageId>
</CopyImageResponse>"""
# TODO almost all of these params should actually be templated based on the ec2 image
DESCRIBE_IMAGES_RESPONSE = """<DescribeImagesResponse xmlns="http://ec2.amazonaws.com/doc/2012-12-01/">
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>

View File

@ -9,6 +9,7 @@ from boto.exception import EC2ResponseError
import sure # noqa
from moto import mock_ec2
from tests.helpers import requires_boto_gte
@mock_ec2
@ -51,6 +52,50 @@ def test_ami_create_and_delete():
cm.exception.request_id.should_not.be.none
@requires_boto_gte("2.14.0")
@mock_ec2
def test_ami_copy():
conn = boto.connect_ec2('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd')
instance = reservation.instances[0]
source_image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")
source_image = conn.get_all_images(image_ids=[source_image_id])[0]
# Boto returns a 'CopyImage' object with an image_id attribute here. Use the image_id to fetch the full info.
copy_image_ref = conn.copy_image(source_image.region.name, source_image.id, "test-copy-ami", "this is a test copy ami")
copy_image_id = copy_image_ref.image_id
copy_image = conn.get_all_images(image_ids=[copy_image_id])[0]
copy_image.id.should.equal(copy_image_id)
copy_image.virtualization_type.should.equal(source_image.virtualization_type)
copy_image.architecture.should.equal(source_image.architecture)
copy_image.kernel_id.should.equal(source_image.kernel_id)
copy_image.platform.should.equal(source_image.platform)
# Validate auto-created volume and snapshot
conn.get_all_volumes().should.have.length_of(2)
conn.get_all_snapshots().should.have.length_of(2)
copy_image.block_device_mapping.current_value.snapshot_id.should_not.equal(
source_image.block_device_mapping.current_value.snapshot_id)
# Copy from non-existent source ID.
with assert_raises(EC2ResponseError) as cm:
conn.copy_image(source_image.region.name, 'ami-abcd1234', "test-copy-ami", "this is a test copy ami")
cm.exception.code.should.equal('InvalidAMIID.NotFound')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
# Copy from non-existent source region.
with assert_raises(EC2ResponseError) as cm:
invalid_region = 'us-east-1' if (source_image.region.name != 'us-east-1') else 'us-west-1'
conn.copy_image(invalid_region, source_image.id, "test-copy-ami", "this is a test copy ami")
cm.exception.code.should.equal('InvalidAMIID.NotFound')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
@mock_ec2
def test_ami_tagging():
conn = boto.connect_vpc('the_key', 'the_secret')