summaryrefslogtreecommitdiffstats
path: root/ansible_collections/amazon
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/amazon')
-rw-r--r--ansible_collections/amazon/aws/.github/workflows/docs-pr.yml1
-rw-r--r--ansible_collections/amazon/aws/CHANGELOG.rst28
-rw-r--r--ansible_collections/amazon/aws/FILES.json61
-rw-r--r--ansible_collections/amazon/aws/MANIFEST.json4
-rw-r--r--ansible_collections/amazon/aws/changelogs/changelog.yaml27
-rw-r--r--ansible_collections/amazon/aws/docs/docsite/rst/CHANGELOG.rst28
-rw-r--r--ansible_collections/amazon/aws/plugins/module_utils/common.py2
-rw-r--r--ansible_collections/amazon/aws/plugins/module_utils/iam.py191
-rw-r--r--ansible_collections/amazon/aws/plugins/module_utils/transformation.py96
-rw-r--r--ansible_collections/amazon/aws/plugins/modules/cloudwatchlogs_log_group_info.py17
-rw-r--r--ansible_collections/amazon/aws/plugins/modules/iam_user_info.py19
-rw-r--r--ansible_collections/amazon/aws/plugins/modules/s3_object.py9
-rw-r--r--ansible_collections/amazon/aws/plugins/plugin_utils/inventory.py36
-rw-r--r--ansible_collections/amazon/aws/tests/integration/targets/ec2_ami/tasks/main.yml4
-rw-r--r--ansible_collections/amazon/aws/tests/integration/targets/ec2_security_group/tasks/multi_nested_target.yml18
-rw-r--r--ansible_collections/amazon/aws/tests/integration/targets/inventory_aws_ec2/playbooks/create_inventory_config.yml5
-rw-r--r--ansible_collections/amazon/aws/tests/integration/targets/inventory_aws_ec2/templates/config.ini.j23
-rw-r--r--ansible_collections/amazon/aws/tests/integration/targets/inventory_aws_ec2/templates/inventory_with_template.yml.j22
-rw-r--r--ansible_collections/amazon/aws/tests/integration/targets/s3_object/aliases1
-rw-r--r--ansible_collections/amazon/aws/tests/integration/targets/s3_object/tasks/copy_object.yml71
-rw-r--r--ansible_collections/amazon/aws/tests/integration/targets/s3_object/tasks/main.yml4
-rw-r--r--ansible_collections/amazon/aws/tests/unit/module_utils/iam/test_iam_resource_transforms.py583
-rw-r--r--ansible_collections/amazon/aws/tests/unit/module_utils/transformation/test_boto3_resource_to_ansible_dict.py140
-rw-r--r--ansible_collections/amazon/aws/tests/unit/plugin_utils/inventory/test_inventory_base.py131
-rw-r--r--ansible_collections/amazon/aws/tests/unit/plugins/inventory/test_aws_ec2.py1
25 files changed, 1302 insertions, 180 deletions
diff --git a/ansible_collections/amazon/aws/.github/workflows/docs-pr.yml b/ansible_collections/amazon/aws/.github/workflows/docs-pr.yml
index 8cc39d8f5..9158b038f 100644
--- a/ansible_collections/amazon/aws/.github/workflows/docs-pr.yml
+++ b/ansible_collections/amazon/aws/.github/workflows/docs-pr.yml
@@ -21,6 +21,7 @@ jobs:
intersphinx-links: |
community_aws:https://ansible-collections.github.io/community.aws/branch/main/
ansible_devel:https://docs.ansible.com/ansible-core/devel/
+ artifact-name: ${{ github.event.repository.name }}_validate_docs_${{ github.event.pull_request.head.sha }}
build-docs:
diff --git a/ansible_collections/amazon/aws/CHANGELOG.rst b/ansible_collections/amazon/aws/CHANGELOG.rst
index 3e5dc1c2c..219d962b4 100644
--- a/ansible_collections/amazon/aws/CHANGELOG.rst
+++ b/ansible_collections/amazon/aws/CHANGELOG.rst
@@ -4,6 +4,27 @@ amazon.aws Release Notes
.. contents:: Topics
+v7.5.0
+======
+
+Release Summary
+---------------
+
+This release includes a new feature for the ``iam_user_info`` module, bugfixes for the ``cloudwatchlogs_log_group_info`` and ``s3_object`` modules and the inventory plugins, and some internal refactoring of ``module_utils``.
+
+Minor Changes
+-------------
+
+- iam_user_info - Add ``login_profile`` to return info that is get from a user, to know if they can login from AWS console (https://github.com/ansible-collections/amazon.aws/pull/2012).
+- module_utils.iam - refactored normalization functions to use ``boto3_resource_to_ansible_dict()`` and ``boto3_resource_list_to_ansible_dict()`` (https://github.com/ansible-collections/amazon.aws/pull/2006).
+- module_utils.transformations - add ``boto3_resource_to_ansible_dict()`` and ``boto3_resource_list_to_ansible_dict()`` helpers (https://github.com/ansible-collections/amazon.aws/pull/2006).
+
+Bugfixes
+--------
+
+- cloudwatchlogs_log_group_info - Implement exponential backoff when making API calls to prevent throttling exceptions (https://github.com/ansible-collections/amazon.aws/issues/2011).
+- plugin_utils.inventory - Ensure templated options in lookup plugins are converted (https://github.com/ansible-collections/amazon.aws/issues/1955).
+- s3_object - Fix the issue when copying an object with overriding metadata. (https://github.com/ansible-collections/amazon.aws/issues/1991).
v7.4.0
======
@@ -232,7 +253,6 @@ Release Summary
This release is the last planned minor release of ``amazon.aws`` prior to the release of 7.0.0.
It includes documentation fixes as well as minor changes and bug fixes for the ``ec2_ami`` and ``elb_application_lb_info`` modules.
-
Minor Changes
-------------
@@ -564,7 +584,6 @@ Release Summary
This release brings few bugfixes.
-
Bugfixes
--------
@@ -585,7 +604,6 @@ Release Summary
This release contains a number of bugfixes, new features and new modules. This is the last planned minor release prior to the release of version 6.0.0.
-
Minor Changes
-------------
@@ -680,7 +698,6 @@ Release Summary
A minor release containing bugfixes for the ``ec2_eni_info`` module and the ``aws_rds`` inventory plugin, as well as improvements to the ``rds_instance`` module.
-
Minor Changes
-------------
@@ -904,7 +921,6 @@ Release Summary
This release contains a minor bugfix for the ``ec2_vol`` module, some minor work on the ``ec2_key`` module, and various documentation fixes. This is the last planned release of the 4.x series.
-
Minor Changes
-------------
@@ -944,7 +960,6 @@ The amazon.aws 4.3.0 release includes a number of minor bug fixes and improvemen
Following the release of amazon.aws 5.0.0, backports to the 4.x series will be limited to
security issues and bugfixes.
-
Minor Changes
-------------
@@ -1099,7 +1114,6 @@ Release Summary
Following the release of amazon.aws 5.0.0, 3.5.0 is a bugfix release and the final planned release for the 3.x series.
-
Minor Changes
-------------
diff --git a/ansible_collections/amazon/aws/FILES.json b/ansible_collections/amazon/aws/FILES.json
index 1f9947ab7..61352c6b6 100644
--- a/ansible_collections/amazon/aws/FILES.json
+++ b/ansible_collections/amazon/aws/FILES.json
@@ -123,7 +123,7 @@
"name": ".github/workflows/docs-pr.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "befbd2d31f5509f704e57c06b07c42fa7867dd353ab3d24856eb865cf8d44b00",
+ "chksum_sha256": "12f439dd44738a38b1468b670158eaf719539b91f0570d2b9a2f0565ef1de659",
"format": 1
},
{
@@ -221,7 +221,7 @@
"name": "changelogs/changelog.yaml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "8484f733ac3da28af9d97a0f85ecc2f3d601009ed6d55d02f8a3cac6dc32eea9",
+ "chksum_sha256": "fab623a9b576e9d450f5285c5ad77eed36f30882a396eeba97046f4b8fdbf3cd",
"format": 1
},
{
@@ -256,7 +256,7 @@
"name": "docs/docsite/rst/CHANGELOG.rst",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "b3a5af02bc807a9248c3820f9f07c6ce0fbf5f75f22613ae3e79a795d34165fc",
+ "chksum_sha256": "f60899f9e09f217d9c8963676ddad7d070ce9233e0f32c02b96ad1839ec3cd9f",
"format": 1
},
{
@@ -543,7 +543,7 @@
"name": "plugins/module_utils/common.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "c2056cf9ef583ee29ac1165aa03e5cf165830ed6d1703c275c4c9d154222f3c3",
+ "chksum_sha256": "cef7b396d560a646961755d2a54c7131e553dfe26fbb26e04be073cce5bb0095",
"format": 1
},
{
@@ -599,7 +599,7 @@
"name": "plugins/module_utils/iam.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "8aaa38e784250525c884b936370b2db5ff61b84fcd62a30c239a5e3dc8e20ca3",
+ "chksum_sha256": "1cd5d5532049e4afd2858a35480cd173f72c4ed9174c67bb26186e47fe183ba5",
"format": 1
},
{
@@ -662,7 +662,7 @@
"name": "plugins/module_utils/transformation.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "77e7b561643de0ed96b958af3ec6694edae360c8fb4e51ffa4144dace02063fe",
+ "chksum_sha256": "882308fe4ef2b74fcb41dd09b0e6575d47e2f3d6a3f2164d8dc1706cec213f0c",
"format": 1
},
{
@@ -851,7 +851,7 @@
"name": "plugins/modules/cloudwatchlogs_log_group_info.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "00a40c6593d6cc582205943f247a33d7621e4ffa49410c9476e6fefc4e800ccd",
+ "chksum_sha256": "03d947455d91f77a833dd705fc05d2882a62786e1e09968f7cc8b668964d56f6",
"format": 1
},
{
@@ -1243,7 +1243,7 @@
"name": "plugins/modules/iam_user_info.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "92d2d3f21e43a53f4e25ed497c76d2935ed533b8bdbf9aa5af41a4a8c27e2cb7",
+ "chksum_sha256": "11b3a159e2c6542b861c94c5d5f1eae8375f1e0e4ecef7ad413932254f6a3157",
"format": 1
},
{
@@ -1446,7 +1446,7 @@
"name": "plugins/modules/s3_object.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "36bf675910b9b5a9a932fc90c1ceb4f2c54d90a191bf9c2cb8a47cd5ebea032f",
+ "chksum_sha256": "47610c1c778b26def1d5c3aef8c5f6d2089537445ae420d1da5f1afb4a0b8600",
"format": 1
},
{
@@ -1495,7 +1495,7 @@
"name": "plugins/plugin_utils/inventory.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "1c652b1e2733fe92f7d95ea6f76bed638fbed402e7ffe15753e896328869f4b3",
+ "chksum_sha256": "2bbe05440920c867bd4b8935a0a97963211b74149ca6c83d9990f9b6a5e4e6ae",
"format": 1
},
{
@@ -2531,7 +2531,7 @@
"name": "tests/integration/targets/ec2_ami/tasks/main.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "9433c585f807cca76bb83c1cbe0a5d0a85966ead2ca30dde4cefd71be3cf155f",
+ "chksum_sha256": "919ee94ad149f90556ea20402e2393253157946b2d0e32a71957a4f58cfa6018",
"format": 1
},
{
@@ -4302,7 +4302,7 @@
"name": "tests/integration/targets/ec2_security_group/tasks/multi_nested_target.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "be781072f2d349ec5c93cbb05e40ef71846d2f4b5884ad297a736d3b0c72e542",
+ "chksum_sha256": "79aa1549464410bff8ff9f0272780d8b408a370d088100617656975024b6fef2",
"format": 1
},
{
@@ -6115,7 +6115,7 @@
"name": "tests/integration/targets/inventory_aws_ec2/playbooks/create_inventory_config.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "c031a3d0f6a2c17914bedc3a7b37c475cbd55420cb7ae7fe4c6f24855bb5565d",
+ "chksum_sha256": "44bab5285a394bde857e65e324b15039d248a20c9a05b5eec45eac2ce312550e",
"format": 1
},
{
@@ -6259,6 +6259,13 @@
"format": 1
},
{
+ "name": "tests/integration/targets/inventory_aws_ec2/templates/config.ini.j2",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "777e02f32a471e59d05fc66ef26716aaaa368085f991f179866d8f38a7d44aae",
+ "format": 1
+ },
+ {
"name": "tests/integration/targets/inventory_aws_ec2/templates/inventory.yml.j2",
"ftype": "file",
"chksum_type": "sha256",
@@ -6332,7 +6339,7 @@
"name": "tests/integration/targets/inventory_aws_ec2/templates/inventory_with_template.yml.j2",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "156031d22e1ae780ea2012e96afd68679bd27ee0cfe4bec4b03bf556f5477375",
+ "chksum_sha256": "a15b10d8b1dc85d869d6df0054d44d95b68d53d5cda6c9372dc86d72e260a00d",
"format": 1
},
{
@@ -9447,7 +9454,7 @@
"name": "tests/integration/targets/s3_object/tasks/copy_object.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "9b412470d4ef414efef1f407f84c72f1685f77c6ef2551517cdfa4cd6ab1515d",
+ "chksum_sha256": "a935fc78dc9973eac71decd7308d4e827c27a7f30b29c7c416f9f676163b4ec7",
"format": 1
},
{
@@ -9475,7 +9482,7 @@
"name": "tests/integration/targets/s3_object/tasks/main.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "c7568cf168c02a65bef0d921cea02304fd568fa6cd93175395bed359e6a14cf7",
+ "chksum_sha256": "697d66379fe4c4b33f82b1f7ac363f1e25331480201701b23d28494165346043",
"format": 1
},
{
@@ -9503,7 +9510,7 @@
"name": "tests/integration/targets/s3_object/aliases",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "da6542f9ffbf6dfd96214cc7e7c08e8bd4662a5479a21ad1b3f79ad2b163c9ad",
+ "chksum_sha256": "e217807ba499d974106cc2132230a411f7564e6c337b0a731a6301c5a1d69619",
"format": 1
},
{
@@ -10284,6 +10291,13 @@
"format": 1
},
{
+ "name": "tests/unit/module_utils/iam/test_iam_resource_transforms.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9ba95e45f5d2c2502a8ae6ea6dacc35e340f931a4d4d7fde2064ba0f89018ed0",
+ "format": 1
+ },
+ {
"name": "tests/unit/module_utils/iam/test_validate_iam_identifiers.py",
"ftype": "file",
"chksum_type": "sha256",
@@ -10452,6 +10466,13 @@
"format": 1
},
{
+ "name": "tests/unit/module_utils/transformation/test_boto3_resource_to_ansible_dict.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8b27e0ccb712e9bf43897b4226b99318c42e27f344f660326d13699bf1b45c45",
+ "format": 1
+ },
+ {
"name": "tests/unit/module_utils/transformation/test_map_complex_type.py",
"ftype": "file",
"chksum_type": "sha256",
@@ -10630,7 +10651,7 @@
"name": "tests/unit/plugin_utils/inventory/test_inventory_base.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "4668d08e6f30a2aae02c0b27174dc759f4ec554d7207bcf33f879e9dee67720e",
+ "chksum_sha256": "ebae64f9db5ec8444c35b5d41127f1a5191ed10cd34acb6c89d0e5de2879b33d",
"format": 1
},
{
@@ -10693,7 +10714,7 @@
"name": "tests/unit/plugins/inventory/test_aws_ec2.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "cbb0ce6de6b22c4d62588d230353de215a4ccb273838b4870c17da8548ad3f16",
+ "chksum_sha256": "8556d8258e0d0aaeadd02898d1198152c478fa23677ff51990a0e1420f99e482",
"format": 1
},
{
@@ -11659,7 +11680,7 @@
"name": "CHANGELOG.rst",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "b3a5af02bc807a9248c3820f9f07c6ce0fbf5f75f22613ae3e79a795d34165fc",
+ "chksum_sha256": "f60899f9e09f217d9c8963676ddad7d070ce9233e0f32c02b96ad1839ec3cd9f",
"format": 1
},
{
diff --git a/ansible_collections/amazon/aws/MANIFEST.json b/ansible_collections/amazon/aws/MANIFEST.json
index 3eb50b454..cd19577e3 100644
--- a/ansible_collections/amazon/aws/MANIFEST.json
+++ b/ansible_collections/amazon/aws/MANIFEST.json
@@ -2,7 +2,7 @@
"collection_info": {
"namespace": "amazon",
"name": "aws",
- "version": "7.4.0",
+ "version": "7.5.0",
"authors": [
"Ansible (https://github.com/ansible)"
],
@@ -25,7 +25,7 @@
"name": "FILES.json",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "524e7534581a787eb78ed1366e0d732b0673fcb3e4b5df5ffae1ca6c92c0ffe5",
+ "chksum_sha256": "34bdeb6686c662a0a524953b80ae953b67d328542ba5d43bc1e4058c45ed4136",
"format": 1
},
"format": 1
diff --git a/ansible_collections/amazon/aws/changelogs/changelog.yaml b/ansible_collections/amazon/aws/changelogs/changelog.yaml
index 24f7b8247..587c55a28 100644
--- a/ansible_collections/amazon/aws/changelogs/changelog.yaml
+++ b/ansible_collections/amazon/aws/changelogs/changelog.yaml
@@ -2692,7 +2692,7 @@ releases:
- iam_user_info - the ``path`` parameter has been renamed ``path_prefix`` for
consistency with other IAM modules, ``path`` remains as an alias. No change
to playbooks is required (https://github.com/ansible-collections/amazon.aws/pull/1933).
- release_summary: This release includes new features and a bugfix.
+ release_summary: This release includes new features and a bugfix.
fragments:
- 1918-ec2_instance-add-support-to-modify-metadata-options.yml
- 20231206-iam_user_path.yml
@@ -2777,3 +2777,28 @@ releases:
- 20240227-iam-refactor.yml
- release_summary.yml
release_date: '2024-03-05'
+ 7.5.0:
+ changes:
+ bugfixes:
+ - cloudwatchlogs_log_group_info - Implement exponential backoff when making
+ API calls to prevent throttling exceptions (https://github.com/ansible-collections/amazon.aws/issues/2011).
+ - plugin_utils.inventory - Ensure templated options in lookup plugins are converted
+ (https://github.com/ansible-collections/amazon.aws/issues/1955).
+ - s3_object - Fix the issue when copying an object with overriding metadata.
+ (https://github.com/ansible-collections/amazon.aws/issues/1991).
+ minor_changes:
+ - iam_user_info - Add ``login_profile`` to return info that is get from a user,
+ to know if they can login from AWS console (https://github.com/ansible-collections/amazon.aws/pull/2012).
+ - module_utils.iam - refactored normalization functions to use ``boto3_resource_to_ansible_dict()``
+ and ``boto3_resource_list_to_ansible_dict()`` (https://github.com/ansible-collections/amazon.aws/pull/2006).
+ - module_utils.transformations - add ``boto3_resource_to_ansible_dict()`` and
+ ``boto3_resource_list_to_ansible_dict()`` helpers (https://github.com/ansible-collections/amazon.aws/pull/2006).
+ release_summary: This release includes a new feature for the ``iam_user_info``
+ module, bugfixes for the ``cloudwatchlogs_log_group_info`` and ``s3_object``
+ modules and the inventory plugins, and some internal refactoring of ``module_utils``.
+ fragments:
+ - 2006-normalize-refactor.yml
+ - 20240314-cloudwatchlogs_log_group_info-fix-throttling-exceptions.yml
+ - 20240314-s3_object-copy-mode-with-metadata.yml
+ - 20240321-iam-user-info.yml
+ release_date: '2024-04-03'
diff --git a/ansible_collections/amazon/aws/docs/docsite/rst/CHANGELOG.rst b/ansible_collections/amazon/aws/docs/docsite/rst/CHANGELOG.rst
index 3e5dc1c2c..219d962b4 100644
--- a/ansible_collections/amazon/aws/docs/docsite/rst/CHANGELOG.rst
+++ b/ansible_collections/amazon/aws/docs/docsite/rst/CHANGELOG.rst
@@ -4,6 +4,27 @@ amazon.aws Release Notes
.. contents:: Topics
+v7.5.0
+======
+
+Release Summary
+---------------
+
+This release includes a new feature for the ``iam_user_info`` module, bugfixes for the ``cloudwatchlogs_log_group_info`` and ``s3_object`` modules and the inventory plugins, and some internal refactoring of ``module_utils``.
+
+Minor Changes
+-------------
+
+- iam_user_info - Add ``login_profile`` to return info that is get from a user, to know if they can login from AWS console (https://github.com/ansible-collections/amazon.aws/pull/2012).
+- module_utils.iam - refactored normalization functions to use ``boto3_resource_to_ansible_dict()`` and ``boto3_resource_list_to_ansible_dict()`` (https://github.com/ansible-collections/amazon.aws/pull/2006).
+- module_utils.transformations - add ``boto3_resource_to_ansible_dict()`` and ``boto3_resource_list_to_ansible_dict()`` helpers (https://github.com/ansible-collections/amazon.aws/pull/2006).
+
+Bugfixes
+--------
+
+- cloudwatchlogs_log_group_info - Implement exponential backoff when making API calls to prevent throttling exceptions (https://github.com/ansible-collections/amazon.aws/issues/2011).
+- plugin_utils.inventory - Ensure templated options in lookup plugins are converted (https://github.com/ansible-collections/amazon.aws/issues/1955).
+- s3_object - Fix the issue when copying an object with overriding metadata. (https://github.com/ansible-collections/amazon.aws/issues/1991).
v7.4.0
======
@@ -232,7 +253,6 @@ Release Summary
This release is the last planned minor release of ``amazon.aws`` prior to the release of 7.0.0.
It includes documentation fixes as well as minor changes and bug fixes for the ``ec2_ami`` and ``elb_application_lb_info`` modules.
-
Minor Changes
-------------
@@ -564,7 +584,6 @@ Release Summary
This release brings few bugfixes.
-
Bugfixes
--------
@@ -585,7 +604,6 @@ Release Summary
This release contains a number of bugfixes, new features and new modules. This is the last planned minor release prior to the release of version 6.0.0.
-
Minor Changes
-------------
@@ -680,7 +698,6 @@ Release Summary
A minor release containing bugfixes for the ``ec2_eni_info`` module and the ``aws_rds`` inventory plugin, as well as improvements to the ``rds_instance`` module.
-
Minor Changes
-------------
@@ -904,7 +921,6 @@ Release Summary
This release contains a minor bugfix for the ``ec2_vol`` module, some minor work on the ``ec2_key`` module, and various documentation fixes. This is the last planned release of the 4.x series.
-
Minor Changes
-------------
@@ -944,7 +960,6 @@ The amazon.aws 4.3.0 release includes a number of minor bug fixes and improvemen
Following the release of amazon.aws 5.0.0, backports to the 4.x series will be limited to
security issues and bugfixes.
-
Minor Changes
-------------
@@ -1099,7 +1114,6 @@ Release Summary
Following the release of amazon.aws 5.0.0, 3.5.0 is a bugfix release and the final planned release for the 3.x series.
-
Minor Changes
-------------
diff --git a/ansible_collections/amazon/aws/plugins/module_utils/common.py b/ansible_collections/amazon/aws/plugins/module_utils/common.py
index 673915725..41ba80231 100644
--- a/ansible_collections/amazon/aws/plugins/module_utils/common.py
+++ b/ansible_collections/amazon/aws/plugins/module_utils/common.py
@@ -4,7 +4,7 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
AMAZON_AWS_COLLECTION_NAME = "amazon.aws"
-AMAZON_AWS_COLLECTION_VERSION = "7.4.0"
+AMAZON_AWS_COLLECTION_VERSION = "7.5.0"
_collection_info_context = {
diff --git a/ansible_collections/amazon/aws/plugins/module_utils/iam.py b/ansible_collections/amazon/aws/plugins/module_utils/iam.py
index 430823f3b..56920d53e 100644
--- a/ansible_collections/amazon/aws/plugins/module_utils/iam.py
+++ b/ansible_collections/amazon/aws/plugins/module_utils/iam.py
@@ -4,7 +4,6 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
import re
-from copy import deepcopy
try:
import botocore
@@ -12,17 +11,20 @@ except ImportError:
pass # Modules are responsible for handling this.
from ansible.module_utils._text import to_native
-from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict
from .arn import parse_aws_arn
from .arn import validate_aws_arn
from .botocore import is_boto3_error_code
-from .botocore import normalize_boto3_result
from .errors import AWSErrorHandler
from .exceptions import AnsibleAWSError
from .retries import AWSRetry
from .tagging import ansible_dict_to_boto3_tag_list
-from .tagging import boto3_tag_list_to_ansible_dict
+from .transformation import AnsibleAWSResource
+from .transformation import AnsibleAWSResourceList
+from .transformation import BotoResource
+from .transformation import BotoResourceList
+from .transformation import boto3_resource_list_to_ansible_dict
+from .transformation import boto3_resource_to_ansible_dict
class AnsibleIAMError(AnsibleAWSError):
@@ -198,66 +200,6 @@ def get_iam_managed_policy_version(client, arn, version):
return client.get_policy_version(PolicyArn=arn, VersionId=version)["PolicyVersion"]
-def normalize_iam_mfa_device(device):
- """Converts IAM MFA Device from the CamelCase boto3 format to the snake_case Ansible format"""
- if not device:
- return device
- camel_device = camel_dict_to_snake_dict(device)
- camel_device["tags"] = boto3_tag_list_to_ansible_dict(device.pop("Tags", []))
- return camel_device
-
-
-def normalize_iam_mfa_devices(devices):
- """Converts a list of IAM MFA Devices from the CamelCase boto3 format to the snake_case Ansible format"""
- if not devices:
- return []
- devices = [normalize_iam_mfa_device(d) for d in devices]
- return devices
-
-
-def normalize_iam_user(user):
- """Converts IAM users from the CamelCase boto3 format to the snake_case Ansible format"""
- if not user:
- return user
- camel_user = camel_dict_to_snake_dict(user)
- camel_user["tags"] = boto3_tag_list_to_ansible_dict(user.pop("Tags", []))
- return camel_user
-
-
-def normalize_iam_policy(policy):
- """Converts IAM policies from the CamelCase boto3 format to the snake_case Ansible format"""
- if not policy:
- return policy
- camel_policy = camel_dict_to_snake_dict(policy)
- camel_policy["tags"] = boto3_tag_list_to_ansible_dict(policy.get("Tags", []))
- return camel_policy
-
-
-def normalize_iam_group(group):
- """Converts IAM Groups from the CamelCase boto3 format to the snake_case Ansible format"""
- if not group:
- return group
- camel_group = camel_dict_to_snake_dict(normalize_boto3_result(group))
- return camel_group
-
-
-def normalize_iam_access_key(access_key):
- """Converts IAM access keys from the CamelCase boto3 format to the snake_case Ansible format"""
- if not access_key:
- return access_key
- camel_key = camel_dict_to_snake_dict(normalize_boto3_result(access_key))
- return camel_key
-
-
-def normalize_iam_access_keys(access_keys):
- """Converts a list of IAM access keys from the CamelCase boto3 format to the snake_case Ansible format"""
- if not access_keys:
- return []
- access_keys = [normalize_iam_access_key(k) for k in access_keys]
- sorted_keys = sorted(access_keys, key=lambda d: d.get("create_date", None))
- return sorted_keys
-
-
def convert_managed_policy_names_to_arns(client, policy_names):
if all(validate_aws_arn(policy, service="iam") for policy in policy_names if policy is not None):
return policy_names
@@ -386,47 +328,6 @@ def list_iam_instance_profiles(client, name=None, prefix=None, role=None):
return _list_iam_instance_profiles(client)
-def normalize_iam_instance_profile(profile, _v7_compat=False):
- """
- Converts a boto3 format IAM instance profile into "Ansible" format
-
- _v7_compat is deprecated and will be removed in release after 2025-05-01 DO NOT USE.
- """
-
- new_profile = camel_dict_to_snake_dict(deepcopy(profile))
- if profile.get("Roles"):
- new_profile["roles"] = [normalize_iam_role(role, _v7_compat=_v7_compat) for role in profile.get("Roles")]
- if profile.get("Tags"):
- new_profile["tags"] = boto3_tag_list_to_ansible_dict(profile.get("Tags"))
- else:
- new_profile["tags"] = {}
- new_profile["original"] = profile
- return new_profile
-
-
-def normalize_iam_role(role, _v7_compat=False):
- """
- Converts a boto3 format IAM instance role into "Ansible" format
-
- _v7_compat is deprecated and will be removed in release after 2025-05-01 DO NOT USE.
- """
-
- new_role = camel_dict_to_snake_dict(deepcopy(role))
- if role.get("InstanceProfiles"):
- new_role["instance_profiles"] = [
- normalize_iam_instance_profile(profile, _v7_compat=_v7_compat) for profile in role.get("InstanceProfiles")
- ]
- if role.get("AssumeRolePolicyDocument"):
- if _v7_compat:
- # new_role["assume_role_policy_document"] = role.get("AssumeRolePolicyDocument")
- new_role["assume_role_policy_document_raw"] = role.get("AssumeRolePolicyDocument")
- else:
- new_role["assume_role_policy_document"] = role.get("AssumeRolePolicyDocument")
-
- new_role["tags"] = boto3_tag_list_to_ansible_dict(role.get("Tags", []))
- return new_role
-
-
@IAMErrorHandler.common_error_handler("tag instance profile")
@AWSRetry.jittered_backoff()
def tag_iam_instance_profile(client, name, tags):
@@ -497,3 +398,83 @@ def validate_iam_identifiers(resource_type, name=None, path=None):
return path_problem
return None
+
+
+def normalize_iam_mfa_device(device: BotoResource) -> AnsibleAWSResource:
+ """Converts IAM MFA Device from the CamelCase boto3 format to the snake_case Ansible format"""
+ # MFA Devices don't support Tags (as of 1.34.52)
+ return boto3_resource_to_ansible_dict(device)
+
+
+def normalize_iam_mfa_devices(devices: BotoResourceList) -> AnsibleAWSResourceList:
+ """Converts a list of IAM MFA Devices from the CamelCase boto3 format to the snake_case Ansible format"""
+ # MFA Devices don't support Tags (as of 1.34.52)
+ return boto3_resource_list_to_ansible_dict(devices)
+
+
+def normalize_iam_user(user: BotoResource) -> AnsibleAWSResource:
+ """Converts IAM users from the CamelCase boto3 format to the snake_case Ansible format"""
+ return boto3_resource_to_ansible_dict(user)
+
+
+def normalize_iam_policy(policy: BotoResource) -> AnsibleAWSResource:
+ """Converts IAM policies from the CamelCase boto3 format to the snake_case Ansible format"""
+ return boto3_resource_to_ansible_dict(policy)
+
+
+def normalize_iam_group(group: BotoResource) -> AnsibleAWSResource:
+ """Converts IAM Groups from the CamelCase boto3 format to the snake_case Ansible format"""
+ # Groups don't support Tags (as of 1.34.52)
+ return boto3_resource_to_ansible_dict(group, force_tags=False)
+
+
+def normalize_iam_access_key(access_key: BotoResource) -> AnsibleAWSResource:
+ """Converts IAM access keys from the CamelCase boto3 format to the snake_case Ansible format"""
+ # Access Keys don't support Tags (as of 1.34.52)
+ return boto3_resource_to_ansible_dict(access_key, force_tags=False)
+
+
+def normalize_iam_access_keys(access_keys: BotoResourceList) -> AnsibleAWSResourceList:
+ """Converts a list of IAM access keys from the CamelCase boto3 format to the snake_case Ansible format"""
+ # Access Keys don't support Tags (as of 1.34.52)
+ if not access_keys:
+ return access_keys
+ access_keys = boto3_resource_list_to_ansible_dict(access_keys, force_tags=False)
+ return sorted(access_keys, key=lambda d: d.get("create_date", None))
+
+
+def normalize_iam_instance_profile(profile: BotoResource) -> AnsibleAWSResource:
+ """
+ Converts a boto3 format IAM instance profile into "Ansible" format
+
+ _v7_compat is deprecated and will be removed in release after 2025-05-01 DO NOT USE.
+ """
+ transforms = {"Roles": _normalize_iam_roles}
+ transformed_profile = boto3_resource_to_ansible_dict(profile, nested_transforms=transforms)
+ return transformed_profile
+
+
+def normalize_iam_role(role: BotoResource, _v7_compat: bool = False) -> AnsibleAWSResource:
+ """
+ Converts a boto3 format IAM instance role into "Ansible" format
+
+ _v7_compat is deprecated and will be removed in release after 2025-05-01 DO NOT USE.
+ """
+ transforms = {"InstanceProfiles": _normalize_iam_instance_profiles}
+ ignore_list = [] if _v7_compat else ["AssumeRolePolicyDocument"]
+ transformed_role = boto3_resource_to_ansible_dict(role, nested_transforms=transforms, ignore_list=ignore_list)
+ if _v7_compat and role.get("AssumeRolePolicyDocument"):
+ transformed_role["assume_role_policy_document_raw"] = role["AssumeRolePolicyDocument"]
+ return transformed_role
+
+
+def _normalize_iam_instance_profiles(profiles: BotoResourceList) -> AnsibleAWSResourceList:
+ if not profiles:
+ return profiles
+ return [normalize_iam_instance_profile(p) for p in profiles]
+
+
+def _normalize_iam_roles(roles: BotoResourceList) -> AnsibleAWSResourceList:
+ if not roles:
+ return roles
+ return [normalize_iam_role(r) for r in roles]
diff --git a/ansible_collections/amazon/aws/plugins/module_utils/transformation.py b/ansible_collections/amazon/aws/plugins/module_utils/transformation.py
index 708736fc0..a5bc23607 100644
--- a/ansible_collections/amazon/aws/plugins/module_utils/transformation.py
+++ b/ansible_collections/amazon/aws/plugins/module_utils/transformation.py
@@ -28,9 +28,26 @@
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+from copy import deepcopy
+from typing import Any
+from typing import Callable
+from typing import Mapping
+from typing import Optional
+from typing import Sequence
+from typing import Union
+
+from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict
from ansible.module_utils.six import integer_types
from ansible.module_utils.six import string_types
+from .botocore import normalize_boto3_result
+from .tagging import boto3_tag_list_to_ansible_dict
+
+BotoResource = Union[None, Mapping[str, Any]]
+BotoResourceList = Union[None, Sequence[Mapping[str, Any]]]
+AnsibleAWSResource = Union[None, Mapping[str, Any]]
+AnsibleAWSResourceList = Union[None, Sequence[Mapping[str, Any]]]
+
def ansible_dict_to_boto3_filter_list(filters_dict):
"""Convert an Ansible dict of filters to list of dicts that boto3 can use
@@ -133,3 +150,82 @@ def scrub_none_parameters(parameters, descend_into_lists=True):
clean_parameters[k] = v
return clean_parameters
+
+
+def _perform_nested_transforms(
+ resource: Mapping[str, Any],
+ nested_transforms: Optional[Mapping[str, Callable]],
+) -> Mapping[str, Any]:
+ if not nested_transforms:
+ return resource
+
+ for k, transform in nested_transforms.items():
+ if k in resource:
+ resource[k] = transform(resource[k])
+
+ return resource
+
+
+def boto3_resource_to_ansible_dict(
+ resource: BotoResource,
+ transform_tags: bool = True,
+ force_tags: bool = True,
+ normalize: bool = True,
+ ignore_list: Optional[Sequence[str]] = None,
+ nested_transforms: Optional[Mapping[str, Callable]] = None,
+) -> AnsibleAWSResource:
+ """
+ Transforms boto3-style (CamelCase) resource to the ansible-style (snake_case).
+
+ :param resource: a dictionary representing the resource
+ :param transform_tags: whether or not to perform "tag list" to "dictionary" conversion on the "Tags" key
+ :param normalize: whether resources should be passed through .botocore.normalize_boto3_result
+ :param ignore_list: a list of keys, the contents of which should not be transformed
+ :param nested_transforms: a mapping of keys to Callable, the Callable will only be passed the value for the key
+ in the resource dictionary
+ :return: dictionary representing the transformed resource
+ """
+ if not resource:
+ return resource
+ ignore_list = ignore_list or []
+ nested_transforms = nested_transforms or {}
+
+ transformed_resource = deepcopy(resource)
+ if normalize:
+ transformed_resource = normalize_boto3_result(transformed_resource)
+ transformed_resource = _perform_nested_transforms(transformed_resource, nested_transforms)
+ ignore_list = [*ignore_list, *nested_transforms]
+ camel_resource = camel_dict_to_snake_dict(transformed_resource, ignore_list=ignore_list)
+ if transform_tags and "Tags" in resource:
+ camel_resource["tags"] = boto3_tag_list_to_ansible_dict(resource["Tags"])
+ if force_tags and "Tags" not in resource:
+ camel_resource["tags"] = {}
+
+ return camel_resource
+
+
+def boto3_resource_list_to_ansible_dict(
+ resource_list: BotoResourceList,
+ transform_tags: bool = True,
+ force_tags: bool = True,
+ normalize: bool = True,
+ ignore_list: Optional[Sequence[str]] = None,
+ nested_transforms: Optional[Mapping[str, Callable]] = None,
+) -> AnsibleAWSResourceList:
+ """
+ Transforms a list of boto3-style (CamelCase) resources to the ansible-style (snake_case).
+
+ :param resource_list: a list of dictionaries representing the resources
+ :param transform_tags: whether or not to perform "tag list" to "dictionary" conversion on the "Tags" key
+ :param normalize: whether resources should be passed through .botocore.normalize_boto3_result()
+ :param ignore_list: a list of keys, the contents of which should not be transformed
+ :param nested_transforms: a mapping of keys to Callable, the Callable will only be passed the value for the key
+ in the resource dictionary
+ :return: list of dictionaries representing the transformed resources
+ """
+ if not resource_list:
+ return resource_list
+ return [
+ boto3_resource_to_ansible_dict(resource, transform_tags, force_tags, normalize, ignore_list, nested_transforms)
+ for resource in resource_list
+ ]
diff --git a/ansible_collections/amazon/aws/plugins/modules/cloudwatchlogs_log_group_info.py b/ansible_collections/amazon/aws/plugins/modules/cloudwatchlogs_log_group_info.py
index 0cfe22e22..453d268d5 100644
--- a/ansible_collections/amazon/aws/plugins/modules/cloudwatchlogs_log_group_info.py
+++ b/ansible_collections/amazon/aws/plugins/modules/cloudwatchlogs_log_group_info.py
@@ -82,6 +82,18 @@ from ansible.module_utils.common.dict_transformations import camel_dict_to_snake
from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code
from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule
+from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry
+
+
+@AWSRetry.exponential_backoff()
+def list_tags_log_group_with_backoff(client, log_group_name):
+ return client.list_tags_log_group(logGroupName=log_group_name)
+
+
+@AWSRetry.exponential_backoff()
+def describe_log_groups_with_backoff(client, **kwargs):
+ paginator = client.get_paginator("describe_log_groups")
+ return paginator.paginate(**kwargs).build_full_result()
def describe_log_group(client, log_group_name, module):
@@ -89,15 +101,14 @@ def describe_log_group(client, log_group_name, module):
if log_group_name:
params["logGroupNamePrefix"] = log_group_name
try:
- paginator = client.get_paginator("describe_log_groups")
- desc_log_group = paginator.paginate(**params).build_full_result()
+ desc_log_group = describe_log_groups_with_backoff(client, **params)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg=f"Unable to describe log group {log_group_name}")
for log_group in desc_log_group["logGroups"]:
log_group_name = log_group["logGroupName"]
try:
- tags = client.list_tags_log_group(logGroupName=log_group_name)
+ tags = list_tags_log_group_with_backoff(client, log_group_name)
except is_boto3_error_code("AccessDeniedException"):
tags = {}
module.warn(f"Permission denied listing tags for log group {log_group_name}")
diff --git a/ansible_collections/amazon/aws/plugins/modules/iam_user_info.py b/ansible_collections/amazon/aws/plugins/modules/iam_user_info.py
index 259d26803..2ddbe1d5a 100644
--- a/ansible_collections/amazon/aws/plugins/modules/iam_user_info.py
+++ b/ansible_collections/amazon/aws/plugins/modules/iam_user_info.py
@@ -103,14 +103,27 @@ iam_users:
type: dict
returned: if user exists
sample: '{"Env": "Prod"}'
+ login_profile:
+ description: Detailed login profile information if the user has access to log in from AWS default console. Returns an empty object {} if no access.
+ returned: always
+ type: dict
+ sample: {"create_date": "2024-03-20T12:50:56+00:00", "password_reset_required": false, "user_name": "i_am_a_user"}
"""
from ansible_collections.amazon.aws.plugins.module_utils.iam import AnsibleIAMError
+from ansible_collections.amazon.aws.plugins.module_utils.iam import IAMErrorHandler
from ansible_collections.amazon.aws.plugins.module_utils.iam import get_iam_group
from ansible_collections.amazon.aws.plugins.module_utils.iam import get_iam_user
from ansible_collections.amazon.aws.plugins.module_utils.iam import list_iam_users
from ansible_collections.amazon.aws.plugins.module_utils.iam import normalize_iam_user
from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule
+from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry
+
+
+@IAMErrorHandler.list_error_handler("get login profile", {})
+@AWSRetry.jittered_backoff()
+def check_console_access(connection, user_name):
+ return connection.get_login_profile(UserName=user_name)["LoginProfile"]
def _list_users(connection, name, group, path):
@@ -136,6 +149,8 @@ def _list_users(connection, name, group, path):
def list_users(connection, name, group, path):
users = _list_users(connection, name, group, path)
users = [u for u in users if u is not None]
+ for user in users:
+ user["LoginProfile"] = check_console_access(connection, user["UserName"])
return [normalize_iam_user(user) for user in users]
@@ -147,7 +162,9 @@ def main():
)
module = AnsibleAWSModule(
- argument_spec=argument_spec, mutually_exclusive=[["group", "path_prefix"]], supports_check_mode=True
+ argument_spec=argument_spec,
+ mutually_exclusive=[["group", "path_prefix"]],
+ supports_check_mode=True,
)
name = module.params.get("name")
diff --git a/ansible_collections/amazon/aws/plugins/modules/s3_object.py b/ansible_collections/amazon/aws/plugins/modules/s3_object.py
index 2c4ebe9c3..2cd897c89 100644
--- a/ansible_collections/amazon/aws/plugins/modules/s3_object.py
+++ b/ansible_collections/amazon/aws/plugins/modules/s3_object.py
@@ -315,7 +315,9 @@ EXAMPLES = r"""
object: /my/desired/key.txt
src: /usr/local/myfile.txt
mode: put
- metadata: 'Content-Encoding=gzip,Cache-Control=no-cache'
+ metadata:
+ Content-Encoding: gzip
+ Cache-Control: no-cache
- name: PUT/upload with custom headers
amazon.aws.s3_object:
@@ -1314,6 +1316,11 @@ def copy_object_to_bucket(module, s3, bucket, obj, encrypt, metadata, validate,
metadata,
)
)
+ if metadata:
+ # 'MetadataDirective' Specifies whether the metadata is copied from the source object or replaced
+ # with metadata that's provided in the request. The default value is 'COPY', therefore when user
+ # specifies a metadata we should set it to 'REPLACE'
+ params.update({"MetadataDirective": "REPLACE"})
s3.copy_object(aws_retry=True, **params)
put_object_acl(module, s3, bucket, obj)
# Tags
diff --git a/ansible_collections/amazon/aws/plugins/plugin_utils/inventory.py b/ansible_collections/amazon/aws/plugins/plugin_utils/inventory.py
index 144f77a7a..b0e47f7ef 100644
--- a/ansible_collections/amazon/aws/plugins/plugin_utils/inventory.py
+++ b/ansible_collections/amazon/aws/plugins/plugin_utils/inventory.py
@@ -33,7 +33,10 @@ class AWSInventoryBase(BaseInventoryPlugin, Constructable, Cacheable, AWSPluginB
"secret_key",
"session_token",
"profile",
- "iam_role_name",
+ "endpoint_url",
+ "assume_role_arn",
+ "region",
+ "regions",
)
def __init__(self, templar, options):
@@ -48,20 +51,21 @@ class AWSInventoryBase(BaseInventoryPlugin, Constructable, Cacheable, AWSPluginB
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 @@ class AWSInventoryBase(BaseInventoryPlugin, Constructable, Cacheable, AWSPluginB
}
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 @@ class AWSInventoryBase(BaseInventoryPlugin, Constructable, Cacheable, AWSPluginB
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 @@ class AWSInventoryBase(BaseInventoryPlugin, Constructable, Cacheable, AWSPluginB
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:
diff --git a/ansible_collections/amazon/aws/tests/integration/targets/ec2_ami/tasks/main.yml b/ansible_collections/amazon/aws/tests/integration/targets/ec2_ami/tasks/main.yml
index a9289b3c1..267e52abb 100644
--- a/ansible_collections/amazon/aws/tests/integration/targets/ec2_ami/tasks/main.yml
+++ b/ansible_collections/amazon/aws/tests/integration/targets/ec2_ami/tasks/main.yml
@@ -708,8 +708,8 @@
tags:
Name: "{{ ec2_ami_name }}_permissions"
launch_permissions:
- org_arns: [arn:aws:organizations::123456789012:organization/o-123ab4cdef]
- org_unit_arns: [arn:aws:organizations::123456789012:ou/o-123example/ou-1234-5exampld]
+ org_arns: ["arn:aws:organizations::123456789012:organization/o-123ab4cdef"]
+ org_unit_arns: ["arn:aws:organizations::123456789012:ou/o-123example/ou-1234-5exampld"]
register: permissions_update_result
- name: Get ami info
diff --git a/ansible_collections/amazon/aws/tests/integration/targets/ec2_security_group/tasks/multi_nested_target.yml b/ansible_collections/amazon/aws/tests/integration/targets/ec2_security_group/tasks/multi_nested_target.yml
index dcb7ac7bb..02057003a 100644
--- a/ansible_collections/amazon/aws/tests/integration/targets/ec2_security_group/tasks/multi_nested_target.yml
+++ b/ansible_collections/amazon/aws/tests/integration/targets/ec2_security_group/tasks/multi_nested_target.yml
@@ -12,7 +12,7 @@
to_port: 8182
cidr_ipv6:
- 64:ff9b::/96
- - [2620::/32]
+ - ["2620::/32"]
- proto: tcp
ports: 5665
cidr_ip:
@@ -38,7 +38,7 @@
to_port: 8182
cidr_ipv6:
- 64:ff9b::/96
- - [2620::/32]
+ - ["2620::/32"]
- proto: tcp
ports: 5665
cidr_ip:
@@ -66,7 +66,7 @@
to_port: 8182
cidr_ipv6:
- 64:ff9b::/96
- - [2620::/32]
+ - ["2620::/32"]
- proto: tcp
ports: 5665
cidr_ip:
@@ -92,7 +92,7 @@
to_port: 8182
cidr_ipv6:
- 64:ff9b::/96
- - [2620::/32]
+ - ["2620::/32"]
- proto: tcp
ports: 5665
cidr_ip:
@@ -117,7 +117,7 @@
to_port: 8182
cidr_ipv6:
- 64:ff9b::/96
- - [2620::/32]
+ - ["2620::/32"]
- proto: tcp
ports: 5665
cidr_ip:
@@ -142,7 +142,7 @@
to_port: 8182
cidr_ipv6:
- 64:ff9b::/96
- - [2620::/32]
+ - ["2620::/32"]
- proto: tcp
ports: 5665
cidr_ip:
@@ -167,7 +167,7 @@
from_port: 8182
to_port: 8182
cidr_ipv6:
- - [2620::/32, 64:ff9b::/96]
+ - ["2620::/32", "64:ff9b::/96"]
- proto: tcp
ports: 5665
cidr_ip:
@@ -190,8 +190,8 @@
from_port: 8182
to_port: 8182
cidr_ipv6:
- - [2620::/32, 64:ff9b::/96]
- - [2001:DB8:A0B:12F0::1/64]
+ - ["2620::/32", "64:ff9b::/96"]
+ - ["2001:DB8:A0B:12F0::1/64"]
- proto: tcp
ports: 5665
cidr_ip:
diff --git a/ansible_collections/amazon/aws/tests/integration/targets/inventory_aws_ec2/playbooks/create_inventory_config.yml b/ansible_collections/amazon/aws/tests/integration/targets/inventory_aws_ec2/playbooks/create_inventory_config.yml
index 232911d24..282ca43ee 100644
--- a/ansible_collections/amazon/aws/tests/integration/targets/inventory_aws_ec2/playbooks/create_inventory_config.yml
+++ b/ansible_collections/amazon/aws/tests/integration/targets/inventory_aws_ec2/playbooks/create_inventory_config.yml
@@ -9,3 +9,8 @@
ansible.builtin.copy:
dest: ../test.aws_ec2.yml
content: "{{ lookup('template', template_name) }}"
+
+ - name: write ini configuration
+ ansible.builtin.copy:
+ dest: ../config.ini
+ content: "{{ lookup('template', '../templates/config.ini.j2') }}"
diff --git a/ansible_collections/amazon/aws/tests/integration/targets/inventory_aws_ec2/templates/config.ini.j2 b/ansible_collections/amazon/aws/tests/integration/targets/inventory_aws_ec2/templates/config.ini.j2
new file mode 100644
index 000000000..f7320a7fb
--- /dev/null
+++ b/ansible_collections/amazon/aws/tests/integration/targets/inventory_aws_ec2/templates/config.ini.j2
@@ -0,0 +1,3 @@
+[ansible-test]
+
+region = {{ aws_region }} \ No newline at end of file
diff --git a/ansible_collections/amazon/aws/tests/integration/targets/inventory_aws_ec2/templates/inventory_with_template.yml.j2 b/ansible_collections/amazon/aws/tests/integration/targets/inventory_aws_ec2/templates/inventory_with_template.yml.j2
index 44a132c1c..dee7422a9 100644
--- a/ansible_collections/amazon/aws/tests/integration/targets/inventory_aws_ec2/templates/inventory_with_template.yml.j2
+++ b/ansible_collections/amazon/aws/tests/integration/targets/inventory_aws_ec2/templates/inventory_with_template.yml.j2
@@ -5,7 +5,7 @@ secret_key: '{{ aws_secret_key }}'
session_token: '{{ security_token }}'
{% endif %}
regions:
-- '{{ aws_region }}'
+- '{{ '{{ lookup("ansible.builtin.ini", "region", section="ansible-test", file="config.ini") }}' }}'
filters:
tag:Name:
- '{{ resource_prefix }}'
diff --git a/ansible_collections/amazon/aws/tests/integration/targets/s3_object/aliases b/ansible_collections/amazon/aws/tests/integration/targets/s3_object/aliases
index d34fac48d..2a1c5ccb6 100644
--- a/ansible_collections/amazon/aws/tests/integration/targets/s3_object/aliases
+++ b/ansible_collections/amazon/aws/tests/integration/targets/s3_object/aliases
@@ -1,3 +1,4 @@
cloud/aws
aws_s3
s3_object_info
+time=12m
diff --git a/ansible_collections/amazon/aws/tests/integration/targets/s3_object/tasks/copy_object.yml b/ansible_collections/amazon/aws/tests/integration/targets/s3_object/tasks/copy_object.yml
index 9ae36b952..994733d81 100644
--- a/ansible_collections/amazon/aws/tests/integration/targets/s3_object/tasks/copy_object.yml
+++ b/ansible_collections/amazon/aws/tests/integration/targets/s3_object/tasks/copy_object.yml
@@ -1,5 +1,12 @@
---
-- block:
+- vars:
+ withmeta_data:
+ something: exists
+ version: "1.0.2"
+ metacopy_data:
+ name: metacopy
+ version: "1.0.3"
+ block:
- name: define bucket name used for tests
ansible.builtin.set_fact:
copy_bucket:
@@ -142,6 +149,68 @@
- result is not changed
- result.msg == "Key this_key_does_not_exist.txt does not exist in bucket "+copy_bucket.src+"."
+ # Copy with metadata
+ - name: Set fact for bucket name
+ ansible.builtin.set_fact:
+ bucket_name: "{{ copy_bucket.dst }}"
+
+ - name: Create test bucket
+ amazon.aws.s3_bucket:
+ name: "{{ bucket_name }}"
+ state: present
+
+ - name: Create test object
+ amazon.aws.s3_object:
+ bucket: "{{ bucket_name }}"
+ object: nometa
+ mode: put
+ content: "some content"
+
+ - name: Copy and add metadata
+ amazon.aws.s3_object:
+ bucket: "{{ bucket_name }}"
+ object: metacopy
+ mode: copy
+ copy_src:
+ bucket: "{{ bucket_name }}"
+ object: nometa
+ metadata: "{{ metacopy_data }}"
+
+ - name: Create test object with metadata
+ amazon.aws.s3_object:
+ bucket: "{{ bucket_name }}"
+ object: withmeta
+ mode: put
+ content: "another content"
+ metadata: "{{ withmeta_data }}"
+
+ - name: Copy and preserve metadata
+ amazon.aws.s3_object:
+ bucket: "{{ bucket_name }}"
+ object: copywithmeta
+ mode: copy
+ copy_src:
+ bucket: "{{ bucket_name }}"
+ object: withmeta
+
+ - name: Get objects info
+ amazon.aws.s3_object_info:
+ bucket_name: "{{ bucket_name }}"
+ object_name: "{{ item }}"
+ loop:
+ - nometa
+ - metacopy
+ - withmeta
+ - copywithmeta
+ register: obj_info
+
+ - assert:
+ that:
+ - obj_info.results | selectattr('item', 'equalto', 'nometa') | map(attribute='object_info.0.object_data.metadata') | first == {}
+ - obj_info.results | selectattr('item', 'equalto', 'withmeta') | map(attribute='object_info.0.object_data.metadata') | first == withmeta_data
+ - obj_info.results | selectattr('item', 'equalto', 'metacopy') | map(attribute='object_info.0.object_data.metadata') | first == metacopy_data
+ - obj_info.results | selectattr('item', 'equalto', 'copywithmeta') | map(attribute='object_info.0.object_data.metadata') | first == withmeta_data
+
always:
- ansible.builtin.include_tasks: delete_bucket.yml
with_items:
diff --git a/ansible_collections/amazon/aws/tests/integration/targets/s3_object/tasks/main.yml b/ansible_collections/amazon/aws/tests/integration/targets/s3_object/tasks/main.yml
index ed65fe31f..7a8a585de 100644
--- a/ansible_collections/amazon/aws/tests/integration/targets/s3_object/tasks/main.yml
+++ b/ansible_collections/amazon/aws/tests/integration/targets/s3_object/tasks/main.yml
@@ -837,8 +837,6 @@
that:
- binary_files.results[0].stat.checksum == binary_files.results[1].stat.checksum
- - ansible.builtin.include_tasks: copy_object.yml
- - ansible.builtin.include_tasks: copy_object_acl_disabled_bucket.yml
- name: Run tagging tests
block:
# ============================================================
@@ -1074,6 +1072,8 @@
- (result.tags | length) == 0
- ansible.builtin.include_tasks: copy_recursively.yml
+ - ansible.builtin.include_tasks: copy_object.yml
+ - ansible.builtin.include_tasks: copy_object_acl_disabled_bucket.yml
always:
- name: delete temporary files
file:
diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/iam/test_iam_resource_transforms.py b/ansible_collections/amazon/aws/tests/unit/module_utils/iam/test_iam_resource_transforms.py
new file mode 100644
index 000000000..28090f993
--- /dev/null
+++ b/ansible_collections/amazon/aws/tests/unit/module_utils/iam/test_iam_resource_transforms.py
@@ -0,0 +1,583 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: Contributors to the Ansible project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+import dateutil
+
+from ansible_collections.amazon.aws.plugins.module_utils.iam import normalize_iam_access_key
+from ansible_collections.amazon.aws.plugins.module_utils.iam import normalize_iam_access_keys
+from ansible_collections.amazon.aws.plugins.module_utils.iam import normalize_iam_group
+from ansible_collections.amazon.aws.plugins.module_utils.iam import normalize_iam_instance_profile
+from ansible_collections.amazon.aws.plugins.module_utils.iam import normalize_iam_mfa_device
+from ansible_collections.amazon.aws.plugins.module_utils.iam import normalize_iam_mfa_devices
+from ansible_collections.amazon.aws.plugins.module_utils.iam import normalize_iam_policy
+from ansible_collections.amazon.aws.plugins.module_utils.iam import normalize_iam_role
+from ansible_collections.amazon.aws.plugins.module_utils.iam import normalize_iam_user
+
+# The various normalize_ functions are based upon ..transformation.boto3_resource_to_ansible_dict
+# As such these tests will be relatively light touch.
+
+example_date1_txt = "2020-12-30T00:00:00.000Z"
+example_date2_txt = "2021-04-26T01:23:58.000Z"
+example_date1_iso = "2020-12-30T00:00:00+00:00"
+example_date2_iso = "2021-04-26T01:23:58+00:00"
+example_date1 = dateutil.parser.parse(example_date1_txt)
+example_date2 = dateutil.parser.parse(example_date2_txt)
+
+
+class TestIamResourceToAnsibleDict:
+ def setup_method(self):
+ pass
+
+ def test_normalize_iam_mfa_device(self):
+ INPUT = {
+ "UserName": "ExampleUser",
+ "SerialNumber": "arn:aws:iam::123456789012:mfa/ExampleUser",
+ "EnableDate": example_date1,
+ }
+ OUTPUT = {
+ "user_name": "ExampleUser",
+ "serial_number": "arn:aws:iam::123456789012:mfa/ExampleUser",
+ "enable_date": example_date1_iso,
+ "tags": {},
+ }
+
+ assert OUTPUT == normalize_iam_mfa_device(INPUT)
+
+ def test_normalize_iam_mfa_devices(self):
+ INPUT = [
+ {
+ "UserName": "ExampleUser",
+ "SerialNumber": "arn:aws:iam::123456789012:mfa/ExampleUser",
+ "EnableDate": example_date1,
+ }
+ ]
+ OUTPUT = [
+ {
+ "user_name": "ExampleUser",
+ "serial_number": "arn:aws:iam::123456789012:mfa/ExampleUser",
+ "enable_date": example_date1_iso,
+ "tags": {},
+ }
+ ]
+
+ assert OUTPUT == normalize_iam_mfa_devices(INPUT)
+
+ def test_normalize_iam_user(self):
+ INPUT = {
+ "Path": "/MyPath/",
+ "UserName": "ExampleUser",
+ "UserId": "AIDU12345EXAMPLE12345",
+ "Arn": "arn:aws:iam::123456789012:user/MyPath/ExampleUser",
+ "CreateDate": example_date1,
+ "PasswordLastUsed": example_date2,
+ "PermissionsBoundary": {
+ "PermissionsBoundaryType": "PermissionsBoundaryPolicy",
+ "PermissionsBoundaryArn": "arn:aws:iam::123456789012:policy/ExamplePolicy",
+ },
+ "Tags": [
+ {"Key": "MyKey", "Value": "Example Value"},
+ ],
+ }
+
+ OUTPUT = {
+ "path": "/MyPath/",
+ "user_name": "ExampleUser",
+ "user_id": "AIDU12345EXAMPLE12345",
+ "arn": "arn:aws:iam::123456789012:user/MyPath/ExampleUser",
+ "create_date": example_date1_iso,
+ "password_last_used": example_date2_iso,
+ "permissions_boundary": {
+ "permissions_boundary_type": "PermissionsBoundaryPolicy",
+ "permissions_boundary_arn": "arn:aws:iam::123456789012:policy/ExamplePolicy",
+ },
+ "tags": {"MyKey": "Example Value"},
+ }
+
+ assert OUTPUT == normalize_iam_user(INPUT)
+
+ def test_normalize_iam_policy(self):
+ INPUT = {
+ "PolicyName": "AnsibleIntegratation-CI-ApplicationServices",
+ "PolicyId": "ANPA12345EXAMPLE12345",
+ "Arn": "arn:aws:iam::123456789012:policy/AnsibleIntegratation-CI-ApplicationServices",
+ "Path": "/examples/",
+ "DefaultVersionId": "v6",
+ "AttachmentCount": 2,
+ "PermissionsBoundaryUsageCount": 0,
+ "IsAttachable": True,
+ "CreateDate": example_date1,
+ "UpdateDate": example_date2,
+ "Tags": [
+ {"Key": "MyKey", "Value": "Example Value"},
+ ],
+ }
+
+ OUTPUT = {
+ "policy_name": "AnsibleIntegratation-CI-ApplicationServices",
+ "policy_id": "ANPA12345EXAMPLE12345",
+ "arn": "arn:aws:iam::123456789012:policy/AnsibleIntegratation-CI-ApplicationServices",
+ "path": "/examples/",
+ "default_version_id": "v6",
+ "attachment_count": 2,
+ "permissions_boundary_usage_count": 0,
+ "is_attachable": True,
+ "create_date": example_date1_iso,
+ "update_date": example_date2_iso,
+ "tags": {"MyKey": "Example Value"},
+ }
+
+ assert OUTPUT == normalize_iam_policy(INPUT)
+
+ def test_normalize_iam_group(self):
+ INPUT = {
+ "Users": [
+ {
+ "Path": "/",
+ "UserName": "ansible_test",
+ "UserId": "AIDA12345EXAMPLE12345",
+ "Arn": "arn:aws:iam::123456789012:user/ansible_test",
+ "CreateDate": example_date1,
+ "PasswordLastUsed": example_date2,
+ }
+ ],
+ "Group": {
+ "Path": "/",
+ "GroupName": "ansible-integration-ci",
+ "GroupId": "AGPA01234EXAMPLE01234",
+ "Arn": "arn:aws:iam::123456789012:group/ansible-integration-ci",
+ "CreateDate": example_date1,
+ },
+ "AttachedPolicies": [
+ {
+ "PolicyName": "AnsibleIntegratation-CI-Paas",
+ "PolicyArn": "arn:aws:iam::123456789012:policy/AnsibleIntegratation-CI-Paas",
+ },
+ {
+ "PolicyName": "AnsibleIntegratation-CI-ApplicationServices",
+ "PolicyArn": "arn:aws:iam::123456789012:policy/AnsibleIntegratation-CI-ApplicationServices",
+ },
+ ],
+ }
+
+ OUTPUT = {
+ "users": [
+ {
+ "path": "/",
+ "user_name": "ansible_test",
+ "user_id": "AIDA12345EXAMPLE12345",
+ "arn": "arn:aws:iam::123456789012:user/ansible_test",
+ "create_date": example_date1_iso,
+ "password_last_used": example_date2_iso,
+ }
+ ],
+ "group": {
+ "path": "/",
+ "group_name": "ansible-integration-ci",
+ "group_id": "AGPA01234EXAMPLE01234",
+ "arn": "arn:aws:iam::123456789012:group/ansible-integration-ci",
+ "create_date": example_date1_iso,
+ },
+ "attached_policies": [
+ {
+ "policy_name": "AnsibleIntegratation-CI-Paas",
+ "policy_arn": "arn:aws:iam::123456789012:policy/AnsibleIntegratation-CI-Paas",
+ },
+ {
+ "policy_name": "AnsibleIntegratation-CI-ApplicationServices",
+ "policy_arn": "arn:aws:iam::123456789012:policy/AnsibleIntegratation-CI-ApplicationServices",
+ },
+ ],
+ }
+
+ assert OUTPUT == normalize_iam_group(INPUT)
+
+ def test_normalize_access_key(self):
+ INPUT = {
+ "UserName": "ansible_test",
+ "AccessKeyId": "AKIA12345EXAMPLE1234",
+ "Status": "Active",
+ "CreateDate": example_date1,
+ }
+
+ OUTPUT = {
+ "user_name": "ansible_test",
+ "access_key_id": "AKIA12345EXAMPLE1234",
+ "status": "Active",
+ "create_date": example_date1_iso,
+ }
+
+ assert OUTPUT == normalize_iam_access_key(INPUT)
+
+ def test_normalize_access_keys(self):
+ INPUT = [
+ {
+ "UserName": "ansible_test",
+ "AccessKeyId": "AKIA12345EXAMPLE1234",
+ "Status": "Active",
+ "CreateDate": example_date1,
+ },
+ {
+ "UserName": "ansible_test",
+ "AccessKeyId": "AKIA01234EXAMPLE4321",
+ "Status": "Active",
+ "CreateDate": example_date2,
+ },
+ ]
+
+ OUTPUT = [
+ {
+ "access_key_id": "AKIA12345EXAMPLE1234",
+ "create_date": example_date1_iso,
+ "status": "Active",
+ "user_name": "ansible_test",
+ },
+ {
+ "access_key_id": "AKIA01234EXAMPLE4321",
+ "create_date": example_date2_iso,
+ "status": "Active",
+ "user_name": "ansible_test",
+ },
+ ]
+
+ assert OUTPUT == normalize_iam_access_keys(INPUT)
+
+ # Switch order to test that they're sorted by Creation Date
+ INPUT = [
+ {
+ "UserName": "ansible_test",
+ "AccessKeyId": "AKIA12345EXAMPLE1234",
+ "Status": "Active",
+ "CreateDate": example_date2,
+ },
+ {
+ "UserName": "ansible_test",
+ "AccessKeyId": "AKIA01234EXAMPLE4321",
+ "Status": "Active",
+ "CreateDate": example_date1,
+ },
+ ]
+
+ OUTPUT = [
+ {
+ "access_key_id": "AKIA01234EXAMPLE4321",
+ "create_date": example_date1_iso,
+ "status": "Active",
+ "user_name": "ansible_test",
+ },
+ {
+ "access_key_id": "AKIA12345EXAMPLE1234",
+ "create_date": example_date2_iso,
+ "status": "Active",
+ "user_name": "ansible_test",
+ },
+ ]
+
+ assert OUTPUT == normalize_iam_access_keys(INPUT)
+
+ def test_normalize_role(self):
+ INPUT = {
+ "Arn": "arn:aws:iam::123456789012:role/ansible-test-76640355",
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {"Action": "sts:AssumeRole", "Effect": "Deny", "Principal": {"Service": "ec2.amazonaws.com"}}
+ ],
+ "Version": "2012-10-17",
+ },
+ "CreateDate": example_date1,
+ "Description": "Ansible Test Role (updated) ansible-test-76640355",
+ "InlinePolicies": ["inline-policy-a", "inline-policy-b"],
+ "InstanceProfiles": [
+ {
+ "Arn": "arn:aws:iam::123456789012:instance-profile/ansible-test-76640355",
+ "CreateDate": example_date2,
+ "InstanceProfileId": "AIPA12345EXAMPLE12345",
+ "InstanceProfileName": "ansible-test-76640355",
+ "Path": "/",
+ "Roles": [
+ {
+ "Arn": "arn:aws:iam::123456789012:role/ansible-test-76640355",
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Deny",
+ "Principal": {"Service": "ec2.amazonaws.com"},
+ }
+ ],
+ "Version": "2012-10-17",
+ },
+ "CreateDate": example_date1,
+ "Path": "/",
+ "RoleId": "AROA12345EXAMPLE12345",
+ "RoleName": "ansible-test-76640355",
+ # XXX Bug in iam_role_info - Tags should have been in here.
+ "Tags": [{"Key": "TagB", "Value": "ValueB"}],
+ }
+ ],
+ "Tags": [{"Key": "TagA", "Value": "Value A"}],
+ }
+ ],
+ "ManagedPolicies": [
+ {
+ "PolicyArn": "arn:aws:iam::123456789012:policy/ansible-test-76640355",
+ "PolicyName": "ansible-test-76640355",
+ }
+ ],
+ "MaxSessionDuration": 43200,
+ "Path": "/",
+ "RoleId": "AROA12345EXAMPLE12345",
+ "RoleLastUsed": {},
+ "RoleName": "ansible-test-76640355",
+ "Tags": [{"Key": "TagB", "Value": "ValueB"}],
+ }
+
+ OUTPUT = {
+ "arn": "arn:aws:iam::123456789012:role/ansible-test-76640355",
+ "assume_role_policy_document": {
+ "Statement": [
+ {"Action": "sts:AssumeRole", "Effect": "Deny", "Principal": {"Service": "ec2.amazonaws.com"}}
+ ],
+ "Version": "2012-10-17",
+ },
+ "create_date": example_date1_iso,
+ "description": "Ansible Test Role (updated) ansible-test-76640355",
+ "inline_policies": ["inline-policy-a", "inline-policy-b"],
+ "instance_profiles": [
+ {
+ "arn": "arn:aws:iam::123456789012:instance-profile/ansible-test-76640355",
+ "create_date": example_date2_iso,
+ "instance_profile_id": "AIPA12345EXAMPLE12345",
+ "instance_profile_name": "ansible-test-76640355",
+ "path": "/",
+ "roles": [
+ {
+ "arn": "arn:aws:iam::123456789012:role/ansible-test-76640355",
+ "assume_role_policy_document": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Deny",
+ "Principal": {"Service": "ec2.amazonaws.com"},
+ }
+ ],
+ "Version": "2012-10-17",
+ },
+ "create_date": example_date1_iso,
+ "path": "/",
+ "role_id": "AROA12345EXAMPLE12345",
+ "role_name": "ansible-test-76640355",
+ "tags": {"TagB": "ValueB"},
+ }
+ ],
+ "tags": {"TagA": "Value A"},
+ }
+ ],
+ "managed_policies": [
+ {
+ "policy_arn": "arn:aws:iam::123456789012:policy/ansible-test-76640355",
+ "policy_name": "ansible-test-76640355",
+ }
+ ],
+ "max_session_duration": 43200,
+ "path": "/",
+ "role_id": "AROA12345EXAMPLE12345",
+ "role_last_used": {},
+ "role_name": "ansible-test-76640355",
+ "tags": {"TagB": "ValueB"},
+ }
+
+ assert OUTPUT == normalize_iam_role(INPUT)
+
+ def test_normalize_role_compat(self):
+ INPUT = {
+ "Arn": "arn:aws:iam::123456789012:role/ansible-test-76640355",
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {"Action": "sts:AssumeRole", "Effect": "Deny", "Principal": {"Service": "ec2.amazonaws.com"}}
+ ],
+ "Version": "2012-10-17",
+ },
+ "CreateDate": example_date1,
+ "Description": "Ansible Test Role (updated) ansible-test-76640355",
+ "InlinePolicies": ["inline-policy-a", "inline-policy-b"],
+ "InstanceProfiles": [
+ {
+ "Arn": "arn:aws:iam::123456789012:instance-profile/ansible-test-76640355",
+ "CreateDate": example_date2,
+ "InstanceProfileId": "AIPA12345EXAMPLE12345",
+ "InstanceProfileName": "ansible-test-76640355",
+ "Path": "/",
+ "Roles": [
+ {
+ "Arn": "arn:aws:iam::123456789012:role/ansible-test-76640355",
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Deny",
+ "Principal": {"Service": "ec2.amazonaws.com"},
+ }
+ ],
+ "Version": "2012-10-17",
+ },
+ "CreateDate": example_date1,
+ "Path": "/",
+ "RoleId": "AROA12345EXAMPLE12345",
+ "RoleName": "ansible-test-76640355",
+ # XXX Bug in iam_role_info - Tags should have been in here.
+ "Tags": [{"Key": "TagB", "Value": "ValueB"}],
+ }
+ ],
+ "Tags": [{"Key": "TagA", "Value": "Value A"}],
+ }
+ ],
+ "ManagedPolicies": [
+ {
+ "PolicyArn": "arn:aws:iam::123456789012:policy/ansible-test-76640355",
+ "PolicyName": "ansible-test-76640355",
+ }
+ ],
+ "MaxSessionDuration": 43200,
+ "Path": "/",
+ "RoleId": "AROA12345EXAMPLE12345",
+ "RoleLastUsed": {},
+ "RoleName": "ansible-test-76640355",
+ "Tags": [{"Key": "TagB", "Value": "ValueB"}],
+ }
+
+ OUTPUT = {
+ "arn": "arn:aws:iam::123456789012:role/ansible-test-76640355",
+ "assume_role_policy_document": {
+ "statement": [
+ {"action": "sts:AssumeRole", "effect": "Deny", "principal": {"service": "ec2.amazonaws.com"}}
+ ],
+ "version": "2012-10-17",
+ },
+ "assume_role_policy_document_raw": {
+ "Statement": [
+ {"Action": "sts:AssumeRole", "Effect": "Deny", "Principal": {"Service": "ec2.amazonaws.com"}}
+ ],
+ "Version": "2012-10-17",
+ },
+ "create_date": example_date1_iso,
+ "description": "Ansible Test Role (updated) ansible-test-76640355",
+ "inline_policies": ["inline-policy-a", "inline-policy-b"],
+ "instance_profiles": [
+ {
+ "arn": "arn:aws:iam::123456789012:instance-profile/ansible-test-76640355",
+ "create_date": example_date2_iso,
+ "instance_profile_id": "AIPA12345EXAMPLE12345",
+ "instance_profile_name": "ansible-test-76640355",
+ "path": "/",
+ "roles": [
+ {
+ "arn": "arn:aws:iam::123456789012:role/ansible-test-76640355",
+ "assume_role_policy_document": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Deny",
+ "Principal": {"Service": "ec2.amazonaws.com"},
+ }
+ ],
+ "Version": "2012-10-17",
+ },
+ "create_date": example_date1_iso,
+ "path": "/",
+ "role_id": "AROA12345EXAMPLE12345",
+ "role_name": "ansible-test-76640355",
+ "tags": {"TagB": "ValueB"},
+ }
+ ],
+ "tags": {"TagA": "Value A"},
+ }
+ ],
+ "managed_policies": [
+ {
+ "policy_arn": "arn:aws:iam::123456789012:policy/ansible-test-76640355",
+ "policy_name": "ansible-test-76640355",
+ }
+ ],
+ "max_session_duration": 43200,
+ "path": "/",
+ "role_id": "AROA12345EXAMPLE12345",
+ "role_last_used": {},
+ "role_name": "ansible-test-76640355",
+ "tags": {"TagB": "ValueB"},
+ }
+
+ assert OUTPUT == normalize_iam_role(INPUT, _v7_compat=True)
+
+ def test_normalize_instance_profile(self):
+ INPUT = {
+ "Arn": "arn:aws:iam::123456789012:instance-profile/ansible-test-40050922/ansible-test-40050922",
+ "CreateDate": example_date1,
+ "InstanceProfileId": "AIPA12345EXAMPLE12345",
+ "InstanceProfileName": "ansible-test-40050922",
+ "Path": "/ansible-test-40050922/",
+ "Roles": [
+ {
+ "Arn": "arn:aws:iam::123456789012:role/ansible-test-40050922/ansible-test-40050922",
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Deny",
+ "Principal": {"Service": "ec2.amazonaws.com"},
+ }
+ ],
+ "Version": "2012-10-17",
+ },
+ "CreateDate": example_date2,
+ "Path": "/ansible-test-40050922/",
+ "RoleId": "AROA12345EXAMPLE12345",
+ "RoleName": "ansible-test-40050922",
+ "Tags": [{"Key": "TagC", "Value": "ValueC"}],
+ }
+ ],
+ "Tags": [
+ {"Key": "Key with Spaces", "Value": "Value with spaces"},
+ {"Key": "snake_case_key", "Value": "snake_case_value"},
+ {"Key": "CamelCaseKey", "Value": "CamelCaseValue"},
+ {"Key": "pascalCaseKey", "Value": "pascalCaseValue"},
+ ],
+ }
+
+ OUTPUT = {
+ "arn": "arn:aws:iam::123456789012:instance-profile/ansible-test-40050922/ansible-test-40050922",
+ "create_date": "2020-12-30T00:00:00+00:00",
+ "instance_profile_id": "AIPA12345EXAMPLE12345",
+ "instance_profile_name": "ansible-test-40050922",
+ "path": "/ansible-test-40050922/",
+ "roles": [
+ {
+ "arn": "arn:aws:iam::123456789012:role/ansible-test-40050922/ansible-test-40050922",
+ "assume_role_policy_document": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Deny",
+ "Principal": {"Service": "ec2.amazonaws.com"},
+ }
+ ],
+ "Version": "2012-10-17",
+ },
+ "create_date": "2021-04-26T01:23:58+00:00",
+ "path": "/ansible-test-40050922/",
+ "role_id": "AROA12345EXAMPLE12345",
+ "role_name": "ansible-test-40050922",
+ "tags": {"TagC": "ValueC"},
+ }
+ ],
+ "tags": {
+ "CamelCaseKey": "CamelCaseValue",
+ "Key with Spaces": "Value with spaces",
+ "pascalCaseKey": "pascalCaseValue",
+ "snake_case_key": "snake_case_value",
+ },
+ }
+
+ assert OUTPUT == normalize_iam_instance_profile(INPUT)
diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/transformation/test_boto3_resource_to_ansible_dict.py b/ansible_collections/amazon/aws/tests/unit/module_utils/transformation/test_boto3_resource_to_ansible_dict.py
new file mode 100644
index 000000000..89a0a837c
--- /dev/null
+++ b/ansible_collections/amazon/aws/tests/unit/module_utils/transformation/test_boto3_resource_to_ansible_dict.py
@@ -0,0 +1,140 @@
+# (c) 2017 Red Hat Inc.
+#
+# This file is part of Ansible
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from copy import deepcopy
+from unittest.mock import sentinel
+
+import dateutil
+import pytest
+
+from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict
+
+from ansible_collections.amazon.aws.plugins.module_utils.transformation import boto3_resource_to_ansible_dict
+
+example_date_txt = "2020-12-30T00:00:00.000Z"
+example_date_iso = "2020-12-30T00:00:00+00:00"
+example_date = dateutil.parser.parse(example_date_txt)
+
+EXAMPLE_BOTO3 = [
+ None,
+ {},
+ {"ExampleDate": example_date},
+ {"ExampleTxtDate": example_date_txt},
+ {"Tags": [{"Key": "MyKey", "Value": "MyValue"}, {"Key": "Normal case", "Value": "Normal Value"}]},
+ {
+ "Name": "ExampleResource",
+ "ExampleDate": example_date,
+ "Tags": [{"Key": "MyKey", "Value": "MyValue"}, {"Key": "Normal case", "Value": "Normal Value"}],
+ },
+ {"ExampleNested": {"ExampleKey": "Example Value"}},
+]
+
+EXAMPLE_DICT = [
+ None,
+ {},
+ {"example_date": example_date_iso, "tags": {}},
+ {"example_txt_date": example_date_txt, "tags": {}},
+ {"tags": {"MyKey": "MyValue", "Normal case": "Normal Value"}},
+ {
+ "name": "ExampleResource",
+ "example_date": example_date_iso,
+ "tags": {"MyKey": "MyValue", "Normal case": "Normal Value"},
+ },
+ {"example_nested": {"example_key": "Example Value"}, "tags": {}},
+]
+
+TEST_DATA = zip(EXAMPLE_BOTO3, EXAMPLE_DICT)
+
+NESTED_DATA = {"sentinal": sentinel.MY_VALUE}
+
+
+def do_transform_nested(resource):
+ return {"sentinal": sentinel.MY_VALUE}
+
+
+class TestBoto3ResourceToAnsibleDict:
+ def setup_method(self):
+ pass
+
+ @pytest.mark.parametrize("input_params, output_params", deepcopy(TEST_DATA))
+ def test_default_conversion(self, input_params, output_params):
+ # Test default behaviour
+ assert boto3_resource_to_ansible_dict(input_params) == output_params
+
+ @pytest.mark.parametrize("input_params, output_params", deepcopy(TEST_DATA))
+ def test_normalize(self, input_params, output_params):
+ # Test with normalize explicitly enabled
+ assert boto3_resource_to_ansible_dict(input_params, normalize=True) == output_params
+
+ @pytest.mark.parametrize("input_params, output_params", deepcopy(TEST_DATA))
+ def test_no_normalize(self, input_params, output_params):
+ # Test with normalize explicitly disabled
+ expected_value = deepcopy(output_params)
+ if input_params and "ExampleDate" in input_params:
+ expected_value["example_date"] = example_date
+ assert expected_value == boto3_resource_to_ansible_dict(input_params, normalize=False)
+
+ @pytest.mark.parametrize("input_params, output_params", deepcopy(TEST_DATA))
+ def test_no_skip(self, input_params, output_params):
+ # Test with ignore_list explicitly set to []
+ assert boto3_resource_to_ansible_dict(input_params, ignore_list=[]) == output_params
+ assert boto3_resource_to_ansible_dict(input_params, ignore_list=["NotUsed"]) == output_params
+
+ @pytest.mark.parametrize("input_params, output_params", deepcopy(TEST_DATA))
+ def test_skip(self, input_params, output_params):
+ # Test with ignore_list explicitly set
+ expected_value = deepcopy(output_params)
+ if input_params and "ExampleNested" in input_params:
+ expected_value["example_nested"] = input_params["ExampleNested"]
+ assert expected_value == boto3_resource_to_ansible_dict(input_params, ignore_list=["ExampleNested"])
+ assert expected_value == boto3_resource_to_ansible_dict(input_params, ignore_list=["NotUsed", "ExampleNested"])
+ assert expected_value == boto3_resource_to_ansible_dict(input_params, ignore_list=["ExampleNested", "NotUsed"])
+
+ @pytest.mark.parametrize("input_params, output_params", deepcopy(TEST_DATA))
+ def test_tags(self, input_params, output_params):
+ # Test with transform_tags explicitly enabled
+ assert boto3_resource_to_ansible_dict(input_params, transform_tags=True) == output_params
+
+ @pytest.mark.parametrize("input_params, output_params", deepcopy(TEST_DATA))
+ def test_no_tags(self, input_params, output_params):
+ # Test with transform_tags explicitly disabled
+ expected_value = deepcopy(output_params)
+ if input_params and "Tags" in input_params:
+ camel_tags = camel_dict_to_snake_dict({"tags": input_params["Tags"]})
+ expected_value.update(camel_tags)
+ assert expected_value == boto3_resource_to_ansible_dict(input_params, transform_tags=False)
+
+ @pytest.mark.parametrize("input_params, output_params", deepcopy(TEST_DATA))
+ def test_no_nested(self, input_params, output_params):
+ # Test with transform_nested explicitly set to an empty dictionary
+ assert boto3_resource_to_ansible_dict(input_params, nested_transforms={}) == output_params
+
+ @pytest.mark.parametrize("input_params, output_params", deepcopy(TEST_DATA))
+ def test_nested(self, input_params, output_params):
+ # Test with a custom transformation of nested resources
+ transform_map = {"ExampleNested": do_transform_nested}
+ expected_value = deepcopy(output_params)
+
+ actual_value = boto3_resource_to_ansible_dict(input_params, nested_transforms=transform_map)
+
+ if input_params and "ExampleNested" in input_params:
+ assert actual_value["example_nested"] == NESTED_DATA
+ del actual_value["example_nested"]
+ del expected_value["example_nested"]
+
+ assert expected_value == actual_value
+
+ @pytest.mark.parametrize("input_params, output_params", deepcopy(TEST_DATA))
+ def test_force_tags(self, input_params, output_params):
+ # Test with force_tags explicitly enabled
+ assert boto3_resource_to_ansible_dict(input_params, force_tags=True) == output_params
+
+ @pytest.mark.parametrize("input_params, output_params", deepcopy(TEST_DATA))
+ def test_no_force_tags(self, input_params, output_params):
+ # Test with force_tags explicitly enabled
+ expected_value = deepcopy(output_params)
+ if input_params and "Tags" not in input_params:
+ del expected_value["tags"]
+ assert boto3_resource_to_ansible_dict(input_params, force_tags=False) == expected_value
diff --git a/ansible_collections/amazon/aws/tests/unit/plugin_utils/inventory/test_inventory_base.py b/ansible_collections/amazon/aws/tests/unit/plugin_utils/inventory/test_inventory_base.py
index 32eb3f7ab..4da5792a8 100644
--- a/ansible_collections/amazon/aws/tests/unit/plugin_utils/inventory/test_inventory_base.py
+++ b/ansible_collections/amazon/aws/tests/unit/plugin_utils/inventory/test_inventory_base.py
@@ -3,6 +3,7 @@
# This file is part of Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+import re
from unittest.mock import MagicMock
from unittest.mock import call
from unittest.mock import patch
@@ -11,6 +12,8 @@ from unittest.mock import sentinel
import pytest
import ansible.plugins.inventory as base_inventory
+from ansible.errors import AnsibleError
+from ansible.module_utils.six import string_types
import ansible_collections.amazon.aws.plugins.plugin_utils.inventory as utils_inventory
@@ -65,3 +68,131 @@ def test_inventory_verify_file(monkeypatch, filename, result):
assert inventory_plugin.verify_file(filename) is result
base_verify.return_value = False
assert inventory_plugin.verify_file(filename) is False
+
+
+class AwsUnitTestTemplar:
+ def __init__(self, config):
+ self.config = config
+
+ def is_template_string(self, key):
+ m = re.findall("{{([ ]*[a-zA-Z0-9_]*[ ]*)}}", key)
+ return bool(m)
+
+ def is_template(self, data):
+ if isinstance(data, string_types):
+ return self.is_template_string(data)
+ elif isinstance(data, (list, tuple)):
+ for v in data:
+ if self.is_template(v):
+ return True
+ elif isinstance(data, dict):
+ for k in data:
+ if self.is_template(k) or self.is_template(data[k]):
+ return True
+ return False
+
+ def template(self, variable, disable_lookups):
+ for k, v in self.config.items():
+ variable = re.sub("{{([ ]*%s[ ]*)}}" % k, v, variable)
+ if self.is_template_string(variable):
+ m = re.findall("{{([ ]*[a-zA-Z0-9_]*[ ]*)}}", variable)
+ raise AnsibleError(f"Missing variables: {','.join([k.replace(' ', '') for k in m])}")
+ return variable
+
+
+@pytest.fixture
+def aws_inventory_base():
+ inventory = utils_inventory.AWSInventoryBase()
+ inventory._options = {}
+ inventory.templar = None
+ return inventory
+
+
+@pytest.mark.parametrize(
+ "option,value",
+ [
+ ("access_key", "amazon_ansible_access_key_001"),
+ ("secret_key", "amazon_ansible_secret_key_890"),
+ ("session_token", None),
+ ("use_ssm_inventory", False),
+ ("This_field_is_undefined", None),
+ ("assume_role_arn", "arn:aws:iam::123456789012:role/ansible-test-inventory"),
+ ("region", "us-east-2"),
+ ],
+)
+def test_inventory_get_options_without_templar(aws_inventory_base, mocker, option, value):
+ inventory_options = {
+ "access_key": "amazon_ansible_access_key_001",
+ "secret_key": "amazon_ansible_secret_key_890",
+ "endpoint": "http//ansible.amazon.com",
+ "assume_role_arn": "arn:aws:iam::123456789012:role/ansible-test-inventory",
+ "region": "us-east-2",
+ "use_ssm_inventory": False,
+ }
+ aws_inventory_base._options = inventory_options
+
+ super_get_options_patch = mocker.patch(
+ "ansible_collections.amazon.aws.plugins.plugin_utils.inventory.BaseInventoryPlugin.get_options"
+ )
+ super_get_options_patch.return_value = aws_inventory_base._options
+
+ options = aws_inventory_base.get_options()
+ assert value == options.get(option)
+
+
+@pytest.mark.parametrize(
+ "option,value,error",
+ [
+ ("access_key", "amazon_ansible_access_key_001", None),
+ ("session_token", None, None),
+ ("use_ssm_inventory", "{{ aws_inventory_use_ssm }}", None),
+ ("This_field_is_undefined", None, None),
+ ("region", "us-east-1", None),
+ ("profile", None, "Missing variables: ansible_version"),
+ ],
+)
+def test_inventory_get_options_with_templar(aws_inventory_base, mocker, option, value, error):
+ inventory_options = {
+ "access_key": "amazon_ansible_access_key_001",
+ "profile": "ansbile_{{ ansible_os }}_{{ ansible_version }}",
+ "endpoint": "{{ aws_endpoint }}",
+ "region": "{{ aws_region_country }}-east-{{ aws_region_id }}",
+ "use_ssm_inventory": "{{ aws_inventory_use_ssm }}",
+ }
+ aws_inventory_base._options = inventory_options
+ templar_config = {
+ "ansible_os": "RedHat",
+ "aws_region_country": "us",
+ "aws_region_id": "1",
+ "aws_endpoint": "http//ansible.amazon.com",
+ }
+ aws_inventory_base.templar = AwsUnitTestTemplar(templar_config)
+
+ super_get_options_patch = mocker.patch(
+ "ansible_collections.amazon.aws.plugins.plugin_utils.inventory.BaseInventoryPlugin.get_options"
+ )
+ super_get_options_patch.return_value = aws_inventory_base._options
+
+ super_get_option_patch = mocker.patch(
+ "ansible_collections.amazon.aws.plugins.plugin_utils.inventory.BaseInventoryPlugin.get_option"
+ )
+ super_get_option_patch.side_effect = lambda x, hostvars=None: aws_inventory_base._options.get(x)
+
+ if error:
+ # test using get_options()
+ with pytest.raises(AnsibleError) as exc:
+ options = aws_inventory_base.get_options()
+ options.get(option)
+ assert error == str(exc.value)
+
+ # test using get_option()
+ with pytest.raises(AnsibleError) as exc:
+ aws_inventory_base.get_option(option)
+ assert error == str(exc.value)
+ else:
+ # test using get_options()
+ options = aws_inventory_base.get_options()
+ assert value == options.get(option)
+
+ # test using get_option()
+ assert value == aws_inventory_base.get_option(option)
diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/inventory/test_aws_ec2.py b/ansible_collections/amazon/aws/tests/unit/plugins/inventory/test_aws_ec2.py
index 8cced1662..e33b78c51 100644
--- a/ansible_collections/amazon/aws/tests/unit/plugins/inventory/test_aws_ec2.py
+++ b/ansible_collections/amazon/aws/tests/unit/plugins/inventory/test_aws_ec2.py
@@ -240,6 +240,7 @@ def test_get_tag_hostname(preference, instance, expected):
)
def test_inventory_build_include_filters(inventory, _options, expected):
inventory._options = _options
+ inventory.templar = None
assert inventory.build_include_filters() == expected