diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-05 16:18:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-05 16:18:41 +0000 |
commit | b643c52cf29ce5bbab738b43290af3556efa1ca9 (patch) | |
tree | 21d5c53d7a9b696627a255777cefdf6f78968824 /ansible_collections/amazon/aws/plugins/module_utils | |
parent | Releasing progress-linux version 9.5.1+dfsg-1~progress7.99u1. (diff) | |
download | ansible-b643c52cf29ce5bbab738b43290af3556efa1ca9.tar.xz ansible-b643c52cf29ce5bbab738b43290af3556efa1ca9.zip |
Merging upstream version 10.0.0+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/amazon/aws/plugins/module_utils')
10 files changed, 159 insertions, 113 deletions
diff --git a/ansible_collections/amazon/aws/plugins/module_utils/acm.py b/ansible_collections/amazon/aws/plugins/module_utils/acm.py index ab3a9f073..4febe8743 100644 --- a/ansible_collections/amazon/aws/plugins/module_utils/acm.py +++ b/ansible_collections/amazon/aws/plugins/module_utils/acm.py @@ -40,7 +40,7 @@ def acm_catch_boto_exception(func): return func(*args, **kwargs) except is_boto3_error_code(ignore_error_codes): return None - except (BotoCoreError, ClientError) as e: + except (BotoCoreError, ClientError) as e: # pylint: disable=duplicate-except if not module: raise module.fail_json_aws(e, msg=error) diff --git a/ansible_collections/amazon/aws/plugins/module_utils/botocore.py b/ansible_collections/amazon/aws/plugins/module_utils/botocore.py index 858e4e593..d5ad7ea83 100644 --- a/ansible_collections/amazon/aws/plugins/module_utils/botocore.py +++ b/ansible_collections/amazon/aws/plugins/module_utils/botocore.py @@ -202,7 +202,14 @@ def _aws_region(params): return None -def get_aws_region(module, boto3=None): +def get_aws_region(module, boto3=None): # pylint: disable=redefined-outer-name + if boto3 is not None: + module.deprecate( + "get_aws_region(): the boto3 parameter will be removed in a release after 2025-05-01. " + "The parameter has been ignored since release 4.0.0.", + date="2025-05-01", + collection_name="amazon.aws", + ) try: return _aws_region(module.params) except AnsibleBotocoreError as e: @@ -266,7 +273,14 @@ def _aws_connection_info(params): return region, endpoint_url, boto_params -def get_aws_connection_info(module, boto3=None): +def get_aws_connection_info(module, boto3=None): # pylint: disable=redefined-outer-name + if boto3 is not None: + module.deprecate( + "get_aws_connection_info(): the boto3 parameter will be removed in a release after 2025-05-01. " + "The parameter has been ignored since release 4.0.0.", + date="2025-05-01", + collection_name="amazon.aws", + ) try: return _aws_connection_info(module.params) except AnsibleBotocoreError as e: @@ -335,7 +349,7 @@ def is_boto3_error_code(code, e=None): import sys dummy, e, dummy = sys.exc_info() - if not isinstance(code, list): + if not isinstance(code, (list, tuple, set)): code = [code] if isinstance(e, ClientError) and e.response["Error"]["Code"] in code: return ClientError diff --git a/ansible_collections/amazon/aws/plugins/module_utils/common.py b/ansible_collections/amazon/aws/plugins/module_utils/common.py index 41ba80231..e802a8d80 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.5.0" +AMAZON_AWS_COLLECTION_VERSION = "8.0.0" _collection_info_context = { diff --git a/ansible_collections/amazon/aws/plugins/module_utils/ec2.py b/ansible_collections/amazon/aws/plugins/module_utils/ec2.py index afe8208f5..f3aa9f3f1 100644 --- a/ansible_collections/amazon/aws/plugins/module_utils/ec2.py +++ b/ansible_collections/amazon/aws/plugins/module_utils/ec2.py @@ -39,6 +39,7 @@ up in this module because "that's where the AWS code was" (originally). import re +import ansible.module_utils.common.warnings as ansible_warnings from ansible.module_utils.ansible_release import __version__ # Used to live here, moved into ansible.module_utils.common.dict_transformations @@ -72,7 +73,6 @@ from .modules import aws_argument_spec as ec2_argument_spec # pylint: disable=u # Used to live here, moved into ansible_collections.amazon.aws.plugins.module_utils.policy from .policy import _py3cmp as py3cmp # pylint: disable=unused-import from .policy import compare_policies # pylint: disable=unused-import -from .policy import sort_json_policy_dict # pylint: disable=unused-import # Used to live here, moved into ansible_collections.amazon.aws.plugins.module_utils.retries from .retries import AWSRetry # pylint: disable=unused-import @@ -99,12 +99,22 @@ def get_ec2_security_group_ids_from_names(sec_group_list, ec2_connection, vpc_id a try block """ - def get_sg_name(sg, boto3=None): + def get_sg_name(sg): return str(sg["GroupName"]) - def get_sg_id(sg, boto3=None): + def get_sg_id(sg): return str(sg["GroupId"]) + if boto3 is not None: + ansible_warnings.deprecate( + ( + "The boto3 parameter for get_ec2_security_group_ids_from_names() has been deprecated." + "The parameter has been ignored since release 4.0.0." + ), + date="2025-05-01", + collection_name="amazon.aws", + ) + sec_group_id_list = [] if isinstance(sec_group_list, string_types): @@ -124,7 +134,7 @@ def get_ec2_security_group_ids_from_names(sec_group_list, ec2_connection, vpc_id else: all_sec_groups = ec2_connection.describe_security_groups()["SecurityGroups"] - unmatched = set(sec_group_list).difference(str(get_sg_name(all_sg, boto3)) for all_sg in all_sec_groups) + unmatched = set(sec_group_list).difference(str(get_sg_name(all_sg)) for all_sg in all_sec_groups) sec_group_name_list = list(set(sec_group_list) - set(unmatched)) if len(unmatched) > 0: diff --git a/ansible_collections/amazon/aws/plugins/module_utils/elbv2.py b/ansible_collections/amazon/aws/plugins/module_utils/elbv2.py index 758eb9a33..3da2114c7 100644 --- a/ansible_collections/amazon/aws/plugins/module_utils/elbv2.py +++ b/ansible_collections/amazon/aws/plugins/module_utils/elbv2.py @@ -449,7 +449,7 @@ class ApplicationLoadBalancer(ElasticLoadBalancerV2): if module.params.get("security_groups") is not None: try: self.security_groups = AWSRetry.jittered_backoff()(get_ec2_security_group_ids_from_names)( - module.params.get("security_groups"), self.connection_ec2, boto3=True + module.params.get("security_groups"), self.connection_ec2 ) except ValueError as e: self.module.fail_json(msg=str(e), exception=traceback.format_exc()) @@ -775,6 +775,9 @@ class ELBListeners: dict((x, listener_dict[x]) for x in listener_dict if listener_dict[x] is not None) for listener_dict in listeners ] + # AlpnPolicy is set as str into input but API is expected a list + # Transform a single item into a list of one element + listeners = self._ensure_listeners_alpn_policy(listeners) self.listeners = self._ensure_listeners_default_action_has_arn(listeners) self.current_listeners = self._get_elb_listeners() self.purge_listeners = module.params.get("purge_listeners") @@ -805,6 +808,16 @@ class ELBListeners: except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e) + @staticmethod + def _ensure_listeners_alpn_policy(listeners): + result = [] + for l in listeners: + update_listener = deepcopy(l) + if "AlpnPolicy" in l: + update_listener["AlpnPolicy"] = [update_listener["AlpnPolicy"]] + result.append(update_listener) + return result + def _ensure_listeners_default_action_has_arn(self, listeners): """ If a listener DefaultAction has been passed with a Target Group Name instead of ARN, lookup the ARN and @@ -863,7 +876,8 @@ class ELBListeners: return listeners_to_add, listeners_to_modify, listeners_to_delete - def _compare_listener(self, current_listener, new_listener): + @staticmethod + def _compare_listener(current_listener, new_listener): """ Compare two listeners. @@ -882,43 +896,53 @@ class ELBListeners: if current_listener["Protocol"] != new_listener["Protocol"]: modified_listener["Protocol"] = new_listener["Protocol"] - # If Protocol is HTTPS, check additional attributes - if current_listener["Protocol"] == "HTTPS" and new_listener["Protocol"] == "HTTPS": - # Cert - if current_listener["SslPolicy"] != new_listener["SslPolicy"]: - modified_listener["SslPolicy"] = new_listener["SslPolicy"] - if ( - current_listener["Certificates"][0]["CertificateArn"] - != new_listener["Certificates"][0]["CertificateArn"] + # If Protocol is HTTPS or TLS, check additional attributes + # SslPolicy + new_ssl_policy = new_listener.get("SslPolicy") + if new_ssl_policy and new_listener["Protocol"] in ("HTTPS", "TLS"): + current_ssl_policy = current_listener.get("SslPolicy") + if not current_ssl_policy or (current_ssl_policy and current_ssl_policy != new_ssl_policy): + modified_listener["SslPolicy"] = new_ssl_policy + + # Certificates + new_certificates = new_listener.get("Certificates") + if new_certificates and new_listener["Protocol"] in ("HTTPS", "TLS"): + current_certificates = current_listener.get("Certificates") + if not current_certificates or ( + current_certificates + and current_certificates[0]["CertificateArn"] != new_certificates[0]["CertificateArn"] ): - modified_listener["Certificates"] = [] - modified_listener["Certificates"].append({}) - modified_listener["Certificates"][0]["CertificateArn"] = new_listener["Certificates"][0][ - "CertificateArn" - ] - elif current_listener["Protocol"] != "HTTPS" and new_listener["Protocol"] == "HTTPS": - modified_listener["SslPolicy"] = new_listener["SslPolicy"] - modified_listener["Certificates"] = [] - modified_listener["Certificates"].append({}) - modified_listener["Certificates"][0]["CertificateArn"] = new_listener["Certificates"][0]["CertificateArn"] + modified_listener["Certificates"] = [{"CertificateArn": new_certificates[0]["CertificateArn"]}] # Default action # If the lengths of the actions are the same, we'll have to verify that the # contents of those actions are the same - if len(current_listener["DefaultActions"]) == len(new_listener["DefaultActions"]): - current_actions_sorted = _sort_actions(current_listener["DefaultActions"]) - new_actions_sorted = _sort_actions(new_listener["DefaultActions"]) - - new_actions_sorted_no_secret = [_prune_secret(i) for i in new_actions_sorted] - - if [_prune_ForwardConfig(i) for i in current_actions_sorted] != [ - _prune_ForwardConfig(i) for i in new_actions_sorted_no_secret - ]: - modified_listener["DefaultActions"] = new_listener["DefaultActions"] - # If the action lengths are different, then replace with the new actions - else: - modified_listener["DefaultActions"] = new_listener["DefaultActions"] + current_default_actions = current_listener.get("DefaultActions") + new_default_actions = new_listener.get("DefaultActions") + if new_default_actions: + if current_default_actions and len(current_default_actions) == len(new_default_actions): + current_actions_sorted = _sort_actions(current_default_actions) + new_actions_sorted = _sort_actions(new_default_actions) + + new_actions_sorted_no_secret = [_prune_secret(i) for i in new_actions_sorted] + + if [_prune_ForwardConfig(i) for i in current_actions_sorted] != [ + _prune_ForwardConfig(i) for i in new_actions_sorted_no_secret + ]: + modified_listener["DefaultActions"] = new_default_actions + # If the action lengths are different, then replace with the new actions + else: + modified_listener["DefaultActions"] = new_default_actions + + new_alpn_policy = new_listener.get("AlpnPolicy") + if new_alpn_policy: + if current_listener["Protocol"] == "TLS" and new_listener["Protocol"] == "TLS": + current_alpn_policy = current_listener.get("AlpnPolicy") + if not current_alpn_policy or current_alpn_policy[0] != new_alpn_policy[0]: + modified_listener["AlpnPolicy"] = new_alpn_policy + elif current_listener["Protocol"] != "TLS" and new_listener["Protocol"] == "TLS": + modified_listener["AlpnPolicy"] = new_alpn_policy if modified_listener: return modified_listener @@ -946,7 +970,23 @@ class ELBListener: # Rules is not a valid parameter for create_listener if "Rules" in self.listener: self.listener.pop("Rules") - AWSRetry.jittered_backoff()(self.connection.create_listener)(LoadBalancerArn=self.elb_arn, **self.listener) + + # handle multiple certs by adding only 1 cert during listener creation and make calls to add_listener_certificates to add other certs + listener_certificates = self.listener.get("Certificates", []) + first_certificate, other_certs = [], [] + if len(listener_certificates) > 0: + first_certificate, other_certs = listener_certificates[0], listener_certificates[1:] + self.listener["Certificates"] = [first_certificate] + # create listener + create_listener_result = AWSRetry.jittered_backoff()(self.connection.create_listener)( + LoadBalancerArn=self.elb_arn, **self.listener + ) + # only one cert can be specified per call to add_listener_certificates + for cert in other_certs: + AWSRetry.jittered_backoff()(self.connection.add_listener_certificates)( + ListenerArn=create_listener_result["Listeners"][0]["ListenerArn"], Certificates=[cert] + ) + except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e) diff --git a/ansible_collections/amazon/aws/plugins/module_utils/iam.py b/ansible_collections/amazon/aws/plugins/module_utils/iam.py index 56920d53e..155a63152 100644 --- a/ansible_collections/amazon/aws/plugins/module_utils/iam.py +++ b/ansible_collections/amazon/aws/plugins/module_utils/iam.py @@ -49,14 +49,14 @@ def detach_iam_group_policy(client, arn, group): @IAMErrorHandler.deletion_error_handler("detach role policy") @AWSRetry.jittered_backoff() def detach_iam_role_policy(client, arn, role): - client.detach_group_policy(PolicyArn=arn, RoleName=role) + client.detach_role_policy(PolicyArn=arn, RoleName=role) return True @IAMErrorHandler.deletion_error_handler("detach user policy") @AWSRetry.jittered_backoff() def detach_iam_user_policy(client, arn, user): - client.detach_group_policy(PolicyArn=arn, UserName=user) + client.detach_user_policy(PolicyArn=arn, UserName=user) return True @@ -446,8 +446,6 @@ def normalize_iam_access_keys(access_keys: BotoResourceList) -> AnsibleAWSResour 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) @@ -458,10 +456,10 @@ def normalize_iam_role(role: BotoResource, _v7_compat: bool = False) -> AnsibleA """ 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. + _v7_compat is deprecated and will be removed in release after 2026-05-01 DO NOT USE. """ transforms = {"InstanceProfiles": _normalize_iam_instance_profiles} - ignore_list = [] if _v7_compat else ["AssumeRolePolicyDocument"] + ignore_list = ["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"] diff --git a/ansible_collections/amazon/aws/plugins/module_utils/modules.py b/ansible_collections/amazon/aws/plugins/module_utils/modules.py index 8a2ff3c0b..82a81811d 100644 --- a/ansible_collections/amazon/aws/plugins/module_utils/modules.py +++ b/ansible_collections/amazon/aws/plugins/module_utils/modules.py @@ -84,11 +84,11 @@ class AnsibleAWSModule: def __init__(self, **kwargs): local_settings = {} - for key in AnsibleAWSModule.default_settings: + for key, default_value in AnsibleAWSModule.default_settings.items(): try: local_settings[key] = kwargs.pop(key) except KeyError: - local_settings[key] = AnsibleAWSModule.default_settings[key] + local_settings[key] = default_value self.settings = local_settings if local_settings["default_args"]: @@ -192,21 +192,21 @@ class AnsibleAWSModule: return self._module.md5(*args, **kwargs) def client(self, service, retry_decorator=None, **extra_params): - region, endpoint_url, aws_connect_kwargs = get_aws_connection_info(self, boto3=True) + region, endpoint_url, aws_connect_kwargs = get_aws_connection_info(self) kw_args = dict(region=region, endpoint=endpoint_url, **aws_connect_kwargs) kw_args.update(extra_params) conn = boto3_conn(self, conn_type="client", resource=service, **kw_args) return conn if retry_decorator is None else RetryingBotoClientWrapper(conn, retry_decorator) def resource(self, service, **extra_params): - region, endpoint_url, aws_connect_kwargs = get_aws_connection_info(self, boto3=True) + region, endpoint_url, aws_connect_kwargs = get_aws_connection_info(self) kw_args = dict(region=region, endpoint=endpoint_url, **aws_connect_kwargs) kw_args.update(extra_params) return boto3_conn(self, conn_type="resource", resource=service, **kw_args) @property def region(self): - return get_aws_region(self, True) + return get_aws_region(self) def fail_json_aws(self, exception, msg=None, **kwargs): """call fail_json with processed exception diff --git a/ansible_collections/amazon/aws/plugins/module_utils/policy.py b/ansible_collections/amazon/aws/plugins/module_utils/policy.py index 60b096f84..61b5edc1c 100644 --- a/ansible_collections/amazon/aws/plugins/module_utils/policy.py +++ b/ansible_collections/amazon/aws/plugins/module_utils/policy.py @@ -30,7 +30,6 @@ from functools import cmp_to_key -import ansible.module_utils.common.warnings as ansible_warnings from ansible.module_utils._text import to_text from ansible.module_utils.six import binary_type from ansible.module_utils.six import string_types @@ -151,59 +150,3 @@ def compare_policies(current_policy, new_policy, default_version="2008-10-17"): new_policy.setdefault("Version", default_version) return set(_hashable_policy(new_policy, [])) != set(_hashable_policy(current_policy, [])) - - -def sort_json_policy_dict(policy_dict): - """ - DEPRECATED - will be removed in amazon.aws 8.0.0 - - Sort any lists in an IAM JSON policy so that comparison of two policies with identical values but - different orders will return true - Args: - policy_dict (dict): Dict representing IAM JSON policy. - Basic Usage: - >>> my_iam_policy = {'Principle': {'AWS':["31","7","14","101"]} - >>> sort_json_policy_dict(my_iam_policy) - Returns: - Dict: Will return a copy of the policy as a Dict but any List will be sorted - { - 'Principle': { - 'AWS': [ '7', '14', '31', '101' ] - } - } - """ - - ansible_warnings.deprecate( - ( - "amazon.aws.module_utils.policy.sort_json_policy_dict has been deprecated, consider using " - "amazon.aws.module_utils.policy.compare_policies instead" - ), - version="8.0.0", - collection_name="amazon.aws", - ) - - def value_is_list(my_list): - checked_list = [] - for item in my_list: - if isinstance(item, dict): - checked_list.append(sort_json_policy_dict(item)) - elif isinstance(item, list): - checked_list.append(value_is_list(item)) - else: - checked_list.append(item) - - # Sort list. If it's a list of dictionaries, sort by tuple of key-value - # pairs, since Python 3 doesn't allow comparisons such as `<` between dictionaries. - checked_list.sort(key=lambda x: sorted(x.items()) if isinstance(x, dict) else x) - return checked_list - - ordered_policy_dict = {} - for key, value in policy_dict.items(): - if isinstance(value, dict): - ordered_policy_dict[key] = sort_json_policy_dict(value) - elif isinstance(value, list): - ordered_policy_dict[key] = value_is_list(value) - else: - ordered_policy_dict[key] = value - - return ordered_policy_dict diff --git a/ansible_collections/amazon/aws/plugins/module_utils/rds.py b/ansible_collections/amazon/aws/plugins/module_utils/rds.py index 85cde2e4e..20e0ae5e0 100644 --- a/ansible_collections/amazon/aws/plugins/module_utils/rds.py +++ b/ansible_collections/amazon/aws/plugins/module_utils/rds.py @@ -5,6 +5,9 @@ from collections import namedtuple from time import sleep +from typing import Any +from typing import Dict +from typing import List try: from botocore.exceptions import BotoCoreError @@ -16,6 +19,8 @@ except ImportError: from ansible.module_utils._text import to_text from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict +from .botocore import is_boto3_error_code +from .core import AnsibleAWSModule from .retries import AWSRetry from .tagging import ansible_dict_to_boto3_tag_list from .tagging import boto3_tag_list_to_ansible_dict @@ -440,3 +445,39 @@ def update_iam_roles(client, module, instance_id, roles_to_add, roles_to_remove) params = {"DBInstanceIdentifier": instance_id, "RoleArn": role["role_arn"], "FeatureName": role["feature_name"]} _result, changed = call_method(client, module, method_name="add_role_to_db_instance", parameters=params) return changed + + +@AWSRetry.jittered_backoff() +def describe_db_cluster_parameter_groups( + module: AnsibleAWSModule, connection: Any, group_name: str +) -> List[Dict[str, Any]]: + result = [] + try: + params = {} + if group_name is not None: + params["DBClusterParameterGroupName"] = group_name + paginator = connection.get_paginator("describe_db_cluster_parameter_groups") + result = paginator.paginate(**params).build_full_result()["DBClusterParameterGroups"] + except is_boto3_error_code("DBParameterGroupNotFound"): + pass + except ClientError as e: # pylint: disable=duplicate-except + module.fail_json_aws(e, msg="Couldn't access parameter groups information") + return result + + +@AWSRetry.jittered_backoff() +def describe_db_cluster_parameters( + module: AnsibleAWSModule, connection: Any, group_name: str, source: str = "all" +) -> List[Dict[str, Any]]: + result = [] + try: + paginator = connection.get_paginator("describe_db_cluster_parameters") + params = {"DBClusterParameterGroupName": group_name} + if source != "all": + params["Source"] = source + result = paginator.paginate(**params).build_full_result()["Parameters"] + except is_boto3_error_code("DBParameterGroupNotFound"): + pass + except ClientError as e: # pylint: disable=duplicate-except + module.fail_json_aws(e, msg="Couldn't access RDS cluster parameters information") + return result diff --git a/ansible_collections/amazon/aws/plugins/module_utils/s3.py b/ansible_collections/amazon/aws/plugins/module_utils/s3.py index 73297ffc7..961f36f22 100644 --- a/ansible_collections/amazon/aws/plugins/module_utils/s3.py +++ b/ansible_collections/amazon/aws/plugins/module_utils/s3.py @@ -58,7 +58,7 @@ def calculate_etag(module, filename, etag, s3, bucket, obj, version=None): if not HAS_MD5: return None - if "-" in etag: + if etag is not None and "-" in etag: # Multi-part ETag; a hash of the hashes of each part. parts = int(etag[1:-1].split("-")[1]) try: @@ -73,7 +73,7 @@ def calculate_etag_content(module, content, etag, s3, bucket, obj, version=None) if not HAS_MD5: return None - if "-" in etag: + if etag is not None and "-" in etag: # Multi-part ETag; a hash of the hashes of each part. parts = int(etag[1:-1].split("-")[1]) try: |