mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-22 12:50:22 -07: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
|
@ -101,6 +101,17 @@ options:
|
|||
description:
|
||||
- A dictionary of one or more tags to assign to the load balancer.
|
||||
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:
|
||||
- aws
|
||||
- ec2
|
||||
|
@ -342,623 +353,132 @@ vpc_id:
|
|||
type: string
|
||||
sample: vpc-0011223344
|
||||
'''
|
||||
import time
|
||||
import collections
|
||||
from copy import deepcopy
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
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, get_ec2_security_group_ids_from_names, \
|
||||
ansible_dict_to_boto3_tag_list, boto3_tag_list_to_ansible_dict, compare_aws_tags, HAS_BOTO3
|
||||
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||
from ansible.module_utils.ec2 import boto3_conn, get_aws_connection_info, camel_dict_to_snake_dict, ec2_argument_spec, \
|
||||
boto3_tag_list_to_ansible_dict, compare_aws_tags, HAS_BOTO3
|
||||
|
||||
try:
|
||||
import boto3
|
||||
from botocore.exceptions import ClientError, NoCredentialsError
|
||||
except ImportError:
|
||||
HAS_BOTO3 = False
|
||||
from ansible.module_utils.aws.elbv2 import ApplicationLoadBalancer, ELBListeners, ELBListener, ELBListenerRules, ELBListenerRule
|
||||
from ansible.module_utils.aws.elb_utils import get_elb_listener_rules
|
||||
|
||||
|
||||
def convert_tg_name_to_arn(connection, module, tg_name):
|
||||
|
||||
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):
|
||||
def create_or_update_elb(elb_obj):
|
||||
"""Create ELB or modify main attributes. json_exit here"""
|
||||
|
||||
changed = False
|
||||
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:
|
||||
if elb_obj.elb:
|
||||
# ELB exists so check subnets, security groups and tags match what has been passed
|
||||
|
||||
# Subnets
|
||||
if set(_get_subnet_ids_from_subnet_list(elb['AvailabilityZones'])) != set(params['Subnets']):
|
||||
try:
|
||||
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
|
||||
if not elb_obj.compare_subnets():
|
||||
elb_obj.modify_subnets()
|
||||
|
||||
# Security Groups
|
||||
if set(elb['SecurityGroups']) != set(params['SecurityGroups']):
|
||||
try:
|
||||
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
|
||||
if not elb_obj.compare_security_groups():
|
||||
elb_obj.modify_security_groups()
|
||||
|
||||
# Tags - only need to play with tags if tags parameter has been set to something
|
||||
if module.params.get("tags"):
|
||||
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))
|
||||
if elb_obj.tags is not None:
|
||||
|
||||
# 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']),
|
||||
purge_tags)
|
||||
tags_need_modify, tags_to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(elb_obj.elb['tags']),
|
||||
boto3_tag_list_to_ansible_dict(elb_obj.tags), elb_obj.purge_tags)
|
||||
if tags_to_delete:
|
||||
try:
|
||||
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
|
||||
elb_obj.delete_tags(tags_to_delete)
|
||||
|
||||
# Add/update tags
|
||||
if tags_need_modify:
|
||||
try:
|
||||
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
|
||||
elb_obj.modify_tags()
|
||||
|
||||
else:
|
||||
try:
|
||||
elb = connection.create_load_balancer(**params)['LoadBalancers'][0]
|
||||
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))
|
||||
# Create load balancer
|
||||
elb_obj.create_elb()
|
||||
|
||||
if module.params.get("wait"):
|
||||
status_achieved, new_elb = wait_for_status(connection, module, elb['LoadBalancerArn'], 'active')
|
||||
# ELB attributes
|
||||
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
|
||||
update_attributes = []
|
||||
# Listeners
|
||||
listeners_obj = ELBListeners(elb_obj.connection, elb_obj.module, elb_obj.elb['LoadBalancerArn'])
|
||||
|
||||
# Get current attributes
|
||||
current_elb_attributes = get_elb_attributes(connection, module, elb['LoadBalancerArn'])
|
||||
listeners_to_add, listeners_to_modify, listeners_to_delete = listeners_obj.compare_listeners()
|
||||
|
||||
if access_logs_enabled and current_elb_attributes['access_logs_s3_enabled'] != "true":
|
||||
update_attributes.append({'Key': 'access_logs.s3.enabled', 'Value': "true"})
|
||||
if not access_logs_enabled and current_elb_attributes['access_logs_s3_enabled'] != "false":
|
||||
update_attributes.append({'Key': 'access_logs.s3.enabled', 'Value': 'false'})
|
||||
if access_logs_s3_bucket is not None and access_logs_s3_bucket != current_elb_attributes['access_logs_s3_bucket']:
|
||||
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)})
|
||||
# Delete listeners
|
||||
for listener_to_delete in listeners_to_delete:
|
||||
listener_obj = ELBListener(elb_obj.connection, elb_obj.module, listener_to_delete, elb_obj.elb['LoadBalancerArn'])
|
||||
listener_obj.delete()
|
||||
listeners_obj.changed = True
|
||||
|
||||
if update_attributes:
|
||||
try:
|
||||
connection.modify_load_balancer_attributes(LoadBalancerArn=elb['LoadBalancerArn'], Attributes=update_attributes)
|
||||
changed = True
|
||||
except ClientError as e:
|
||||
# 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))
|
||||
# Add listeners
|
||||
for listener_to_add in listeners_to_add:
|
||||
listener_obj = ELBListener(elb_obj.connection, elb_obj.module, listener_to_add, elb_obj.elb['LoadBalancerArn'])
|
||||
listener_obj.add()
|
||||
listeners_obj.changed = True
|
||||
|
||||
# Now, if required, set ELB listeners. Use try statement here so we can remove the ELB if this stage fails
|
||||
try:
|
||||
listener_changed = create_or_update_elb_listeners(connection, module, elb)
|
||||
if listener_changed:
|
||||
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 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))
|
||||
# Modify listeners
|
||||
for listener_to_modify in listeners_to_modify:
|
||||
listener_obj = ELBListener(elb_obj.connection, elb_obj.module, listener_to_modify, elb_obj.elb['LoadBalancerArn'])
|
||||
listener_obj.modify()
|
||||
listeners_obj.changed = True
|
||||
|
||||
# If listeners changed, mark ELB as changed
|
||||
if listeners_obj.changed:
|
||||
elb_obj.changed = True
|
||||
|
||||
# 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
|
||||
elb = get_elb(connection, module)
|
||||
elb_obj.update()
|
||||
|
||||
# Get the ELB listeners again
|
||||
elb['listeners'] = get_elb_listeners(connection, module, elb['LoadBalancerArn'])
|
||||
listeners_obj.update()
|
||||
|
||||
# For each listener, get listener rules
|
||||
for listener in elb['listeners']:
|
||||
listener['rules'] = get_listener_rules(connection, module, listener['ListenerArn'])
|
||||
# Update the ELB attributes
|
||||
elb_obj.update_elb_attributes()
|
||||
|
||||
# Get the ELB attributes again
|
||||
elb.update(get_elb_attributes(connection, module, elb['LoadBalancerArn']))
|
||||
# Convert to snake_case and merge in everything we want to return to the user
|
||||
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
|
||||
snaked_elb = camel_dict_to_snake_dict(elb)
|
||||
# Change tags to ansible friendly dict
|
||||
snaked_elb['tags'] = boto3_tag_list_to_ansible_dict(snaked_elb['tags'])
|
||||
|
||||
# Get the tags of the 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)
|
||||
elb_obj.module.exit_json(changed=elb_obj.changed, **snaked_elb)
|
||||
|
||||
|
||||
def delete_elb(connection, module):
|
||||
def delete_elb(elb_obj):
|
||||
|
||||
changed = False
|
||||
elb = get_elb(connection, module)
|
||||
if elb_obj.elb:
|
||||
elb_obj.delete()
|
||||
|
||||
if elb:
|
||||
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)
|
||||
elb_obj.module.exit_json(changed=elb_obj.changed)
|
||||
|
||||
|
||||
def main():
|
||||
|
@ -969,9 +489,19 @@ def main():
|
|||
access_logs_enabled=dict(type='bool'),
|
||||
access_logs_s3_bucket=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'),
|
||||
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'),
|
||||
purge_listeners=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'),
|
||||
tags=dict(default={}, type='dict'),
|
||||
wait_timeout=dict(type='int'),
|
||||
wait=dict(type='bool')
|
||||
wait=dict(default=False, type='bool')
|
||||
)
|
||||
)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
required_if=[
|
||||
('state', 'present', ['subnets', 'security_groups'])
|
||||
],
|
||||
required_together=(
|
||||
['access_logs_enabled', 'access_logs_s3_bucket', 'access_logs_s3_prefix']
|
||||
)
|
||||
)
|
||||
module = AnsibleAWSModule(argument_spec=argument_spec,
|
||||
required_if=[
|
||||
('state', 'present', ['subnets', 'security_groups'])
|
||||
],
|
||||
required_together=(
|
||||
['access_logs_enabled', 'access_logs_s3_bucket', 'access_logs_s3_prefix']
|
||||
)
|
||||
)
|
||||
|
||||
# Quick check of listeners parameters
|
||||
listeners = module.params.get("listeners")
|
||||
if listeners is not None:
|
||||
for listener in listeners:
|
||||
for key in listener.keys():
|
||||
if key not in ['Protocol', 'Port', 'SslPolicy', 'Certificates', 'DefaultActions', 'Rules']:
|
||||
module.fail_json(msg="listeners parameter contains invalid dict keys. Should be one of 'Protocol', "
|
||||
"'Port', 'SslPolicy', 'Certificates', 'DefaultActions', 'Rules'.")
|
||||
# Make sure Port is always an integer
|
||||
elif key == 'Port':
|
||||
listener[key] = int(listener[key])
|
||||
if key == 'Protocol' and listener[key] == 'HTTPS':
|
||||
if listener.get('SslPolicy') is None:
|
||||
module.fail_json(msg="'SslPolicy' is a required listener dict key when Protocol = HTTPS")
|
||||
|
||||
if not HAS_BOTO3:
|
||||
module.fail_json(msg='boto3 required for this module')
|
||||
if listener.get('Certificates') is None:
|
||||
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)
|
||||
|
||||
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")
|
||||
connection = module.client('elbv2')
|
||||
connection_ec2 = module.client('ec2')
|
||||
|
||||
state = module.params.get("state")
|
||||
|
||||
elb = ApplicationLoadBalancer(connection, connection_ec2, module)
|
||||
|
||||
if state == 'present':
|
||||
create_or_update_elb(connection, connection_ec2, module)
|
||||
create_or_update_elb(elb)
|
||||
else:
|
||||
delete_elb(connection, module)
|
||||
delete_elb(elb)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue