We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
`lookup` environment variables `aws_ec2.yml` file
### Summary

I'm trying to `lookup` environment variables in `aws_ec2.yml` file but the `lookup` seems to be not working as expected, is this something intentionally not allowed? 

### Issue Type

Bug Report

### Component Name

ec2_instnace

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.15.2]
  config file = /Users/<user>/.ansible.cfg
  configured module search path = ['/Users/<user>/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /opt/homebrew/lib/python3.11/site-packages/ansible
  ansible collection location = /Users/<user>/.ansible/collections:/usr/share/ansible/collections
  executable location = /opt/homebrew/bin/ansible
  python version = 3.11.6 (main, Oct  2 2023, 20:46:14) [Clang 14.0.3 (clang-1403.0.22.14.1)] (/opt/homebrew/opt/python@3.11/bin/python3.11)
  jinja version = 3.1.2
  libyaml = True
```


### Collection Versions

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

# /Users/<user>/.ansible/collections/ansible_collections
Collection            Version
--------------------- -------
amazon.aws            6.5.0  
ansible.windows       2.0.0  
awx.awx               21.0.0 
chocolatey.chocolatey 1.5.1  
community.mysql       3.7.2  
community.windows     2.0.0  
lowlydba.sqlserver    0.7.0  
microsoft.ad          1.4.1  ****
```


### AWS SDK versions

```console (paste below)
$ pip show boto boto3 botocore
Name: boto3
Version: 1.28.21
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: /opt/homebrew/lib/python3.11/site-packages
Requires: botocore, jmespath, s3transfer
Required-by: 
---
Name: botocore
Version: 1.31.21
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: /opt/homebrew/lib/python3.11/site-packages
Requires: jmespath, python-dateutil, urllib3
Required-by: aws-secretsmanager-caching, boto3, **s3transfer**
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
CONFIG_FILE() = /Users/<user>/.ansible.cfg
PAGER(env: PAGER) = less
```


### OS / Environment

macOS Ventura Version 13.6.2 (22G320)

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
plugin: aws_ec2
assume_role_arn: "arn:aws:iam::{{ lookup('env', 'AWS_ACCOUNT_ID') | default('XXXXXXXXXXX', true) }}:role/ansible-manager"
regions:
  - "{{ lookup('env', 'AWS_REGION') | default('us-east-1', true) }}"
```

i have this `yml` file which is something i was expecting it would lookup the env variables and get the authorization 

### Expected Results

Expected result that `lookup` would happen and assume role/region would get set right, instead it is being parsed as plain text and `lookup` is not being recognized is what i understand looking at the result below


### Actual Results

```console (paste below)
aws_ec2.yml with auto plugin: An error occurred (AccessDenied) when calling the AssumeRole operation: User:
<UserName> is not
authorized to perform: sts:AssumeRole on resource: arn:aws:iam::{{ lookup('ansible.builtin.env', 'AWS_ACCOUNT_ID') |
default('XXXXXXXXXXX', True) }}:role/ansible-manager

aws_ec2.yml with auto plugin: Couldn't connect to AWS: Provided region_name '{{ lookup('env', 'AWS_REGION') |
default('us-east-1', true) }}' doesn't match a supported format.
```


### Code of Conduct

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

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `plugins/plugin_utils/inventory.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 # Copyright: (c) 2022, Ansible Project
4 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5 
6 try:
7     import boto3
8     import botocore
9 except ImportError:
10     pass  # will be captured by imported HAS_BOTO3
11 
12 from ansible.plugins.inventory import BaseInventoryPlugin
13 from ansible.plugins.inventory import Cacheable
14 from ansible.plugins.inventory import Constructable
15 
16 from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code
17 from ansible_collections.amazon.aws.plugins.plugin_utils.base import AWSPluginBase
18 from ansible_collections.amazon.aws.plugins.plugin_utils.botocore import AnsibleBotocoreError
19 
20 
21 def _boto3_session(profile_name=None):
22     if profile_name is None:
23         return boto3.Session()
24     return boto3.session.Session(profile_name=profile_name)
25 
26 
27 class AWSInventoryBase(BaseInventoryPlugin, Constructable, Cacheable, AWSPluginBase):
28     class TemplatedOptions:
29         # When someone looks up the TEMPLATABLE_OPTIONS using get() any templates
30         # will be templated using the loader passed to parse.
31         TEMPLATABLE_OPTIONS = (
32             "access_key",
33             "secret_key",
34             "session_token",
35             "profile",
36             "iam_role_name",
37         )
38 
39         def __init__(self, templar, options):
40             self.original_options = options
41             self.templar = templar
42 
43         def __getitem__(self, *args):
44             return self.original_options.__getitem__(self, *args)
45 
46         def __setitem__(self, *args):
47             return self.original_options.__setitem__(self, *args)
48 
49         def get(self, *args):
50             value = self.original_options.get(*args)
51             if not value:
52                 return value
53             if args[0] not in self.TEMPLATABLE_OPTIONS:
54                 return value
55             if not self.templar.is_template(value):
56                 return value
57 
58             return self.templar.template(variable=value, disable_lookups=False)
59 
60     def get_options(self, *args):
61         original_options = super().get_options(*args)
62         if not self.templar:
63             return original_options
64         return self.TemplatedOptions(self.templar, original_options)
65 
66     def __init__(self):
67         super().__init__()
68         self._frozen_credentials = {}
69 
70     # pylint: disable=too-many-arguments
71     def parse(self, inventory, loader, path, cache=True, botocore_version=None, boto3_version=None):
72         super().parse(inventory, loader, path)
73         self.require_aws_sdk(botocore_version=botocore_version, boto3_version=boto3_version)
74         self._read_config_data(path)
75         self._set_frozen_credentials()
76 
77     def client(self, *args, **kwargs):
78         kw_args = dict(self._frozen_credentials)
79         kw_args.update(kwargs)
80         return super().client(*args, **kw_args)
81 
82     def resource(self, *args, **kwargs):
83         kw_args = dict(self._frozen_credentials)
84         kw_args.update(kwargs)
85         return super().resource(*args, **kw_args)
86 
87     def _freeze_iam_role(self, iam_role_arn):
88         if hasattr(self, "ansible_name"):
89             role_session_name = f"ansible_aws_{self.ansible_name}_dynamic_inventory"
90         else:
91             role_session_name = "ansible_aws_dynamic_inventory"
92         assume_params = {"RoleArn": iam_role_arn, "RoleSessionName": role_session_name}
93 
94         try:
95             sts = self.client("sts")
96             assumed_role = sts.assume_role(**assume_params)
97         except AnsibleBotocoreError as e:
98             self.fail_aws(f"Unable to assume role {iam_role_arn}", exception=e)
99 
100         credentials = assumed_role.get("Credentials")
101         if not credentials:
102             self.fail_aws(f"Unable to assume role {iam_role_arn}")
103 
104         self._frozen_credentials = {
105             "profile_name": None,
106             "aws_access_key_id": credentials.get("AccessKeyId"),
107             "aws_secret_access_key": credentials.get("SecretAccessKey"),
108             "aws_session_token": credentials.get("SessionToken"),
109         }
110 
111     def _set_frozen_credentials(self):
112         options = self.get_options()
113         iam_role_arn = options.get("assume_role_arn")
114         if iam_role_arn:
115             self._freeze_iam_role(iam_role_arn)
116 
117     def _describe_regions(self, service):
118         # Try pulling a list of regions from the service
119         try:
120             initial_region = self.region or "us-east-1"
121             client = self.client(service, region=initial_region)
122             resp = client.describe_regions()
123         except AttributeError:
124             # Not all clients support describe
125             pass
126         except is_boto3_error_code("UnauthorizedOperation"):
127             self.warn(f"UnauthorizedOperation when trying to list {service} regions")
128         except botocore.exceptions.NoRegionError:
129             self.warn(f"NoRegionError when trying to list {service} regions")
130         except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
131             self.warn(f"Unexpected error while trying to list {service} regions: {e}")
132         else:
133             regions = [x["RegionName"] for x in resp.get("Regions", [])]
134             if regions:
135                 return regions
136         return None
137 
138     def _boto3_regions(self, service):
139         options = self.get_options()
140 
141         if options.get("regions"):
142             return options.get("regions")
143 
144         # boto3 has hard coded lists of available regions for resources, however this does bit-rot
145         # As such we try to query the service, and fall back to ec2 for a list of regions
146         for resource_type in list({service, "ec2"}):
147             regions = self._describe_regions(resource_type)
148             if regions:
149                 return regions
150 
151         # fallback to local list hardcoded in boto3 if still no regions
152         session = _boto3_session(options.get("profile"))
153         regions = session.get_available_regions(service)
154 
155         if not regions:
156             # I give up, now you MUST give me regions
157             self.fail_aws(
158                 "Unable to get regions list from available methods, you must specify the 'regions' option to continue."
159             )
160 
161         return regions
162 
163     def all_clients(self, service):
164         """
165         Generator that yields a boto3 client and the region
166 
167         :param service: The boto3 service to connect to.
168 
169         Note: For services which don't support 'DescribeRegions' this may include bad
170         endpoints, and as such EndpointConnectionError should be cleanly handled as a non-fatal
171         error.
172         """
173         regions = self._boto3_regions(service=service)
174 
175         for region in regions:
176             connection = self.client(service, region=region)
177             yield connection, region
178 
179     def get_cached_result(self, path, cache):
180         # false when refresh_cache or --flush-cache is used
181         if not cache:
182             return False, None
183         # get the user-specified directive
184         if not self.get_option("cache"):
185             return False, None
186 
187         cache_key = self.get_cache_key(path)
188         try:
189             cached_value = self._cache[cache_key]
190         except KeyError:
191             # if cache expires or cache file doesn"t exist
192             return False, None
193 
194         return True, cached_value
195 
196     def update_cached_result(self, path, cache, result):
197         if not self.get_option("cache"):
198             return
199 
200         cache_key = self.get_cache_key(path)
201         # We weren't explicitly told to flush the cache, and there's already a cache entry,
202         # this means that the result we're being passed came from the cache.  As such we don't
203         # want to "update" the cache as that could reset a TTL on the cache entry.
204         if cache and cache_key in self._cache:
205             return
206 
207         self._cache[cache_key] = result
208 
209     def verify_file(self, path):
210         """
211         :param path: the path to the inventory config file
212         :return the contents of the config file
213         """
214         if not super().verify_file(path):
215             return False
216 
217         if hasattr(self, "INVENTORY_FILE_SUFFIXES"):
218             if not path.endswith(self.INVENTORY_FILE_SUFFIXES):
219                 return False
220 
221         return True
222
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/plugins/plugin_utils/inventory.py b/plugins/plugin_utils/inventory.py
--- a/plugins/plugin_utils/inventory.py
+++ b/plugins/plugin_utils/inventory.py
@@ -33,7 +33,10 @@
             "secret_key",
             "session_token",
             "profile",
-            "iam_role_name",
+            "endpoint_url",
+            "assume_role_arn",
+            "region",
+            "regions",
         )
 
         def __init__(self, templar, options):
@@ -48,20 +51,21 @@
 
         def get(self, *args):
             value = self.original_options.get(*args)
-            if not value:
-                return value
-            if args[0] not in self.TEMPLATABLE_OPTIONS:
-                return value
-            if not self.templar.is_template(value):
+            if (
+                not value
+                or not self.templar
+                or args[0] not in self.TEMPLATABLE_OPTIONS
+                or not self.templar.is_template(value)
+            ):
                 return value
 
             return self.templar.template(variable=value, disable_lookups=False)
 
     def get_options(self, *args):
-        original_options = super().get_options(*args)
-        if not self.templar:
-            return original_options
-        return self.TemplatedOptions(self.templar, original_options)
+        return self.TemplatedOptions(self.templar, super().get_options(*args))
+
+    def get_option(self, option, hostvars=None):
+        return self.TemplatedOptions(self.templar, {option: super().get_option(option, hostvars)}).get(option)
 
     def __init__(self):
         super().__init__()
@@ -109,8 +113,7 @@
         }
 
     def _set_frozen_credentials(self):
-        options = self.get_options()
-        iam_role_arn = options.get("assume_role_arn")
+        iam_role_arn = self.get_option("assume_role_arn")
         if iam_role_arn:
             self._freeze_iam_role(iam_role_arn)
 
@@ -136,10 +139,9 @@
         return None
 
     def _boto3_regions(self, service):
-        options = self.get_options()
-
-        if options.get("regions"):
-            return options.get("regions")
+        regions = self.get_option("regions")
+        if regions:
+            return regions
 
         # boto3 has hard coded lists of available regions for resources, however this does bit-rot
         # As such we try to query the service, and fall back to ec2 for a list of regions
@@ -149,7 +151,7 @@
                 return regions
 
         # fallback to local list hardcoded in boto3 if still no regions
-        session = _boto3_session(options.get("profile"))
+        session = _boto3_session(self.get_option("profile"))
         regions = session.get_available_regions(service)
 
         if not regions:

{"golden_diff": "diff --git a/plugins/plugin_utils/inventory.py b/plugins/plugin_utils/inventory.py\n--- a/plugins/plugin_utils/inventory.py\n+++ b/plugins/plugin_utils/inventory.py\n@@ -33,7 +33,10 @@\n             \"secret_key\",\n             \"session_token\",\n             \"profile\",\n-            \"iam_role_name\",\n+            \"endpoint_url\",\n+            \"assume_role_arn\",\n+            \"region\",\n+            \"regions\",\n         )\n \n         def __init__(self, templar, options):\n@@ -48,20 +51,21 @@\n \n         def get(self, *args):\n             value = self.original_options.get(*args)\n-            if not value:\n-                return value\n-            if args[0] not in self.TEMPLATABLE_OPTIONS:\n-                return value\n-            if not self.templar.is_template(value):\n+            if (\n+                not value\n+                or not self.templar\n+                or args[0] not in self.TEMPLATABLE_OPTIONS\n+                or not self.templar.is_template(value)\n+            ):\n                 return value\n \n             return self.templar.template(variable=value, disable_lookups=False)\n \n     def get_options(self, *args):\n-        original_options = super().get_options(*args)\n-        if not self.templar:\n-            return original_options\n-        return self.TemplatedOptions(self.templar, original_options)\n+        return self.TemplatedOptions(self.templar, super().get_options(*args))\n+\n+    def get_option(self, option, hostvars=None):\n+        return self.TemplatedOptions(self.templar, {option: super().get_option(option, hostvars)}).get(option)\n \n     def __init__(self):\n         super().__init__()\n@@ -109,8 +113,7 @@\n         }\n \n     def _set_frozen_credentials(self):\n-        options = self.get_options()\n-        iam_role_arn = options.get(\"assume_role_arn\")\n+        iam_role_arn = self.get_option(\"assume_role_arn\")\n         if iam_role_arn:\n             self._freeze_iam_role(iam_role_arn)\n \n@@ -136,10 +139,9 @@\n         return None\n \n     def _boto3_regions(self, service):\n-        options = self.get_options()\n-\n-        if options.get(\"regions\"):\n-            return options.get(\"regions\")\n+        regions = self.get_option(\"regions\")\n+        if regions:\n+            return regions\n \n         # boto3 has hard coded lists of available regions for resources, however this does bit-rot\n         # As such we try to query the service, and fall back to ec2 for a list of regions\n@@ -149,7 +151,7 @@\n                 return regions\n \n         # fallback to local list hardcoded in boto3 if still no regions\n-        session = _boto3_session(options.get(\"profile\"))\n+        session = _boto3_session(self.get_option(\"profile\"))\n         regions = session.get_available_regions(service)\n \n         if not regions:\n", "issue": "`lookup` environment variables `aws_ec2.yml` file\n### Summary\n\nI'm trying to `lookup` environment variables in `aws_ec2.yml` file but the `lookup` seems to be not working as expected, is this something intentionally not allowed? \n\n### Issue Type\n\nBug Report\n\n### Component Name\n\nec2_instnace\n\n### Ansible Version\n\n```console (paste below)\r\n$ ansible --version\r\nansible [core 2.15.2]\r\n  config file = /Users/<user>/.ansible.cfg\r\n  configured module search path = ['/Users/<user>/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']\r\n  ansible python module location = /opt/homebrew/lib/python3.11/site-packages/ansible\r\n  ansible collection location = /Users/<user>/.ansible/collections:/usr/share/ansible/collections\r\n  executable location = /opt/homebrew/bin/ansible\r\n  python version = 3.11.6 (main, Oct  2 2023, 20:46:14) [Clang 14.0.3 (clang-1403.0.22.14.1)] (/opt/homebrew/opt/python@3.11/bin/python3.11)\r\n  jinja version = 3.1.2\r\n  libyaml = True\r\n```\r\n\n\n### Collection Versions\n\n```console (paste below)\r\n$ ansible-galaxy collection list\r\n\r\n# /Users/<user>/.ansible/collections/ansible_collections\r\nCollection            Version\r\n--------------------- -------\r\namazon.aws            6.5.0  \r\nansible.windows       2.0.0  \r\nawx.awx               21.0.0 \r\nchocolatey.chocolatey 1.5.1  \r\ncommunity.mysql       3.7.2  \r\ncommunity.windows     2.0.0  \r\nlowlydba.sqlserver    0.7.0  \r\nmicrosoft.ad          1.4.1  ****\r\n```\r\n\n\n### AWS SDK versions\n\n```console (paste below)\r\n$ pip show boto boto3 botocore\r\nName: boto3\r\nVersion: 1.28.21\r\nSummary: The AWS SDK for Python\r\nHome-page: https://github.com/boto/boto3\r\nAuthor: Amazon Web Services\r\nAuthor-email: \r\nLicense: Apache License 2.0\r\nLocation: /opt/homebrew/lib/python3.11/site-packages\r\nRequires: botocore, jmespath, s3transfer\r\nRequired-by: \r\n---\r\nName: botocore\r\nVersion: 1.31.21\r\nSummary: Low-level, data-driven core of boto 3.\r\nHome-page: https://github.com/boto/botocore\r\nAuthor: Amazon Web Services\r\nAuthor-email: \r\nLicense: Apache License 2.0\r\nLocation: /opt/homebrew/lib/python3.11/site-packages\r\nRequires: jmespath, python-dateutil, urllib3\r\nRequired-by: aws-secretsmanager-caching, boto3, **s3transfer**\r\n```\r\n\n\n### Configuration\n\n```console (paste below)\r\n$ ansible-config dump --only-changed\r\nCONFIG_FILE() = /Users/<user>/.ansible.cfg\r\nPAGER(env: PAGER) = less\r\n```\r\n\n\n### OS / Environment\n\nmacOS Ventura Version 13.6.2 (22G320)\n\n### Steps to Reproduce\n\n<!--- Paste example playbooks or commands between quotes below -->\r\n```yaml (paste below)\r\nplugin: aws_ec2\r\nassume_role_arn: \"arn:aws:iam::{{ lookup('env', 'AWS_ACCOUNT_ID') | default('XXXXXXXXXXX', true) }}:role/ansible-manager\"\r\nregions:\r\n  - \"{{ lookup('env', 'AWS_REGION') | default('us-east-1', true) }}\"\r\n```\r\n\r\ni have this `yml` file which is something i was expecting it would lookup the env variables and get the authorization \n\n### Expected Results\n\nExpected result that `lookup` would happen and assume role/region would get set right, instead it is being parsed as plain text and `lookup` is not being recognized is what i understand looking at the result below\r\n\n\n### Actual Results\n\n```console (paste below)\r\naws_ec2.yml with auto plugin: An error occurred (AccessDenied) when calling the AssumeRole operation: User:\r\n<UserName> is not\r\nauthorized to perform: sts:AssumeRole on resource: arn:aws:iam::{{ lookup('ansible.builtin.env', 'AWS_ACCOUNT_ID') |\r\ndefault('XXXXXXXXXXX', True) }}:role/ansible-manager\r\n\r\naws_ec2.yml with auto plugin: Couldn't connect to AWS: Provided region_name '{{ lookup('env', 'AWS_REGION') |\r\ndefault('us-east-1', true) }}' doesn't match a supported format.\r\n```\r\n\n\n### Code of Conduct\n\n- [X] I agree to follow the Ansible Code of Conduct\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\n# Copyright: (c) 2022, Ansible Project\n# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)\n\ntry:\n    import boto3\n    import botocore\nexcept ImportError:\n    pass  # will be captured by imported HAS_BOTO3\n\nfrom ansible.plugins.inventory import BaseInventoryPlugin\nfrom ansible.plugins.inventory import Cacheable\nfrom ansible.plugins.inventory import Constructable\n\nfrom ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code\nfrom ansible_collections.amazon.aws.plugins.plugin_utils.base import AWSPluginBase\nfrom ansible_collections.amazon.aws.plugins.plugin_utils.botocore import AnsibleBotocoreError\n\n\ndef _boto3_session(profile_name=None):\n    if profile_name is None:\n        return boto3.Session()\n    return boto3.session.Session(profile_name=profile_name)\n\n\nclass AWSInventoryBase(BaseInventoryPlugin, Constructable, Cacheable, AWSPluginBase):\n    class TemplatedOptions:\n        # When someone looks up the TEMPLATABLE_OPTIONS using get() any templates\n        # will be templated using the loader passed to parse.\n        TEMPLATABLE_OPTIONS = (\n            \"access_key\",\n            \"secret_key\",\n            \"session_token\",\n            \"profile\",\n            \"iam_role_name\",\n        )\n\n        def __init__(self, templar, options):\n            self.original_options = options\n            self.templar = templar\n\n        def __getitem__(self, *args):\n            return self.original_options.__getitem__(self, *args)\n\n        def __setitem__(self, *args):\n            return self.original_options.__setitem__(self, *args)\n\n        def get(self, *args):\n            value = self.original_options.get(*args)\n            if not value:\n                return value\n            if args[0] not in self.TEMPLATABLE_OPTIONS:\n                return value\n            if not self.templar.is_template(value):\n                return value\n\n            return self.templar.template(variable=value, disable_lookups=False)\n\n    def get_options(self, *args):\n        original_options = super().get_options(*args)\n        if not self.templar:\n            return original_options\n        return self.TemplatedOptions(self.templar, original_options)\n\n    def __init__(self):\n        super().__init__()\n        self._frozen_credentials = {}\n\n    # pylint: disable=too-many-arguments\n    def parse(self, inventory, loader, path, cache=True, botocore_version=None, boto3_version=None):\n        super().parse(inventory, loader, path)\n        self.require_aws_sdk(botocore_version=botocore_version, boto3_version=boto3_version)\n        self._read_config_data(path)\n        self._set_frozen_credentials()\n\n    def client(self, *args, **kwargs):\n        kw_args = dict(self._frozen_credentials)\n        kw_args.update(kwargs)\n        return super().client(*args, **kw_args)\n\n    def resource(self, *args, **kwargs):\n        kw_args = dict(self._frozen_credentials)\n        kw_args.update(kwargs)\n        return super().resource(*args, **kw_args)\n\n    def _freeze_iam_role(self, iam_role_arn):\n        if hasattr(self, \"ansible_name\"):\n            role_session_name = f\"ansible_aws_{self.ansible_name}_dynamic_inventory\"\n        else:\n            role_session_name = \"ansible_aws_dynamic_inventory\"\n        assume_params = {\"RoleArn\": iam_role_arn, \"RoleSessionName\": role_session_name}\n\n        try:\n            sts = self.client(\"sts\")\n            assumed_role = sts.assume_role(**assume_params)\n        except AnsibleBotocoreError as e:\n            self.fail_aws(f\"Unable to assume role {iam_role_arn}\", exception=e)\n\n        credentials = assumed_role.get(\"Credentials\")\n        if not credentials:\n            self.fail_aws(f\"Unable to assume role {iam_role_arn}\")\n\n        self._frozen_credentials = {\n            \"profile_name\": None,\n            \"aws_access_key_id\": credentials.get(\"AccessKeyId\"),\n            \"aws_secret_access_key\": credentials.get(\"SecretAccessKey\"),\n            \"aws_session_token\": credentials.get(\"SessionToken\"),\n        }\n\n    def _set_frozen_credentials(self):\n        options = self.get_options()\n        iam_role_arn = options.get(\"assume_role_arn\")\n        if iam_role_arn:\n            self._freeze_iam_role(iam_role_arn)\n\n    def _describe_regions(self, service):\n        # Try pulling a list of regions from the service\n        try:\n            initial_region = self.region or \"us-east-1\"\n            client = self.client(service, region=initial_region)\n            resp = client.describe_regions()\n        except AttributeError:\n            # Not all clients support describe\n            pass\n        except is_boto3_error_code(\"UnauthorizedOperation\"):\n            self.warn(f\"UnauthorizedOperation when trying to list {service} regions\")\n        except botocore.exceptions.NoRegionError:\n            self.warn(f\"NoRegionError when trying to list {service} regions\")\n        except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:\n            self.warn(f\"Unexpected error while trying to list {service} regions: {e}\")\n        else:\n            regions = [x[\"RegionName\"] for x in resp.get(\"Regions\", [])]\n            if regions:\n                return regions\n        return None\n\n    def _boto3_regions(self, service):\n        options = self.get_options()\n\n        if options.get(\"regions\"):\n            return options.get(\"regions\")\n\n        # boto3 has hard coded lists of available regions for resources, however this does bit-rot\n        # As such we try to query the service, and fall back to ec2 for a list of regions\n        for resource_type in list({service, \"ec2\"}):\n            regions = self._describe_regions(resource_type)\n            if regions:\n                return regions\n\n        # fallback to local list hardcoded in boto3 if still no regions\n        session = _boto3_session(options.get(\"profile\"))\n        regions = session.get_available_regions(service)\n\n        if not regions:\n            # I give up, now you MUST give me regions\n            self.fail_aws(\n                \"Unable to get regions list from available methods, you must specify the 'regions' option to continue.\"\n            )\n\n        return regions\n\n    def all_clients(self, service):\n        \"\"\"\n        Generator that yields a boto3 client and the region\n\n        :param service: The boto3 service to connect to.\n\n        Note: For services which don't support 'DescribeRegions' this may include bad\n        endpoints, and as such EndpointConnectionError should be cleanly handled as a non-fatal\n        error.\n        \"\"\"\n        regions = self._boto3_regions(service=service)\n\n        for region in regions:\n            connection = self.client(service, region=region)\n            yield connection, region\n\n    def get_cached_result(self, path, cache):\n        # false when refresh_cache or --flush-cache is used\n        if not cache:\n            return False, None\n        # get the user-specified directive\n        if not self.get_option(\"cache\"):\n            return False, None\n\n        cache_key = self.get_cache_key(path)\n        try:\n            cached_value = self._cache[cache_key]\n        except KeyError:\n            # if cache expires or cache file doesn\"t exist\n            return False, None\n\n        return True, cached_value\n\n    def update_cached_result(self, path, cache, result):\n        if not self.get_option(\"cache\"):\n            return\n\n        cache_key = self.get_cache_key(path)\n        # We weren't explicitly told to flush the cache, and there's already a cache entry,\n        # this means that the result we're being passed came from the cache.  As such we don't\n        # want to \"update\" the cache as that could reset a TTL on the cache entry.\n        if cache and cache_key in self._cache:\n            return\n\n        self._cache[cache_key] = result\n\n    def verify_file(self, path):\n        \"\"\"\n        :param path: the path to the inventory config file\n        :return the contents of the config file\n        \"\"\"\n        if not super().verify_file(path):\n            return False\n\n        if hasattr(self, \"INVENTORY_FILE_SUFFIXES\"):\n            if not path.endswith(self.INVENTORY_FILE_SUFFIXES):\n                return False\n\n        return True\n", "path": "plugins/plugin_utils/inventory.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\n# Copyright: (c) 2022, Ansible Project\n# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)\n\ntry:\n    import boto3\n    import botocore\nexcept ImportError:\n    pass  # will be captured by imported HAS_BOTO3\n\nfrom ansible.plugins.inventory import BaseInventoryPlugin\nfrom ansible.plugins.inventory import Cacheable\nfrom ansible.plugins.inventory import Constructable\n\nfrom ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code\nfrom ansible_collections.amazon.aws.plugins.plugin_utils.base import AWSPluginBase\nfrom ansible_collections.amazon.aws.plugins.plugin_utils.botocore import AnsibleBotocoreError\n\n\ndef _boto3_session(profile_name=None):\n    if profile_name is None:\n        return boto3.Session()\n    return boto3.session.Session(profile_name=profile_name)\n\n\nclass AWSInventoryBase(BaseInventoryPlugin, Constructable, Cacheable, AWSPluginBase):\n    class TemplatedOptions:\n        # When someone looks up the TEMPLATABLE_OPTIONS using get() any templates\n        # will be templated using the loader passed to parse.\n        TEMPLATABLE_OPTIONS = (\n            \"access_key\",\n            \"secret_key\",\n            \"session_token\",\n            \"profile\",\n            \"endpoint_url\",\n            \"assume_role_arn\",\n            \"region\",\n            \"regions\",\n        )\n\n        def __init__(self, templar, options):\n            self.original_options = options\n            self.templar = templar\n\n        def __getitem__(self, *args):\n            return self.original_options.__getitem__(self, *args)\n\n        def __setitem__(self, *args):\n            return self.original_options.__setitem__(self, *args)\n\n        def get(self, *args):\n            value = self.original_options.get(*args)\n            if (\n                not value\n                or not self.templar\n                or args[0] not in self.TEMPLATABLE_OPTIONS\n                or not self.templar.is_template(value)\n            ):\n                return value\n\n            return self.templar.template(variable=value, disable_lookups=False)\n\n    def get_options(self, *args):\n        return self.TemplatedOptions(self.templar, super().get_options(*args))\n\n    def get_option(self, option, hostvars=None):\n        return self.TemplatedOptions(self.templar, {option: super().get_option(option, hostvars)}).get(option)\n\n    def __init__(self):\n        super().__init__()\n        self._frozen_credentials = {}\n\n    # pylint: disable=too-many-arguments\n    def parse(self, inventory, loader, path, cache=True, botocore_version=None, boto3_version=None):\n        super().parse(inventory, loader, path)\n        self.require_aws_sdk(botocore_version=botocore_version, boto3_version=boto3_version)\n        self._read_config_data(path)\n        self._set_frozen_credentials()\n\n    def client(self, *args, **kwargs):\n        kw_args = dict(self._frozen_credentials)\n        kw_args.update(kwargs)\n        return super().client(*args, **kw_args)\n\n    def resource(self, *args, **kwargs):\n        kw_args = dict(self._frozen_credentials)\n        kw_args.update(kwargs)\n        return super().resource(*args, **kw_args)\n\n    def _freeze_iam_role(self, iam_role_arn):\n        if hasattr(self, \"ansible_name\"):\n            role_session_name = f\"ansible_aws_{self.ansible_name}_dynamic_inventory\"\n        else:\n            role_session_name = \"ansible_aws_dynamic_inventory\"\n        assume_params = {\"RoleArn\": iam_role_arn, \"RoleSessionName\": role_session_name}\n\n        try:\n            sts = self.client(\"sts\")\n            assumed_role = sts.assume_role(**assume_params)\n        except AnsibleBotocoreError as e:\n            self.fail_aws(f\"Unable to assume role {iam_role_arn}\", exception=e)\n\n        credentials = assumed_role.get(\"Credentials\")\n        if not credentials:\n            self.fail_aws(f\"Unable to assume role {iam_role_arn}\")\n\n        self._frozen_credentials = {\n            \"profile_name\": None,\n            \"aws_access_key_id\": credentials.get(\"AccessKeyId\"),\n            \"aws_secret_access_key\": credentials.get(\"SecretAccessKey\"),\n            \"aws_session_token\": credentials.get(\"SessionToken\"),\n        }\n\n    def _set_frozen_credentials(self):\n        iam_role_arn = self.get_option(\"assume_role_arn\")\n        if iam_role_arn:\n            self._freeze_iam_role(iam_role_arn)\n\n    def _describe_regions(self, service):\n        # Try pulling a list of regions from the service\n        try:\n            initial_region = self.region or \"us-east-1\"\n            client = self.client(service, region=initial_region)\n            resp = client.describe_regions()\n        except AttributeError:\n            # Not all clients support describe\n            pass\n        except is_boto3_error_code(\"UnauthorizedOperation\"):\n            self.warn(f\"UnauthorizedOperation when trying to list {service} regions\")\n        except botocore.exceptions.NoRegionError:\n            self.warn(f\"NoRegionError when trying to list {service} regions\")\n        except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:\n            self.warn(f\"Unexpected error while trying to list {service} regions: {e}\")\n        else:\n            regions = [x[\"RegionName\"] for x in resp.get(\"Regions\", [])]\n            if regions:\n                return regions\n        return None\n\n    def _boto3_regions(self, service):\n        regions = self.get_option(\"regions\")\n        if regions:\n            return regions\n\n        # boto3 has hard coded lists of available regions for resources, however this does bit-rot\n        # As such we try to query the service, and fall back to ec2 for a list of regions\n        for resource_type in list({service, \"ec2\"}):\n            regions = self._describe_regions(resource_type)\n            if regions:\n                return regions\n\n        # fallback to local list hardcoded in boto3 if still no regions\n        session = _boto3_session(self.get_option(\"profile\"))\n        regions = session.get_available_regions(service)\n\n        if not regions:\n            # I give up, now you MUST give me regions\n            self.fail_aws(\n                \"Unable to get regions list from available methods, you must specify the 'regions' option to continue.\"\n            )\n\n        return regions\n\n    def all_clients(self, service):\n        \"\"\"\n        Generator that yields a boto3 client and the region\n\n        :param service: The boto3 service to connect to.\n\n        Note: For services which don't support 'DescribeRegions' this may include bad\n        endpoints, and as such EndpointConnectionError should be cleanly handled as a non-fatal\n        error.\n        \"\"\"\n        regions = self._boto3_regions(service=service)\n\n        for region in regions:\n            connection = self.client(service, region=region)\n            yield connection, region\n\n    def get_cached_result(self, path, cache):\n        # false when refresh_cache or --flush-cache is used\n        if not cache:\n            return False, None\n        # get the user-specified directive\n        if not self.get_option(\"cache\"):\n            return False, None\n\n        cache_key = self.get_cache_key(path)\n        try:\n            cached_value = self._cache[cache_key]\n        except KeyError:\n            # if cache expires or cache file doesn\"t exist\n            return False, None\n\n        return True, cached_value\n\n    def update_cached_result(self, path, cache, result):\n        if not self.get_option(\"cache\"):\n            return\n\n        cache_key = self.get_cache_key(path)\n        # We weren't explicitly told to flush the cache, and there's already a cache entry,\n        # this means that the result we're being passed came from the cache.  As such we don't\n        # want to \"update\" the cache as that could reset a TTL on the cache entry.\n        if cache and cache_key in self._cache:\n            return\n\n        self._cache[cache_key] = result\n\n    def verify_file(self, path):\n        \"\"\"\n        :param path: the path to the inventory config file\n        :return the contents of the config file\n        \"\"\"\n        if not super().verify_file(path):\n            return False\n\n        if hasattr(self, \"INVENTORY_FILE_SUFFIXES\"):\n            if not path.endswith(self.INVENTORY_FILE_SUFFIXES):\n                return False\n\n        return True\n", "path": "plugins/plugin_utils/inventory.py"}]}