From 0e459f65f092e8807777c0cc69d7033cd737b1a2 Mon Sep 17 00:00:00 2001 From: Peter Sankauskas Date: Fri, 18 Oct 2013 14:34:17 -0700 Subject: [PATCH 1/2] First round of changes to the EIP module to get it to be able to work well inside a VPC, and to be able to delete EIPs as well --- library/cloud/ec2_eip | 112 +++++++++++++++++++++++++++++++++++------- 1 file changed, 93 insertions(+), 19 deletions(-) diff --git a/library/cloud/ec2_eip b/library/cloud/ec2_eip index a91f5dfa106..13e1bdb567c 100644 --- a/library/cloud/ec2_eip +++ b/library/cloud/ec2_eip @@ -47,6 +47,11 @@ options: required: false default: null aliases: [ ec2_region ] + in_vpc: + description: + - allocate an EIP inside a VPC or not + required: false + default: false requirements: [ "boto" ] author: Lorin Hochstein notes: @@ -81,6 +86,11 @@ EXAMPLES = ''' ec2_eip: "instance_id={{ item }}" with_items: ec2.instance_ids +- name: allocate a new elastic IP inside a VPC in us-west-2 + ec2_eip: region=us-west-2 in_vpc=yes + register: eip +- name: output the IP + debug: msg="Allocated IP inside a VPC is {{ eip.public_ip }}" ''' try: @@ -122,23 +132,30 @@ def connect(ec2_url, ec2_secret_key, ec2_access_key, region): return ec2 -def associate_ip_and_instance(ec2, public_ip, instance_id, module): - if ip_is_associated_with_instance(ec2, public_ip, instance_id): - module.exit_json(changed=False, public_ip=public_ip) +def associate_ip_and_instance(ec2, address, instance_id, module): + if ip_is_associated_with_instance(ec2, address.public_ip, instance_id, module): + module.exit_json(changed=False, public_ip=address.public_ip) # If we're in check mode, nothing else to do if module.check_mode: module.exit_json(changed=True) - res = ec2.associate_address(instance_id, public_ip) + try: + if address.domain == "vpc": + res = ec2.associate_address(instance_id, allocation_id=address.allocation_id) + else: + res = ec2.associate_address(instance_id, public_ip=address.public_ip) + except boto.exception.EC2ResponseError, e: + module.fail_json(msg=str(e)) + if res: - module.exit_json(changed=True, public_ip=public_ip) + module.exit_json(changed=True, public_ip=address.public_ip) else: module.fail_json(msg="association failed") def disassociate_ip_and_instance(ec2, public_ip, instance_id, module): - if not ip_is_associated_with_instance(ec2, public_ip, instance_id): + if not ip_is_associated_with_instance(ec2, public_ip, instance_id, module): module.exit_json(changed=False, public_ip=public_ip) # If we're in check mode, nothing else to do @@ -152,25 +169,68 @@ def disassociate_ip_and_instance(ec2, public_ip, instance_id, module): module.fail_json(msg="disassociation failed") -def ip_is_associated_with_instance(ec2, public_ip, instance_id): +def find_address(ec2, public_ip, module): + """ Find an existing Elastic IP address """ + + try: + addresses = ec2.get_all_addresses([public_ip]) + except boto.exception.EC2ResponseError, e: + module.fail_json(msg=str(e.message)) + + return addresses[0] + + +def ip_is_associated_with_instance(ec2, public_ip, instance_id, module): """ Check if the elastic IP is currently associated with the instance """ - addresses = ec2.get_all_addresses([public_ip]) - if addresses: - return addresses[0].instance_id == instance_id + address = find_address(ec2, public_ip, module) + if address: + return address.instance_id == instance_id else: return False -def allocate_new_ip(ec2, module): - """ Allocate a new elastic IP and return the IP address""" +def allocate_address(ec2, domain, module): + """ Allocate a new elastic IP address and return it """ # If we're in check mode, nothing else to do if module.check_mode: module.exit_json(change=True) - ec2_address = ec2.allocate_address() - return ec2_address.public_ip + address = ec2.allocate_address(domain=domain) + return address +def release_address(ec2, public_ip, module): + """ Release a previously allocated elastic IP address """ + + address = find_address(ec2, public_ip, module) + + # If we're in check mode, nothing else to do + if module.check_mode: + module.exit_json(change=True) + + res = address.release() + if res: + module.exit_json(changed=True) + else: + module.fail_json(msg="release failed") + + +def find_instance(ec2, instance_id, module): + """ Attempt to find the EC2 instance and return it """ + + try: + reservations = ec2.get_all_reservations(instance_ids=[instance_id]) + except boto.exception.EC2ResponseError, e: + module.fail_json(msg=str(e)) + + if len(reservations) == 1: + instances = reservations[0].instances + if len(instances) == 1: + return instances[0] + + module.fail_json(msg="could not find instance" + instance_id) + + def main(): module = AnsibleModule( argument_spec = dict( @@ -181,7 +241,8 @@ def main(): ec2_url = dict(required=False, aliases=['EC2_URL']), ec2_secret_key = dict(required=False, aliases=['EC2_SECRET_KEY'], no_log=True), ec2_access_key = dict(required=False, aliases=['EC2_ACCESS_KEY']), - region = dict(required=False, aliases=['ec2_region']) + region = dict(required=False, aliases=['ec2_region']), + in_vpc = dict(required=False, choices=BOOLEANS, default=False), ), supports_check_mode=True ) @@ -197,15 +258,28 @@ def main(): instance_id = module.params.get('instance_id') public_ip = module.params.get('public_ip') state = module.params.get('state') + in_vpc = module.params.get('in_vpc') + domain = "vpc" if in_vpc else None if state == 'present': if public_ip is None: - public_ip = allocate_new_ip(ec2, module) if instance_id is None: - module.exit_json(changed=True, public_ip=public_ip) - associate_ip_and_instance(ec2, public_ip, instance_id, module) + address = allocate_address(ec2, domain, module) + module.exit_json(changed=True, public_ip=address.public_ip) + else: + # Determine if the instance is inside a VPC or not + instance = find_instance(ec2, instance_id, module) + if instance.vpc_id != None: + domain = "vpc" + address = allocate_address(ec2, domain, module) + else: + address = find_address(ec2, public_ip, module) + associate_ip_and_instance(ec2, address, instance_id, module) else: - disassociate_ip_and_instance(ec2, public_ip, instance_id, module) + if instance_id is None: + release_address(ec2, public_ip, module) + else: + disassociate_ip_and_instance(ec2, public_ip, instance_id, module) From b793248df438a06a27ac5105b2427d0213ffe57d Mon Sep 17 00:00:00 2001 From: Peter Sankauskas Date: Fri, 18 Oct 2013 15:12:49 -0700 Subject: [PATCH 2/2] Now this disassociates EIPs from instances inside a VPC correctly. Time for a PR. --- library/cloud/ec2_eip | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/library/cloud/ec2_eip b/library/cloud/ec2_eip index 13e1bdb567c..ae8d100efe4 100644 --- a/library/cloud/ec2_eip +++ b/library/cloud/ec2_eip @@ -154,15 +154,22 @@ def associate_ip_and_instance(ec2, address, instance_id, module): module.fail_json(msg="association failed") -def disassociate_ip_and_instance(ec2, public_ip, instance_id, module): - if not ip_is_associated_with_instance(ec2, public_ip, instance_id, module): - module.exit_json(changed=False, public_ip=public_ip) +def disassociate_ip_and_instance(ec2, address, instance_id, module): + if not ip_is_associated_with_instance(ec2, address.public_ip, instance_id, module): + module.exit_json(changed=False, public_ip=address.public_ip) # If we're in check mode, nothing else to do if module.check_mode: module.exit_json(changed=True) - res = ec2.disassociate_address(public_ip) + try: + if address.domain == "vpc": + res = ec2.disassociate_address(association_id=address.association_id) + else: + res = ec2.disassociate_address(public_ip=address.public_ip) + except boto.exception.EC2ResponseError, e: + module.fail_json(msg=str(e)) + if res: module.exit_json(changed=True) else: @@ -279,7 +286,8 @@ def main(): if instance_id is None: release_address(ec2, public_ip, module) else: - disassociate_ip_and_instance(ec2, public_ip, instance_id, module) + address = find_address(ec2, public_ip, module) + disassociate_ip_and_instance(ec2, address, instance_id, module)