ability to use lambda target in elb_target_group (#57394)

* enable elb_lambda_target test
This commit is contained in:
Markus Bergholz 2019-07-31 00:35:36 +02:00 committed by Jill R
parent e07c4f41d7
commit 196347ff32
5 changed files with 332 additions and 88 deletions

View file

@ -110,14 +110,15 @@ options:
target_type: target_type:
description: description:
- The type of target that you must specify when registering targets with this target group. The possible values are - The type of target that you must specify when registering targets with this target group. The possible values are
C(instance) (targets are specified by instance ID) or C(ip) (targets are specified by IP address). C(instance) (targets are specified by instance ID), C(ip) (targets are specified by IP address) or C(lambda) (target is specified by ARN).
Note that you can't specify targets for a target group using both instance IDs and IP addresses. Note that you can't specify targets for a target group using more than one type. Target type lambda only accept one target. When more than
one target is specified, only the first one is used. All additional targets are ignored.
If the target type is ip, specify IP addresses from the subnets of the virtual private cloud (VPC) for the target If the target type is ip, specify IP addresses from the subnets of the virtual private cloud (VPC) for the target
group, the RFC 1918 range (10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16), and the RFC 6598 range (100.64.0.0/10). group, the RFC 1918 range (10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16), and the RFC 6598 range (100.64.0.0/10).
You can't specify publicly routable IP addresses. You can't specify publicly routable IP addresses.
required: false required: false
default: instance default: instance
choices: ['instance', 'ip'] choices: ['instance', 'ip', 'lambda']
version_added: 2.5 version_added: 2.5
targets: targets:
description: description:
@ -212,6 +213,37 @@ EXAMPLES = '''
wait_timeout: 200 wait_timeout: 200
wait: True wait: True
# Using lambda as targets require that the target group
# itself is allow to invoke the lambda function.
# therefore you need first to create an empty target group
# to receive its arn, second, allow the target group
# to invoke the lamba function and third, add the target
# to the target group
- name: first, create empty target group
elb_target_group:
name: my-lambda-targetgroup
target_type: lambda
state: present
modify_targets: False
register: out
- name: second, allow invoke of the lambda
lambda_policy:
state: "{{ state | default('present') }}"
function_name: my-lambda-function
statement_id: someID
action: lambda:InvokeFunction
principal: elasticloadbalancing.amazonaws.com
source_arn: "{{ out.target_group_arn }}"
- name: third, add target
elb_target_group:
name: my-lambda-targetgroup
target_type: lambda
state: present
targets:
- Id: arn:aws:lambda:eu-central-1:123456789012:function:my-lambda-function
''' '''
RETURN = ''' RETURN = '''
@ -389,9 +421,10 @@ def create_or_update_target_group(connection, module):
new_target_group = False new_target_group = False
params = dict() params = dict()
params['Name'] = module.params.get("name") params['Name'] = module.params.get("name")
params['Protocol'] = module.params.get("protocol").upper() if module.params.get("target_type") != "lambda":
params['Port'] = module.params.get("port") params['Protocol'] = module.params.get("protocol").upper()
params['VpcId'] = module.params.get("vpc_id") params['Port'] = module.params.get("port")
params['VpcId'] = module.params.get("vpc_id")
tags = module.params.get("tags") tags = module.params.get("tags")
purge_tags = module.params.get("purge_tags") purge_tags = module.params.get("purge_tags")
deregistration_delay_timeout = module.params.get("deregistration_delay_timeout") deregistration_delay_timeout = module.params.get("deregistration_delay_timeout")
@ -505,90 +538,128 @@ def create_or_update_target_group(connection, module):
# Do we need to modify targets? # Do we need to modify targets?
if module.params.get("modify_targets"): if module.params.get("modify_targets"):
# get list of current target instances. I can't see anything like a describe targets in the doco so
# describe_target_health seems to be the only way to get them
try:
current_targets = connection.describe_target_health(
TargetGroupArn=tg['TargetGroupArn'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't get target group health")
if module.params.get("targets"): if module.params.get("targets"):
params['Targets'] = module.params.get("targets")
# Correct type of target ports if module.params.get("target_type") != "lambda":
for target in params['Targets']: params['Targets'] = module.params.get("targets")
target['Port'] = int(target.get('Port', module.params.get('port')))
# get list of current target instances. I can't see anything like a describe targets in the doco so # Correct type of target ports
# describe_target_health seems to be the only way to get them
try:
current_targets = connection.describe_target_health(TargetGroupArn=tg['TargetGroupArn'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't get target group health")
current_instance_ids = []
for instance in current_targets['TargetHealthDescriptions']:
current_instance_ids.append(instance['Target']['Id'])
new_instance_ids = []
for instance in params['Targets']:
new_instance_ids.append(instance['Id'])
add_instances = set(new_instance_ids) - set(current_instance_ids)
if add_instances:
instances_to_add = []
for target in params['Targets']: for target in params['Targets']:
if target['Id'] in add_instances: target['Port'] = int(target.get('Port', module.params.get('port')))
instances_to_add.append({'Id': target['Id'], 'Port': target['Port']})
changed = True current_instance_ids = []
for instance in current_targets['TargetHealthDescriptions']:
current_instance_ids.append(instance['Target']['Id'])
new_instance_ids = []
for instance in params['Targets']:
new_instance_ids.append(instance['Id'])
add_instances = set(new_instance_ids) - set(current_instance_ids)
if add_instances:
instances_to_add = []
for target in params['Targets']:
if target['Id'] in add_instances:
instances_to_add.append({'Id': target['Id'], 'Port': target['Port']})
changed = True
try:
connection.register_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=instances_to_add)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't register targets")
if module.params.get("wait"):
status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], instances_to_add, 'healthy')
if not status_achieved:
module.fail_json(msg='Error waiting for target registration to be healthy - please check the AWS console')
remove_instances = set(current_instance_ids) - set(new_instance_ids)
if remove_instances:
instances_to_remove = []
for target in current_targets['TargetHealthDescriptions']:
if target['Target']['Id'] in remove_instances:
instances_to_remove.append({'Id': target['Target']['Id'], 'Port': target['Target']['Port']})
changed = True
try:
connection.deregister_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=instances_to_remove)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't remove targets")
if module.params.get("wait"):
status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], instances_to_remove, 'unused')
if not status_achieved:
module.fail_json(msg='Error waiting for target deregistration - please check the AWS console')
# register lambda target
else:
try: try:
connection.register_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=instances_to_add) changed = False
target = module.params.get("targets")[0]
if len(current_targets["TargetHealthDescriptions"]) == 0:
changed = True
else:
for item in current_targets["TargetHealthDescriptions"]:
if target["Id"] != item["Target"]["Id"]:
changed = True
break # only one target is possible with lambda
if changed:
if target.get("Id"):
response = connection.register_targets(
TargetGroupArn=tg['TargetGroupArn'],
Targets=[
{
"Id": target['Id']
}
]
)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't register targets") module.fail_json_aws(
e, msg="Couldn't register targets")
else:
if module.params.get("target_type") != "lambda":
if module.params.get("wait"): current_instances = current_targets['TargetHealthDescriptions']
status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], instances_to_add, 'healthy')
if not status_achieved:
module.fail_json(msg='Error waiting for target registration to be healthy - please check the AWS console')
remove_instances = set(current_instance_ids) - set(new_instance_ids) if current_instances:
instances_to_remove = []
if remove_instances: for target in current_targets['TargetHealthDescriptions']:
instances_to_remove = []
for target in current_targets['TargetHealthDescriptions']:
if target['Target']['Id'] in remove_instances:
instances_to_remove.append({'Id': target['Target']['Id'], 'Port': target['Target']['Port']}) instances_to_remove.append({'Id': target['Target']['Id'], 'Port': target['Target']['Port']})
changed = True changed = True
try: try:
connection.deregister_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=instances_to_remove) connection.deregister_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=instances_to_remove)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't remove targets") module.fail_json_aws(e, msg="Couldn't remove targets")
if module.params.get("wait"): if module.params.get("wait"):
status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], instances_to_remove, 'unused') status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], instances_to_remove, 'unused')
if not status_achieved: if not status_achieved:
module.fail_json(msg='Error waiting for target deregistration - please check the AWS console') module.fail_json(msg='Error waiting for target deregistration - please check the AWS console')
else:
try:
current_targets = connection.describe_target_health(TargetGroupArn=tg['TargetGroupArn'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't get target health")
current_instances = current_targets['TargetHealthDescriptions'] # remove lambda targets
else:
if current_instances: changed = False
instances_to_remove = [] if current_targets["TargetHealthDescriptions"]:
for target in current_targets['TargetHealthDescriptions']: changed = True
instances_to_remove.append({'Id': target['Target']['Id'], 'Port': target['Target']['Port']}) # only one target is possible with lambda
target_to_remove = current_targets["TargetHealthDescriptions"][0]["Target"]["Id"]
changed = True if changed:
try: connection.deregister_targets(
connection.deregister_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=instances_to_remove) TargetGroupArn=tg['TargetGroupArn'], Targets=[{"Id": target_to_remove}])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't remove targets")
if module.params.get("wait"):
status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], instances_to_remove, 'unused')
if not status_achieved:
module.fail_json(msg='Error waiting for target deregistration - please check the AWS console')
else: else:
try: try:
connection.create_target_group(**params) connection.create_target_group(**params)
@ -600,16 +671,33 @@ def create_or_update_target_group(connection, module):
tg = get_target_group(connection, module) tg = get_target_group(connection, module)
if module.params.get("targets"): if module.params.get("targets"):
params['Targets'] = module.params.get("targets") if module.params.get("target_type") != "lambda":
try: params['Targets'] = module.params.get("targets")
connection.register_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=params['Targets']) try:
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: connection.register_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=params['Targets'])
module.fail_json_aws(e, msg="Couldn't register targets") except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't register targets")
if module.params.get("wait"): if module.params.get("wait"):
status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], params['Targets'], 'healthy') status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], params['Targets'], 'healthy')
if not status_achieved: if not status_achieved:
module.fail_json(msg='Error waiting for target registration to be healthy - please check the AWS console') module.fail_json(msg='Error waiting for target registration to be healthy - please check the AWS console')
else:
try:
target = module.params.get("targets")[0]
response = connection.register_targets(
TargetGroupArn=tg['TargetGroupArn'],
Targets=[
{
"Id": target["Id"]
}
]
)
changed = True
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(
e, msg="Couldn't register targets")
# Now set target group attributes # Now set target group attributes
update_attributes = [] update_attributes = []
@ -712,7 +800,7 @@ def main():
state=dict(required=True, choices=['present', 'absent']), state=dict(required=True, choices=['present', 'absent']),
successful_response_codes=dict(), successful_response_codes=dict(),
tags=dict(default={}, type='dict'), tags=dict(default={}, type='dict'),
target_type=dict(default='instance', choices=['instance', 'ip']), target_type=dict(default='instance', choices=['instance', 'ip', 'lambda']),
targets=dict(type='list'), targets=dict(type='list'),
unhealthy_threshold_count=dict(type='int'), unhealthy_threshold_count=dict(type='int'),
vpc_id=dict(), vpc_id=dict(),
@ -722,7 +810,11 @@ def main():
) )
module = AnsibleAWSModule(argument_spec=argument_spec, module = AnsibleAWSModule(argument_spec=argument_spec,
required_if=[['state', 'present', ['protocol', 'port', 'vpc_id']]]) required_if=[
['target_type', 'instance', ['protocol', 'port', 'vpc_id']],
['target_type', 'ip', ['protocol', 'port', 'vpc_id']],
]
)
connection = module.client('elbv2') connection = module.client('elbv2')

View file

@ -3,4 +3,5 @@
environment: "{{ ansible_test.environment }}" environment: "{{ ansible_test.environment }}"
roles: roles:
- elb_lambda_target
- elb_target - elb_target

View file

@ -0,0 +1,8 @@
import json
def lambda_handler(event, context):
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}

View file

@ -0,0 +1,8 @@
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}
}

View file

@ -0,0 +1,135 @@
---
- name: set up aws connection info
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: set up lambda as elb_target
block:
- name: create zip to deploy lambda code
archive:
path: "{{ role_path }}/files/ansible_lambda_target.py"
dest: /tmp/lambda.zip
format: zip
- name: "create or update service-role for lambda"
iam_role:
<<: *aws_connection_info
name: ansible_lambda_execution
assume_role_policy_document: "{{ lookup('file', role_path + '/files/assume-role.json') }}"
managed_policy:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
register: ROLE_ARN
- name: when it is to fast, the role is not usable.
pause:
minutes: 1
- name: deploy lambda.zip to ansible_lambda_target function
lambda:
<<: *aws_connection_info
name: "ansible_lambda_target"
state: present
zip_file: "/tmp/lambda.zip"
runtime: "python3.7"
role: "{{ ROLE_ARN.arn }}"
handler: "ansible_lambda_target.lambda_handler"
timeout: 30
register: lambda_function
retries: 3
delay: 15
until: lambda_function.changed
- name: create empty target group
elb_target_group:
<<: *aws_connection_info
name: ansible-lambda-targetgroup
target_type: lambda
state: present
modify_targets: False
register: elb_target_group
- name: tg is created, state must be changed
assert:
that:
- elb_target_group.changed
- name: allow elb to invoke the lambda function
lambda_policy:
<<: *aws_connection_info
state: present
function_name: ansible_lambda_target
version: "{{ lambda_function.configuration.version }}"
statement_id: elb1
action: lambda:InvokeFunction
principal: elasticloadbalancing.amazonaws.com
source_arn: "{{ elb_target_group.target_group_arn }}"
- name: add lambda to elb target
elb_target_group:
<<: *aws_connection_info
name: ansible-lambda-targetgroup
target_type: lambda
state: present
targets:
- Id: "{{ lambda_function.configuration.function_arn }}"
register: elb_target_group
- name: target is updated, state must be changed
assert:
that:
- elb_target_group.changed
- name: re-add lambda to elb target (idempotency)
elb_target_group:
<<: *aws_connection_info
name: ansible-lambda-targetgroup
target_type: lambda
state: present
targets:
- Id: "{{ lambda_function.configuration.function_arn }}"
register: elb_target_group
- name: target is still the same, state must not be changed (idempotency)
assert:
that:
- not elb_target_group.changed
- name: remove lambda target from target group
elb_target_group:
<<: *aws_connection_info
name: ansible-lambda-targetgroup
target_type: lambda
state: absent
targets: []
register: elb_target_group
- name: target is still the same, state must not be changed (idempotency)
assert:
that:
- elb_target_group.changed
always:
- name: remove elb target group
elb_target_group:
<<: *aws_connection_info
name: ansible-lambda-targetgroup
target_type: lambda
state: absent
- name: remove lambda function
lambda:
<<: *aws_connection_info
name: "ansible_lambda_target"
state: absent
- name: remove iam role for lambda
iam_role:
<<: *aws_connection_info
name: ansible_lambda_execution
state: absent