From 42675e2af22f0334ba5b48d312f77147224ed8b8 Mon Sep 17 00:00:00 2001 From: Bikouo Aubin <79859644+abikouo@users.noreply.github.com> Date: Thu, 4 Jul 2024 08:38:30 +0200 Subject: [PATCH] Refactor aws_az_region_info* modules (#2163) SUMMARY Refactor aws_az_info,aws_region_info modules ISSUE TYPE Feature Pull Request COMPONENT NAME aws_az_info aws_region_info Reviewed-by: Alina Buzachis --- ...07-refactor_aws_az_region_info-modules.yml | 3 ++ plugins/module_utils/transformation.py | 22 ++++++++++++ plugins/modules/aws_az_info.py | 21 ++++------- plugins/modules/aws_region_info.py | 21 ++++------- .../tasks/main.yml | 35 +++++++++++++----- .../tasks/select_availability_zone.yml | 23 ++++++++++++ ...t_sanitize_filters_to_boto3_filter_list.py | 36 +++++++++++++++++++ 7 files changed, 124 insertions(+), 37 deletions(-) create mode 100644 changelogs/fragments/20240107-refactor_aws_az_region_info-modules.yml create mode 100644 tests/integration/targets/ec2_instance_placement_options/tasks/select_availability_zone.yml create mode 100644 tests/unit/module_utils/transformation/test_sanitize_filters_to_boto3_filter_list.py diff --git a/changelogs/fragments/20240107-refactor_aws_az_region_info-modules.yml b/changelogs/fragments/20240107-refactor_aws_az_region_info-modules.yml new file mode 100644 index 00000000000..6ca681c013d --- /dev/null +++ b/changelogs/fragments/20240107-refactor_aws_az_region_info-modules.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - aws_az_info - refactored code to use ``AnsibleEC2Error`` as well as moving shared code into module_utils.ec2 (https://github.com/ansible-collections/amazon.aws/pull/2163). - aws_region_info - refactored code to use ``AnsibleEC2Error`` as well as moving shared code into module_utils.ec2 (https://github.com/ansible-collections/amazon.aws/pull/2163). \ No newline at end of file diff --git a/plugins/module_utils/transformation.py b/plugins/module_utils/transformation.py index a5bc23607df..d082c671953 100644 --- a/plugins/module_utils/transformation.py +++ b/plugins/module_utils/transformation.py @@ -31,6 +31,8 @@ from copy import deepcopy from typing import Any from typing import Callable +from typing import Dict +from typing import List from typing import Mapping from typing import Optional from typing import Sequence @@ -229,3 +231,23 @@ def boto3_resource_list_to_ansible_dict( boto3_resource_to_ansible_dict(resource, transform_tags, force_tags, normalize, ignore_list, nested_transforms) for resource in resource_list ] + + +def sanitize_filters_to_boto3_filter_list( + filters: Dict[str, Any], ignore_keys: Optional[List[str]] = None +) -> Dict[str, Any]: + """ + Replace filter key underscores with dashes, for compatibility and tranform ansible dict + into boto3 filter list. + + :param filters: Ansible module params filters + :return: Sanitized filters + """ + sanitized_filters = deepcopy(filters) + for k, v in filters.items(): + if ignore_keys and any((k.startswith(x) for x in ignore_keys)): + continue + if "_" in k: + sanitized_filters[k.replace("_", "-")] = v + del sanitized_filters[k] + return ansible_dict_to_boto3_filter_list(sanitized_filters) diff --git a/plugins/modules/aws_az_info.py b/plugins/modules/aws_az_info.py index 0c5ac8694f4..1d5f6a8e7f9 100644 --- a/plugins/modules/aws_az_info.py +++ b/plugins/modules/aws_az_info.py @@ -155,9 +155,9 @@ from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import describe_availability_zones from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry -from ansible_collections.amazon.aws.plugins.module_utils.transformation import ansible_dict_to_boto3_filter_list +from ansible_collections.amazon.aws.plugins.module_utils.transformation import sanitize_filters_to_boto3_filter_list def main(): @@ -165,24 +165,17 @@ def main(): module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True) - connection = module.client("ec2", retry_decorator=AWSRetry.jittered_backoff()) - - # Replace filter key underscores with dashes, for compatibility - sanitized_filters = dict(module.params.get("filters")) - for k in module.params.get("filters").keys(): - if "_" in k: - sanitized_filters[k.replace("_", "-")] = sanitized_filters[k] - del sanitized_filters[k] + connection = module.client("ec2") + # Sanitize filters + sanitized_filters = sanitize_filters_to_boto3_filter_list(module.params.get("filters")) try: - availability_zones = connection.describe_availability_zones( - aws_retry=True, Filters=ansible_dict_to_boto3_filter_list(sanitized_filters) - ) + availability_zones = describe_availability_zones(connection, Filters=sanitized_filters) except (BotoCoreError, ClientError) as e: module.fail_json_aws(e, msg="Unable to describe availability zones.") # Turn the boto3 result into ansible_friendly_snaked_names - snaked_availability_zones = [camel_dict_to_snake_dict(az) for az in availability_zones["AvailabilityZones"]] + snaked_availability_zones = [camel_dict_to_snake_dict(az) for az in availability_zones] module.exit_json(availability_zones=snaked_availability_zones) diff --git a/plugins/modules/aws_region_info.py b/plugins/modules/aws_region_info.py index 94b2aeeb628..1fc6a371be4 100644 --- a/plugins/modules/aws_region_info.py +++ b/plugins/modules/aws_region_info.py @@ -77,9 +77,9 @@ from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import describe_regions from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry -from ansible_collections.amazon.aws.plugins.module_utils.transformation import ansible_dict_to_boto3_filter_list +from ansible_collections.amazon.aws.plugins.module_utils.transformation import sanitize_filters_to_boto3_filter_list def main(): @@ -89,23 +89,16 @@ def main(): module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True) - connection = module.client("ec2", retry_decorator=AWSRetry.jittered_backoff()) - - # Replace filter key underscores with dashes, for compatibility - sanitized_filters = dict(module.params.get("filters")) - for k in module.params.get("filters").keys(): - if "_" in k: - sanitized_filters[k.replace("_", "-")] = sanitized_filters[k] - del sanitized_filters[k] + connection = module.client("ec2") + # Sanitize filters + sanitized_filters = sanitize_filters_to_boto3_filter_list(module.params.get("filters")) try: - regions = connection.describe_regions( - aws_retry=True, Filters=ansible_dict_to_boto3_filter_list(sanitized_filters) - ) + regions = describe_regions(connection, Filters=sanitized_filters) except (BotoCoreError, ClientError) as e: module.fail_json_aws(e, msg="Unable to describe regions.") - module.exit_json(regions=[camel_dict_to_snake_dict(r) for r in regions["Regions"]]) + module.exit_json(regions=[camel_dict_to_snake_dict(r) for r in regions]) if __name__ == "__main__": diff --git a/tests/integration/targets/ec2_instance_placement_options/tasks/main.yml b/tests/integration/targets/ec2_instance_placement_options/tasks/main.yml index 3b5eb5dff73..1ae90b0720a 100644 --- a/tests/integration/targets/ec2_instance_placement_options/tasks/main.yml +++ b/tests/integration/targets/ec2_instance_placement_options/tasks/main.yml @@ -27,7 +27,7 @@ instance_type: "{{ ec2_instance_type }}" wait: true ignore_errors: true - register: instance_creation + register: instance_creation_group_name - name: Gather ec2 facts to check placement group options amazon.aws.ec2_instance_info: @@ -41,11 +41,11 @@ - name: Validate instance with placement group name ansible.builtin.assert: that: - - instance_creation is success - - instance_creation is changed + - instance_creation_group_name is success + - instance_creation_group_name is changed - instance_facts.instances[0].placement.group_name == ec2_placement_group_name - # - instance_creation is failed - # - '"You are not authorized to perform this operation." in instance_creation.msg' + + - ansible.builtin.include_tasks: select_availability_zone.yml - name: New instance with dedicated tenancy amazon.aws.ec2_instance: @@ -58,10 +58,10 @@ TestId: "{{ ec2_instance_tag_TestId }}" security_group: default instance_type: "{{ ec2_instance_type }}" - availability_zone: us-east-1c + availability_zone: "{{ ec2_availability_zone }}" wait: true ignore_errors: true - register: instance_creation + register: instance_creation_tenancy - name: Gather ec2 facts to check placement tenancy amazon.aws.ec2_instance_info: @@ -75,8 +75,25 @@ - name: Validate instance with dedicated tenancy ansible.builtin.assert: that: - - instance_creation is success - - instance_creation is changed + - instance_creation_tenancy is success + - instance_creation_tenancy is changed - instance_facts.instances[0].placement.tenancy == ec2_tenancy # - instance_creation is failed # - '"You are not authorized to perform this operation." in instance_creation.msg' + + always: + - name: Delete instances created with placement group name + amazon.aws.ec2_instance: + state: absent + instance_ids: "{{ instance_creation_group_name.instance_ids }}" + wait: false + ignore_errors: true + when: instance_creation_group_name is defined + + - name: Delete instances created with placement tenancy + amazon.aws.ec2_instance: + state: absent + instance_ids: "{{ instance_creation_tenancy.instance_ids }}" + wait: false + ignore_errors: true + when: instance_creation_tenancy is defined \ No newline at end of file diff --git a/tests/integration/targets/ec2_instance_placement_options/tasks/select_availability_zone.yml b/tests/integration/targets/ec2_instance_placement_options/tasks/select_availability_zone.yml new file mode 100644 index 00000000000..0b48ebcb85c --- /dev/null +++ b/tests/integration/targets/ec2_instance_placement_options/tasks/select_availability_zone.yml @@ -0,0 +1,23 @@ +# aws regions supporting 't3.micro' instance type +- ansible.builtin.set_fact: + av_zones: + - us-east-1a + - us-east-1b + - us-east-1c + - us-east-1d + - us-east-1f + +- name: List t3.micro instances + amazon.aws.ec2_instance_info: + filters: + instance-type: t3.micro + instance-state-name: + - pending + - running + register: _instances + +- ansible.builtin.set_fact: + instances_zones: "{{ _instances.instances | map(attribute='placement.availability_zone') | list | ansible.builtin.unique }}" + +- ansible.builtin.set_fact: + ec2_availability_zone: "{{ av_zones | ansible.builtin.difference(instances_zones) | list | ansible.builtin.random }}" diff --git a/tests/unit/module_utils/transformation/test_sanitize_filters_to_boto3_filter_list.py b/tests/unit/module_utils/transformation/test_sanitize_filters_to_boto3_filter_list.py new file mode 100644 index 00000000000..3e32dd92a1d --- /dev/null +++ b/tests/unit/module_utils/transformation/test_sanitize_filters_to_boto3_filter_list.py @@ -0,0 +1,36 @@ +# +# (c) 2024 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 unittest.mock import patch + +from ansible_collections.amazon.aws.plugins.module_utils import transformation + +filters = { + "tag:Test_Name": "ansible-test-units", + "ansible_test_version": "milestone", +} + +expected_no_ignore_keys = { + "tag:Test-Name": "ansible-test-units", + "ansible-test-version": "milestone", +} + +expected_ignore_keys = { + "tag:Test_Name": "ansible-test-units", + "ansible-test-version": "milestone", +} + + +@patch("ansible_collections.amazon.aws.plugins.module_utils.transformation.ansible_dict_to_boto3_filter_list") +def test_sanitize_filters_to_boto3_filter_list_no_ignore_keys(m_ansible_dict_to_boto3_filter_list): + m_ansible_dict_to_boto3_filter_list.side_effect = lambda x: x + assert expected_no_ignore_keys == transformation.sanitize_filters_to_boto3_filter_list(filters) + + +@patch("ansible_collections.amazon.aws.plugins.module_utils.transformation.ansible_dict_to_boto3_filter_list") +def test_sanitize_filters_to_boto3_filter_list_ignore_keys(m_ansible_dict_to_boto3_filter_list): + m_ansible_dict_to_boto3_filter_list.side_effect = lambda x: x + assert expected_ignore_keys == transformation.sanitize_filters_to_boto3_filter_list(filters, ignore_keys=["tag:"])