diff --git a/changelogs/fragments/migrate_ec2_vpc_nat_gateway.yml b/changelogs/fragments/migrate_ec2_vpc_nat_gateway.yml new file mode 100644 index 00000000000..fc978309a23 --- /dev/null +++ b/changelogs/fragments/migrate_ec2_vpc_nat_gateway.yml @@ -0,0 +1,10 @@ +major_changes: +- ec2_vpc_nat_gateway_facts - The module has been migrated from the ``community.aws`` + collection. Playbooks using the Fully Qualified Collection Name for this module + should be updated to use ``amazon.aws.ec2_vpc_nat_gateway_facts``. +- ec2_vpc_nat_gateway - The module has been migrated from the ``community.aws`` collection. + Playbooks using the Fully Qualified Collection Name for this module should be updated + to use ``amazon.aws.ec2_vpc_nat_gateway``. +- ec2_vpc_nat_gateway_info - The module has been migrated from the ``community.aws`` + collection. Playbooks using the Fully Qualified Collection Name for this module + should be updated to use ``amazon.aws.ec2_vpc_nat_gateway_info``. diff --git a/meta/runtime.yml b/meta/runtime.yml index df3b4370954..ce1387bb51a 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -49,74 +49,83 @@ action_groups: - ec2_vpc_subnet - ec2_vpc_subnet_info - s3_bucket - + - ec2_vpc_nat_gateway_facts + - ec2_vpc_nat_gateway + - ec2_vpc_nat_gateway_info plugin_routing: modules: aws_az_facts: deprecation: removal_date: 2022-06-01 warning_text: >- - aws_az_facts was renamed in Ansible 2.9 to aws_az_info. - Please update your tasks. + aws_az_facts was renamed in Ansible 2.9 to aws_az_info. + Please update your tasks. aws_caller_facts: deprecation: removal_date: 2021-12-01 warning_text: >- - aws_caller_facts was renamed in Ansible 2.9 to aws_caller_info. - Please update your tasks. + aws_caller_facts was renamed in Ansible 2.9 to aws_caller_info. + Please update your tasks. cloudformation_facts: deprecation: removal_date: 2021-12-01 warning_text: >- - cloudformation_facts has been deprecated and will be removed. - The cloudformation_info module returns the same information, but - not as ansible_facts. See the module documentation for more - information. + cloudformation_facts has been deprecated and will be removed. + The cloudformation_info module returns the same information, but + not as ansible_facts. See the module documentation for more + information. ec2_ami_facts: deprecation: removal_date: 2021-12-01 warning_text: >- - ec2_ami_facts was renamed in Ansible 2.9 to ec2_ami_info. - Please update your tasks. + ec2_ami_facts was renamed in Ansible 2.9 to ec2_ami_info. + Please update your tasks. ec2_eni_facts: deprecation: removal_date: 2021-12-01 warning_text: >- - ec2_eni_facts was renamed in Ansible 2.9 to ec2_eni_info. - Please update your tasks. + ec2_eni_facts was renamed in Ansible 2.9 to ec2_eni_info. + Please update your tasks. ec2_group_facts: deprecation: removal_date: 2021-12-01 warning_text: >- - ec2_group_facts was renamed in Ansible 2.9 to ec2_group_info. - Please update your tasks. + ec2_group_facts was renamed in Ansible 2.9 to ec2_group_info. + Please update your tasks. ec2_snapshot_facts: deprecation: removal_date: 2021-12-01 warning_text: >- - ec2_snapshot_facts was renamed in Ansible 2.9 to ec2_snapshot_info. - Please update your tasks. + ec2_snapshot_facts was renamed in Ansible 2.9 to ec2_snapshot_info. + Please update your tasks. ec2_vol_facts: deprecation: removal_date: 2021-12-01 warning_text: >- - ec2_vol_facts was renamed in Ansible 2.9 to ec2_vol_info. - Please update your tasks. + ec2_vol_facts was renamed in Ansible 2.9 to ec2_vol_info. + Please update your tasks. ec2_vpc_dhcp_option_facts: deprecation: removal_date: 2021-12-01 warning_text: >- - ec2_vpc_dhcp_option_facts was renamed in Ansible 2.9 to - ec2_vpc_dhcp_option_info. Please update your tasks. + ec2_vpc_dhcp_option_facts was renamed in Ansible 2.9 to + ec2_vpc_dhcp_option_info. Please update your tasks. ec2_vpc_net_facts: deprecation: removal_date: 2021-12-01 warning_text: >- - ec2_vpc_net_facts was renamed in Ansible 2.9 to ec2_vpc_net_info. - Please update your tasks. + ec2_vpc_net_facts was renamed in Ansible 2.9 to ec2_vpc_net_info. + Please update your tasks. ec2_vpc_subnet_facts: deprecation: removal_date: 2021-12-01 warning_text: >- - ec2_vpc_subnet_facts was renamed in Ansible 2.9 to - ec2_vpc_subnet_info. Please update your tasks. + ec2_vpc_subnet_facts was renamed in Ansible 2.9 to + ec2_vpc_subnet_info. Please update your tasks. + ec2_vpc_nat_gateway_facts: + deprecation: + removal_date: 2021-12-01 + warning_text: >- + ec2_vpc_nat_gateway_facts was renamed in Ansible 2.9 to + ec2_vpc_nat_gateway_info. + Please update your tasks. diff --git a/plugins/modules/ec2_vpc_nat_gateway.py b/plugins/modules/ec2_vpc_nat_gateway.py new file mode 100644 index 00000000000..30a28ca1391 --- /dev/null +++ b/plugins/modules/ec2_vpc_nat_gateway.py @@ -0,0 +1,1000 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: ec2_vpc_nat_gateway +version_added: 1.0.0 +short_description: Manage AWS VPC NAT Gateways. +description: + - Ensure the state of AWS VPC NAT Gateways based on their id, allocation and subnet ids. +options: + state: + description: + - Ensure NAT Gateway is present or absent. + default: "present" + choices: ["present", "absent"] + type: str + nat_gateway_id: + description: + - The id AWS dynamically allocates to the NAT Gateway on creation. + This is required when the absent option is present. + type: str + subnet_id: + description: + - The id of the subnet to create the NAT Gateway in. This is required + with the present option. + type: str + allocation_id: + description: + - The id of the elastic IP allocation. If this is not passed and the + eip_address is not passed. An EIP is generated for this NAT Gateway. + type: str + eip_address: + description: + - The elastic IP address of the EIP you want attached to this NAT Gateway. + If this is not passed and the allocation_id is not passed, + an EIP is generated for this NAT Gateway. + type: str + if_exist_do_not_create: + description: + - if a NAT Gateway exists already in the subnet_id, then do not create a new one. + required: false + default: false + type: bool + tags: + description: + - A dict of tags to apply to the NAT gateway. + - To remove all tags set I(tags={}) and I(purge_tags=true). + aliases: [ 'resource_tags' ] + type: dict + version_added: 1.4.0 + purge_tags: + description: + - Remove tags not listed in I(tags). + type: bool + default: true + version_added: 1.4.0 + release_eip: + description: + - Deallocate the EIP from the VPC. + - Option is only valid with the absent state. + - You should use this with the wait option. Since you can not release an address while a delete operation is happening. + default: false + type: bool + wait: + description: + - Wait for operation to complete before returning. + default: false + type: bool + wait_timeout: + description: + - How many seconds to wait for an operation to complete before timing out. + default: 320 + type: int + client_token: + description: + - Optional unique token to be used during create to ensure idempotency. + When specifying this option, ensure you specify the eip_address parameter + as well otherwise any subsequent runs will fail. + type: str +author: + - Allen Sanabria (@linuxdynasty) + - Jon Hadfield (@jonhadfield) + - Karen Cheng (@Etherdaemon) + - Alina Buzachis (@alinabuzachis) +extends_documentation_fragment: +- amazon.aws.aws +- amazon.aws.ec2 +''' + +EXAMPLES = r''' +# Note: These examples do not set authentication details, see the AWS Guide for details. + +- name: Create new nat gateway with client token. + community.aws.ec2_vpc_nat_gateway: + state: present + subnet_id: subnet-12345678 + eip_address: 52.1.1.1 + region: ap-southeast-2 + client_token: abcd-12345678 + register: new_nat_gateway + +- name: Create new nat gateway using an allocation-id. + community.aws.ec2_vpc_nat_gateway: + state: present + subnet_id: subnet-12345678 + allocation_id: eipalloc-12345678 + region: ap-southeast-2 + register: new_nat_gateway + +- name: Create new nat gateway, using an EIP address and wait for available status. + community.aws.ec2_vpc_nat_gateway: + state: present + subnet_id: subnet-12345678 + eip_address: 52.1.1.1 + wait: true + region: ap-southeast-2 + register: new_nat_gateway + +- name: Create new nat gateway and allocate new EIP. + community.aws.ec2_vpc_nat_gateway: + state: present + subnet_id: subnet-12345678 + wait: true + region: ap-southeast-2 + register: new_nat_gateway + +- name: Create new nat gateway and allocate new EIP if a nat gateway does not yet exist in the subnet. + community.aws.ec2_vpc_nat_gateway: + state: present + subnet_id: subnet-12345678 + wait: true + region: ap-southeast-2 + if_exist_do_not_create: true + register: new_nat_gateway + +- name: Delete nat gateway using discovered nat gateways from facts module. + community.aws.ec2_vpc_nat_gateway: + state: absent + region: ap-southeast-2 + wait: true + nat_gateway_id: "{{ item.NatGatewayId }}" + release_eip: true + register: delete_nat_gateway_result + loop: "{{ gateways_to_remove.result }}" + +- name: Delete nat gateway and wait for deleted status. + community.aws.ec2_vpc_nat_gateway: + state: absent + nat_gateway_id: nat-12345678 + wait: true + wait_timeout: 500 + region: ap-southeast-2 + +- name: Delete nat gateway and release EIP. + community.aws.ec2_vpc_nat_gateway: + state: absent + nat_gateway_id: nat-12345678 + release_eip: true + wait: yes + wait_timeout: 300 + region: ap-southeast-2 + +- name: Create new nat gateway using allocation-id and tags. + community.aws.ec2_vpc_nat_gateway: + state: present + subnet_id: subnet-12345678 + allocation_id: eipalloc-12345678 + region: ap-southeast-2 + tags: + Tag1: tag1 + Tag2: tag2 + register: new_nat_gateway + +- name: Update tags without purge + community.aws.ec2_vpc_nat_gateway: + subnet_id: subnet-12345678 + allocation_id: eipalloc-12345678 + region: ap-southeast-2 + purge_tags: no + tags: + Tag3: tag3 + wait: yes + register: update_tags_nat_gateway +''' + +RETURN = r''' +create_time: + description: The ISO 8601 date time format in UTC. + returned: In all cases. + type: str + sample: "2016-03-05T05:19:20.282000+00:00'" +nat_gateway_id: + description: id of the VPC NAT Gateway + returned: In all cases. + type: str + sample: "nat-0d1e3a878585988f8" +subnet_id: + description: id of the Subnet + returned: In all cases. + type: str + sample: "subnet-12345" +state: + description: The current state of the NAT Gateway. + returned: In all cases. + type: str + sample: "available" +tags: + description: The tags associated the VPC NAT Gateway. + type: dict + returned: When tags are present. + sample: + tags: + "Ansible": "Test" +vpc_id: + description: id of the VPC. + returned: In all cases. + type: str + sample: "vpc-12345" +nat_gateway_addresses: + description: List of dictionaries containing the public_ip, network_interface_id, private_ip, and allocation_id. + returned: In all cases. + type: str + sample: [ + { + 'public_ip': '52.52.52.52', + 'network_interface_id': 'eni-12345', + 'private_ip': '10.0.0.100', + 'allocation_id': 'eipalloc-12345' + } + ] +''' + +import datetime + +try: + import botocore +except ImportError: + pass # Handled by AnsibleAWSModule + +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code +from ansible_collections.amazon.aws.plugins.module_utils.waiters import get_waiter +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_filter_list +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_tag_list +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import compare_aws_tags + + +@AWSRetry.jittered_backoff(retries=10) +def _describe_nat_gateways(client, **params): + try: + paginator = client.get_paginator('describe_nat_gateways') + return paginator.paginate(**params).build_full_result()['NatGateways'] + except is_boto3_error_code('InvalidNatGatewayID.NotFound'): + return None + + +def wait_for_status(client, module, waiter_name, nat_gateway_id): + wait_timeout = module.params.get('wait_timeout') + try: + waiter = get_waiter(client, waiter_name) + attempts = 1 + int(wait_timeout / waiter.config.delay) + waiter.wait( + NatGatewayIds=[nat_gateway_id], + WaiterConfig={'MaxAttempts': attempts} + ) + except botocore.exceptions.WaiterError as e: + module.fail_json_aws(e, msg="NAT gateway failed to reach expected state.") + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="Unable to wait for NAT gateway state to update.") + + +def get_nat_gateways(client, module, subnet_id=None, nat_gateway_id=None, states=None): + """Retrieve a list of NAT Gateways + Args: + client (botocore.client.EC2): Boto3 client + module: AnsibleAWSModule class instance + + Kwargs: + subnet_id (str): The subnet_id the nat resides in. + nat_gateway_id (str): The Amazon NAT id. + states (list): States available (pending, failed, available, deleting, and deleted) + default=None + + Basic Usage: + >>> client = boto3.client('ec2') + >>> module = AnsibleAWSModule(...) + >>> subnet_id = 'subnet-12345678' + >>> get_nat_gateways(client, module, subnet_id) + [ + true, + "", + { + "create_time": "2016-03-05T00:33:21.209000+00:00", + "delete_time": "2016-03-05T00:36:37.329000+00:00", + "nat_gateway_addresses": [ + { + "public_ip": "55.55.55.55", + "network_interface_id": "eni-1234567", + "private_ip": "10.0.0.102", + "allocation_id": "eipalloc-1234567" + } + ], + "nat_gateway_id": "nat-123456789", + "state": "deleted", + "subnet_id": "subnet-123456789", + "tags": {}, + "vpc_id": "vpc-12345678" + } + + Returns: + Tuple (bool, str, list) + """ + + params = dict() + existing_gateways = list() + + if not states: + states = ['available', 'pending'] + if nat_gateway_id: + params['NatGatewayIds'] = [nat_gateway_id] + else: + params['Filter'] = [ + { + 'Name': 'subnet-id', + 'Values': [subnet_id] + }, + { + 'Name': 'state', + 'Values': states + } + ] + + try: + gateways = _describe_nat_gateways(client, **params) + if gateways: + for gw in gateways: + existing_gateways.append(camel_dict_to_snake_dict(gw)) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e) + + return existing_gateways + + +def gateway_in_subnet_exists(client, module, subnet_id, allocation_id=None): + """Retrieve all NAT Gateways for a subnet. + Args: + client (botocore.client.EC2): Boto3 client + module: AnsibleAWSModule class instance + subnet_id (str): The subnet_id the nat resides in. + + Kwargs: + allocation_id (str): The EIP Amazon identifier. + default = None + + Basic Usage: + >>> client = boto3.client('ec2') + >>> module = AnsibleAWSModule(...) + >>> subnet_id = 'subnet-1234567' + >>> allocation_id = 'eipalloc-1234567' + >>> gateway_in_subnet_exists(client, module, subnet_id, allocation_id) + ( + [ + { + "create_time": "2016-03-05T00:33:21.209000+00:00", + "delete_time": "2016-03-05T00:36:37.329000+00:00", + "nat_gateway_addresses": [ + { + "public_ip": "55.55.55.55", + "network_interface_id": "eni-1234567", + "private_ip": "10.0.0.102", + "allocation_id": "eipalloc-1234567" + } + ], + "nat_gateway_id": "nat-123456789", + "state": "deleted", + "subnet_id": "subnet-123456789", + "tags": {}, + "vpc_id": "vpc-1234567" + } + ], + False + ) + + Returns: + Tuple (list, bool) + """ + + allocation_id_exists = False + gateways = [] + states = ['available', 'pending'] + + gws_retrieved = (get_nat_gateways(client, module, subnet_id, states=states)) + + if gws_retrieved: + for gw in gws_retrieved: + for address in gw['nat_gateway_addresses']: + if allocation_id: + if address.get('allocation_id') == allocation_id: + allocation_id_exists = True + gateways.append(gw) + else: + gateways.append(gw) + + return gateways, allocation_id_exists + + +def get_eip_allocation_id_by_address(client, module, eip_address): + """Release an EIP from your EIP Pool + Args: + client (botocore.client.EC2): Boto3 client + module: AnsibleAWSModule class instance + eip_address (str): The Elastic IP Address of the EIP. + + Basic Usage: + >>> client = boto3.client('ec2') + >>> module = AnsibleAWSModule(...) + >>> eip_address = '52.87.29.36' + >>> get_eip_allocation_id_by_address(client, module, eip_address) + 'eipalloc-36014da3' + + Returns: + Tuple (str, str) + """ + + params = { + 'PublicIps': [eip_address], + } + allocation_id = None + msg = '' + + try: + allocations = client.describe_addresses(aws_retry=True, **params)['Addresses'] + + if len(allocations) == 1: + allocation = allocations[0] + else: + allocation = None + + if allocation: + if allocation.get('Domain') != 'vpc': + msg = ( + "EIP {0} is a non-VPC EIP, please allocate a VPC scoped EIP" + .format(eip_address) + ) + else: + allocation_id = allocation.get('AllocationId') + + except is_boto3_error_code('InvalidAddress.Malformed') as e: + module.fail_json(msg='EIP address {0} is invalid.'.format(eip_address)) + except is_boto3_error_code('InvalidAddress.NotFound') as e: # pylint: disable=duplicate-except + msg = ( + "EIP {0} does not exist".format(eip_address) + ) + allocation_id = None + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except + module.fail_json_aws(e) + + return allocation_id, msg + + +def allocate_eip_address(client, module): + """Release an EIP from your EIP Pool + Args: + client (botocore.client.EC2): Boto3 client + module: AnsibleAWSModule class instance + + Basic Usage: + >>> client = boto3.client('ec2') + >>> module = AnsibleAWSModule(...) + >>> allocate_eip_address(client, module) + True + + Returns: + Tuple (bool, str) + """ + + new_eip = None + msg = '' + params = { + 'Domain': 'vpc', + } + + if module.check_mode: + ip_allocated = True + new_eip = None + return ip_allocated, msg, new_eip + + try: + new_eip = client.allocate_address(aws_retry=True, **params)['AllocationId'] + ip_allocated = True + msg = 'eipalloc id {0} created'.format(new_eip) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e) + + return ip_allocated, msg, new_eip + + +def release_address(client, module, allocation_id): + """Release an EIP from your EIP Pool + Args: + client (botocore.client.EC2): Boto3 client + module: AnsibleAWSModule class instance + allocation_id (str): The eip Amazon identifier. + + Basic Usage: + >>> client = boto3.client('ec2') + >>> module = AnsibleAWSModule(...) + >>> allocation_id = "eipalloc-123456" + >>> release_address(client, module, allocation_id) + True + + Returns: + Boolean, string + """ + + msg = '' + + if module.check_mode: + return True, '' + + ip_released = False + + try: + client.describe_addresses(aws_retry=True, AllocationIds=[allocation_id]) + except is_boto3_error_code('InvalidAllocationID.NotFound') as e: + # IP address likely already released + # Happens with gateway in 'deleted' state that + # still lists associations + return True, e + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except + module.fail_json_aws(e) + + try: + client.release_address(aws_retry=True, AllocationId=allocation_id) + ip_released = True + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e) + + return ip_released, msg + + +def create(client, module, subnet_id, allocation_id, tags, purge_tags, client_token=None, + wait=False): + """Create an Amazon NAT Gateway. + Args: + client (botocore.client.EC2): Boto3 client + module: AnsibleAWSModule class instance + subnet_id (str): The subnet_id the nat resides in + allocation_id (str): The eip Amazon identifier + tags (dict): Tags to associate to the NAT gateway + purge_tags (bool): If true, remove tags not listed in I(tags) + type: bool + + Kwargs: + wait (bool): Wait for the nat to be in the deleted state before returning. + default = False + client_token (str): + default = None + + Basic Usage: + >>> client = boto3.client('ec2') + >>> module = AnsibleAWSModule(...) + >>> subnet_id = 'subnet-1234567' + >>> allocation_id = 'eipalloc-1234567' + >>> create(client, module, subnet_id, allocation_id, wait=True) + [ + true, + "", + { + "create_time": "2016-03-05T00:33:21.209000+00:00", + "delete_time": "2016-03-05T00:36:37.329000+00:00", + "nat_gateway_addresses": [ + { + "public_ip": "55.55.55.55", + "network_interface_id": "eni-1234567", + "private_ip": "10.0.0.102", + "allocation_id": "eipalloc-1234567" + } + ], + "nat_gateway_id": "nat-123456789", + "state": "deleted", + "subnet_id": "subnet-1234567", + "tags": {}, + "vpc_id": "vpc-1234567" + } + ] + + Returns: + Tuple (bool, str, list) + """ + + params = { + 'SubnetId': subnet_id, + 'AllocationId': allocation_id + } + request_time = datetime.datetime.utcnow() + changed = False + token_provided = False + result = {} + msg = '' + + if client_token: + token_provided = True + params['ClientToken'] = client_token + + if module.check_mode: + changed = True + return changed, result, msg + + try: + result = camel_dict_to_snake_dict(client.create_nat_gateway(aws_retry=True, **params)["NatGateway"]) + changed = True + + create_time = result['create_time'].replace(tzinfo=None) + + if token_provided and (request_time > create_time): + changed = False + + elif wait and result.get('state') != 'available': + wait_for_status(client, module, 'nat_gateway_available', result['nat_gateway_id']) + + # Get new result + result = camel_dict_to_snake_dict( + _describe_nat_gateways(client, NatGatewayIds=[result['nat_gateway_id']])[0] + ) + + result['tags'], _tags_update_exists = ensure_tags( + client, module, nat_gw_id=result['nat_gateway_id'], tags=tags, + purge_tags=purge_tags + ) + except is_boto3_error_code('IdempotentParameterMismatch') as e: + msg = ( + 'NAT Gateway does not support update and token has already been provided:' + e + ) + changed = False + result = None + except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: # pylint: disable=duplicate-except + module.fail_json_aws(e) + + return changed, result, msg + + +def pre_create(client, module, subnet_id, tags, purge_tags, allocation_id=None, eip_address=None, + if_exist_do_not_create=False, wait=False, client_token=None): + """Create an Amazon NAT Gateway. + Args: + client (botocore.client.EC2): Boto3 client + module: AnsibleAWSModule class instance + subnet_id (str): The subnet_id the nat resides in + tags (dict): Tags to associate to the NAT gateway + purge_tags (bool): If true, remove tags not listed in I(tags) + + Kwargs: + allocation_id (str): The EIP Amazon identifier. + default = None + eip_address (str): The Elastic IP Address of the EIP. + default = None + if_exist_do_not_create (bool): if a nat gateway already exists in this + subnet, than do not create another one. + default = False + wait (bool): Wait for the nat to be in the deleted state before returning. + default = False + client_token (str): + default = None + + Basic Usage: + >>> client = boto3.client('ec2') + >>> module = AnsibleAWSModule(...) + >>> subnet_id = 'subnet-w4t12897' + >>> allocation_id = 'eipalloc-36014da3' + >>> pre_create(client, module, subnet_id, allocation_id, if_exist_do_not_create=True, wait=True) + [ + true, + "", + { + "create_time": "2016-03-05T00:33:21.209000+00:00", + "delete_time": "2016-03-05T00:36:37.329000+00:00", + "nat_gateway_addresses": [ + { + "public_ip": "52.87.29.36", + "network_interface_id": "eni-5579742d", + "private_ip": "10.0.0.102", + "allocation_id": "eipalloc-36014da3" + } + ], + "nat_gateway_id": "nat-03835afb6e31df79b", + "state": "deleted", + "subnet_id": "subnet-w4t12897", + "tags": {}, + "vpc_id": "vpc-w68571b5" + } + ] + + Returns: + Tuple (bool, bool, str, list) + """ + + changed = False + msg = '' + results = {} + + if not allocation_id and not eip_address: + existing_gateways, allocation_id_exists = (gateway_in_subnet_exists(client, module, subnet_id)) + + if len(existing_gateways) > 0 and if_exist_do_not_create: + results = existing_gateways[0] + results['tags'], tags_update_exists = ensure_tags( + client, module, results['nat_gateway_id'], tags, purge_tags + ) + + if tags_update_exists: + changed = True + return changed, msg, results + + changed = False + msg = ( + 'NAT Gateway {0} already exists in subnet_id {1}' + .format( + existing_gateways[0]['nat_gateway_id'], subnet_id + ) + ) + return changed, msg, results + else: + changed, msg, allocation_id = ( + allocate_eip_address(client, module) + ) + if not changed: + return changed, msg, dict() + + elif eip_address or allocation_id: + if eip_address and not allocation_id: + allocation_id, msg = ( + get_eip_allocation_id_by_address( + client, module, eip_address + ) + ) + if not allocation_id: + changed = False + return changed, msg, dict() + + existing_gateways, allocation_id_exists = ( + gateway_in_subnet_exists( + client, module, subnet_id, allocation_id + ) + ) + + if len(existing_gateways) > 0 and (allocation_id_exists or if_exist_do_not_create): + results = existing_gateways[0] + results['tags'], tags_update_exists = ensure_tags( + client, module, results['nat_gateway_id'], tags, purge_tags + ) + + if tags_update_exists: + changed = True + return changed, msg, results + + changed = False + msg = ( + 'NAT Gateway {0} already exists in subnet_id {1}' + .format( + existing_gateways[0]['nat_gateway_id'], subnet_id + ) + ) + return changed, msg, results + + changed, results, msg = create( + client, module, subnet_id, allocation_id, tags, purge_tags, client_token, wait + ) + + return changed, msg, results + + +def remove(client, module, nat_gateway_id, wait=False, release_eip=False): + """Delete an Amazon NAT Gateway. + Args: + client (botocore.client.EC2): Boto3 client + module: AnsibleAWSModule class instance + nat_gateway_id (str): The Amazon nat id + + Kwargs: + wait (bool): Wait for the nat to be in the deleted state before returning. + release_eip (bool): Once the nat has been deleted, you can deallocate the eip from the vpc. + + Basic Usage: + >>> client = boto3.client('ec2') + >>> module = AnsibleAWSModule(...) + >>> nat_gw_id = 'nat-03835afb6e31df79b' + >>> remove(client, module, nat_gw_id, wait=True, release_eip=True) + [ + true, + "", + { + "create_time": "2016-03-05T00:33:21.209000+00:00", + "delete_time": "2016-03-05T00:36:37.329000+00:00", + "nat_gateway_addresses": [ + { + "public_ip": "52.87.29.36", + "network_interface_id": "eni-5579742d", + "private_ip": "10.0.0.102", + "allocation_id": "eipalloc-36014da3" + } + ], + "nat_gateway_id": "nat-03835afb6e31df79b", + "state": "deleted", + "subnet_id": "subnet-w4t12897", + "tags": {}, + "vpc_id": "vpc-w68571b5" + } + ] + + Returns: + Tuple (bool, str, list) + """ + + params = { + 'NatGatewayId': nat_gateway_id + } + changed = False + results = {} + states = ['pending', 'available'] + msg = '' + + if module.check_mode: + changed = True + return changed, msg, results + + try: + gw_list = ( + get_nat_gateways( + client, module, nat_gateway_id=nat_gateway_id, + states=states + ) + ) + + if len(gw_list) == 1: + results = gw_list[0] + client.delete_nat_gateway(aws_retry=True, **params) + allocation_id = ( + results['nat_gateway_addresses'][0]['allocation_id'] + ) + changed = True + msg = ( + 'NAT gateway {0} is in a deleting state. Delete was successful' + .format(nat_gateway_id) + ) + + if wait and results.get('state') != 'deleted': + wait_for_status(client, module, 'nat_gateway_deleted', nat_gateway_id) + + # Get new results + results = camel_dict_to_snake_dict( + _describe_nat_gateways(client, NatGatewayIds=[nat_gateway_id])[0] + ) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e) + + if release_eip: + eip_released, msg = ( + release_address(client, module, allocation_id)) + if not eip_released: + module.fail_json( + msg="Failed to release EIP {0}: {1}".format(allocation_id, msg) + ) + + return changed, msg, results + + +def ensure_tags(client, module, nat_gw_id, tags, purge_tags): + final_tags = [] + changed = False + + if module.check_mode and nat_gw_id is None: + # We can't describe tags without an EIP id, we might get here when creating a new EIP in check_mode + return final_tags, changed + + filters = ansible_dict_to_boto3_filter_list({'resource-id': nat_gw_id, 'resource-type': 'natgateway'}) + cur_tags = None + try: + cur_tags = client.describe_tags(aws_retry=True, Filters=filters) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, 'Couldnt describe tags') + if tags is None: + return boto3_tag_list_to_ansible_dict(cur_tags['Tags']), changed + + to_update, to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(cur_tags.get('Tags')), tags, purge_tags) + final_tags = boto3_tag_list_to_ansible_dict(cur_tags.get('Tags')) + + if to_update: + try: + if module.check_mode: + final_tags.update(to_update) + else: + client.create_tags( + aws_retry=True, + Resources=[nat_gw_id], + Tags=ansible_dict_to_boto3_tag_list(to_update) + ) + changed = True + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, "Couldn't create tags") + + if to_delete: + try: + if module.check_mode: + for key in to_delete: + del final_tags[key] + else: + tags_list = [] + for key in to_delete: + tags_list.append({'Key': key}) + + client.delete_tags(aws_retry=True, Resources=[nat_gw_id], Tags=tags_list) + changed = True + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, "Couldn't delete tags") + + if not module.check_mode and (to_update or to_delete): + try: + response = client.describe_tags(aws_retry=True, Filters=filters) + final_tags = boto3_tag_list_to_ansible_dict(response.get('Tags')) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, "Couldn't describe tags") + + return final_tags, changed + + +def main(): + argument_spec = dict( + subnet_id=dict(type='str'), + eip_address=dict(type='str'), + allocation_id=dict(type='str'), + if_exist_do_not_create=dict(type='bool', default=False), + state=dict(default='present', choices=['present', 'absent']), + wait=dict(type='bool', default=False), + wait_timeout=dict(type='int', default=320, required=False), + release_eip=dict(type='bool', default=False), + nat_gateway_id=dict(type='str'), + client_token=dict(type='str', no_log=False), + tags=dict(required=False, type='dict', aliases=['resource_tags']), + purge_tags=dict(default=True, type='bool'), + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + mutually_exclusive=[ + ['allocation_id', 'eip_address'] + ], + required_if=[['state', 'absent', ['nat_gateway_id']], + ['state', 'present', ['subnet_id']]], + ) + + state = module.params.get('state').lower() + subnet_id = module.params.get('subnet_id') + allocation_id = module.params.get('allocation_id') + eip_address = module.params.get('eip_address') + nat_gateway_id = module.params.get('nat_gateway_id') + wait = module.params.get('wait') + release_eip = module.params.get('release_eip') + client_token = module.params.get('client_token') + if_exist_do_not_create = module.params.get('if_exist_do_not_create') + tags = module.params.get('tags') + purge_tags = module.params.get('purge_tags') + + try: + client = module.client('ec2', retry_decorator=AWSRetry.jittered_backoff()) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg='Failed to connect to AWS.') + + changed = False + msg = '' + + if state == 'present': + changed, msg, results = ( + pre_create( + client, module, subnet_id, tags, purge_tags, allocation_id, eip_address, + if_exist_do_not_create, wait, client_token + ) + ) + else: + changed, msg, results = ( + remove( + client, module, nat_gateway_id, wait, release_eip + ) + ) + + module.exit_json(msg=msg, changed=changed, **results) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/ec2_vpc_nat_gateway_facts.py b/plugins/modules/ec2_vpc_nat_gateway_facts.py new file mode 100755 index 00000000000..5acd59a819a --- /dev/null +++ b/plugins/modules/ec2_vpc_nat_gateway_facts.py @@ -0,0 +1,218 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +module: ec2_vpc_nat_gateway_info +short_description: Retrieves AWS VPC Managed Nat Gateway details using AWS methods. +version_added: 1.0.0 +description: + - Gets various details related to AWS VPC Managed Nat Gateways + - This module was called C(ec2_vpc_nat_gateway_facts) before Ansible 2.9. The usage did not change. +options: + nat_gateway_ids: + description: + - List of specific nat gateway IDs to fetch details for. + type: list + elements: str + filters: + description: + - A dict of filters to apply. Each dict item consists of a filter key and a filter value. + See U(https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeNatGateways.html) + for possible filters. + type: dict +author: Karen Cheng (@Etherdaemon) +extends_documentation_fragment: +- amazon.aws.aws +- amazon.aws.ec2 +''' + +EXAMPLES = r''' +# Simple example of listing all nat gateways +- name: List all managed nat gateways in ap-southeast-2 + community.aws.ec2_vpc_nat_gateway_info: + region: ap-southeast-2 + register: all_ngws + +- name: Debugging the result + ansible.builtin.debug: + msg: "{{ all_ngws.result }}" + +- name: Get details on specific nat gateways + community.aws.ec2_vpc_nat_gateway_info: + nat_gateway_ids: + - nat-1234567891234567 + - nat-7654321987654321 + region: ap-southeast-2 + register: specific_ngws + +- name: Get all nat gateways with specific filters + community.aws.ec2_vpc_nat_gateway_info: + region: ap-southeast-2 + filters: + state: ['pending'] + register: pending_ngws + +- name: Get nat gateways with specific filter + community.aws.ec2_vpc_nat_gateway_info: + region: ap-southeast-2 + filters: + subnet-id: subnet-12345678 + state: ['available'] + register: existing_nat_gateways +''' + +RETURN = r''' +changed: + description: True if listing the internet gateways succeeds + type: bool + returned: always + sample: false +result: + description: + - The result of the describe, converted to ansible snake case style. + - See also U(http://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.describe_nat_gateways) + returned: suceess + type: list + contains: + create_time: + description: The date and time the NAT gateway was created + returned: always + type: str + sample: "2021-03-11T22:43:25+00:00" + delete_time: + description: The date and time the NAT gateway was deleted + returned: when the NAT gateway has been deleted + type: str + sample: "2021-03-11T22:43:25+00:00" + nat_gateway_addresses: + description: List containing a dictionary with the IP addresses and network interface associated with the NAT gateway + returned: always + type: dict + contains: + allocation_id: + description: The allocation ID of the Elastic IP address that's associated with the NAT gateway + returned: always + type: str + sample: eipalloc-0853e66a40803da76 + network_interface_id: + description: The ID of the network interface associated with the NAT gateway + returned: always + type: str + sample: eni-0a37acdbe306c661c + private_ip: + description: The private IP address associated with the Elastic IP address + returned: always + type: str + sample: 10.0.238.227 + public_ip: + description: The Elastic IP address associated with the NAT gateway + returned: always + type: str + sample: 34.204.123.52 + nat_gateway_id: + description: The ID of the NAT gateway + returned: always + type: str + sample: nat-0c242a2397acf6173 + state: + description: state of the NAT gateway + returned: always + type: str + sample: available + subnet_id: + description: The ID of the subnet in which the NAT gateway is located + returned: always + type: str + sample: subnet-098c447465d4344f9 + vpc_id: + description: The ID of the VPC in which the NAT gateway is located + returned: always + type: str + sample: vpc-02f37f48438ab7d4c + tags: + description: Tags applied to the NAT gateway + returned: always + type: dict + sample: + Tag1: tag1 + Tag_2: tag_2 +''' + + +try: + import botocore +except ImportError: + pass # Handled by AnsibleAWSModule + +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_filter_list +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict +from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code +from ansible_collections.amazon.aws.plugins.module_utils.core import normalize_boto3_result + + +@AWSRetry.jittered_backoff(retries=10) +def _describe_nat_gateways(client, module, **params): + try: + paginator = client.get_paginator('describe_nat_gateways') + return paginator.paginate(**params).build_full_result()['NatGateways'] + except is_boto3_error_code('InvalidNatGatewayID.NotFound'): + module.exit_json(msg="NAT gateway not found.") + except is_boto3_error_code('NatGatewayMalformed'): # pylint: disable=duplicate-except + module.fail_json_aws(msg="NAT gateway id is malformed.") + + +def get_nat_gateways(client, module): + params = dict() + nat_gateways = list() + + params['Filter'] = ansible_dict_to_boto3_filter_list(module.params.get('filters')) + params['NatGatewayIds'] = module.params.get('nat_gateway_ids') + + try: + result = normalize_boto3_result(_describe_nat_gateways(client, module, **params)) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, 'Unable to describe NAT gateways.') + + for gateway in result: + # Turn the boto3 result into ansible_friendly_snaked_names + converted_gateway = camel_dict_to_snake_dict(gateway) + if 'tags' in converted_gateway: + # Turn the boto3 result into ansible friendly tag dictionary + converted_gateway['tags'] = boto3_tag_list_to_ansible_dict(converted_gateway['tags']) + nat_gateways.append(converted_gateway) + + return nat_gateways + + +def main(): + argument_spec = dict( + filters=dict(default={}, type='dict'), + nat_gateway_ids=dict(default=[], type='list', elements='str'), + ) + + module = AnsibleAWSModule(argument_spec=argument_spec, + supports_check_mode=True,) + if module._name == 'ec2_vpc_nat_gateway_facts': + module.deprecate("The 'ec2_vpc_nat_gateway_facts' module has been renamed to 'ec2_vpc_nat_gateway_info'", + date='2021-12-01', collection_name='community.aws') + + try: + connection = module.client('ec2', retry_decorator=AWSRetry.jittered_backoff()) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg='Failed to connect to AWS') + + results = get_nat_gateways(connection, module) + + module.exit_json(result=results) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/ec2_vpc_nat_gateway_info.py b/plugins/modules/ec2_vpc_nat_gateway_info.py new file mode 100644 index 00000000000..5acd59a819a --- /dev/null +++ b/plugins/modules/ec2_vpc_nat_gateway_info.py @@ -0,0 +1,218 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +module: ec2_vpc_nat_gateway_info +short_description: Retrieves AWS VPC Managed Nat Gateway details using AWS methods. +version_added: 1.0.0 +description: + - Gets various details related to AWS VPC Managed Nat Gateways + - This module was called C(ec2_vpc_nat_gateway_facts) before Ansible 2.9. The usage did not change. +options: + nat_gateway_ids: + description: + - List of specific nat gateway IDs to fetch details for. + type: list + elements: str + filters: + description: + - A dict of filters to apply. Each dict item consists of a filter key and a filter value. + See U(https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeNatGateways.html) + for possible filters. + type: dict +author: Karen Cheng (@Etherdaemon) +extends_documentation_fragment: +- amazon.aws.aws +- amazon.aws.ec2 +''' + +EXAMPLES = r''' +# Simple example of listing all nat gateways +- name: List all managed nat gateways in ap-southeast-2 + community.aws.ec2_vpc_nat_gateway_info: + region: ap-southeast-2 + register: all_ngws + +- name: Debugging the result + ansible.builtin.debug: + msg: "{{ all_ngws.result }}" + +- name: Get details on specific nat gateways + community.aws.ec2_vpc_nat_gateway_info: + nat_gateway_ids: + - nat-1234567891234567 + - nat-7654321987654321 + region: ap-southeast-2 + register: specific_ngws + +- name: Get all nat gateways with specific filters + community.aws.ec2_vpc_nat_gateway_info: + region: ap-southeast-2 + filters: + state: ['pending'] + register: pending_ngws + +- name: Get nat gateways with specific filter + community.aws.ec2_vpc_nat_gateway_info: + region: ap-southeast-2 + filters: + subnet-id: subnet-12345678 + state: ['available'] + register: existing_nat_gateways +''' + +RETURN = r''' +changed: + description: True if listing the internet gateways succeeds + type: bool + returned: always + sample: false +result: + description: + - The result of the describe, converted to ansible snake case style. + - See also U(http://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.describe_nat_gateways) + returned: suceess + type: list + contains: + create_time: + description: The date and time the NAT gateway was created + returned: always + type: str + sample: "2021-03-11T22:43:25+00:00" + delete_time: + description: The date and time the NAT gateway was deleted + returned: when the NAT gateway has been deleted + type: str + sample: "2021-03-11T22:43:25+00:00" + nat_gateway_addresses: + description: List containing a dictionary with the IP addresses and network interface associated with the NAT gateway + returned: always + type: dict + contains: + allocation_id: + description: The allocation ID of the Elastic IP address that's associated with the NAT gateway + returned: always + type: str + sample: eipalloc-0853e66a40803da76 + network_interface_id: + description: The ID of the network interface associated with the NAT gateway + returned: always + type: str + sample: eni-0a37acdbe306c661c + private_ip: + description: The private IP address associated with the Elastic IP address + returned: always + type: str + sample: 10.0.238.227 + public_ip: + description: The Elastic IP address associated with the NAT gateway + returned: always + type: str + sample: 34.204.123.52 + nat_gateway_id: + description: The ID of the NAT gateway + returned: always + type: str + sample: nat-0c242a2397acf6173 + state: + description: state of the NAT gateway + returned: always + type: str + sample: available + subnet_id: + description: The ID of the subnet in which the NAT gateway is located + returned: always + type: str + sample: subnet-098c447465d4344f9 + vpc_id: + description: The ID of the VPC in which the NAT gateway is located + returned: always + type: str + sample: vpc-02f37f48438ab7d4c + tags: + description: Tags applied to the NAT gateway + returned: always + type: dict + sample: + Tag1: tag1 + Tag_2: tag_2 +''' + + +try: + import botocore +except ImportError: + pass # Handled by AnsibleAWSModule + +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_filter_list +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict +from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code +from ansible_collections.amazon.aws.plugins.module_utils.core import normalize_boto3_result + + +@AWSRetry.jittered_backoff(retries=10) +def _describe_nat_gateways(client, module, **params): + try: + paginator = client.get_paginator('describe_nat_gateways') + return paginator.paginate(**params).build_full_result()['NatGateways'] + except is_boto3_error_code('InvalidNatGatewayID.NotFound'): + module.exit_json(msg="NAT gateway not found.") + except is_boto3_error_code('NatGatewayMalformed'): # pylint: disable=duplicate-except + module.fail_json_aws(msg="NAT gateway id is malformed.") + + +def get_nat_gateways(client, module): + params = dict() + nat_gateways = list() + + params['Filter'] = ansible_dict_to_boto3_filter_list(module.params.get('filters')) + params['NatGatewayIds'] = module.params.get('nat_gateway_ids') + + try: + result = normalize_boto3_result(_describe_nat_gateways(client, module, **params)) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, 'Unable to describe NAT gateways.') + + for gateway in result: + # Turn the boto3 result into ansible_friendly_snaked_names + converted_gateway = camel_dict_to_snake_dict(gateway) + if 'tags' in converted_gateway: + # Turn the boto3 result into ansible friendly tag dictionary + converted_gateway['tags'] = boto3_tag_list_to_ansible_dict(converted_gateway['tags']) + nat_gateways.append(converted_gateway) + + return nat_gateways + + +def main(): + argument_spec = dict( + filters=dict(default={}, type='dict'), + nat_gateway_ids=dict(default=[], type='list', elements='str'), + ) + + module = AnsibleAWSModule(argument_spec=argument_spec, + supports_check_mode=True,) + if module._name == 'ec2_vpc_nat_gateway_facts': + module.deprecate("The 'ec2_vpc_nat_gateway_facts' module has been renamed to 'ec2_vpc_nat_gateway_info'", + date='2021-12-01', collection_name='community.aws') + + try: + connection = module.client('ec2', retry_decorator=AWSRetry.jittered_backoff()) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg='Failed to connect to AWS') + + results = get_nat_gateways(connection, module) + + module.exit_json(result=results) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/ec2_vpc_nat_gateway/aliases b/tests/integration/targets/ec2_vpc_nat_gateway/aliases new file mode 100644 index 00000000000..f5291520b8b --- /dev/null +++ b/tests/integration/targets/ec2_vpc_nat_gateway/aliases @@ -0,0 +1,2 @@ +cloud/aws +ec2_vpc_nat_gateway_info diff --git a/tests/integration/targets/ec2_vpc_nat_gateway/defaults/main.yml b/tests/integration/targets/ec2_vpc_nat_gateway/defaults/main.yml new file mode 100644 index 00000000000..6ea912c898e --- /dev/null +++ b/tests/integration/targets/ec2_vpc_nat_gateway/defaults/main.yml @@ -0,0 +1,5 @@ +--- +vpc_name: "{{ resource_prefix }}-vpc" +vpc_seed: "{{ resource_prefix }}" +vpc_cidr: "10.0.0.0/16" +subnet_cidr: "10.0.{{ 256 | random(seed=vpc_seed) }}.0/24" diff --git a/tests/integration/targets/ec2_vpc_nat_gateway/tasks/main.yml b/tests/integration/targets/ec2_vpc_nat_gateway/tasks/main.yml new file mode 100644 index 00000000000..e7e215559f9 --- /dev/null +++ b/tests/integration/targets/ec2_vpc_nat_gateway/tasks/main.yml @@ -0,0 +1,946 @@ +--- +- name: ec2_vpc_nat_gateway tests + module_defaults: + group/aws: + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + security_token: "{{ security_token | default(omit) }}" + region: "{{ aws_region }}" + collections: + - amazon.aws + + block: + + # ============================================================ + - name: Create a VPC + ec2_vpc_net: + name: "{{ vpc_name }}" + state: present + cidr_block: "{{ vpc_cidr }}" + register: vpc_result + + - name: Assert success + assert: + that: + - vpc_result is successful + - '"vpc" in vpc_result' + - '"cidr_block" in vpc_result.vpc' + - vpc_result.vpc.cidr_block == vpc_cidr + - '"id" in vpc_result.vpc' + - vpc_result.vpc.id.startswith("vpc-") + - '"state" in vpc_result.vpc' + - vpc_result.vpc.state == 'available' + - '"tags" in vpc_result.vpc' + + - name: "set fact: VPC ID" + set_fact: + vpc_id: "{{ vpc_result.vpc.id }}" + + + # ============================================================ + - name: Allocate a new EIP + ec2_eip: + in_vpc: true + reuse_existing_ip_allowed: true + tag_name: FREE + register: eip_result + + - name: Assert success + assert: + that: + - eip_result is successful + - '"allocation_id" in eip_result' + - 'eip_result.allocation_id.startswith("eipalloc-")' + - '"public_ip" in eip_result' + + - name: "set fact: EIP allocation ID and EIP public IP" + set_fact: + eip_address: "{{ eip_result.public_ip }}" + allocation_id: "{{ eip_result.allocation_id }}" + + + # ============================================================ + - name: Create subnet and associate to the VPC + ec2_vpc_subnet: + state: present + vpc_id: "{{ vpc_id }}" + cidr: "{{ subnet_cidr }}" + register: subnet_result + + - name: Assert success + assert: + that: + - subnet_result is successful + - '"subnet" in subnet_result' + - '"cidr_block" in subnet_result.subnet' + - subnet_result.subnet.cidr_block == subnet_cidr + - '"id" in subnet_result.subnet' + - subnet_result.subnet.id.startswith("subnet-") + - '"state" in subnet_result.subnet' + - subnet_result.subnet.state == 'available' + - '"tags" in subnet_result.subnet' + - subnet_result.subnet.vpc_id == vpc_id + + - name: "set fact: VPC subnet ID" + set_fact: + subnet_id: "{{ subnet_result.subnet.id }}" + + + # ============================================================ + - name: Search for NAT gateways by subnet (no matches) - CHECK_MODE + ec2_vpc_nat_gateway_info: + filters: + subnet-id: "{{ subnet_id }}" + state: ['available'] + register: existing_ngws + check_mode: yes + + - name: Assert no NAT gateway found - CHECK_MODE + assert: + that: + - existing_ngws is successful + - (existing_ngws.result|length) == 0 + + - name: Search for NAT gateways by subnet - no matches + ec2_vpc_nat_gateway_info: + filters: + subnet-id: "{{ subnet_id }}" + state: ['available'] + register: existing_ngws + + - name: Assert no NAT gateway found + assert: + that: + - existing_ngws is successful + - (existing_ngws.result|length) == 0 + + + # ============================================================ + - name: Create IGW + ec2_vpc_igw: + vpc_id: "{{ vpc_id }}" + register: create_igw + + - name: Assert success + assert: + that: + - create_igw is successful + - create_igw.gateway_id.startswith("igw-") + - create_igw.vpc_id == vpc_id + - '"gateway_id" in create_igw' + + + # ============================================================ + - name: Create new NAT gateway with eip allocation-id - CHECK_MODE + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + wait: yes + register: create_ngw + check_mode: yes + + - name: Assert creation happened (expected changed=true) - CHECK_MODE + assert: + that: + - create_ngw.changed + + - name: Create new NAT gateway with eip allocation-id + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + wait: yes + register: create_ngw + + - name: Assert creation happened (expected changed=true) + assert: + that: + - create_ngw.changed + - '"create_time" in create_ngw' + - '"nat_gateway_addresses" in create_ngw' + - '"nat_gateway_id" in create_ngw' + - create_ngw.nat_gateway_addresses[0].allocation_id == allocation_id + - create_ngw.nat_gateway_id.startswith("nat-") + - '"state" in create_ngw' + - create_ngw.state == 'available' + - '"subnet_id" in create_ngw' + - create_ngw.subnet_id == subnet_id + - '"tags" in create_ngw' + - '"vpc_id" in create_ngw' + - create_ngw.vpc_id == vpc_id + + - name: "set facts: NAT gateway ID" + set_fact: + nat_gateway_id: "{{ create_ngw.nat_gateway_id }}" + network_interface_id: "{{ create_ngw.nat_gateway_addresses[0].network_interface_id }}" + + + # ============================================================ + - name: Get NAT gateway with specific filters (state and subnet) + ec2_vpc_nat_gateway_info: + filters: + subnet-id: "{{ subnet_id }}" + state: ['available'] + register: avalaible_ngws + + - name: Assert success + assert: + that: + - avalaible_ngws is successful + - avalaible_ngws.result | length == 1 + - '"create_time" in first_ngw' + - '"nat_gateway_addresses" in first_ngw' + - '"nat_gateway_id" in first_ngw' + - first_ngw.nat_gateway_id == nat_gateway_id + - '"state" in first_ngw' + - first_ngw.state == 'available' + - '"subnet_id" in first_ngw' + - first_ngw.subnet_id == subnet_id + - '"tags" in first_ngw' + - '"vpc_id" in first_ngw' + - first_ngw.vpc_id == vpc_id + vars: + first_ngw: '{{ avalaible_ngws.result[0] }}' + + # ============================================================ + - name: Trying this again for idempotency - create new NAT gateway with eip allocation-id - CHECK_MODE + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + wait: yes + register: create_ngw + check_mode: yes + + - name: Assert recreation would do nothing (expected changed=false) - CHECK_MODE + assert: + that: + - not create_ngw.changed + - '"create_time" in create_ngw' + - '"nat_gateway_addresses" in create_ngw' + - '"nat_gateway_id" in create_ngw' + - create_ngw.nat_gateway_addresses[0].allocation_id == allocation_id + - create_ngw.nat_gateway_id.startswith("nat-") + - '"state" in create_ngw' + - create_ngw.state == 'available' + - '"subnet_id" in create_ngw' + - create_ngw.subnet_id == subnet_id + - '"tags" in create_ngw' + - '"vpc_id" in create_ngw' + - create_ngw.vpc_id == vpc_id + + - name: Trying this again for idempotency - create new NAT gateway with eip allocation-id + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + wait: yes + register: create_ngw + + - name: Assert recreation would do nothing (expected changed=false) + assert: + that: + - not create_ngw.changed + - '"create_time" in create_ngw' + - '"nat_gateway_addresses" in create_ngw' + - '"nat_gateway_id" in create_ngw' + - create_ngw.nat_gateway_addresses[0].allocation_id == allocation_id + - create_ngw.nat_gateway_id.startswith("nat-") + - '"state" in create_ngw' + - create_ngw.state == 'available' + - '"subnet_id" in create_ngw' + - create_ngw.subnet_id == subnet_id + - '"tags" in create_ngw' + - '"vpc_id" in create_ngw' + - create_ngw.vpc_id == vpc_id + + + # ============================================================ + - name: Create new NAT gateway only if one does not exist already - CHECK_MODE + ec2_vpc_nat_gateway: + if_exist_do_not_create: yes + subnet_id: "{{ subnet_id }}" + wait: yes + register: create_ngw + check_mode: yes + + - name: Assert recreation would do nothing (expected changed=false) - CHECK_MODE + assert: + that: + - not create_ngw.changed + - '"create_time" in create_ngw' + - '"nat_gateway_addresses" in create_ngw' + - '"nat_gateway_id" in create_ngw' + - create_ngw.nat_gateway_addresses[0].allocation_id == allocation_id + - create_ngw.nat_gateway_id.startswith("nat-") + - '"state" in create_ngw' + - create_ngw.state == 'available' + - '"subnet_id" in create_ngw' + - create_ngw.subnet_id == subnet_id + - '"tags" in create_ngw' + - '"vpc_id" in create_ngw' + - create_ngw.vpc_id == vpc_id + + - name: Create new NAT gateway only if one does not exist already + ec2_vpc_nat_gateway: + if_exist_do_not_create: yes + subnet_id: "{{ subnet_id }}" + wait: yes + register: create_ngw + + - name: Assert recreation would do nothing (expected changed=false) + assert: + that: + - not create_ngw.changed + - '"create_time" in create_ngw' + - '"nat_gateway_addresses" in create_ngw' + - '"nat_gateway_id" in create_ngw' + - create_ngw.nat_gateway_addresses[0].allocation_id == allocation_id + - create_ngw.nat_gateway_id.startswith("nat-") + - '"state" in create_ngw' + - create_ngw.state == 'available' + - '"subnet_id" in create_ngw' + - create_ngw.subnet_id == subnet_id + - '"tags" in create_ngw' + - '"vpc_id" in create_ngw' + - create_ngw.vpc_id == vpc_id + + + # ============================================================ + - name: Allocate a new EIP + ec2_eip: + in_vpc: true + reuse_existing_ip_allowed: true + tag_name: FREE + register: eip_result + + - name: Assert success + assert: + that: + - eip_result is successful + - '"allocation_id" in eip_result' + - 'eip_result.allocation_id.startswith("eipalloc-")' + - '"public_ip" in eip_result' + + - name: "set fact: EIP allocation ID and EIP public IP" + set_fact: + second_eip_address: "{{ eip_result.public_ip }}" + second_allocation_id: "{{ eip_result.allocation_id }}" + + + # ============================================================ + - name: Create new nat gateway with eip address - CHECK_MODE + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + eip_address: "{{ second_eip_address }}" + wait: yes + register: create_ngw + check_mode: yes + + - name: Assert creation happened (expected changed=true) - CHECK_MODE + assert: + that: + - create_ngw.changed + + - name: Create new NAT gateway with eip address + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + eip_address: "{{ second_eip_address }}" + wait: yes + register: create_ngw + + - name: Assert creation happened (expected changed=true) + assert: + that: + - create_ngw.changed + - '"create_time" in create_ngw' + - '"nat_gateway_addresses" in create_ngw' + - '"nat_gateway_id" in create_ngw' + - create_ngw.nat_gateway_addresses[0].allocation_id == second_allocation_id + - create_ngw.nat_gateway_id.startswith("nat-") + - '"state" in create_ngw' + - create_ngw.state == 'available' + - '"subnet_id" in create_ngw' + - create_ngw.subnet_id == subnet_id + - '"tags" in create_ngw' + - '"vpc_id" in create_ngw' + - create_ngw.vpc_id == vpc_id + + + # ============================================================ + - name: Trying this again for idempotency - create new NAT gateway with eip address - CHECK_MODE + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + eip_address: "{{ second_eip_address }}" + wait: yes + register: create_ngw + check_mode: yes + + - name: Assert recreation would do nothing (expected changed=false) - CHECK_MODE + assert: + that: + - not create_ngw.changed + - '"create_time" in create_ngw' + - '"nat_gateway_addresses" in create_ngw' + - '"nat_gateway_id" in create_ngw' + - create_ngw.nat_gateway_addresses[0].allocation_id == second_allocation_id + - create_ngw.nat_gateway_id.startswith("nat-") + - '"state" in create_ngw' + - create_ngw.state == 'available' + - '"subnet_id" in create_ngw' + - create_ngw.subnet_id == subnet_id + - '"tags" in create_ngw' + - '"vpc_id" in create_ngw' + - create_ngw.vpc_id == vpc_id + + - name: Trying this again for idempotency - create new NAT gateway with eip address + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + eip_address: "{{ second_eip_address }}" + wait: yes + register: create_ngw + + - name: Assert recreation would do nothing (expected changed=false) + assert: + that: + - not create_ngw.changed + - '"create_time" in create_ngw' + - '"nat_gateway_addresses" in create_ngw' + - '"nat_gateway_id" in create_ngw' + - create_ngw.nat_gateway_addresses[0].allocation_id == second_allocation_id + - create_ngw.nat_gateway_id.startswith("nat-") + - '"state" in create_ngw' + - create_ngw.state == 'available' + - '"subnet_id" in create_ngw' + - create_ngw.subnet_id == subnet_id + - '"tags" in create_ngw' + - '"vpc_id" in create_ngw' + - create_ngw.vpc_id == vpc_id + + + # ============================================================ + - name: Fetch NAT gateway by ID (list) + ec2_vpc_nat_gateway_info: + nat_gateway_ids: + - "{{ nat_gateway_id }}" + register: ngw_info + + - name: Check NAT gateway exists + assert: + that: + - ngw_info is successful + - ngw_info.result | length == 1 + - '"create_time" in first_ngw' + - '"nat_gateway_addresses" in first_ngw' + - '"nat_gateway_id" in first_ngw' + - first_ngw.nat_gateway_id == nat_gateway_id + - '"state" in first_ngw' + - first_ngw.state == 'available' + - '"subnet_id" in first_ngw' + - first_ngw.subnet_id == subnet_id + - '"tags" in first_ngw' + - '"vpc_id" in first_ngw' + - first_ngw.vpc_id == vpc_id + vars: + first_ngw: '{{ ngw_info.result[0] }}' + + + # ============================================================ + - name: Delete NAT gateway - CHECK_MODE + ec2_vpc_nat_gateway: + nat_gateway_id: "{{ nat_gateway_id }}" + state: absent + wait: yes + register: delete_nat_gateway + check_mode: yes + + - name: Assert state=absent (expected changed=true) - CHECK_MODE + assert: + that: + - delete_nat_gateway.changed + + - name: Delete NAT gateway + ec2_vpc_nat_gateway: + nat_gateway_id: "{{ nat_gateway_id }}" + state: absent + wait: yes + register: delete_nat_gateway + + - name: Assert state=absent (expected changed=true) + assert: + that: + - delete_nat_gateway.changed + - '"delete_time" in delete_nat_gateway' + - '"nat_gateway_addresses" in delete_nat_gateway' + - '"nat_gateway_id" in delete_nat_gateway' + - delete_nat_gateway.nat_gateway_id == nat_gateway_id + - '"state" in delete_nat_gateway' + - delete_nat_gateway.state == 'deleted' + - '"subnet_id" in delete_nat_gateway' + - delete_nat_gateway.subnet_id == subnet_id + - '"tags" in delete_nat_gateway' + - '"vpc_id" in delete_nat_gateway' + - delete_nat_gateway.vpc_id == vpc_id + + + # ============================================================ + - name: Create new NAT gateway with eip allocation-id and tags - CHECK_MODE + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + tags: + tag_one: '{{ resource_prefix }} One' + "Tag Two": 'two {{ resource_prefix }}' + wait: yes + register: create_ngw + check_mode: yes + + - name: Assert creation happened (expected changed=true) - CHECK_MODE + assert: + that: + - create_ngw.changed + + - name: Create new NAT gateway with eip allocation-id and tags + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + tags: + tag_one: '{{ resource_prefix }} One' + "Tag Two": 'two {{ resource_prefix }}' + wait: yes + register: create_ngw + + - name: Assert creation happened (expected changed=true) + assert: + that: + - create_ngw.changed + - '"create_time" in create_ngw' + - create_ngw.nat_gateway_addresses[0].allocation_id == allocation_id + - '"nat_gateway_id" in create_ngw' + - create_ngw.nat_gateway_id.startswith("nat-") + - '"state" in create_ngw' + - create_ngw.state == 'available' + - '"subnet_id" in create_ngw' + - create_ngw.subnet_id == subnet_id + - '"tags" in create_ngw' + - create_ngw.tags | length == 2 + - create_ngw.tags["tag_one"] == '{{ resource_prefix }} One' + - create_ngw.tags["Tag Two"] == 'two {{ resource_prefix }}' + - '"vpc_id" in create_ngw' + - create_ngw.vpc_id == vpc_id + + - name: "set facts: NAT gateway ID" + set_fact: + ngw_id: "{{ create_ngw.nat_gateway_id }}" + + + # ============================================================ + - name: Update the tags (no change) - CHECK_MODE + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + tags: + tag_one: '{{ resource_prefix }} One' + "Tag Two": 'two {{ resource_prefix }}' + wait: yes + register: update_tags_ngw + check_mode: yes + + - name: assert tag update would do nothing (expected changed=false) - CHECK_MODE + assert: + that: + - not update_tags_ngw.changed + - '"nat_gateway_id" in update_tags_ngw' + - update_tags_ngw.nat_gateway_id == ngw_id + - '"subnet_id" in update_tags_ngw' + - update_tags_ngw.subnet_id == subnet_id + - '"tags" in update_tags_ngw' + - update_tags_ngw.tags | length == 2 + - update_tags_ngw.tags["tag_one"] == '{{ resource_prefix }} One' + - update_tags_ngw.tags["Tag Two"] == 'two {{ resource_prefix }}' + - '"vpc_id" in update_tags_ngw' + - update_tags_ngw.vpc_id == vpc_id + + - name: Update the tags (no change) + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + tags: + tag_one: '{{ resource_prefix }} One' + "Tag Two": 'two {{ resource_prefix }}' + wait: yes + register: update_tags_ngw + + - name: assert tag update would do nothing (expected changed=false) + assert: + that: + - not update_tags_ngw.changed + - '"nat_gateway_id" in update_tags_ngw' + - update_tags_ngw.nat_gateway_id == ngw_id + - '"subnet_id" in update_tags_ngw' + - update_tags_ngw.subnet_id == subnet_id + - '"tags" in update_tags_ngw' + - update_tags_ngw.tags | length == 2 + - update_tags_ngw.tags["tag_one"] == '{{ resource_prefix }} One' + - update_tags_ngw.tags["Tag Two"] == 'two {{ resource_prefix }}' + - '"vpc_id" in update_tags_ngw' + - update_tags_ngw.vpc_id == vpc_id + + + # ============================================================ + - name: Gather information about a filtered list of NAT Gateways using tags and state - CHECK_MODE + ec2_vpc_nat_gateway_info: + filters: + "tag:Tag Two": 'two {{ resource_prefix }}' + state: ['available'] + register: ngw_info + check_mode: yes + + - name: Assert success - CHECK_MODE + assert: + that: + - ngw_info is successful + - ngw_info.result | length == 1 + - '"create_time" in second_ngw' + - '"nat_gateway_addresses" in second_ngw' + - '"nat_gateway_id" in second_ngw' + - second_ngw.nat_gateway_id == ngw_id + - '"state" in second_ngw' + - second_ngw.state == 'available' + - '"subnet_id" in second_ngw' + - second_ngw.subnet_id == subnet_id + - '"tags" in second_ngw' + - second_ngw.tags | length == 2 + - '"tag_one" in second_ngw.tags' + - '"Tag Two" in second_ngw.tags' + - second_ngw.tags["tag_one"] == '{{ resource_prefix }} One' + - second_ngw.tags["Tag Two"] == 'two {{ resource_prefix }}' + - '"vpc_id" in second_ngw' + - second_ngw.vpc_id == vpc_id + vars: + second_ngw: '{{ ngw_info.result[0] }}' + + - name: Gather information about a filtered list of NAT Gateways using tags and state + ec2_vpc_nat_gateway_info: + filters: + "tag:Tag Two": 'two {{ resource_prefix }}' + state: ['available'] + register: ngw_info + + - name: Assert success + assert: + that: + - ngw_info is successful + - ngw_info.result | length == 1 + - '"create_time" in second_ngw' + - '"nat_gateway_addresses" in second_ngw' + - '"nat_gateway_id" in second_ngw' + - second_ngw.nat_gateway_id == ngw_id + - '"state" in second_ngw' + - second_ngw.state == 'available' + - '"subnet_id" in second_ngw' + - second_ngw.subnet_id == subnet_id + - '"tags" in second_ngw' + - second_ngw.tags | length == 2 + - '"tag_one" in second_ngw.tags' + - '"Tag Two" in second_ngw.tags' + - second_ngw.tags["tag_one"] == '{{ resource_prefix }} One' + - second_ngw.tags["Tag Two"] == 'two {{ resource_prefix }}' + - '"vpc_id" in second_ngw' + - second_ngw.vpc_id == vpc_id + vars: + second_ngw: '{{ ngw_info.result[0] }}' + + + # ============================================================ + - name: Update the tags - remove and add - CHECK_MODE + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + tags: + tag_three: '{{ resource_prefix }} Three' + "Tag Two": 'two {{ resource_prefix }}' + wait: yes + register: update_tags_ngw + check_mode: yes + + - name: Assert tag update would happen (expected changed=true) - CHECK_MODE + assert: + that: + - update_tags_ngw.changed + - '"nat_gateway_id" in update_tags_ngw' + - update_tags_ngw.nat_gateway_id == ngw_id + - '"subnet_id" in update_tags_ngw' + - update_tags_ngw.subnet_id == subnet_id + - '"tags" in update_tags_ngw' + - update_tags_ngw.tags | length == 2 + - update_tags_ngw.tags["tag_three"] == '{{ resource_prefix }} Three' + - update_tags_ngw.tags["Tag Two"] == 'two {{ resource_prefix }}' + - '"vpc_id" in update_tags_ngw' + - update_tags_ngw.vpc_id == vpc_id + + - name: Update the tags - remove and add + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + tags: + tag_three: '{{ resource_prefix }} Three' + "Tag Two": 'two {{ resource_prefix }}' + wait: yes + register: update_tags_ngw + + - name: Assert tag update would happen (expected changed=true) + assert: + that: + - update_tags_ngw.changed + - '"nat_gateway_id" in update_tags_ngw' + - update_tags_ngw.nat_gateway_id == ngw_id + - '"subnet_id" in update_tags_ngw' + - update_tags_ngw.subnet_id == subnet_id + - '"tags" in update_tags_ngw' + - update_tags_ngw.tags | length == 2 + - update_tags_ngw.tags["tag_three"] == '{{ resource_prefix }} Three' + - update_tags_ngw.tags["Tag Two"] == 'two {{ resource_prefix }}' + - '"vpc_id" in update_tags_ngw' + - update_tags_ngw.vpc_id == vpc_id + + + # ============================================================ + - name: Gather information about a filtered list of NAT Gateways using tags and state (no match) - CHECK_MODE + ec2_vpc_nat_gateway_info: + filters: + "tag:tag_one": '{{ resource_prefix }} One' + state: ['available'] + register: ngw_info + check_mode: yes + + - name: Assert success - CHECK_MODE + assert: + that: + - ngw_info is successful + - ngw_info.result | length == 0 + + - name: Gather information about a filtered list of NAT Gateways using tags and state (no match) + ec2_vpc_nat_gateway_info: + filters: + "tag:tag_one": '{{ resource_prefix }} One' + state: ['available'] + register: ngw_info + + - name: Assert success + assert: + that: + - ngw_info is successful + - ngw_info.result | length == 0 + + + # ============================================================ + - name: Update the tags add without purge - CHECK_MODE + ec2_vpc_nat_gateway: + if_exist_do_not_create: yes + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + purge_tags: no + tags: + tag_one: '{{ resource_prefix }} One' + wait: yes + register: update_tags_ngw + check_mode: yes + + - name: Assert tags would be added - CHECK_MODE + assert: + that: + - update_tags_ngw.changed + - '"nat_gateway_id" in update_tags_ngw' + - update_tags_ngw.nat_gateway_id == ngw_id + - '"subnet_id" in update_tags_ngw' + - update_tags_ngw.subnet_id == subnet_id + - '"tags" in update_tags_ngw' + - update_tags_ngw.tags | length == 3 + - update_tags_ngw.tags["tag_one"] == '{{ resource_prefix }} One' + - update_tags_ngw.tags["tag_three"] == '{{ resource_prefix }} Three' + - update_tags_ngw.tags["Tag Two"] == 'two {{ resource_prefix }}' + - '"vpc_id" in update_tags_ngw' + - update_tags_ngw.vpc_id == vpc_id + + - name: Update the tags add without purge + ec2_vpc_nat_gateway: + if_exist_do_not_create: yes + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + purge_tags: no + tags: + tag_one: '{{ resource_prefix }} One' + wait: yes + register: update_tags_ngw + + - name: Assert tags would be added + assert: + that: + - update_tags_ngw.changed + - '"nat_gateway_id" in update_tags_ngw' + - update_tags_ngw.nat_gateway_id == ngw_id + - '"subnet_id" in update_tags_ngw' + - update_tags_ngw.subnet_id == subnet_id + - '"tags" in update_tags_ngw' + - update_tags_ngw.tags | length == 3 + - update_tags_ngw.tags["tag_one"] == '{{ resource_prefix }} One' + - update_tags_ngw.tags["tag_three"] == '{{ resource_prefix }} Three' + - update_tags_ngw.tags["Tag Two"] == 'two {{ resource_prefix }}' + - '"vpc_id" in update_tags_ngw' + - update_tags_ngw.vpc_id == vpc_id + + + # ============================================================ + - name: Remove all tags - CHECK_MODE + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + tags: {} + register: delete_tags_ngw + check_mode: yes + + - name: assert tags would be removed - CHECK_MODE + assert: + that: + - delete_tags_ngw.changed + - '"nat_gateway_id" in delete_tags_ngw' + - delete_tags_ngw.nat_gateway_id == ngw_id + - '"subnet_id" in delete_tags_ngw' + - delete_tags_ngw.subnet_id == subnet_id + - '"tags" in delete_tags_ngw' + - delete_tags_ngw.tags | length == 0 + - '"vpc_id" in delete_tags_ngw' + - delete_tags_ngw.vpc_id == vpc_id + + - name: Remove all tags + ec2_vpc_nat_gateway: + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + tags: {} + register: delete_tags_ngw + + - name: assert tags would be removed + assert: + that: + - delete_tags_ngw.changed + - '"nat_gateway_id" in delete_tags_ngw' + - delete_tags_ngw.nat_gateway_id == ngw_id + - '"subnet_id" in delete_tags_ngw' + - delete_tags_ngw.subnet_id == subnet_id + - '"tags" in delete_tags_ngw' + - delete_tags_ngw.tags | length == 0 + - '"vpc_id" in delete_tags_ngw' + - delete_tags_ngw.vpc_id == vpc_id + + + # ============================================================ + - name: Update with CamelCase tags - CHECK_MODE + ec2_vpc_nat_gateway: + if_exist_do_not_create: yes + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + purge_tags: no + tags: + "lowercase spaced": 'hello cruel world ❤️' + "Title Case": 'Hello Cruel World ❤️' + CamelCase: 'SimpleCamelCase ❤️' + snake_case: 'simple_snake_case ❤️' + wait: yes + register: update_tags_ngw + check_mode: yes + + - name: Assert tags would be added - CHECK_MODE + assert: + that: + - update_tags_ngw.changed + - '"nat_gateway_id" in update_tags_ngw' + - update_tags_ngw.nat_gateway_id == ngw_id + - '"subnet_id" in update_tags_ngw' + - update_tags_ngw.subnet_id == subnet_id + - '"tags" in update_tags_ngw' + - update_tags_ngw.tags | length == 4 + - update_tags_ngw.tags["lowercase spaced"] == 'hello cruel world ❤️' + - update_tags_ngw.tags["Title Case"] == 'Hello Cruel World ❤️' + - update_tags_ngw.tags["CamelCase"] == 'SimpleCamelCase ❤️' + - update_tags_ngw.tags["snake_case"] == 'simple_snake_case ❤️' + - '"vpc_id" in update_tags_ngw' + - update_tags_ngw.vpc_id == vpc_id + + - name: Update with CamelCase tags + ec2_vpc_nat_gateway: + if_exist_do_not_create: yes + subnet_id: "{{ subnet_id }}" + allocation_id: "{{ allocation_id }}" + purge_tags: no + tags: + "lowercase spaced": 'hello cruel world ❤️' + "Title Case": 'Hello Cruel World ❤️' + CamelCase: 'SimpleCamelCase ❤️' + snake_case: 'simple_snake_case ❤️' + wait: yes + register: update_tags_ngw + + - name: Assert tags would be added + assert: + that: + - update_tags_ngw.changed + - '"nat_gateway_id" in update_tags_ngw' + - update_tags_ngw.nat_gateway_id == ngw_id + - '"subnet_id" in update_tags_ngw' + - update_tags_ngw.subnet_id == subnet_id + - '"tags" in update_tags_ngw' + - update_tags_ngw.tags | length == 4 + - update_tags_ngw.tags["lowercase spaced"] == 'hello cruel world ❤️' + - update_tags_ngw.tags["Title Case"] == 'Hello Cruel World ❤️' + - update_tags_ngw.tags["CamelCase"] == 'SimpleCamelCase ❤️' + - update_tags_ngw.tags["snake_case"] == 'simple_snake_case ❤️' + - '"vpc_id" in update_tags_ngw' + - update_tags_ngw.vpc_id == vpc_id + + + # ============================================================ + always: + - name: Get NAT gateways + ec2_vpc_nat_gateway_info: + filters: + vpc-id: "{{ vpc_id }}" + state: ['available'] + register: existing_ngws + ignore_errors: true + + - name: Tidy up NAT gateway + ec2_vpc_nat_gateway: + subnet_id: "{{ item.subnet_id }}" + nat_gateway_id: "{{ item.nat_gateway_id }}" + release_eip: yes + state: absent + wait: yes + with_items: "{{ existing_ngws.result }}" + ignore_errors: true + + - name: Delete IGW + ec2_vpc_igw: + vpc_id: "{{ vpc_id }}" + state: absent + ignore_errors: true + + - name: Remove subnet + ec2_vpc_subnet: + state: absent + cidr: "{{ subnet_cidr }}" + vpc_id: "{{ vpc_id }}" + ignore_errors: true + + - name: Ensure EIP is actually released + ec2_eip: + state: absent + device_id: "{{ item.nat_gateway_addresses[0].network_interface_id }}" + in_vpc: yes + with_items: "{{ existing_ngws.result }}" + ignore_errors: yes + + - name: Delete VPC + ec2_vpc_net: + name: "{{ vpc_name }}" + cidr_block: "{{ vpc_cidr }}" + state: absent + purge_cidrs: yes + ignore_errors: yes diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt index 77d93927cf4..8b323404d07 100644 --- a/tests/sanity/ignore-2.10.txt +++ b/tests/sanity/ignore-2.10.txt @@ -314,6 +314,30 @@ plugins/modules/ec2_vpc_dhcp_option_info.py import-3.5!skip plugins/modules/ec2_vpc_dhcp_option_info.py import-3.6!skip plugins/modules/ec2_vpc_dhcp_option_info.py import-3.7!skip plugins/modules/ec2_vpc_dhcp_option_info.py metaclass-boilerplate!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-2.6!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-2.7!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-3.5!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-3.6!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-3.7!skip +plugins/modules/ec2_vpc_nat_gateway.py future-import-boilerplate!skip +plugins/modules/ec2_vpc_nat_gateway.py import-2.6!skip +plugins/modules/ec2_vpc_nat_gateway.py import-2.7!skip +plugins/modules/ec2_vpc_nat_gateway.py import-3.5!skip +plugins/modules/ec2_vpc_nat_gateway.py import-3.6!skip +plugins/modules/ec2_vpc_nat_gateway.py import-3.7!skip +plugins/modules/ec2_vpc_nat_gateway.py metaclass-boilerplate!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-2.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-2.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-3.5!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-3.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-3.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py future-import-boilerplate!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-2.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-2.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-3.5!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-3.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-3.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py metaclass-boilerplate!skip plugins/modules/ec2_vpc_net.py compile-2.6!skip plugins/modules/ec2_vpc_net.py compile-2.7!skip plugins/modules/ec2_vpc_net.py compile-3.5!skip diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt index 77d93927cf4..8b323404d07 100644 --- a/tests/sanity/ignore-2.11.txt +++ b/tests/sanity/ignore-2.11.txt @@ -314,6 +314,30 @@ plugins/modules/ec2_vpc_dhcp_option_info.py import-3.5!skip plugins/modules/ec2_vpc_dhcp_option_info.py import-3.6!skip plugins/modules/ec2_vpc_dhcp_option_info.py import-3.7!skip plugins/modules/ec2_vpc_dhcp_option_info.py metaclass-boilerplate!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-2.6!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-2.7!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-3.5!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-3.6!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-3.7!skip +plugins/modules/ec2_vpc_nat_gateway.py future-import-boilerplate!skip +plugins/modules/ec2_vpc_nat_gateway.py import-2.6!skip +plugins/modules/ec2_vpc_nat_gateway.py import-2.7!skip +plugins/modules/ec2_vpc_nat_gateway.py import-3.5!skip +plugins/modules/ec2_vpc_nat_gateway.py import-3.6!skip +plugins/modules/ec2_vpc_nat_gateway.py import-3.7!skip +plugins/modules/ec2_vpc_nat_gateway.py metaclass-boilerplate!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-2.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-2.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-3.5!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-3.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-3.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py future-import-boilerplate!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-2.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-2.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-3.5!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-3.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-3.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py metaclass-boilerplate!skip plugins/modules/ec2_vpc_net.py compile-2.6!skip plugins/modules/ec2_vpc_net.py compile-2.7!skip plugins/modules/ec2_vpc_net.py compile-3.5!skip diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.12.txt index 77d93927cf4..8b323404d07 100644 --- a/tests/sanity/ignore-2.12.txt +++ b/tests/sanity/ignore-2.12.txt @@ -314,6 +314,30 @@ plugins/modules/ec2_vpc_dhcp_option_info.py import-3.5!skip plugins/modules/ec2_vpc_dhcp_option_info.py import-3.6!skip plugins/modules/ec2_vpc_dhcp_option_info.py import-3.7!skip plugins/modules/ec2_vpc_dhcp_option_info.py metaclass-boilerplate!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-2.6!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-2.7!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-3.5!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-3.6!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-3.7!skip +plugins/modules/ec2_vpc_nat_gateway.py future-import-boilerplate!skip +plugins/modules/ec2_vpc_nat_gateway.py import-2.6!skip +plugins/modules/ec2_vpc_nat_gateway.py import-2.7!skip +plugins/modules/ec2_vpc_nat_gateway.py import-3.5!skip +plugins/modules/ec2_vpc_nat_gateway.py import-3.6!skip +plugins/modules/ec2_vpc_nat_gateway.py import-3.7!skip +plugins/modules/ec2_vpc_nat_gateway.py metaclass-boilerplate!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-2.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-2.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-3.5!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-3.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-3.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py future-import-boilerplate!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-2.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-2.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-3.5!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-3.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-3.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py metaclass-boilerplate!skip plugins/modules/ec2_vpc_net.py compile-2.6!skip plugins/modules/ec2_vpc_net.py compile-2.7!skip plugins/modules/ec2_vpc_net.py compile-3.5!skip diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt index d41b9e62fd8..7bc606b5248 100644 --- a/tests/sanity/ignore-2.9.txt +++ b/tests/sanity/ignore-2.9.txt @@ -327,6 +327,30 @@ plugins/modules/ec2_vpc_dhcp_option_info.py import-3.6!skip plugins/modules/ec2_vpc_dhcp_option_info.py import-3.7!skip plugins/modules/ec2_vpc_dhcp_option_info.py metaclass-boilerplate!skip plugins/modules/ec2_vpc_dhcp_option_info.py pylint:ansible-deprecated-no-version # We use dates for deprecations, Ansible 2.9 only supports this for compatability +plugins/modules/ec2_vpc_nat_gateway.py compile-2.6!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-2.7!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-3.5!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-3.6!skip +plugins/modules/ec2_vpc_nat_gateway.py compile-3.7!skip +plugins/modules/ec2_vpc_nat_gateway.py future-import-boilerplate!skip +plugins/modules/ec2_vpc_nat_gateway.py import-2.6!skip +plugins/modules/ec2_vpc_nat_gateway.py import-2.7!skip +plugins/modules/ec2_vpc_nat_gateway.py import-3.5!skip +plugins/modules/ec2_vpc_nat_gateway.py import-3.6!skip +plugins/modules/ec2_vpc_nat_gateway.py import-3.7!skip +plugins/modules/ec2_vpc_nat_gateway.py metaclass-boilerplate!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-2.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-2.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-3.5!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-3.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py compile-3.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py future-import-boilerplate!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-2.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-2.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-3.5!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-3.6!skip +plugins/modules/ec2_vpc_nat_gateway_info.py import-3.7!skip +plugins/modules/ec2_vpc_nat_gateway_info.py metaclass-boilerplate!skip plugins/modules/ec2_vpc_net.py compile-2.6!skip plugins/modules/ec2_vpc_net.py compile-2.7!skip plugins/modules/ec2_vpc_net.py compile-3.5!skip