From 4d35698a304769b5998a2e7d6662d78e083f1c93 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Thu, 13 Nov 2014 19:38:52 -0500 Subject: [PATCH 01/23] Split out route table and subnet functionality from VPC module. --- cloud/amazon/ec2_vpc_route_table.py | 498 ++++++++++++++++++++++++++++ 1 file changed, 498 insertions(+) create mode 100644 cloud/amazon/ec2_vpc_route_table.py diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py new file mode 100644 index 00000000000..92d938a6ff6 --- /dev/null +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -0,0 +1,498 @@ +#!/usr/bin/python +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +DOCUMENTATION = ''' +--- +module: ec2_vpc_route_table +short_description: Configure route tables for AWS virtual private clouds +description: + - Create or removes route tables from AWS virtual private clouds.''' +'''This module has a dependency on python-boto. +version_added: "1.8" +options: + vpc_id: + description: + - "The VPC in which to create the route table." + required: true + route_table_id: + description: + - "The ID of the route table to update or delete." + required: false + default: null + resource_tags: + description: + - 'A dictionary array of resource tags of the form: { tag1: value1,''' +''' tag2: value2 }. Tags in this list are used to uniquely identify route''' +''' tables within a VPC when the route_table_id is not supplied. + required: false + default: null + aliases: [] + version_added: "1.6" + routes: + description: + - List of routes in the route table. Routes are specified''' +''' as dicts containing the keys 'dest' and one of 'gateway_id',''' +''' 'instance_id', 'interface_id', or 'vpc_peering_connection'. + required: true + aliases: [] + subnets: + description: + - An array of subnets to add to this route table. Subnets may either be''' +''' specified by subnet ID or by a CIDR such as '10.0.0.0/24'. + required: true + aliases: [] + wait: + description: + - wait for the VPC to be in state 'available' before returning + required: false + default: "no" + choices: [ "yes", "no" ] + aliases: [] + wait_timeout: + description: + - how long before wait gives up, in seconds + default: 300 + aliases: [] + state: + description: + - Create or terminate the VPC + required: true + default: present + aliases: [] + region: + description: + - region in which the resource exists. + required: false + default: null + aliases: ['aws_region', 'ec2_region'] + aws_secret_key: + description: + - AWS secret key. If not set then the value of the AWS_SECRET_KEY''' +''' environment variable is used. + required: false + default: None + aliases: ['ec2_secret_key', 'secret_key' ] + aws_access_key: + description: + - AWS access key. If not set then the value of the AWS_ACCESS_KEY''' +''' environment variable is used. + required: false + default: None + aliases: ['ec2_access_key', 'access_key' ] + validate_certs: + description: + - When set to "no", SSL certificates will not be validated for boto''' +''' versions >= 2.6.0. + required: false + default: "yes" + choices: ["yes", "no"] + aliases: [] + version_added: "1.5" + +requirements: [ "boto" ] +author: Robert Estelle +''' + +EXAMPLES = ''' +# Note: None of these examples set aws_access_key, aws_secret_key, or region. +# It is assumed that their matching environment variables are set. + +# Basic creation example: +- name: Set up public subnet route table + local_action: + module: ec2_vpc_route_table + vpc_id: vpc-1245678 + region: us-west-1 + resource_tags: + Name: Public + subnets: + - '{{jumpbox_subnet.subnet_id}}' + - '{{frontend_subnet.subnet_id}}' + - '{{vpn_subnet.subnet_id}}' + routes: + - dest: 0.0.0.0/0 + gateway_id: '{{igw.gateway_id}}' + register: public_route_table + +- name: Set up NAT-protected route table + local_action: + module: ec2_vpc_route_table + vpc_id: vpc-1245678 + region: us-west-1 + resource_tags: + - Name: Internal + subnets: + - '{{application_subnet.subnet_id}}' + - '{{database_subnet.subnet_id}}' + - '{{splunk_subnet.subnet_id}}' + routes: + - dest: 0.0.0.0/0 + instance_id: '{{nat.instance_id}}' + register: nat_route_table +''' + + +import sys + +try: + import boto.ec2 + import boto.vpc + from boto.exception import EC2ResponseError +except ImportError: + print "failed=True msg='boto required for this module'" + sys.exit(1) + + +class RouteTableException(Exception): + pass + + +class TagCreationException(RouteTableException): + pass + + +def get_resource_tags(vpc_conn, resource_id): + return {t.name: t.value for t in + vpc_conn.get_all_tags(filters={'resource-id': resource_id})} + + +def dict_diff(old, new): + x = {} + old_keys = set(old.keys()) + new_keys = set(new.keys()) + + for k in old_keys.difference(new_keys): + x[k] = {'old': old[k]} + + for k in new_keys.difference(old_keys): + x[k] = {'new': new[k]} + + for k in new_keys.intersection(old_keys): + if new[k] != old[k]: + x[k] = {'new': new[k], 'old': old[k]} + + return x + + +def tags_match(match_tags, candidate_tags): + return all((k in candidate_tags and candidate_tags[k] == v + for k, v in match_tags.iteritems())) + + +def ensure_tags(vpc_conn, resource_id, tags, add_only, dry_run): + try: + cur_tags = get_resource_tags(vpc_conn, resource_id) + diff = dict_diff(cur_tags, tags) + if not diff: + return {'changed': False, 'tags': cur_tags} + + to_delete = {k: diff[k]['old'] for k in diff if 'new' not in diff[k]} + if to_delete and not add_only: + vpc_conn.delete_tags(resource_id, to_delete, dry_run=dry_run) + + to_add = {k: diff[k]['new'] for k in diff if 'old' not in diff[k]} + if to_add: + vpc_conn.create_tags(resource_id, to_add, dry_run=dry_run) + + latest_tags = get_resource_tags(vpc_conn, resource_id) + return {'changed': True, 'tags': latest_tags} + except EC2ResponseError as e: + raise TagCreationException('Unable to update tags for {0}, error: {1}' + .format(resource_id, e)) + + +def get_route_table_by_id(vpc_conn, vpc_id, route_table_id): + route_tables = vpc_conn.get_all_route_tables( + route_table_ids=[route_table_id], filters={'vpc_id': vpc_id}) + return route_tables[0] if route_tables else None + + +def get_route_table_by_tags(vpc_conn, vpc_id, tags): + route_tables = vpc_conn.get_all_route_tables(filters={'vpc_id': vpc_id}) + for route_table in route_tables: + this_tags = get_resource_tags(vpc_conn, route_table.id) + if tags_match(tags, this_tags): + return route_table + + +def route_spec_matches_route(route_spec, route): + key_attr_map = { + 'destination_cidr_block': 'destination_cidr_block', + 'gateway_id': 'gateway_id', + 'instance_id': 'instance_id', + 'interface_id': 'interface_id', + 'vpc_peering_connection_id': 'vpc_peering_connection_id', + } + for k in key_attr_map.iterkeys(): + if k in route_spec: + if route_spec[k] != getattr(route, k): + return False + return True + + +def rename_key(d, old_key, new_key): + d[new_key] = d[old_key] + del d[old_key] + + +def index_of_matching_route(route_spec, routes_to_match): + for i, route in enumerate(routes_to_match): + if route_spec_matches_route(route_spec, route): + return i + + +def ensure_routes(vpc_conn, route_table, route_specs, check_mode): + routes_to_match = list(route_table.routes) + route_specs_to_create = [] + for route_spec in route_specs: + i = index_of_matching_route(route_spec, routes_to_match) + if i is None: + route_specs_to_create.append(route_spec) + else: + del routes_to_match[i] + routes_to_delete = [r for r in routes_to_match + if r.gateway_id != 'local'] + + changed = routes_to_delete or route_specs_to_create + if check_mode and changed: + return {'changed': True} + elif changed: + for route_spec in route_specs_to_create: + vpc_conn.create_route(route_table.id, **route_spec) + + for route in routes_to_delete: + vpc_conn.delete_route(route_table.id, route.destination_cidr_block) + return {'changed': True} + else: + return {'changed': False} + + +def get_subnet_by_cidr(vpc_conn, vpc_id, cidr): + subnets = vpc_conn.get_all_subnets( + filters={'cidr': cidr, 'vpc_id': vpc_id}) + if len(subnets) != 1: + raise RouteTableException( + 'Subnet with CIDR {0} has {1} matches'.format(cidr, len(subnets)) + ) + return subnets[0] + + +def get_subnet_by_id(vpc_conn, vpc_id, subnet_id): + subnets = vpc_conn.get_all_subnets(filters={'subnet-id': subnet_id}) + if len(subnets) != 1: + raise RouteTableException( + 'Subnet with ID {0} has {1} matches'.format(subnet_id, len(subnets)) + ) + return subnets[0] + + +def ensure_subnet_association(vpc_conn, vpc_id, route_table_id, subnet_id, + check_mode): + route_tables = vpc_conn.get_all_route_tables( + filters={'association.subnet_id': subnet_id, 'vpc_id': vpc_id} + ) + for route_table in route_tables: + if route_table.id is None: + continue + for a in route_table.associations: + if a.subnet_id == subnet_id: + if route_table.id == route_table_id: + return {'changed': False, 'association_id': a.id} + else: + if check_mode: + return {'changed': True} + vpc_conn.disassociate_route_table(a.id) + + association_id = vpc_conn.associate_route_table(route_table_id, subnet_id) + return {'changed': True, 'association_id': association_id} + + +def ensure_subnet_associations(vpc_conn, vpc_id, route_table, subnets, + check_mode): + current_association_ids = [a.id for a in route_table.associations] + new_association_ids = [] + changed = False + for subnet in subnets: + result = ensure_subnet_association( + vpc_conn, vpc_id, route_table.id, subnet.id, check_mode) + changed = changed or result['changed'] + if changed and check_mode: + return {'changed': True} + new_association_ids.append(result['association_id']) + + to_delete = [a_id for a_id in current_association_ids + if a_id not in new_association_ids] + + for a_id in to_delete: + if check_mode: + return {'changed': True} + changed = True + vpc_conn.disassociate_route_table(a_id) + + return {'changed': changed} + + +def ensure_route_table_absent(vpc_conn, vpc_id, route_table_id, resource_tags, + check_mode): + if route_table_id: + route_table = get_route_table_by_id(vpc_conn, vpc_id, route_table_id) + elif resource_tags: + route_table = get_route_table_by_tags(vpc_conn, vpc_id, resource_tags) + else: + raise RouteTableException( + 'must provide route_table_id or resource_tags') + + if route_table is None: + return {'changed': False} + + if check_mode: + return {'changed': True} + + vpc_conn.delete_route_table(route_table.id) + return {'changed': True} + + +def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, + routes, subnets, check_mode): + changed = False + tags_valid = False + if route_table_id: + route_table = get_route_table_by_id(vpc_conn, vpc_id, route_table_id) + elif resource_tags: + route_table = get_route_table_by_tags(vpc_conn, vpc_id, resource_tags) + tags_valid = route_table is not None + else: + raise RouteTableException( + 'must provide route_table_id or resource_tags') + + if check_mode and route_table is None: + return {'changed': True} + + if route_table is None: + try: + route_table = vpc_conn.create_route_table(vpc_id) + except EC2ResponseError as e: + raise RouteTableException( + 'Unable to create route table {0}, error: {1}' + .format(route_table_id or resource_tags, e) + ) + + if not tags_valid and resource_tags is not None: + result = ensure_tags(vpc_conn, route_table.id, resource_tags, + add_only=True, dry_run=check_mode) + changed = changed or result['changed'] + + if routes is not None: + try: + result = ensure_routes(vpc_conn, route_table, routes, check_mode) + changed = changed or result['changed'] + except EC2ResponseError as e: + raise RouteTableException( + 'Unable to ensure routes for route table {0}, error: {1}' + .format(route_table, e) + ) + + if subnets: + associated_subnets = [] + try: + for subnet_name in subnets: + if ('.' in subnet_name) and ('/' in subnet_name): + subnet = get_subnet_by_cidr(vpc_conn, vpc_id, subnet_name) + else: + subnet = get_subnet_by_id(vpc_conn, vpc_id, subnet_name) + associated_subnets.append(subnet) + except EC2ResponseError as e: + raise RouteTableException( + 'Unable to find subnets for route table {0}, error: {1}' + .format(route_table, e) + ) + + try: + result = ensure_subnet_associations( + vpc_conn, vpc_id, route_table, associated_subnets, check_mode) + changed = changed or result['changed'] + except EC2ResponseError as e: + raise RouteTableException( + 'Unable to associate subnets for route table {0}, error: {1}' + .format(route_table, e) + ) + + return { + 'changed': changed, + 'route_table_id': route_table.id, + } + + +def main(): + argument_spec = ec2_argument_spec() + argument_spec.update({ + 'vpc_id': {'required': True}, + 'route_table_id': {'required': False}, + 'resource_tags': {'type': 'dict', 'required': False}, + 'routes': {'type': 'list', 'required': False}, + 'subnets': {'type': 'list', 'required': False}, + 'state': {'choices': ['present', 'absent'], 'default': 'present'}, + }) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module) + if not region: + module.fail_json(msg='Region must be specified') + + try: + vpc_conn = boto.vpc.connect_to_region( + region, + aws_access_key_id=aws_access_key, + aws_secret_access_key=aws_secret_key + ) + except boto.exception.NoAuthHandlerFound as e: + module.fail_json(msg=str(e)) + + vpc_id = module.params.get('vpc_id') + route_table_id = module.params.get('route_table_id') + resource_tags = module.params.get('resource_tags') + + routes = module.params.get('routes') + for route_spec in routes: + rename_key(route_spec, 'dest', 'destination_cidr_block') + + subnets = module.params.get('subnets') + state = module.params.get('state', 'present') + + try: + if state == 'present': + result = ensure_route_table_present( + vpc_conn, vpc_id, route_table_id, resource_tags, + routes, subnets, module.check_mode + ) + elif state == 'absent': + result = ensure_route_table_absent( + vpc_conn, vpc_id, route_table_id, resource_tags, + module.check_mode + ) + except RouteTableException as e: + module.fail_json(msg=str(e)) + + module.exit_json(**result) + +from ansible.module_utils.basic import * # noqa +from ansible.module_utils.ec2 import * # noqa + +if __name__ == '__main__': + main() From e395bb456ec733be7699002082064310eede9224 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Thu, 13 Nov 2014 19:57:15 -0500 Subject: [PATCH 02/23] EC2 subnet/route-table: Simplify tag updating. --- cloud/amazon/ec2_vpc_route_table.py | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index 92d938a6ff6..6536ff29f94 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -169,24 +169,6 @@ def get_resource_tags(vpc_conn, resource_id): vpc_conn.get_all_tags(filters={'resource-id': resource_id})} -def dict_diff(old, new): - x = {} - old_keys = set(old.keys()) - new_keys = set(new.keys()) - - for k in old_keys.difference(new_keys): - x[k] = {'old': old[k]} - - for k in new_keys.difference(old_keys): - x[k] = {'new': new[k]} - - for k in new_keys.intersection(old_keys): - if new[k] != old[k]: - x[k] = {'new': new[k], 'old': old[k]} - - return x - - def tags_match(match_tags, candidate_tags): return all((k in candidate_tags and candidate_tags[k] == v for k, v in match_tags.iteritems())) @@ -195,15 +177,14 @@ def tags_match(match_tags, candidate_tags): def ensure_tags(vpc_conn, resource_id, tags, add_only, dry_run): try: cur_tags = get_resource_tags(vpc_conn, resource_id) - diff = dict_diff(cur_tags, tags) - if not diff: + if tags == cur_tags: return {'changed': False, 'tags': cur_tags} - to_delete = {k: diff[k]['old'] for k in diff if 'new' not in diff[k]} + to_delete = {k: cur_tags[k] for k in cur_tags if k not in tags} if to_delete and not add_only: vpc_conn.delete_tags(resource_id, to_delete, dry_run=dry_run) - to_add = {k: diff[k]['new'] for k in diff if 'old' not in diff[k]} + to_add = {k: tags[k] for k in tags if k not in cur_tags} if to_add: vpc_conn.create_tags(resource_id, to_add, dry_run=dry_run) From 60efbe8beccf1768e693788d2698c766e0129450 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Mon, 1 Dec 2014 13:41:03 -0500 Subject: [PATCH 03/23] ec2_vpc - VPCException -> AnsibleVPCException --- cloud/amazon/ec2_vpc_route_table.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index 6536ff29f94..0f6184c40d4 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -156,11 +156,11 @@ except ImportError: sys.exit(1) -class RouteTableException(Exception): +class AnsibleRouteTableException(Exception): pass -class TagCreationException(RouteTableException): +class AnsibleTagCreationException(AnsibleRouteTableException): pass @@ -191,8 +191,8 @@ def ensure_tags(vpc_conn, resource_id, tags, add_only, dry_run): latest_tags = get_resource_tags(vpc_conn, resource_id) return {'changed': True, 'tags': latest_tags} except EC2ResponseError as e: - raise TagCreationException('Unable to update tags for {0}, error: {1}' - .format(resource_id, e)) + raise AnsibleTagCreationException( + 'Unable to update tags for {0}, error: {1}'.format(resource_id, e)) def get_route_table_by_id(vpc_conn, vpc_id, route_table_id): @@ -265,7 +265,7 @@ def get_subnet_by_cidr(vpc_conn, vpc_id, cidr): subnets = vpc_conn.get_all_subnets( filters={'cidr': cidr, 'vpc_id': vpc_id}) if len(subnets) != 1: - raise RouteTableException( + raise AnsibleRouteTableException( 'Subnet with CIDR {0} has {1} matches'.format(cidr, len(subnets)) ) return subnets[0] @@ -274,8 +274,9 @@ def get_subnet_by_cidr(vpc_conn, vpc_id, cidr): def get_subnet_by_id(vpc_conn, vpc_id, subnet_id): subnets = vpc_conn.get_all_subnets(filters={'subnet-id': subnet_id}) if len(subnets) != 1: - raise RouteTableException( - 'Subnet with ID {0} has {1} matches'.format(subnet_id, len(subnets)) + raise AnsibleRouteTableException( + 'Subnet with ID {0} has {1} matches'.format( + subnet_id, len(subnets)) ) return subnets[0] @@ -333,7 +334,7 @@ def ensure_route_table_absent(vpc_conn, vpc_id, route_table_id, resource_tags, elif resource_tags: route_table = get_route_table_by_tags(vpc_conn, vpc_id, resource_tags) else: - raise RouteTableException( + raise AnsibleRouteTableException( 'must provide route_table_id or resource_tags') if route_table is None: @@ -356,7 +357,7 @@ def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, route_table = get_route_table_by_tags(vpc_conn, vpc_id, resource_tags) tags_valid = route_table is not None else: - raise RouteTableException( + raise AnsibleRouteTableException( 'must provide route_table_id or resource_tags') if check_mode and route_table is None: @@ -366,7 +367,7 @@ def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, try: route_table = vpc_conn.create_route_table(vpc_id) except EC2ResponseError as e: - raise RouteTableException( + raise AnsibleRouteTableException( 'Unable to create route table {0}, error: {1}' .format(route_table_id or resource_tags, e) ) @@ -381,7 +382,7 @@ def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, result = ensure_routes(vpc_conn, route_table, routes, check_mode) changed = changed or result['changed'] except EC2ResponseError as e: - raise RouteTableException( + raise AnsibleRouteTableException( 'Unable to ensure routes for route table {0}, error: {1}' .format(route_table, e) ) @@ -396,7 +397,7 @@ def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, subnet = get_subnet_by_id(vpc_conn, vpc_id, subnet_name) associated_subnets.append(subnet) except EC2ResponseError as e: - raise RouteTableException( + raise AnsibleRouteTableException( 'Unable to find subnets for route table {0}, error: {1}' .format(route_table, e) ) @@ -406,7 +407,7 @@ def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, vpc_conn, vpc_id, route_table, associated_subnets, check_mode) changed = changed or result['changed'] except EC2ResponseError as e: - raise RouteTableException( + raise AnsibleRouteTableException( 'Unable to associate subnets for route table {0}, error: {1}' .format(route_table, e) ) @@ -467,7 +468,7 @@ def main(): vpc_conn, vpc_id, route_table_id, resource_tags, module.check_mode ) - except RouteTableException as e: + except AnsibleRouteTableException as e: module.fail_json(msg=str(e)) module.exit_json(**result) From 95006afe8cf244c462c55c649989f85e606bc79b Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Mon, 1 Dec 2014 13:45:50 -0500 Subject: [PATCH 04/23] ec2_vpc - Fail module using fail_json on boto import failure. --- cloud/amazon/ec2_vpc_route_table.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index 0f6184c40d4..56d3c16c9ec 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -145,15 +145,17 @@ EXAMPLES = ''' ''' -import sys +import sys # noqa try: import boto.ec2 import boto.vpc from boto.exception import EC2ResponseError + HAS_BOTO = True except ImportError: - print "failed=True msg='boto required for this module'" - sys.exit(1) + HAS_BOTO = False + if __name__ != '__main__': + raise class AnsibleRouteTableException(Exception): @@ -432,6 +434,8 @@ def main(): argument_spec=argument_spec, supports_check_mode=True, ) + if not HAS_BOTO: + module.fail_json(msg='boto is required for this module') ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module) if not region: From a50f5cac2cefb0151981f78b7dfac5a2d80ca19a Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Mon, 1 Dec 2014 14:28:28 -0500 Subject: [PATCH 05/23] ec2_vpc - More efficient tag search. --- cloud/amazon/ec2_vpc_route_table.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index 56d3c16c9ec..e79b1b10ee4 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -171,11 +171,6 @@ def get_resource_tags(vpc_conn, resource_id): vpc_conn.get_all_tags(filters={'resource-id': resource_id})} -def tags_match(match_tags, candidate_tags): - return all((k in candidate_tags and candidate_tags[k] == v - for k, v in match_tags.iteritems())) - - def ensure_tags(vpc_conn, resource_id, tags, add_only, dry_run): try: cur_tags = get_resource_tags(vpc_conn, resource_id) @@ -204,11 +199,18 @@ def get_route_table_by_id(vpc_conn, vpc_id, route_table_id): def get_route_table_by_tags(vpc_conn, vpc_id, tags): - route_tables = vpc_conn.get_all_route_tables(filters={'vpc_id': vpc_id}) - for route_table in route_tables: - this_tags = get_resource_tags(vpc_conn, route_table.id) - if tags_match(tags, this_tags): - return route_table + filters = {'vpc_id': vpc_id} + filters.update({'tag:{}'.format(t): v + for t, v in tags.iteritems()}) + route_tables = vpc_conn.get_all_route_tables(filters=filters) + + if not route_tables: + return None + elif len(route_tables) == 1: + return route_tables[0] + + raise RouteTableException( + 'Found more than one route table based on the supplied tags, aborting') def route_spec_matches_route(route_spec, route): From 0e635dd0907731ffcd4a6962e56ea7295e2482df Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Mon, 1 Dec 2014 14:50:38 -0500 Subject: [PATCH 06/23] ec2_vpc - Update some documentation strings. --- cloud/amazon/ec2_vpc_route_table.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index e79b1b10ee4..60a87ae2430 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -50,8 +50,8 @@ options: aliases: [] subnets: description: - - An array of subnets to add to this route table. Subnets may either be''' -''' specified by subnet ID or by a CIDR such as '10.0.0.0/24'. + - An array of subnets to add to this route table. Subnets may either''' +''' be specified by subnet ID or by a CIDR such as '10.0.0.0/24'. required: true aliases: [] wait: From e3c14c1b021324f84166f8db7286b2447921ddfe Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Mon, 1 Dec 2014 15:00:14 -0500 Subject: [PATCH 07/23] ec2_vpc - Update dict comprehensions and {} formats for python2.6 --- cloud/amazon/ec2_vpc_route_table.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index 60a87ae2430..f12255e7771 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -200,8 +200,8 @@ def get_route_table_by_id(vpc_conn, vpc_id, route_table_id): def get_route_table_by_tags(vpc_conn, vpc_id, tags): filters = {'vpc_id': vpc_id} - filters.update({'tag:{}'.format(t): v - for t, v in tags.iteritems()}) + filters.update(dict((('tag:{0}'.format(t), v) + for t, v in tags.iteritems()))) route_tables = vpc_conn.get_all_route_tables(filters=filters) if not route_tables: From f79aeaee86d9686c09564a47cd4bf8ec9f04087a Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Mon, 1 Dec 2014 15:18:56 -0500 Subject: [PATCH 08/23] ec2_vpc - More dry running in check mode. --- cloud/amazon/ec2_vpc_route_table.py | 33 ++++++++++++----------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index f12255e7771..7f340359077 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -171,7 +171,7 @@ def get_resource_tags(vpc_conn, resource_id): vpc_conn.get_all_tags(filters={'resource-id': resource_id})} -def ensure_tags(vpc_conn, resource_id, tags, add_only, dry_run): +def ensure_tags(vpc_conn, resource_id, tags, add_only, check_mode): try: cur_tags = get_resource_tags(vpc_conn, resource_id) if tags == cur_tags: @@ -179,11 +179,11 @@ def ensure_tags(vpc_conn, resource_id, tags, add_only, dry_run): to_delete = {k: cur_tags[k] for k in cur_tags if k not in tags} if to_delete and not add_only: - vpc_conn.delete_tags(resource_id, to_delete, dry_run=dry_run) + vpc_conn.delete_tags(resource_id, to_delete, dry_run=check_mode) to_add = {k: tags[k] for k in tags if k not in cur_tags} if to_add: - vpc_conn.create_tags(resource_id, to_add, dry_run=dry_run) + vpc_conn.create_tags(resource_id, to_add, dry_run=check_mode) latest_tags = get_resource_tags(vpc_conn, resource_id) return {'changed': True, 'tags': latest_tags} @@ -252,17 +252,17 @@ def ensure_routes(vpc_conn, route_table, route_specs, check_mode): if r.gateway_id != 'local'] changed = routes_to_delete or route_specs_to_create - if check_mode and changed: - return {'changed': True} - elif changed: + if changed: for route_spec in route_specs_to_create: - vpc_conn.create_route(route_table.id, **route_spec) + vpc_conn.create_route(route_table.id, + dry_run=check_mode, + **route_spec) for route in routes_to_delete: - vpc_conn.delete_route(route_table.id, route.destination_cidr_block) - return {'changed': True} - else: - return {'changed': False} + vpc_conn.delete_route(route_table.id, + route.destination_cidr_block, + dry_run=check_mode) + return {'changed': changed} def get_subnet_by_cidr(vpc_conn, vpc_id, cidr): @@ -323,10 +323,8 @@ def ensure_subnet_associations(vpc_conn, vpc_id, route_table, subnets, if a_id not in new_association_ids] for a_id in to_delete: - if check_mode: - return {'changed': True} changed = True - vpc_conn.disassociate_route_table(a_id) + vpc_conn.disassociate_route_table(a_id, dry_run=check_mode) return {'changed': changed} @@ -344,10 +342,7 @@ def ensure_route_table_absent(vpc_conn, vpc_id, route_table_id, resource_tags, if route_table is None: return {'changed': False} - if check_mode: - return {'changed': True} - - vpc_conn.delete_route_table(route_table.id) + vpc_conn.delete_route_table(route_table.id, dry_run=check_mode) return {'changed': True} @@ -378,7 +373,7 @@ def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, if not tags_valid and resource_tags is not None: result = ensure_tags(vpc_conn, route_table.id, resource_tags, - add_only=True, dry_run=check_mode) + add_only=True, check_mode=check_mode) changed = changed or result['changed'] if routes is not None: From f4ce0dbc96b72a24c0527ccf3af37110fbfaf7de Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Mon, 1 Dec 2014 15:56:04 -0500 Subject: [PATCH 09/23] ec2_vpc_route_table - Support route propagation through VGW. Based on work by Bret Martin via pull request #356 --- cloud/amazon/ec2_vpc_route_table.py | 36 +++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index 7f340359077..fc736ba451c 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -54,6 +54,11 @@ options: ''' be specified by subnet ID or by a CIDR such as '10.0.0.0/24'. required: true aliases: [] + propagating_vgw_ids: + description: + - Enables route propagation from virtual gateways specified by ID. + required: false + aliases: [] wait: description: - wait for the VPC to be in state 'available' before returning @@ -329,6 +334,24 @@ def ensure_subnet_associations(vpc_conn, vpc_id, route_table, subnets, return {'changed': changed} +def ensure_propagation(vpc_conn, route_table_id, propagating_vgw_ids, + check_mode): + + # NOTE: As of boto==2.15.0, it is not yet possible to query the existing + # propagating gateways. However, EC2 does support this as evidenced by + # the describe-route-tables tool. For now, just enable the given VGWs + # and do not disable any others. + changed = False + for vgw_id in propagating_vgw_ids: + if vgw_id not in original_association_ids: + changed = True + vpc_conn.enable_vgw_route_propagation(route_table_id, + vgw_id, + test_run=check_mode) + + return {'changed': changed} + + def ensure_route_table_absent(vpc_conn, vpc_id, route_table_id, resource_tags, check_mode): if route_table_id: @@ -347,7 +370,8 @@ def ensure_route_table_absent(vpc_conn, vpc_id, route_table_id, resource_tags, def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, - routes, subnets, check_mode): + routes, subnets, propagating_vgw_ids, + check_mode): changed = False tags_valid = False if route_table_id: @@ -371,6 +395,12 @@ def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, .format(route_table_id or resource_tags, e) ) + if propagating_vgw_ids is not None: + result = ensure_propagation(vpc_conn, route_table_id, + propagating_vgw_ids, + check_mode=check_mode) + changed = changed or result['changed'] + if not tags_valid and resource_tags is not None: result = ensure_tags(vpc_conn, route_table.id, resource_tags, add_only=True, check_mode=check_mode) @@ -422,6 +452,7 @@ def main(): argument_spec.update({ 'vpc_id': {'required': True}, 'route_table_id': {'required': False}, + 'propagating_vgw_ids': {'type': 'list', 'required': False}, 'resource_tags': {'type': 'dict', 'required': False}, 'routes': {'type': 'list', 'required': False}, 'subnets': {'type': 'list', 'required': False}, @@ -450,6 +481,7 @@ def main(): vpc_id = module.params.get('vpc_id') route_table_id = module.params.get('route_table_id') resource_tags = module.params.get('resource_tags') + propagating_vgw_ids = module.params.get('propagating_vgw_ids', []) routes = module.params.get('routes') for route_spec in routes: @@ -462,7 +494,7 @@ def main(): if state == 'present': result = ensure_route_table_present( vpc_conn, vpc_id, route_table_id, resource_tags, - routes, subnets, module.check_mode + routes, subnets, propagating_vgw_ids, module.check_mode ) elif state == 'absent': result = ensure_route_table_absent( From f0a4be1b4bce41ece9e4ab033fc2cbebc444f2b8 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Wed, 3 Dec 2014 13:01:44 -0500 Subject: [PATCH 10/23] ec2_vpc_route_table - Fix unintended tag search regression. --- cloud/amazon/ec2_vpc_route_table.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index fc736ba451c..b6fda27b703 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -176,6 +176,11 @@ def get_resource_tags(vpc_conn, resource_id): vpc_conn.get_all_tags(filters={'resource-id': resource_id})} +def tags_match(match_tags, candidate_tags): + return all((k in candidate_tags and candidate_tags[k] == v + for k, v in match_tags.iteritems())) + + def ensure_tags(vpc_conn, resource_id, tags, add_only, check_mode): try: cur_tags = get_resource_tags(vpc_conn, resource_id) @@ -204,18 +209,11 @@ def get_route_table_by_id(vpc_conn, vpc_id, route_table_id): def get_route_table_by_tags(vpc_conn, vpc_id, tags): - filters = {'vpc_id': vpc_id} - filters.update(dict((('tag:{0}'.format(t), v) - for t, v in tags.iteritems()))) - route_tables = vpc_conn.get_all_route_tables(filters=filters) - - if not route_tables: - return None - elif len(route_tables) == 1: - return route_tables[0] - - raise RouteTableException( - 'Found more than one route table based on the supplied tags, aborting') + route_tables = vpc_conn.get_all_route_tables(filters={'vpc_id': vpc_id}) + for route_table in route_tables: + this_tags = get_resource_tags(vpc_conn, route_table.id) + if tags_match(tags, this_tags): + return route_table def route_spec_matches_route(route_spec, route): From 17ed722d556a4a24a3dadfd37c2833f57287d1c5 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Thu, 4 Dec 2014 22:10:02 -0500 Subject: [PATCH 11/23] ec2_vpc_route_tables - Remove more dict comprehensions. --- cloud/amazon/ec2_vpc_route_table.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index b6fda27b703..af28ef341cc 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -172,8 +172,8 @@ class AnsibleTagCreationException(AnsibleRouteTableException): def get_resource_tags(vpc_conn, resource_id): - return {t.name: t.value for t in - vpc_conn.get_all_tags(filters={'resource-id': resource_id})} + return dict((t.name, t.value) for t in + vpc_conn.get_all_tags(filters={'resource-id': resource_id})) def tags_match(match_tags, candidate_tags): @@ -187,11 +187,11 @@ def ensure_tags(vpc_conn, resource_id, tags, add_only, check_mode): if tags == cur_tags: return {'changed': False, 'tags': cur_tags} - to_delete = {k: cur_tags[k] for k in cur_tags if k not in tags} + to_delete = dict((k, cur_tags[k]) for k in cur_tags if k not in tags) if to_delete and not add_only: vpc_conn.delete_tags(resource_id, to_delete, dry_run=check_mode) - to_add = {k: tags[k] for k in tags if k not in cur_tags} + to_add = dict((k, tags[k]) for k in tags if k not in cur_tags) if to_add: vpc_conn.create_tags(resource_id, to_add, dry_run=check_mode) From 43566b0cafd5f3b6979a4cd182ee3505717a9a71 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Thu, 4 Dec 2014 22:10:49 -0500 Subject: [PATCH 12/23] ec2_vpc_route_tables - Allow reference to subnets by id, name, or cidr. --- cloud/amazon/ec2_vpc_route_table.py | 98 ++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index af28ef341cc..491751e23dd 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -51,7 +51,7 @@ options: subnets: description: - An array of subnets to add to this route table. Subnets may either''' -''' be specified by subnet ID or by a CIDR such as '10.0.0.0/24'. +''' be specified by subnet ID, Name tag, or by a CIDR such as '10.0.0.0/24'. required: true aliases: [] propagating_vgw_ids: @@ -141,8 +141,8 @@ EXAMPLES = ''' - Name: Internal subnets: - '{{application_subnet.subnet_id}}' - - '{{database_subnet.subnet_id}}' - - '{{splunk_subnet.subnet_id}}' + - 'Database Subnet' + - '10.0.0.0/8' routes: - dest: 0.0.0.0/0 instance_id: '{{nat.instance_id}}' @@ -151,6 +151,7 @@ EXAMPLES = ''' import sys # noqa +import re try: import boto.ec2 @@ -171,6 +172,70 @@ class AnsibleTagCreationException(AnsibleRouteTableException): pass +class AnsibleSubnetSearchException(AnsibleRouteTableException): + pass + +CIDR_RE = re.compile('^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$') +SUBNET_RE = re.compile('^subnet-[A-z0-9]+$') +ROUTE_TABLE_RE = re.compile('^rtb-[A-z0-9]+$') + + +def find_subnets(vpc_conn, vpc_id, identified_subnets): + """ + Finds a list of subnets, each identified either by a raw ID, a unique + 'Name' tag, or a CIDR such as 10.0.0.0/8. + + Note that this function is duplicated in other ec2 modules, and should + potentially be moved into potentially be moved into a shared module_utils + """ + subnet_ids = [] + subnet_names = [] + subnet_cidrs = [] + for subnet in (identified_subnets or []): + if re.match(SUBNET_RE, subnet): + subnet_ids.append(subnet) + elif re.match(CIDR_RE, subnet): + subnet_cidrs.append(subnet) + else: + subnet_names.append(subnet) + + subnets_by_id = [] + if subnet_ids: + subnets_by_id = vpc_conn.get_all_subnets( + subnet_ids, filters={'vpc_id': vpc_id}) + + for subnet_id in subnet_ids: + if not any(s.id == subnet_id for s in subnets_by_id): + raise AnsibleSubnetSearchException( + 'Subnet ID "{0}" does not exist'.format(subnet_id)) + + subnets_by_cidr = [] + if subnet_cidrs: + subnets_by_cidr = vpc_conn.get_all_subnets( + filters={'vpc_id': vpc_id, 'cidr': subnet_cidrs}) + + for cidr in subnet_cidrs: + if not any(s.cidr_block == cidr for s in subnets_by_cidr): + raise AnsibleSubnetSearchException( + 'Subnet CIDR "{0}" does not exist'.format(subnet_cidr)) + + subnets_by_name = [] + if subnet_names: + subnets_by_name = vpc_conn.get_all_subnets( + filters={'vpc_id': vpc_id, 'tag:Name': subnet_names}) + + for name in subnet_names: + matching = [s.tags.get('Name') == name for s in subnets_by_name] + if len(matching) == 0: + raise AnsibleSubnetSearchException( + 'Subnet named "{0}" does not exist'.format(name)) + elif len(matching) > 1: + raise AnsibleSubnetSearchException( + 'Multiple subnets named "{0}"'.format(name)) + + return subnets_by_id + subnets_by_cidr + subnets_by_name + + def get_resource_tags(vpc_conn, resource_id): return dict((t.name, t.value) for t in vpc_conn.get_all_tags(filters={'resource-id': resource_id})) @@ -268,26 +333,6 @@ def ensure_routes(vpc_conn, route_table, route_specs, check_mode): return {'changed': changed} -def get_subnet_by_cidr(vpc_conn, vpc_id, cidr): - subnets = vpc_conn.get_all_subnets( - filters={'cidr': cidr, 'vpc_id': vpc_id}) - if len(subnets) != 1: - raise AnsibleRouteTableException( - 'Subnet with CIDR {0} has {1} matches'.format(cidr, len(subnets)) - ) - return subnets[0] - - -def get_subnet_by_id(vpc_conn, vpc_id, subnet_id): - subnets = vpc_conn.get_all_subnets(filters={'subnet-id': subnet_id}) - if len(subnets) != 1: - raise AnsibleRouteTableException( - 'Subnet with ID {0} has {1} matches'.format( - subnet_id, len(subnets)) - ) - return subnets[0] - - def ensure_subnet_association(vpc_conn, vpc_id, route_table_id, subnet_id, check_mode): route_tables = vpc_conn.get_all_route_tables( @@ -417,12 +462,7 @@ def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, if subnets: associated_subnets = [] try: - for subnet_name in subnets: - if ('.' in subnet_name) and ('/' in subnet_name): - subnet = get_subnet_by_cidr(vpc_conn, vpc_id, subnet_name) - else: - subnet = get_subnet_by_id(vpc_conn, vpc_id, subnet_name) - associated_subnets.append(subnet) + associated_subnets = find_subnets(vpc_conn, vpc_id, subnets) except EC2ResponseError as e: raise AnsibleRouteTableException( 'Unable to find subnets for route table {0}, error: {1}' From c9883db03d668c8b5eeff1204c3602c3006fd905 Mon Sep 17 00:00:00 2001 From: Herby Gillot Date: Thu, 11 Jun 2015 14:01:40 +1000 Subject: [PATCH 13/23] Allow VPC igw to be specified by gateway_id: "igw" --- cloud/amazon/ec2_vpc_route_table.py | 36 ++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index 491751e23dd..dc21d9607e1 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -45,7 +45,9 @@ options: description: - List of routes in the route table. Routes are specified''' ''' as dicts containing the keys 'dest' and one of 'gateway_id',''' -''' 'instance_id', 'interface_id', or 'vpc_peering_connection'. +''' 'instance_id', 'interface_id', or 'vpc_peering_connection'. ''' +''' If 'gateway_id' is specified, you can refer to the VPC's IGW ''' +''' by using the value "igw". required: true aliases: [] subnets: @@ -168,6 +170,10 @@ class AnsibleRouteTableException(Exception): pass +class AnsibleIgwSearchException(AnsibleRouteTableException): + pass + + class AnsibleTagCreationException(AnsibleRouteTableException): pass @@ -236,6 +242,29 @@ def find_subnets(vpc_conn, vpc_id, identified_subnets): return subnets_by_id + subnets_by_cidr + subnets_by_name +def find_igw(vpc_conn, vpc_id): + """ + Finds the Internet gateway for the given VPC ID. + + Raises an AnsibleIgwSearchException if either no IGW can be found, or more + than one found for the given VPC. + + Note that this function is duplicated in other ec2 modules, and should + potentially be moved into potentially be moved into a shared module_utils + """ + igw = vpc_conn.get_all_internet_gateways( + filters={'attachment.vpc-id': vpc_id}) + + if not igw: + return AnsibleIgwSearchException('No IGW found for VPC "{0}"'. + format(vpc_id)) + elif len(igw) == 1: + return igw[0].id + else: + raise AnsibleIgwSearchException('Multiple IGWs found for VPC "{0}"'. + format(vpc_id)) + + def get_resource_tags(vpc_conn, resource_id): return dict((t.name, t.value) for t in vpc_conn.get_all_tags(filters={'resource-id': resource_id})) @@ -525,6 +554,11 @@ def main(): for route_spec in routes: rename_key(route_spec, 'dest', 'destination_cidr_block') + if 'gateway_id' in route_spec and route_spec['gateway_id'] and \ + route_spec['gateway_id'].lower() == 'igw': + igw = find_igw(vpc_conn, vpc_id) + route_spec['gateway_id'] = igw + subnets = module.params.get('subnets') state = module.params.get('state', 'present') From 4f2cd7cb6e0ae12a578bc5768846272474c71f46 Mon Sep 17 00:00:00 2001 From: whiter Date: Thu, 11 Jun 2015 14:07:04 +1000 Subject: [PATCH 14/23] Documentation update --- cloud/amazon/ec2_vpc_route_table.py | 87 +++++++---------------------- 1 file changed, 21 insertions(+), 66 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index dc21d9607e1..677d2ea3383 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -1,121 +1,75 @@ #!/usr/bin/python -# This file is part of Ansible # -# Ansible is free software: you can redistribute it and/or modify +# This is a free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# Ansible is distributed in the hope that it will be useful, +# This Ansible library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . +# along with this library. If not, see . DOCUMENTATION = ''' --- module: ec2_vpc_route_table -short_description: Configure route tables for AWS virtual private clouds +short_description: Manage route tables for AWS virtual private clouds description: - - Create or removes route tables from AWS virtual private clouds.''' -'''This module has a dependency on python-boto. -version_added: "1.8" + - Manage route tables for AWS virtual private clouds +version_added: "2.0" +author: Robert Estelle, @erydo options: vpc_id: description: - - "The VPC in which to create the route table." + - VPC ID of the VPC in which to create the route table. required: true route_table_id: description: - - "The ID of the route table to update or delete." + - The ID of the route table to update or delete. required: false default: null resource_tags: description: - - 'A dictionary array of resource tags of the form: { tag1: value1,''' -''' tag2: value2 }. Tags in this list are used to uniquely identify route''' -''' tables within a VPC when the route_table_id is not supplied. + - A dictionary array of resource tags of the form: { tag1: value1, tag2: value2 }. Tags in this list are used to uniquely identify route tables within a VPC when the route_table_id is not supplied. required: false default: null - aliases: [] - version_added: "1.6" routes: description: - - List of routes in the route table. Routes are specified''' -''' as dicts containing the keys 'dest' and one of 'gateway_id',''' -''' 'instance_id', 'interface_id', or 'vpc_peering_connection'. ''' -''' If 'gateway_id' is specified, you can refer to the VPC's IGW ''' -''' by using the value "igw". + - List of routes in the route table. Routes are specified as dicts containing the keys 'dest' and one of 'gateway_id', 'instance_id', 'interface_id', or 'vpc_peering_connection'. If 'gateway_id' is specified, you can refer to the VPC's IGW by using the value 'igw'. required: true aliases: [] subnets: description: - - An array of subnets to add to this route table. Subnets may either''' -''' be specified by subnet ID, Name tag, or by a CIDR such as '10.0.0.0/24'. + - An array of subnets to add to this route table. Subnets may be specified by either subnet ID, Name tag, or by a CIDR such as '10.0.0.0/24'. required: true - aliases: [] propagating_vgw_ids: description: - - Enables route propagation from virtual gateways specified by ID. + - Enable route propagation from virtual gateways specified by ID. required: false - aliases: [] wait: description: - - wait for the VPC to be in state 'available' before returning + - Wait for the VPC to be in state 'available' before returning. required: false default: "no" choices: [ "yes", "no" ] - aliases: [] wait_timeout: description: - - how long before wait gives up, in seconds + - How long before wait gives up, in seconds. default: 300 - aliases: [] state: description: - - Create or terminate the VPC - required: true - default: present - aliases: [] - region: - description: - - region in which the resource exists. + - Create or destroy the VPC route table required: false - default: null - aliases: ['aws_region', 'ec2_region'] - aws_secret_key: - description: - - AWS secret key. If not set then the value of the AWS_SECRET_KEY''' -''' environment variable is used. - required: false - default: None - aliases: ['ec2_secret_key', 'secret_key' ] - aws_access_key: - description: - - AWS access key. If not set then the value of the AWS_ACCESS_KEY''' -''' environment variable is used. - required: false - default: None - aliases: ['ec2_access_key', 'access_key' ] - validate_certs: - description: - - When set to "no", SSL certificates will not be validated for boto''' -''' versions >= 2.6.0. - required: false - default: "yes" - choices: ["yes", "no"] - aliases: [] - version_added: "1.5" - -requirements: [ "boto" ] -author: Robert Estelle + default: present + choices: [ 'present', 'absent' ] +extends_documentation_fragment: aws ''' EXAMPLES = ''' -# Note: None of these examples set aws_access_key, aws_secret_key, or region. -# It is assumed that their matching environment variables are set. +# Note: These examples do not set authentication details, see the AWS Guide for details. # Basic creation example: - name: Set up public subnet route table @@ -583,3 +537,4 @@ from ansible.module_utils.ec2 import * # noqa if __name__ == '__main__': main() + \ No newline at end of file From 3527aec2c5209f10b90827dc7f697cdaffeb2d63 Mon Sep 17 00:00:00 2001 From: whiter Date: Fri, 19 Jun 2015 09:56:50 +1000 Subject: [PATCH 15/23] Changed to use "connect_to_aws" method --- cloud/amazon/ec2_vpc_route_table.py | 54 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index 677d2ea3383..b0fa9cbc426 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -20,7 +20,7 @@ short_description: Manage route tables for AWS virtual private clouds description: - Manage route tables for AWS virtual private clouds version_added: "2.0" -author: Robert Estelle, @erydo +author: Robert Estelle (@erydo) options: vpc_id: description: @@ -470,34 +470,32 @@ def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, def main(): argument_spec = ec2_argument_spec() - argument_spec.update({ - 'vpc_id': {'required': True}, - 'route_table_id': {'required': False}, - 'propagating_vgw_ids': {'type': 'list', 'required': False}, - 'resource_tags': {'type': 'dict', 'required': False}, - 'routes': {'type': 'list', 'required': False}, - 'subnets': {'type': 'list', 'required': False}, - 'state': {'choices': ['present', 'absent'], 'default': 'present'}, - }) - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, + argument_spec.update( + dict( + vpc_id = dict(default=None, required=True), + route_table_id = dict(default=None, required=False), + propagating_vgw_ids = dict(default=None, required=False, type='list'), + resource_tags = dict(default=None, required=False, type='dict'), + routes = dict(default=None, required=False, type='list'), + subnets = dict(default=None, required=False, type='list'), + state = dict(default='present', choices=['present', 'absent']) + ) ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + if not HAS_BOTO: module.fail_json(msg='boto is required for this module') - ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module) - if not region: - module.fail_json(msg='Region must be specified') - - try: - vpc_conn = boto.vpc.connect_to_region( - region, - aws_access_key_id=aws_access_key, - aws_secret_access_key=aws_secret_key - ) - except boto.exception.NoAuthHandlerFound as e: - module.fail_json(msg=str(e)) + region, ec2_url, aws_connect_params = get_aws_connection_info(module) + + if region: + try: + connection = connect_to_aws(boto.vpc, region, **aws_connect_params) + except (boto.exception.NoAuthHandlerFound, StandardError), e: + module.fail_json(msg=str(e)) + else: + module.fail_json(msg="region must be specified") vpc_id = module.params.get('vpc_id') route_table_id = module.params.get('route_table_id') @@ -510,7 +508,7 @@ def main(): if 'gateway_id' in route_spec and route_spec['gateway_id'] and \ route_spec['gateway_id'].lower() == 'igw': - igw = find_igw(vpc_conn, vpc_id) + igw = find_igw(connection, vpc_id) route_spec['gateway_id'] = igw subnets = module.params.get('subnets') @@ -519,12 +517,12 @@ def main(): try: if state == 'present': result = ensure_route_table_present( - vpc_conn, vpc_id, route_table_id, resource_tags, + connection, vpc_id, route_table_id, resource_tags, routes, subnets, propagating_vgw_ids, module.check_mode ) elif state == 'absent': result = ensure_route_table_absent( - vpc_conn, vpc_id, route_table_id, resource_tags, + connection, vpc_id, route_table_id, resource_tags, module.check_mode ) except AnsibleRouteTableException as e: From 3e02c0d3d940c3e7bb0e4c0c3128cf8ffacb6dad Mon Sep 17 00:00:00 2001 From: Rob White Date: Tue, 28 Jul 2015 21:39:09 +1000 Subject: [PATCH 16/23] Blank aliases removed --- cloud/amazon/ec2_vpc_route_table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index b0fa9cbc426..6b3efa3286a 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -40,7 +40,6 @@ options: description: - List of routes in the route table. Routes are specified as dicts containing the keys 'dest' and one of 'gateway_id', 'instance_id', 'interface_id', or 'vpc_peering_connection'. If 'gateway_id' is specified, you can refer to the VPC's IGW by using the value 'igw'. required: true - aliases: [] subnets: description: - An array of subnets to add to this route table. Subnets may be specified by either subnet ID, Name tag, or by a CIDR such as '10.0.0.0/24'. @@ -65,6 +64,7 @@ options: required: false default: present choices: [ 'present', 'absent' ] + extends_documentation_fragment: aws ''' From 546858cec9afbbde1862b9ac4e40da88a1067dc1 Mon Sep 17 00:00:00 2001 From: Bret Martin Date: Mon, 10 Aug 2015 14:30:09 -0400 Subject: [PATCH 17/23] Correct enable_vgw_route_propagation test_run parameter to dry_run --- cloud/amazon/ec2_vpc_route_table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index 6b3efa3286a..53164e254d9 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -373,7 +373,7 @@ def ensure_propagation(vpc_conn, route_table_id, propagating_vgw_ids, changed = True vpc_conn.enable_vgw_route_propagation(route_table_id, vgw_id, - test_run=check_mode) + dry_run=check_mode) return {'changed': changed} From 954f48f28aed4be47634613ccb0a051ef7ab9874 Mon Sep 17 00:00:00 2001 From: Bret Martin Date: Mon, 10 Aug 2015 14:31:46 -0400 Subject: [PATCH 18/23] Don't check original_association_ids since it is not set, per comment above --- cloud/amazon/ec2_vpc_route_table.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index 53164e254d9..bb530cb0e9e 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -369,11 +369,10 @@ def ensure_propagation(vpc_conn, route_table_id, propagating_vgw_ids, # and do not disable any others. changed = False for vgw_id in propagating_vgw_ids: - if vgw_id not in original_association_ids: - changed = True - vpc_conn.enable_vgw_route_propagation(route_table_id, - vgw_id, - dry_run=check_mode) + changed = True + vpc_conn.enable_vgw_route_propagation(route_table_id, + vgw_id, + dry_run=check_mode) return {'changed': changed} From 271cbe833e1ecbc8e43fd295f7d7c140ee41e619 Mon Sep 17 00:00:00 2001 From: Bret Martin Date: Mon, 10 Aug 2015 14:35:25 -0400 Subject: [PATCH 19/23] Call ensure_propagation() with the retrieved route table ID --- cloud/amazon/ec2_vpc_route_table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index bb530cb0e9e..faafc6955e6 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -421,7 +421,7 @@ def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, ) if propagating_vgw_ids is not None: - result = ensure_propagation(vpc_conn, route_table_id, + result = ensure_propagation(vpc_conn, route_table.id, propagating_vgw_ids, check_mode=check_mode) changed = changed or result['changed'] From 29ce49e84f207892c618d89523ca938e07ec01dd Mon Sep 17 00:00:00 2001 From: Bret Martin Date: Mon, 10 Aug 2015 15:10:19 -0400 Subject: [PATCH 20/23] Don't attempt to delete routes using propagating virtual gateways --- cloud/amazon/ec2_vpc_route_table.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index faafc6955e6..d93effcc550 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -290,7 +290,8 @@ def index_of_matching_route(route_spec, routes_to_match): return i -def ensure_routes(vpc_conn, route_table, route_specs, check_mode): +def ensure_routes(vpc_conn, route_table, route_specs, propagating_vgw_ids, + check_mode): routes_to_match = list(route_table.routes) route_specs_to_create = [] for route_spec in route_specs: @@ -299,8 +300,16 @@ def ensure_routes(vpc_conn, route_table, route_specs, check_mode): route_specs_to_create.append(route_spec) else: del routes_to_match[i] + + # NOTE: As of boto==2.38.0, the origin of a route is not available + # (for example, whether it came from a gateway with route propagation + # enabled). Testing for origin == 'EnableVgwRoutePropagation' is more + # correct than checking whether the route uses a propagating VGW. + # The current logic will leave non-propagated routes using propagating + # VGWs in place. routes_to_delete = [r for r in routes_to_match - if r.gateway_id != 'local'] + if r.gateway_id != 'local' + and r.gateway_id not in propagating_vgw_ids] changed = routes_to_delete or route_specs_to_create if changed: @@ -433,7 +442,8 @@ def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, if routes is not None: try: - result = ensure_routes(vpc_conn, route_table, routes, check_mode) + result = ensure_routes(vpc_conn, route_table, routes, + propagating_vgw_ids, check_mode) changed = changed or result['changed'] except EC2ResponseError as e: raise AnsibleRouteTableException( From 96e4194588c088294f1935d7d984065cb3393034 Mon Sep 17 00:00:00 2001 From: Bret Martin Date: Mon, 10 Aug 2015 15:25:13 -0400 Subject: [PATCH 21/23] Don't enable route propagation on a virtual gateway with propagated routes --- cloud/amazon/ec2_vpc_route_table.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index d93effcc550..2328006883c 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -369,17 +369,22 @@ def ensure_subnet_associations(vpc_conn, vpc_id, route_table, subnets, return {'changed': changed} -def ensure_propagation(vpc_conn, route_table_id, propagating_vgw_ids, +def ensure_propagation(vpc_conn, route_table, propagating_vgw_ids, check_mode): - # NOTE: As of boto==2.15.0, it is not yet possible to query the existing - # propagating gateways. However, EC2 does support this as evidenced by - # the describe-route-tables tool. For now, just enable the given VGWs - # and do not disable any others. + # NOTE: As of boto==2.38.0, it is not yet possible to query the existing + # propagating gateways. However, EC2 does support this as shown in its API + # documentation. For now, a reasonable proxy for this is the presence of + # propagated routes using the gateway in the route table. If such a route + # is found, propagation is almost certainly enabled. changed = False for vgw_id in propagating_vgw_ids: + for r in list(route_table.routes): + if r.gateway_id == vgw_id: + return {'changed': False} + changed = True - vpc_conn.enable_vgw_route_propagation(route_table_id, + vpc_conn.enable_vgw_route_propagation(route_table.id, vgw_id, dry_run=check_mode) @@ -430,7 +435,7 @@ def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, ) if propagating_vgw_ids is not None: - result = ensure_propagation(vpc_conn, route_table.id, + result = ensure_propagation(vpc_conn, route_table, propagating_vgw_ids, check_mode=check_mode) changed = changed or result['changed'] From a2fb8edb3c67247adad6b7a420929390cb73ece1 Mon Sep 17 00:00:00 2001 From: whiter Date: Sun, 30 Aug 2015 22:25:05 +0200 Subject: [PATCH 22/23] Added option to specify tags or route-table-id, quoted doc strings, added more detail to returned route table object, numerous minor fixes --- cloud/amazon/ec2_vpc_route_table.py | 298 ++++++++++++++++------------ 1 file changed, 172 insertions(+), 126 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index 2328006883c..a65efaa78fc 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -20,50 +20,47 @@ short_description: Manage route tables for AWS virtual private clouds description: - Manage route tables for AWS virtual private clouds version_added: "2.0" -author: Robert Estelle (@erydo) +author: Robert Estelle (@erydo), Rob White (@wimnat) options: - vpc_id: + lookup: description: - - VPC ID of the VPC in which to create the route table. - required: true - route_table_id: + - "Look up route table by either tags or by route table ID. Non-unique tag lookup will fail. If no tags are specifed then no lookup for an existing route table is performed and a new route table will be created. To change tags of a route table, you must look up by id." + required: false + default: tag + choices: [ 'tag', 'id' ] + propagating_vgw_ids: description: - - The ID of the route table to update or delete. + - "Enable route propagation from virtual gateways specified by ID." required: false - default: null - resource_tags: + route_table_id: description: - - A dictionary array of resource tags of the form: { tag1: value1, tag2: value2 }. Tags in this list are used to uniquely identify route tables within a VPC when the route_table_id is not supplied. + - "The ID of the route table to update or delete." required: false default: null routes: description: - - List of routes in the route table. Routes are specified as dicts containing the keys 'dest' and one of 'gateway_id', 'instance_id', 'interface_id', or 'vpc_peering_connection'. If 'gateway_id' is specified, you can refer to the VPC's IGW by using the value 'igw'. + - "List of routes in the route table. Routes are specified as dicts containing the keys 'dest' and one of 'gateway_id', 'instance_id', 'interface_id', or 'vpc_peering_connection'. If 'gateway_id' is specified, you can refer to the VPC's IGW by using the value 'igw'." required: true + state: + description: + - "Create or destroy the VPC route table" + required: false + default: present + choices: [ 'present', 'absent' ] subnets: description: - - An array of subnets to add to this route table. Subnets may be specified by either subnet ID, Name tag, or by a CIDR such as '10.0.0.0/24'. + - "An array of subnets to add to this route table. Subnets may be specified by either subnet ID, Name tag, or by a CIDR such as '10.0.0.0/24'." required: true - propagating_vgw_ids: + tags: description: - - Enable route propagation from virtual gateways specified by ID. + - "A dictionary array of resource tags of the form: { tag1: value1, tag2: value2 }. Tags in this list are used to uniquely identify route tables within a VPC when the route_table_id is not supplied." required: false - wait: - description: - - Wait for the VPC to be in state 'available' before returning. - required: false - default: "no" - choices: [ "yes", "no" ] - wait_timeout: - description: - - How long before wait gives up, in seconds. - default: 300 - state: + default: null + aliases: [ "resource_tags" ] + vpc_id: description: - - Create or destroy the VPC route table - required: false - default: present - choices: [ 'present', 'absent' ] + - "VPC ID of the VPC in which to create the route table." + required: true extends_documentation_fragment: aws ''' @@ -73,36 +70,35 @@ EXAMPLES = ''' # Basic creation example: - name: Set up public subnet route table - local_action: - module: ec2_vpc_route_table + ec2_vpc_route_table: vpc_id: vpc-1245678 region: us-west-1 - resource_tags: + tags: Name: Public subnets: - - '{{jumpbox_subnet.subnet_id}}' - - '{{frontend_subnet.subnet_id}}' - - '{{vpn_subnet.subnet_id}}' + - "{{ jumpbox_subnet.subnet_id }}" + - "{{ frontend_subnet.subnet_id }}" + - "{{ vpn_subnet.subnet_id }}" routes: - dest: 0.0.0.0/0 - gateway_id: '{{igw.gateway_id}}' + gateway_id: "{{ igw.gateway_id }}" register: public_route_table - name: Set up NAT-protected route table - local_action: - module: ec2_vpc_route_table + ec2_vpc_route_table: vpc_id: vpc-1245678 region: us-west-1 - resource_tags: + tags: - Name: Internal subnets: - - '{{application_subnet.subnet_id}}' + - "{{ application_subnet.subnet_id }}" - 'Database Subnet' - '10.0.0.0/8' routes: - dest: 0.0.0.0/0 - instance_id: '{{nat.instance_id}}' + instance_id: "{{ nat.instance_id }}" register: nat_route_table + ''' @@ -210,12 +206,12 @@ def find_igw(vpc_conn, vpc_id): filters={'attachment.vpc-id': vpc_id}) if not igw: - return AnsibleIgwSearchException('No IGW found for VPC "{0}"'. + raise AnsibleIgwSearchException('No IGW found for VPC {0}'. format(vpc_id)) elif len(igw) == 1: return igw[0].id else: - raise AnsibleIgwSearchException('Multiple IGWs found for VPC "{0}"'. + raise AnsibleIgwSearchException('Multiple IGWs found for VPC {0}'. format(vpc_id)) @@ -251,17 +247,29 @@ def ensure_tags(vpc_conn, resource_id, tags, add_only, check_mode): def get_route_table_by_id(vpc_conn, vpc_id, route_table_id): - route_tables = vpc_conn.get_all_route_tables( - route_table_ids=[route_table_id], filters={'vpc_id': vpc_id}) - return route_tables[0] if route_tables else None - + route_table = None + route_tables = vpc_conn.get_all_route_tables(route_table_ids=[route_table_id], filters={'vpc_id': vpc_id}) + if route_tables: + route_table = route_tables[0] + + return route_table + def get_route_table_by_tags(vpc_conn, vpc_id, tags): + + count = 0 + route_table = None route_tables = vpc_conn.get_all_route_tables(filters={'vpc_id': vpc_id}) - for route_table in route_tables: - this_tags = get_resource_tags(vpc_conn, route_table.id) + for table in route_tables: + this_tags = get_resource_tags(vpc_conn, table.id) if tags_match(tags, this_tags): - return route_table + route_table = table + count +=1 + + if count > 1: + raise RuntimeError("Tags provided do not identify a unique route table") + else: + return route_table def route_spec_matches_route(route_spec, route): @@ -391,75 +399,132 @@ def ensure_propagation(vpc_conn, route_table, propagating_vgw_ids, return {'changed': changed} -def ensure_route_table_absent(vpc_conn, vpc_id, route_table_id, resource_tags, - check_mode): - if route_table_id: - route_table = get_route_table_by_id(vpc_conn, vpc_id, route_table_id) - elif resource_tags: - route_table = get_route_table_by_tags(vpc_conn, vpc_id, resource_tags) - else: - raise AnsibleRouteTableException( - 'must provide route_table_id or resource_tags') +def ensure_route_table_absent(connection, module): + + lookup = module.params.get('lookup') + route_table_id = module.params.get('route_table_id') + tags = module.params.get('tags') + vpc_id = module.params.get('vpc_id') + check_mode = module.params.get('check_mode') + + if lookup == 'tag': + if tags is not None: + try: + route_table = get_route_table_by_tags(connection, vpc_id, tags) + except EC2ResponseError as e: + module.fail_json(msg=e.message) + except RuntimeError as e: + module.fail_json(msg=e.args[0]) + else: + route_table = None + elif lookup == 'id': + try: + route_table = get_route_table_by_id(connection, vpc_id, route_table_id) + except EC2ResponseError as e: + module.fail_json(msg=e.message) if route_table is None: return {'changed': False} - vpc_conn.delete_route_table(route_table.id, dry_run=check_mode) + try: + connection.delete_route_table(route_table.id, dry_run=check_mode) + except EC2ResponseError as e: + module.fail_json(msg=e.message) + return {'changed': True} -def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, - routes, subnets, propagating_vgw_ids, - check_mode): +def get_route_table_info(route_table): + + # Add any routes to array + routes = [] + for route in route_table.routes: + routes.append(route.__dict__) + + route_table_info = { 'id': route_table.id, + 'routes': routes, + 'tags': route_table.tags, + 'vpc_id': route_table.vpc_id + } + + return route_table_info + +def create_route_spec(connection, routes, vpc_id): + + for route_spec in routes: + rename_key(route_spec, 'dest', 'destination_cidr_block') + + if 'gateway_id' in route_spec and route_spec['gateway_id'] and \ + route_spec['gateway_id'].lower() == 'igw': + igw = find_igw(connection, vpc_id) + route_spec['gateway_id'] = igw + + return routes + +def ensure_route_table_present(connection, module): + + lookup = module.params.get('lookup') + propagating_vgw_ids = module.params.get('propagating_vgw_ids', []) + route_table_id = module.params.get('route_table_id') + subnets = module.params.get('subnets') + tags = module.params.get('tags') + vpc_id = module.params.get('vpc_id') + check_mode = module.params.get('check_mode') + try: + routes = create_route_spec(connection, module.params.get('routes'), vpc_id) + except AnsibleIgwSearchException as e: + module.fail_json(msg=e[0]) + changed = False tags_valid = False - if route_table_id: - route_table = get_route_table_by_id(vpc_conn, vpc_id, route_table_id) - elif resource_tags: - route_table = get_route_table_by_tags(vpc_conn, vpc_id, resource_tags) - tags_valid = route_table is not None - else: - raise AnsibleRouteTableException( - 'must provide route_table_id or resource_tags') - - if check_mode and route_table is None: - return {'changed': True} + if lookup == 'tag': + if tags is not None: + try: + route_table = get_route_table_by_tags(connection, vpc_id, tags) + except EC2ResponseError as e: + module.fail_json(msg=e.message) + except RuntimeError as e: + module.fail_json(msg=e.args[0]) + else: + route_table = None + elif lookup == 'id': + try: + route_table = get_route_table_by_id(connection, vpc_id, route_table_id) + except EC2ResponseError as e: + module.fail_json(msg=e.message) + + # If no route table returned then create new route table if route_table is None: + print route_table.keys() + try: + route_table = connection.create_route_table(vpc_id, check_mode) + changed = True + except EC2ResponseError, e: + module.fail_json(msg=e.message) + + if routes is not None: try: - route_table = vpc_conn.create_route_table(vpc_id) + result = ensure_routes(connection, route_table, routes, propagating_vgw_ids, check_mode) + changed = changed or result['changed'] except EC2ResponseError as e: - raise AnsibleRouteTableException( - 'Unable to create route table {0}, error: {1}' - .format(route_table_id or resource_tags, e) - ) + module.fail_json(msg=e.message) if propagating_vgw_ids is not None: - result = ensure_propagation(vpc_conn, route_table, + result = ensure_propagation(vpc_conn, route_table_id, propagating_vgw_ids, check_mode=check_mode) changed = changed or result['changed'] - if not tags_valid and resource_tags is not None: - result = ensure_tags(vpc_conn, route_table.id, resource_tags, + if not tags_valid and tags is not None: + result = ensure_tags(connection, route_table.id, tags, add_only=True, check_mode=check_mode) changed = changed or result['changed'] - if routes is not None: - try: - result = ensure_routes(vpc_conn, route_table, routes, - propagating_vgw_ids, check_mode) - changed = changed or result['changed'] - except EC2ResponseError as e: - raise AnsibleRouteTableException( - 'Unable to ensure routes for route table {0}, error: {1}' - .format(route_table, e) - ) - if subnets: associated_subnets = [] try: - associated_subnets = find_subnets(vpc_conn, vpc_id, subnets) + associated_subnets = find_subnets(connection, vpc_id, subnets) except EC2ResponseError as e: raise AnsibleRouteTableException( 'Unable to find subnets for route table {0}, error: {1}' @@ -467,8 +532,7 @@ def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, ) try: - result = ensure_subnet_associations( - vpc_conn, vpc_id, route_table, associated_subnets, check_mode) + result = ensure_subnet_associations(connection, vpc_id, route_table, associated_subnets, check_mode) changed = changed or result['changed'] except EC2ResponseError as e: raise AnsibleRouteTableException( @@ -476,23 +540,21 @@ def ensure_route_table_present(vpc_conn, vpc_id, route_table_id, resource_tags, .format(route_table, e) ) - return { - 'changed': changed, - 'route_table_id': route_table.id, - } + module.exit_json(changed=changed, route_table=get_route_table_info(route_table)) def main(): argument_spec = ec2_argument_spec() argument_spec.update( dict( - vpc_id = dict(default=None, required=True), - route_table_id = dict(default=None, required=False), + lookup = dict(default='tag', required=False, choices=['tag', 'id']), propagating_vgw_ids = dict(default=None, required=False, type='list'), - resource_tags = dict(default=None, required=False, type='dict'), + route_table_id = dict(default=None, required=False), routes = dict(default=None, required=False, type='list'), + state = dict(default='present', choices=['present', 'absent']), subnets = dict(default=None, required=False, type='list'), - state = dict(default='present', choices=['present', 'absent']) + tags = dict(default=None, required=False, type='dict', aliases=['resource_tags']), + vpc_id = dict(default=None, required=True) ) ) @@ -511,34 +573,18 @@ def main(): else: module.fail_json(msg="region must be specified") - vpc_id = module.params.get('vpc_id') + lookup = module.params.get('lookup') route_table_id = module.params.get('route_table_id') - resource_tags = module.params.get('resource_tags') - propagating_vgw_ids = module.params.get('propagating_vgw_ids', []) - - routes = module.params.get('routes') - for route_spec in routes: - rename_key(route_spec, 'dest', 'destination_cidr_block') - - if 'gateway_id' in route_spec and route_spec['gateway_id'] and \ - route_spec['gateway_id'].lower() == 'igw': - igw = find_igw(connection, vpc_id) - route_spec['gateway_id'] = igw - - subnets = module.params.get('subnets') state = module.params.get('state', 'present') + if lookup == 'id' and route_table_id is None: + module.fail_json("You must specify route_table_id if lookup is set to id") + try: if state == 'present': - result = ensure_route_table_present( - connection, vpc_id, route_table_id, resource_tags, - routes, subnets, propagating_vgw_ids, module.check_mode - ) + result = ensure_route_table_present(connection, module) elif state == 'absent': - result = ensure_route_table_absent( - connection, vpc_id, route_table_id, resource_tags, - module.check_mode - ) + result = ensure_route_table_absent(connection, module) except AnsibleRouteTableException as e: module.fail_json(msg=str(e)) @@ -549,4 +595,4 @@ from ansible.module_utils.ec2 import * # noqa if __name__ == '__main__': main() - \ No newline at end of file + From 65d3a3be59f028def687293ddac5aa92eeb705ce Mon Sep 17 00:00:00 2001 From: whiter Date: Wed, 2 Sep 2015 14:34:56 +0100 Subject: [PATCH 23/23] Remove debug print statement. Fixed ensure_propagation call to pass 'route_table' and 'connection'. --- cloud/amazon/ec2_vpc_route_table.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cloud/amazon/ec2_vpc_route_table.py b/cloud/amazon/ec2_vpc_route_table.py index a65efaa78fc..70f53bad26a 100644 --- a/cloud/amazon/ec2_vpc_route_table.py +++ b/cloud/amazon/ec2_vpc_route_table.py @@ -496,7 +496,6 @@ def ensure_route_table_present(connection, module): # If no route table returned then create new route table if route_table is None: - print route_table.keys() try: route_table = connection.create_route_table(vpc_id, check_mode) changed = True @@ -511,7 +510,7 @@ def ensure_route_table_present(connection, module): module.fail_json(msg=e.message) if propagating_vgw_ids is not None: - result = ensure_propagation(vpc_conn, route_table_id, + result = ensure_propagation(connection, route_table, propagating_vgw_ids, check_mode=check_mode) changed = changed or result['changed']