Repository: ansible-collections/amazon.aws

Issue:
ec2_vpc_net_info module : Cannot retrieve info from a VPC shared from another AWS account
### Summary

When I try to describe all of my VPCs in one AWS account where one of my VPC is a VPC shared from another account, I get the following error : 
```python
"msg": "Unable to describe if ClassicLink is enabled: An error occurred (InvalidVpcID.NotFound) when calling the DescribeVpcClassicLink operation: The vpc ID 'vpc-XXXXXXXX' does not exist"
```

### Issue Type

Bug Report

### Component Name

ec2_vpc_net_info

### Ansible Version

```console (paste below)
$ ansible --version

```
```
ansible 2.9.0
  config file = None
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.6/dist-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 3.6.9 (default, Dec  8 2021, 21:08:43) [GCC 8.4.0]
```


### Collection Versions

```console (paste below)
$ ansible-galaxy collection list
```


### AWS SDK versions

```console (paste below)
$ pip show boto boto3 botocore
```
```
Name: boto
Version: 2.49.0
Summary: Amazon Web Services Library
Home-page: https://github.com/boto/boto/
Author: Mitch Garnaat
Author-email: mitch@garnaat.com
License: MIT
Location: /usr/local/lib/python3.6/dist-packages
Requires:
Required-by:
---
Name: boto3
Version: 1.20.51
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/dist-packages
Requires: botocore, jmespath, s3transfer
Required-by:
---
Name: botocore
Version: 1.23.51
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/dist-packages
Requires: jmespath, python-dateutil, urllib3
Required-by: awscli, boto3, s3transfer
```

### Configuration

```console (paste below)
$ ansible-config dump --only-changed

```


### OS / Environment

Ubuntu 18.04

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
---
- name: Describe VPCs
  hosts: localhost
  tasks:
    - ec2_vpc_net_info:
```


### Expected Results

I expected the module to get info from the VPC because i can still describe it with describe_vpcs

### Actual Results

```console (paste below)
[WARNING]: No inventory was parsed, only implicit localhost is available

[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'


PLAY [Describe VPCs] *******************************************************************************************************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************************************************************************************************************************************************************************************************
ok: [localhost]

TASK [ec2_vpc_net_info] ****************************************************************************************************************************************************************************************************************************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: botocore.exceptions.ClientError: An error occurred (InvalidVpcID.NotFound) when calling the DescribeVpcClassicLink operation: The vpc ID 'vpc-XXXXXXXX' does not exist
fatal: [localhost]: FAILED! => {"changed": false, "error": {"code": "InvalidVpcID.NotFound", "message": "The vpc ID 'vpc-XXXXXXXX' does not exist"}, "msg": "Unable to describe if ClassicLink is enabled: An error occurred (InvalidVpcID.NotFound) when calling the DescribeVpcClassicLink operation: The vpc ID 'vpc-XXXXXXXX' does not exist", "response_metadata": {"http_headers": {"cache-control": "no-cache, no-store", "connection": "close", "content-type": "text/xml;charset=UTF-8", "date": "Wed, 09 Feb 2022 16:49:19 GMT", "server": "AmazonEC2", "strict-transport-security": "max-age=31536000; includeSubDomains", "transfer-encoding": "chunked", "vary": "accept-encoding", "x-amzn-requestid": "XXXXXXXX"}, "http_status_code": 400, "request_id": "XXXXXXXX", "retry_attempts": 0}}

PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0


```


### Code of Conduct

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


Discussion:
Files identified in the description:
* [`plugins/modules/ec2_vpc_net_info.py`](https://github.com/['ansible-collections/amazon.aws', 'ansible-collections/community.aws', 'ansible-collections/community.vmware']/blob/main/plugins/modules/ec2_vpc_net_info.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 @jillr @s-hertel @tremble @whiter
[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_net_info.py b/plugins/modules/ec2_vpc_net_info.py
--- a/plugins/modules/ec2_vpc_net_info.py
+++ b/plugins/modules/ec2_vpc_net_info.py
@@ -181,7 +181,6 @@ def describe_vpcs(connection, module):
 
     # init empty list for return vars
     vpc_info = list()
-    vpc_list = list()
 
     # Get the basic VPC info
     try:
@@ -189,53 +188,34 @@ def describe_vpcs(connection, module):
     except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
         module.fail_json_aws(e, msg="Unable to describe VPCs {0}".format(vpc_ids))
 
-    # Loop through results and create a list of VPC IDs
-    for vpc in response['Vpcs']:
-        vpc_list.append(vpc['VpcId'])
-
     # We can get these results in bulk but still needs two separate calls to the API
-    try:
-        cl_enabled = connection.describe_vpc_classic_link(VpcIds=vpc_list, aws_retry=True)
-    except is_boto3_error_code('UnsupportedOperation'):
-        cl_enabled = {'Vpcs': [{'VpcId': vpc_id, 'ClassicLinkEnabled': False} for vpc_id in vpc_list]}
-    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:  # pylint: disable=duplicate-except
-        module.fail_json_aws(e, msg='Unable to describe if ClassicLink is enabled')
-
-    try:
-        cl_dns_support = connection.describe_vpc_classic_link_dns_support(VpcIds=vpc_list, aws_retry=True)
-    except is_boto3_error_code('UnsupportedOperation'):
-        cl_dns_support = {'Vpcs': [{'VpcId': vpc_id, 'ClassicLinkDnsSupported': False} for vpc_id in vpc_list]}
-    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:  # pylint: disable=duplicate-except
-        module.fail_json_aws(e, msg='Unable to describe if ClassicLinkDns is supported')
-
+    cl_enabled = {}
+    cl_dns_support = {}
+    dns_support = {}
+    dns_hostnames = {}
     # Loop through the results and add the other VPC attributes we gathered
     for vpc in response['Vpcs']:
-        error_message = "Unable to describe VPC attribute {0}"
-        # We have to make two separate calls per VPC to get these attributes.
-        try:
-            dns_support = connection.describe_vpc_attribute(VpcId=vpc['VpcId'],
-                                                            Attribute='enableDnsSupport', aws_retry=True)
-        except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
-            module.fail_json_aws(e, msg=error_message.format('enableDnsSupport'))
-        try:
-            dns_hostnames = connection.describe_vpc_attribute(VpcId=vpc['VpcId'],
-                                                              Attribute='enableDnsHostnames', aws_retry=True)
-        except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
-            module.fail_json_aws(e, msg=error_message.format('enableDnsHostnames'))
-
-        # loop through the ClassicLink Enabled results and add the value for the correct VPC
-        for item in cl_enabled['Vpcs']:
-            if vpc['VpcId'] == item['VpcId']:
-                vpc['ClassicLinkEnabled'] = item['ClassicLinkEnabled']
-
-        # loop through the ClassicLink DNS support results and add the value for the correct VPC
-        for item in cl_dns_support['Vpcs']:
-            if vpc['VpcId'] == item['VpcId']:
-                vpc['ClassicLinkDnsSupported'] = item['ClassicLinkDnsSupported']
+        error_message = "Unable to describe VPC attribute {0} on VPC {1}"
+        cl_enabled = describe_classic_links(module, connection, vpc['VpcId'], 'ClassicLinkEnabled', error_message)
+        cl_dns_support = describe_classic_links(module, connection, vpc['VpcId'], 'ClassicLinkDnsSupported', error_message)
+        dns_support = describe_vpc_attribute(module, connection, vpc['VpcId'], 'enableDnsSupport', error_message)
+        dns_hostnames = describe_vpc_attribute(module, connection, vpc['VpcId'], 'enableDnsHostnames', error_message)
+        if cl_enabled:
+            # loop through the ClassicLink Enabled results and add the value for the correct VPC
+            for item in cl_enabled['Vpcs']:
+                if vpc['VpcId'] == item['VpcId']:
+                    vpc['ClassicLinkEnabled'] = item['ClassicLinkEnabled']
+        if cl_dns_support:
+            # loop through the ClassicLink DNS support results and add the value for the correct VPC
+            for item in cl_dns_support['Vpcs']:
+                if vpc['VpcId'] == item['VpcId']:
+                    vpc['ClassicLinkDnsSupported'] = item['ClassicLinkDnsSupported']
 
         # add the two DNS attributes
-        vpc['EnableDnsSupport'] = dns_support['EnableDnsSupport'].get('Value')
-        vpc['EnableDnsHostnames'] = dns_hostnames['EnableDnsHostnames'].get('Value')
+        if dns_support:
+            vpc['EnableDnsSupport'] = dns_support['EnableDnsSupport'].get('Value')
+        if dns_hostnames:
+            vpc['EnableDnsHostnames'] = dns_hostnames['EnableDnsHostnames'].get('Value')
         # for backwards compatibility
         vpc['id'] = vpc['VpcId']
         vpc_info.append(camel_dict_to_snake_dict(vpc))
@@ -245,6 +225,33 @@ def describe_vpcs(connection, module):
     module.exit_json(vpcs=vpc_info)
 
 
+def describe_classic_links(module, connection, vpc, attribute, error_message):
+    result = None
+    try:
+        if attribute == "ClassicLinkEnabled":
+            result = connection.describe_vpc_classic_link(VpcIds=[vpc], aws_retry=True)
+        else:
+            result = connection.describe_vpc_classic_link_dns_support(VpcIds=[vpc], aws_retry=True)
+    except is_boto3_error_code('UnsupportedOperation'):
+        result = {'Vpcs': [{'VpcId': vpc, 'ClassicLinkEnabled': False}]}
+    except is_boto3_error_code('InvalidVpcID.NotFound'):
+        module.warn(error_message.format(attribute, vpc))
+    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:  # pylint: disable=duplicate-except
+        module.fail_json_aws(e, msg='Unable to describe if {0} is enabled'.format(attribute))
+    return result
+
+
+def describe_vpc_attribute(module, connection, vpc, attribute, error_message):
+    result = None
+    try:
+        return connection.describe_vpc_attribute(VpcId=vpc, Attribute=attribute, aws_retry=True)
+    except is_boto3_error_code('InvalidVpcID.NotFound'):
+        module.warn(error_message.format(attribute, vpc))
+    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
+        module.fail_json_aws(e, msg=error_message.format(attribute, vpc))
+    return result
+
+
 def main():
     argument_spec = dict(
         vpc_ids=dict(type='list', elements='str', default=[]),


Test patch: