mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
[aws] Create classes for Application Load Balancer (#33769)
* Create classes for Application Load Balancer * Add unsupported CI alias * Add AWSRetry * Add integration tests using the ALB
This commit is contained in:
parent
8ac69b0a5f
commit
b5cffe8ced
14 changed files with 1568 additions and 762 deletions
110
lib/ansible/module_utils/aws/elb_utils.py
Normal file
110
lib/ansible/module_utils/aws/elb_utils.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# Copyright (c) 2017 Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from ansible.module_utils.ec2 import AWSRetry
|
||||||
|
|
||||||
|
# Non-ansible imports
|
||||||
|
try:
|
||||||
|
from botocore.exceptions import BotoCoreError, ClientError
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_elb(connection, module, elb_name):
|
||||||
|
"""
|
||||||
|
Get an ELB based on name. If not found, return None.
|
||||||
|
|
||||||
|
:param connection: AWS boto3 elbv2 connection
|
||||||
|
:param module: Ansible module
|
||||||
|
:param elb_name: Name of load balancer to get
|
||||||
|
:return: boto3 ELB dict or None if not found
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return _get_elb(connection, module, elb_name)
|
||||||
|
except (BotoCoreError, ClientError) as e:
|
||||||
|
module.fail_json_aws(e)
|
||||||
|
|
||||||
|
|
||||||
|
@AWSRetry.jittered_backoff()
|
||||||
|
def _get_elb(connection, module, elb_name):
|
||||||
|
"""
|
||||||
|
Get an ELB based on name using AWSRetry. If not found, return None.
|
||||||
|
|
||||||
|
:param connection: AWS boto3 elbv2 connection
|
||||||
|
:param module: Ansible module
|
||||||
|
:param elb_name: Name of load balancer to get
|
||||||
|
:return: boto3 ELB dict or None if not found
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
load_balancer_paginator = connection.get_paginator('describe_load_balancers')
|
||||||
|
return (load_balancer_paginator.paginate(Names=[elb_name]).build_full_result())['LoadBalancers'][0]
|
||||||
|
except (BotoCoreError, ClientError) as e:
|
||||||
|
if e.response['Error']['Code'] == 'LoadBalancerNotFound':
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
def get_elb_listener(connection, module, elb_arn, listener_port):
|
||||||
|
"""
|
||||||
|
Get an ELB listener based on the port provided. If not found, return None.
|
||||||
|
|
||||||
|
:param connection: AWS boto3 elbv2 connection
|
||||||
|
:param module: Ansible module
|
||||||
|
:param elb_arn: ARN of the ELB to look at
|
||||||
|
:param listener_port: Port of the listener to look for
|
||||||
|
:return: boto3 ELB listener dict or None if not found
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
listener_paginator = connection.get_paginator('describe_listeners')
|
||||||
|
listeners = (AWSRetry.jittered_backoff()(listener_paginator.paginate)(LoadBalancerArn=elb_arn).build_full_result())['Listeners']
|
||||||
|
except (BotoCoreError, ClientError) as e:
|
||||||
|
module.fail_json_aws(e)
|
||||||
|
|
||||||
|
l = None
|
||||||
|
|
||||||
|
for listener in listeners:
|
||||||
|
if listener['Port'] == listener_port:
|
||||||
|
l = listener
|
||||||
|
break
|
||||||
|
|
||||||
|
return l
|
||||||
|
|
||||||
|
|
||||||
|
def get_elb_listener_rules(connection, module, listener_arn):
|
||||||
|
"""
|
||||||
|
Get rules for a particular ELB listener using the listener ARN.
|
||||||
|
|
||||||
|
:param connection: AWS boto3 elbv2 connection
|
||||||
|
:param module: Ansible module
|
||||||
|
:param listener_arn: ARN of the ELB listener
|
||||||
|
:return: boto3 ELB rules list
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
return AWSRetry.jittered_backoff()(connection.describe_rules)(ListenerArn=listener_arn)['Rules']
|
||||||
|
except (BotoCoreError, ClientError) as e:
|
||||||
|
module.fail_json_aws(e)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_tg_name_to_arn(connection, module, tg_name):
|
||||||
|
"""
|
||||||
|
Get ARN of a target group using the target group's name
|
||||||
|
|
||||||
|
:param connection: AWS boto3 elbv2 connection
|
||||||
|
:param module: Ansible module
|
||||||
|
:param tg_name: Name of the target group
|
||||||
|
:return: target group ARN string
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = AWSRetry.jittered_backoff()(connection.describe_target_groups)(Names=[tg_name])
|
||||||
|
except (BotoCoreError, ClientError) as e:
|
||||||
|
module.fail_json_aws(e)
|
||||||
|
|
||||||
|
tg_arn = response['TargetGroups'][0]['TargetGroupArn']
|
||||||
|
|
||||||
|
return tg_arn
|
757
lib/ansible/module_utils/aws/elbv2.py
Normal file
757
lib/ansible/module_utils/aws/elbv2.py
Normal file
|
@ -0,0 +1,757 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# Copyright (c) 2017 Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
import time
|
||||||
|
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_id_list = []
|
||||||
|
subnets = []
|
||||||
|
|
||||||
|
# Check if we're dealing with subnets or subnet_mappings
|
||||||
|
if self.subnets is not None:
|
||||||
|
# We need to first get the subnet ID from the list
|
||||||
|
subnets = self.subnets
|
||||||
|
|
||||||
|
if self.subnet_mappings is not None:
|
||||||
|
# Make a list from the subnet_mappings dict
|
||||||
|
subnets_from_mappings = []
|
||||||
|
for subnet_mapping in self.subnet_mappings:
|
||||||
|
subnets.append(subnet_mapping['SubnetId'])
|
||||||
|
|
||||||
|
for subnet in self.elb['AvailabilityZones']:
|
||||||
|
subnet_id_list.append(subnet['SubnetId'])
|
||||||
|
|
||||||
|
if set(subnet_id_list) != set(subnets):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
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.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 ELB attributes if required
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
update_attributes = []
|
||||||
|
|
||||||
|
if self.access_logs_enabled and self.elb_attributes['access_logs_s3_enabled'] != "true":
|
||||||
|
update_attributes.append({'Key': 'access_logs.s3.enabled', 'Value': "true"})
|
||||||
|
if not self.access_logs_enabled and self.elb_attributes['access_logs_s3_enabled'] != "false":
|
||||||
|
update_attributes.append({'Key': 'access_logs.s3.enabled', 'Value': 'false'})
|
||||||
|
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 and self.elb_attributes['deletion_protection_enabled'] != "true":
|
||||||
|
update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "true"})
|
||||||
|
if self.deletion_protection is not None and not self.deletion_protection and self.elb_attributes['deletion_protection_enabled'] != "false":
|
||||||
|
update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "false"})
|
||||||
|
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 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'
|
||||||
|
|
||||||
|
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
|
||||||
|
params['Scheme'] = self.scheme
|
||||||
|
if self.tags is not None:
|
||||||
|
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 ELB attributes if required
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
update_attributes = []
|
||||||
|
|
||||||
|
if self.deletion_protection and self.elb_attributes['deletion_protection_enabled'] != "true":
|
||||||
|
update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "true"})
|
||||||
|
if self.deletion_protection is not None and not self.deletion_protection and self.elb_attributes['deletion_protection_enabled'] != "false":
|
||||||
|
update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "false"})
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
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 = []
|
||||||
|
|
||||||
|
for listener in listeners:
|
||||||
|
if 'TargetGroupName' in listener['DefaultActions'][0]:
|
||||||
|
listener['DefaultActions'][0]['TargetGroupArn'] = convert_tg_name_to_arn(self.connection, self.module,
|
||||||
|
listener['DefaultActions'][0]['TargetGroupName'])
|
||||||
|
del listener['DefaultActions'][0]['TargetGroupName']
|
||||||
|
|
||||||
|
return 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
|
||||||
|
# We wont worry about the Action Type because it is always 'forward'
|
||||||
|
if current_listener['DefaultActions'][0]['TargetGroupArn'] != new_listener['DefaultActions'][0]['TargetGroupArn']:
|
||||||
|
modified_listener['DefaultActions'] = []
|
||||||
|
modified_listener['DefaultActions'].append({})
|
||||||
|
modified_listener['DefaultActions'][0]['TargetGroupArn'] = new_listener['DefaultActions'][0]['TargetGroupArn']
|
||||||
|
modified_listener['DefaultActions'][0]['Type'] = 'forward'
|
||||||
|
|
||||||
|
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:
|
||||||
|
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:
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for rule in rules:
|
||||||
|
if 'TargetGroupName' in rule['Actions'][0]:
|
||||||
|
rule['Actions'][0]['TargetGroupArn'] = convert_tg_name_to_arn(self.connection, self.module, rule['Actions'][0]['TargetGroupName'])
|
||||||
|
del rule['Actions'][0]['TargetGroupName']
|
||||||
|
|
||||||
|
return 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['Field'] == condition['Field'] and current_condition['Values'][0] == condition['Values'][0]:
|
||||||
|
condition_found = True
|
||||||
|
break
|
||||||
|
|
||||||
|
return condition_found
|
||||||
|
|
||||||
|
def _compare_rule(self, current_rule, new_rule):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
modified_rule = {}
|
||||||
|
|
||||||
|
# Priority
|
||||||
|
if current_rule['Priority'] != new_rule['Priority']:
|
||||||
|
modified_rule['Priority'] = new_rule['Priority']
|
||||||
|
|
||||||
|
# Actions
|
||||||
|
# We wont worry about the Action Type because it is always 'forward'
|
||||||
|
if current_rule['Actions'][0]['TargetGroupArn'] != new_rule['Actions'][0]['TargetGroupArn']:
|
||||||
|
modified_rule['Actions'] = []
|
||||||
|
modified_rule['Actions'].append({})
|
||||||
|
modified_rule['Actions'][0]['TargetGroupArn'] = new_rule['Actions'][0]['TargetGroupArn']
|
||||||
|
modified_rule['Actions'][0]['Type'] = 'forward'
|
||||||
|
|
||||||
|
# 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'] == 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:
|
||||||
|
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:
|
||||||
|
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
|
|
@ -101,6 +101,17 @@ options:
|
||||||
description:
|
description:
|
||||||
- A dictionary of one or more tags to assign to the load balancer.
|
- A dictionary of one or more tags to assign to the load balancer.
|
||||||
required: false
|
required: false
|
||||||
|
wait:
|
||||||
|
description:
|
||||||
|
- Wait for the load balancer to have a state of 'active' before completing. A status check is
|
||||||
|
performed every 15 seconds until a successful state is reached. An error is returned after 40 failed checks.
|
||||||
|
default: no
|
||||||
|
type: bool
|
||||||
|
version_added: 2.6
|
||||||
|
wait_timeout:
|
||||||
|
description:
|
||||||
|
- The time in seconds to use in conjunction with I(wait).
|
||||||
|
version_added: 2.6
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- aws
|
- aws
|
||||||
- ec2
|
- ec2
|
||||||
|
@ -342,623 +353,132 @@ vpc_id:
|
||||||
type: string
|
type: string
|
||||||
sample: vpc-0011223344
|
sample: vpc-0011223344
|
||||||
'''
|
'''
|
||||||
import time
|
|
||||||
import collections
|
|
||||||
from copy import deepcopy
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||||
from ansible.module_utils.six import string_types
|
from ansible.module_utils.ec2 import boto3_conn, get_aws_connection_info, camel_dict_to_snake_dict, ec2_argument_spec, \
|
||||||
from ansible.module_utils.ec2 import boto3_conn, get_aws_connection_info, camel_dict_to_snake_dict, ec2_argument_spec, get_ec2_security_group_ids_from_names, \
|
boto3_tag_list_to_ansible_dict, compare_aws_tags, HAS_BOTO3
|
||||||
ansible_dict_to_boto3_tag_list, boto3_tag_list_to_ansible_dict, compare_aws_tags, HAS_BOTO3
|
|
||||||
|
|
||||||
try:
|
from ansible.module_utils.aws.elbv2 import ApplicationLoadBalancer, ELBListeners, ELBListener, ELBListenerRules, ELBListenerRule
|
||||||
import boto3
|
from ansible.module_utils.aws.elb_utils import get_elb_listener_rules
|
||||||
from botocore.exceptions import ClientError, NoCredentialsError
|
|
||||||
except ImportError:
|
|
||||||
HAS_BOTO3 = False
|
|
||||||
|
|
||||||
|
|
||||||
def convert_tg_name_to_arn(connection, module, tg_name):
|
def create_or_update_elb(elb_obj):
|
||||||
|
|
||||||
try:
|
|
||||||
response = connection.describe_target_groups(Names=[tg_name])
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
tg_arn = response['TargetGroups'][0]['TargetGroupArn']
|
|
||||||
|
|
||||||
return tg_arn
|
|
||||||
|
|
||||||
|
|
||||||
def wait_for_status(connection, module, elb_arn, status):
|
|
||||||
polling_increment_secs = 15
|
|
||||||
max_retries = module.params.get('wait_timeout') // polling_increment_secs
|
|
||||||
status_achieved = False
|
|
||||||
|
|
||||||
for x in range(0, max_retries):
|
|
||||||
try:
|
|
||||||
response = connection.describe_load_balancers(LoadBalancerArns=[elb_arn])
|
|
||||||
if response['LoadBalancers'][0]['State']['Code'] == status:
|
|
||||||
status_achieved = True
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
time.sleep(polling_increment_secs)
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
result = response
|
|
||||||
return status_achieved, result
|
|
||||||
|
|
||||||
|
|
||||||
def _get_subnet_ids_from_subnet_list(subnet_list):
|
|
||||||
|
|
||||||
subnet_id_list = []
|
|
||||||
for subnet in subnet_list:
|
|
||||||
subnet_id_list.append(subnet['SubnetId'])
|
|
||||||
|
|
||||||
return subnet_id_list
|
|
||||||
|
|
||||||
|
|
||||||
def get_elb_listeners(connection, module, elb_arn):
|
|
||||||
|
|
||||||
try:
|
|
||||||
listener_paginator = connection.get_paginator('describe_listeners')
|
|
||||||
return (listener_paginator.paginate(LoadBalancerArn=elb_arn).build_full_result())['Listeners']
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
|
|
||||||
def get_elb_attributes(connection, module, elb_arn):
|
|
||||||
|
|
||||||
try:
|
|
||||||
elb_attributes = boto3_tag_list_to_ansible_dict(connection.describe_load_balancer_attributes(LoadBalancerArn=elb_arn)['Attributes'])
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
# Replace '.' with '_' in attribute key names to make it more Ansibley
|
|
||||||
return dict((k.replace('.', '_'), v) for k, v in elb_attributes.items())
|
|
||||||
|
|
||||||
|
|
||||||
def get_listener(connection, module, elb_arn, listener_port):
|
|
||||||
"""
|
|
||||||
Get a listener based on the port provided.
|
|
||||||
|
|
||||||
:param connection: ELBv2 boto3 connection
|
|
||||||
:param module: Ansible module object
|
|
||||||
:param listener_port:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
listener_paginator = connection.get_paginator('describe_listeners')
|
|
||||||
listeners = (listener_paginator.paginate(LoadBalancerArn=elb_arn).build_full_result())['Listeners']
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
l = None
|
|
||||||
|
|
||||||
for listener in listeners:
|
|
||||||
if listener['Port'] == listener_port:
|
|
||||||
l = listener
|
|
||||||
break
|
|
||||||
|
|
||||||
return l
|
|
||||||
|
|
||||||
|
|
||||||
def get_elb(connection, module):
|
|
||||||
"""
|
|
||||||
Get an application load balancer based on name. If not found, return None
|
|
||||||
|
|
||||||
:param connection: ELBv2 boto3 connection
|
|
||||||
:param module: Ansible module object
|
|
||||||
:return: Dict of load balancer attributes or None if not found
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
load_balancer_paginator = connection.get_paginator('describe_load_balancers')
|
|
||||||
return (load_balancer_paginator.paginate(Names=[module.params.get("name")]).build_full_result())['LoadBalancers'][0]
|
|
||||||
except ClientError as e:
|
|
||||||
if e.response['Error']['Code'] == 'LoadBalancerNotFound':
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
|
|
||||||
def get_listener_rules(connection, module, listener_arn):
|
|
||||||
|
|
||||||
try:
|
|
||||||
return connection.describe_rules(ListenerArn=listener_arn)['Rules']
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_listeners_default_action_has_arn(connection, module, listeners):
|
|
||||||
"""
|
|
||||||
If a listener DefaultAction has been passed with a Target Group Name instead of ARN, lookup the ARN and
|
|
||||||
replace the name.
|
|
||||||
|
|
||||||
:param connection: ELBv2 boto3 connection
|
|
||||||
:param module: Ansible module object
|
|
||||||
: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 = []
|
|
||||||
|
|
||||||
for listener in listeners:
|
|
||||||
if 'TargetGroupName' in listener['DefaultActions'][0]:
|
|
||||||
listener['DefaultActions'][0]['TargetGroupArn'] = convert_tg_name_to_arn(connection, module, listener['DefaultActions'][0]['TargetGroupName'])
|
|
||||||
del listener['DefaultActions'][0]['TargetGroupName']
|
|
||||||
|
|
||||||
return listeners
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_rules_action_has_arn(connection, module, rules):
|
|
||||||
"""
|
|
||||||
If a rule Action has been passed with a Target Group Name instead of ARN, lookup the ARN and
|
|
||||||
replace the name.
|
|
||||||
|
|
||||||
:param connection: ELBv2 boto3 connection
|
|
||||||
:param module: Ansible module object
|
|
||||||
: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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
for rule in rules:
|
|
||||||
if 'TargetGroupName' in rule['Actions'][0]:
|
|
||||||
rule['Actions'][0]['TargetGroupArn'] = convert_tg_name_to_arn(connection, module, rule['Actions'][0]['TargetGroupName'])
|
|
||||||
del rule['Actions'][0]['TargetGroupName']
|
|
||||||
|
|
||||||
return rules
|
|
||||||
|
|
||||||
|
|
||||||
def compare_listener(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
|
|
||||||
# We wont worry about the Action Type because it is always 'forward'
|
|
||||||
if current_listener['DefaultActions'][0]['TargetGroupArn'] != new_listener['DefaultActions'][0]['TargetGroupArn']:
|
|
||||||
modified_listener['DefaultActions'] = []
|
|
||||||
modified_listener['DefaultActions'].append({})
|
|
||||||
modified_listener['DefaultActions'][0]['TargetGroupArn'] = new_listener['DefaultActions'][0]['TargetGroupArn']
|
|
||||||
modified_listener['DefaultActions'][0]['Type'] = 'forward'
|
|
||||||
|
|
||||||
if modified_listener:
|
|
||||||
return modified_listener
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def compare_condition(current_conditions, condition):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:param current_conditions:
|
|
||||||
:param condition:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
condition_found = False
|
|
||||||
|
|
||||||
for current_condition in current_conditions:
|
|
||||||
if current_condition['Field'] == condition['Field'] and current_condition['Values'][0] == condition['Values'][0]:
|
|
||||||
condition_found = True
|
|
||||||
break
|
|
||||||
|
|
||||||
return condition_found
|
|
||||||
|
|
||||||
|
|
||||||
def compare_rule(current_rule, new_rule):
|
|
||||||
"""
|
|
||||||
Compare two rules.
|
|
||||||
|
|
||||||
:param current_rule:
|
|
||||||
:param new_rule:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
modified_rule = {}
|
|
||||||
|
|
||||||
# Priority
|
|
||||||
if current_rule['Priority'] != new_rule['Priority']:
|
|
||||||
modified_rule['Priority'] = new_rule['Priority']
|
|
||||||
|
|
||||||
# Actions
|
|
||||||
# We wont worry about the Action Type because it is always 'forward'
|
|
||||||
if current_rule['Actions'][0]['TargetGroupArn'] != new_rule['Actions'][0]['TargetGroupArn']:
|
|
||||||
modified_rule['Actions'] = []
|
|
||||||
modified_rule['Actions'].append({})
|
|
||||||
modified_rule['Actions'][0]['TargetGroupArn'] = new_rule['Actions'][0]['TargetGroupArn']
|
|
||||||
modified_rule['Actions'][0]['Type'] = 'forward'
|
|
||||||
|
|
||||||
# Conditions
|
|
||||||
modified_conditions = []
|
|
||||||
for condition in new_rule['Conditions']:
|
|
||||||
if not compare_condition(current_rule['Conditions'], condition):
|
|
||||||
modified_conditions.append(condition)
|
|
||||||
|
|
||||||
if modified_conditions:
|
|
||||||
modified_rule['Conditions'] = modified_conditions
|
|
||||||
|
|
||||||
return modified_rule
|
|
||||||
|
|
||||||
|
|
||||||
def compare_listeners(connection, module, current_listeners, new_listeners, purge_listeners):
|
|
||||||
"""
|
|
||||||
Compare listeners and return listeners to add, listeners to modify and listeners to remove
|
|
||||||
Listeners are compared based on port
|
|
||||||
|
|
||||||
:param connection: ELBv2 boto3 connection
|
|
||||||
:param module: Ansible module object
|
|
||||||
:param current_listeners:
|
|
||||||
:param new_listeners:
|
|
||||||
:param purge_listeners:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
listeners_to_modify = []
|
|
||||||
listeners_to_delete = []
|
|
||||||
|
|
||||||
# Check each current listener port to see if it's been passed to the module
|
|
||||||
for current_listener in current_listeners:
|
|
||||||
current_listener_passed_to_module = False
|
|
||||||
for new_listener in new_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'
|
|
||||||
new_listeners.remove(new_listener)
|
|
||||||
modified_listener = 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 purge_listeners:
|
|
||||||
listeners_to_delete.append(current_listener['ListenerArn'])
|
|
||||||
|
|
||||||
listeners_to_add = new_listeners
|
|
||||||
|
|
||||||
return listeners_to_add, listeners_to_modify, listeners_to_delete
|
|
||||||
|
|
||||||
|
|
||||||
def compare_rules(connection, module, current_listeners, listener):
|
|
||||||
"""
|
|
||||||
Compare rules and return rules to add, rules to modify and rules to remove
|
|
||||||
Rules are compared based on priority
|
|
||||||
|
|
||||||
:param connection: ELBv2 boto3 connection
|
|
||||||
:param module: Ansible module object
|
|
||||||
:param current_listeners: list of listeners currently associated with the ELB
|
|
||||||
:param listener: dict object of a listener passed by the user
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Run through listeners looking for a match (by port) to get the ARN
|
|
||||||
for current_listener in current_listeners:
|
|
||||||
if current_listener['Port'] == listener['Port']:
|
|
||||||
listener['ListenerArn'] = current_listener['ListenerArn']
|
|
||||||
break
|
|
||||||
|
|
||||||
# If the listener exists (i.e. has an ARN) get rules for the listener
|
|
||||||
if 'ListenerArn' in listener:
|
|
||||||
current_rules = get_listener_rules(connection, module, listener['ListenerArn'])
|
|
||||||
else:
|
|
||||||
current_rules = []
|
|
||||||
|
|
||||||
rules_to_modify = []
|
|
||||||
rules_to_delete = []
|
|
||||||
|
|
||||||
for current_rule in current_rules:
|
|
||||||
current_rule_passed_to_module = False
|
|
||||||
for new_rule in listener['Rules'][:]:
|
|
||||||
if current_rule['Priority'] == 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'
|
|
||||||
listener['Rules'].remove(new_rule)
|
|
||||||
modified_rule = 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'])
|
|
||||||
|
|
||||||
rules_to_add = listener['Rules']
|
|
||||||
|
|
||||||
return rules_to_add, rules_to_modify, rules_to_delete
|
|
||||||
|
|
||||||
|
|
||||||
def create_or_update_elb_listeners(connection, module, elb):
|
|
||||||
"""Create or update ELB listeners. Return true if changed, else false"""
|
|
||||||
|
|
||||||
listener_changed = False
|
|
||||||
# Ensure listeners are using Target Group ARN not name
|
|
||||||
listeners = ensure_listeners_default_action_has_arn(connection, module, module.params.get("listeners"))
|
|
||||||
purge_listeners = module.params.get("purge_listeners")
|
|
||||||
|
|
||||||
# Does the ELB have any listeners exist?
|
|
||||||
current_listeners = get_elb_listeners(connection, module, elb['LoadBalancerArn'])
|
|
||||||
|
|
||||||
listeners_to_add, listeners_to_modify, listeners_to_delete = compare_listeners(connection, module, current_listeners, deepcopy(listeners), purge_listeners)
|
|
||||||
|
|
||||||
# Add listeners
|
|
||||||
for listener_to_add in listeners_to_add:
|
|
||||||
try:
|
|
||||||
listener_to_add['LoadBalancerArn'] = elb['LoadBalancerArn']
|
|
||||||
# Rules is not a valid parameter for create_listener
|
|
||||||
if 'Rules' in listener_to_add:
|
|
||||||
listener_to_add.pop('Rules')
|
|
||||||
response = connection.create_listener(**listener_to_add)
|
|
||||||
# Add the new listener
|
|
||||||
current_listeners.append(response['Listeners'][0])
|
|
||||||
listener_changed = True
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
# Modify listeners
|
|
||||||
for listener_to_modify in listeners_to_modify:
|
|
||||||
try:
|
|
||||||
# Rules is not a valid parameter for modify_listener
|
|
||||||
if 'Rules' in listener_to_modify:
|
|
||||||
listener_to_modify.pop('Rules')
|
|
||||||
connection.modify_listener(**listener_to_modify)
|
|
||||||
listener_changed = True
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
# Delete listeners
|
|
||||||
for listener_to_delete in listeners_to_delete:
|
|
||||||
try:
|
|
||||||
connection.delete_listener(ListenerArn=listener_to_delete)
|
|
||||||
listener_changed = True
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
# For each listener, check rules
|
|
||||||
for listener in deepcopy(listeners):
|
|
||||||
if 'Rules' in listener:
|
|
||||||
# Ensure rules are using Target Group ARN not name
|
|
||||||
listener['Rules'] = ensure_rules_action_has_arn(connection, module, listener['Rules'])
|
|
||||||
rules_to_add, rules_to_modify, rules_to_delete = compare_rules(connection, module, current_listeners, listener)
|
|
||||||
|
|
||||||
# Get listener based on port so we can use ARN
|
|
||||||
looked_up_listener = get_listener(connection, module, elb['LoadBalancerArn'], listener['Port'])
|
|
||||||
|
|
||||||
# Delete rules
|
|
||||||
for rule in rules_to_delete:
|
|
||||||
try:
|
|
||||||
connection.delete_rule(RuleArn=rule)
|
|
||||||
listener_changed = True
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
# Add rules
|
|
||||||
for rule in rules_to_add:
|
|
||||||
try:
|
|
||||||
rule['ListenerArn'] = looked_up_listener['ListenerArn']
|
|
||||||
rule['Priority'] = int(rule['Priority'])
|
|
||||||
connection.create_rule(**rule)
|
|
||||||
listener_changed = True
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
# Modify rules
|
|
||||||
for rule in rules_to_modify:
|
|
||||||
try:
|
|
||||||
del rule['Priority']
|
|
||||||
connection.modify_rule(**rule)
|
|
||||||
listener_changed = True
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
return listener_changed
|
|
||||||
|
|
||||||
|
|
||||||
def create_or_update_elb(connection, connection_ec2, module):
|
|
||||||
"""Create ELB or modify main attributes. json_exit here"""
|
"""Create ELB or modify main attributes. json_exit here"""
|
||||||
|
|
||||||
changed = False
|
if elb_obj.elb:
|
||||||
new_load_balancer = False
|
|
||||||
params = dict()
|
|
||||||
params['Name'] = module.params.get("name")
|
|
||||||
params['Subnets'] = module.params.get("subnets")
|
|
||||||
try:
|
|
||||||
params['SecurityGroups'] = get_ec2_security_group_ids_from_names(module.params.get('security_groups'), connection_ec2, boto3=True)
|
|
||||||
except ValueError as e:
|
|
||||||
module.fail_json(msg=str(e), exception=traceback.format_exc())
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
except NoCredentialsError as e:
|
|
||||||
module.fail_json(msg="AWS authentication problem. " + e.message, exception=traceback.format_exc())
|
|
||||||
|
|
||||||
params['Scheme'] = module.params.get("scheme")
|
|
||||||
if module.params.get("tags"):
|
|
||||||
params['Tags'] = ansible_dict_to_boto3_tag_list(module.params.get("tags"))
|
|
||||||
purge_tags = module.params.get("purge_tags")
|
|
||||||
access_logs_enabled = module.params.get("access_logs_enabled")
|
|
||||||
access_logs_s3_bucket = module.params.get("access_logs_s3_bucket")
|
|
||||||
access_logs_s3_prefix = module.params.get("access_logs_s3_prefix")
|
|
||||||
deletion_protection = module.params.get("deletion_protection")
|
|
||||||
idle_timeout = module.params.get("idle_timeout")
|
|
||||||
|
|
||||||
# Does the ELB currently exist?
|
|
||||||
elb = get_elb(connection, module)
|
|
||||||
|
|
||||||
if elb:
|
|
||||||
# ELB exists so check subnets, security groups and tags match what has been passed
|
# ELB exists so check subnets, security groups and tags match what has been passed
|
||||||
|
|
||||||
# Subnets
|
# Subnets
|
||||||
if set(_get_subnet_ids_from_subnet_list(elb['AvailabilityZones'])) != set(params['Subnets']):
|
if not elb_obj.compare_subnets():
|
||||||
try:
|
elb_obj.modify_subnets()
|
||||||
connection.set_subnets(LoadBalancerArn=elb['LoadBalancerArn'], Subnets=params['Subnets'])
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
# Security Groups
|
# Security Groups
|
||||||
if set(elb['SecurityGroups']) != set(params['SecurityGroups']):
|
if not elb_obj.compare_security_groups():
|
||||||
try:
|
elb_obj.modify_security_groups()
|
||||||
connection.set_security_groups(LoadBalancerArn=elb['LoadBalancerArn'], SecurityGroups=params['SecurityGroups'])
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
# Tags - only need to play with tags if tags parameter has been set to something
|
# Tags - only need to play with tags if tags parameter has been set to something
|
||||||
if module.params.get("tags"):
|
if elb_obj.tags is not None:
|
||||||
try:
|
|
||||||
elb_tags = connection.describe_tags(ResourceArns=[elb['LoadBalancerArn']])['TagDescriptions'][0]['Tags']
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
# Delete necessary tags
|
# Delete necessary tags
|
||||||
tags_need_modify, tags_to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(elb_tags), boto3_tag_list_to_ansible_dict(params['Tags']),
|
tags_need_modify, tags_to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(elb_obj.elb['tags']),
|
||||||
purge_tags)
|
boto3_tag_list_to_ansible_dict(elb_obj.tags), elb_obj.purge_tags)
|
||||||
if tags_to_delete:
|
if tags_to_delete:
|
||||||
try:
|
elb_obj.delete_tags(tags_to_delete)
|
||||||
connection.remove_tags(ResourceArns=[elb['LoadBalancerArn']], TagKeys=tags_to_delete)
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
# Add/update tags
|
# Add/update tags
|
||||||
if tags_need_modify:
|
if tags_need_modify:
|
||||||
try:
|
elb_obj.modify_tags()
|
||||||
connection.add_tags(ResourceArns=[elb['LoadBalancerArn']], Tags=params['Tags'])
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
try:
|
# Create load balancer
|
||||||
elb = connection.create_load_balancer(**params)['LoadBalancers'][0]
|
elb_obj.create_elb()
|
||||||
changed = True
|
|
||||||
new_load_balancer = True
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
if module.params.get("wait"):
|
# ELB attributes
|
||||||
status_achieved, new_elb = wait_for_status(connection, module, elb['LoadBalancerArn'], 'active')
|
elb_obj.update_elb_attributes()
|
||||||
|
elb_obj.modify_elb_attributes()
|
||||||
|
|
||||||
# Now set ELB attributes. Use try statement here so we can remove the ELB if this stage fails
|
# Listeners
|
||||||
update_attributes = []
|
listeners_obj = ELBListeners(elb_obj.connection, elb_obj.module, elb_obj.elb['LoadBalancerArn'])
|
||||||
|
|
||||||
# Get current attributes
|
listeners_to_add, listeners_to_modify, listeners_to_delete = listeners_obj.compare_listeners()
|
||||||
current_elb_attributes = get_elb_attributes(connection, module, elb['LoadBalancerArn'])
|
|
||||||
|
|
||||||
if access_logs_enabled and current_elb_attributes['access_logs_s3_enabled'] != "true":
|
# Delete listeners
|
||||||
update_attributes.append({'Key': 'access_logs.s3.enabled', 'Value': "true"})
|
for listener_to_delete in listeners_to_delete:
|
||||||
if not access_logs_enabled and current_elb_attributes['access_logs_s3_enabled'] != "false":
|
listener_obj = ELBListener(elb_obj.connection, elb_obj.module, listener_to_delete, elb_obj.elb['LoadBalancerArn'])
|
||||||
update_attributes.append({'Key': 'access_logs.s3.enabled', 'Value': 'false'})
|
listener_obj.delete()
|
||||||
if access_logs_s3_bucket is not None and access_logs_s3_bucket != current_elb_attributes['access_logs_s3_bucket']:
|
listeners_obj.changed = True
|
||||||
update_attributes.append({'Key': 'access_logs.s3.bucket', 'Value': access_logs_s3_bucket})
|
|
||||||
if access_logs_s3_prefix is not None and access_logs_s3_prefix != current_elb_attributes['access_logs_s3_prefix']:
|
|
||||||
update_attributes.append({'Key': 'access_logs.s3.prefix', 'Value': access_logs_s3_prefix})
|
|
||||||
if deletion_protection and current_elb_attributes['deletion_protection_enabled'] != "true":
|
|
||||||
update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "true"})
|
|
||||||
if not deletion_protection and current_elb_attributes['deletion_protection_enabled'] != "false":
|
|
||||||
update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "false"})
|
|
||||||
if idle_timeout is not None and str(idle_timeout) != current_elb_attributes['idle_timeout_timeout_seconds']:
|
|
||||||
update_attributes.append({'Key': 'idle_timeout.timeout_seconds', 'Value': str(idle_timeout)})
|
|
||||||
|
|
||||||
if update_attributes:
|
# Add listeners
|
||||||
try:
|
for listener_to_add in listeners_to_add:
|
||||||
connection.modify_load_balancer_attributes(LoadBalancerArn=elb['LoadBalancerArn'], Attributes=update_attributes)
|
listener_obj = ELBListener(elb_obj.connection, elb_obj.module, listener_to_add, elb_obj.elb['LoadBalancerArn'])
|
||||||
changed = True
|
listener_obj.add()
|
||||||
except ClientError as e:
|
listeners_obj.changed = True
|
||||||
# Something went wrong setting attributes. If this ELB was created during this task, delete it to leave a consistent state
|
|
||||||
if new_load_balancer:
|
|
||||||
connection.delete_load_balancer(LoadBalancerArn=elb['LoadBalancerArn'])
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
# Now, if required, set ELB listeners. Use try statement here so we can remove the ELB if this stage fails
|
# Modify listeners
|
||||||
try:
|
for listener_to_modify in listeners_to_modify:
|
||||||
listener_changed = create_or_update_elb_listeners(connection, module, elb)
|
listener_obj = ELBListener(elb_obj.connection, elb_obj.module, listener_to_modify, elb_obj.elb['LoadBalancerArn'])
|
||||||
if listener_changed:
|
listener_obj.modify()
|
||||||
changed = True
|
listeners_obj.changed = True
|
||||||
except ClientError as e:
|
|
||||||
# Something went wrong setting listeners. If this ELB was created during this task, delete it to leave a consistent state
|
# If listeners changed, mark ELB as changed
|
||||||
if new_load_balancer:
|
if listeners_obj.changed:
|
||||||
connection.delete_load_balancer(LoadBalancerArn=elb['LoadBalancerArn'])
|
elb_obj.changed = True
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
# Rules of each listener
|
||||||
|
for listener in listeners_obj.listeners:
|
||||||
|
if 'Rules' in listener:
|
||||||
|
rules_obj = ELBListenerRules(elb_obj.connection, elb_obj.module, elb_obj.elb['LoadBalancerArn'], listener['Rules'], listener['Port'])
|
||||||
|
|
||||||
|
rules_to_add, rules_to_modify, rules_to_delete = rules_obj.compare_rules()
|
||||||
|
|
||||||
|
# Delete rules
|
||||||
|
for rule in rules_to_delete:
|
||||||
|
rule_obj = ELBListenerRule(elb_obj.connection, elb_obj.module, {'RuleArn': rule}, rules_obj.listener_arn)
|
||||||
|
rule_obj.delete()
|
||||||
|
elb_obj.changed = True
|
||||||
|
|
||||||
|
# Add rules
|
||||||
|
for rule in rules_to_add:
|
||||||
|
rule_obj = ELBListenerRule(elb_obj.connection, elb_obj.module, rule, rules_obj.listener_arn)
|
||||||
|
rule_obj.create()
|
||||||
|
elb_obj.changed = True
|
||||||
|
|
||||||
|
# Modify rules
|
||||||
|
for rule in rules_to_modify:
|
||||||
|
rule_obj = ELBListenerRule(elb_obj.connection, elb_obj.module, rule, rules_obj.listener_arn)
|
||||||
|
rule_obj.modify()
|
||||||
|
elb_obj.changed = True
|
||||||
|
|
||||||
# Get the ELB again
|
# Get the ELB again
|
||||||
elb = get_elb(connection, module)
|
elb_obj.update()
|
||||||
|
|
||||||
# Get the ELB listeners again
|
# Get the ELB listeners again
|
||||||
elb['listeners'] = get_elb_listeners(connection, module, elb['LoadBalancerArn'])
|
listeners_obj.update()
|
||||||
|
|
||||||
# For each listener, get listener rules
|
# Update the ELB attributes
|
||||||
for listener in elb['listeners']:
|
elb_obj.update_elb_attributes()
|
||||||
listener['rules'] = get_listener_rules(connection, module, listener['ListenerArn'])
|
|
||||||
|
|
||||||
# Get the ELB attributes again
|
# Convert to snake_case and merge in everything we want to return to the user
|
||||||
elb.update(get_elb_attributes(connection, module, elb['LoadBalancerArn']))
|
snaked_elb = camel_dict_to_snake_dict(elb_obj.elb)
|
||||||
|
snaked_elb.update(camel_dict_to_snake_dict(elb_obj.elb_attributes))
|
||||||
|
snaked_elb['listeners'] = []
|
||||||
|
for listener in listeners_obj.current_listeners:
|
||||||
|
# For each listener, get listener rules
|
||||||
|
listener['rules'] = get_elb_listener_rules(elb_obj.connection, elb_obj.module, listener['ListenerArn'])
|
||||||
|
snaked_elb['listeners'].append(camel_dict_to_snake_dict(listener))
|
||||||
|
|
||||||
# Convert to snake_case
|
# Change tags to ansible friendly dict
|
||||||
snaked_elb = camel_dict_to_snake_dict(elb)
|
snaked_elb['tags'] = boto3_tag_list_to_ansible_dict(snaked_elb['tags'])
|
||||||
|
|
||||||
# Get the tags of the ELB
|
elb_obj.module.exit_json(changed=elb_obj.changed, **snaked_elb)
|
||||||
elb_tags = connection.describe_tags(ResourceArns=[elb['LoadBalancerArn']])['TagDescriptions'][0]['Tags']
|
|
||||||
snaked_elb['tags'] = boto3_tag_list_to_ansible_dict(elb_tags)
|
|
||||||
|
|
||||||
module.exit_json(changed=changed, **snaked_elb)
|
|
||||||
|
|
||||||
|
|
||||||
def delete_elb(connection, module):
|
def delete_elb(elb_obj):
|
||||||
|
|
||||||
changed = False
|
if elb_obj.elb:
|
||||||
elb = get_elb(connection, module)
|
elb_obj.delete()
|
||||||
|
|
||||||
if elb:
|
elb_obj.module.exit_json(changed=elb_obj.changed)
|
||||||
try:
|
|
||||||
connection.delete_load_balancer(LoadBalancerArn=elb['LoadBalancerArn'])
|
|
||||||
changed = True
|
|
||||||
except ClientError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
except NoCredentialsError as e:
|
|
||||||
module.fail_json(msg="AWS authentication problem. " + e.message, exception=traceback.format_exc())
|
|
||||||
|
|
||||||
module.exit_json(changed=changed)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -969,9 +489,19 @@ def main():
|
||||||
access_logs_enabled=dict(type='bool'),
|
access_logs_enabled=dict(type='bool'),
|
||||||
access_logs_s3_bucket=dict(type='str'),
|
access_logs_s3_bucket=dict(type='str'),
|
||||||
access_logs_s3_prefix=dict(type='str'),
|
access_logs_s3_prefix=dict(type='str'),
|
||||||
deletion_protection=dict(default=False, type='bool'),
|
deletion_protection=dict(type='bool'),
|
||||||
idle_timeout=dict(type='int'),
|
idle_timeout=dict(type='int'),
|
||||||
listeners=dict(type='list'),
|
listeners=dict(type='list',
|
||||||
|
elements='dict',
|
||||||
|
options=dict(
|
||||||
|
Protocol=dict(type='str', required=True),
|
||||||
|
Port=dict(type='int', required=True),
|
||||||
|
SslPolicy=dict(type='str'),
|
||||||
|
Certificates=dict(type='list'),
|
||||||
|
DefaultActions=dict(type='list', required=True),
|
||||||
|
Rules=dict(type='list')
|
||||||
|
)
|
||||||
|
),
|
||||||
name=dict(required=True, type='str'),
|
name=dict(required=True, type='str'),
|
||||||
purge_listeners=dict(default=True, type='bool'),
|
purge_listeners=dict(default=True, type='bool'),
|
||||||
purge_tags=dict(default=True, type='bool'),
|
purge_tags=dict(default=True, type='bool'),
|
||||||
|
@ -981,48 +511,42 @@ def main():
|
||||||
state=dict(choices=['present', 'absent'], type='str'),
|
state=dict(choices=['present', 'absent'], type='str'),
|
||||||
tags=dict(default={}, type='dict'),
|
tags=dict(default={}, type='dict'),
|
||||||
wait_timeout=dict(type='int'),
|
wait_timeout=dict(type='int'),
|
||||||
wait=dict(type='bool')
|
wait=dict(default=False, type='bool')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=argument_spec,
|
module = AnsibleAWSModule(argument_spec=argument_spec,
|
||||||
required_if=[
|
required_if=[
|
||||||
('state', 'present', ['subnets', 'security_groups'])
|
('state', 'present', ['subnets', 'security_groups'])
|
||||||
],
|
],
|
||||||
required_together=(
|
required_together=(
|
||||||
['access_logs_enabled', 'access_logs_s3_bucket', 'access_logs_s3_prefix']
|
['access_logs_enabled', 'access_logs_s3_bucket', 'access_logs_s3_prefix']
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Quick check of listeners parameters
|
# Quick check of listeners parameters
|
||||||
listeners = module.params.get("listeners")
|
listeners = module.params.get("listeners")
|
||||||
if listeners is not None:
|
if listeners is not None:
|
||||||
for listener in listeners:
|
for listener in listeners:
|
||||||
for key in listener.keys():
|
for key in listener.keys():
|
||||||
if key not in ['Protocol', 'Port', 'SslPolicy', 'Certificates', 'DefaultActions', 'Rules']:
|
if key == 'Protocol' and listener[key] == 'HTTPS':
|
||||||
module.fail_json(msg="listeners parameter contains invalid dict keys. Should be one of 'Protocol', "
|
if listener.get('SslPolicy') is None:
|
||||||
"'Port', 'SslPolicy', 'Certificates', 'DefaultActions', 'Rules'.")
|
module.fail_json(msg="'SslPolicy' is a required listener dict key when Protocol = HTTPS")
|
||||||
# Make sure Port is always an integer
|
|
||||||
elif key == 'Port':
|
|
||||||
listener[key] = int(listener[key])
|
|
||||||
|
|
||||||
if not HAS_BOTO3:
|
if listener.get('Certificates') is None:
|
||||||
module.fail_json(msg='boto3 required for this module')
|
module.fail_json(msg="'Certificates' is a required listener dict key when Protocol = HTTPS")
|
||||||
|
|
||||||
region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
|
connection = module.client('elbv2')
|
||||||
|
connection_ec2 = module.client('ec2')
|
||||||
if region:
|
|
||||||
connection = boto3_conn(module, conn_type='client', resource='elbv2', region=region, endpoint=ec2_url, **aws_connect_params)
|
|
||||||
connection_ec2 = boto3_conn(module, conn_type='client', resource='ec2', region=region, endpoint=ec2_url, **aws_connect_params)
|
|
||||||
else:
|
|
||||||
module.fail_json(msg="region must be specified")
|
|
||||||
|
|
||||||
state = module.params.get("state")
|
state = module.params.get("state")
|
||||||
|
|
||||||
|
elb = ApplicationLoadBalancer(connection, connection_ec2, module)
|
||||||
|
|
||||||
if state == 'present':
|
if state == 'present':
|
||||||
create_or_update_elb(connection, connection_ec2, module)
|
create_or_update_elb(elb)
|
||||||
else:
|
else:
|
||||||
delete_elb(connection, module)
|
delete_elb(elb)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
2
test/integration/targets/elb_application_lb/aliases
Normal file
2
test/integration/targets/elb_application_lb/aliases
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
cloud/aws
|
||||||
|
unsupported
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
# load balancer and target group names have to be less than 32 characters
|
||||||
|
# the 8 digit identifier at the end of resource_prefix helps determine during which test something
|
||||||
|
# was created and allows tests to be run in parallel
|
||||||
|
alb_name: "my-alb-{{ resource_prefix | regex_search('([0-9]+)$') }}"
|
||||||
|
tg_name: "my-tg-{{ resource_prefix | regex_search('([0-9]+)$') }}"
|
|
@ -0,0 +1,3 @@
|
||||||
|
dependencies:
|
||||||
|
- prepare_tests
|
||||||
|
- setup_ec2
|
204
test/integration/targets/elb_application_lb/tasks/main.yml
Normal file
204
test/integration/targets/elb_application_lb/tasks/main.yml
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
- block:
|
||||||
|
|
||||||
|
- name: set connection information for all tasks
|
||||||
|
set_fact:
|
||||||
|
aws_connection_info: &aws_connection_info
|
||||||
|
aws_access_key: "{{ aws_access_key }}"
|
||||||
|
aws_secret_key: "{{ aws_secret_key }}"
|
||||||
|
security_token: "{{ security_token }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
no_log: yes
|
||||||
|
|
||||||
|
- name: create VPC
|
||||||
|
ec2_vpc_net:
|
||||||
|
cidr_block: 10.228.228.0/22
|
||||||
|
name: "{{ resource_prefix }}_vpc"
|
||||||
|
state: present
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: vpc
|
||||||
|
|
||||||
|
- name: create internet gateway
|
||||||
|
ec2_vpc_igw:
|
||||||
|
vpc_id: "{{ vpc.vpc.id }}"
|
||||||
|
state: present
|
||||||
|
tags:
|
||||||
|
Name: "{{ resource_prefix }}"
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: igw
|
||||||
|
|
||||||
|
- name: create public subnet
|
||||||
|
ec2_vpc_subnet:
|
||||||
|
cidr: "{{ item.cidr }}"
|
||||||
|
az: "{{ aws_region}}{{ item.az }}"
|
||||||
|
vpc_id: "{{ vpc.vpc.id }}"
|
||||||
|
state: present
|
||||||
|
tags:
|
||||||
|
Public: "{{ item.public|string }}"
|
||||||
|
Name: "{{ item.public|ternary('public', 'private') }}-{{ item.az }}"
|
||||||
|
<<: *aws_connection_info
|
||||||
|
with_items:
|
||||||
|
- cidr: 10.228.228.0/24
|
||||||
|
az: "a"
|
||||||
|
public: "True"
|
||||||
|
- cidr: 10.228.229.0/24
|
||||||
|
az: "b"
|
||||||
|
public: "True"
|
||||||
|
- cidr: 10.228.230.0/24
|
||||||
|
az: "a"
|
||||||
|
public: "False"
|
||||||
|
- cidr: 10.228.231.0/24
|
||||||
|
az: "b"
|
||||||
|
public: "False"
|
||||||
|
register: subnets
|
||||||
|
|
||||||
|
- ec2_vpc_subnet_facts:
|
||||||
|
filters:
|
||||||
|
vpc-id: "{{ vpc.vpc.id }}"
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: vpc_subnets
|
||||||
|
|
||||||
|
- name: create list of subnet ids
|
||||||
|
set_fact:
|
||||||
|
alb_subnets: "{{ vpc_subnets|json_query('subnets[?tags.Public == `True`].id') }}"
|
||||||
|
private_subnets: "{{ vpc_subnets|json_query('subnets[?tags.Public != `True`].id') }}"
|
||||||
|
|
||||||
|
- name: create a route table
|
||||||
|
ec2_vpc_route_table:
|
||||||
|
vpc_id: "{{ vpc.vpc.id }}"
|
||||||
|
<<: *aws_connection_info
|
||||||
|
tags:
|
||||||
|
Name: igw-route
|
||||||
|
Created: "{{ resource_prefix }}"
|
||||||
|
subnets: "{{ alb_subnets + private_subnets }}"
|
||||||
|
routes:
|
||||||
|
- dest: 0.0.0.0/0
|
||||||
|
gateway_id: "{{ igw.gateway_id }}"
|
||||||
|
register: route_table
|
||||||
|
|
||||||
|
- ec2_group:
|
||||||
|
name: "{{ resource_prefix }}"
|
||||||
|
description: "security group for Ansible ALB integration tests"
|
||||||
|
state: present
|
||||||
|
vpc_id: "{{ vpc.vpc.id }}"
|
||||||
|
rules:
|
||||||
|
- proto: tcp
|
||||||
|
from_port: 1
|
||||||
|
to_port: 65535
|
||||||
|
cidr_ip: 0.0.0.0/0
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: sec_group
|
||||||
|
|
||||||
|
- name: create a target group for testing
|
||||||
|
elb_target_group:
|
||||||
|
name: "{{ tg_name }}"
|
||||||
|
protocol: http
|
||||||
|
port: 80
|
||||||
|
vpc_id: "{{ vpc.vpc.id }}"
|
||||||
|
state: present
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: tg
|
||||||
|
|
||||||
|
- include_tasks: test_alb_bad_listener_options.yml
|
||||||
|
- include_tasks: test_alb_tags.yml
|
||||||
|
- include_tasks: test_creating_alb.yml
|
||||||
|
- include_tasks: test_alb_with_asg.yml
|
||||||
|
- include_tasks: test_modifying_alb_listeners.yml
|
||||||
|
- include_tasks: test_deleting_alb.yml
|
||||||
|
|
||||||
|
always:
|
||||||
|
#############################################################################
|
||||||
|
# TEAR DOWN STARTS HERE
|
||||||
|
#############################################################################
|
||||||
|
- name: destroy ALB
|
||||||
|
elb_application_lb:
|
||||||
|
name: "{{ alb_name }}"
|
||||||
|
state: absent
|
||||||
|
wait: yes
|
||||||
|
wait_timeout: 600
|
||||||
|
<<: *aws_connection_info
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: destroy target group if it was created
|
||||||
|
elb_target_group:
|
||||||
|
name: "{{ tg_name }}"
|
||||||
|
protocol: http
|
||||||
|
port: 80
|
||||||
|
vpc_id: "{{ vpc.vpc.id }}"
|
||||||
|
state: absent
|
||||||
|
wait: yes
|
||||||
|
wait_timeout: 600
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: remove_tg
|
||||||
|
retries: 5
|
||||||
|
delay: 3
|
||||||
|
until: remove_tg is success
|
||||||
|
when: tg is defined
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: destroy sec group
|
||||||
|
ec2_group:
|
||||||
|
name: "{{ sec_group.group_name }}"
|
||||||
|
description: "security group for Ansible ALB integration tests"
|
||||||
|
state: absent
|
||||||
|
vpc_id: "{{ vpc.vpc.id }}"
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: remove_sg
|
||||||
|
retries: 10
|
||||||
|
delay: 5
|
||||||
|
until: remove_sg is success
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: remove route table
|
||||||
|
ec2_vpc_route_table:
|
||||||
|
vpc_id: "{{ vpc.vpc.id }}"
|
||||||
|
route_table_id: "{{ route_table.route_table.route_table_id }}"
|
||||||
|
lookup: id
|
||||||
|
state: absent
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: remove_rt
|
||||||
|
retries: 10
|
||||||
|
delay: 5
|
||||||
|
until: remove_rt is success
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: destroy subnets
|
||||||
|
ec2_vpc_subnet:
|
||||||
|
cidr: "{{ item.cidr }}"
|
||||||
|
vpc_id: "{{ vpc.vpc.id }}"
|
||||||
|
state: absent
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: remove_subnet
|
||||||
|
retries: 10
|
||||||
|
delay: 5
|
||||||
|
until: remove_subnet is success
|
||||||
|
with_items:
|
||||||
|
- cidr: 10.228.228.0/24
|
||||||
|
- cidr: 10.228.229.0/24
|
||||||
|
- cidr: 10.228.230.0/24
|
||||||
|
- cidr: 10.228.231.0/24
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: destroy internet gateway
|
||||||
|
ec2_vpc_igw:
|
||||||
|
vpc_id: "{{ vpc.vpc.id }}"
|
||||||
|
tags:
|
||||||
|
Name: "{{ resource_prefix }}"
|
||||||
|
state: absent
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: remove_igw
|
||||||
|
retries: 10
|
||||||
|
delay: 5
|
||||||
|
until: remove_igw is success
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: destroy VPC
|
||||||
|
ec2_vpc_net:
|
||||||
|
cidr_block: 10.228.228.0/22
|
||||||
|
name: "{{ resource_prefix }}_vpc"
|
||||||
|
state: absent
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: remove_vpc
|
||||||
|
retries: 10
|
||||||
|
delay: 5
|
||||||
|
until: remove_vpc is success
|
||||||
|
ignore_errors: yes
|
|
@ -0,0 +1,71 @@
|
||||||
|
- block:
|
||||||
|
|
||||||
|
- name: set connection information for all tasks
|
||||||
|
set_fact:
|
||||||
|
aws_connection_info: &aws_connection_info
|
||||||
|
aws_access_key: "{{ aws_access_key }}"
|
||||||
|
aws_secret_key: "{{ aws_secret_key }}"
|
||||||
|
security_token: "{{ security_token }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
no_log: yes
|
||||||
|
|
||||||
|
- name: test creating an ALB with invalid listener options
|
||||||
|
elb_application_lb:
|
||||||
|
name: "{{ alb_name }}"
|
||||||
|
subnets: "{{ alb_subnets }}"
|
||||||
|
security_groups: "{{ sec_group.group_id }}"
|
||||||
|
state: present
|
||||||
|
listeners:
|
||||||
|
- Protocol: HTTPS
|
||||||
|
Port: 80
|
||||||
|
DefaultActions:
|
||||||
|
- Type: forward
|
||||||
|
TargetGroupName: "{{ tg_name }}"
|
||||||
|
<<: *aws_connection_info
|
||||||
|
ignore_errors: yes
|
||||||
|
register: alb
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- alb is failed
|
||||||
|
- alb.msg.startswith("'SslPolicy' is a required listener dict key when Protocol = HTTPS")
|
||||||
|
|
||||||
|
- name: test creating an ALB without providing required listener options
|
||||||
|
elb_application_lb:
|
||||||
|
name: "{{ alb_name }}"
|
||||||
|
subnets: "{{ alb_subnets }}"
|
||||||
|
security_groups: "{{ sec_group.group_id }}"
|
||||||
|
state: present
|
||||||
|
listeners:
|
||||||
|
- Port: 80
|
||||||
|
<<: *aws_connection_info
|
||||||
|
ignore_errors: yes
|
||||||
|
register: alb
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- alb is failed
|
||||||
|
- '"missing required arguments" in alb.msg'
|
||||||
|
- '"Protocol" in alb.msg'
|
||||||
|
- '"DefaultActions" in alb.msg'
|
||||||
|
|
||||||
|
- name: test creating an ALB providing an invalid listener option type
|
||||||
|
elb_application_lb:
|
||||||
|
name: "{{ alb_name }}"
|
||||||
|
subnets: "{{ alb_subnets }}"
|
||||||
|
security_groups: "{{ sec_group.group_id }}"
|
||||||
|
state: present
|
||||||
|
listeners:
|
||||||
|
- Protocol: HTTP
|
||||||
|
Port: "bad type"
|
||||||
|
DefaultActions:
|
||||||
|
- Type: forward
|
||||||
|
TargetGroupName: "{{ tg_name }}"
|
||||||
|
<<: *aws_connection_info
|
||||||
|
ignore_errors: yes
|
||||||
|
register: alb
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- alb is failed
|
||||||
|
- "'unable to convert to int' in alb.msg"
|
|
@ -0,0 +1,93 @@
|
||||||
|
- block:
|
||||||
|
|
||||||
|
- name: set connection information for all tasks
|
||||||
|
set_fact:
|
||||||
|
aws_connection_info: &aws_connection_info
|
||||||
|
aws_access_key: "{{ aws_access_key }}"
|
||||||
|
aws_secret_key: "{{ aws_secret_key }}"
|
||||||
|
security_token: "{{ security_token }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
no_log: yes
|
||||||
|
|
||||||
|
- name: create ALB with no listeners
|
||||||
|
elb_application_lb:
|
||||||
|
name: "{{ alb_name }}"
|
||||||
|
subnets: "{{ alb_subnets }}"
|
||||||
|
security_groups: "{{ sec_group.group_id }}"
|
||||||
|
state: present
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: alb
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- alb.changed
|
||||||
|
|
||||||
|
- name: re-create ALB with no listeners
|
||||||
|
elb_application_lb:
|
||||||
|
name: "{{ alb_name }}"
|
||||||
|
subnets: "{{ alb_subnets }}"
|
||||||
|
security_groups: "{{ sec_group.group_id }}"
|
||||||
|
state: present
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: alb
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- not alb.changed
|
||||||
|
|
||||||
|
- name: add tags to ALB
|
||||||
|
elb_application_lb:
|
||||||
|
name: "{{ alb_name }}"
|
||||||
|
subnets: "{{ alb_subnets }}"
|
||||||
|
security_groups: "{{ sec_group.group_id }}"
|
||||||
|
state: present
|
||||||
|
tags:
|
||||||
|
created_by: "ALB test {{ resource_prefix }}"
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: alb
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- alb.changed
|
||||||
|
- 'alb.tags == {"created_by": "ALB test {{ resource_prefix }}"}'
|
||||||
|
|
||||||
|
- name: remove tags from ALB
|
||||||
|
elb_application_lb:
|
||||||
|
name: "{{ alb_name }}"
|
||||||
|
subnets: "{{ alb_subnets }}"
|
||||||
|
security_groups: "{{ sec_group.group_id }}"
|
||||||
|
state: present
|
||||||
|
tags: {}
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: alb
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- alb.changed
|
||||||
|
- not alb.tags
|
||||||
|
|
||||||
|
- name: test idempotence
|
||||||
|
elb_application_lb:
|
||||||
|
name: "{{ alb_name }}"
|
||||||
|
subnets: "{{ alb_subnets }}"
|
||||||
|
security_groups: "{{ sec_group.group_id }}"
|
||||||
|
state: present
|
||||||
|
tags: {}
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: alb
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- not alb.changed
|
||||||
|
- not alb.tags
|
||||||
|
|
||||||
|
- name: destroy ALB with no listeners
|
||||||
|
elb_application_lb:
|
||||||
|
name: "{{ alb_name }}"
|
||||||
|
state: absent
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: alb
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- alb.changed
|
|
@ -0,0 +1,88 @@
|
||||||
|
- block:
|
||||||
|
|
||||||
|
- name: set connection information for all tasks
|
||||||
|
set_fact:
|
||||||
|
aws_connection_info: &aws_connection_info
|
||||||
|
aws_access_key: "{{ aws_access_key }}"
|
||||||
|
aws_secret_key: "{{ aws_secret_key }}"
|
||||||
|
security_token: "{{ security_token }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
no_log: yes
|
||||||
|
|
||||||
|
- ec2_ami_facts:
|
||||||
|
<<: *aws_connection_info
|
||||||
|
filters:
|
||||||
|
architecture: x86_64
|
||||||
|
virtualization-type: hvm
|
||||||
|
root-device-type: ebs
|
||||||
|
name: "amzn-ami-hvm*"
|
||||||
|
register: amis
|
||||||
|
|
||||||
|
- set_fact:
|
||||||
|
latest_amazon_linux: "{{ amis.images | sort(attribute='creation_date') | last }}"
|
||||||
|
|
||||||
|
- ec2_asg:
|
||||||
|
<<: *aws_connection_info
|
||||||
|
state: absent
|
||||||
|
name: "{{ resource_prefix }}-webservers"
|
||||||
|
wait_timeout: 900
|
||||||
|
|
||||||
|
- ec2_lc:
|
||||||
|
<<: *aws_connection_info
|
||||||
|
name: "{{ resource_prefix }}-web-lcfg"
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Create launch config for testing
|
||||||
|
ec2_lc:
|
||||||
|
<<: *aws_connection_info
|
||||||
|
name: "{{ resource_prefix }}-web-lcfg"
|
||||||
|
assign_public_ip: true
|
||||||
|
image_id: "{{ latest_amazon_linux.image_id }}"
|
||||||
|
security_groups: "{{ sec_group.group_id }}"
|
||||||
|
instance_type: t2.medium
|
||||||
|
user_data: |
|
||||||
|
#!/bin/bash
|
||||||
|
set -x
|
||||||
|
yum update -y --nogpgcheck
|
||||||
|
yum install -y --nogpgcheck httpd
|
||||||
|
echo "Hello Ansiblings!" >> /var/www/html/index.html
|
||||||
|
service httpd start
|
||||||
|
volumes:
|
||||||
|
- device_name: /dev/xvda
|
||||||
|
volume_size: 10
|
||||||
|
volume_type: gp2
|
||||||
|
delete_on_termination: true
|
||||||
|
|
||||||
|
- name: Create autoscaling group for app server fleet
|
||||||
|
ec2_asg:
|
||||||
|
<<: *aws_connection_info
|
||||||
|
name: "{{ resource_prefix }}-webservers"
|
||||||
|
vpc_zone_identifier: "{{ alb_subnets }}"
|
||||||
|
launch_config_name: "{{ resource_prefix }}-web-lcfg"
|
||||||
|
termination_policies:
|
||||||
|
- OldestLaunchConfiguration
|
||||||
|
- Default
|
||||||
|
health_check_period: 600
|
||||||
|
health_check_type: EC2
|
||||||
|
replace_all_instances: true
|
||||||
|
min_size: 0
|
||||||
|
max_size: 2
|
||||||
|
desired_capacity: 1
|
||||||
|
wait_for_instances: true
|
||||||
|
target_group_arns:
|
||||||
|
- "{{ tg.target_group_arn }}"
|
||||||
|
|
||||||
|
always:
|
||||||
|
|
||||||
|
- ec2_asg:
|
||||||
|
<<: *aws_connection_info
|
||||||
|
state: absent
|
||||||
|
name: "{{ resource_prefix }}-webservers"
|
||||||
|
wait_timeout: 900
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- ec2_lc:
|
||||||
|
<<: *aws_connection_info
|
||||||
|
name: "{{ resource_prefix }}-web-lcfg"
|
||||||
|
state: absent
|
||||||
|
ignore_errors: yes
|
|
@ -0,0 +1,52 @@
|
||||||
|
- block:
|
||||||
|
|
||||||
|
- name: set connection information for all tasks
|
||||||
|
set_fact:
|
||||||
|
aws_connection_info: &aws_connection_info
|
||||||
|
aws_access_key: "{{ aws_access_key }}"
|
||||||
|
aws_secret_key: "{{ aws_secret_key }}"
|
||||||
|
security_token: "{{ security_token }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
no_log: yes
|
||||||
|
|
||||||
|
- name: create ALB with a listener
|
||||||
|
elb_application_lb:
|
||||||
|
name: "{{ alb_name }}"
|
||||||
|
subnets: "{{ alb_subnets }}"
|
||||||
|
security_groups: "{{ sec_group.group_id }}"
|
||||||
|
state: present
|
||||||
|
listeners:
|
||||||
|
- Protocol: HTTP
|
||||||
|
Port: 80
|
||||||
|
DefaultActions:
|
||||||
|
- Type: forward
|
||||||
|
TargetGroupName: "{{ tg_name }}"
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: alb
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- alb.changed
|
||||||
|
- alb.listeners|length == 1
|
||||||
|
- alb.listeners[0].rules|length == 1
|
||||||
|
|
||||||
|
- name: test idempotence creating ALB with a listener
|
||||||
|
elb_application_lb:
|
||||||
|
name: "{{ alb_name }}"
|
||||||
|
subnets: "{{ alb_subnets }}"
|
||||||
|
security_groups: "{{ sec_group.group_id }}"
|
||||||
|
state: present
|
||||||
|
listeners:
|
||||||
|
- Protocol: HTTP
|
||||||
|
Port: 80
|
||||||
|
DefaultActions:
|
||||||
|
- Type: forward
|
||||||
|
TargetGroupName: "{{ tg_name }}"
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: alb
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- not alb.changed
|
||||||
|
- alb.listeners|length == 1
|
||||||
|
- alb.listeners[0].rules|length == 1
|
|
@ -0,0 +1,52 @@
|
||||||
|
- block:
|
||||||
|
|
||||||
|
- name: set connection information for all tasks
|
||||||
|
set_fact:
|
||||||
|
aws_connection_info: &aws_connection_info
|
||||||
|
aws_access_key: "{{ aws_access_key }}"
|
||||||
|
aws_secret_key: "{{ aws_secret_key }}"
|
||||||
|
security_token: "{{ security_token }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
no_log: yes
|
||||||
|
|
||||||
|
- name: destroy ALB with listener
|
||||||
|
elb_application_lb:
|
||||||
|
name: "{{ alb_name }}"
|
||||||
|
subnets: "{{ alb_subnets }}"
|
||||||
|
security_groups: "{{ sec_group.group_id }}"
|
||||||
|
state: absent
|
||||||
|
listeners:
|
||||||
|
- Protocol: HTTP
|
||||||
|
Port: 80
|
||||||
|
DefaultActions:
|
||||||
|
- Type: forward
|
||||||
|
TargetGroupName: "{{ tg_name }}"
|
||||||
|
<<: *aws_connection_info
|
||||||
|
wait: yes
|
||||||
|
wait_timeout: 300
|
||||||
|
register: alb
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- alb.changed
|
||||||
|
|
||||||
|
- name: test idempotence
|
||||||
|
elb_application_lb:
|
||||||
|
name: "{{ alb_name }}"
|
||||||
|
subnets: "{{ alb_subnets }}"
|
||||||
|
security_groups: "{{ sec_group.group_id }}"
|
||||||
|
state: absent
|
||||||
|
listeners:
|
||||||
|
- Protocol: HTTP
|
||||||
|
Port: 80
|
||||||
|
DefaultActions:
|
||||||
|
- Type: forward
|
||||||
|
TargetGroupName: "{{ tg_name }}"
|
||||||
|
<<: *aws_connection_info
|
||||||
|
wait: yes
|
||||||
|
wait_timeout: 300
|
||||||
|
register: alb
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- not alb.changed
|
|
@ -92,9 +92,7 @@ lib/ansible/modules/cloud/amazon/elasticache.py E324
|
||||||
lib/ansible/modules/cloud/amazon/elasticache.py E326
|
lib/ansible/modules/cloud/amazon/elasticache.py E326
|
||||||
lib/ansible/modules/cloud/amazon/elasticache_parameter_group.py E326
|
lib/ansible/modules/cloud/amazon/elasticache_parameter_group.py E326
|
||||||
lib/ansible/modules/cloud/amazon/elasticache_subnet_group.py E324
|
lib/ansible/modules/cloud/amazon/elasticache_subnet_group.py E324
|
||||||
lib/ansible/modules/cloud/amazon/elb_application_lb.py E322
|
|
||||||
lib/ansible/modules/cloud/amazon/elb_application_lb.py E324
|
lib/ansible/modules/cloud/amazon/elb_application_lb.py E324
|
||||||
lib/ansible/modules/cloud/amazon/elb_application_lb.py E325
|
|
||||||
lib/ansible/modules/cloud/amazon/elb_classic_lb_facts.py E323
|
lib/ansible/modules/cloud/amazon/elb_classic_lb_facts.py E323
|
||||||
lib/ansible/modules/cloud/amazon/elb_instance.py E326
|
lib/ansible/modules/cloud/amazon/elb_instance.py E326
|
||||||
lib/ansible/modules/cloud/amazon/elb_target.py E327
|
lib/ansible/modules/cloud/amazon/elb_target.py E327
|
||||||
|
|
|
@ -1,154 +0,0 @@
|
||||||
#
|
|
||||||
# (c) 2017 Michael Tinning
|
|
||||||
#
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
import json
|
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from ansible.module_utils._text import to_bytes
|
|
||||||
from ansible.module_utils import basic
|
|
||||||
from ansible.module_utils.ec2 import HAS_BOTO3
|
|
||||||
|
|
||||||
if not HAS_BOTO3:
|
|
||||||
pytestmark = pytest.mark.skip("test_elb_application_lb.py requires the `boto3` and `botocore` modules")
|
|
||||||
|
|
||||||
import ansible.modules.cloud.amazon.elb_application_lb as elb_module
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def listener():
|
|
||||||
return {
|
|
||||||
'Protocol': 'HTTP',
|
|
||||||
'Port': 80,
|
|
||||||
'DefaultActions': [{
|
|
||||||
'Type': 'forward',
|
|
||||||
'TargetGroupName': 'target-group'
|
|
||||||
}],
|
|
||||||
'Rules': [{
|
|
||||||
'Conditions': [{
|
|
||||||
'Field': 'host-header',
|
|
||||||
'Values': [
|
|
||||||
'www.example.com'
|
|
||||||
]
|
|
||||||
}],
|
|
||||||
'Priority': 1,
|
|
||||||
'Actions': [{
|
|
||||||
'TargetGroupName': 'other-target-group',
|
|
||||||
'Type': 'forward'
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def compare_listeners(mocker):
|
|
||||||
return mocker.Mock()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def ensure_listeners(mocker):
|
|
||||||
ensure_listeners_mock = mocker.Mock()
|
|
||||||
ensure_listeners_mock.return_value = []
|
|
||||||
return ensure_listeners_mock
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def compare_rules(mocker):
|
|
||||||
compare_rules_mock = mocker.Mock()
|
|
||||||
compare_rules_mock.return_value = ([], [], [])
|
|
||||||
return compare_rules_mock
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def get_elb_listeners(mocker):
|
|
||||||
get_elb_listeners_mock = mocker.Mock()
|
|
||||||
get_elb_listeners_mock.return_value = []
|
|
||||||
return get_elb_listeners_mock
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def elb(mocker, monkeypatch, compare_listeners, ensure_listeners, compare_rules, get_elb_listeners):
|
|
||||||
monkeypatch.setattr(elb_module, "ensure_listeners_default_action_has_arn", ensure_listeners)
|
|
||||||
monkeypatch.setattr(elb_module, "get_elb_listeners", get_elb_listeners)
|
|
||||||
monkeypatch.setattr(elb_module, "ensure_rules_action_has_arn", mocker.Mock())
|
|
||||||
monkeypatch.setattr(elb_module, "get_listener", mocker.Mock())
|
|
||||||
monkeypatch.setattr(elb_module, "compare_rules", compare_rules)
|
|
||||||
monkeypatch.setattr(elb_module, "compare_listeners", compare_listeners)
|
|
||||||
return elb_module
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def created_listener(mocker, listener):
|
|
||||||
return {
|
|
||||||
'Port': listener['Port'],
|
|
||||||
'ListenerArn': 'new-listener-arn'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def connection(mocker, created_listener):
|
|
||||||
connection_mock = mocker.Mock()
|
|
||||||
connection_mock.create_listener.return_value = {
|
|
||||||
'Listeners': [created_listener]
|
|
||||||
}
|
|
||||||
return connection_mock
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def existing_elb():
|
|
||||||
return {'LoadBalancerArn': 'fake'}
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_listeners_called_with_correct_args(mocker, connection, listener, elb, compare_listeners, existing_elb):
|
|
||||||
compare_listeners.return_value = ([listener], [], [])
|
|
||||||
|
|
||||||
elb.create_or_update_elb_listeners(connection, mocker.Mock(), existing_elb)
|
|
||||||
|
|
||||||
connection.create_listener.assert_called_once_with(
|
|
||||||
Protocol=listener['Protocol'],
|
|
||||||
Port=listener['Port'],
|
|
||||||
DefaultActions=listener['DefaultActions'],
|
|
||||||
LoadBalancerArn=existing_elb['LoadBalancerArn']
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_modify_listeners_called_with_correct_args(mocker, connection, listener, elb, compare_listeners, existing_elb):
|
|
||||||
# In the case of modify listener, LoadBalancerArn is set in compare_listeners
|
|
||||||
listener['LoadBalancerArn'] = existing_elb['LoadBalancerArn']
|
|
||||||
compare_listeners.return_value = ([], [listener], [])
|
|
||||||
|
|
||||||
elb.create_or_update_elb_listeners(connection, mocker.Mock(), existing_elb)
|
|
||||||
|
|
||||||
connection.modify_listener.assert_called_once_with(
|
|
||||||
Protocol=listener['Protocol'],
|
|
||||||
Port=listener['Port'],
|
|
||||||
DefaultActions=listener['DefaultActions'],
|
|
||||||
LoadBalancerArn=existing_elb['LoadBalancerArn']
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_compare_rules_called_with_new_listener(
|
|
||||||
mocker,
|
|
||||||
connection,
|
|
||||||
listener,
|
|
||||||
elb,
|
|
||||||
compare_listeners,
|
|
||||||
ensure_listeners,
|
|
||||||
compare_rules,
|
|
||||||
existing_elb,
|
|
||||||
created_listener
|
|
||||||
):
|
|
||||||
compare_listeners.return_value = ([listener], [], [])
|
|
||||||
listener_from_ensure_listeners = deepcopy(listener)
|
|
||||||
ensure_listeners.return_value = [listener_from_ensure_listeners]
|
|
||||||
|
|
||||||
elb.create_or_update_elb_listeners(connection, mocker.Mock(), existing_elb)
|
|
||||||
|
|
||||||
(_conn, _module, current_listeners, _listener), _kwargs = compare_rules.call_args
|
|
||||||
|
|
||||||
assert created_listener in current_listeners
|
|
Loading…
Reference in a new issue