From b4a9748faa5c36acf94107aca6970a8a0c4af69c Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 19 Feb 2014 19:56:51 -0500 Subject: [PATCH 1/5] Added multi VPC support Initial commit Refactored terminate_vpc method to support vpc_id_tags Cleaned up find_vpc() method --- cloud/ec2_vpc | 106 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 68 insertions(+), 38 deletions(-) diff --git a/cloud/ec2_vpc b/cloud/ec2_vpc index 9b9fb95a0b2..88f44f7f93e 100644 --- a/cloud/ec2_vpc +++ b/cloud/ec2_vpc @@ -56,6 +56,12 @@ options: required: false default: null aliases: [] + vpc_id_tags: + description: + - A list of tags uniquely identifying a VPC in the form of: {Tag1: Value1, Tag2: Value2, ...}. This list works in conjunction with CIDR (cidr_block) and is gnored when VPC id (vpc_id) is specified. + required: false + default: null + aliases: [] internet_gateway: description: - Toggle whether there should be an Internet gateway attached to the VPC @@ -127,6 +133,7 @@ EXAMPLES = ''' module: ec2_vpc state: present cidr_block: 172.23.0.0/16 + vpc_id_tags: { "Environment":"Development" } region: us-west-2 # Full creation example with subnets and optional availability zones. # The absence or presense of subnets deletes or creates them respectively. @@ -134,6 +141,7 @@ EXAMPLES = ''' module: ec2_vpc state: present cidr_block: 172.22.0.0/16 + vpc_id_tags: { "Environment":"Development" } subnets: - cidr: 172.22.1.0/24 az: us-west-2c @@ -193,9 +201,54 @@ def get_vpc_info(vpc): 'state': vpc.state, }) +def find_vpc(module, vpc_conn, vpc_id=None, cidr=None): + """ + Finds a VPC that matches a specific id or cidr + tags + + module : AnsibleModule object + vpc_conn: authenticated VPCConnection connection object + + Returns: + A VPC object that matches either an ID or CIDR and one or more tag values + """ + + if vpc_id == None and cidr == None: + module.fail_json( + msg='You must specify either a vpc id or a cidr block + list of unique tags, aborting' + ) + + found_vpcs = [] + + vpc_id_tags = module.params.get('vpc_id_tags') + + # Check for existing VPC by cidr_block or id + if vpc_id is not None: + found_vpcs = vpc_conn.get_all_vpcs(None, {'vpc-id': vpc_id, 'state': 'available',}) + + else: + previous_vpcs = vpc_conn.get_all_vpcs(None, {'cidr': cidr, 'state': 'available'}) + + for vpc in previous_vpcs: + # Get all tags for each of the found VPCs + vpc_tags = dict((t.name, t.value) for t in vpc_conn.get_all_tags(filters={'resource-id': vpc.id})) + + # If the supplied list of ID Tags match a subset of the VPC Tags, we found our VPC + if set(vpc_id_tags.items()).issubset(set(vpc_tags.items())): + found_vpcs.append(vpc) + + found_vpc = None + + if len(found_vpcs) == 1: + found_vpc = found_vpcs[0] + + if len(found_vpcs) > 1: + module.fail_json(msg='Found more than one vpc based on the supplied criteria, aborting') + + return (found_vpc) + def create_vpc(module, vpc_conn): """ - Creates a new VPC + Creates a new or modifies an existing VPC. module : AnsibleModule object vpc_conn: authenticated VPCConnection connection object @@ -217,20 +270,12 @@ def create_vpc(module, vpc_conn): wait_timeout = int(module.params.get('wait_timeout')) changed = False - # Check for existing VPC by cidr_block or id - if id != None: - filter_dict = {'vpc-id':id, 'state': 'available',} - previous_vpcs = vpc_conn.get_all_vpcs(None, filter_dict) - else: - filter_dict = {'cidr': cidr_block, 'state': 'available'} - previous_vpcs = vpc_conn.get_all_vpcs(None, filter_dict) - - if len(previous_vpcs) > 1: - module.fail_json(msg='EC2 returned more than one VPC, aborting') + # Check for existing VPC by cidr_block + tags or id + previous_vpc = find_vpc(module, vpc_conn, id, cidr_block) - if len(previous_vpcs) == 1: + if previous_vpc is not None: changed = False - vpc = previous_vpcs[0] + vpc = previous_vpc else: changed = True try: @@ -269,6 +314,7 @@ def create_vpc(module, vpc_conn): module.fail_json(msg='subnets needs to be a list of cidr blocks') current_subnets = vpc_conn.get_all_subnets(filters={ 'vpc_id': vpc.id }) + # First add all new subnets for subnet in subnets: add_subnet = True @@ -281,6 +327,7 @@ def create_vpc(module, vpc_conn): changed = True except EC2ResponseError, e: module.fail_json(msg='Unable to create subnet {0}, error: {1}'.format(subnet['cidr'], e)) + # Now delete all absent subnets for csubnet in current_subnets: delete_subnet = True @@ -332,7 +379,7 @@ def create_vpc(module, vpc_conn): if not isinstance(route_tables, list): module.fail_json(msg='route tables need to be a list of dictionaries') - # Work through each route table and update/create to match dictionary array +# Work through each route table and update/create to match dictionary array all_route_tables = [] for rt in route_tables: try: @@ -350,7 +397,7 @@ def create_vpc(module, vpc_conn): # Associate with subnets for sn in rt['subnets']: - rsn = vpc_conn.get_all_subnets(filters={'cidr': sn}) + rsn = vpc_conn.get_all_subnets(filters={'cidr': sn, 'vpc_id': vpc.id }) if len(rsn) != 1: module.fail_json( msg='The subnet {0} to associate with route_table {1} ' \ @@ -360,7 +407,7 @@ def create_vpc(module, vpc_conn): # Disassociate then associate since we don't have replace old_rt = vpc_conn.get_all_route_tables( - filters={'association.subnet_id': rsn.id} + filters={'association.subnet_id': rsn.id, 'vpc_id': vpc.id} ) if len(old_rt) == 1: old_rt = old_rt[0] @@ -434,23 +481,10 @@ def terminate_vpc(module, vpc_conn, vpc_id=None, cidr=None): vpc_dict = {} terminated_vpc_id = '' changed = False - - if vpc_id == None and cidr == None: - module.fail_json( - msg='You must either specify a vpc id or a cidr '\ - 'block to terminate a VPC, aborting' - ) - if vpc_id is not None: - vpc_rs = vpc_conn.get_all_vpcs(vpc_id) - else: - vpc_rs = vpc_conn.get_all_vpcs(filters={'cidr': cidr}) - if len(vpc_rs) > 1: - module.fail_json( - msg='EC2 returned more than one VPC for id {0} ' \ - 'or cidr {1}, aborting'.format(vpc_id,vidr) - ) - if len(vpc_rs) == 1: - vpc = vpc_rs[0] + + vpc = find_vpc(module, vpc_conn, vpc_id, cidr) + + if vpc is not None: if vpc.state == 'available': terminated_vpc_id=vpc.id vpc_dict=get_vpc_info(vpc) @@ -497,6 +531,7 @@ def main(): dns_hostnames = dict(choices=BOOLEANS, default=True), subnets = dict(type='list'), vpc_id = dict(), + vpc_id_tags = dict(type='dict'), internet_gateway = dict(choices=BOOLEANS, default=False), route_tables = dict(type='list'), state = dict(choices=['present', 'absent'], default='present'), @@ -527,11 +562,6 @@ def main(): if module.params.get('state') == 'absent': vpc_id = module.params.get('vpc_id') cidr = module.params.get('cidr_block') - if vpc_id == None and cidr == None: - module.fail_json( - msg='You must either specify a vpc id or a cidr '\ - 'block to terminate a VPC, aborting' - ) (changed, vpc_dict, new_vpc_id) = terminate_vpc(module, vpc_conn, vpc_id, cidr) subnets_changed = None elif module.params.get('state') == 'present': From ef6ba741566633ef7ee1415f2bcd54d461fbabf4 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 5 Mar 2014 10:32:51 -0500 Subject: [PATCH 2/5] Renamed vpc_id_tags to resource_tags --- cloud/ec2_vpc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cloud/ec2_vpc b/cloud/ec2_vpc index 88f44f7f93e..6bb2c7d235c 100644 --- a/cloud/ec2_vpc +++ b/cloud/ec2_vpc @@ -56,7 +56,7 @@ options: required: false default: null aliases: [] - vpc_id_tags: + resource_tags: description: - A list of tags uniquely identifying a VPC in the form of: {Tag1: Value1, Tag2: Value2, ...}. This list works in conjunction with CIDR (cidr_block) and is gnored when VPC id (vpc_id) is specified. required: false @@ -133,7 +133,7 @@ EXAMPLES = ''' module: ec2_vpc state: present cidr_block: 172.23.0.0/16 - vpc_id_tags: { "Environment":"Development" } + resource_tags: { "Environment":"Development" } region: us-west-2 # Full creation example with subnets and optional availability zones. # The absence or presense of subnets deletes or creates them respectively. @@ -141,7 +141,7 @@ EXAMPLES = ''' module: ec2_vpc state: present cidr_block: 172.22.0.0/16 - vpc_id_tags: { "Environment":"Development" } + resource_tags: { "Environment":"Development" } subnets: - cidr: 172.22.1.0/24 az: us-west-2c @@ -219,7 +219,7 @@ def find_vpc(module, vpc_conn, vpc_id=None, cidr=None): found_vpcs = [] - vpc_id_tags = module.params.get('vpc_id_tags') + resource_tags = module.params.get('resource_tags') # Check for existing VPC by cidr_block or id if vpc_id is not None: @@ -233,7 +233,7 @@ def find_vpc(module, vpc_conn, vpc_id=None, cidr=None): vpc_tags = dict((t.name, t.value) for t in vpc_conn.get_all_tags(filters={'resource-id': vpc.id})) # If the supplied list of ID Tags match a subset of the VPC Tags, we found our VPC - if set(vpc_id_tags.items()).issubset(set(vpc_tags.items())): + if set(resource_tags.items()).issubset(set(vpc_tags.items())): found_vpcs.append(vpc) found_vpc = None @@ -531,7 +531,7 @@ def main(): dns_hostnames = dict(choices=BOOLEANS, default=True), subnets = dict(type='list'), vpc_id = dict(), - vpc_id_tags = dict(type='dict'), + resource_tags = dict(type='dict'), internet_gateway = dict(choices=BOOLEANS, default=False), route_tables = dict(type='list'), state = dict(choices=['present', 'absent'], default='present'), From 2de088a3256e3bb68c8d895a89398d39f41a0263 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 5 Mar 2014 11:53:17 -0500 Subject: [PATCH 3/5] Updated module documentation, re: resource_tags --- cloud/ec2_vpc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/ec2_vpc b/cloud/ec2_vpc index 6bb2c7d235c..d470ccdeaf4 100644 --- a/cloud/ec2_vpc +++ b/cloud/ec2_vpc @@ -58,7 +58,7 @@ options: aliases: [] resource_tags: description: - - A list of tags uniquely identifying a VPC in the form of: {Tag1: Value1, Tag2: Value2, ...}. This list works in conjunction with CIDR (cidr_block) and is gnored when VPC id (vpc_id) is specified. + - A dictionary array of resource tags of the form: { tag1: value1, tag2: value2 }. Tags in this list are used in conjunction with CIDR block to uniquely identify a VPC in lieu of vpc_id. Therefore, if CIDR/Tag combination does not exits, a new VPC will be created. VPC tags not on this list will be ignored. required: false default: null aliases: [] From ee6079d5e91d9476c16755be7236932a6d1d4e6d Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 5 Mar 2014 13:51:57 -0500 Subject: [PATCH 4/5] Added code to create tags on the VPC resource --- cloud/ec2_vpc | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/cloud/ec2_vpc b/cloud/ec2_vpc index d470ccdeaf4..35d38517c0d 100644 --- a/cloud/ec2_vpc +++ b/cloud/ec2_vpc @@ -300,7 +300,21 @@ def create_vpc(module, vpc_conn): module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) # Done with base VPC, now change to attributes and features. - + + # Add resource tags + vpc_spec_tags = module.params.get('resource_tags') + vpc_tags = dict((t.name, t.value) for t in vpc_conn.get_all_tags(filters={'resource-id': vpc.id})) + + if not set(vpc_spec_tags.items()).issubset(set(vpc_tags.items())): + new_tags = {} + + for (key, value) in set(vpc_spec_tags.items()): + if (key, value) not in set(vpc_tags.items()): + new_tags[key] = value + + if new_tags: + vpc_conn.create_tags(vpc.id, new_tags) + # boto doesn't appear to have a way to determine the existing # value of the dns attributes, so we just set them. From 007809b0fc644238bd82dfbe49a53592b1b7b47d Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Tue, 11 Mar 2014 13:25:58 -0400 Subject: [PATCH 5/5] Added 'version_added' to the 'resrouce_tags' attribute in DOCUMENTATION section. --- cloud/ec2_vpc | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud/ec2_vpc b/cloud/ec2_vpc index 35d38517c0d..abeb20e3226 100644 --- a/cloud/ec2_vpc +++ b/cloud/ec2_vpc @@ -62,6 +62,7 @@ options: required: false default: null aliases: [] + version_added: "1.6" internet_gateway: description: - Toggle whether there should be an Internet gateway attached to the VPC