Repository: ansible-collections/amazon.aws

Issue:
ec2_vpc_nat_gateway fails silently if EIP doesn't exist
### Summary

Trying to create a new NAT gateway with an elastic IP.  The task reports ok, but the result does not include a nat_gateway_id.  The NAT gateway is not created, and does not already exist.  When running with -vvv I can see a message which says the EIP does not exist.

### Issue Type

Bug Report

### Component Name

ec2_vpc_nat_gateway

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.12.2]
  config file = /home/pfletcher/pcm/ansible_code/ansible.cfg
  configured module search path = ['/home/pfletcher/pcm/ansible_code/library']
  ansible python module location = /usr/lib/python3.8/site-packages/ansible
  ansible collection location = /home/pfletcher/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.8.12 (default, Sep 16 2021, 10:46:05) [GCC 8.5.0 20210514 (Red Hat 8.5.0-3)]
  jinja version = 2.10.3
  libyaml = True

```


### Collection Versions

```console (paste below)
$ ansible-navigator collections list -m interactive
 NAME                                       VERSION         SHADOWED          TYPE                PATH
 0│amazon.aws                                 5.1.0              False          contained           /usr/share/ansible/collections/ansible_collections/amazon/aws/
 1│ansible.controller                         4.1.2              False          contained           /usr/share/ansible/collections/ansible_collections/ansible/controller/
 2│ansible.netcommon                          4.1.0              False          contained           /usr/share/ansible/collections/ansible_collections/ansible/netcommon/
 3│ansible.posix                              1.4.0              False          contained           /usr/share/ansible/collections/ansible_collections/ansible/posix/
 4│ansible.utils                              2.8.0              False          contained           /usr/share/ansible/collections/ansible_collections/ansible/utils/
 5│ansible.windows                            1.12.0             False          contained           /usr/share/ansible/collections/ansible_collections/ansible/windows/
 6│awx.awx                                    21.10.0            False          contained           /usr/share/ansible/collections/ansible_collections/awx/awx/
 7│chocolatey.chocolatey                      1.3.1              False          contained           /usr/share/ansible/collections/ansible_collections/chocolatey/chocolatey/
 8│community.aws                              5.0.0              False          contained           /usr/share/ansible/collections/ansible_collections/community/aws/
 9│community.crypto                           2.9.0              False          contained           /usr/share/ansible/collections/ansible_collections/community/crypto/
10│community.docker                           3.3.1              False          contained           /usr/share/ansible/collections/ansible_collections/community/docker/
11│community.general                          6.1.0              False          contained           /usr/share/ansible/collections/ansible_collections/community/general/
12│community.google                           1.0.0              False          contained           /usr/share/ansible/collections/ansible_collections/community/google/
13│community.kubernetes                       2.0.1              False          contained           /usr/share/ansible/collections/ansible_collections/community/kubernetes/
14│community.postgresql                       2.3.1              False          contained           /usr/share/ansible/collections/ansible_collections/community/postgresql/
15│community.windows                          1.11.1             False          contained           /usr/share/ansible/collections/ansible_collections/community/windows/
16│containers.podman                          1.10.1             False          contained           /usr/share/ansible/collections/ansible_collections/containers/podman/
17│f5networks.f5_modules                      1.21.0             False          contained           /usr/share/ansible/collections/ansible_collections/f5networks/f5_modules/
18│google.cloud                               1.0.2              False          contained           /usr/share/ansible/collections/ansible_collections/google/cloud/
19│kubernetes.core                            2.3.2              False          contained           /usr/share/ansible/collections/ansible_collections/kubernetes/core/

```


### AWS SDK versions

```console (paste below)
$ pip show boto boto3 botocore
WARNING: Package(s) not found: boto
Name: boto3
Version: 1.23.10
Summary: The AWS SDK for Python
Home-page: https://github.com/boto/boto3
Author: Amazon Web Services
Author-email:
License: Apache License 2.0
Location: /usr/local/lib/python3.6/site-packages
Requires: botocore, jmespath, s3transfer
Required-by: gimme-aws-creds
---
Name: botocore
Version: 1.26.10
Summary: Low-level, data-driven core of boto 3.
Home-page: https://github.com/boto/botocore
Author: Amazon Web Services
Author-email:
License: Apache License 2.0
Location: /usr/local/lib/python3.6/site-packages
Requires: jmespath, python-dateutil, urllib3
Required-by: boto3, s3transfer
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
CALLBACKS_ENABLED(/home/pfletcher/pcm/ansible_code/ansible.cfg) = ['profile_tasks', 'community.general.yaml']
DEFAULT_BECOME_FLAGS(/home/pfletcher/pcm/ansible_code/ansible.cfg) = -H -S -i
DEFAULT_FILTER_PLUGIN_PATH(/home/pfletcher/pcm/ansible_code/ansible.cfg) = ['/home/pfletcher/pcm/ansible_code/filter_plugins']
DEFAULT_FORKS(/home/pfletcher/pcm/ansible_code/ansible.cfg) = 200
DEFAULT_INVENTORY_PLUGIN_PATH(/home/pfletcher/pcm/ansible_code/ansible.cfg) = ['/home/pfletcher/pcm/ansible_code/inventory_plugins']
DEFAULT_MODULE_PATH(/home/pfletcher/pcm/ansible_code/ansible.cfg) = ['/home/pfletcher/pcm/ansible_code/library']
DEFAULT_ROLES_PATH(/home/pfletcher/pcm/ansible_code/ansible.cfg) = ['/home/pfletcher/pcm/ansible_code/shared/roles']
DEFAULT_STDOUT_CALLBACK(env: ANSIBLE_STDOUT_CALLBACK) = yaml
DEFAULT_TIMEOUT(/home/pfletcher/pcm/ansible_code/ansible.cfg) = 90
INVENTORY_ENABLED(/home/pfletcher/pcm/ansible_code/ansible.cfg) = ['host_list', 'script', 'yaml', 'ini', 'auto', 'gcp_compute']

```


### OS / Environment

Red Hat Enterprise Linux release 8.6 (Ootpa)
Linux aac.az-b.us-east-1.shared.dev.symcld.net 4.18.0-372.13.1.el8_6.x86_64 #1 SMP Mon Jun 6 15:05:22 EDT 2022 x86_64 x86_64 x86_64 GNU/Linux

Using ansible-navigator with a container execution environment

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
- name: Create NAT Gateway
  ec2_vpc_nat_gateway:
    region:       "{{ region_name }}"
    subnet_id:    "{{ vpc_public_subnets_list[0].id }}"
    eip_address:  "67.219.240.2"
    if_exist_do_not_create: true
    state:        present
    wait:         yes
  register: _nat_gateway

- set_fact:
    vpc_nat_gateway_id: "{{ _nat_gateway.nat_gateway_id }}"
  name: Save NAT gateway ID
```


### Expected Results

I expect either a NAT gateway to be created with the specified parameters, and for the task to report changed and return the nat_gateway_id, or for the task to fail.  In fact it reported ok (but the gateway does not already exist), and it does not return a nat_gateway_id.  This causes subsequent tasks to fail which try to use the id, but the problem is earlier.

### Actual Results

```console (paste below)
TASK [Create NAT Gateway] ****************************************************************************************************************************************************************************************************************************
task path: /home/pfletcher/pcm/ansible_code/vpc/create_nat_gateway.yml:7
Wednesday 07 December 2022  18:13:52 +0000 (0:00:00.211)       0:00:33.617 ****
redirecting (type: modules) ansible.builtin.ec2_vpc_nat_gateway to community.aws.ec2_vpc_nat_gateway
redirecting (type: modules) community.aws.ec2_vpc_nat_gateway to amazon.aws.ec2_vpc_nat_gateway
ok: [virtual-vpc-prod-us-east-1] => {"changed": false, "msg": "EIP 67.219.240.2 does not exist"}
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/ec2_vpc_nat_gateway.py`](https://github.com/['ansible-collections/amazon.aws', 'ansible-collections/community.aws', 'ansible-collections/community.vmware']/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 @alinabuzachis @jillr @jonhadfield @linuxdynasty @s-hertel @tremble
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@bloob00k Thank you for reporting this. Does your EIP "67.219.240.2" exist prior creating the NAT gateway?
Hi, no the EIP does not exist.  So in that respect the module is correct; but I think in that case it should fail, rather than report ok.  (For further context, the EIP is a BYOIP, which I forgot to allocate before creating the NAT GW)

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
@@ -75,6 +75,16 @@
         When specifying this option, ensure you specify the eip_address parameter
         as well otherwise any subsequent runs will fail.
     type: str
+  default_create:
+    description:
+      - When I(default_create=True) and I(eip_address) has been set, but not yet
+        allocated, the NAT gateway is created and a new EIP is automatically allocated.
+      - When I(default_create=False) and I(eip_address) has been set, but not yet
+        allocated, the module will fail.
+      - If I(eip_address) has not been set, this parameter has no effect.
+    default: false
+    type: bool
+    version_added: 6.2.0
 author:
   - Allen Sanabria (@linuxdynasty)
   - Jon Hadfield (@jonhadfield)
@@ -660,6 +670,7 @@ def pre_create(
     wait=False,
     client_token=None,
     connectivity_type="public",
+    default_create=False,
 ):
     """Create an Amazon NAT Gateway.
     Args:
@@ -681,6 +692,8 @@ def pre_create(
             default = False
         client_token (str):
             default = None
+        default_create (bool): create a NAT gateway even if EIP address is not found.
+            default = False
 
     Basic Usage:
         >>> client = boto3.client('ec2')
@@ -744,9 +757,25 @@ def pre_create(
     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:
+            if not allocation_id and not default_create:
                 changed = False
-                return changed, msg, dict()
+                module.fail_json(msg=msg)
+            elif not allocation_id and default_create:
+                eip_address = None
+                return pre_create(
+                    client,
+                    module,
+                    subnet_id,
+                    tags,
+                    purge_tags,
+                    allocation_id,
+                    eip_address,
+                    if_exist_do_not_create,
+                    wait,
+                    client_token,
+                    connectivity_type,
+                    default_create,
+                )
 
         existing_gateways, allocation_id_exists = gateway_in_subnet_exists(client, module, subnet_id, allocation_id)
 
@@ -869,6 +898,7 @@ def main():
         client_token=dict(type="str", no_log=False),
         tags=dict(required=False, type="dict", aliases=["resource_tags"]),
         purge_tags=dict(default=True, type="bool"),
+        default_create=dict(type="bool", default=False),
     )
 
     module = AnsibleAWSModule(
@@ -890,6 +920,7 @@ def main():
     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")
+    default_create = module.params.get("default_create")
 
     try:
         client = module.client("ec2", retry_decorator=AWSRetry.jittered_backoff())
@@ -912,6 +943,7 @@ def main():
             wait,
             client_token,
             connectivity_type,
+            default_create,
         )
     else:
         changed, msg, results = remove(client, module, nat_gateway_id, wait, release_eip, connectivity_type)


Test patch:
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
@@ -411,6 +411,47 @@
       - create_ngw.vpc_id == vpc_id
 
 
+  # ============================================================
+  - name: Create new NAT gateway when eip_address is invalid and create_default is true
+    ec2_vpc_nat_gateway:
+      subnet_id: '{{ subnet_id }}'
+      eip_address:  "192.0.2.1"
+      state: present
+      wait: yes
+      default_create: true
+    register: _nat_gateway
+
+  - name: 
+    assert:
+      that:
+        - _nat_gateway.changed
+        - '"create_time" in _nat_gateway'
+        - '"nat_gateway_addresses" in _nat_gateway'
+        - '"nat_gateway_id" in _nat_gateway'
+        - _nat_gateway.nat_gateway_id.startswith("nat-")
+        - '"state" in _nat_gateway'
+        - _nat_gateway.state == 'available'
+        - '"subnet_id" in _nat_gateway'
+        - _nat_gateway.subnet_id == subnet_id
+        - '"tags" in _nat_gateway'
+        - '"vpc_id" in _nat_gateway'
+        - _nat_gateway.vpc_id == vpc_id
+  
+  - name: Fail when eip_address is invalid and create_default is false
+    ec2_vpc_nat_gateway:
+      subnet_id: '{{ subnet_id }}'
+      eip_address:  "192.0.2.1"
+      state: present
+      wait: yes
+    register: _fail_nat_gateway
+    ignore_errors: true
+
+  - name: Assert fail because eip_address is invalid
+    assert:
+      that:
+        _fail_nat_gateway.msg == "EIP 192.0.2.1 does not exist"
+
+
   # ============================================================
   - name: Fetch NAT gateway by ID (list)
     ec2_vpc_nat_gateway_info: