diff --git a/changelogs/fragments/20240107-refactor_ec2_eip-modules.yml b/changelogs/fragments/20240107-refactor_ec2_eip-modules.yml new file mode 100644 index 00000000000..6c3b86b37ad --- /dev/null +++ b/changelogs/fragments/20240107-refactor_ec2_eip-modules.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - ec2_eip - refactored code to use ``AnsibleEC2Error`` as well as moving shared code into module_utils.ec2 (https://github.com/ansible-collections/amazon.aws/pull/2165). - ec2_eip_info - refactored code to use ``AnsibleEC2Error`` as well as moving shared code into module_utils.ec2 (https://github.com/ansible-collections/amazon.aws/pull/2165). \ No newline at end of file diff --git a/plugins/modules/ec2_eip.py b/plugins/modules/ec2_eip.py index 52080ff3685..10a0ef463de 100644 --- a/plugins/modules/ec2_eip.py +++ b/plugins/modules/ec2_eip.py @@ -216,143 +216,80 @@ sample: 52.88.159.209 """ -try: - import botocore.exceptions -except ImportError: - pass # caught by AnsibleAWSModule - -from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple +from typing import Union + +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AnsibleEC2Error +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import allocate_address as allocate_ip_address +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import associate_address +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import describe_addresses +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import describe_instances +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import describe_network_interfaces +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import disassociate_address from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ensure_ec2_tags +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import release_address from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_specifications from ansible_collections.amazon.aws.plugins.module_utils.transformation import ansible_dict_to_boto3_filter_list -class EipError(Exception): - pass - - -def associate_ip_and_device( - ec2, module, address, private_ip_address, device_id, allow_reassociation, check_mode, is_instance=True -): - if address_is_associated_with_device(ec2, module, address, device_id, is_instance): - return {"changed": False} - - # If we're in check mode, nothing else to do - if not check_mode: - if is_instance: - try: - params = dict( - InstanceId=device_id, - AllowReassociation=allow_reassociation, - ) - if private_ip_address: - params["PrivateIpAddress"] = private_ip_address - if address["Domain"] == "vpc": - params["AllocationId"] = address["AllocationId"] - else: - params["PublicIp"] = address["PublicIp"] - res = ec2.associate_address(aws_retry=True, **params) - except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: - msg = f"Couldn't associate Elastic IP address with instance '{device_id}'" - module.fail_json_aws(e, msg=msg) - else: - params = dict( - NetworkInterfaceId=device_id, - AllocationId=address["AllocationId"], - AllowReassociation=allow_reassociation, - ) - - if private_ip_address: - params["PrivateIpAddress"] = private_ip_address - - try: - res = ec2.associate_address(aws_retry=True, **params) - except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: - msg = f"Couldn't associate Elastic IP address with network interface '{device_id}'" - module.fail_json_aws(e, msg=msg) - if not res: - module.fail_json(msg="Association failed.") - - return {"changed": True} - - -def disassociate_ip_and_device(ec2, module, address, device_id, check_mode, is_instance=True): - if not address_is_associated_with_device(ec2, module, address, device_id, is_instance): - return {"changed": False} - - # If we're in check mode, nothing else to do - if not check_mode: - try: - if address["Domain"] == "vpc": - ec2.disassociate_address(AssociationId=address["AssociationId"], aws_retry=True) - else: - ec2.disassociate_address(PublicIp=address["PublicIp"], aws_retry=True) - except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: - module.fail_json_aws(e, msg="Dissassociation of Elastic IP failed") - - return {"changed": True} - - -@AWSRetry.jittered_backoff() -def find_address(ec2, module, public_ip, device_id, is_instance=True): +def find_address( + ec2, public_ip: Optional[str], device_id: Optional[str], is_instance +) -> Optional[Dict[str, Union[str, List[Dict[str, str]]]]]: """Find an existing Elastic IP address""" - filters = [] - kwargs = {} + filters = None + + if not public_ip and not device_id: + return None if public_ip: - kwargs["PublicIps"] = [public_ip] - elif device_id: + params = {"PublicIps": [public_ip]} + else: if is_instance: - filters.append({"Name": "instance-id", "Values": [device_id]}) + filters = [{"Name": "instance-id", "Values": [device_id]}] else: - filters.append({"Name": "network-interface-id", "Values": [device_id]}) + filters = [{"Name": "network-interface-id", "Values": [device_id]}] + params = {"Filters": filters} + + result = None + addresses = describe_addresses(ec2, **params) + if addresses: + if len(addresses) > 1: + raise AnsibleEC2Error(f"Found more than one address using args {params} Addresses found: {addresses}") + result = addresses[0] + return result - if len(filters) > 0: - kwargs["Filters"] = filters - elif len(filters) == 0 and public_ip is None: - return None - try: - addresses = ec2.describe_addresses(**kwargs) - except is_boto3_error_code("InvalidAddress.NotFound") as e: - # If we're releasing and we can't find it, it's already gone... - if module.params.get("state") == "absent": - module.exit_json(changed=False, disassociated=False, released=False) - module.fail_json_aws(e, msg="Couldn't obtain list of existing Elastic IP addresses") - - addresses = addresses["Addresses"] - if len(addresses) == 1: - return addresses[0] - elif len(addresses) > 1: - msg = f"Found more than one address using args {kwargs} Addresses found: {addresses}" - module.fail_json_aws(botocore.exceptions.ClientError, msg=msg) - - -def address_is_associated_with_device(ec2, module, address, device_id, is_instance=True): - """Check if the elastic IP is currently associated with the device""" - address = find_address(ec2, module, address["PublicIp"], device_id, is_instance) - if address: - if is_instance: - if "InstanceId" in address and address["InstanceId"] == device_id: - return address - else: - if "NetworkInterfaceId" in address and address["NetworkInterfaceId"] == device_id: - return address - return False +def address_is_associated_with_device( + ec2, + address: Optional[Dict[str, str]], + device_id: str, + is_instance: bool = True, +) -> Optional[str]: + """Check if the elastic IP is currently associated with the device and return the association Id""" + public_ip = None if not address else address.get("PublicIp") + result = find_address(ec2, public_ip, device_id, is_instance) + association_id = None + if result: + instance_id = result.get("InstanceId") if is_instance else result.get("NetworkInterfaceId") + if instance_id == device_id: + association_id = result.get("AssociationId") + return association_id def allocate_address( - ec2, - module, - domain, - reuse_existing_ip_allowed, - check_mode, - tags, - search_tags=None, - public_ipv4_pool=None, -): + client, + check_mode: bool, + search_tags: Optional[Dict[str, str]], + domain: Optional[str], + reuse_existing_ip_allowed: bool, + tags: Optional[Dict[str, str]], + public_ipv4_pool: Optional[bool] = None, +) -> Tuple[Dict[str, str], bool]: """Allocate a new elastic IP address (when needed) and return it""" if not domain: domain = "standard" @@ -364,12 +301,7 @@ def allocate_address( if search_tags is not None: filters += ansible_dict_to_boto3_filter_list(search_tags) - try: - all_addresses = ec2.describe_addresses(Filters=filters, aws_retry=True) - except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: - module.fail_json_aws(e, msg="Couldn't obtain list of existing Elastic IP addresses") - - all_addresses = all_addresses["Addresses"] + all_addresses = describe_addresses(client, Filters=filters) if domain == "vpc": unassociated_addresses = [a for a in all_addresses if not a.get("AssociationId", None)] @@ -378,203 +310,160 @@ def allocate_address( if unassociated_addresses: return unassociated_addresses[0], False - if public_ipv4_pool: - return ( - allocate_address_from_pool( - ec2, - module, - domain, - check_mode, - public_ipv4_pool, - tags, - ), - True, - ) - params = {"Domain": domain} + if public_ipv4_pool: + params.update({"PublicIpv4Pool": public_ipv4_pool}) if tags: params["TagSpecifications"] = boto3_tag_specifications(tags, types="elastic-ip") - - try: - if check_mode: - return None, True - result = ec2.allocate_address(aws_retry=True, **params), True - except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: - module.fail_json_aws(e, msg="Couldn't allocate Elastic IP address") - return result - - -def release_address(ec2, module, address, check_mode): - """Release a previously allocated elastic IP address""" - - # If we're in check mode, nothing else to do + address = None if not check_mode: - try: - ec2.release_address(AllocationId=address["AllocationId"], aws_retry=True) - except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: - module.fail_json_aws(e, msg="Couldn't release Elastic IP address") - - return {"changed": True} + address = allocate_ip_address(client, **params) + return address, True -@AWSRetry.jittered_backoff() -def describe_eni_with_backoff(ec2, module, device_id): - try: - return ec2.describe_network_interfaces(NetworkInterfaceIds=[device_id]) - except is_boto3_error_code("InvalidNetworkInterfaceID.NotFound") as e: - module.fail_json_aws(e, msg="Couldn't get list of network interfaces.") - - -def find_device(ec2, module, device_id, is_instance=True): +def find_device(client, device_id: str, is_instance: bool) -> Optional[Dict[str, Any]]: """Attempt to find the EC2 instance and return it""" + result = None if is_instance: - try: - paginator = ec2.get_paginator("describe_instances") - reservations = list(paginator.paginate(InstanceIds=[device_id]).search("Reservations[]")) - except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: - module.fail_json_aws(e, msg="Couldn't get list of instances") - + reservations = describe_instances(client, InstanceIds=[device_id]) if len(reservations) == 1: instances = reservations[0]["Instances"] if len(instances) == 1: - return instances[0] + result = instances[0] else: - try: - interfaces = describe_eni_with_backoff(ec2, module, device_id) - except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: - module.fail_json_aws(e, msg="Couldn't get list of network interfaces.") + interfaces = describe_network_interfaces(client, NetworkInterfaceIds=[device_id]) if len(interfaces) == 1: - return interfaces[0] - - -def ensure_present( - ec2, - module, - domain, - address, - private_ip_address, - device_id, - reuse_existing_ip_allowed, - allow_reassociation, - check_mode, - tags, - is_instance=True, -): - changed = False - - # Return the EIP object since we've been given a public IP - if not address: - if check_mode: - return {"changed": True} - - address, changed = allocate_address( - ec2, - module, - domain, - reuse_existing_ip_allowed, - check_mode, - tags, - ) + result = interfaces[0] + return result - if device_id: - # Allocate an IP for instance since no public_ip was provided - if is_instance: - instance = find_device(ec2, module, device_id) - if reuse_existing_ip_allowed: - if instance["VpcId"] and len(instance["VpcId"]) > 0 and domain is None: - msg = "You must set 'in_vpc' to true to associate an instance with an existing ip in a vpc" - module.fail_json_aws(botocore.exceptions.ClientError, msg=msg) - - # Associate address object (provided or allocated) with instance - assoc_result = associate_ip_and_device( - ec2, module, address, private_ip_address, device_id, allow_reassociation, check_mode - ) - else: - instance = find_device(ec2, module, device_id, is_instance=False) - # Associate address object (provided or allocated) with instance - assoc_result = associate_ip_and_device( - ec2, module, address, private_ip_address, device_id, allow_reassociation, check_mode, is_instance=False - ) - changed = changed or assoc_result["changed"] +def generate_tag_dict(module: AnsibleAWSModule) -> Optional[Dict[str, str]]: + """Generates a dictionary to be passed as a filter to Amazon""" + tag_name = module.params.get("tag_name") + tag_value = module.params.get("tag_value") + result = None - return {"changed": changed, "public_ip": address["PublicIp"], "allocation_id": address["AllocationId"]} + if not tag_name: + return result + if not tag_value: + if tag_name.startswith("tag:"): + tag_name = tag_name.strip("tag:") + result = {"tag-key": tag_name} + else: + if not tag_name.startswith("tag:"): + tag_name = "tag:" + tag_name + result = {tag_name: tag_value} -def ensure_absent(ec2, module, address, device_id, check_mode, is_instance=True): - if not address: - return {"changed": False} + return result - # disassociating address from instance - if device_id: - if is_instance: - return disassociate_ip_and_device(ec2, module, address, device_id, check_mode) - else: - return disassociate_ip_and_device(ec2, module, address, device_id, check_mode, is_instance=False) - # releasing address - else: - return release_address(ec2, module, address, check_mode) +def check_is_instance(module: AnsibleAWSModule) -> bool: + device_id = module.params.get("device_id") + in_vpc = module.params.get("in_vpc") + if not device_id: + return False + if device_id.startswith("i-"): + return True -def allocate_address_from_pool( - ec2, - module, - domain, - check_mode, - public_ipv4_pool, - tags, -): - # type: (EC2Connection, AnsibleAWSModule, str, bool, str) -> Address - """Overrides botocore's allocate_address function to support BYOIP""" - if check_mode: - return None + if device_id.startswith("eni-") and not in_vpc: + raise module.fail_json("If you are specifying an ENI, in_vpc must be true") + return False - params = {} - if domain is not None: - params["Domain"] = domain +def ensure_absent( + client: Any, module: AnsibleAWSModule, address: Optional[Dict[str, Any]], is_instance: bool +) -> Dict[str, bool]: + disassociated = False + released = False - if public_ipv4_pool is not None: - params["PublicIpv4Pool"] = public_ipv4_pool + device_id = module.params.get("device_id") + release_on_disassociation = module.params.get("release_on_disassociation") - if tags: - params["TagSpecifications"] = boto3_tag_specifications(tags, types="elastic-ip") + if address: + if device_id: + # disassociating address from instance + association_id = address_is_associated_with_device(client, address, device_id, is_instance) + if association_id: + disassociated = True + if not module.check_mode: + disassociated = disassociate_address(client, association_id=association_id) - try: - result = ec2.allocate_address(aws_retry=True, **params) - except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: - module.fail_json_aws(e, msg="Couldn't allocate Elastic IP address") - return result + if not device_id or (disassociated and release_on_disassociation): + # Release or Release on disassociation + released = True + if not module.check_mode: + released = release_address(client, allocation_id=address["AllocationId"]) + return {"changed": disassociated or released, "disassociated": disassociated, "released": released} -def generate_tag_dict(module, tag_name, tag_value): - # type: (AnsibleAWSModule, str, str) -> Optional[Dict] - """Generates a dictionary to be passed as a filter to Amazon""" - if tag_name and not tag_value: - if tag_name.startswith("tag:"): - tag_name = tag_name.strip("tag:") - return {"tag-key": tag_name} +def ensure_present( + client, module: AnsibleAWSModule, address: Optional[Dict[str, Any]], is_instance: bool +) -> Dict[str, Any]: + device_id = module.params.get("device_id") + private_ip_address = module.params.get("private_ip_address") + in_vpc = module.params.get("in_vpc") + domain = "vpc" if in_vpc else None + reuse_existing_ip_allowed = module.params.get("reuse_existing_ip_allowed") + allow_reassociation = module.params.get("allow_reassociation") + public_ipv4_pool = module.params.get("public_ipv4_pool") + tags = module.params.get("tags") + purge_tags = module.params.get("purge_tags") - elif tag_name and tag_value: - if not tag_name.startswith("tag:"): - tag_name = "tag:" + tag_name - return {tag_name: tag_value} + # Tags for *searching* for an EIP. + search_tags = generate_tag_dict(module) + result = {} + changed = False - elif tag_value and not tag_name: - module.fail_json(msg="parameters are required together: ('tag_name', 'tag_value')") + # Allocate address + if not address: + address, changed = allocate_address( + client, module.check_mode, search_tags, domain, reuse_existing_ip_allowed, tags, public_ipv4_pool + ) + # Associate address to instance + if device_id: + # Find instance + instance = find_device(client, device_id, is_instance) + # Allocate an IP for instance since no public_ip was provided + if is_instance and reuse_existing_ip_allowed: + if instance["VpcId"] and len(instance["VpcId"]) > 0 and domain is None: + raise AnsibleEC2Error( + "You must set 'in_vpc' to true to associate an instance with an existing ip in a vpc" + ) -def check_is_instance(device_id, in_vpc): - if not device_id: - return False - if device_id.startswith("i-"): - return True + # check if the address is already associated to the device + association_id = address_is_associated_with_device(client, address, device_id, is_instance) + if not association_id: + changed = True + if not module.check_mode: + # Associate address object (provided or allocated) with instance + if is_instance: + params = {"InstanceId": device_id, "AllowReassociation": allow_reassociation} + if address.get("Domain") == "vpc": + params["AllocationId"] = address.get("AllocationId") + else: + params["PublicIp"] = address.get("PublicIp") + else: + params = { + "NetworkInterfaceId": device_id, + "AllocationId": address.get("AllocationId"), + "AllowReassociation": allow_reassociation, + } - if device_id.startswith("eni-") and not in_vpc: - raise EipError("If you are specifying an ENI, in_vpc must be true") + if private_ip_address: + params["PrivateIpAddress"] = private_ip_address + associate_address(client, **params) - return False + # Ensure tags + if address: + changed |= ensure_ec2_tags( + client, module, address["AllocationId"], resource_type="elastic-ip", tags=tags, purge_tags=purge_tags + ) + result.update({"public_ip": address["PublicIp"], "allocation_id": address["AllocationId"]}) + result["changed"] = changed + return result def main(): @@ -599,115 +488,28 @@ def main(): supports_check_mode=True, required_by={ "private_ip_address": ["device_id"], + "tag_value": ["tag_name"], }, ) - ec2 = module.client("ec2", retry_decorator=AWSRetry.jittered_backoff()) + ec2 = module.client("ec2") device_id = module.params.get("device_id") public_ip = module.params.get("public_ip") - private_ip_address = module.params.get("private_ip_address") state = module.params.get("state") - in_vpc = module.params.get("in_vpc") - domain = "vpc" if in_vpc else None - reuse_existing_ip_allowed = module.params.get("reuse_existing_ip_allowed") - release_on_disassociation = module.params.get("release_on_disassociation") - allow_reassociation = module.params.get("allow_reassociation") - tag_name = module.params.get("tag_name") - tag_value = module.params.get("tag_value") - public_ipv4_pool = module.params.get("public_ipv4_pool") - tags = module.params.get("tags") - purge_tags = module.params.get("purge_tags") - - try: - is_instance = check_is_instance(device_id, in_vpc) - except EipError as e: - module.fail_json(msg=str(e)) - # Tags for *searching* for an EIP. - search_tags = generate_tag_dict(module, tag_name, tag_value) + is_instance = check_is_instance(module) try: - if device_id: - address = find_address(ec2, module, public_ip, device_id, is_instance=is_instance) - else: - address = find_address(ec2, module, public_ip, None) - + # Find existing address + address = find_address(ec2, public_ip, device_id, is_instance) if state == "present": - if device_id: - result = ensure_present( - ec2, - module, - domain, - address, - private_ip_address, - device_id, - reuse_existing_ip_allowed, - allow_reassociation, - module.check_mode, - tags, - is_instance=is_instance, - ) - if "allocation_id" not in result: - # Don't check tags on check_mode here - no EIP to pass through - module.exit_json(**result) - else: - if address: - result = { - "changed": False, - "public_ip": address["PublicIp"], - "allocation_id": address["AllocationId"], - } - else: - address, changed = allocate_address( - ec2, - module, - domain, - reuse_existing_ip_allowed, - module.check_mode, - tags, - search_tags, - public_ipv4_pool, - ) - if address: - result = { - "changed": changed, - "public_ip": address["PublicIp"], - "allocation_id": address["AllocationId"], - } - else: - # Don't check tags on check_mode here - no EIP to pass through - result = {"changed": changed} - module.exit_json(**result) - - result["changed"] |= ensure_ec2_tags( - ec2, module, result["allocation_id"], resource_type="elastic-ip", tags=tags, purge_tags=purge_tags - ) + result = ensure_present(ec2, module, address, is_instance) else: - if device_id: - disassociated = ensure_absent( - ec2, module, address, device_id, module.check_mode, is_instance=is_instance - ) - - if release_on_disassociation and disassociated["changed"]: - released = release_address(ec2, module, address, module.check_mode) - result = { - "changed": True, - "disassociated": disassociated["changed"], - "released": released["changed"], - } - else: - result = { - "changed": disassociated["changed"], - "disassociated": disassociated["changed"], - "released": False, - } - else: - released = release_address(ec2, module, address, module.check_mode) - result = {"changed": released["changed"], "disassociated": False, "released": released["changed"]} + result = ensure_absent(ec2, module, address, is_instance) - except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: - module.fail_json_aws(str(e)) + except AnsibleEC2Error as e: + module.fail_json_aws_error(e) module.exit_json(**result) diff --git a/plugins/modules/ec2_eip_info.py b/plugins/modules/ec2_eip_info.py index 18d03160e3e..b50cd20365e 100644 --- a/plugins/modules/ec2_eip_info.py +++ b/plugins/modules/ec2_eip_info.py @@ -133,29 +133,24 @@ } """ -try: - from botocore.exceptions import BotoCoreError - from botocore.exceptions import ClientError -except ImportError: - pass # caught by imported AnsibleAWSModule - from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AnsibleEC2Error +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import describe_addresses from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_list_to_ansible_dict from ansible_collections.amazon.aws.plugins.module_utils.transformation import ansible_dict_to_boto3_filter_list def get_eips_details(module): - connection = module.client("ec2", retry_decorator=AWSRetry.jittered_backoff()) + connection = module.client("ec2") filters = module.params.get("filters") try: - response = connection.describe_addresses(aws_retry=True, Filters=ansible_dict_to_boto3_filter_list(filters)) - except (BotoCoreError, ClientError) as e: + addresses = describe_addresses(connection, Filters=ansible_dict_to_boto3_filter_list(filters)) + except AnsibleEC2Error as e: module.fail_json_aws(e, msg="Error retrieving EIPs") - addresses = camel_dict_to_snake_dict(response)["addresses"] + addresses = [camel_dict_to_snake_dict(addr) for addr in addresses] for address in addresses: if "tags" in address: address["tags"] = boto3_tag_list_to_ansible_dict(address["tags"]) diff --git a/tests/integration/targets/ec2_eip/aliases b/tests/integration/targets/ec2_eip/aliases index 97936fdf3d8..546342abaf6 100644 --- a/tests/integration/targets/ec2_eip/aliases +++ b/tests/integration/targets/ec2_eip/aliases @@ -1,4 +1,4 @@ -unstable +time=5m cloud/aws ec2_eip_info diff --git a/tests/integration/targets/ec2_eip/tasks/allocate.yml b/tests/integration/targets/ec2_eip/tasks/allocate.yml new file mode 100644 index 00000000000..85d7e6a67f7 --- /dev/null +++ b/tests/integration/targets/ec2_eip/tasks/allocate.yml @@ -0,0 +1,146 @@ +- name: Test EIP allocation + block: + # ------------------------------------------------------------------------------------------ + # Allocate EIP with no condition + # ------------------------------------------------------------------------------------------ + - name: Get current state of EIPs + amazon.aws.ec2_eip_info: + register: eip_info_start + + - name: Allocate a new EIP with no conditions - check_mode + amazon.aws.ec2_eip: + state: present + tags: + AnsibleEIPTestPrefix: "{{ resource_prefix }}" + register: eip + check_mode: true + + - ansible.builtin.assert: + that: + - eip is changed + + - name: Allocate a new EIP with no conditions + amazon.aws.ec2_eip: + state: present + tags: + AnsibleEIPTestPrefix: "{{ resource_prefix }}" + register: eip + + - amazon.aws.ec2_eip_info: + register: eip_info + check_mode: true + + - ansible.builtin.assert: + that: + - eip is changed + - "'ec2:CreateTags' not in eip.resource_actions" + - "'ec2:DeleteTags' not in eip.resource_actions" + - eip.public_ip is defined and ( eip.public_ip | ansible.utils.ipaddr ) + - eip.allocation_id is defined and eip.allocation_id.startswith("eipalloc-") + - ( eip_info_start.addresses | length ) + 1 == ( eip_info.addresses | length ) + + - name: Get EIP info via public ip + amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - '"addresses" in eip_info' + - eip_info.addresses | length == 1 + - eip_info.addresses[0].allocation_id == eip.allocation_id + - eip_info.addresses[0].domain == "vpc" + - eip_info.addresses[0].public_ip == eip.public_ip + - '"AnsibleEIPTestPrefix" in eip_info.addresses[0].tags' + - eip_info.addresses[0].tags['AnsibleEIPTestPrefix'] == resource_prefix + + - name: Get EIP info via allocation id + amazon.aws.ec2_eip_info: + filters: + allocation-id: "{{ eip.allocation_id }}" + register: eip_info + + - ansible.builtin.assert: + that: + - '"addresses" in eip_info' + - eip_info.addresses | length == 1 + - eip_info.addresses[0].allocation_id == eip.allocation_id + - eip_info.addresses[0].domain == "vpc" + - eip_info.addresses[0].public_ip == eip.public_ip + - '"AnsibleEIPTestPrefix" in eip_info.addresses[0].tags' + - eip_info.addresses[0].tags['AnsibleEIPTestPrefix'] == resource_prefix + + - name: Allocate a new ip (idempotence) - check_mode + amazon.aws.ec2_eip: + state: present + public_ip: "{{ eip.public_ip }}" + register: eip + check_mode: true + + - ansible.builtin.assert: + that: + - eip is not changed + + - name: Allocate a new ip (idempotence) + amazon.aws.ec2_eip: + state: present + public_ip: "{{ eip.public_ip }}" + register: eip + + - amazon.aws.ec2_eip_info: + register: eip_info + + - ansible.builtin.assert: + that: + - eip is not changed + - eip.public_ip is defined and ( eip.public_ip | ansible.utils.ipaddr ) + - eip.allocation_id is defined and eip.allocation_id.startswith("eipalloc-") + - ( eip_info_start.addresses | length ) + 1 == ( eip_info.addresses | length ) + + # ------------------------------------------------------------------------------------------ + # Allocate EIP from a pool + # ------------------------------------------------------------------------------------------ + - name: Get current state of EIPs + amazon.aws.ec2_eip_info: + register: eip_info_start + + - name: Allocate a new EIP from a pool - check_mode + amazon.aws.ec2_eip: + state: present + public_ipv4_pool: amazon + register: eip_pool + check_mode: true + + - ansible.builtin.assert: + that: + - eip_pool is changed + + - name: Allocate a new EIP from a pool + amazon.aws.ec2_eip: + state: present + public_ipv4_pool: amazon + register: eip_pool + + - amazon.aws.ec2_eip_info: + register: eip_info + + - ansible.builtin.assert: + that: + - eip_pool is changed + - eip_pool.public_ip is defined and ( eip_pool.public_ip | ansible.utils.ipaddr ) + - eip_pool.allocation_id is defined and eip_pool.allocation_id.startswith("eipalloc-") + - ( eip_info_start.addresses | length ) + 1 == ( eip_info.addresses | length ) + + always: + - name: Release EIP + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + when: eip is defined + + - name: Release EIP created from pool + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip_pool.public_ip }}" + when: eip_pool is defined diff --git a/tests/integration/targets/ec2_eip/tasks/attach_detach_to_eni.yml b/tests/integration/targets/ec2_eip/tasks/attach_detach_to_eni.yml new file mode 100644 index 00000000000..516b727f3f8 --- /dev/null +++ b/tests/integration/targets/ec2_eip/tasks/attach_detach_to_eni.yml @@ -0,0 +1,331 @@ +--- +- name: Test attach EIP + block: + - name: Allocate a new EIP with no conditions + amazon.aws.ec2_eip: + state: present + tags: + AnsibleEIPTestPrefix: "{{ resource_prefix }}" + register: eip + + # ------------------------------------------------------------------------------------------ + # Test attach EIP to ENI + # ------------------------------------------------------------------------------------------ + - name: Attach EIP to ENI A - check_mode + amazon.aws.ec2_eip: + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_a.interface.id }}" + register: associate_eip + check_mode: true + + - ansible.builtin.assert: + that: + - associate_eip is changed + + - name: Attach EIP to ENI A + amazon.aws.ec2_eip: + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_a.interface.id }}" + register: associate_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - associate_eip is changed + - eip_info.addresses | length == 1 + - associate_eip.public_ip is defined and eip.public_ip == associate_eip.public_ip + - associate_eip.allocation_id is defined and eip.allocation_id == associate_eip.allocation_id + - eip_info.addresses[0].allocation_id == eip.allocation_id + - eip_info.addresses[0].domain == "vpc" + - eip_info.addresses[0].public_ip == eip.public_ip + - eip_info.addresses[0].association_id is defined and eip_info.addresses[0].association_id.startswith("eipassoc-") + - eip_info.addresses[0].network_interface_id == eni_create_a.interface.id + - eip_info.addresses[0].private_ip_address is defined and ( eip_info.addresses[0].private_ip_address | ansible.utils.ipaddr ) + - eip_info.addresses[0].network_interface_owner_id == caller_info.account + + - name: Attach EIP to ENI A (idempotence) - check_mode + amazon.aws.ec2_eip: + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_a.interface.id }}" + register: associate_eip + check_mode: true + + - ansible.builtin.assert: + that: + - associate_eip is not changed + + - name: Attach EIP to ENI A (idempotence) + amazon.aws.ec2_eip: + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_a.interface.id }}" + register: associate_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - associate_eip is not changed + - associate_eip.public_ip is defined and eip.public_ip == associate_eip.public_ip + - associate_eip.allocation_id is defined and eip.allocation_id == associate_eip.allocation_id + - eip_info.addresses | length == 1 + - eip_info.addresses[0].allocation_id == eip.allocation_id + - eip_info.addresses[0].domain == "vpc" + - eip_info.addresses[0].public_ip == eip.public_ip + - eip_info.addresses[0].association_id is defined and eip_info.addresses[0].association_id.startswith("eipassoc-") + - eip_info.addresses[0].network_interface_id == eni_create_a.interface.id + - eip_info.addresses[0].private_ip_address is defined and ( eip_info.addresses[0].private_ip_address | ansible.utils.ipaddr ) + + # ------------------------------------------------------------------------------------------ + # Test attach EIP already attached + # ------------------------------------------------------------------------------------------ + - name: Attach EIP to ENI B (should fail, already associated) + amazon.aws.ec2_eip: + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_b.interface.id }}" + register: associate_eip + ignore_errors: true + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - associate_eip is failed + - eip_info.addresses | length == 1 + - eip_info.addresses[0].allocation_id == eip.allocation_id + - eip_info.addresses[0].domain == "vpc" + - eip_info.addresses[0].public_ip == eip.public_ip + - eip_info.addresses[0].association_id is defined and eip_info.addresses[0].association_id.startswith("eipassoc-") + - eip_info.addresses[0].network_interface_id == eni_create_a.interface.id + - eip_info.addresses[0].private_ip_address is defined and ( eip_info.addresses[0].private_ip_address | ansible.utils.ipaddr ) + + - name: Attach EIP to ENI B - check_mode + amazon.aws.ec2_eip: + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_b.interface.id }}" + allow_reassociation: true + register: associate_eip + check_mode: true + + - ansible.builtin.assert: + that: + - associate_eip is changed + + - name: Attach EIP to ENI B + amazon.aws.ec2_eip: + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_b.interface.id }}" + allow_reassociation: true + register: associate_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - associate_eip is changed + - associate_eip.public_ip is defined and eip.public_ip == associate_eip.public_ip + - associate_eip.allocation_id is defined and eip.allocation_id == associate_eip.allocation_id + - eip_info.addresses | length == 1 + - eip_info.addresses[0].allocation_id == eip.allocation_id + - eip_info.addresses[0].domain == "vpc" + - eip_info.addresses[0].public_ip == eip.public_ip + - eip_info.addresses[0].association_id is defined and eip_info.addresses[0].association_id.startswith("eipassoc-") + - eip_info.addresses[0].network_interface_id == eni_create_b.interface.id + - eip_info.addresses[0].private_ip_address is defined and ( eip_info.addresses[0].private_ip_address | ansible.utils.ipaddr ) + + - name: Attach EIP to ENI B (idempotence) - check_mode + amazon.aws.ec2_eip: + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_b.interface.id }}" + allow_reassociation: true + register: associate_eip + check_mode: true + + - ansible.builtin.assert: + that: + - associate_eip is not changed + + - name: Attach EIP to ENI B (idempotence) + amazon.aws.ec2_eip: + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_b.interface.id }}" + allow_reassociation: true + register: associate_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - associate_eip is not changed + - associate_eip.public_ip is defined and eip.public_ip == associate_eip.public_ip + - associate_eip.allocation_id is defined and eip.allocation_id == associate_eip.allocation_id + - eip_info.addresses | length == 1 + - eip_info.addresses[0].allocation_id == eip.allocation_id + - eip_info.addresses[0].domain == "vpc" + - eip_info.addresses[0].public_ip == eip.public_ip + - eip_info.addresses[0].association_id is defined and eip_info.addresses[0].association_id.startswith("eipassoc-") + - eip_info.addresses[0].network_interface_id == eni_create_b.interface.id + - eip_info.addresses[0].private_ip_address is defined and ( eip_info.addresses[0].private_ip_address | ansible.utils.ipaddr ) + + # ------------------------------------------------------------------------------------------ + # Detach EIP from ENI B without enabling release on disassociation + # ------------------------------------------------------------------------------------------ + - name: Detach EIP from ENI B, without enabling release on disassociation - check_mode + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_b.interface.id }}" + register: disassociate_eip + check_mode: true + + - ansible.builtin.assert: + that: + - disassociate_eip is changed + + - name: Detach EIP from ENI B, without enabling release on disassociation + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_b.interface.id }}" + register: disassociate_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - disassociate_eip.changed + - disassociate_eip.disassociated + - not disassociate_eip.released + - eip_info.addresses | length == 1 + + - name: Detach EIP from ENI B, without enabling release on disassociation (idempotence) - check_mode + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_b.interface.id }}" + register: disassociate_eip + check_mode: true + + - ansible.builtin.assert: + that: + - disassociate_eip is not changed + + - name: Detach EIP from ENI B, without enabling release on disassociation (idempotence) + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_b.interface.id }}" + register: disassociate_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - not disassociate_eip.changed + - not disassociate_eip.disassociated + - not disassociate_eip.released + - eip_info.addresses | length == 1 + + # ------------------------------------------------------------------------------------------ + # Detach EIP from ENI A enabling release on disassociation + # ------------------------------------------------------------------------------------------ + + - name: Attach EIP to ENI A + amazon.aws.ec2_eip: + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_a.interface.id }}" + register: associate_eip + + - name: Detach EIP from ENI A, enabling release on disassociation - check_mode + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_a.interface.id }}" + release_on_disassociation: true + register: disassociate_eip + check_mode: true + + - ansible.builtin.assert: + that: + - disassociate_eip is changed + + - name: Detach EIP from ENI A, enabling release on disassociation + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_a.interface.id }}" + release_on_disassociation: true + register: disassociate_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - disassociate_eip.changed + - disassociate_eip.disassociated + - disassociate_eip.released + - eip_info.addresses | length == 0 + + - name: Detach EIP from ENI A, enabling release on disassociation (idempotence) - check_mode + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_a.interface.id }}" + release_on_disassociation: true + register: disassociate_eip + check_mode: true + + - ansible.builtin.assert: + that: + - disassociate_eip is not changed + + - name: Detach EIP from ENI A, enabling release on disassociation (idempotence) + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + device_id: "{{ eni_create_a.interface.id }}" + release_on_disassociation: true + register: disassociate_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - not disassociate_eip.changed + - not disassociate_eip.disassociated + - not disassociate_eip.released + - eip_info.addresses | length == 0 + + always: + - name: Release EIP + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + when: eip is defined diff --git a/tests/integration/targets/ec2_eip/tasks/attach_detach_to_instance.yml b/tests/integration/targets/ec2_eip/tasks/attach_detach_to_instance.yml new file mode 100644 index 00000000000..fdc101432d8 --- /dev/null +++ b/tests/integration/targets/ec2_eip/tasks/attach_detach_to_instance.yml @@ -0,0 +1,289 @@ +--- +- name: Test attach/detach EIP to instance + block: + - name: Create instance for attaching + amazon.aws.ec2_instance: + name: "{{ resource_prefix }}-instance" + image_id: "{{ ec2_ami_id }}" + security_group: "{{ security_group.group_id }}" + vpc_subnet_id: "{{ vpc_subnet_create.subnet.id }}" + instance_type: t2.micro + wait: true + state: running + register: create_ec2_instance_result + + # ------------------------------------------------------------------------------------------ + # Create EIP and attach to EC2 instance + # ------------------------------------------------------------------------------------------ + - name: Attach EIP to an EC2 instance - check_mode + amazon.aws.ec2_eip: + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + state: present + release_on_disassociation: true + register: instance_eip + check_mode: true + + - ansible.builtin.assert: + that: + - instance_eip is changed + + - name: Attach EIP to an EC2 instance + amazon.aws.ec2_eip: + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + state: present + release_on_disassociation: true + register: instance_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ instance_eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - instance_eip is changed + - eip_info.addresses[0].allocation_id is defined + - eip_info.addresses[0].instance_id == create_ec2_instance_result.instance_ids[0] + + - name: Attach EIP to an EC2 instance (idempotence) - check_mode + amazon.aws.ec2_eip: + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + state: present + release_on_disassociation: true + register: instance_eip + check_mode: true + + - ansible.builtin.assert: + that: + - instance_eip is not changed + + - name: Attach EIP to an EC2 instance (idempotence) + amazon.aws.ec2_eip: + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + state: present + release_on_disassociation: true + register: instance_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ instance_eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - instance_eip is not changed + - eip_info.addresses[0].allocation_id is defined + - eip_info.addresses[0].instance_id == create_ec2_instance_result.instance_ids[0] + + # ------------------------------------------------------------------------------------------ + # Detach EIP to EC2 instance + # ------------------------------------------------------------------------------------------ + - name: Detach EIP from EC2 instance, without enabling release on disassociation - check_mode + amazon.aws.ec2_eip: + state: absent + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + register: detach_eip + check_mode: true + + - ansible.builtin.assert: + that: + - detach_eip is changed + + - name: Detach EIP from EC2 instance, without enabling release on disassociation + amazon.aws.ec2_eip: + state: absent + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + register: detach_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ instance_eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - detach_eip.changed + - detach_eip.disassociated + - not detach_eip.released + - eip_info.addresses | length == 1 + + - name: Detach EIP from EC2 instance, without enabling release on disassociation (idempotence) - check_mode + amazon.aws.ec2_eip: + state: absent + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + register: detach_eip + check_mode: true + + - ansible.builtin.assert: + that: + - detach_eip is not changed + + - name: Detach EIP from EC2 instance, without enabling release on disassociation (idempotence) + amazon.aws.ec2_eip: + state: absent + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + register: detach_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ instance_eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - not detach_eip.changed + - not detach_eip.disassociated + - not detach_eip.released + - eip_info.addresses | length == 1 + + - name: Release EIP + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ instance_eip.public_ip }}" + + # ------------------------------------------------------------------------------------------ + # Attach EIP to EC2 instance with Private IP specified + # ------------------------------------------------------------------------------------------ + - name: Attach EIP to an EC2 instance with private Ip specified - check_mode + amazon.aws.ec2_eip: + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + private_ip_address: "{{ create_ec2_instance_result.instances[0].private_ip_address }}" + state: present + release_on_disassociation: true + register: instance_eip + check_mode: true + + - ansible.builtin.assert: + that: + - instance_eip is changed + + - name: Attach EIP to an EC2 instance with private Ip specified + amazon.aws.ec2_eip: + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + private_ip_address: "{{ create_ec2_instance_result.instances[0].private_ip_address }}" + state: present + release_on_disassociation: true + register: instance_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ instance_eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - instance_eip is changed + - eip_info.addresses[0].allocation_id is defined + - eip_info.addresses[0].instance_id == create_ec2_instance_result.instance_ids[0] + + - name: Attach EIP to an EC2 instance with private Ip specified (idempotence) - check_mode + amazon.aws.ec2_eip: + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + private_ip_address: "{{ create_ec2_instance_result.instances[0].private_ip_address }}" + state: present + release_on_disassociation: true + register: instance_eip + check_mode: true + + - ansible.builtin.assert: + that: + - instance_eip is not changed + + - name: Attach EIP to an EC2 instance with private Ip specified (idempotence) + amazon.aws.ec2_eip: + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + private_ip_address: "{{ create_ec2_instance_result.instances[0].private_ip_address }}" + state: present + release_on_disassociation: true + register: instance_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ instance_eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - instance_eip is not changed + - eip_info.addresses[0].allocation_id is defined + - eip_info.addresses[0].instance_id == create_ec2_instance_result.instance_ids[0] + + # ------------------------------------------------------------------------------------------ + # Detach EIP from EC2 instance enabling release on disassociation + # ------------------------------------------------------------------------------------------ + - name: Detach EIP from EC2 instance, enabling release on disassociation - check_mode + amazon.aws.ec2_eip: + state: absent + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + release_on_disassociation: true + register: disassociate_eip + check_mode: true + + - ansible.builtin.assert: + that: + - disassociate_eip is changed + + - name: Detach EIP from EC2 instance, enabling release on disassociation + amazon.aws.ec2_eip: + state: absent + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + release_on_disassociation: true + register: disassociate_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ instance_eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - disassociate_eip.changed + - disassociate_eip.disassociated + - disassociate_eip.released + - eip_info.addresses | length == 0 + + - name: Detach EIP from EC2 instance, enabling release on disassociation (idempotence) - check_mode + amazon.aws.ec2_eip: + state: absent + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + release_on_disassociation: true + register: disassociate_eip + check_mode: true + + - ansible.builtin.assert: + that: + - disassociate_eip is not changed + + - name: Detach EIP from EC2 instance, enabling release on disassociation (idempotence) + amazon.aws.ec2_eip: + state: absent + device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" + release_on_disassociation: true + register: disassociate_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ instance_eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - not disassociate_eip.changed + - not disassociate_eip.disassociated + - not disassociate_eip.released + - eip_info.addresses | length == 0 + + always: + - name: Cleanup instance (by id) + amazon.aws.ec2_instance: + instance_ids: "{{ create_ec2_instance_result.instance_ids }}" + state: absent + wait: true + ignore_errors: true + when: create_ec2_instance_result is defined + + - name: Release EIP + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ instance_eip.public_ip }}" + when: instance_eip is defined diff --git a/tests/integration/targets/ec2_eip/tasks/main.yml b/tests/integration/targets/ec2_eip/tasks/main.yml index df19c6f9b68..9523fd1ac33 100644 --- a/tests/integration/targets/ec2_eip/tasks/main.yml +++ b/tests/integration/targets/ec2_eip/tasks/main.yml @@ -10,1389 +10,13 @@ in_vpc: true block: - - name: Get the current caller identity facts - amazon.aws.aws_caller_info: - register: caller_info - - - name: List available AZs - amazon.aws.aws_az_info: - register: region_azs - - - name: Create a VPC - amazon.aws.ec2_vpc_net: - name: "{{ resource_prefix }}-vpc" - state: present - cidr_block: "{{ vpc_cidr }}" - tags: - AnsibleEIPTest: Pending - AnsibleEIPTestPrefix: "{{ resource_prefix }}" - register: vpc_result - - - name: Look for signs of concurrent EIP tests. Pause if they are running or their prefix comes before ours. - vars: - running_query: vpcs[?tags.AnsibleEIPTest=='Running'] - pending_query: vpcs[?tags.AnsibleEIPTest=='Pending'].tags.AnsibleEIPTestPrefix - amazon.aws.ec2_vpc_net_info: - filters: - tag:AnsibleEIPTest: - - Pending - - Running - register: vpc_info - retries: 10 - delay: 5 - until: - - ( vpc_info.vpcs | map(attribute='tags') | selectattr('AnsibleEIPTest', 'equalto', 'Running') | length == 0 ) - - ( vpc_info.vpcs | map(attribute='tags') | selectattr('AnsibleEIPTest', 'equalto', 'Pending') | map(attribute='AnsibleEIPTestPrefix') | sort | first == resource_prefix - ) - - - name: Create subnet - amazon.aws.ec2_vpc_subnet: - cidr: "{{ subnet_cidr }}" - az: "{{ subnet_az }}" - vpc_id: "{{ vpc_result.vpc.id }}" - state: present - register: vpc_subnet_create - - - name: Create internet gateway - amazon.aws.ec2_vpc_igw: - state: present - vpc_id: "{{ vpc_result.vpc.id }}" - register: vpc_igw - - - name: Create security group - amazon.aws.ec2_security_group: - state: present - name: "{{ resource_prefix }}-sg" - description: a security group for ansible tests - vpc_id: "{{ vpc_result.vpc.id }}" - rules: - - proto: tcp - from_port: 22 - to_port: 22 - cidr_ip: "0.0.0.0/0" - register: security_group - - - name: Create instance for attaching - amazon.aws.ec2_instance: - name: "{{ resource_prefix }}-instance" - image_id: "{{ ec2_ami_id }}" - security_group: "{{ security_group.group_id }}" - vpc_subnet_id: "{{ vpc_subnet_create.subnet.id }}" - wait: true - state: running - register: create_ec2_instance_result - - - name: Create ENI A - amazon.aws.ec2_eni: - subnet_id: "{{ vpc_subnet_create.subnet.id }}" - register: eni_create_a - - - name: Create ENI B - amazon.aws.ec2_eni: - subnet_id: "{{ vpc_subnet_create.subnet.id }}" - register: eni_create_b - - - name: Make a crude lock - amazon.aws.ec2_vpc_net: - name: "{{ resource_prefix }}-vpc" - state: present - cidr_block: "{{ vpc_cidr }}" - tags: - AnsibleEIPTest: Running - AnsibleEIPTestPrefix: "{{ resource_prefix }}" - - - name: Get current state of EIPs - amazon.aws.ec2_eip_info: - register: eip_info_start - - - name: Require that there are no free IPs when we start, otherwise we can't test things properly - ansible.builtin.assert: - that: - - '"addresses" in eip_info_start' - - ( eip_info_start.addresses | length ) == ( eip_info_start.addresses | select('match', 'association_id') | length ) - - # ------------------------------------------------------------------------------------------ - - - name: Allocate a new EIP with no conditions - check_mode - amazon.aws.ec2_eip: - state: present - tags: - AnsibleEIPTestPrefix: "{{ resource_prefix }}" - register: eip - check_mode: true - - - ansible.builtin.assert: - that: - - eip is changed - - - name: Allocate a new EIP with no conditions - amazon.aws.ec2_eip: - state: present - tags: - AnsibleEIPTestPrefix: "{{ resource_prefix }}" - register: eip - - - amazon.aws.ec2_eip_info: - register: eip_info - check_mode: true - - - ansible.builtin.assert: - that: - - eip is changed - - "'ec2:CreateTags' not in eip.resource_actions" - - "'ec2:DeleteTags' not in eip.resource_actions" - - eip.public_ip is defined and ( eip.public_ip | ansible.utils.ipaddr ) - - eip.allocation_id is defined and eip.allocation_id.startswith("eipalloc-") - - ( eip_info_start.addresses | length ) + 1 == ( eip_info.addresses | length ) - - - name: Get EIP info via public ip - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - '"addresses" in eip_info' - - eip_info.addresses | length == 1 - - eip_info.addresses[0].allocation_id == eip.allocation_id - - eip_info.addresses[0].domain == "vpc" - - eip_info.addresses[0].public_ip == eip.public_ip - - '"AnsibleEIPTestPrefix" in eip_info.addresses[0].tags' - - eip_info.addresses[0].tags['AnsibleEIPTestPrefix'] == resource_prefix - - - name: Get EIP info via allocation id - amazon.aws.ec2_eip_info: - filters: - allocation-id: "{{ eip.allocation_id }}" - register: eip_info - - - ansible.builtin.assert: - that: - - '"addresses" in eip_info' - - eip_info.addresses | length == 1 - - eip_info.addresses[0].allocation_id == eip.allocation_id - - eip_info.addresses[0].domain == "vpc" - - eip_info.addresses[0].public_ip == eip.public_ip - - '"AnsibleEIPTestPrefix" in eip_info.addresses[0].tags' - - eip_info.addresses[0].tags['AnsibleEIPTestPrefix'] == resource_prefix - - - name: Allocate a new ip (idempotence) - check_mode - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - register: eip - check_mode: true - - - ansible.builtin.assert: - that: - - eip is not changed - - - name: Allocate a new ip (idempotence) - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - register: eip - - - amazon.aws.ec2_eip_info: - register: eip_info - - - ansible.builtin.assert: - that: - - eip is not changed - - eip.public_ip is defined and ( eip.public_ip | ansible.utils.ipaddr ) - - eip.allocation_id is defined and eip.allocation_id.startswith("eipalloc-") - - ( eip_info_start.addresses | length ) + 1 == ( eip_info.addresses | length ) - - # ------------------------------------------------------------------------------------------ - - - name: Release EIP - check_mode - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ eip.public_ip }}" - register: eip_release - check_mode: true - - - ansible.builtin.assert: - that: - - eip_release.changed - - - name: Release eip - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ eip.public_ip }}" - register: eip_release - - - amazon.aws.ec2_eip_info: - register: eip_info - - - ansible.builtin.assert: - that: - - eip_release.changed - - not eip_release.disassociated - - eip_release.released - - ( eip_info_start.addresses | length ) == ( eip_info.addresses | length ) - - - name: Release EIP (idempotence) - check_mode - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ eip.public_ip }}" - register: eip_release - check_mode: true - - - ansible.builtin.assert: - that: - - eip_release is not changed - - - name: Release EIP (idempotence) - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ eip.public_ip }}" - register: eip_release - - - amazon.aws.ec2_eip_info: - register: eip_info - - - ansible.builtin.assert: - that: - - not eip_release.changed - - not eip_release.disassociated - - not eip_release.released - - ( eip_info_start.addresses | length ) == ( eip_info.addresses | length ) - - # ------------------------------------------------------------------------------------------ - - - name: Allocate a new EIP - attempt reusing unallocated ones (none available) - check_mode - amazon.aws.ec2_eip: - state: present - reuse_existing_ip_allowed: true - register: eip - check_mode: true - - - ansible.builtin.assert: - that: - - eip is changed - - - name: Allocate a new EIP - attempt reusing unallocated ones (none available) - amazon.aws.ec2_eip: - state: present - reuse_existing_ip_allowed: true - register: eip - - - amazon.aws.ec2_eip_info: - register: eip_info - - - ansible.builtin.assert: - that: - - eip is changed - - eip.public_ip is defined and ( eip.public_ip | ansible.utils.ipaddr ) - - eip.allocation_id is defined and eip.allocation_id.startswith("eipalloc-") - - ( eip_info_start.addresses | length ) + 1 == ( eip_info.addresses | length ) - - - name: Re-Allocate a new EIP - attempt reusing unallocated ones (one available) - check_mode - amazon.aws.ec2_eip: - state: present - reuse_existing_ip_allowed: true - register: reallocate_eip - check_mode: true - - - ansible.builtin.assert: - that: - - reallocate_eip is not changed - - - name: Re-Allocate a new EIP - attempt reusing unallocated ones (one available) - amazon.aws.ec2_eip: - state: present - reuse_existing_ip_allowed: true - register: reallocate_eip - - - amazon.aws.ec2_eip_info: - register: eip_info - - - ansible.builtin.assert: - that: - - reallocate_eip is not changed - - reallocate_eip.public_ip is defined and ( reallocate_eip.public_ip | ansible.utils.ipaddr ) - - reallocate_eip.allocation_id is defined and reallocate_eip.allocation_id.startswith("eipalloc-") - - ( eip_info_start.addresses | length ) + 1 == ( eip_info.addresses | length ) - - # ------------------------------------------------------------------------------------------ - - - name: attempt reusing an existing EIP with a tag (No match available) - check_mode - amazon.aws.ec2_eip: - state: present - reuse_existing_ip_allowed: true - tag_name: Team - register: no_tagged_eip - check_mode: true - - - ansible.builtin.assert: - that: - - no_tagged_eip is changed - - - name: attempt reusing an existing EIP with a tag (No match available) - amazon.aws.ec2_eip: - state: present - reuse_existing_ip_allowed: true - tag_name: Team - register: no_tagged_eip - - - amazon.aws.ec2_eip_info: - register: eip_info - - - ansible.builtin.assert: - that: - - no_tagged_eip is changed - - no_tagged_eip.public_ip is defined and ( no_tagged_eip.public_ip | ansible.utils.ipaddr ) - - no_tagged_eip.allocation_id is defined and no_tagged_eip.allocation_id.startswith("eipalloc-") - - ( eip_info_start.addresses | length ) + 2 == ( eip_info.addresses | length ) - - # ------------------------------------------------------------------------------------------ - - - name: Tag EIP so we can try matching it - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - tags: - Team: Frontend - - - name: Attempt reusing an existing EIP with a tag (Match available) - check_mode - amazon.aws.ec2_eip: - state: present - reuse_existing_ip_allowed: true - tag_name: Team - register: reallocate_eip - check_mode: true - - - ansible.builtin.assert: - that: - - reallocate_eip is not changed - - - name: Attempt reusing an existing EIP with a tag (Match available) - amazon.aws.ec2_eip: - state: present - reuse_existing_ip_allowed: true - tag_name: Team - register: reallocate_eip - - - amazon.aws.ec2_eip_info: - register: eip_info - - - ansible.builtin.assert: - that: - - reallocate_eip is not changed - - reallocate_eip.public_ip is defined and ( reallocate_eip.public_ip | ansible.utils.ipaddr ) - - reallocate_eip.allocation_id is defined and reallocate_eip.allocation_id.startswith("eipalloc-") - - ( eip_info_start.addresses | length ) + 2 == ( eip_info.addresses | length ) - - - name: Attempt reusing an existing EIP with a tag and it's value (no match available) - check_mode - amazon.aws.ec2_eip: - state: present - reuse_existing_ip_allowed: true - tag_name: Team - tag_value: Backend - register: backend_eip - check_mode: true - - - ansible.builtin.assert: - that: - - backend_eip is changed - - - name: Attempt reusing an existing EIP with a tag and it's value (no match available) - amazon.aws.ec2_eip: - state: present - reuse_existing_ip_allowed: true - tag_name: Team - tag_value: Backend - register: backend_eip - - - amazon.aws.ec2_eip_info: - register: eip_info - - - ansible.builtin.assert: - that: - - backend_eip is changed - - backend_eip.public_ip is defined and ( backend_eip.public_ip | ansible.utils.ipaddr ) - - backend_eip.allocation_id is defined and backend_eip.allocation_id.startswith("eipalloc-") - - ( eip_info_start.addresses | length ) + 3 == ( eip_info.addresses | length ) - - # ------------------------------------------------------------------------------------------ - - - name: Tag EIP so we can try matching it - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - tags: - Team: Backend - - - name: Attempt reusing an existing EIP with a tag and it's value (match available) - check_mode - amazon.aws.ec2_eip: - state: present - reuse_existing_ip_allowed: true - tag_name: Team - tag_value: Backend - register: reallocate_eip - check_mode: true - - - ansible.builtin.assert: - that: - - reallocate_eip is not changed - - - name: Attempt reusing an existing EIP with a tag and it's value (match available) - amazon.aws.ec2_eip: - state: present - reuse_existing_ip_allowed: true - tag_name: Team - tag_value: Backend - register: reallocate_eip - - - amazon.aws.ec2_eip_info: - register: eip_info - - - ansible.builtin.assert: - that: - - reallocate_eip is not changed - - reallocate_eip.public_ip is defined and reallocate_eip.public_ip != "" - - reallocate_eip.allocation_id is defined and reallocate_eip.allocation_id != "" - - ( eip_info_start.addresses | length ) + 3 == ( eip_info.addresses | length ) - - - name: Release backend_eip - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ backend_eip.public_ip }}" - - - name: Release no_tagged_eip - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ no_tagged_eip.public_ip }}" - - - name: Release eip - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ eip.public_ip }}" - - - amazon.aws.ec2_eip_info: - register: eip_info - - - ansible.builtin.assert: - that: - - ( eip_info_start.addresses | length ) == ( eip_info.addresses | length ) - - # ------------------------------------------------------------------------------------------ - - - name: Allocate a new EIP from a pool - check_mode - amazon.aws.ec2_eip: - state: present - public_ipv4_pool: amazon - register: eip - check_mode: true - - - ansible.builtin.assert: - that: - - eip is changed - - - name: Allocate a new EIP from a pool - amazon.aws.ec2_eip: - state: present - public_ipv4_pool: amazon - register: eip - - - amazon.aws.ec2_eip_info: - register: eip_info - - - ansible.builtin.assert: - that: - - eip is changed - - eip.public_ip is defined and ( eip.public_ip | ansible.utils.ipaddr ) - - eip.allocation_id is defined and eip.allocation_id.startswith("eipalloc-") - - ( eip_info_start.addresses | length ) + 1 == ( eip_info.addresses | length ) - - # ------------------------------------------------------------------------------------------ - - - name: Attach EIP to ENI A - check_mode - amazon.aws.ec2_eip: - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_a.interface.id }}" - register: associate_eip - check_mode: true - - - ansible.builtin.assert: - that: - - associate_eip is changed - - - name: Attach EIP to ENI A - amazon.aws.ec2_eip: - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_a.interface.id }}" - register: associate_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - associate_eip is changed - - eip_info.addresses | length == 1 - - associate_eip.public_ip is defined and eip.public_ip == associate_eip.public_ip - - associate_eip.allocation_id is defined and eip.allocation_id == associate_eip.allocation_id - - eip_info.addresses[0].allocation_id == eip.allocation_id - - eip_info.addresses[0].domain == "vpc" - - eip_info.addresses[0].public_ip == eip.public_ip - - eip_info.addresses[0].association_id is defined and eip_info.addresses[0].association_id.startswith("eipassoc-") - - eip_info.addresses[0].network_interface_id == eni_create_a.interface.id - - eip_info.addresses[0].private_ip_address is defined and ( eip_info.addresses[0].private_ip_address | ansible.utils.ipaddr ) - - eip_info.addresses[0].network_interface_owner_id == caller_info.account - - - name: Attach EIP to ENI A (idempotence) - check_mode - amazon.aws.ec2_eip: - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_a.interface.id }}" - register: associate_eip - check_mode: true - - - ansible.builtin.assert: - that: - - associate_eip is not changed - - - name: Attach EIP to ENI A (idempotence) - amazon.aws.ec2_eip: - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_a.interface.id }}" - register: associate_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - associate_eip is not changed - - associate_eip.public_ip is defined and eip.public_ip == associate_eip.public_ip - - associate_eip.allocation_id is defined and eip.allocation_id == associate_eip.allocation_id - - eip_info.addresses | length == 1 - - eip_info.addresses[0].allocation_id == eip.allocation_id - - eip_info.addresses[0].domain == "vpc" - - eip_info.addresses[0].public_ip == eip.public_ip - - eip_info.addresses[0].association_id is defined and eip_info.addresses[0].association_id.startswith("eipassoc-") - - eip_info.addresses[0].network_interface_id == eni_create_a.interface.id - - eip_info.addresses[0].private_ip_address is defined and ( eip_info.addresses[0].private_ip_address | ansible.utils.ipaddr ) - - # ------------------------------------------------------------------------------------------ - - - name: Attach EIP to ENI B (should fail, already associated) - amazon.aws.ec2_eip: - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_b.interface.id }}" - register: associate_eip - ignore_errors: true - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - associate_eip is failed - - eip_info.addresses | length == 1 - - eip_info.addresses[0].allocation_id == eip.allocation_id - - eip_info.addresses[0].domain == "vpc" - - eip_info.addresses[0].public_ip == eip.public_ip - - eip_info.addresses[0].association_id is defined and eip_info.addresses[0].association_id.startswith("eipassoc-") - - eip_info.addresses[0].network_interface_id == eni_create_a.interface.id - - eip_info.addresses[0].private_ip_address is defined and ( eip_info.addresses[0].private_ip_address | ansible.utils.ipaddr ) - - - name: Attach EIP to ENI B - check_mode - amazon.aws.ec2_eip: - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_b.interface.id }}" - allow_reassociation: true - register: associate_eip - check_mode: true - - - ansible.builtin.assert: - that: - - associate_eip is changed - - - name: Attach EIP to ENI B - amazon.aws.ec2_eip: - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_b.interface.id }}" - allow_reassociation: true - register: associate_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - associate_eip is changed - - associate_eip.public_ip is defined and eip.public_ip == associate_eip.public_ip - - associate_eip.allocation_id is defined and eip.allocation_id == associate_eip.allocation_id - - eip_info.addresses | length == 1 - - eip_info.addresses[0].allocation_id == eip.allocation_id - - eip_info.addresses[0].domain == "vpc" - - eip_info.addresses[0].public_ip == eip.public_ip - - eip_info.addresses[0].association_id is defined and eip_info.addresses[0].association_id.startswith("eipassoc-") - - eip_info.addresses[0].network_interface_id == eni_create_b.interface.id - - eip_info.addresses[0].private_ip_address is defined and ( eip_info.addresses[0].private_ip_address | ansible.utils.ipaddr ) - - - name: Attach EIP to ENI B (idempotence) - check_mode - amazon.aws.ec2_eip: - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_b.interface.id }}" - allow_reassociation: true - register: associate_eip - check_mode: true - - - ansible.builtin.assert: - that: - - associate_eip is not changed - - - name: Attach EIP to ENI B (idempotence) - amazon.aws.ec2_eip: - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_b.interface.id }}" - allow_reassociation: true - register: associate_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - associate_eip is not changed - - associate_eip.public_ip is defined and eip.public_ip == associate_eip.public_ip - - associate_eip.allocation_id is defined and eip.allocation_id == associate_eip.allocation_id - - eip_info.addresses | length == 1 - - eip_info.addresses[0].allocation_id == eip.allocation_id - - eip_info.addresses[0].domain == "vpc" - - eip_info.addresses[0].public_ip == eip.public_ip - - eip_info.addresses[0].association_id is defined and eip_info.addresses[0].association_id.startswith("eipassoc-") - - eip_info.addresses[0].network_interface_id == eni_create_b.interface.id - - eip_info.addresses[0].private_ip_address is defined and ( eip_info.addresses[0].private_ip_address | ansible.utils.ipaddr ) - - # ------------------------------------------------------------------------------------------ - - - name: Detach EIP from ENI B, without enabling release on disassociation - check_mode - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_b.interface.id }}" - register: disassociate_eip - check_mode: true - - - ansible.builtin.assert: - that: - - disassociate_eip is changed - - - name: Detach EIP from ENI B, without enabling release on disassociation - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_b.interface.id }}" - register: disassociate_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - disassociate_eip.changed - - disassociate_eip.disassociated - - not disassociate_eip.released - - eip_info.addresses | length == 1 - - - name: Detach EIP from ENI B, without enabling release on disassociation (idempotence) - check_mode - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_b.interface.id }}" - register: disassociate_eip - check_mode: true - - - ansible.builtin.assert: - that: - - disassociate_eip is not changed - - - name: Detach EIP from ENI B, without enabling release on disassociation (idempotence) - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_b.interface.id }}" - register: disassociate_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - not disassociate_eip.changed - - not disassociate_eip.disassociated - - not disassociate_eip.released - - eip_info.addresses | length == 1 - - # ------------------------------------------------------------------------------------------ - - - name: Attach EIP to ENI A - amazon.aws.ec2_eip: - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_a.interface.id }}" - register: associate_eip - - - name: Detach EIP from ENI A, enabling release on disassociation - check_mode - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_a.interface.id }}" - release_on_disassociation: true - register: disassociate_eip - check_mode: true - - - ansible.builtin.assert: - that: - - disassociate_eip is changed - - - name: Detach EIP from ENI A, enabling release on disassociation - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_a.interface.id }}" - release_on_disassociation: true - register: disassociate_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - disassociate_eip.changed - - disassociate_eip.disassociated - - disassociate_eip.released - - eip_info.addresses | length == 0 - - - name: Detach EIP from ENI A, enabling release on disassociation (idempotence) - check_mode - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_a.interface.id }}" - release_on_disassociation: true - register: disassociate_eip - check_mode: true - - - ansible.builtin.assert: - that: - - disassociate_eip is not changed - - - name: Detach EIP from ENI A, enabling release on disassociation (idempotence) - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ eip.public_ip }}" - device_id: "{{ eni_create_a.interface.id }}" - release_on_disassociation: true - register: disassociate_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - not disassociate_eip.changed - - not disassociate_eip.disassociated - - not disassociate_eip.released - - eip_info.addresses | length == 0 - - # ------------------------------------------------------------------------------------------ - - - name: Attach EIP to an EC2 instance - check_mode - amazon.aws.ec2_eip: - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - state: present - release_on_disassociation: true - register: instance_eip - check_mode: true - - - ansible.builtin.assert: - that: - - instance_eip is changed - - - name: Attach EIP to an EC2 instance - amazon.aws.ec2_eip: - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - state: present - release_on_disassociation: true - register: instance_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ instance_eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - instance_eip is changed - - eip_info.addresses[0].allocation_id is defined - - eip_info.addresses[0].instance_id == create_ec2_instance_result.instance_ids[0] - - - name: Attach EIP to an EC2 instance (idempotence) - check_mode - amazon.aws.ec2_eip: - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - state: present - release_on_disassociation: true - register: instance_eip - check_mode: true - - - ansible.builtin.assert: - that: - - instance_eip is not changed - - - name: Attach EIP to an EC2 instance (idempotence) - amazon.aws.ec2_eip: - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - state: present - release_on_disassociation: true - register: instance_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ instance_eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - instance_eip is not changed - - eip_info.addresses[0].allocation_id is defined - - eip_info.addresses[0].instance_id == create_ec2_instance_result.instance_ids[0] - - # ------------------------------------------------------------------------------------------ - - - name: Detach EIP from EC2 instance, without enabling release on disassociation - check_mode - amazon.aws.ec2_eip: - state: absent - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - register: detach_eip - check_mode: true - - - ansible.builtin.assert: - that: - - detach_eip is changed - - - name: Detach EIP from EC2 instance, without enabling release on disassociation - amazon.aws.ec2_eip: - state: absent - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - register: detach_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ instance_eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - detach_eip.changed - - detach_eip.disassociated - - not detach_eip.released - - eip_info.addresses | length == 1 - - - name: Detach EIP from EC2 instance, without enabling release on disassociation (idempotence) - check_mode - amazon.aws.ec2_eip: - state: absent - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - register: detach_eip - check_mode: true - - - ansible.builtin.assert: - that: - - detach_eip is not changed - - - name: Detach EIP from EC2 instance, without enabling release on disassociation (idempotence) - amazon.aws.ec2_eip: - state: absent - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - register: detach_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ instance_eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - not detach_eip.changed - - not detach_eip.disassociated - - not detach_eip.released - - eip_info.addresses | length == 1 - - - name: Release EIP - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ instance_eip.public_ip }}" - - # ------------------------------------------------------------------------------------------ - - - name: Attach EIP to an EC2 instance with private Ip specified - check_mode - amazon.aws.ec2_eip: - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - private_ip_address: "{{ create_ec2_instance_result.instances[0].private_ip_address }}" - state: present - release_on_disassociation: true - register: instance_eip - check_mode: true - - - ansible.builtin.assert: - that: - - instance_eip is changed - - - name: Attach EIP to an EC2 instance with private Ip specified - amazon.aws.ec2_eip: - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - private_ip_address: "{{ create_ec2_instance_result.instances[0].private_ip_address }}" - state: present - release_on_disassociation: true - register: instance_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ instance_eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - instance_eip is changed - - eip_info.addresses[0].allocation_id is defined - - eip_info.addresses[0].instance_id == create_ec2_instance_result.instance_ids[0] - - - name: Attach EIP to an EC2 instance with private Ip specified (idempotence) - check_mode - amazon.aws.ec2_eip: - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - private_ip_address: "{{ create_ec2_instance_result.instances[0].private_ip_address }}" - state: present - release_on_disassociation: true - register: instance_eip - check_mode: true - - - ansible.builtin.assert: - that: - - instance_eip is not changed - - - name: Attach EIP to an EC2 instance with private Ip specified (idempotence) - amazon.aws.ec2_eip: - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - private_ip_address: "{{ create_ec2_instance_result.instances[0].private_ip_address }}" - state: present - release_on_disassociation: true - register: instance_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ instance_eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - instance_eip is not changed - - eip_info.addresses[0].allocation_id is defined - - eip_info.addresses[0].instance_id == create_ec2_instance_result.instance_ids[0] - - # ------------------------------------------------------------------------------------------ - - - name: Detach EIP from EC2 instance, enabling release on disassociation - check_mode - amazon.aws.ec2_eip: - state: absent - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - release_on_disassociation: true - register: disassociate_eip - check_mode: true - - - ansible.builtin.assert: - that: - - disassociate_eip is changed - - - name: Detach EIP from EC2 instance, enabling release on disassociation - amazon.aws.ec2_eip: - state: absent - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - release_on_disassociation: true - register: disassociate_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ instance_eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - disassociate_eip.changed - - disassociate_eip.disassociated - - disassociate_eip.released - - eip_info.addresses | length == 0 - - - name: Detach EIP from EC2 instance, enabling release on disassociation (idempotence) - check_mode - amazon.aws.ec2_eip: - state: absent - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - release_on_disassociation: true - register: disassociate_eip - check_mode: true - - - ansible.builtin.assert: - that: - - disassociate_eip is not changed - - - name: Detach EIP from EC2 instance, enabling release on disassociation (idempotence) - amazon.aws.ec2_eip: - state: absent - device_id: "{{ create_ec2_instance_result.instance_ids[0] }}" - release_on_disassociation: true - register: disassociate_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ instance_eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - not disassociate_eip.changed - - not disassociate_eip.disassociated - - not disassociate_eip.released - - eip_info.addresses | length == 0 - - # ------------------------------------------------------------------------------------------ - - - name: Allocate a new eip - amazon.aws.ec2_eip: - state: present - register: eip - - - name: Tag EIP - check_mode - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - tags: - AnsibleEIPTestPrefix: "{{ resource_prefix }}" - another_tag: another Value {{ resource_prefix }} - register: tag_eip - check_mode: true - - - ansible.builtin.assert: - that: - - tag_eip is changed - - - name: Tag EIP - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - tags: - AnsibleEIPTestPrefix: "{{ resource_prefix }}" - another_tag: another Value {{ resource_prefix }} - register: tag_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - tag_eip is changed - - '"AnsibleEIPTestPrefix" in eip_info.addresses[0].tags' - - '"another_tag" in eip_info.addresses[0].tags' - - eip_info.addresses[0].tags['AnsibleEIPTestPrefix'] == resource_prefix - - eip_info.addresses[0].tags['another_tag'] == 'another Value ' + resource_prefix - - ( eip_info_start.addresses | length ) + 1 == ( eip_info.addresses | length ) - - - name: Tag EIP (idempotence) - check_mode - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - tags: - AnsibleEIPTestPrefix: "{{ resource_prefix }}" - another_tag: another Value {{ resource_prefix }} - register: tag_eip - check_mode: true - - - ansible.builtin.assert: - that: - - tag_eip is not changed - - - name: Tag EIP (idempotence) - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - tags: - AnsibleEIPTestPrefix: "{{ resource_prefix }}" - another_tag: another Value {{ resource_prefix }} - register: tag_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - tag_eip is not changed - - '"AnsibleEIPTestPrefix" in eip_info.addresses[0].tags' - - '"another_tag" in eip_info.addresses[0].tags' - - eip_info.addresses[0].tags['AnsibleEIPTestPrefix'] == resource_prefix - - eip_info.addresses[0].tags['another_tag'] == 'another Value ' + resource_prefix - - ( eip_info_start.addresses | length ) + 1 == ( eip_info.addresses | length ) - - # ------------------------------------------------------------------------------------------ - - - name: Add another Tag - check_mode - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - tags: - third tag: Third tag - {{ resource_prefix }} - purge_tags: false - register: tag_eip - check_mode: true - - - ansible.builtin.assert: - that: - - tag_eip is changed - - - name: Add another Tag - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - tags: - third tag: Third tag - {{ resource_prefix }} - purge_tags: false - register: tag_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - tag_eip is changed - - '"AnsibleEIPTestPrefix" in eip_info.addresses[0].tags' - - '"another_tag" in eip_info.addresses[0].tags' - - '"third tag" in eip_info.addresses[0].tags' - - eip_info.addresses[0].tags['AnsibleEIPTestPrefix'] == resource_prefix - - eip_info.addresses[0].tags['another_tag'] == 'another Value ' + resource_prefix - - eip_info.addresses[0].tags['third tag'] == 'Third tag - ' + resource_prefix - - ( eip_info_start.addresses | length ) + 1 == ( eip_info.addresses | length ) - - - name: Add another Tag (idempotence) - check_mode - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - tags: - third tag: Third tag - {{ resource_prefix }} - purge_tags: false - register: tag_eip - check_mode: true - - - ansible.builtin.assert: - that: - - tag_eip is not changed - - - name: Add another Tag (idempotence) - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - tags: - third tag: Third tag - {{ resource_prefix }} - purge_tags: false - register: tag_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - tag_eip is not changed - - '"AnsibleEIPTestPrefix" in eip_info.addresses[0].tags' - - '"another_tag" in eip_info.addresses[0].tags' - - '"third tag" in eip_info.addresses[0].tags' - - eip_info.addresses[0].tags['AnsibleEIPTestPrefix'] == resource_prefix - - eip_info.addresses[0].tags['another_tag'] == 'another Value ' + resource_prefix - - eip_info.addresses[0].tags['third tag'] == 'Third tag - ' + resource_prefix - - # ------------------------------------------------------------------------------------------ - - - name: Purge tags - check_mode - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - tags: - third tag: Third tag - {{ resource_prefix }} - purge_tags: true - register: tag_eip - check_mode: true - - - ansible.builtin.assert: - that: - - tag_eip is changed - - - name: Purge tags - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - tags: - third tag: Third tag - {{ resource_prefix }} - purge_tags: true - register: tag_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - tag_eip is changed - - '"AnsibleEIPTestPrefix" not in eip_info.addresses[0].tags' - - '"another_tag" not in eip_info.addresses[0].tags' - - '"third tag" in eip_info.addresses[0].tags' - - eip_info.addresses[0].tags['third tag'] == 'Third tag - ' + resource_prefix - - - name: Purge tags (idempotence) - check_mode - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - tags: - third tag: Third tag - {{ resource_prefix }} - purge_tags: true - register: tag_eip - check_mode: true - - - ansible.builtin.assert: - that: - - tag_eip is not changed - - - name: Purge tags (idempotence) - amazon.aws.ec2_eip: - state: present - public_ip: "{{ eip.public_ip }}" - tags: - third tag: Third tag - {{ resource_prefix }} - purge_tags: true - register: tag_eip - - - amazon.aws.ec2_eip_info: - filters: - public-ip: "{{ eip.public_ip }}" - register: eip_info - - - ansible.builtin.assert: - that: - - tag_eip is not changed - - '"AnsibleEIPTestPrefix" not in eip_info.addresses[0].tags' - - '"another_tag" not in eip_info.addresses[0].tags' - - '"third tag" in eip_info.addresses[0].tags' - - eip_info.addresses[0].tags['third tag'] == 'Third tag - ' + resource_prefix - - # ----- Cleanup ------------------------------------------------------------------------------ + - ansible.builtin.include_tasks: tasks/setup.yml + - ansible.builtin.include_tasks: tasks/allocate.yml + - ansible.builtin.include_tasks: tasks/reuse_with_tag.yml + - ansible.builtin.include_tasks: tasks/tagging.yml + - ansible.builtin.include_tasks: tasks/release.yml + - ansible.builtin.include_tasks: tasks/attach_detach_to_eni.yml + - ansible.builtin.include_tasks: tasks/attach_detach_to_instance.yml always: - - name: Cleanup instance (by id) - amazon.aws.ec2_instance: - instance_ids: "{{ create_ec2_instance_result.instance_ids }}" - state: absent - wait: true - ignore_errors: true - - - name: Cleanup instance (by name) - amazon.aws.ec2_instance: - name: "{{ resource_prefix }}-instance" - state: absent - wait: true - ignore_errors: true - - - name: Cleanup ENI A - amazon.aws.ec2_eni: - state: absent - eni_id: "{{ eni_create_a.interface.id }}" - ignore_errors: true - - - name: Cleanup ENI B - amazon.aws.ec2_eni: - state: absent - eni_id: "{{ eni_create_b.interface.id }}" - ignore_errors: true - - - name: Cleanup instance eip - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ instance_eip.public_ip }}" - retries: 5 - delay: 5 - until: eip_cleanup is successful - ignore_errors: true - - - name: Cleanup IGW - amazon.aws.ec2_vpc_igw: - state: absent - vpc_id: "{{ vpc_result.vpc.id }}" - register: vpc_igw - ignore_errors: true - - - name: Cleanup security group - amazon.aws.ec2_security_group: - state: absent - name: "{{ resource_prefix }}-sg" - ignore_errors: true - - - name: Cleanup Subnet - amazon.aws.ec2_vpc_subnet: - state: absent - cidr: "{{ subnet_cidr }}" - vpc_id: "{{ vpc_result.vpc.id }}" - ignore_errors: true - - - name: Cleanup eip - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ eip.public_ip }}" - ignore_errors: true - - - name: Cleanup reallocate_eip - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ reallocate_eip.public_ip }}" - ignore_errors: true - - - name: Cleanup backend_eip - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ backend_eip.public_ip }}" - ignore_errors: true - - - name: Cleanup no_tagged_eip - amazon.aws.ec2_eip: - state: absent - public_ip: "{{ no_tagged_eip.public_ip }}" - ignore_errors: true - - - name: Cleanup VPC - amazon.aws.ec2_vpc_net: - state: absent - name: "{{ resource_prefix }}-vpc" - cidr_block: "{{ vpc_cidr }}" - ignore_errors: true + - ansible.builtin.include_tasks: tasks/teardown.yml diff --git a/tests/integration/targets/ec2_eip/tasks/release.yml b/tests/integration/targets/ec2_eip/tasks/release.yml new file mode 100644 index 00000000000..fe3e448db35 --- /dev/null +++ b/tests/integration/targets/ec2_eip/tasks/release.yml @@ -0,0 +1,75 @@ +- name: Test release EIP + block: + # ------------------------------------------------------------------------------------------ + # Release EIP + # ------------------------------------------------------------------------------------------ + - name: Get current state of EIPs + amazon.aws.ec2_eip_info: + register: eip_info_start + + - name: Allocate a new EIP with no conditions + amazon.aws.ec2_eip: + state: present + tags: + ResourcePrefix: "{{ resource_prefix }}" + register: eip + + - name: Release EIP - check_mode + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + register: eip_release + check_mode: true + + - ansible.builtin.assert: + that: + - eip_release.changed + + - name: Release eip + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + register: eip_release + + - amazon.aws.ec2_eip_info: + register: eip_info + + - ansible.builtin.assert: + that: + - eip_release.changed + - not eip_release.disassociated + - eip_release.released + - ( eip_info_start.addresses | length ) == ( eip_info.addresses | length ) + + - name: Release EIP (idempotence) - check_mode + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + register: eip_release + check_mode: true + + - ansible.builtin.assert: + that: + - eip_release is not changed + + - name: Release EIP (idempotence) + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + register: eip_release + + - amazon.aws.ec2_eip_info: + register: eip_info + + - ansible.builtin.assert: + that: + - not eip_release.changed + - not eip_release.disassociated + - not eip_release.released + - ( eip_info_start.addresses | length ) == ( eip_info.addresses | length ) + always: + - name: Release eip + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + when: eip is defined diff --git a/tests/integration/targets/ec2_eip/tasks/reuse_with_tag.yml b/tests/integration/targets/ec2_eip/tasks/reuse_with_tag.yml new file mode 100644 index 00000000000..c03f51bde08 --- /dev/null +++ b/tests/integration/targets/ec2_eip/tasks/reuse_with_tag.yml @@ -0,0 +1,186 @@ +--- +- name: Attempt reusing using tags + block: + # ------------------------------------------------------------------------------------------ + # Reuse with tag - No match available + # ------------------------------------------------------------------------------------------ + - name: Get current state of EIPs + amazon.aws.ec2_eip_info: + register: eip_current + + - name: attempt reusing an existing EIP with a tag (No match available) - check_mode + amazon.aws.ec2_eip: + state: present + reuse_existing_ip_allowed: true + tag_name: Team + register: no_tagged_eip + check_mode: true + + - ansible.builtin.assert: + that: + - no_tagged_eip is changed + + - name: attempt reusing an existing EIP with a tag (No match available) + amazon.aws.ec2_eip: + state: present + reuse_existing_ip_allowed: true + tag_name: Team + register: no_tagged_eip + + - amazon.aws.ec2_eip_info: + register: eip_info + + - ansible.builtin.assert: + that: + - no_tagged_eip is changed + - "'ec2:CreateTags' not in no_tagged_eip.resource_actions" + - no_tagged_eip.public_ip is defined and ( no_tagged_eip.public_ip | ansible.utils.ipaddr ) + - no_tagged_eip.allocation_id is defined and no_tagged_eip.allocation_id.startswith("eipalloc-") + - ( eip_current.addresses | length ) + 1 == ( eip_info.addresses | length ) + + # ------------------------------------------------------------------------------------------ + # Reuse with tag - Match available + # ------------------------------------------------------------------------------------------ + - name: Set latest EIPs list + ansible.builtin.set_fact: + eip_current: "{{ eip_info }}" + + - name: Tag EIP so we can try matching it + amazon.aws.ec2_eip: + state: present + public_ip: "{{ no_tagged_eip.public_ip }}" + tags: + Team: Frontend + register: tag_eip + + - ansible.builtin.assert: + that: + - tag_eip is changed + - "'ec2:CreateTags' in tag_eip.resource_actions" + - no_tagged_eip.public_ip == tag_eip.public_ip + - no_tagged_eip.allocation_id == tag_eip.allocation_id + + - name: Attempt reusing an existing EIP with a tag (Match available) - check_mode + amazon.aws.ec2_eip: + state: present + reuse_existing_ip_allowed: true + tag_name: Team + register: reallocate_eip + check_mode: true + + - ansible.builtin.assert: + that: + - reallocate_eip is not changed + + - name: Attempt reusing an existing EIP with a tag (Match available) + amazon.aws.ec2_eip: + state: present + reuse_existing_ip_allowed: true + tag_name: Team + register: reallocate_eip + + - amazon.aws.ec2_eip_info: + register: eip_info + + - ansible.builtin.assert: + that: + - reallocate_eip is not changed + - reallocate_eip.public_ip is defined and ( reallocate_eip.public_ip | ansible.utils.ipaddr ) + - reallocate_eip.allocation_id is defined and reallocate_eip.allocation_id.startswith("eipalloc-") + - ( eip_current.addresses | length ) == ( eip_info.addresses | length ) + + # ------------------------------------------------------------------------------------------ + # Reuse with tag and value - No match available + # ------------------------------------------------------------------------------------------ + - name: Set latest EIPs list + ansible.builtin.set_fact: + eip_current: "{{ eip_info }}" + + - name: Attempt reusing an existing EIP with a tag and it's value (no match available) - check_mode + amazon.aws.ec2_eip: + state: present + reuse_existing_ip_allowed: true + tag_name: Team + tag_value: Backend + register: backend_eip + check_mode: true + + - ansible.builtin.assert: + that: + - backend_eip is changed + + - name: Attempt reusing an existing EIP with a tag and it's value (no match available) + amazon.aws.ec2_eip: + state: present + reuse_existing_ip_allowed: true + tag_name: Team + tag_value: Backend + register: backend_eip + + - amazon.aws.ec2_eip_info: + register: eip_info + + - ansible.builtin.assert: + that: + - backend_eip is changed + - backend_eip.public_ip is defined and ( backend_eip.public_ip | ansible.utils.ipaddr ) + - backend_eip.allocation_id is defined and backend_eip.allocation_id.startswith("eipalloc-") + - ( eip_current.addresses | length ) + 1 == ( eip_info.addresses | length ) + + # ------------------------------------------------------------------------------------------ + # Reuse with tag - Match available + # ------------------------------------------------------------------------------------------ + - name: Set latest EIPs list + ansible.builtin.set_fact: + eip_current: "{{ eip_info }}" + + - name: Tag EIP so we can try matching it + amazon.aws.ec2_eip: + state: present + public_ip: "{{ backend_eip.public_ip }}" + tags: + Team: Backend + + - name: Attempt reusing an existing EIP with a tag and it's value (match available) - check_mode + amazon.aws.ec2_eip: + state: present + reuse_existing_ip_allowed: true + tag_name: Team + tag_value: Backend + register: reallocate_eip + check_mode: true + + - ansible.builtin.assert: + that: + - reallocate_eip is not changed + + - name: Attempt reusing an existing EIP with a tag and it's value (match available) + amazon.aws.ec2_eip: + state: present + reuse_existing_ip_allowed: true + tag_name: Team + tag_value: Backend + register: reallocate_eip + + - amazon.aws.ec2_eip_info: + register: eip_info + + - ansible.builtin.assert: + that: + - reallocate_eip is not changed + - reallocate_eip.public_ip is defined and reallocate_eip.public_ip != "" + - reallocate_eip.allocation_id is defined and reallocate_eip.allocation_id != "" + - ( eip_current.addresses | length ) == ( eip_info.addresses | length ) + + always: + - name: Release backend_eip + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ backend_eip.public_ip }}" + when: backend_eip is defined + + - name: Release no_tagged_eip + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ no_tagged_eip.public_ip }}" + when: no_tagged_eip is defined diff --git a/tests/integration/targets/ec2_eip/tasks/setup.yml b/tests/integration/targets/ec2_eip/tasks/setup.yml new file mode 100644 index 00000000000..a4380822e69 --- /dev/null +++ b/tests/integration/targets/ec2_eip/tasks/setup.yml @@ -0,0 +1,54 @@ +- name: Get the current caller identity facts + amazon.aws.aws_caller_info: + register: caller_info + +- name: List available AZs + amazon.aws.aws_az_info: + register: region_azs + +- name: Create a VPC + amazon.aws.ec2_vpc_net: + name: "{{ resource_prefix }}-vpc" + state: present + cidr_block: "{{ vpc_cidr }}" + tags: + AnsibleEIPTest: Pending + AnsibleEIPTestPrefix: "{{ resource_prefix }}" + register: vpc_result + +- name: Create subnet + amazon.aws.ec2_vpc_subnet: + cidr: "{{ subnet_cidr }}" + az: "{{ subnet_az }}" + vpc_id: "{{ vpc_result.vpc.id }}" + state: present + register: vpc_subnet_create + +- name: Create internet gateway + amazon.aws.ec2_vpc_igw: + state: present + vpc_id: "{{ vpc_result.vpc.id }}" + register: vpc_igw + +- name: Create security group + amazon.aws.ec2_security_group: + state: present + name: "{{ resource_prefix }}-sg" + description: a security group for ansible tests + vpc_id: "{{ vpc_result.vpc.id }}" + rules: + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: "0.0.0.0/0" + register: security_group + +- name: Create ENI A + amazon.aws.ec2_eni: + subnet_id: "{{ vpc_subnet_create.subnet.id }}" + register: eni_create_a + +- name: Create ENI B + amazon.aws.ec2_eni: + subnet_id: "{{ vpc_subnet_create.subnet.id }}" + register: eni_create_b diff --git a/tests/integration/targets/ec2_eip/tasks/tagging.yml b/tests/integration/targets/ec2_eip/tasks/tagging.yml new file mode 100644 index 00000000000..26d2835775b --- /dev/null +++ b/tests/integration/targets/ec2_eip/tasks/tagging.yml @@ -0,0 +1,254 @@ +--- +- name: Test EIP tagging + block: + # ------------------------------------------------------------------------------------------ + # Test tagging + # ------------------------------------------------------------------------------------------ + - name: Get current state of EIPs + amazon.aws.ec2_eip_info: + register: eip_info_start + + - name: Allocate a new eip + amazon.aws.ec2_eip: + state: present + register: eip + + - name: Tag EIP - check_mode + amazon.aws.ec2_eip: + state: present + public_ip: "{{ eip.public_ip }}" + tags: + AnsibleEIPTestPrefix: "{{ resource_prefix }}" + another_tag: another Value {{ resource_prefix }} + register: tag_eip + check_mode: true + + - ansible.builtin.assert: + that: + - tag_eip is changed + + - name: Tag EIP + amazon.aws.ec2_eip: + state: present + public_ip: "{{ eip.public_ip }}" + tags: + AnsibleEIPTestPrefix: "{{ resource_prefix }}" + another_tag: another Value {{ resource_prefix }} + register: tag_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - tag_eip is changed + - '"AnsibleEIPTestPrefix" in eip_info.addresses[0].tags' + - '"another_tag" in eip_info.addresses[0].tags' + - eip_info.addresses[0].tags['AnsibleEIPTestPrefix'] == resource_prefix + - eip_info.addresses[0].tags['another_tag'] == 'another Value ' + resource_prefix + - ( eip_info_start.addresses | length ) + 1 == ( eip_info.addresses | length ) + + - name: Tag EIP (idempotence) - check_mode + amazon.aws.ec2_eip: + state: present + public_ip: "{{ eip.public_ip }}" + tags: + AnsibleEIPTestPrefix: "{{ resource_prefix }}" + another_tag: another Value {{ resource_prefix }} + register: tag_eip + check_mode: true + + - ansible.builtin.assert: + that: + - tag_eip is not changed + + - name: Tag EIP (idempotence) + amazon.aws.ec2_eip: + state: present + public_ip: "{{ eip.public_ip }}" + tags: + AnsibleEIPTestPrefix: "{{ resource_prefix }}" + another_tag: another Value {{ resource_prefix }} + register: tag_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - tag_eip is not changed + - '"AnsibleEIPTestPrefix" in eip_info.addresses[0].tags' + - '"another_tag" in eip_info.addresses[0].tags' + - eip_info.addresses[0].tags['AnsibleEIPTestPrefix'] == resource_prefix + - eip_info.addresses[0].tags['another_tag'] == 'another Value ' + resource_prefix + - ( eip_info_start.addresses | length ) + 1 == ( eip_info.addresses | length ) + + # ------------------------------------------------------------------------------------------ + # Test updating tags + # ------------------------------------------------------------------------------------------ + - name: Get current state of EIPs + amazon.aws.ec2_eip_info: + register: eip_info_start + + - name: Add another Tag - check_mode + amazon.aws.ec2_eip: + state: present + public_ip: "{{ eip.public_ip }}" + tags: + third tag: Third tag - {{ resource_prefix }} + purge_tags: false + register: tag_eip + check_mode: true + + - ansible.builtin.assert: + that: + - tag_eip is changed + + - name: Add another Tag + amazon.aws.ec2_eip: + state: present + public_ip: "{{ eip.public_ip }}" + tags: + third tag: Third tag - {{ resource_prefix }} + purge_tags: false + register: tag_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - tag_eip is changed + - '"AnsibleEIPTestPrefix" in eip_info.addresses[0].tags' + - '"another_tag" in eip_info.addresses[0].tags' + - '"third tag" in eip_info.addresses[0].tags' + - eip_info.addresses[0].tags['AnsibleEIPTestPrefix'] == resource_prefix + - eip_info.addresses[0].tags['another_tag'] == 'another Value ' + resource_prefix + - eip_info.addresses[0].tags['third tag'] == 'Third tag - ' + resource_prefix + - ( eip_info_start.addresses | length ) == ( eip_info.addresses | length ) + + - name: Add another Tag (idempotence) - check_mode + amazon.aws.ec2_eip: + state: present + public_ip: "{{ eip.public_ip }}" + tags: + third tag: Third tag - {{ resource_prefix }} + purge_tags: false + register: tag_eip + check_mode: true + + - ansible.builtin.assert: + that: + - tag_eip is not changed + + - name: Add another Tag (idempotence) + amazon.aws.ec2_eip: + state: present + public_ip: "{{ eip.public_ip }}" + tags: + third tag: Third tag - {{ resource_prefix }} + purge_tags: false + register: tag_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - tag_eip is not changed + - '"AnsibleEIPTestPrefix" in eip_info.addresses[0].tags' + - '"another_tag" in eip_info.addresses[0].tags' + - '"third tag" in eip_info.addresses[0].tags' + - eip_info.addresses[0].tags['AnsibleEIPTestPrefix'] == resource_prefix + - eip_info.addresses[0].tags['another_tag'] == 'another Value ' + resource_prefix + - eip_info.addresses[0].tags['third tag'] == 'Third tag - ' + resource_prefix + + # ------------------------------------------------------------------------------------------ + # Test purge_tags + # ------------------------------------------------------------------------------------------ + - name: Purge tags - check_mode + amazon.aws.ec2_eip: + state: present + public_ip: "{{ eip.public_ip }}" + tags: + third tag: Third tag - {{ resource_prefix }} + purge_tags: true + register: tag_eip + check_mode: true + + - ansible.builtin.assert: + that: + - tag_eip is changed + + - name: Purge tags + amazon.aws.ec2_eip: + state: present + public_ip: "{{ eip.public_ip }}" + tags: + third tag: Third tag - {{ resource_prefix }} + purge_tags: true + register: tag_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - tag_eip is changed + - '"AnsibleEIPTestPrefix" not in eip_info.addresses[0].tags' + - '"another_tag" not in eip_info.addresses[0].tags' + - '"third tag" in eip_info.addresses[0].tags' + - eip_info.addresses[0].tags['third tag'] == 'Third tag - ' + resource_prefix + + - name: Purge tags (idempotence) - check_mode + amazon.aws.ec2_eip: + state: present + public_ip: "{{ eip.public_ip }}" + tags: + third tag: Third tag - {{ resource_prefix }} + purge_tags: true + register: tag_eip + check_mode: true + + - ansible.builtin.assert: + that: + - tag_eip is not changed + + - name: Purge tags (idempotence) + amazon.aws.ec2_eip: + state: present + public_ip: "{{ eip.public_ip }}" + tags: + third tag: Third tag - {{ resource_prefix }} + purge_tags: true + register: tag_eip + + - amazon.aws.ec2_eip_info: + filters: + public-ip: "{{ eip.public_ip }}" + register: eip_info + + - ansible.builtin.assert: + that: + - tag_eip is not changed + - '"AnsibleEIPTestPrefix" not in eip_info.addresses[0].tags' + - '"another_tag" not in eip_info.addresses[0].tags' + - '"third tag" in eip_info.addresses[0].tags' + - eip_info.addresses[0].tags['third tag'] == 'Third tag - ' + resource_prefix + + always: + - name: Release EIP + amazon.aws.ec2_eip: + state: absent + public_ip: "{{ eip.public_ip }}" + when: eip is defined \ No newline at end of file diff --git a/tests/integration/targets/ec2_eip/tasks/teardown.yml b/tests/integration/targets/ec2_eip/tasks/teardown.yml new file mode 100644 index 00000000000..82378b95183 --- /dev/null +++ b/tests/integration/targets/ec2_eip/tasks/teardown.yml @@ -0,0 +1,39 @@ +--- +- name: Cleanup ENI A + amazon.aws.ec2_eni: + state: absent + eni_id: "{{ eni_create_a.interface.id }}" + ignore_errors: true + +- name: Cleanup ENI B + amazon.aws.ec2_eni: + state: absent + eni_id: "{{ eni_create_b.interface.id }}" + ignore_errors: true + +- name: Cleanup IGW + amazon.aws.ec2_vpc_igw: + state: absent + vpc_id: "{{ vpc_result.vpc.id }}" + register: vpc_igw + ignore_errors: true + +- name: Cleanup security group + amazon.aws.ec2_security_group: + state: absent + name: "{{ resource_prefix }}-sg" + ignore_errors: true + +- name: Cleanup Subnet + amazon.aws.ec2_vpc_subnet: + state: absent + cidr: "{{ subnet_cidr }}" + vpc_id: "{{ vpc_result.vpc.id }}" + ignore_errors: true + +- name: Cleanup VPC + amazon.aws.ec2_vpc_net: + state: absent + name: "{{ resource_prefix }}-vpc" + cidr_block: "{{ vpc_cidr }}" + ignore_errors: true diff --git a/tests/unit/plugins/modules/ec2_eip/test_check_is_instance.py b/tests/unit/plugins/modules/ec2_eip/test_check_is_instance.py index 0afeab56a5e..278b1180fba 100644 --- a/tests/unit/plugins/modules/ec2_eip/test_check_is_instance.py +++ b/tests/unit/plugins/modules/ec2_eip/test_check_is_instance.py @@ -1,6 +1,8 @@ # This file is part of Ansible # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from unittest.mock import MagicMock + import pytest from ansible_collections.amazon.aws.plugins.modules import ec2_eip @@ -54,12 +56,22 @@ ] -def test_check_is_instance_needs_in_vpc(): - with pytest.raises(ec2_eip.EipError): - ec2_eip.check_is_instance("eni-123456789", False) +@pytest.fixture +def ansible_module(): + module = MagicMock() + module.params = {} + module.fail_json.side_effect = SystemExit(1) + return module + + +def test_check_is_instance_needs_in_vpc(ansible_module): + with pytest.raises(SystemExit): + ansible_module.params.update({"device_id": "eni-123456789", "in_vpc": False}) + ec2_eip.check_is_instance(ansible_module) @pytest.mark.parametrize("device,in_vpc,expected", EXAMPLE_DATA) -def test_check_is_instance(device, in_vpc, expected): - result = ec2_eip.check_is_instance(device, in_vpc) +def test_check_is_instance(ansible_module, device, in_vpc, expected): + ansible_module.params.update({"device_id": device, "in_vpc": in_vpc}) + result = ec2_eip.check_is_instance(ansible_module) assert result is expected diff --git a/tests/unit/plugins/modules/ec2_eip/test_generate_tag_dict.py b/tests/unit/plugins/modules/ec2_eip/test_generate_tag_dict.py new file mode 100644 index 00000000000..6d94268d795 --- /dev/null +++ b/tests/unit/plugins/modules/ec2_eip/test_generate_tag_dict.py @@ -0,0 +1,32 @@ +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from unittest.mock import ANY +from unittest.mock import MagicMock + +import pytest + +from ansible_collections.amazon.aws.plugins.modules import ec2_eip + + +@pytest.fixture +def ansible_module(): + module = MagicMock() + module.params = {} + module.fail_json.side_effect = SystemExit(1) + return module + + +@pytest.mark.parametrize( + "tag_name,tag_value,expected", + [ + (None, ANY, None), + ("tag:SomeKey", None, {"tag-key": "SomeKey"}), + ("SomeKey", None, {"tag-key": "SomeKey"}), + ("tag:AnotherKey", "SomeValue", {"tag:AnotherKey": "SomeValue"}), + ("AnotherKey", "AnotherValue", {"tag:AnotherKey": "AnotherValue"}), + ], +) +def test_generate_tag_dict(ansible_module, tag_name, tag_value, expected): + ansible_module.params.update({"tag_name": tag_name, "tag_value": tag_value}) + assert expected == ec2_eip.generate_tag_dict(ansible_module)