diff --git a/library/cloud/ec2_vpc b/library/cloud/ec2_vpc index 9b9fb95a0b..88f44f7f93 100644 --- a/library/cloud/ec2_vpc +++ b/library/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) + # 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: - module.fail_json(msg='EC2 returned more than one VPC, aborting') - - 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':