Add tagging to all applicable EC2 objects. Closes #66.

This commit is contained in:
Steve Pulec 2014-05-11 19:00:28 -04:00
parent 06481ebe7e
commit 955b4c6c4a
9 changed files with 159 additions and 22 deletions

View File

@ -36,7 +36,13 @@ class InstanceState(object):
self.code = code self.code = code
class Instance(BotoInstance): class TaggedEC2Instance(object):
def get_tags(self, *args, **kwargs):
tags = ec2_backend.describe_tags(self.id)
return tags
class Instance(BotoInstance, TaggedEC2Instance):
def __init__(self, image_id, user_data, security_groups, **kwargs): def __init__(self, image_id, user_data, security_groups, **kwargs):
super(Instance, self).__init__() super(Instance, self).__init__()
self.id = random_instance_id() self.id = random_instance_id()
@ -86,10 +92,6 @@ class Instance(BotoInstance):
self._state.name = "running" self._state.name = "running"
self._state.code = 16 self._state.code = 16
def get_tags(self, *args, **kwargs):
tags = ec2_backend.describe_tags(self.id)
return tags
class InstanceBackend(object): class InstanceBackend(object):
@ -267,7 +269,7 @@ class TagBackend(object):
return results return results
class Ami(object): class Ami(TaggedEC2Instance):
def __init__(self, ami_id, instance, name, description): def __init__(self, ami_id, instance, name, description):
self.id = ami_id self.id = ami_id
self.instance = instance self.instance = instance
@ -657,7 +659,7 @@ class EBSBackend(object):
return False return False
class VPC(object): class VPC(TaggedEC2Instance):
def __init__(self, vpc_id, cidr_block): def __init__(self, vpc_id, cidr_block):
self.id = vpc_id self.id = vpc_id
self.cidr_block = cidr_block self.cidr_block = cidr_block
@ -703,7 +705,7 @@ class VPCBackend(object):
return vpc return vpc
class Subnet(object): class Subnet(TaggedEC2Instance):
def __init__(self, subnet_id, vpc_id, cidr_block): def __init__(self, subnet_id, vpc_id, cidr_block):
self.id = subnet_id self.id = subnet_id
self.vpc_id = vpc_id self.vpc_id = vpc_id
@ -1061,7 +1063,7 @@ class ElasticAddressBackend(object):
return False return False
class DHCPOptionsSet(object): class DHCPOptionsSet(TaggedEC2Instance):
def __init__(self, domain_name_servers=None, domain_name=None, def __init__(self, domain_name_servers=None, domain_name=None,
ntp_servers=None, netbios_name_servers=None, ntp_servers=None, netbios_name_servers=None,
netbios_node_type=None): netbios_node_type=None):
@ -1071,7 +1073,7 @@ class DHCPOptionsSet(object):
"ntp-servers": ntp_servers, "ntp-servers": ntp_servers,
"netbios-name-servers": netbios_name_servers, "netbios-name-servers": netbios_name_servers,
"netbios-node-type": netbios_node_type, "netbios-node-type": netbios_node_type,
} }
self.id = random_dhcp_option_id() self.id = random_dhcp_option_id()
self.vpc = None self.vpc = None
@ -1113,7 +1115,7 @@ class DHCPOptionsSetBackend(object):
if options_id in self.dhcp_options_sets: if options_id in self.dhcp_options_sets:
if self.dhcp_options_sets[options_id].vpc: if self.dhcp_options_sets[options_id].vpc:
raise DependencyViolationError("Cannot delete assigned DHCP options.") raise DependencyViolationError("Cannot delete assigned DHCP options.")
dhcp_opt = self.dhcp_options_sets.pop(options_id) self.dhcp_options_sets.pop(options_id)
else: else:
raise InvalidDHCPOptionsIdError(options_id) raise InvalidDHCPOptionsIdError(options_id)
return True return True

View File

@ -92,7 +92,16 @@ DESCRIBE_IMAGES_RESPONSE = """<DescribeImagesResponse xmlns="http://ec2.amazonaw
</item> </item>
</blockDeviceMapping> </blockDeviceMapping>
<virtualizationType>{{ image.virtualization_type }}</virtualizationType> <virtualizationType>{{ image.virtualization_type }}</virtualizationType>
<tagSet/> <tagSet>
{% for tag in image.get_tags() %}
<item>
<resourceId>{{ tag.resource_id }}</resourceId>
<resourceType>{{ tag.resource_type }}</resourceType>
<key>{{ tag.key }}</key>
<value>{{ tag.value }}</value>
</item>
{% endfor %}
</tagSet>
<hypervisor>xen</hypervisor> <hypervisor>xen</hypervisor>
</item> </item>
{% endfor %} {% endfor %}

View File

@ -6,8 +6,8 @@ from moto.ec2.utils import (
from moto.ec2.models import ec2_backend from moto.ec2.models import ec2_backend
from moto.ec2.exceptions import( from moto.ec2.exceptions import(
InvalidVPCIdError, InvalidVPCIdError,
InvalidParameterValueError InvalidParameterValueError,
) )
NETBIOS_NODE_TYPES = [1, 2, 4, 8] NETBIOS_NODE_TYPES = [1, 2, 4, 8]
@ -52,7 +52,7 @@ class DHCPOptions(BaseResponse):
ntp_servers=ntp_servers, ntp_servers=ntp_servers,
netbios_name_servers=netbios_name_servers, netbios_name_servers=netbios_name_servers,
netbios_node_type=netbios_node_type netbios_node_type=netbios_node_type
) )
template = Template(CREATE_DHCP_OPTIONS_RESPONSE) template = Template(CREATE_DHCP_OPTIONS_RESPONSE)
return template.render(dhcp_options_set=dhcp_options_set) return template.render(dhcp_options_set=dhcp_options_set)
@ -105,7 +105,16 @@ CREATE_DHCP_OPTIONS_RESPONSE = u"""
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</dhcpConfigurationSet> </dhcpConfigurationSet>
<tagSet/> <tagSet>
{% for tag in dhcp_options_set.get_tags() %}
<item>
<resourceId>{{ tag.resource_id }}</resourceId>
<resourceType>{{ tag.resource_type }}</resourceType>
<key>{{ tag.key }}</key>
<value>{{ tag.value }}</value>
</item>
{% endfor %}
</tagSet>
</dhcpOptions> </dhcpOptions>
</CreateDhcpOptionsResponse> </CreateDhcpOptionsResponse>
""" """
@ -141,7 +150,16 @@ DESCRIBE_DHCP_OPTIONS_RESPONSE = u"""
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</dhcpConfigurationSet> </dhcpConfigurationSet>
<tagSet/> <tagSet>
{% for tag in dhcp_options_set.get_tags() %}
<item>
<resourceId>{{ tag.resource_id }}</resourceId>
<resourceType>{{ tag.resource_type }}</resourceType>
<key>{{ tag.key }}</key>
<value>{{ tag.value }}</value>
</item>
{% endfor %}
</tagSet>
</dhcpOptions> </dhcpOptions>
{% endfor %} {% endfor %}
</item> </item>

View File

@ -37,7 +37,16 @@ CREATE_SUBNET_RESPONSE = """
<cidrBlock>{{ subnet.cidr_block }}</cidrBlock> <cidrBlock>{{ subnet.cidr_block }}</cidrBlock>
<availableIpAddressCount>251</availableIpAddressCount> <availableIpAddressCount>251</availableIpAddressCount>
<availabilityZone>us-east-1a</availabilityZone> <availabilityZone>us-east-1a</availabilityZone>
<tagSet/> <tagSet>
{% for tag in subnet.get_tags() %}
<item>
<resourceId>{{ tag.resource_id }}</resourceId>
<resourceType>{{ tag.resource_type }}</resourceType>
<key>{{ tag.key }}</key>
<value>{{ tag.value }}</value>
</item>
{% endfor %}
</tagSet>
</subnet> </subnet>
</CreateSubnetResponse>""" </CreateSubnetResponse>"""
@ -59,7 +68,16 @@ DESCRIBE_SUBNETS_RESPONSE = """
<cidrBlock>{{ subnet.cidr_block }}</cidrBlock> <cidrBlock>{{ subnet.cidr_block }}</cidrBlock>
<availableIpAddressCount>251</availableIpAddressCount> <availableIpAddressCount>251</availableIpAddressCount>
<availabilityZone>us-east-1a</availabilityZone> <availabilityZone>us-east-1a</availabilityZone>
<tagSet/> <tagSet>
{% for tag in subnet.get_tags() %}
<item>
<resourceId>{{ tag.resource_id }}</resourceId>
<resourceType>{{ tag.resource_type }}</resourceType>
<key>{{ tag.key }}</key>
<value>{{ tag.value }}</value>
</item>
{% endfor %}
</tagSet>
</item> </item>
{% endfor %} {% endfor %}
</subnetSet> </subnetSet>

View File

@ -35,7 +35,16 @@ CREATE_VPC_RESPONSE = """
<cidrBlock>{{ vpc.cidr_block }}</cidrBlock> <cidrBlock>{{ vpc.cidr_block }}</cidrBlock>
<dhcpOptionsId>dopt-1a2b3c4d2</dhcpOptionsId> <dhcpOptionsId>dopt-1a2b3c4d2</dhcpOptionsId>
<instanceTenancy>default</instanceTenancy> <instanceTenancy>default</instanceTenancy>
<tagSet/> <tagSet>
{% for tag in vpc.get_tags() %}
<item>
<resourceId>{{ tag.resource_id }}</resourceId>
<resourceType>{{ tag.resource_type }}</resourceType>
<key>{{ tag.key }}</key>
<value>{{ tag.value }}</value>
</item>
{% endfor %}
</tagSet>
</vpc> </vpc>
</CreateVpcResponse>""" </CreateVpcResponse>"""
@ -50,7 +59,16 @@ DESCRIBE_VPCS_RESPONSE = """
<cidrBlock>{{ vpc.cidr_block }}</cidrBlock> <cidrBlock>{{ vpc.cidr_block }}</cidrBlock>
<dhcpOptionsId>dopt-7a8b9c2d</dhcpOptionsId> <dhcpOptionsId>dopt-7a8b9c2d</dhcpOptionsId>
<instanceTenancy>default</instanceTenancy> <instanceTenancy>default</instanceTenancy>
<tagSet/> <tagSet>
{% for tag in vpc.get_tags() %}
<item>
<resourceId>{{ tag.resource_id }}</resourceId>
<resourceType>{{ tag.resource_type }}</resourceType>
<key>{{ tag.key }}</key>
<value>{{ tag.value }}</value>
</item>
{% endfor %}
</tagSet>
</item> </item>
{% endfor %} {% endfor %}
</vpcSet> </vpcSet>

View File

@ -22,6 +22,26 @@ def test_ami_create_and_delete():
success = conn.deregister_image.when.called_with(image).should.throw(EC2ResponseError) success = conn.deregister_image.when.called_with(image).should.throw(EC2ResponseError)
@mock_ec2
def test_ami_tagging():
conn = boto.connect_vpc('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd')
instance = reservation.instances[0]
conn.create_image(instance.id, "test-ami", "this is a test ami")
image = conn.get_all_images()[0]
image.add_tag("a key", "some value")
tag = conn.get_all_tags()[0]
tag.name.should.equal("a key")
tag.value.should.equal("some value")
# Refresh the DHCP options
image = conn.get_all_images()[0]
image.tags.should.have.length_of(1)
image.tags["a key"].should.equal("some value")
@mock_ec2 @mock_ec2
def test_ami_create_from_missing_instance(): def test_ami_create_from_missing_instance():
conn = boto.connect_ec2('the_key', 'the_secret') conn = boto.connect_ec2('the_key', 'the_secret')

View File

@ -114,5 +114,22 @@ def test_delete_dhcp_options():
def test_delete_dhcp_options_invalid_id(): def test_delete_dhcp_options_invalid_id():
conn = boto.connect_vpc('the_key', 'the_secret') conn = boto.connect_vpc('the_key', 'the_secret')
dhcp_option = conn.create_dhcp_options() conn.create_dhcp_options()
conn.delete_dhcp_options.when.called_with("1").should.throw(EC2ResponseError) conn.delete_dhcp_options.when.called_with("1").should.throw(EC2ResponseError)
@mock_ec2
def test_dhcp_tagging():
conn = boto.connect_vpc('the_key', 'the_secret')
dhcp_option = conn.create_dhcp_options()
dhcp_option.add_tag("a key", "some value")
tag = conn.get_all_tags()[0]
tag.name.should.equal("a key")
tag.value.should.equal("some value")
# Refresh the DHCP options
dhcp_option = conn.get_all_dhcp_options()[0]
dhcp_option.tags.should.have.length_of(1)
dhcp_option.tags["a key"].should.equal("some value")

View File

@ -21,3 +21,21 @@ def test_subnets():
conn.delete_subnet.when.called_with( conn.delete_subnet.when.called_with(
subnet.id).should.throw(EC2ResponseError) subnet.id).should.throw(EC2ResponseError)
@mock_ec2
def test_subnet_tagging():
conn = boto.connect_vpc('the_key', 'the_secret')
vpc = conn.create_vpc("10.0.0.0/16")
subnet = conn.create_subnet(vpc.id, "10.0.0.0/18")
subnet.add_tag("a key", "some value")
tag = conn.get_all_tags()[0]
tag.name.should.equal("a key")
tag.value.should.equal("some value")
# Refresh the subnet
subnet = conn.get_all_subnets()[0]
subnet.tags.should.have.length_of(1)
subnet.tags["a key"].should.equal("some value")

View File

@ -21,3 +21,20 @@ def test_vpcs():
conn.delete_vpc.when.called_with( conn.delete_vpc.when.called_with(
"vpc-1234abcd").should.throw(EC2ResponseError) "vpc-1234abcd").should.throw(EC2ResponseError)
@mock_ec2
def test_vpc_tagging():
conn = boto.connect_vpc()
vpc = conn.create_vpc("10.0.0.0/16")
vpc.add_tag("a key", "some value")
tag = conn.get_all_tags()[0]
tag.name.should.equal("a key")
tag.value.should.equal("some value")
# Refresh the vpc
vpc = conn.get_all_vpcs()[0]
vpc.tags.should.have.length_of(1)
vpc.tags["a key"].should.equal("some value")