From 64fa90527cf4f325b85ef7fd56bab7715eaf5ae0 Mon Sep 17 00:00:00 2001 From: Ian Glover Date: Wed, 11 Feb 2015 11:31:16 +0000 Subject: [PATCH 1/5] Module for creating VPC peering connections. --- cloud/ec2/ec2_vpc_peering.py | 183 +++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 cloud/ec2/ec2_vpc_peering.py diff --git a/cloud/ec2/ec2_vpc_peering.py b/cloud/ec2/ec2_vpc_peering.py new file mode 100644 index 00000000000..a4f52e7b2b0 --- /dev/null +++ b/cloud/ec2/ec2_vpc_peering.py @@ -0,0 +1,183 @@ +#!/usr/bin/python +# +DOCUMENTATION = """ +--- +module: ec2_vpc_peer +short_description: create or remove a peering connection between to ec2 VPCs. +description: + - +options: + vpc_id: + description: + - VPC id of the requesting VPC. + required: true + vpc_peer_id: + description: + - VPC id of the accepting VPC. + required: true + state: + description: + - Create or delete the peering connection. + required: false + default: present + choices: ['present', 'absent'] + wait_timeout: + description: + - How long to wait for peering connection state changes, in seconds + required: false + default: 10 + region: + description: + - The AWS region to use. Must be specified if ec2_url is not used. If not specified then the value of the EC2_REGION environment variable, if any, is used. + 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'] +""" + +import sys +import time + +try: + import boto.ec2 + import boto.vpc + import boto.exception +except ImportError: + print "failed=True msg='boto required for this module'" + sys.exit(1) + +def wait_for_connection_state(vpc_conn, peering_conn_id, status_code, timeout): + """Wait until the peering connection has transition into the required state. + Return True if the state is reached before timing out and False if the wait + times out. + """ + wait_end = time.time() + timeout + while wait_end > time.time(): + conn = vpc_conn.get_all_vpc_peering_connections([peering_conn_id])[0] + if conn.status_code == status_code: + return True + time.sleep(1) + return False + +def create_peer_connection(module, vpc_conn, vpc_id, vpc_peer_id, timeout): + """ + Creates a VPC peeering connection. + + module: Ansible module object + vpc_conn: authenticated VPCConnection connection object + vpc_id: id of the requesting VPC + vpc_peer_id: id of the accepting VPC + timeout: how long, in seconds, to wait for connection state changes. + + Returns a tuple containing the peering connection id and a boolean + indicating whether any changes were made. + """ + peering_conns = vpc_conn.get_all_vpc_peering_connections(filters=[ + ('requester-vpc-info.vpc-id', vpc_id), + ('accepter-vpc-info.vpc-id', vpc_peer_id)]) + for peering_conn in peering_conns: + if peering_conn.status_code == 'active': + return (peering_conn.id, False) + if peering_conn.status_code == 'pending-acceptance': + vpc_conn.accept_vpc_peering_connection(peering_conn.id) + result = wait_for_connection_state(vpc_conn, peering_conn.id, + 'active', timeout) + if result: + return (peering_conn.id, True) + else: + module.fail_json(msg='VPC peering connection with id ' + + peering_conn.id + ' could not be ' + + 'accepted.') + + peering_conn = vpc_conn.create_vpc_peering_connection(vpc_id, vpc_peer_id) + wait_for_connection_state(vpc_conn, peering_conn.id, 'pending-acceptance', + timeout) + + vpc_conn.accept_vpc_peering_connection(peering_conn.id) + wait_for_connection_state(vpc_conn, peering_conn.id, 'active', + timeout) + return (peering_conn.id, True) + +def delete_peer_connection(module, vpc_conn, vpc_id, vpc_peer_id): + """ + Deletes a VPC peering connection + + module: Ansible module object + vpc_conn: authenticated VPCConnection connection object + vpc_id: id of the requesting VPC + vpc_peer_id: id of the accepting VPC + + Returns a list of the peering connections that have been deleted. + """ + peering_conns = vpc_conn.get_all_vpc_peering_connections(filters=[ + ('requester-vpc-info.vpc-id', vpc_id), + ('accepter-vpc-info.vpc-id', vpc_peer_id)]) + removed_conns = [] + for peering_conn in peering_conns: + if peering_conn.status_code == 'active': + if vpc_conn.delete_vpc_peering_connection(peering_conn.id): + removed_conns.append(peering_conn.id) + else: + module.fail_json(msg="VPC peering connection with id " + + peering_conn.id + " could not be " + + "deleted") + return removed_conns + + +def main(): + """ + Module entry point. + """ + arguent_spec = ec2_argument_spec() + arguent_spec.update(dict( + vpc_id=dict(required=True), + vpc_peer_id=dict(required=True), + state=dict(choices=['present', 'absent'], default='present'), + wait_timeout=dict(type='int', default=15), + )) + module = AnsibleModule( + argument_spec=arguent_spec, + ) + + state = module.params.get('state') + _, aws_access_key, aws_secret_key, region = get_ec2_creds(module) + + if region: + 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, ex: + module.fail_json(msg=str(ex)) + else: + module.fail_json(msg="region must be specified") + + vpc_id = module.params.get('vpc_id') + vpc_peer_id = module.params.get('vpc_peer_id') + timeout = module.params.get('wait_timeout') + if state == 'present': + (connection, changed) = create_peer_connection(module, vpc_conn, vpc_id, + vpc_peer_id, timeout) + module.exit_json(changed=changed, connection_id=connection) + elif state == 'absent': + removed = delete_peer_connection(module, vpc_conn, vpc_id, + vpc_peer_id) + changed = (len(removed) > 0) + module.exit_json(peering_connections=removed, changed=changed) + +from ansible.module_utils.basic import * +from ansible.module_utils.ec2 import * +main() From 1edac0a2918aefb55d845c832a325706e42568f2 Mon Sep 17 00:00:00 2001 From: Ian Glover Date: Wed, 11 Feb 2015 14:09:00 +0000 Subject: [PATCH 2/5] Use update method on peering connection rather than requerying. --- cloud/ec2/ec2_vpc_peering.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/cloud/ec2/ec2_vpc_peering.py b/cloud/ec2/ec2_vpc_peering.py index a4f52e7b2b0..f84300e139f 100644 --- a/cloud/ec2/ec2_vpc_peering.py +++ b/cloud/ec2/ec2_vpc_peering.py @@ -43,7 +43,9 @@ - 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'] + aliases: ['ec2_access_key', 'access_key'] + +requirements: [ "boto" ] """ import sys @@ -57,15 +59,15 @@ print "failed=True msg='boto required for this module'" sys.exit(1) -def wait_for_connection_state(vpc_conn, peering_conn_id, status_code, timeout): +def wait_for_connection_state(peering_conn, status_code, timeout): """Wait until the peering connection has transition into the required state. Return True if the state is reached before timing out and False if the wait times out. """ wait_end = time.time() + timeout while wait_end > time.time(): - conn = vpc_conn.get_all_vpc_peering_connections([peering_conn_id])[0] - if conn.status_code == status_code: + peering_conn.update() + if peering_conn.status_code == status_code: return True time.sleep(1) return False @@ -91,8 +93,7 @@ def create_peer_connection(module, vpc_conn, vpc_id, vpc_peer_id, timeout): return (peering_conn.id, False) if peering_conn.status_code == 'pending-acceptance': vpc_conn.accept_vpc_peering_connection(peering_conn.id) - result = wait_for_connection_state(vpc_conn, peering_conn.id, - 'active', timeout) + result = wait_for_connection_state(peering_conn, 'active', timeout) if result: return (peering_conn.id, True) else: @@ -101,12 +102,10 @@ def create_peer_connection(module, vpc_conn, vpc_id, vpc_peer_id, timeout): 'accepted.') peering_conn = vpc_conn.create_vpc_peering_connection(vpc_id, vpc_peer_id) - wait_for_connection_state(vpc_conn, peering_conn.id, 'pending-acceptance', - timeout) + wait_for_connection_state(peering_conn, 'pending-acceptance', timeout) vpc_conn.accept_vpc_peering_connection(peering_conn.id) - wait_for_connection_state(vpc_conn, peering_conn.id, 'active', - timeout) + wait_for_connection_state(peering_conn, 'active', timeout) return (peering_conn.id, True) def delete_peer_connection(module, vpc_conn, vpc_id, vpc_peer_id): From a18d9f49eeb31f0fdc0bf5e7fd6219c05fa1f098 Mon Sep 17 00:00:00 2001 From: Ian Glover Date: Wed, 11 Feb 2015 15:27:24 +0000 Subject: [PATCH 3/5] Add support for adding peering connection to route tables. --- cloud/ec2/ec2_vpc_peering.py | 78 +++++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 11 deletions(-) diff --git a/cloud/ec2/ec2_vpc_peering.py b/cloud/ec2/ec2_vpc_peering.py index f84300e139f..8c8f6dbcd48 100644 --- a/cloud/ec2/ec2_vpc_peering.py +++ b/cloud/ec2/ec2_vpc_peering.py @@ -26,6 +26,11 @@ - How long to wait for peering connection state changes, in seconds required: false default: 10 + update_routes: + description: + - Whether to update the VPC route tables to add the peering connection. + required: false + default: true region: description: - The AWS region to use. Must be specified if ec2_url is not used. If not specified then the value of the EC2_REGION environment variable, if any, is used. @@ -60,7 +65,8 @@ sys.exit(1) def wait_for_connection_state(peering_conn, status_code, timeout): - """Wait until the peering connection has transition into the required state. + """ + Wait until the peering connection has transition into the required state. Return True if the state is reached before timing out and False if the wait times out. """ @@ -72,7 +78,52 @@ def wait_for_connection_state(peering_conn, status_code, timeout): time.sleep(1) return False -def create_peer_connection(module, vpc_conn, vpc_id, vpc_peer_id, timeout): +def update_vpc_routes(vpc_conn, peering_conn_id, src_vpc_info, dest_vpc_info): + """ + Update the route tables in the source VPC to point to the destination VPC. + """ + subnet = vpc_conn.get_all_subnets(filters={'cidr': src_vpc_info.cidr_block, + 'vpc_id': src_vpc_info.vpc_id}) + if len(subnet) != 1: + return False + + subnet = subnet[0] + rt = vpc_conn.get_all_route_tables(filters= + {'vpc_id': src_vpc_info.vpc_id, + 'association.subnet_id': subnet.id}) + if len(rt) != 1: + return False + + rt = rt[0] + replace = False + for route in rt.routes: + if route.destination_cidr_block == dest_vpc_info.cidr_block: + replace = True + break + + if replace: + vpc_conn.replace_route(rt.id, dest_vpc_info.cidr_block, + vpc_peering_connection_id=peering_conn_id) + else: + vpc_conn.create_route(rt.id, dest_vpc_info.cidr_block, + vpc_peering_connection_id=peering_conn_id) + return True + +def update_routes(module, vpc_conn, peering_conn): + """ + Update the route tables to account for the peering connection. + """ + if not module.params.get('update_routes'): + return + + update_vpc_routes(vpc_conn, peering_conn.id, + peering_conn.requester_vpc_info, + peering_conn.accepter_vpc_info) + update_vpc_routes(vpc_conn, peering_conn.id, + peering_conn.accepter_vpc_info, + peering_conn.requester_vpc_info) + +def create_peer_connection(module, vpc_conn): """ Creates a VPC peeering connection. @@ -85,6 +136,10 @@ def create_peer_connection(module, vpc_conn, vpc_id, vpc_peer_id, timeout): Returns a tuple containing the peering connection id and a boolean indicating whether any changes were made. """ + vpc_id = module.params.get('vpc_id') + vpc_peer_id = module.params.get('vpc_peer_id') + timeout = module.params.get('wait_timeout') + peering_conns = vpc_conn.get_all_vpc_peering_connections(filters=[ ('requester-vpc-info.vpc-id', vpc_id), ('accepter-vpc-info.vpc-id', vpc_peer_id)]) @@ -95,6 +150,7 @@ def create_peer_connection(module, vpc_conn, vpc_id, vpc_peer_id, timeout): vpc_conn.accept_vpc_peering_connection(peering_conn.id) result = wait_for_connection_state(peering_conn, 'active', timeout) if result: + update_routes(module, vpc_conn, peering_conn) return (peering_conn.id, True) else: module.fail_json(msg='VPC peering connection with id ' + @@ -106,9 +162,11 @@ def create_peer_connection(module, vpc_conn, vpc_id, vpc_peer_id, timeout): vpc_conn.accept_vpc_peering_connection(peering_conn.id) wait_for_connection_state(peering_conn, 'active', timeout) + + update_routes(module, vpc_conn, peering_conn) return (peering_conn.id, True) -def delete_peer_connection(module, vpc_conn, vpc_id, vpc_peer_id): +def delete_peer_connection(module, vpc_conn): """ Deletes a VPC peering connection @@ -119,6 +177,9 @@ def delete_peer_connection(module, vpc_conn, vpc_id, vpc_peer_id): Returns a list of the peering connections that have been deleted. """ + vpc_id = module.params.get('vpc_id') + vpc_peer_id = module.params.get('vpc_peer_id') + peering_conns = vpc_conn.get_all_vpc_peering_connections(filters=[ ('requester-vpc-info.vpc-id', vpc_id), ('accepter-vpc-info.vpc-id', vpc_peer_id)]) @@ -133,7 +194,6 @@ def delete_peer_connection(module, vpc_conn, vpc_id, vpc_peer_id): "deleted") return removed_conns - def main(): """ Module entry point. @@ -144,6 +204,7 @@ def main(): vpc_peer_id=dict(required=True), state=dict(choices=['present', 'absent'], default='present'), wait_timeout=dict(type='int', default=15), + update_routes=dict(type='bool', default=True) )) module = AnsibleModule( argument_spec=arguent_spec, @@ -164,16 +225,11 @@ def main(): else: module.fail_json(msg="region must be specified") - vpc_id = module.params.get('vpc_id') - vpc_peer_id = module.params.get('vpc_peer_id') - timeout = module.params.get('wait_timeout') if state == 'present': - (connection, changed) = create_peer_connection(module, vpc_conn, vpc_id, - vpc_peer_id, timeout) + (connection, changed) = create_peer_connection(module, vpc_conn) module.exit_json(changed=changed, connection_id=connection) elif state == 'absent': - removed = delete_peer_connection(module, vpc_conn, vpc_id, - vpc_peer_id) + removed = delete_peer_connection(module, vpc_conn) changed = (len(removed) > 0) module.exit_json(peering_connections=removed, changed=changed) From b5c1d2ec36c4974be009431b6cec48b98c3813a5 Mon Sep 17 00:00:00 2001 From: Ian Glover Date: Mon, 2 Mar 2015 09:18:30 +0000 Subject: [PATCH 4/5] Rename directory to match core modules. --- cloud/{ec2 => amazon}/ec2_vpc_peering.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cloud/{ec2 => amazon}/ec2_vpc_peering.py (100%) diff --git a/cloud/ec2/ec2_vpc_peering.py b/cloud/amazon/ec2_vpc_peering.py similarity index 100% rename from cloud/ec2/ec2_vpc_peering.py rename to cloud/amazon/ec2_vpc_peering.py From 9cd2678715f01f64f17334ea0bdb44d6b3d01451 Mon Sep 17 00:00:00 2001 From: Ian Glover Date: Fri, 18 Dec 2015 10:28:17 +0000 Subject: [PATCH 5/5] Change to using HAS_BOTO pattern. --- cloud/amazon/ec2_vpc_peering.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/cloud/amazon/ec2_vpc_peering.py b/cloud/amazon/ec2_vpc_peering.py index 8c8f6dbcd48..2deda5c0763 100644 --- a/cloud/amazon/ec2_vpc_peering.py +++ b/cloud/amazon/ec2_vpc_peering.py @@ -60,9 +60,9 @@ import boto.ec2 import boto.vpc import boto.exception + HAS_BOTO = True except ImportError: - print "failed=True msg='boto required for this module'" - sys.exit(1) + HAS_BOTO = False def wait_for_connection_state(peering_conn, status_code, timeout): """ @@ -117,9 +117,9 @@ def update_routes(module, vpc_conn, peering_conn): return update_vpc_routes(vpc_conn, peering_conn.id, - peering_conn.requester_vpc_info, + peering_conn.requester_vpc_info, peering_conn.accepter_vpc_info) - update_vpc_routes(vpc_conn, peering_conn.id, + update_vpc_routes(vpc_conn, peering_conn.id, peering_conn.accepter_vpc_info, peering_conn.requester_vpc_info) @@ -159,7 +159,7 @@ def create_peer_connection(module, vpc_conn): peering_conn = vpc_conn.create_vpc_peering_connection(vpc_id, vpc_peer_id) wait_for_connection_state(peering_conn, 'pending-acceptance', timeout) - + vpc_conn.accept_vpc_peering_connection(peering_conn.id) wait_for_connection_state(peering_conn, 'active', timeout) @@ -179,7 +179,7 @@ def delete_peer_connection(module, vpc_conn): """ vpc_id = module.params.get('vpc_id') vpc_peer_id = module.params.get('vpc_peer_id') - + peering_conns = vpc_conn.get_all_vpc_peering_connections(filters=[ ('requester-vpc-info.vpc-id', vpc_id), ('accepter-vpc-info.vpc-id', vpc_peer_id)]) @@ -190,7 +190,7 @@ def delete_peer_connection(module, vpc_conn): removed_conns.append(peering_conn.id) else: module.fail_json(msg="VPC peering connection with id " + - peering_conn.id + " could not be " + + peering_conn.id + " could not be " + "deleted") return removed_conns @@ -210,6 +210,9 @@ def main(): argument_spec=arguent_spec, ) + if not HAS_BOTO: + module.fail_json(msg='boto required for this module') + state = module.params.get('state') _, aws_access_key, aws_secret_key, region = get_ec2_creds(module)