Add logic to handle multiple actions in an ALB listener rule, Fixes #41861 (#41975)

* added logic to handle multiple actions in an ALB listener rule (#41861)

* fix linting and pep8 issues

* added test for multiple actions using OIDC authentication

* added error messages related to old versions of botocore and multiple actions

* fix action validation error checks (need to check the exception string)

* added logic to make oidc configs idempotent (remove clientsecret for check)

* modified TargetGroupName to TargetGroupArn substitution to account for multiple rule actions

* refactored tests so that it can be run against different versions of botocore

* fix runme.sh to refelct changes to cloud testsuite

* add UseExistingClientSecret to oidc config (AWS api change)

* remove tests for OIDC auth action; add tests for redirect and fixed-response

* add in fixes from markuman and mjmayer

* remove documentation for cognito integration (not sure how to test); added example config for fixed-response and redirect actions

* renamed oidc/multiple action tests; leaving commented due to some AWS API changes

* pep8 fix

* more pep8 fixes

* Restructure elb_application_lb test suite

Move from runme.sh to virtualenv based roles

Update policies to fix tests

Don't log temp dir deletion, so many files in the diff!
pull/60154/head
Jesse Evers 5 years ago committed by Will Thames
parent 309e342aaa
commit e410dcbfed

@ -132,10 +132,12 @@
"elasticloadbalancing:CreateListener",
"elasticloadbalancing:CreateLoadBalancer",
"elasticloadbalancing:CreateLoadBalancerListeners",
"elasticloadbalancing:CreateRule",
"elasticloadbalancing:CreateTargetGroup",
"elasticloadbalancing:DeleteListener",
"elasticloadbalancing:DeleteLoadBalancer",
"elasticloadbalancing:DeleteLoadBalancerListeners",
"elasticloadbalancing:DeleteRule",
"elasticloadbalancing:DeleteTargetGroup",
"elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
"elasticloadbalancing:DescribeInstanceHealth",
@ -143,7 +145,9 @@
"elasticloadbalancing:DescribeTags",
"elasticloadbalancing:DisableAvailabilityZonesForLoadBalancer",
"elasticloadbalancing:EnableAvailabilityZonesForLoadBalancer",
"elasticloadbalancing:ModifyListener",
"elasticloadbalancing:ModifyLoadBalancerAttributes",
"elasticloadbalancing:ModifyRule",
"elasticloadbalancing:RegisterInstancesWithLoadBalancer",
"elasticloadbalancing:RemoveTags"
],

@ -122,7 +122,10 @@
"Effect": "Allow",
"Action": [
"iam:ListServerCertificates",
"iam:UploadServerCertificate"
"iam:UploadServerCertificate",
"iam:UpdateServerCertificate",
"iam:DeleteServerCertificate",
"iam:GetServerCertificate"
],
"Resource": "*"
}

@ -466,13 +466,20 @@ class ELBListeners(object):
if not listeners:
listeners = []
fixed_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
fixed_actions = []
for action in listener['DefaultActions']:
if 'TargetGroupName' in action:
action['TargetGroupArn'] = convert_tg_name_to_arn(self.connection,
self.module,
action['TargetGroupName'])
del action['TargetGroupName']
fixed_actions.append(action)
listener['DefaultActions'] = fixed_actions
fixed_listeners.append(listener)
return fixed_listeners
def compare_listeners(self):
"""
@ -540,12 +547,47 @@ class ELBListeners(object):
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'
# Check proper rule format on current listener
if len(current_listener['DefaultActions']) > 1:
for action in current_listener['DefaultActions']:
if 'Order' not in action:
self.module.fail_json(msg="'Order' key not found in actions. "
"installed version of botocore does not support "
"multiple actions, please upgrade botocore to version "
"1.10.30 or higher")
# If the lengths of the actions are the same, we'll have to verify that the
# contents of those actions are the same
if len(current_listener['DefaultActions']) == len(new_listener['DefaultActions']):
# if actions have just one element, compare the contents and then update if
# they're different
if len(current_listener['DefaultActions']) == 1 and len(new_listener['DefaultActions']) == 1:
if current_listener['DefaultActions'] != new_listener['DefaultActions']:
modified_listener['DefaultActions'] = new_listener['DefaultActions']
# if actions have multiple elements, we'll have to order them first before comparing.
# multiple actions will have an 'Order' key for this purpose
else:
current_actions_sorted = sorted(current_listener['DefaultActions'], key=lambda x: x['Order'])
new_actions_sorted = sorted(new_listener['DefaultActions'], key=lambda x: x['Order'])
# the AWS api won't return the client secret, so we'll have to remove it
# or the module will always see the new and current actions as different
# and try to apply the same config
new_actions_sorted_no_secret = []
for action in new_actions_sorted:
# the secret is currently only defined in the oidc config
if action['Type'] == 'authenticate-oidc':
action['AuthenticateOidcConfig'].pop('ClientSecret')
new_actions_sorted_no_secret.append(action)
else:
new_actions_sorted_no_secret.append(action)
if current_actions_sorted != new_actions_sorted_no_secret:
modified_listener['DefaultActions'] = new_listener['DefaultActions']
# If the action lengths are different, then replace with the new actions
else:
modified_listener['DefaultActions'] = new_listener['DefaultActions']
if modified_listener:
return modified_listener
@ -577,7 +619,12 @@ class ELBListener(object):
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)
if '"Order", must be one of: Type, TargetGroupArn' in str(e):
self.module.fail_json(msg="installed version of botocore does not support "
"multiple actions, please upgrade botocore to version "
"1.10.30 or higher")
else:
self.module.fail_json_aws(e)
def modify(self):
@ -587,7 +634,12 @@ class ELBListener(object):
self.listener.pop('Rules')
AWSRetry.jittered_backoff()(self.connection.modify_listener)(**self.listener)
except (BotoCoreError, ClientError) as e:
self.module.fail_json_aws(e)
if '"Order", must be one of: Type, TargetGroupArn' in str(e):
self.module.fail_json(msg="installed version of botocore does not support "
"multiple actions, please upgrade botocore to version "
"1.10.30 or higher")
else:
self.module.fail_json_aws(e)
def delete(self):
@ -629,12 +681,18 @@ class ELBListenerRules(object):
:return: the same list of dicts ensuring that each rule Actions dict has TargetGroupArn key. If a TargetGroupName key exists, it is removed.
"""
fixed_rules = []
for rule in rules:
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']
fixed_actions = []
for action in rule['Actions']:
if 'TargetGroupName' in action:
action['TargetGroupArn'] = convert_tg_name_to_arn(self.connection, self.module, action['TargetGroupName'])
del action['TargetGroupName']
fixed_actions.append(action)
rule['Actions'] = fixed_actions
fixed_rules.append(rule)
return rules
return fixed_rules
def _get_elb_listener_rules(self):
@ -654,7 +712,12 @@ class ELBListenerRules(object):
condition_found = False
for current_condition in current_conditions:
if current_condition['Field'] == condition['Field'] and current_condition['Values'][0] == condition['Values'][0]:
if current_condition.get('SourceIpConfig'):
if (current_condition['Field'] == condition['Field'] and
current_condition['SourceIpConfig']['Values'][0] == condition['SourceIpConfig']['Values'][0]):
condition_found = True
break
elif current_condition['Field'] == condition['Field'] and current_condition['Values'][0] == condition['Values'][0]:
condition_found = True
break
@ -669,16 +732,57 @@ class ELBListenerRules(object):
modified_rule = {}
# Priority
if current_rule['Priority'] != new_rule['Priority']:
if int(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'
# Check proper rule format on current listener
if len(current_rule['Actions']) > 1:
for action in current_rule['Actions']:
if 'Order' not in action:
self.module.fail_json(msg="'Order' key not found in actions. "
"installed version of botocore does not support "
"multiple actions, please upgrade botocore to version "
"1.10.30 or higher")
# If the lengths of the actions are the same, we'll have to verify that the
# contents of those actions are the same
if len(current_rule['Actions']) == len(new_rule['Actions']):
# if actions have just one element, compare the contents and then update if
# they're different
if len(current_rule['Actions']) == 1 and len(new_rule['Actions']) == 1:
if current_rule['Actions'] != new_rule['Actions']:
modified_rule['Actions'] = new_rule['Actions']
print("modified_rule:")
print(new_rule['Actions'])
# if actions have multiple elements, we'll have to order them first before comparing.
# multiple actions will have an 'Order' key for this purpose
else:
current_actions_sorted = sorted(current_rule['Actions'], key=lambda x: x['Order'])
new_actions_sorted = sorted(new_rule['Actions'], key=lambda x: x['Order'])
# the AWS api won't return the client secret, so we'll have to remove it
# or the module will always see the new and current actions as different
# and try to apply the same config
new_actions_sorted_no_secret = []
for action in new_actions_sorted:
# the secret is currently only defined in the oidc config
if action['Type'] == 'authenticate-oidc':
action['AuthenticateOidcConfig'].pop('ClientSecret')
new_actions_sorted_no_secret.append(action)
else:
new_actions_sorted_no_secret.append(action)
if current_actions_sorted != new_actions_sorted_no_secret:
modified_rule['Actions'] = new_rule['Actions']
print("modified_rule:")
print(new_rule['Actions'])
# If the action lengths are different, then replace with the new actions
else:
modified_rule['Actions'] = new_rule['Actions']
print("modified_rule:")
print(new_rule['Actions'])
# Conditions
modified_conditions = []
@ -746,7 +850,12 @@ class ELBListenerRule(object):
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)
if '"Order", must be one of: Type, TargetGroupArn' in str(e):
self.module.fail_json(msg="installed version of botocore does not support "
"multiple actions, please upgrade botocore to version "
"1.10.30 or higher")
else:
self.module.fail_json_aws(e)
self.changed = True
@ -761,7 +870,12 @@ class ELBListenerRule(object):
del self.rule['Priority']
AWSRetry.jittered_backoff()(self.connection.modify_rule)(**self.rule)
except (BotoCoreError, ClientError) as e:
self.module.fail_json_aws(e)
if '"Order", must be one of: Type, TargetGroupArn' in str(e):
self.module.fail_json(msg="installed version of botocore does not support "
"multiple actions, please upgrade botocore to version "
"1.10.30 or higher")
else:
self.module.fail_json_aws(e)
self.changed = True

@ -157,7 +157,7 @@ EXAMPLES = '''
Certificates: # The ARN of the certificate (only one certficate ARN should be provided)
- CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com
DefaultActions:
- Type: forward # Required. Only 'forward' is accepted at this time
- Type: forward # Required.
TargetGroupName: # Required. The name of the target group
state: present
@ -181,7 +181,7 @@ EXAMPLES = '''
Certificates: # The ARN of the certificate (only one certficate ARN should be provided)
- CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com
DefaultActions:
- Type: forward # Required. Only 'forward' is accepted at this time
- Type: forward # Required.
TargetGroupName: # Required. The name of the target group
state: present
@ -212,6 +212,31 @@ EXAMPLES = '''
Actions:
- TargetGroupName: test-target-group
Type: forward
- Conditions:
- Field: path-pattern
Values:
- "/redirect-path/*"
Priority: '2'
Actions:
- Type: redirect
RedirectConfig:
Host: "#{host}"
Path: "/example/redir" # or /#{path}
Port: "#{port}"
Protocol: "#{protocol}"
Query: "#{query}"
StatusCode: "HTTP_302" # or HTTP_301
- Conditions:
- Field: path-pattern
Values:
- "/fixed-response-path/"
Priority: '3'
Actions:
- Type: fixed-response
FixedResponseConfig:
ContentType: "text/plain"
MessageBody: "This is the page you're looking for"
StatusCode: "200"
state: present
# Remove an ELB

@ -1,3 +1,2 @@
dependencies:
- prepare_tests
- setup_ec2
- setup_remote_tmp_dir

@ -0,0 +1,259 @@
- 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 }}"
awscli_connection_info: &awscli_connection_info
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
AWS_SESSION_TOKEN: "{{ security_token }}"
AWS_DEFAULT_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_info:
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
- name: create privatekey for testing
openssl_privatekey:
path: ./ansible_alb_test.pem
size: 2048
- name: create csr for cert
openssl_csr:
path: ./ansible_alb_test.csr
privatekey_path: ./ansible_alb_test.pem
C: US
ST: AnyPrincipality
L: AnyTown
O: AnsibleIntegrationTest
OU: Test
CN: ansible-alb-test.example.com
- name: create certificate
openssl_certificate:
path: ./ansible_alb_test.crt
privatekey_path: ./ansible_alb_test.pem
csr_path: ./ansible_alb_test.csr
provider: selfsigned
# This really should be an ACM Cert, but there is no acm_cert resource module
- name: upload server cert to iam
iam_cert:
name: "{{ alb_name }}"
state: present
cert: ./ansible_alb_test.crt
key: ./ansible_alb_test.pem
<<: *aws_connection_info
register: cert_upload
- name: register certificate arn to acm_arn fact
set_fact:
cert_arn: "{{ cert_upload.arn }}"
- 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
- include_tasks: test_multiple_actions.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 acm certificate
iam_cert:
name: "{{ alb_name }}"
state: absent
<<: *aws_connection_info
register: remove_cert
retries: 5
delay: 3
until: remove_cert is success
when: cert_arn 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

@ -1,204 +1,44 @@
- 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_info:
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
- set_fact:
virtualenv: "{{ remote_tmp_dir }}/virtualenv"
virtualenv_command: "{{ ansible_python_interpreter }} -m virtualenv"
- set_fact:
virtualenv_interpreter: "{{ virtualenv }}/bin/python"
- pip:
name: virtualenv
- pip:
name:
- 'botocore<1.10.30'
- boto3
- boto
- coverage
- cryptography
virtualenv: "{{ virtualenv }}"
virtualenv_command: "{{ virtualenv_command }}"
virtualenv_site_packages: no
- include_tasks: multiple_actions_fail.yml
vars:
ansible_python_interpreter: "{{ virtualenv_interpreter }}"
- pip:
name:
- 'botocore>=1.10.30'
- boto3
- boto
- coverage
- cryptography
virtualenv: "{{ virtualenv }}"
virtualenv_command: "{{ virtualenv_command }}"
virtualenv_site_packages: no
- include_tasks: full_test.yml
vars:
ansible_python_interpreter: "{{ virtualenv_interpreter }}"
- file:
path: "{{ virtualenv }}"
state: absent

@ -0,0 +1,253 @@
- 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 }}"
awscli_connection_info: &awscli_connection_info
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
AWS_SESSION_TOKEN: "{{ security_token }}"
AWS_DEFAULT_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
- name: create privatekey for testing
openssl_privatekey:
path: ./ansible_alb_test.pem
size: 2048
- name: create csr for cert
openssl_csr:
path: ./ansible_alb_test.csr
privatekey_path: ./ansible_alb_test.pem
C: US
ST: AnyPrincipality
L: AnyTown
O: AnsibleIntegrationTest
OU: Test
CN: ansible-alb-test.example.com
- name: create certificate
openssl_certificate:
path: ./ansible_alb_test.crt
privatekey_path: ./ansible_alb_test.pem
csr_path: ./ansible_alb_test.csr
provider: selfsigned
# This really should be an ACM Cert, but there is no acm_cert resource module
- name: upload server cert to iam
iam_cert:
name: "{{ alb_name }}"
state: present
cert: ./ansible_alb_test.crt
key: ./ansible_alb_test.pem
<<: *aws_connection_info
register: cert_upload
- name: register certificate arn to acm_arn fact
set_fact:
cert_arn: "{{ cert_upload.arn }}"
- include_tasks: test_multiple_actions_fail.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: 10
delay: 5
until: remove_tg is success
when: tg is defined
ignore_errors: yes
- name: destroy acm certificate
iam_cert:
name: "{{ alb_name }}"
state: absent
<<: *aws_connection_info
register: remove_cert
retries: 10
delay: 5
until: remove_cert is success
when: cert_arn 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,467 @@
- 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: register dummy OIDC config
set_fact:
AuthenticateOidcActionConfig:
AuthorizationEndpoint: "https://www.example.com/auth"
ClientId: "eeeeeeeeeeeeeeeeeeeeeeeeee"
ClientSecret: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
Issuer: "https://www.example.com/issuer"
OnUnauthenticatedRequest: "authenticate"
Scope: "openid"
SessionCookieName: "AWSELBAuthSessionCookie"
SessionTimeout: 604800
TokenEndpoint: "https://www.example.com/token"
UserInfoEndpoint: "https://www.example.com/userinfo"
UseExistingClientSecret: true
- name: register fixed response action
set_fact:
FixedResponseActionConfig:
ContentType: "text/plain"
MessageBody: "This is the page you're looking for"
StatusCode: "200"
- name: register redirect action
set_fact:
RedirectActionConfig:
Host: "#{host}"
Path: "/example/redir" # or /#{path}
Port: "#{port}"
Protocol: "#{protocol}"
Query: "#{query}"
StatusCode: "HTTP_302" # or HTTP_301
- name: delete existing ALB to avoid target group association issues
elb_application_lb:
name: "{{ alb_name }}"
state: absent
<<: *aws_connection_info
wait: yes
wait_timeout: 600
- name: cleanup tg to avoid target group association issues
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: cleanup_tg
retries: 5
delay: 3
until: cleanup_tg is success
- name: recreate a target group
elb_target_group:
name: "{{ tg_name }}"
protocol: http
port: 80
vpc_id: "{{ vpc.vpc.id }}"
state: present
<<: *aws_connection_info
register: tg
- name: create ALB with redirect DefaultAction
elb_application_lb:
name: "{{ alb_name }}"
subnets: "{{ alb_subnets }}"
security_groups: "{{ sec_group.group_id }}"
state: present
listeners:
- Protocol: HTTPS
Port: 443
DefaultActions:
- Type: redirect
RedirectConfig: "{{ RedirectActionConfig }}"
Certificates:
- CertificateArn: "{{ cert_arn }}"
SslPolicy: ELBSecurityPolicy-2016-08
<<: *aws_connection_info
register: alb
- assert:
that:
- alb.changed
- alb.listeners|length == 1
- alb.listeners[0].rules[0].actions|length == 1
- alb.listeners[0].rules[0].actions[0].type == "redirect"
- name: test idempotence with redirect DefaultAction
elb_application_lb:
name: "{{ alb_name }}"
subnets: "{{ alb_subnets }}"
security_groups: "{{ sec_group.group_id }}"
state: present
listeners:
- Protocol: HTTPS
Port: 443
DefaultActions:
- Type: redirect
RedirectConfig: "{{ RedirectActionConfig }}"
Certificates:
- CertificateArn: "{{ cert_arn }}"
SslPolicy: ELBSecurityPolicy-2016-08
<<: *aws_connection_info
register: alb
- assert:
that:
- not alb.changed
- alb.listeners|length == 1
- alb.listeners[0].rules[0].actions|length == 1
- alb.listeners[0].rules[0].actions[0].type == "redirect"
- name: update ALB with fixed-response DefaultAction
elb_application_lb:
name: "{{ alb_name }}"
subnets: "{{ alb_subnets }}"
security_groups: "{{ sec_group.group_id }}"
state: present
listeners:
- Protocol: HTTPS
Port: 443
DefaultActions:
- Type: fixed-response
FixedResponseConfig: "{{ FixedResponseActionConfig }}"
Certificates:
- CertificateArn: "{{ cert_arn }}"
SslPolicy: ELBSecurityPolicy-2016-08
<<: *aws_connection_info
register: alb
- assert:
that:
- alb.changed
- alb.listeners|length == 1
- alb.listeners[0].rules[0].actions|length == 1
- alb.listeners[0].rules[0].actions[0].type == "fixed-response"
- name: test idempotence with fixed-response DefaultAction
elb_application_lb:
name: "{{ alb_name }}"
subnets: "{{ alb_subnets }}"
security_groups: "{{ sec_group.group_id }}"
state: present
listeners:
- Protocol: HTTPS
Port: 443
DefaultActions:
- Type: fixed-response
FixedResponseConfig: "{{ FixedResponseActionConfig }}"
Certificates:
- CertificateArn: "{{ cert_arn }}"
SslPolicy: ELBSecurityPolicy-2016-08
<<: *aws_connection_info
register: alb
- assert:
that:
- not alb.changed
- alb.listeners|length == 1
- alb.listeners[0].rules[0].actions|length == 1
- alb.listeners[0].rules[0].actions[0].type == "fixed-response"
- name: test multiple non-default rules
elb_application_lb:
name: "{{ alb_name }}"
subnets: "{{ alb_subnets }}"
security_groups: "{{ sec_group.group_id }}"
state: present
listeners:
- Protocol: HTTPS
Port: 443
DefaultActions:
- Type: fixed-response
FixedResponseConfig: "{{ FixedResponseActionConfig }}"
Certificates:
- CertificateArn: "{{ cert_arn }}"
SslPolicy: ELBSecurityPolicy-2016-08
Rules:
- Conditions:
- Field: path-pattern
Values:
- "/forward-path/*"
Priority: 1
Actions:
- Type: forward
TargetGroupName: "{{ tg_name }}"
- Conditions:
- Field: path-pattern
Values:
- "/redirect-path/*"
Priority: 2
Actions:
- Type: redirect
RedirectConfig: "{{ RedirectActionConfig }}"
- Conditions:
- Field: path-pattern
Values:
- "/fixed-response-path/"
Priority: 3
Actions:
- Type: fixed-response
FixedResponseConfig: "{{ FixedResponseActionConfig }}"
<<: *aws_connection_info
register: alb
- assert:
that:
- alb.changed
- alb.listeners|length == 1
- alb.listeners[0].rules|length == 4 ## defaultactions is included as a rule
- alb.listeners[0].rules[0].actions|length == 1
- alb.listeners[0].rules[0].actions[0].type == "forward"
- alb.listeners[0].rules[1].actions|length == 1
- alb.listeners[0].rules[1].actions[0].type == "redirect"
- alb.listeners[0].rules[2].actions|length == 1
- alb.listeners[0].rules[2].actions[0].type == "fixed-response"
- name: test idempotence multiple non-default rules
elb_application_lb:
name: "{{ alb_name }}"
subnets: "{{ alb_subnets }}"
security_groups: "{{ sec_group.group_id }}"
state: present
listeners:
- Protocol: HTTPS
Port: 443
DefaultActions:
- Type: fixed-response
FixedResponseConfig: "{{ FixedResponseActionConfig }}"
Certificates:
- CertificateArn: "{{ cert_arn }}"
SslPolicy: ELBSecurityPolicy-2016-08
Rules:
- Conditions:
- Field: path-pattern
Values:
- "/forward-path/*"
Priority: 1
Actions:
- Type: forward
TargetGroupName: "{{ tg_name }}"
- Conditions:
- Field: path-pattern
Values:
- "/redirect-path/*"
Priority: 2
Actions:
- Type: redirect
RedirectConfig: "{{ RedirectActionConfig }}"
- Conditions:
- Field: path-pattern
Values:
- "/fixed-response-path/"
Priority: 3
Actions:
- Type: fixed-response
FixedResponseConfig: "{{ FixedResponseActionConfig }}"
<<: *aws_connection_info
register: alb
- assert:
that:
- not alb.changed
- alb.listeners|length == 1
- alb.listeners[0].rules|length == 4 ## defaultactions is included as a rule
- alb.listeners[0].rules[0].actions|length == 1
- alb.listeners[0].rules[0].actions[0].type == "forward"
- alb.listeners[0].rules[1].actions|length == 1
- alb.listeners[0].rules[1].actions[0].type == "redirect"
- alb.listeners[0].rules[2].actions|length == 1
- alb.listeners[0].rules[2].actions[0].type == "fixed-response"
# - name: test creating ALB with a default listener with multiple actions
# elb_application_lb:
# name: "{{ alb_name }}"
# subnets: "{{ alb_subnets }}"
# security_groups: "{{ sec_group.group_id }}"
# state: present
# listeners:
# - Protocol: HTTPS
# Port: 443
# DefaultActions:
# - Type: forward
# TargetGroupName: "{{ tg_name }}"
# Order: 2
# - Type: authenticate-oidc
# AuthenticateOidcConfig: "{{ AuthenticateOidcActionConfig }}"
# Order: 1
# Certificates:
# - CertificateArn: "{{ cert_arn }}"
# SslPolicy: ELBSecurityPolicy-2016-08
# <<: *aws_connection_info
# register: alb
#
# - assert:
# that:
# - alb.listeners|length == 1
# - alb.listeners[0].rules[0].actions|length == 2
#
# - name: test changing order of actions
# elb_application_lb:
# name: "{{ alb_name }}"
# subnets: "{{ alb_subnets }}"
# security_groups: "{{ sec_group.group_id }}"
# state: present
# listeners:
# - Protocol: HTTPS
# Port: 443
# DefaultActions:
# - Type: authenticate-oidc
# AuthenticateOidcConfig: "{{ AuthenticateOidcActionConfig }}"
# Order: 1
# - Type: forward
# TargetGroupName: "{{ tg_name }}"
# Order: 2
# Certificates:
# - CertificateArn: "{{ cert_arn }}"
# SslPolicy: ELBSecurityPolicy-2016-08
# <<: *aws_connection_info
# register: alb
#
# - assert:
# that:
# - not alb.changed
# - alb.listeners|length == 1
# - alb.listeners[0].rules[0].actions|length == 2
#
# - name: test non-default rule with multiple actions
# elb_application_lb:
# name: "{{ alb_name }}"
# subnets: "{{ alb_subnets }}"
# security_groups: "{{ sec_group.group_id }}"
# state: present
# listeners:
# - Protocol: HTTPS
# Port: 443
# DefaultActions:
# - Type: authenticate-oidc
# AuthenticateOidcConfig: "{{ AuthenticateOidcActionConfig }}"
# Order: 1
# - Type: forward
# TargetGroupName: "{{ tg_name }}"
# Order: 2
# Certificates:
# - CertificateArn: "{{ cert_arn }}"
# SslPolicy: ELBSecurityPolicy-2016-08
# Rules:
# - Conditions:
# - Field: path-pattern
# Values:
# - "*"
# Priority: 1
# Actions:
# - Type: forward
# TargetGroupName: "{{ tg_name }}"
# Order: 2
# - Type: authenticate-oidc
# AuthenticateOidcConfig: "{{ AuthenticateOidcActionConfig }}"
# Order: 1
# <<: *aws_connection_info
# register: alb
#
# - assert:
# that:
# - alb.changed
# - alb.listeners|length == 1
# - alb.listeners[0].rules[0].actions|length == 2
# - alb.listeners[0].rules[1].actions|length == 2
#
# - name: test idempotency non-default rule with multiple actions
# elb_application_lb:
# name: "{{ alb_name }}"
# subnets: "{{ alb_subnets }}"
# security_groups: "{{ sec_group.group_id }}"
# state: present
# listeners:
# - Protocol: HTTPS
# Port: 443
# DefaultActions:
# - Type: authenticate-oidc
# AuthenticateOidcConfig: "{{ AuthenticateOidcActionConfig }}"
# Order: 1
# - Type: forward
# TargetGroupName: "{{ tg_name }}"
# Order: 2
# Certificates:
# - CertificateArn: "{{ cert_arn }}"
# SslPolicy: ELBSecurityPolicy-2016-08
# Rules:
# - Conditions:
# - Field: path-pattern
# Values:
# - "*"
# Priority: 1
# Actions:
# - Type: forward
# TargetGroupName: "{{ tg_name }}"
# Order: 2
# - Type: authenticate-oidc
# AuthenticateOidcConfig: "{{ AuthenticateOidcActionConfig }}"
# Order: 1
# <<: *aws_connection_info
# register: alb
#
# - assert:
# that:
# - not alb.changed
# - alb.listeners|length == 1
# - alb.listeners[0].rules[0].actions|length == 2
# - alb.listeners[0].rules[1].actions|length == 2
#
# - name: test non-default rule action order change
# elb_application_lb:
# name: "{{ alb_name }}"
# subnets: "{{ alb_subnets }}"
# security_groups: "{{ sec_group.group_id }}"
# state: present
# listeners:
# - Protocol: HTTPS
# Port: 443
# DefaultActions:
# - Type: authenticate-oidc
# AuthenticateOidcConfig: "{{ AuthenticateOidcActionConfig }}"
# Order: 1
# - Type: forward
# TargetGroupName: "{{ tg_name }}"
# Order: 2
# Certificates:
# - CertificateArn: "{{ cert_arn }}"
# SslPolicy: ELBSecurityPolicy-2016-08
# Rules:
# - Conditions:
# - Field: path-pattern
# Values:
# - "*"
# Priority: 1
# Actions:
# - Type: authenticate-oidc
# AuthenticateOidcConfig: "{{ AuthenticateOidcActionConfig }}"
# Order: 1
# - Type: forward
# TargetGroupName: "{{ tg_name }}"
# Order: 2
# <<: *aws_connection_info
# register: alb
#
# - assert:
# that:
# - not alb.changed
# - alb.listeners|length == 1
# - alb.listeners[0].rules[0].actions|length == 2
# - alb.listeners[0].rules[1].actions|length == 2

@ -0,0 +1,53 @@
- 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: register dummy OIDC config
set_fact:
AuthenticateOidcActionConfig:
AuthorizationEndpoint: "https://www.example.com/auth"
ClientId: "eeeeeeeeeeeeeeeeeeeeeeeeee"
ClientSecret: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
Issuer: "https://www.example.com/issuer"
OnUnauthenticatedRequest: "authenticate"
Scope: "openid"
SessionCookieName: "AWSELBAuthSessionCookie"
SessionTimeout: 604800
TokenEndpoint: "https://www.example.com/token"
UserInfoEndpoint: "https://www.example.com/userinfo"
- name: create ALB with multiple DefaultActions
elb_application_lb:
name: "{{ alb_name }}"
subnets: "{{ alb_subnets }}"
security_groups: "{{ sec_group.group_id }}"
state: present
listeners:
- Protocol: HTTPS
Port: 443
DefaultActions:
- Type: forward
TargetGroupName: "{{ tg_name }}"
Order: 2
- Type: authenticate-oidc
AuthenticateOidcConfig: "{{ AuthenticateOidcActionConfig }}"
Order: 1
Certificates:
- CertificateArn: "{{ cert_arn }}"
SslPolicy: ELBSecurityPolicy-2016-08
<<: *aws_connection_info
register: alb
ignore_errors: yes
- name: check for a graceful failure message
assert:
that:
- alb.failed
- 'alb.msg == "installed version of botocore does not support multiple actions, please upgrade botocore to version 1.10.30 or higher"'

@ -2,6 +2,7 @@
file:
path: "{{ remote_tmp_dir }}"
state: absent
no_log: yes
- name: delete temporary directory (windows)
win_file:

Loading…
Cancel
Save