diff options
Diffstat (limited to 'lib/ansible/module_utils/aws/elbv2.py')
-rw-r--r-- | lib/ansible/module_utils/aws/elbv2.py | 891 |
1 files changed, 0 insertions, 891 deletions
diff --git a/lib/ansible/module_utils/aws/elbv2.py b/lib/ansible/module_utils/aws/elbv2.py deleted file mode 100644 index 0b68bde3a4..0000000000 --- a/lib/ansible/module_utils/aws/elbv2.py +++ /dev/null @@ -1,891 +0,0 @@ -# Copyright (c) 2017 Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -# Ansible imports -from ansible.module_utils.ec2 import camel_dict_to_snake_dict, get_ec2_security_group_ids_from_names, \ - ansible_dict_to_boto3_tag_list, boto3_tag_list_to_ansible_dict, compare_policies as compare_dicts, \ - AWSRetry -from ansible.module_utils.aws.elb_utils import get_elb, get_elb_listener, convert_tg_name_to_arn - -# Non-ansible imports -try: - from botocore.exceptions import BotoCoreError, ClientError -except ImportError: - pass -import traceback -from copy import deepcopy - - -class ElasticLoadBalancerV2(object): - - def __init__(self, connection, module): - - self.connection = connection - self.module = module - self.changed = False - self.new_load_balancer = False - self.scheme = module.params.get("scheme") - self.name = module.params.get("name") - self.subnet_mappings = module.params.get("subnet_mappings") - self.subnets = module.params.get("subnets") - self.deletion_protection = module.params.get("deletion_protection") - self.wait = module.params.get("wait") - - if module.params.get("tags") is not None: - self.tags = ansible_dict_to_boto3_tag_list(module.params.get("tags")) - else: - self.tags = None - self.purge_tags = module.params.get("purge_tags") - - self.elb = get_elb(connection, module, self.name) - if self.elb is not None: - self.elb_attributes = self.get_elb_attributes() - self.elb['tags'] = self.get_elb_tags() - else: - self.elb_attributes = None - - def wait_for_status(self, elb_arn): - """ - Wait for load balancer to reach 'active' status - - :param elb_arn: The load balancer ARN - :return: - """ - - try: - waiter = self.connection.get_waiter('load_balancer_available') - waiter.wait(LoadBalancerArns=[elb_arn]) - except (BotoCoreError, ClientError) as e: - self.module.fail_json_aws(e) - - def get_elb_attributes(self): - """ - Get load balancer attributes - - :return: - """ - - try: - attr_list = AWSRetry.jittered_backoff()( - self.connection.describe_load_balancer_attributes - )(LoadBalancerArn=self.elb['LoadBalancerArn'])['Attributes'] - - elb_attributes = boto3_tag_list_to_ansible_dict(attr_list) - except (BotoCoreError, ClientError) as e: - self.module.fail_json_aws(e) - - # Replace '.' with '_' in attribute key names to make it more Ansibley - return dict((k.replace('.', '_'), v) for k, v in elb_attributes.items()) - - def update_elb_attributes(self): - """ - Update the elb_attributes parameter - :return: - """ - self.elb_attributes = self.get_elb_attributes() - - def get_elb_tags(self): - """ - Get load balancer tags - - :return: - """ - - try: - return AWSRetry.jittered_backoff()( - self.connection.describe_tags - )(ResourceArns=[self.elb['LoadBalancerArn']])['TagDescriptions'][0]['Tags'] - except (BotoCoreError, ClientError) as e: - self.module.fail_json_aws(e) - - def delete_tags(self, tags_to_delete): - """ - Delete elb tags - - :return: - """ - - try: - AWSRetry.jittered_backoff()( - self.connection.remove_tags - )(ResourceArns=[self.elb['LoadBalancerArn']], TagKeys=tags_to_delete) - except (BotoCoreError, ClientError) as e: - self.module.fail_json_aws(e) - - self.changed = True - - def modify_tags(self): - """ - Modify elb tags - - :return: - """ - - try: - AWSRetry.jittered_backoff()( - self.connection.add_tags - )(ResourceArns=[self.elb['LoadBalancerArn']], Tags=self.tags) - except (BotoCoreError, ClientError) as e: - self.module.fail_json_aws(e) - - self.changed = True - - def delete(self): - """ - Delete elb - :return: - """ - - try: - AWSRetry.jittered_backoff()( - self.connection.delete_load_balancer - )(LoadBalancerArn=self.elb['LoadBalancerArn']) - except (BotoCoreError, ClientError) as e: - self.module.fail_json_aws(e) - - self.changed = True - - def compare_subnets(self): - """ - Compare user subnets with current ELB subnets - - :return: bool True if they match otherwise False - """ - - subnet_mapping_id_list = [] - subnet_mappings = [] - - # Check if we're dealing with subnets or subnet_mappings - if self.subnets is not None: - # Convert subnets to subnet_mappings format for comparison - for subnet in self.subnets: - subnet_mappings.append({'SubnetId': subnet}) - - if self.subnet_mappings is not None: - # Use this directly since we're comparing as a mapping - subnet_mappings = self.subnet_mappings - - # Build a subnet_mapping style struture of what's currently - # on the load balancer - for subnet in self.elb['AvailabilityZones']: - this_mapping = {'SubnetId': subnet['SubnetId']} - for address in subnet.get('LoadBalancerAddresses', []): - if 'AllocationId' in address: - this_mapping['AllocationId'] = address['AllocationId'] - break - - subnet_mapping_id_list.append(this_mapping) - - return set(frozenset(mapping.items()) for mapping in subnet_mapping_id_list) == set(frozenset(mapping.items()) for mapping in subnet_mappings) - - def modify_subnets(self): - """ - Modify elb subnets to match module parameters - :return: - """ - - try: - AWSRetry.jittered_backoff()( - self.connection.set_subnets - )(LoadBalancerArn=self.elb['LoadBalancerArn'], Subnets=self.subnets) - except (BotoCoreError, ClientError) as e: - self.module.fail_json_aws(e) - - self.changed = True - - def update(self): - """ - Update the elb from AWS - :return: - """ - - self.elb = get_elb(self.connection, self.module, self.module.params.get("name")) - self.elb['tags'] = self.get_elb_tags() - - -class ApplicationLoadBalancer(ElasticLoadBalancerV2): - - def __init__(self, connection, connection_ec2, module): - """ - - :param connection: boto3 connection - :param module: Ansible module - """ - super(ApplicationLoadBalancer, self).__init__(connection, module) - - self.connection_ec2 = connection_ec2 - - # Ansible module parameters specific to ALBs - self.type = 'application' - 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) - except ValueError as e: - self.module.fail_json(msg=str(e), exception=traceback.format_exc()) - except (BotoCoreError, ClientError) as e: - self.module.fail_json_aws(e) - else: - self.security_groups = module.params.get('security_groups') - self.access_logs_enabled = module.params.get("access_logs_enabled") - self.access_logs_s3_bucket = module.params.get("access_logs_s3_bucket") - self.access_logs_s3_prefix = module.params.get("access_logs_s3_prefix") - self.idle_timeout = module.params.get("idle_timeout") - self.http2 = module.params.get("http2") - - if self.elb is not None and self.elb['Type'] != 'application': - self.module.fail_json(msg="The load balancer type you are trying to manage is not application. Try elb_network_lb module instead.") - - def create_elb(self): - """ - Create a load balancer - :return: - """ - - # Required parameters - params = dict() - params['Name'] = self.name - params['Type'] = self.type - - # Other parameters - if self.subnets is not None: - params['Subnets'] = self.subnets - if self.subnet_mappings is not None: - params['SubnetMappings'] = self.subnet_mappings - if self.security_groups is not None: - params['SecurityGroups'] = self.security_groups - params['Scheme'] = self.scheme - if self.tags: - params['Tags'] = self.tags - - try: - self.elb = AWSRetry.jittered_backoff()(self.connection.create_load_balancer)(**params)['LoadBalancers'][0] - self.changed = True - self.new_load_balancer = True - except (BotoCoreError, ClientError) as e: - self.module.fail_json_aws(e) - - if self.wait: - self.wait_for_status(self.elb['LoadBalancerArn']) - - def modify_elb_attributes(self): - """ - Update Application ELB attributes if required - - :return: - """ - - update_attributes = [] - - if self.access_logs_enabled is not None and str(self.access_logs_enabled).lower() != self.elb_attributes['access_logs_s3_enabled']: - update_attributes.append({'Key': 'access_logs.s3.enabled', 'Value': str(self.access_logs_enabled).lower()}) - if self.access_logs_s3_bucket is not None and self.access_logs_s3_bucket != self.elb_attributes['access_logs_s3_bucket']: - update_attributes.append({'Key': 'access_logs.s3.bucket', 'Value': self.access_logs_s3_bucket}) - if self.access_logs_s3_prefix is not None and self.access_logs_s3_prefix != self.elb_attributes['access_logs_s3_prefix']: - update_attributes.append({'Key': 'access_logs.s3.prefix', 'Value': self.access_logs_s3_prefix}) - if self.deletion_protection is not None and str(self.deletion_protection).lower() != self.elb_attributes['deletion_protection_enabled']: - update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': str(self.deletion_protection).lower()}) - if self.idle_timeout is not None and str(self.idle_timeout) != self.elb_attributes['idle_timeout_timeout_seconds']: - update_attributes.append({'Key': 'idle_timeout.timeout_seconds', 'Value': str(self.idle_timeout)}) - if self.http2 is not None and str(self.http2).lower() != self.elb_attributes['routing_http2_enabled']: - update_attributes.append({'Key': 'routing.http2.enabled', 'Value': str(self.http2).lower()}) - - if update_attributes: - try: - AWSRetry.jittered_backoff()( - self.connection.modify_load_balancer_attributes - )(LoadBalancerArn=self.elb['LoadBalancerArn'], Attributes=update_attributes) - self.changed = True - except (BotoCoreError, ClientError) as e: - # Something went wrong setting attributes. If this ELB was created during this task, delete it to leave a consistent state - if self.new_load_balancer: - AWSRetry.jittered_backoff()(self.connection.delete_load_balancer)(LoadBalancerArn=self.elb['LoadBalancerArn']) - self.module.fail_json_aws(e) - - def compare_security_groups(self): - """ - Compare user security groups with current ELB security groups - - :return: bool True if they match otherwise False - """ - - if set(self.elb['SecurityGroups']) != set(self.security_groups): - return False - else: - return True - - def modify_security_groups(self): - """ - Modify elb security groups to match module parameters - :return: - """ - - try: - AWSRetry.jittered_backoff()( - self.connection.set_security_groups - )(LoadBalancerArn=self.elb['LoadBalancerArn'], SecurityGroups=self.security_groups) - except (BotoCoreError, ClientError) as e: - self.module.fail_json_aws(e) - - self.changed = True - - -class NetworkLoadBalancer(ElasticLoadBalancerV2): - - def __init__(self, connection, connection_ec2, module): - - """ - - :param connection: boto3 connection - :param module: Ansible module - """ - super(NetworkLoadBalancer, self).__init__(connection, module) - - self.connection_ec2 = connection_ec2 - - # Ansible module parameters specific to NLBs - self.type = 'network' - self.cross_zone_load_balancing = module.params.get('cross_zone_load_balancing') - - if self.elb is not None and self.elb['Type'] != 'network': - self.module.fail_json(msg="The load balancer type you are trying to manage is not network. Try elb_application_lb module instead.") - - def create_elb(self): - """ - Create a load balancer - :return: - """ - - # Required parameters - params = dict() - params['Name'] = self.name - params['Type'] = self.type - - # Other parameters - if self.subnets is not None: - params['Subnets'] = self.subnets - if self.subnet_mappings is not None: - params['SubnetMappings'] = self.subnet_mappings - params['Scheme'] = self.scheme - if self.tags: - params['Tags'] = self.tags - - try: - self.elb = AWSRetry.jittered_backoff()(self.connection.create_load_balancer)(**params)['LoadBalancers'][0] - self.changed = True - self.new_load_balancer = True - except (BotoCoreError, ClientError) as e: - self.module.fail_json_aws(e) - - if self.wait: - self.wait_for_status(self.elb['LoadBalancerArn']) - - def modify_elb_attributes(self): - """ - Update Network ELB attributes if required - - :return: - """ - - update_attributes = [] - - if self.cross_zone_load_balancing is not None and str(self.cross_zone_load_balancing).lower() != \ - self.elb_attributes['load_balancing_cross_zone_enabled']: - update_attributes.append({'Key': 'load_balancing.cross_zone.enabled', 'Value': str(self.cross_zone_load_balancing).lower()}) - if self.deletion_protection is not None and str(self.deletion_protection).lower() != self.elb_attributes['deletion_protection_enabled']: - update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': str(self.deletion_protection).lower()}) - - if update_attributes: - try: - AWSRetry.jittered_backoff()( - self.connection.modify_load_balancer_attributes - )(LoadBalancerArn=self.elb['LoadBalancerArn'], Attributes=update_attributes) - self.changed = True - except (BotoCoreError, ClientError) as e: - # Something went wrong setting attributes. If this ELB was created during this task, delete it to leave a consistent state - if self.new_load_balancer: - AWSRetry.jittered_backoff()(self.connection.delete_load_balancer)(LoadBalancerArn=self.elb['LoadBalancerArn']) - self.module.fail_json_aws(e) - - def modify_subnets(self): - """ - Modify elb subnets to match module parameters (unsupported for NLB) - :return: - """ - - self.module.fail_json(msg='Modifying subnets and elastic IPs is not supported for Network Load Balancer') - - -class ELBListeners(object): - - def __init__(self, connection, module, elb_arn): - - self.connection = connection - self.module = module - self.elb_arn = elb_arn - listeners = module.params.get("listeners") - if listeners is not None: - # Remove suboption argspec defaults of None from each listener - listeners = [dict((x, listener_dict[x]) for x in listener_dict if listener_dict[x] is not None) for listener_dict in 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") - self.changed = False - - def update(self): - """ - Update the listeners for the ELB - - :return: - """ - self.current_listeners = self._get_elb_listeners() - - def _get_elb_listeners(self): - """ - Get ELB listeners - - :return: - """ - - try: - listener_paginator = self.connection.get_paginator('describe_listeners') - return (AWSRetry.jittered_backoff()(listener_paginator.paginate)(LoadBalancerArn=self.elb_arn).build_full_result())['Listeners'] - except (BotoCoreError, ClientError) as e: - self.module.fail_json_aws(e) - - 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 - replace the name. - - :param listeners: a list of listener dicts - :return: the same list of dicts ensuring that each listener DefaultActions dict has TargetGroupArn key. If a TargetGroupName key exists, it is removed. - """ - - if not listeners: - listeners = [] - - fixed_listeners = [] - for listener in listeners: - fixed_actions = [] - for action in listener['DefaultActions']: - if 'TargetGroupName' in action: - action['TargetGroupArn'] = convert_tg_name_to_arn(self.connection, - self.module, - action['TargetGroupName']) - del action['TargetGroupName'] - fixed_actions.append(action) - listener['DefaultActions'] = fixed_actions - fixed_listeners.append(listener) - - return fixed_listeners - - def compare_listeners(self): - """ - - :return: - """ - listeners_to_modify = [] - listeners_to_delete = [] - listeners_to_add = deepcopy(self.listeners) - - # Check each current listener port to see if it's been passed to the module - for current_listener in self.current_listeners: - current_listener_passed_to_module = False - for new_listener in self.listeners[:]: - new_listener['Port'] = int(new_listener['Port']) - if current_listener['Port'] == new_listener['Port']: - current_listener_passed_to_module = True - # Remove what we match so that what is left can be marked as 'to be added' - listeners_to_add.remove(new_listener) - modified_listener = self._compare_listener(current_listener, new_listener) - if modified_listener: - modified_listener['Port'] = current_listener['Port'] - modified_listener['ListenerArn'] = current_listener['ListenerArn'] - listeners_to_modify.append(modified_listener) - break - - # If the current listener was not matched against passed listeners and purge is True, mark for removal - if not current_listener_passed_to_module and self.purge_listeners: - listeners_to_delete.append(current_listener['ListenerArn']) - - return listeners_to_add, listeners_to_modify, listeners_to_delete - - def _compare_listener(self, current_listener, new_listener): - """ - Compare two listeners. - - :param current_listener: - :param new_listener: - :return: - """ - - modified_listener = {} - - # Port - if current_listener['Port'] != new_listener['Port']: - modified_listener['Port'] = new_listener['Port'] - - # Protocol - 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']: - 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'] - - # Default action - - # Check proper rule format on current listener - if len(current_listener['DefaultActions']) > 1: - for action in current_listener['DefaultActions']: - if 'Order' not in action: - self.module.fail_json(msg="'Order' key not found in actions. " - "installed version of botocore does not support " - "multiple actions, please upgrade botocore to version " - "1.10.30 or higher") - - # 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']): - # if actions have just one element, compare the contents and then update if - # they're different - if len(current_listener['DefaultActions']) == 1 and len(new_listener['DefaultActions']) == 1: - if current_listener['DefaultActions'] != new_listener['DefaultActions']: - modified_listener['DefaultActions'] = new_listener['DefaultActions'] - # if actions have multiple elements, we'll have to order them first before comparing. - # multiple actions will have an 'Order' key for this purpose - else: - current_actions_sorted = sorted(current_listener['DefaultActions'], key=lambda x: x['Order']) - new_actions_sorted = sorted(new_listener['DefaultActions'], key=lambda x: x['Order']) - - # the AWS api won't return the client secret, so we'll have to remove it - # or the module will always see the new and current actions as different - # and try to apply the same config - new_actions_sorted_no_secret = [] - for action in new_actions_sorted: - # the secret is currently only defined in the oidc config - if action['Type'] == 'authenticate-oidc': - action['AuthenticateOidcConfig'].pop('ClientSecret') - new_actions_sorted_no_secret.append(action) - else: - new_actions_sorted_no_secret.append(action) - - if current_actions_sorted != 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'] - - if modified_listener: - return modified_listener - else: - return None - - -class ELBListener(object): - - def __init__(self, connection, module, listener, elb_arn): - """ - - :param connection: - :param module: - :param listener: - :param elb_arn: - """ - - self.connection = connection - self.module = module - self.listener = listener - self.elb_arn = elb_arn - - def add(self): - - try: - # 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) - except (BotoCoreError, ClientError) as e: - if '"Order", must be one of: Type, TargetGroupArn' in str(e): - self.module.fail_json(msg="installed version of botocore does not support " - "multiple actions, please upgrade botocore to version " - "1.10.30 or higher") - else: - self.module.fail_json_aws(e) - - def modify(self): - - try: - # Rules is not a valid parameter for modify_listener - if 'Rules' in self.listener: - self.listener.pop('Rules') - AWSRetry.jittered_backoff()(self.connection.modify_listener)(**self.listener) - except (BotoCoreError, ClientError) as e: - if '"Order", must be one of: Type, TargetGroupArn' in str(e): - self.module.fail_json(msg="installed version of botocore does not support " - "multiple actions, please upgrade botocore to version " - "1.10.30 or higher") - else: - self.module.fail_json_aws(e) - - def delete(self): - - try: - AWSRetry.jittered_backoff()(self.connection.delete_listener)(ListenerArn=self.listener) - except (BotoCoreError, ClientError) as e: - self.module.fail_json_aws(e) - - -class ELBListenerRules(object): - - def __init__(self, connection, module, elb_arn, listener_rules, listener_port): - - self.connection = connection - self.module = module - self.elb_arn = elb_arn - self.rules = self._ensure_rules_action_has_arn(listener_rules) - self.changed = False - - # Get listener based on port so we can use ARN - self.current_listener = get_elb_listener(connection, module, elb_arn, listener_port) - self.listener_arn = self.current_listener['ListenerArn'] - self.rules_to_add = deepcopy(self.rules) - self.rules_to_modify = [] - self.rules_to_delete = [] - - # If the listener exists (i.e. has an ARN) get rules for the listener - if 'ListenerArn' in self.current_listener: - self.current_rules = self._get_elb_listener_rules() - else: - self.current_rules = [] - - def _ensure_rules_action_has_arn(self, rules): - """ - If a rule Action has been passed with a Target Group Name instead of ARN, lookup the ARN and - replace the name. - - :param rules: a list of rule dicts - :return: the same list of dicts ensuring that each rule Actions dict has TargetGroupArn key. If a TargetGroupName key exists, it is removed. - """ - - fixed_rules = [] - for rule in rules: - fixed_actions = [] - for action in rule['Actions']: - if 'TargetGroupName' in action: - action['TargetGroupArn'] = convert_tg_name_to_arn(self.connection, self.module, action['TargetGroupName']) - del action['TargetGroupName'] - fixed_actions.append(action) - rule['Actions'] = fixed_actions - fixed_rules.append(rule) - - return fixed_rules - - def _get_elb_listener_rules(self): - - try: - return AWSRetry.jittered_backoff()(self.connection.describe_rules)(ListenerArn=self.current_listener['ListenerArn'])['Rules'] - except (BotoCoreError, ClientError) as e: - self.module.fail_json_aws(e) - - def _compare_condition(self, current_conditions, condition): - """ - - :param current_conditions: - :param condition: - :return: - """ - - condition_found = False - - for current_condition in current_conditions: - if current_condition.get('SourceIpConfig'): - if (current_condition['Field'] == condition['Field'] and - current_condition['SourceIpConfig']['Values'][0] == condition['SourceIpConfig']['Values'][0]): - condition_found = True - break - elif current_condition['Field'] == condition['Field'] and sorted(current_condition['Values']) == sorted(condition['Values']): - condition_found = True - break - - return condition_found - - def _compare_rule(self, current_rule, new_rule): - """ - - :return: - """ - - modified_rule = {} - - # Priority - if int(current_rule['Priority']) != int(new_rule['Priority']): - modified_rule['Priority'] = new_rule['Priority'] - - # Actions - - # Check proper rule format on current listener - if len(current_rule['Actions']) > 1: - for action in current_rule['Actions']: - if 'Order' not in action: - self.module.fail_json(msg="'Order' key not found in actions. " - "installed version of botocore does not support " - "multiple actions, please upgrade botocore to version " - "1.10.30 or higher") - - # 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_rule['Actions']) == len(new_rule['Actions']): - # if actions have just one element, compare the contents and then update if - # they're different - if len(current_rule['Actions']) == 1 and len(new_rule['Actions']) == 1: - if current_rule['Actions'] != new_rule['Actions']: - modified_rule['Actions'] = new_rule['Actions'] - # if actions have multiple elements, we'll have to order them first before comparing. - # multiple actions will have an 'Order' key for this purpose - else: - current_actions_sorted = sorted(current_rule['Actions'], key=lambda x: x['Order']) - new_actions_sorted = sorted(new_rule['Actions'], key=lambda x: x['Order']) - - # the AWS api won't return the client secret, so we'll have to remove it - # or the module will always see the new and current actions as different - # and try to apply the same config - new_actions_sorted_no_secret = [] - for action in new_actions_sorted: - # the secret is currently only defined in the oidc config - if action['Type'] == 'authenticate-oidc': - action['AuthenticateOidcConfig'].pop('ClientSecret') - new_actions_sorted_no_secret.append(action) - else: - new_actions_sorted_no_secret.append(action) - - if current_actions_sorted != new_actions_sorted_no_secret: - modified_rule['Actions'] = new_rule['Actions'] - # If the action lengths are different, then replace with the new actions - else: - modified_rule['Actions'] = new_rule['Actions'] - - # Conditions - modified_conditions = [] - for condition in new_rule['Conditions']: - if not self._compare_condition(current_rule['Conditions'], condition): - modified_conditions.append(condition) - - if modified_conditions: - modified_rule['Conditions'] = modified_conditions - - return modified_rule - - def compare_rules(self): - """ - - :return: - """ - - rules_to_modify = [] - rules_to_delete = [] - rules_to_add = deepcopy(self.rules) - - for current_rule in self.current_rules: - current_rule_passed_to_module = False - for new_rule in self.rules[:]: - if current_rule['Priority'] == str(new_rule['Priority']): - current_rule_passed_to_module = True - # Remove what we match so that what is left can be marked as 'to be added' - rules_to_add.remove(new_rule) - modified_rule = self._compare_rule(current_rule, new_rule) - if modified_rule: - modified_rule['Priority'] = int(current_rule['Priority']) - modified_rule['RuleArn'] = current_rule['RuleArn'] - modified_rule['Actions'] = new_rule['Actions'] - modified_rule['Conditions'] = new_rule['Conditions'] - rules_to_modify.append(modified_rule) - break - - # If the current rule was not matched against passed rules, mark for removal - if not current_rule_passed_to_module and not current_rule['IsDefault']: - rules_to_delete.append(current_rule['RuleArn']) - - return rules_to_add, rules_to_modify, rules_to_delete - - -class ELBListenerRule(object): - - def __init__(self, connection, module, rule, listener_arn): - - self.connection = connection - self.module = module - self.rule = rule - self.listener_arn = listener_arn - self.changed = False - - def create(self): - """ - Create a listener rule - - :return: - """ - - try: - self.rule['ListenerArn'] = self.listener_arn - self.rule['Priority'] = int(self.rule['Priority']) - AWSRetry.jittered_backoff()(self.connection.create_rule)(**self.rule) - except (BotoCoreError, ClientError) as e: - if '"Order", must be one of: Type, TargetGroupArn' in str(e): - self.module.fail_json(msg="installed version of botocore does not support " - "multiple actions, please upgrade botocore to version " - "1.10.30 or higher") - else: - self.module.fail_json_aws(e) - - self.changed = True - - def modify(self): - """ - Modify a listener rule - - :return: - """ - - try: - del self.rule['Priority'] - AWSRetry.jittered_backoff()(self.connection.modify_rule)(**self.rule) - except (BotoCoreError, ClientError) as e: - if '"Order", must be one of: Type, TargetGroupArn' in str(e): - self.module.fail_json(msg="installed version of botocore does not support " - "multiple actions, please upgrade botocore to version " - "1.10.30 or higher") - else: - self.module.fail_json_aws(e) - - self.changed = True - - def delete(self): - """ - Delete a listener rule - - :return: - """ - - try: - AWSRetry.jittered_backoff()(self.connection.delete_rule)(RuleArn=self.rule['RuleArn']) - except (BotoCoreError, ClientError) as e: - self.module.fail_json_aws(e) - - self.changed = True |