Repository: ansible-collections/amazon.aws

Issue:
ec2_vpc_igw should be able to remove detached internet gateway
### Summary

I am trying to delete an internet gateway that is in a detached state.

The way the ec2_vpc_igw module currently works a `vpc_id` has to be supplied and the igw is deleted based on looking up the igw by the vpc_id, then it is detached from the VPC and finally deleted. This makes it such that igws in a detached state cannot be deleted.

It would be useful to be able to supply an `internet_gateway_id` and have the module detach it from the VPC if needed and then delete it.

### Issue Type

Feature Idea

### Component Name

ec2_vpc_igw

### Additional Information

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
---
- name: Testing playbook
  hosts: localhost
  gather_facts: false

  tasks:
    - name: Gather internet gateway info
      amazon.aws.ec2_vpc_route_table_info:
        profile: <<REDACTED>>
        region: us-east-2
      register: aws_igws

    - name: Remove internet gateways
      amazon.aws.ec2_vpc_igw:
        profile: <<REDACTED>>
        region: us-east-2
        internet_gateway_id: "{{ igw['internet_gateway_id'] }}"
        state: absent
      loop: "{{ aws_igws['internet_gateways'] }}"
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/ec2_vpc_igw.py`](https://github.com/['ansible-collections/amazon.aws', 'ansible-collections/community.aws', 'ansible-collections/community.vmware']/blob/main/plugins/modules/ec2_vpc_igw.py)
* [`test/`](https://github.com/['ansible-collections/amazon.aws', 'ansible-collections/community.aws', 'ansible-collections/community.vmware']/blob/main/test/)
* [`tests/integration/targets/inventory_aws_ec2/playbooks/tasks`](https://github.com/['ansible-collections/amazon.aws', 'ansible-collections/community.aws', 'ansible-collections/community.vmware']/blob/main/tests/integration/targets/inventory_aws_ec2/playbooks/tasks)

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 @jillr @s-hertel @tremble
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@branic Thank you for bringing this up. Will you be willing to work on a patch to fix this and open a pull request?
@alinabuzachis Yes, I'll work on a patch and open a PR.

Resolving patch:
diff --git a/plugins/modules/ec2_vpc_igw.py b/plugins/modules/ec2_vpc_igw.py
--- a/plugins/modules/ec2_vpc_igw.py
+++ b/plugins/modules/ec2_vpc_igw.py
@@ -13,10 +13,17 @@
     - Manage an AWS VPC Internet gateway
 author: Robert Estelle (@erydo)
 options:
+  internet_gateway_id:
+    version_added: 7.0.0
+    description:
+      - The ID of Internet Gateway to manage.
+    required: false
+    type: str
   vpc_id:
     description:
-      - The VPC ID for the VPC in which to manage the Internet Gateway.
-    required: true
+      - The VPC ID for the VPC to attach (when state=present)
+      - VPC ID can also be provided to find the internet gateway to manage that the VPC is attached to
+    required: false
     type: str
   state:
     description:
@@ -24,6 +31,21 @@
     default: present
     choices: [ 'present', 'absent' ]
     type: str
+  force_attach:
+    version_added: 7.0.0
+    description:
+      - Force attaching VPC to I(vpc_id).
+      - Setting this option to true will detach an existing VPC attachment and attach to the supplied I(vpc_id).
+      - Ignored when I(state=absent).
+      - I(vpc_id) must be specified when I(force_attach) is true
+    default: false
+    type: bool
+  detach_vpc:
+    version_added: 7.0.0
+    description:
+      - Remove attached VPC from gateway
+    default: false
+    type: bool
 notes:
 - Support for I(purge_tags) was added in release 1.3.0.
 extends_documentation_fragment:
@@ -53,11 +75,38 @@
         Tag2: tag2
   register: igw
 
-- name: Delete Internet gateway
+- name: Create a detached gateway
+  amazon.aws.ec2_vpc_igw:
+    state: present
+  register: igw
+
+- name: Change the VPC the gateway is attached to
+  amazon.aws.ec2_vpc_igw:
+    internet_gateway_id: igw-abcdefgh
+    vpc_id: vpc-stuvwxyz
+    force_attach: true
+    state: present
+  register: igw
+
+- name: Delete Internet gateway using the attached vpc id
   amazon.aws.ec2_vpc_igw:
     state: absent
     vpc_id: vpc-abcdefgh
   register: vpc_igw_delete
+
+- name: Delete Internet gateway with gateway id
+  amazon.aws.ec2_vpc_igw:
+    state: absent
+    internet_gateway_id: igw-abcdefgh
+  register: vpc_igw_delete
+
+- name: Delete Internet gateway ensuring attached VPC is correct
+  amazon.aws.ec2_vpc_igw:
+    state: absent
+    internet_gateway_id: igw-abcdefgh
+    vpc_id: vpc-abcdefgh
+  register: vpc_igw_delete
+
 """
 
 RETURN = r"""
@@ -109,6 +158,11 @@ def describe_igws_with_backoff(connection, **params):
     return paginator.paginate(**params).build_full_result()["InternetGateways"]
 
 
+def describe_vpcs_with_backoff(connection, **params):
+    paginator = connection.get_paginator("describe_vpcs")
+    return paginator.paginate(**params).build_full_result()["Vpcs"]
+
+
 class AnsibleEc2Igw:
     def __init__(self, module, results):
         self._module = module
@@ -117,15 +171,18 @@ def __init__(self, module, results):
         self._check_mode = self._module.check_mode
 
     def process(self):
+        internet_gateway_id = self._module.params.get("internet_gateway_id")
         vpc_id = self._module.params.get("vpc_id")
         state = self._module.params.get("state", "present")
         tags = self._module.params.get("tags")
         purge_tags = self._module.params.get("purge_tags")
+        force_attach = self._module.params.get("force_attach")
+        detach_vpc = self._module.params.get("detach_vpc")
 
         if state == "present":
-            self.ensure_igw_present(vpc_id, tags, purge_tags)
+            self.ensure_igw_present(internet_gateway_id, vpc_id, tags, purge_tags, force_attach, detach_vpc)
         elif state == "absent":
-            self.ensure_igw_absent(vpc_id)
+            self.ensure_igw_absent(internet_gateway_id, vpc_id)
 
     def get_matching_igw(self, vpc_id, gateway_id=None):
         """
@@ -136,11 +193,11 @@ def get_matching_igw(self, vpc_id, gateway_id=None):
             Returns:
                 igw (dict): dict of igw found, None if none found
         """
-        filters = ansible_dict_to_boto3_filter_list({"attachment.vpc-id": vpc_id})
         try:
             # If we know the gateway_id, use it to avoid bugs with using filters
             # See https://github.com/ansible-collections/amazon.aws/pull/766
             if not gateway_id:
+                filters = ansible_dict_to_boto3_filter_list({"attachment.vpc-id": vpc_id})
                 igws = describe_igws_with_backoff(self._connection, Filters=filters)
             else:
                 igws = describe_igws_with_backoff(self._connection, InternetGatewayIds=[gateway_id])
@@ -155,6 +212,30 @@ def get_matching_igw(self, vpc_id, gateway_id=None):
 
         return igw
 
+    def get_matching_vpc(self, vpc_id):
+        """
+        Returns the virtual private cloud found.
+            Parameters:
+                vpc_id (str): VPC ID
+            Returns:
+                vpc (dict): dict of vpc found, None if none found
+        """
+        try:
+            vpcs = describe_vpcs_with_backoff(self._connection, VpcIds=[vpc_id])
+        except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
+            # self._module.fail_json(msg=f"{str(e)}")
+            if "InvalidVpcID.NotFound" in str(e):
+                self._module.fail_json(msg=f"VPC with Id {vpc_id} not found, aborting")
+            self._module.fail_json_aws(e)
+
+        vpc = None
+        if len(vpcs) > 1:
+            self._module.fail_json(msg=f"EC2 returned more than one VPC for {vpc_id}, aborting")
+        elif vpcs:
+            vpc = camel_dict_to_snake_dict(vpcs[0])
+
+        return vpc
+
     @staticmethod
     def get_igw_info(igw, vpc_id):
         return {
@@ -163,28 +244,62 @@ def get_igw_info(igw, vpc_id):
             "vpc_id": vpc_id,
         }
 
-    def ensure_igw_absent(self, vpc_id):
-        igw = self.get_matching_igw(vpc_id)
+    def detach_vpc(self, igw_id, vpc_id):
+        try:
+            self._connection.detach_internet_gateway(aws_retry=True, InternetGatewayId=igw_id, VpcId=vpc_id)
+
+            self._results["changed"] = True
+        except botocore.exceptions.WaiterError as e:
+            self._module.fail_json_aws(e, msg="Unable to detach VPC.")
+
+    def attach_vpc(self, igw_id, vpc_id):
+        try:
+            self._connection.attach_internet_gateway(aws_retry=True, InternetGatewayId=igw_id, VpcId=vpc_id)
+
+            # Ensure the gateway is attached before proceeding
+            waiter = get_waiter(self._connection, "internet_gateway_attached")
+            waiter.wait(InternetGatewayIds=[igw_id])
+
+            self._results["changed"] = True
+        except botocore.exceptions.WaiterError as e:
+            self._module.fail_json_aws(e, msg="Failed to attach VPC.")
+
+    def ensure_igw_absent(self, igw_id, vpc_id):
+        igw = self.get_matching_igw(vpc_id, gateway_id=igw_id)
         if igw is None:
             return self._results
 
+        igw_vpc_id = ""
+
+        if len(igw["attachments"]) > 0:
+            igw_vpc_id = igw["attachments"][0]["vpc_id"]
+
+        if vpc_id and (igw_vpc_id != vpc_id):
+            self._module.fail_json(msg=f"Supplied VPC ({vpc_id}) does not match found VPC ({igw_vpc_id}), aborting")
+
         if self._check_mode:
             self._results["changed"] = True
             return self._results
 
         try:
             self._results["changed"] = True
-            self._connection.detach_internet_gateway(
-                aws_retry=True, InternetGatewayId=igw["internet_gateway_id"], VpcId=vpc_id
-            )
+
+            if igw_vpc_id:
+                self.detach_vpc(igw["internet_gateway_id"], igw_vpc_id)
+
             self._connection.delete_internet_gateway(aws_retry=True, InternetGatewayId=igw["internet_gateway_id"])
         except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
             self._module.fail_json_aws(e, msg="Unable to delete Internet Gateway")
 
         return self._results
 
-    def ensure_igw_present(self, vpc_id, tags, purge_tags):
-        igw = self.get_matching_igw(vpc_id)
+    def ensure_igw_present(self, igw_id, vpc_id, tags, purge_tags, force_attach, detach_vpc):
+        igw = None
+
+        if igw_id:
+            igw = self.get_matching_igw(None, gateway_id=igw_id)
+        elif vpc_id:
+            igw = self.get_matching_igw(vpc_id)
 
         if igw is None:
             if self._check_mode:
@@ -192,26 +307,61 @@ def ensure_igw_present(self, vpc_id, tags, purge_tags):
                 self._results["gateway_id"] = None
                 return self._results
 
+            if vpc_id:
+                self.get_matching_vpc(vpc_id)
+
             try:
                 response = self._connection.create_internet_gateway(aws_retry=True)
 
                 # Ensure the gateway exists before trying to attach it or add tags
                 waiter = get_waiter(self._connection, "internet_gateway_exists")
                 waiter.wait(InternetGatewayIds=[response["InternetGateway"]["InternetGatewayId"]])
+                self._results["changed"] = True
 
                 igw = camel_dict_to_snake_dict(response["InternetGateway"])
-                self._connection.attach_internet_gateway(
-                    aws_retry=True, InternetGatewayId=igw["internet_gateway_id"], VpcId=vpc_id
-                )
 
-                # Ensure the gateway is attached before proceeding
-                waiter = get_waiter(self._connection, "internet_gateway_attached")
-                waiter.wait(InternetGatewayIds=[igw["internet_gateway_id"]])
-                self._results["changed"] = True
+                if vpc_id:
+                    self.attach_vpc(igw["internet_gateway_id"], vpc_id)
             except botocore.exceptions.WaiterError as e:
                 self._module.fail_json_aws(e, msg="No Internet Gateway exists.")
             except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
                 self._module.fail_json_aws(e, msg="Unable to create Internet Gateway")
+        else:
+            igw_vpc_id = None
+
+            if len(igw["attachments"]) > 0:
+                igw_vpc_id = igw["attachments"][0]["vpc_id"]
+
+                if detach_vpc:
+                    if self._check_mode:
+                        self._results["changed"] = True
+                        self._results["gateway_id"] = igw["internet_gateway_id"]
+                        return self._results
+
+                    self.detach_vpc(igw["internet_gateway_id"], igw_vpc_id)
+
+                elif igw_vpc_id != vpc_id:
+                    if self._check_mode:
+                        self._results["changed"] = True
+                        self._results["gateway_id"] = igw["internet_gateway_id"]
+                        return self._results
+
+                    if force_attach:
+                        self.get_matching_vpc(vpc_id)
+
+                        self.detach_vpc(igw["internet_gateway_id"], igw_vpc_id)
+                        self.attach_vpc(igw["internet_gateway_id"], vpc_id)
+                    else:
+                        self._module.fail_json(msg="VPC already attached, but does not match requested VPC.")
+
+            elif vpc_id:
+                if self._check_mode:
+                    self._results["changed"] = True
+                    self._results["gateway_id"] = igw["internet_gateway_id"]
+                    return self._results
+
+                self.get_matching_vpc(vpc_id)
+                self.attach_vpc(igw["internet_gateway_id"], vpc_id)
 
         # Modify tags
         self._results["changed"] |= ensure_ec2_tags(
@@ -234,16 +384,30 @@ def ensure_igw_present(self, vpc_id, tags, purge_tags):
 
 def main():
     argument_spec = dict(
-        vpc_id=dict(required=True),
+        internet_gateway_id=dict(),
+        vpc_id=dict(),
         state=dict(default="present", choices=["present", "absent"]),
         tags=dict(required=False, type="dict", aliases=["resource_tags"]),
         purge_tags=dict(default=True, type="bool"),
+        force_attach=dict(default=False, type="bool"),
+        detach_vpc=dict(default=False, type="bool"),
     )
 
+    required_if = [
+        ("force_attach", True, ("vpc_id",), False),
+        ("state", "absent", ("internet_gateway_id", "vpc_id"), True),
+        ("detach_vpc", True, ("internet_gateway_id", "vpc_id"), True),
+    ]
+
+    mutually_exclusive = [("force_attach", "detach_vpc")]
+
     module = AnsibleAWSModule(
         argument_spec=argument_spec,
         supports_check_mode=True,
+        required_if=required_if,
+        mutually_exclusive=mutually_exclusive,
     )
+
     results = dict(changed=False)
     igw_manager = AnsibleEc2Igw(module=module, results=results)
     igw_manager.process()


Test patch:
diff --git a/tests/integration/targets/ec2_vpc_igw/defaults/main.yml b/tests/integration/targets/ec2_vpc_igw/defaults/main.yml
--- a/tests/integration/targets/ec2_vpc_igw/defaults/main.yml
+++ b/tests/integration/targets/ec2_vpc_igw/defaults/main.yml
@@ -1,3 +1,6 @@
 vpc_name: '{{ resource_prefix }}-vpc'
 vpc_seed: '{{ resource_prefix }}'
 vpc_cidr: 10.{{ 256 | random(seed=vpc_seed) }}.0.0/16
+vpc_name_2: '{{ tiny_prefix }}-vpc-2'
+vpc_seed_2: '{{ tiny_prefix }}'
+vpc_cidr_2: 10.{{ 256 | random(seed=vpc_seed_2) }}.0.0/16
diff --git a/tests/integration/targets/ec2_vpc_igw/tasks/main.yml b/tests/integration/targets/ec2_vpc_igw/tasks/main.yml
--- a/tests/integration/targets/ec2_vpc_igw/tasks/main.yml
+++ b/tests/integration/targets/ec2_vpc_igw/tasks/main.yml
@@ -39,6 +39,16 @@
       - vpc_result.vpc.tags["Name"] == "{{ resource_prefix }}-vpc"
       - vpc_result.vpc.tags["Description"] == "Created by ansible-test"
 
+  # ============================================================
+  - name: Create a second VPC
+    ec2_vpc_net:
+      name: '{{ vpc_name_2 }}'
+      state: present
+      cidr_block: '{{ vpc_cidr_2 }}'
+      tags:
+        Description: Created by ansible-test
+    register: vpc_2_result
+  
   # ============================================================
   - name: Search for internet gateway by VPC - no matches
     ec2_vpc_igw_info:
@@ -95,6 +105,7 @@
     set_fact:
       igw_id: '{{ vpc_igw_create.gateway_id }}'
       vpc_id: '{{ vpc_result.vpc.id }}'
+      vpc_2_id: '{{ vpc_2_result.vpc.id }}'
 
   - name: Search for internet gateway by VPC
     ec2_vpc_igw_info:
@@ -534,6 +545,274 @@
       that:
       - vpc_igw_delete is not changed
 
+  # ============================================================
+  - name: Create new detached internet gateway - CHECK_MODE
+    ec2_vpc_igw:
+      state: present
+    register: detached_igw_result
+    check_mode: true
+
+  - name: Assert creation would happen (expected changed=true) - CHECK_MODE
+    assert:
+      that:
+      - detached_igw_result is changed
+
+  - name: Create new detached internet gateway (expected changed=true)
+    ec2_vpc_igw:
+      state: present
+    register: detached_igw_result
+
+  - name: Assert creation happened (expected changed=true)
+    assert:
+      that:
+      - detached_igw_result is changed
+      - '"gateway_id" in detached_igw_result'
+      - detached_igw_result.gateway_id.startswith("igw-")
+      - not detached_igw_result.vpc_id
+      
+  # ============================================================
+  - name: Test state=absent when supplying only a gateway id (expected chaged=true) - CHECK_MODE
+    ec2_vpc_igw:
+      state: absent
+      internet_gateway_id: '{{ detached_igw_result.gateway_id }}'
+    register: vpc_igw_delete
+    check_mode: true
+
+  - name: Assert state=absent when supplying only a gateway id (expected chaged=true) - CHECK_MODE
+    assert:
+      that:
+        - vpc_igw_delete is changed
+
+  - name: Search for IGW by ID
+    ec2_vpc_igw_info:
+      internet_gateway_ids: '{{ detached_igw_result.gateway_id }}'
+    register: igw_info
+
+  - name: Check that IGW was not deleted in check mode
+    assert:
+      that:
+        - '"internet_gateways" in igw_info'
+        - igw_info.internet_gateways | length == 1
+
+  - name: Test state=absent when supplying only a gateway id (expected chaged=true)
+    ec2_vpc_igw:
+      state: absent
+      internet_gateway_id: '{{ detached_igw_result.gateway_id }}'
+    register: vpc_igw_delete
+
+  - name: Assert state=absent when supplying only a gateway id (expected chaged=true)
+    assert:
+      that:
+        - vpc_igw_delete is changed
+
+  - name: Fetch removed IGW by ID
+    ec2_vpc_igw_info:
+      internet_gateway_ids: '{{ detached_igw_result.gateway_id }}'
+    register: igw_info
+    ignore_errors: true
+
+  - name: Check IGW does not exist
+    assert:
+      that:
+        # Deliberate choice not to change bevahiour when searching by ID
+      - igw_info is failed
+
+  # ============================================================
+  - name: Create new internet gateway for vpc tests
+    ec2_vpc_igw:
+      state: present
+    register: detached_igw_result
+      
+  # ============================================================
+  - name: Test attaching VPC to gateway - CHECK_MODE
+    ec2_vpc_igw:
+      state: present
+      internet_gateway_id: '{{ detached_igw_result.gateway_id }}'
+      vpc_id: '{{ vpc_id }}'
+    register: attach_vpc_result
+    check_mode: true
+
+  - name: Assert that VPC was attached - CHECK_MODE
+    assert:
+      that:
+        - attach_vpc_result is changed
+
+  - name: Test attaching VPC to gateway
+    ec2_vpc_igw:
+      state: present
+      internet_gateway_id: '{{ detached_igw_result.gateway_id }}'
+      vpc_id: '{{ vpc_id }}'
+    register: attach_vpc_result
+
+  - name: Assert that VPC was attached
+    assert:
+      that:
+        - attach_vpc_result is changed
+        - attach_vpc_result.vpc_id == vpc_id
+        - attach_vpc_result.gateway_id == detached_igw_result.gateway_id
+
+  # ============================================================
+  - name: Test detaching VPC from gateway - CHECK_MODE
+    ec2_vpc_igw:
+      state: present
+      internet_gateway_id: '{{ detached_igw_result.gateway_id }}'
+      detach_vpc: true
+    register: detach_vpc_result
+    check_mode: true
+
+  - name: Assert that VPC was detached - CHECK_MODE
+    assert:
+      that:
+        - detach_vpc_result is changed
+
+  - name: Test detaching VPC from gateway
+    ec2_vpc_igw:
+      state: present
+      internet_gateway_id: '{{ detached_igw_result.gateway_id }}'
+      detach_vpc: true
+    register: detach_vpc_result
+
+  - name: Assert that VPC was detached
+    assert:
+      that:
+        - detach_vpc_result is changed
+        - not detach_vpc_result.vpc_id
+        - detach_vpc_result.gateway_id == detached_igw_result.gateway_id
+
+  # ============================================================
+  - name: Attach VPC to gateway for VPC change tests
+    ec2_vpc_igw:
+      state: present
+      internet_gateway_id: '{{ detached_igw_result.gateway_id }}'
+      vpc_id: '{{ vpc_id }}'
+
+  # ============================================================
+  - name: Attempt change attached VPC with force_attach=false (default) - CHECK_MODE
+    ec2_vpc_igw:
+      state: present
+      internet_gateway_id: '{{ detached_igw_result.gateway_id }}'
+      vpc_id: '{{ vpc_2_result.vpc.id }}'
+    register: igw_vpc_changed_result
+    check_mode: true
+
+  - name: Assert VPC changed with force_attach=false (default) - CHECK_MODE
+    assert:
+      that:
+        - igw_vpc_changed_result is changed
+        - vpc_id not in igw_vpc_changed_result
+      
+  - name: Attempt change with force_attach=false (default) (expected failure)
+    ec2_vpc_igw:
+      state: present
+      internet_gateway_id: '{{ detached_igw_result.gateway_id }}'
+      vpc_id: '{{ vpc_2_result.vpc.id }}'
+    register: igw_vpc_changed_result
+    ignore_errors: true
+
+  - name: Assert VPC changed with force_attach=false (default)
+    assert:
+      that:
+        - igw_vpc_changed_result is failed
+        - igw_vpc_changed_result.msg is search('VPC already attached, but does not match requested VPC.')
+
+  # ============================================================
+  - name: Attempt change attached VPC with force_attach=true - CHECK_MODE
+    ec2_vpc_igw:
+      state: present
+      internet_gateway_id: '{{ detached_igw_result.gateway_id }}'
+      vpc_id: '{{ vpc_2_result.vpc.id }}'
+      force_attach: true
+    register: igw_vpc_changed_result
+    check_mode: true
+
+  - name: Assert VPC changed with force_attach=true - CHECK_MODE
+    assert:
+      that:
+        - igw_vpc_changed_result is changed
+        - vpc_id not in igw_vpc_changed_result
+      
+  - name: Attempt change with force_attach=true
+    ec2_vpc_igw:
+      state: present
+      internet_gateway_id: '{{ detached_igw_result.gateway_id }}'
+      vpc_id: '{{ vpc_2_result.vpc.id }}'
+      force_attach: true
+    register: igw_vpc_changed_result
+
+  - name: Assert VPC changed with force_attach=true
+    assert:
+      that:
+        - igw_vpc_changed_result is changed
+        - igw_vpc_changed_result.vpc_id == vpc_2_id
+
+  # ============================================================
+  - name: Test state=absent when supplying a gateway id and wrong vpc id (expected failure) - CHECK_MODE
+    ec2_vpc_igw:
+      state: absent
+      internet_gateway_id: '{{ detached_igw_result.gateway_id }}'
+      vpc_id: 'vpc-xxxxxxxxx'
+    register: vpc_igw_delete
+    check_mode: true
+    ignore_errors: true
+
+  - name: Assert state=absent when supplying a gateway id and wrong vpc id (expected failure) - CHECK_MODE
+    assert:
+      that:
+        - vpc_igw_delete is failed
+        - vpc_igw_delete.msg is search('Supplied VPC.*does not match found VPC.*')
+
+  - name: Test state=absent when supplying a gateway id and wrong vpc id (expected failure)
+    ec2_vpc_igw:
+      state: absent
+      internet_gateway_id: '{{ detached_igw_result.gateway_id}}'
+      vpc_id: 'vpc-xxxxxxxxx'
+    register: vpc_igw_delete
+    ignore_errors: true
+
+  - name: Assert state=absent when supplying a gateway id and wrong vpc id (expected failure)
+    assert:
+      that:
+        - vpc_igw_delete is failed
+        - vpc_igw_delete.msg is search('Supplied VPC.*does not match found VPC.*')
+
+  # ============================================================
+  - name: Test state=absent when supplying a gateway id and vpc id (expected chaged=true) - CHECK_MODE
+    ec2_vpc_igw:
+      state: absent
+      internet_gateway_id: '{{ detached_igw_result.gateway_id }}'
+      vpc_id: '{{ vpc_2_id }}'
+    register: vpc_igw_delete
+    check_mode: true
+
+  - name: Assert state=absent when supplying a gateway id and vpc id (expected chaged=true) - CHECK_MODE
+    assert:
+      that:
+        - vpc_igw_delete is changed
+
+  - name: Test state=absent when supplying a gateway id and vpc id (expected chaged=true)
+    ec2_vpc_igw:
+      state: absent
+      internet_gateway_id: '{{ detached_igw_result.gateway_id }}'
+      vpc_id: '{{ vpc_2_id }}'
+    register: vpc_igw_delete
+
+  - name: Assert state=absent when supplying a gateway id and vpc id (expected chaged=true)
+    assert:
+      that:
+        - vpc_igw_delete is changed
+
+  - name: Fetch removed IGW by ID
+    ec2_vpc_igw_info:
+      internet_gateway_ids: '{{ detached_igw_result.gateway_id }}'
+    register: igw_info
+    ignore_errors: true
+
+  - name: Check IGW does not exist
+    assert:
+      that:
+        # Deliberate choice not to change bevahiour when searching by ID
+      - igw_info is failed
+
   always:
     # ============================================================
   - name: Tidy up IGW
@@ -542,9 +821,28 @@
       vpc_id: '{{ vpc_result.vpc.id }}'
     ignore_errors: true
 
+  - name: Tidy up IGW on second VPC
+    ec2_vpc_igw:
+      state: absent
+      vpc_id: '{{ vpc_2_result.vpc.id }}'
+    ignore_errors: true
+
+  - name: Tidy up detached IGW
+    ec2_vpc_igw:
+      state: absent
+      internet_gateway_id: '{{ detached_igw_result.gateway_id }}'
+    ignore_errors: true
+
   - name: Tidy up VPC
     ec2_vpc_net:
       name: '{{ vpc_name }}'
       state: absent
       cidr_block: '{{ vpc_cidr }}'
     ignore_errors: true
+
+  - name: Tidy up second VPC
+    ec2_vpc_net:
+      name: '{{ vpc_name_2 }}'
+      state: absent
+      cidr_block: '{{ vpc_cidr_2 }}'
+    ignore_errors: true