From c1526ff87b32af025f0c07e4d3a8b7e50b605d8f Mon Sep 17 00:00:00 2001 From: Evan Carter Date: Wed, 5 Aug 2015 14:22:09 -0400 Subject: [PATCH] Adding the ability to associate eips with network interfaces --- lib/ansible/modules/cloud/amazon/ec2_eip.py | 151 +++++++++++++------- 1 file changed, 103 insertions(+), 48 deletions(-) diff --git a/lib/ansible/modules/cloud/amazon/ec2_eip.py b/lib/ansible/modules/cloud/amazon/ec2_eip.py index 6a937ed2d06..fc12cc535ca 100644 --- a/lib/ansible/modules/cloud/amazon/ec2_eip.py +++ b/lib/ansible/modules/cloud/amazon/ec2_eip.py @@ -26,6 +26,11 @@ options: description: - The EC2 instance id required: false + network_interface_id: + description: + - The Elastic Network Interface (ENI) id + required: false + version_added: "2.0" public_ip: description: - The elastic IP address to associate with the instance. @@ -57,7 +62,6 @@ options: required: false default: false version_added: "1.6" - extends_documentation_fragment: aws author: "Lorin Hochstein (@lorin) " notes: @@ -72,22 +76,21 @@ notes: EXAMPLES = ''' - name: associate an elastic IP with an instance ec2_eip: instance_id=i-1212f003 ip=93.184.216.119 - +- name: associate an elastic IP with a device + ec2_eip: network_interface_id=eni-c8ad70f3 ip=93.184.216.119 - name: disassociate an elastic IP from an instance ec2_eip: instance_id=i-1212f003 ip=93.184.216.119 state=absent - +- name: disassociate an elastic IP with a device + ec2_eip: network_interface_id=eni-c8ad70f3 ip=93.184.216.119 state=absent - name: allocate a new elastic IP and associate it with an instance ec2_eip: instance_id=i-1212f003 - - name: allocate a new elastic IP without associating it to anything action: ec2_eip register: eip - name: output the IP debug: msg="Allocated IP is {{ eip.public_ip }}" - - name: another way of allocating an elastic IP without associating it to anything ec2_eip: state='present' - - name: provision new instances with ec2 ec2: keypair=mykey instance_type=c1.medium image=emi-40603AD1 wait=yes''' ''' group=webserver count=3 @@ -95,7 +98,6 @@ EXAMPLES = ''' - name: associate new elastic IPs with each of the instances 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 @@ -113,27 +115,27 @@ except ImportError: class EIPException(Exception): pass - -def associate_ip_and_instance(ec2, address, instance_id, check_mode): - if address_is_associated_with_instance(ec2, address, instance_id): +def associate_ip_and_device(ec2, address, device_id, check_mode, isinstance=True): + if address_is_associated_with_device(ec2, address, device_id, isinstance): return {'changed': False} # If we're in check mode, nothing else to do if not check_mode: - if address.domain == 'vpc': - res = ec2.associate_address(instance_id, - allocation_id=address.allocation_id) + if isinstance: + if address.domain == "vpc": + res = ec2.associate_address(device_id, allocation_id=address.allocation_id) + else: + res = ec2.associate_address(device_id, public_ip=address.public_ip) else: - res = ec2.associate_address(instance_id, - public_ip=address.public_ip) + res = ec2.associate_address(network_interface_id=device_id, allocation_id=address.allocation_id) if not res: raise EIPException('association failed') return {'changed': True} -def disassociate_ip_and_instance(ec2, address, instance_id, check_mode): - if not address_is_associated_with_instance(ec2, address, instance_id): +def disassociate_ip_and_device(ec2, address, device_id, check_mode, isinstance=True): + if not address_is_associated_with_device(ec2, address, device_id, isinstance): return {'changed': False} # If we're in check mode, nothing else to do @@ -158,24 +160,33 @@ def _find_address_by_ip(ec2, public_ip): raise -def _find_address_by_instance_id(ec2, instance_id): - addresses = ec2.get_all_addresses(None, {'instance-id': instance_id}) +def _find_address_by_device_id(ec2, device_id, isinstance=True): + if isinstance: + addresses = ec2.get_all_addresses(None, {'instance-id': device_id}) + else: + addresses = ec2.get_all_addresses(None, {'network-interface-id': device_id}) if addresses: return addresses[0] -def find_address(ec2, public_ip, instance_id): +def find_address(ec2, public_ip, device_id, isinstance=True): """ Find an existing Elastic IP address """ if public_ip: return _find_address_by_ip(ec2, public_ip) - elif instance_id: - return _find_address_by_instance_id(ec2, instance_id) + elif device_id and isinstance: + return _find_address_by_device_id(ec2, device_id) + elif device_id: + return _find_address_by_device_id(ec2, device_id, isinstance=False) -def address_is_associated_with_instance(ec2, address, instance_id): - """ Check if the elastic IP is currently associated with the instance """ +def address_is_associated_with_device(ec2, address, device_id, isinstance=True): + """ Check if the elastic IP is currently associated with the device """ + address = ec2.get_all_addresses(address.public_ip) if address: - return address and address.instance_id == instance_id + if isinstance: + return address and address[0].instance_id == device_id + else: + return address and address[0].network_interface_id == device_id return False @@ -186,7 +197,7 @@ def allocate_address(ec2, domain, reuse_existing_ip_allowed): all_addresses = ec2.get_all_addresses(filters=domain_filter) unassociated_addresses = [a for a in all_addresses - if not a.instance_id] + if not a.device_id] if unassociated_addresses: return unassociated_addresses[0] @@ -204,21 +215,33 @@ def release_address(ec2, address, check_mode): return {'changed': True} -def find_instance(ec2, instance_id): +def find_device(ec2, device_id, isinstance=True): """ Attempt to find the EC2 instance and return it """ - reservations = ec2.get_all_reservations(instance_ids=[instance_id]) + if isinstance: + try: + reservations = ec2.get_all_reservations(instance_ids=[device_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] + else: + try: + interfaces = ec2.get_all_network_interfaces(network_interface_ids=[device_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] + if len(interfaces) == 1: + return interfaces[0] - raise EIPException("could not find instance" + instance_id) + raise EIPException("could not find instance" + device_id) -def ensure_present(ec2, domain, address, instance_id, - reuse_existing_ip_allowed, check_mode): +def ensure_present(ec2, domain, address, device_id, + reuse_existing_ip_allowed, check_mode, isinstance=True): changed = False # Return the EIP object since we've been given a public IP @@ -229,28 +252,39 @@ def ensure_present(ec2, domain, address, instance_id, address = allocate_address(ec2, domain, reuse_existing_ip_allowed) changed = True - if instance_id: + if device_id: # Allocate an IP for instance since no public_ip was provided - instance = find_instance(ec2, instance_id) + if isinstance: + instance = find_device(ec2, device_id) + # Associate address object (provided or allocated) with instance + assoc_result = associate_ip_and_device(ec2, address, device_id, + check_mode) + else: + instance = find_device(ec2, device_id, isinstance=False) + # Associate address object (provided or allocated) with instance + assoc_result = associate_ip_and_device(ec2, address, device_id, + check_mode, isinstance=False) + if instance.vpc_id: domain = 'vpc' - # Associate address object (provided or allocated) with instance - assoc_result = associate_ip_and_instance(ec2, address, instance_id, - check_mode) changed = changed or assoc_result['changed'] return {'changed': changed, 'public_ip': address.public_ip} -def ensure_absent(ec2, domain, address, instance_id, check_mode): +def ensure_absent(ec2, domain, address, device_id, check_mode, isinstance=True): if not address: return {'changed': False} # disassociating address from instance - if instance_id: - return disassociate_ip_and_instance(ec2, address, instance_id, - check_mode) + if device_id: + if isinstance: + return disassociate_ip_and_device(ec2, address, device_id, + check_mode) + else: + return disassociate_ip_and_device(ec2, address, device_id, + check_mode, isinstance=False) # releasing address else: return release_address(ec2, address, check_mode) @@ -260,6 +294,7 @@ def main(): argument_spec = ec2_argument_spec() argument_spec.update(dict( instance_id=dict(required=False), + network_interface_id=dict(required=False), public_ip=dict(required=False, aliases=['ip']), state=dict(required=False, default='present', choices=['present', 'absent']), @@ -280,6 +315,7 @@ def main(): ec2 = ec2_connect(module) instance_id = module.params.get('instance_id') + network_interface_id = module.params.get('network_interface_id') public_ip = module.params.get('public_ip') state = module.params.get('state') in_vpc = module.params.get('in_vpc') @@ -287,20 +323,39 @@ def main(): reuse_existing_ip_allowed = module.params.get('reuse_existing_ip_allowed') try: - address = find_address(ec2, public_ip, instance_id) + if network_interface_id: + address = find_address(ec2, public_ip, network_interface_id, isinstance=False) + elif instance_id: + address = find_address(ec2, public_ip, instance_id) + else: + address = False if state == 'present': - result = ensure_present(ec2, domain, address, instance_id, + if instance_id: + result = ensure_present(ec2, domain, address, instance_id, reuse_existing_ip_allowed, module.check_mode) + elif network_interface_id: + result = ensure_present(ec2, domain, address, network_interface_id, + reuse_existing_ip_allowed, + module.check_mode, isinstance=False) + else: + address = allocate_address(ec2, domain, reuse_existing_ip_allowed) + result = {'changed': True, 'public_ip': address.public_ip} else: - result = ensure_absent(ec2, domain, address, instance_id, module.check_mode) + if network_interface_id: + result = ensure_absent(ec2, domain, address, network_interface_id, module.check_mode, isinstance=False) + elif instance_id: + result = ensure_absent(ec2, domain, address, instance_id, module.check_mode) + else: + address = find_address(ec2, public_ip, None) + result = release_address(ec2, address, module.check_mode) + except (boto.exception.EC2ResponseError, EIPException) as e: module.fail_json(msg=str(e)) module.exit_json(**result) - # import module snippets from ansible.module_utils.basic import * # noqa from ansible.module_utils.ec2 import * # noqa