Repository: ansible-collections/community.aws

Issue:
ec2_vpc_nat_gateway should support tags
##### SUMMARY
The ec2_vpc_nat_gateway module does not support tags and it should. NAT Gateways support tags in AWS.

##### ISSUE TYPE
- Feature Idea

##### COMPONENT NAME
The module name is ec2_vpc_nat_gateway

##### ADDITIONAL INFORMATION
The ec2_vpc_nat_gateway should accept a dictionary of tags, like the internet gateway module (as documented [here](https://docs.ansible.com/ansible/latest/collections/community/aws/ec2_vpc_igw_module.html)).

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: setup NAT gateways
  tags: always
  ec2_vpc_nat_gateway:
    profile: your_aws_profile
    state: present
    region: us-west-1
    subnet_id: "{{ item }}"
    if_exist_do_not_create: yes
    tags: #<-- This is currently not supported
      - Name: MyNATGateway
      - Purpuse: NATing things 
  loop: "{{nat_subnet_list}}" #This is an array of subnet IDs in format `nat_subnet_id: subnet-12345, subnet-54321`

```



Discussion:
Files identified in the description:
* [`plugins/modules/ec2_vpc_nat_gateway.py`](https://github.com/['ansible-collections/amazon.aws', 'ansible-collections/community.aws']/blob/main/plugins/modules/ec2_vpc_nat_gateway.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @Etherdaemon @jillr @jonhadfield @linuxdynasty @s-hertel @tremble @wimnat
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/plugins/modules/ec2_vpc_nat_gateway.py b/plugins/modules/ec2_vpc_nat_gateway.py
--- a/plugins/modules/ec2_vpc_nat_gateway.py
+++ b/plugins/modules/ec2_vpc_nat_gateway.py
@@ -48,6 +48,19 @@
     required: false
     default: false
     type: bool
+  tags:
+    description:
+      - A dict of tags to apply to the internet 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.
@@ -153,6 +166,17 @@
     wait: yes
     wait_timeout: 300
     region: ap-southeast-2
+
+- name: Create new nat gateway using an 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
 '''
 
 RETURN = '''
@@ -176,6 +200,13 @@
   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.
@@ -204,11 +235,17 @@
 except ImportError:
     pass  # Handled by AnsibleAWSModule
 
-from ansible.module_utils._text import to_native
+
+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.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
+from ansible.module_utils.six import string_types
+from ansible.module_utils._text import to_native
 
 DRY_RUN_GATEWAYS = [
     {
@@ -451,6 +488,7 @@ def gateway_in_subnet_exists(client, subnet_id, allocation_id=None,
     allocation_id_exists = False
     gateways = []
     states = ['available', 'pending']
+
     gws_retrieved, err_msg, gws = (
         get_nat_gateways(
             client, subnet_id, states=states, check_mode=check_mode
@@ -609,7 +647,7 @@ def release_address(client, allocation_id, check_mode=False):
     return ip_released, err_msg
 
 
-def create(client, subnet_id, allocation_id, client_token=None,
+def create(client, module, subnet_id, allocation_id, tags, purge_tags, client_token=None,
            wait=False, wait_timeout=0, if_exist_do_not_create=False,
            check_mode=False):
     """Create an Amazon NAT Gateway.
@@ -680,7 +718,6 @@ def create(client, subnet_id, allocation_id, client_token=None,
             result['create_time'] = datetime.datetime.utcnow()
             result['nat_gateway_addresses'][0]['allocation_id'] = allocation_id
             result['subnet_id'] = subnet_id
-
         success = True
         changed = True
         create_time = result['create_time'].replace(tzinfo=None)
@@ -689,15 +726,18 @@ def create(client, subnet_id, allocation_id, client_token=None,
         elif wait:
             success, err_msg, result = (
                 wait_for_status(
-                    client, wait_timeout, result['nat_gateway_id'], 'available',
-                    check_mode=check_mode
+                    client, wait_timeout, result['nat_gateway_id'],
+                    'available', check_mode=check_mode
                 )
             )
             if success:
                 err_msg = (
                     'NAT gateway {0} created'.format(result['nat_gateway_id'])
                 )
-
+        result['tags'], tags_update_exists = ensure_tags(
+            client, module, nat_gw_id=result['nat_gateway_id'], tags=tags,
+            purge_tags=purge_tags, check_mode=check_mode
+        )
     except is_boto3_error_code('IdempotentParameterMismatch'):
         err_msg = (
             'NAT Gateway does not support update and token has already been provided: ' + err_msg
@@ -714,7 +754,7 @@ def create(client, subnet_id, allocation_id, client_token=None,
     return success, changed, err_msg, result
 
 
-def pre_create(client, subnet_id, allocation_id=None, eip_address=None,
+def pre_create(client, module, subnet_id, tags, purge_tags, allocation_id=None, eip_address=None,
                if_exist_do_not_create=False, wait=False, wait_timeout=0,
                client_token=None, check_mode=False):
     """Create an Amazon NAT Gateway.
@@ -772,14 +812,18 @@ def pre_create(client, subnet_id, allocation_id=None, eip_address=None,
     results = list()
 
     if not allocation_id and not eip_address:
-        existing_gateways, allocation_id_exists = (
-            gateway_in_subnet_exists(client, subnet_id, check_mode=check_mode)
-        )
-
+        existing_gateways, allocation_id_exists = (gateway_in_subnet_exists(client, subnet_id, check_mode=check_mode))
         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, check_mode)
+
+            if tags_update_exists:
+                success = True
+                changed = True
+                return success, changed, err_msg, results
+
             success = True
             changed = False
-            results = existing_gateways[0]
             err_msg = (
                 'NAT Gateway {0} already exists in subnet_id {1}'
                 .format(
@@ -805,16 +849,22 @@ def pre_create(client, subnet_id, allocation_id=None, eip_address=None,
                 success = False
                 changed = False
                 return success, changed, err_msg, dict()
-
         existing_gateways, allocation_id_exists = (
             gateway_in_subnet_exists(
                 client, subnet_id, allocation_id, check_mode=check_mode
             )
         )
+
         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, check_mode)
+            if tags_update_exists:
+                success = True
+                changed = True
+                return success, changed, err_msg, results
+
             success = True
             changed = False
-            results = existing_gateways[0]
             err_msg = (
                 'NAT Gateway {0} already exists in subnet_id {1}'
                 .format(
@@ -824,7 +874,7 @@ def pre_create(client, subnet_id, allocation_id=None, eip_address=None,
             return success, changed, err_msg, results
 
     success, changed, err_msg, results = create(
-        client, subnet_id, allocation_id, client_token,
+        client, module, subnet_id, allocation_id, tags, purge_tags, client_token,
         wait, wait_timeout, if_exist_do_not_create, check_mode=check_mode
     )
 
@@ -919,8 +969,7 @@ def remove(client, nat_gateway_id, wait=False, wait_timeout=0,
 
     if release_eip:
         eip_released, eip_err = (
-            release_address(client, allocation_id, check_mode)
-        )
+            release_address(client, allocation_id, check_mode))
         if not eip_released:
             err_msg = (
                 "{0}: Failed to release EIP {1}: {2}"
@@ -931,6 +980,64 @@ def remove(client, nat_gateway_id, wait=False, wait_timeout=0,
     return success, changed, err_msg, results
 
 
+def ensure_tags(client, module, nat_gw_id, tags, purge_tags, check_mode):
+    final_tags = []
+    changed = False
+
+    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 check_mode:
+                # update tags
+                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 check_mode:
+                # update tags
+                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 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'),
@@ -943,6 +1050,8 @@ def main():
         release_eip=dict(type='bool', default=False),
         nat_gateway_id=dict(type='str'),
         client_token=dict(type='str'),
+        tags=dict(required=False, type='dict', aliases=['resource_tags']),
+        purge_tags=dict(default=True, type='bool'),
     )
     module = AnsibleAWSModule(
         argument_spec=argument_spec,
@@ -965,9 +1074,11 @@ def main():
     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')
+        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')
 
@@ -977,7 +1088,7 @@ def main():
     if state == 'present':
         success, changed, err_msg, results = (
             pre_create(
-                client, subnet_id, allocation_id, eip_address,
+                client, module, subnet_id, tags, purge_tags, allocation_id, eip_address,
                 if_exist_do_not_create, wait, wait_timeout,
                 client_token, check_mode=check_mode
             )


Test patch:
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
--- /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
--- a/tests/integration/targets/ec2_vpc_nat_gateway/tasks/main.yml
+++ b/tests/integration/targets/ec2_vpc_nat_gateway/tasks/main.yml
@@ -3,80 +3,546 @@
 # They take advantage of hard-coded results within the module to trigger both changed and unchanged responses.
 # They were migrated to maintain test coverage while removing unit tests that depended on use of TaskQueueManager.
 
-- name: Create new nat gateway with eip allocation-id
-  ec2_vpc_nat_gateway:
-    subnet_id: subnet-12345678
-    allocation_id: eipalloc-12345678
-    wait: yes
-    region: us-west-2
-  register: nat_gateway
-  check_mode: yes
-
-- assert:
-    that:
-      - nat_gateway.changed
-
-- name: Create new nat gateway with eip allocation-id
-  ec2_vpc_nat_gateway:
-    subnet_id: subnet-123456789
-    allocation_id: eipalloc-1234567
-    wait: yes
-    region: us-west-2
-  register: nat_gateway
-  check_mode: yes
-
-- assert:
-    that:
-      - not nat_gateway.changed
-
-- name: Create new nat gateway with eip address
-  ec2_vpc_nat_gateway:
-    subnet_id: subnet-12345678
-    eip_address: 55.55.55.55
-    wait: yes
-    region: us-west-2
-  register: nat_gateway
-  check_mode: yes
-
-- assert:
-    that:
-      - nat_gateway.changed
-
-- name: Create new nat gateway with eip address
-  ec2_vpc_nat_gateway:
-    subnet_id: subnet-123456789
-    eip_address: 55.55.55.55
-    wait: yes
-    region: us-west-2
-  register: nat_gateway
-  check_mode: yes
-
-- assert:
-    that:
-      - not nat_gateway.changed
-
-- 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-123456789
-    wait: yes
-    region: us-west-2
-  register: nat_gateway
-  check_mode: yes
-
-- assert:
-    that:
-      - not nat_gateway.changed
-
-- name: Delete Nat Gateway
-  ec2_vpc_nat_gateway:
-    nat_gateway_id: nat-123456789
-    state: absent
-    wait: yes
-    region: us-west-2
-  register: nat_gateway
-  check_mode: yes
-
-- assert:
-    that:
-      - nat_gateway.changed
+---
+# ============================================================
+# Known issues:
+#
+# `check_mode` is not working correctly due to the hard-coded DRY_RUN_GATEWAY (module code). The values passed here, 
+# when CHECK_MODE is used, don't correspond to those used for the DRY_RUN_GATEWAY and all test 
+# (except when the NAT gateway is created for the first time) fail.
+#
+# `Create new NAT gateway with eip address` - when the task is run for the first time, do we expect changed=true? 
+# As we use the same EIP, I think changed should be false (if this is correct, lines 194-218 are redundant and 
+# lines 177 and 190 should report `not create_ngw.changed`).  
+#  ============================================================
+
+- 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
+
+  - 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
+
+  - 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
+
+  - name: "set fact: VPC subnet ID"
+    set_fact:
+      subnet_id: "{{ subnet_result.subnet.id }}"
+ 
+
+  # ============================================================
+  - name: Search for NAT gateways by subnet - no matches
+    ec2_vpc_nat_gateway_info:
+      filters:
+        subnet-id: "{{ subnet_id }}"
+        state: ['available']
+    register: existing_ngws
+    retries: 10
+    until: existing_ngws is not failed
+  
+  - 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
+
+
+  # ============================================================
+  - 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
+    retries: 10
+    until: create_ngw is not failed
+ 
+  - name: Assert creation happened (expected changed=true)
+    assert:
+      that:
+        - create_ngw.changed
+
+  - 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: 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: 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
+    retries: 10
+    until: create_ngw is not failed
+
+  - name: Assert recreation would do nothing (expected changed=false)
+    assert:
+      that:
+        - not create_ngw.changed
+  
+  # - 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
+
+
+  # ============================================================
+  #- name: Create new NAT gateway with eip address 
+  #  ec2_vpc_nat_gateway:
+  #     subnet_id: "{{ subnet_id }}"
+  #     eip_address: "{{ eip_address }}"
+  #     wait: yes
+  #  register: create_ngw
+  #
+  #- name: Assert creation happened (expected changed=true)
+  #  assert:
+  #     that:
+  #       - create_ngw.changed
+  
+  # - name: Create new nat gateway with eip address - CHECK_MODE
+  #   ec2_vpc_nat_gateway:
+  #     subnet_id: "{{ subnet_id }}"
+  #     eip_address: "{{ 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: Trying this again for idempotency -  create new nat gateway with eip address
+  #   ec2_vpc_nat_gateway:
+  #     subnet_id: "{{ subnet_id }}"
+  #     eip_address: "{{ eip_address }}"
+  #     wait: yes
+  #   register: create_ngw
+
+  # - name: Assert recreation would do nothing (expected changed=false)
+  #   assert:
+  #     that:
+  #       - not create_ngw.changed
+
+  # - 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: "{{ 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
+
+
+  # ============================================================
+  - 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
+    retries: 10
+    until: create_ngw is not failed
+
+  - name: Assert recreation would do nothing (expected changed=false)
+    assert:
+      that:
+        - not create_ngw.changed
+  
+  # - 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
+
+
+  # ============================================================  
+  - name: Delete NAT gateway
+    ec2_vpc_nat_gateway:
+      nat_gateway_id: "{{ nat_gateway_id }}"
+      state: absent
+      wait: yes
+    register: delete_nat_gateway
+    retries: 10
+    until: delete_nat_gateway is not failed
+
+  - name: Assert state=absent (expected changed=true)
+    assert:
+      that:
+        - delete_nat_gateway.changed
+  
+  # - 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: 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
+
+  #  - 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: 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        
+  
+  # - 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
+
+
+  # ============================================================
+  - 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
+  
+  # - 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
+
+
+  # ============================================================
+  - 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
+
+  # - 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
+
+
+  # ============================================================
+  - 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
+  
+  # - 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
+
+  
+  
+  # ============================================================
+  - 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
+         - 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 ❤️'
+
+
+  # - 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
+
+  #- name: Assert tags would be added - CHECK_MODE
+  #  assert:
+  #    that:
+  #       - update_tags_ngw.changed
+  #       - 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 ❤️'
+
+  # ============================================================
+  
+  
+  always:
+  - name: Get NAT gateways
+    ec2_vpc_nat_gateway_info:
+     filters:
+         vpc-id: "{{ vpc_id }}"
+    register: existing_ngws
+    retries: 10
+    until: existing_ngws is not failed
+    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