mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-27 07:01:22 -07:00
AWS WAF module custom waiter (#37026)
Consolidate waiters to a single file * Add waiter message with token ID * Add waiter Add waiter for WAF change tokens Working waiter for waf_condition module Add support for waiters to waf_rule * WAF data model refactor * Fix ref to self.client * Add custom waiters to aws_waf_web_acl * Allow add/remove rule tasks to operate in parallel, then wait for their change tokens to complete * Move waiter into run_func_with_change_token_backoff since it is generic to all WAF update operations * Wait for deletes on waf_web_acl * Remove always-wait * Remove waiter retry catch
This commit is contained in:
parent
534e9e142b
commit
1c7b9e66b4
5 changed files with 82 additions and 19 deletions
|
@ -30,6 +30,7 @@ This module adds shared support for Web Application Firewall modules
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from ansible.module_utils.ec2 import camel_dict_to_snake_dict, AWSRetry
|
from ansible.module_utils.ec2 import camel_dict_to_snake_dict, AWSRetry
|
||||||
|
from ansible.module_utils.aws.waiters import get_waiter
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import botocore
|
import botocore
|
||||||
|
@ -183,6 +184,13 @@ def get_change_token(client, module):
|
||||||
|
|
||||||
|
|
||||||
@AWSRetry.backoff(tries=10, delay=2, backoff=2.0, catch_extra_error_codes=['WAFStaleDataException'])
|
@AWSRetry.backoff(tries=10, delay=2, backoff=2.0, catch_extra_error_codes=['WAFStaleDataException'])
|
||||||
def run_func_with_change_token_backoff(client, module, params, func):
|
def run_func_with_change_token_backoff(client, module, params, func, wait=False):
|
||||||
params['ChangeToken'] = get_change_token(client, module)
|
params['ChangeToken'] = get_change_token(client, module)
|
||||||
return func(**params)
|
result = func(**params)
|
||||||
|
if wait:
|
||||||
|
get_waiter(
|
||||||
|
client, 'change_token_in_sync',
|
||||||
|
).wait(
|
||||||
|
ChangeToken=result['ChangeToken']
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
|
@ -119,54 +119,90 @@ ec2_data = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def model_for(name):
|
waf_data = {
|
||||||
|
"version": 2,
|
||||||
|
"waiters": {
|
||||||
|
"ChangeTokenInSync": {
|
||||||
|
"delay": 20,
|
||||||
|
"maxAttempts": 60,
|
||||||
|
"operation": "GetChangeTokenStatus",
|
||||||
|
"acceptors": [
|
||||||
|
{
|
||||||
|
"matcher": "path",
|
||||||
|
"expected": True,
|
||||||
|
"argument": "ChangeTokenStatus == 'INSYNC'",
|
||||||
|
"state": "success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "error",
|
||||||
|
"expected": "WAFInternalErrorException",
|
||||||
|
"state": "retry"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def ec2_model(name):
|
||||||
ec2_models = core_waiter.WaiterModel(waiter_config=ec2_data)
|
ec2_models = core_waiter.WaiterModel(waiter_config=ec2_data)
|
||||||
return ec2_models.get_waiter(name)
|
return ec2_models.get_waiter(name)
|
||||||
|
|
||||||
|
|
||||||
|
def waf_model(name):
|
||||||
|
waf_models = core_waiter.WaiterModel(waiter_config=waf_data)
|
||||||
|
return waf_models.get_waiter(name)
|
||||||
|
|
||||||
|
|
||||||
waiters_by_name = {
|
waiters_by_name = {
|
||||||
('EC2', 'route_table_exists'): lambda ec2: core_waiter.Waiter(
|
('EC2', 'route_table_exists'): lambda ec2: core_waiter.Waiter(
|
||||||
'route_table_exists',
|
'route_table_exists',
|
||||||
model_for('RouteTableExists'),
|
ec2_model('RouteTableExists'),
|
||||||
core_waiter.NormalizedOperationMethod(
|
core_waiter.NormalizedOperationMethod(
|
||||||
ec2.describe_route_tables
|
ec2.describe_route_tables
|
||||||
)),
|
)),
|
||||||
('EC2', 'subnet_exists'): lambda ec2: core_waiter.Waiter(
|
('EC2', 'subnet_exists'): lambda ec2: core_waiter.Waiter(
|
||||||
'subnet_exists',
|
'subnet_exists',
|
||||||
model_for('SubnetExists'),
|
ec2_model('SubnetExists'),
|
||||||
core_waiter.NormalizedOperationMethod(
|
core_waiter.NormalizedOperationMethod(
|
||||||
ec2.describe_subnets
|
ec2.describe_subnets
|
||||||
)),
|
)),
|
||||||
('EC2', 'subnet_has_map_public'): lambda ec2: core_waiter.Waiter(
|
('EC2', 'subnet_has_map_public'): lambda ec2: core_waiter.Waiter(
|
||||||
'subnet_has_map_public',
|
'subnet_has_map_public',
|
||||||
model_for('SubnetHasMapPublic'),
|
ec2_model('SubnetHasMapPublic'),
|
||||||
core_waiter.NormalizedOperationMethod(
|
core_waiter.NormalizedOperationMethod(
|
||||||
ec2.describe_subnets
|
ec2.describe_subnets
|
||||||
)),
|
)),
|
||||||
('EC2', 'subnet_no_map_public'): lambda ec2: core_waiter.Waiter(
|
('EC2', 'subnet_no_map_public'): lambda ec2: core_waiter.Waiter(
|
||||||
'subnet_no_map_public',
|
'subnet_no_map_public',
|
||||||
model_for('SubnetNoMapPublic'),
|
ec2_model('SubnetNoMapPublic'),
|
||||||
core_waiter.NormalizedOperationMethod(
|
core_waiter.NormalizedOperationMethod(
|
||||||
ec2.describe_subnets
|
ec2.describe_subnets
|
||||||
)),
|
)),
|
||||||
('EC2', 'subnet_has_assign_ipv6'): lambda ec2: core_waiter.Waiter(
|
('EC2', 'subnet_has_assign_ipv6'): lambda ec2: core_waiter.Waiter(
|
||||||
'subnet_has_assign_ipv6',
|
'subnet_has_assign_ipv6',
|
||||||
model_for('SubnetHasAssignIpv6'),
|
ec2_model('SubnetHasAssignIpv6'),
|
||||||
core_waiter.NormalizedOperationMethod(
|
core_waiter.NormalizedOperationMethod(
|
||||||
ec2.describe_subnets
|
ec2.describe_subnets
|
||||||
)),
|
)),
|
||||||
('EC2', 'subnet_no_assign_ipv6'): lambda ec2: core_waiter.Waiter(
|
('EC2', 'subnet_no_assign_ipv6'): lambda ec2: core_waiter.Waiter(
|
||||||
'subnet_no_assign_ipv6',
|
'subnet_no_assign_ipv6',
|
||||||
model_for('SubnetNoAssignIpv6'),
|
ec2_model('SubnetNoAssignIpv6'),
|
||||||
core_waiter.NormalizedOperationMethod(
|
core_waiter.NormalizedOperationMethod(
|
||||||
ec2.describe_subnets
|
ec2.describe_subnets
|
||||||
)),
|
)),
|
||||||
('EC2', 'subnet_deleted'): lambda ec2: core_waiter.Waiter(
|
('EC2', 'subnet_deleted'): lambda ec2: core_waiter.Waiter(
|
||||||
'subnet_deleted',
|
'subnet_deleted',
|
||||||
model_for('SubnetDeleted'),
|
ec2_model('SubnetDeleted'),
|
||||||
core_waiter.NormalizedOperationMethod(
|
core_waiter.NormalizedOperationMethod(
|
||||||
ec2.describe_subnets
|
ec2.describe_subnets
|
||||||
)),
|
)),
|
||||||
|
('WAF', 'change_token_in_sync'): lambda waf: core_waiter.Waiter(
|
||||||
|
'change_token_in_sync',
|
||||||
|
waf_model('ChangeTokenInSync'),
|
||||||
|
core_waiter.NormalizedOperationMethod(
|
||||||
|
waf.get_change_token_status
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -451,7 +451,7 @@ class Condition(object):
|
||||||
updates.extend([{'Action': 'DELETE', 'RegexPatternString': pattern} for pattern in extra])
|
updates.extend([{'Action': 'DELETE', 'RegexPatternString': pattern} for pattern in extra])
|
||||||
run_func_with_change_token_backoff(self.client, self.module,
|
run_func_with_change_token_backoff(self.client, self.module,
|
||||||
{'RegexPatternSetId': pattern_set['RegexPatternSetId'], 'Updates': updates},
|
{'RegexPatternSetId': pattern_set['RegexPatternSetId'], 'Updates': updates},
|
||||||
self.client.update_regex_pattern_set)
|
self.client.update_regex_pattern_set, wait=True)
|
||||||
return self.get_regex_pattern_set_with_backoff(pattern_set['RegexPatternSetId'])['RegexPatternSet']
|
return self.get_regex_pattern_set_with_backoff(pattern_set['RegexPatternSetId'])['RegexPatternSet']
|
||||||
|
|
||||||
def delete_unused_regex_pattern(self, regex_pattern_set_id):
|
def delete_unused_regex_pattern(self, regex_pattern_set_id):
|
||||||
|
@ -466,8 +466,10 @@ class Condition(object):
|
||||||
|
|
||||||
run_func_with_change_token_backoff(self.client, self.module,
|
run_func_with_change_token_backoff(self.client, self.module,
|
||||||
{'RegexPatternSetId': regex_pattern_set_id},
|
{'RegexPatternSetId': regex_pattern_set_id},
|
||||||
self.client.delete_regex_pattern_set)
|
self.client.delete_regex_pattern_set, wait=True)
|
||||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||||
|
if e.response['Error']['Code'] == 'WAFNonexistentItemException':
|
||||||
|
return
|
||||||
self.module.fail_json_aws(e, msg='Could not delete regex pattern')
|
self.module.fail_json_aws(e, msg='Could not delete regex pattern')
|
||||||
|
|
||||||
def get_condition_by_name(self, name):
|
def get_condition_by_name(self, name):
|
||||||
|
@ -537,6 +539,7 @@ class Condition(object):
|
||||||
func = getattr(self.client, 'update_' + self.method_suffix)
|
func = getattr(self.client, 'update_' + self.method_suffix)
|
||||||
params = self.format_for_deletion(current_condition)
|
params = self.format_for_deletion(current_condition)
|
||||||
try:
|
try:
|
||||||
|
# We do not need to wait for the conditiontuple delete because we wait later for the delete_* call
|
||||||
run_func_with_change_token_backoff(self.client, self.module, params, func)
|
run_func_with_change_token_backoff(self.client, self.module, params, func)
|
||||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||||
self.module.fail_json_aws(e, msg='Could not delete filters from condition')
|
self.module.fail_json_aws(e, msg='Could not delete filters from condition')
|
||||||
|
@ -544,7 +547,7 @@ class Condition(object):
|
||||||
params = dict()
|
params = dict()
|
||||||
params[self.conditionsetid] = condition_set_id
|
params[self.conditionsetid] = condition_set_id
|
||||||
try:
|
try:
|
||||||
run_func_with_change_token_backoff(self.client, self.module, params, func)
|
run_func_with_change_token_backoff(self.client, self.module, params, func, wait=True)
|
||||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||||
self.module.fail_json_aws(e, msg='Could not delete condition')
|
self.module.fail_json_aws(e, msg='Could not delete condition')
|
||||||
# tidy up regex patterns
|
# tidy up regex patterns
|
||||||
|
@ -580,7 +583,7 @@ class Condition(object):
|
||||||
update['Updates'] = missing + extra
|
update['Updates'] = missing + extra
|
||||||
func = getattr(self.client, 'update_' + self.method_suffix)
|
func = getattr(self.client, 'update_' + self.method_suffix)
|
||||||
try:
|
try:
|
||||||
run_func_with_change_token_backoff(self.client, self.module, update, func)
|
result = run_func_with_change_token_backoff(self.client, self.module, update, func, wait=True)
|
||||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||||
self.module.fail_json_aws(e, msg='Could not update condition')
|
self.module.fail_json_aws(e, msg='Could not update condition')
|
||||||
return changed, self.get_condition_by_id(condition_set_id)
|
return changed, self.get_condition_by_id(condition_set_id)
|
||||||
|
|
|
@ -207,7 +207,7 @@ def find_and_update_rule(client, module, rule_id):
|
||||||
}
|
}
|
||||||
if changed:
|
if changed:
|
||||||
try:
|
try:
|
||||||
run_func_with_change_token_backoff(client, module, update, client.update_rule)
|
run_func_with_change_token_backoff(client, module, update, client.update_rule, wait=True)
|
||||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||||
module.fail_json_aws(e, msg='Could not update rule conditions')
|
module.fail_json_aws(e, msg='Could not update rule conditions')
|
||||||
|
|
||||||
|
@ -282,7 +282,7 @@ def ensure_rule_absent(client, module):
|
||||||
if rule_id:
|
if rule_id:
|
||||||
remove_rule_conditions(client, module, rule_id)
|
remove_rule_conditions(client, module, rule_id)
|
||||||
try:
|
try:
|
||||||
return True, run_func_with_change_token_backoff(client, module, {'RuleId': rule_id}, client.delete_rule)
|
return True, run_func_with_change_token_backoff(client, module, {'RuleId': rule_id}, client.delete_rule, wait=True)
|
||||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||||
module.fail_json_aws(e, msg='Could not delete rule')
|
module.fail_json_aws(e, msg='Could not delete rule')
|
||||||
return False, {}
|
return False, {}
|
||||||
|
|
|
@ -135,6 +135,7 @@ except ImportError:
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from ansible.module_utils.aws.core import AnsibleAWSModule
|
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||||
|
from ansible.module_utils.aws.waiters import get_waiter
|
||||||
from ansible.module_utils.ec2 import boto3_conn, get_aws_connection_info, ec2_argument_spec, camel_dict_to_snake_dict
|
from ansible.module_utils.ec2 import boto3_conn, get_aws_connection_info, ec2_argument_spec, camel_dict_to_snake_dict
|
||||||
from ansible.module_utils.aws.waf import list_rules_with_backoff, list_web_acls_with_backoff, run_func_with_change_token_backoff
|
from ansible.module_utils.aws.waf import list_rules_with_backoff, list_web_acls_with_backoff, run_func_with_change_token_backoff
|
||||||
|
|
||||||
|
@ -193,18 +194,33 @@ def find_and_update_web_acl(client, module, web_acl_id):
|
||||||
'WebACLId': acl['WebACLId'],
|
'WebACLId': acl['WebACLId'],
|
||||||
'DefaultAction': acl['DefaultAction']
|
'DefaultAction': acl['DefaultAction']
|
||||||
}
|
}
|
||||||
|
change_tokens = []
|
||||||
if deletions:
|
if deletions:
|
||||||
try:
|
try:
|
||||||
params['Updates'] = deletions
|
params['Updates'] = deletions
|
||||||
run_func_with_change_token_backoff(client, module, params, client.update_web_acl)
|
result = run_func_with_change_token_backoff(client, module, params, client.update_web_acl)
|
||||||
|
change_tokens.append(result['ChangeToken'])
|
||||||
|
get_waiter(
|
||||||
|
client, 'change_token_in_sync',
|
||||||
|
).wait(
|
||||||
|
ChangeToken=result['ChangeToken']
|
||||||
|
)
|
||||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||||
module.fail_json_aws(e, msg='Could not update Web ACL')
|
module.fail_json_aws(e, msg='Could not update Web ACL')
|
||||||
if insertions:
|
if insertions:
|
||||||
try:
|
try:
|
||||||
params['Updates'] = insertions
|
params['Updates'] = insertions
|
||||||
run_func_with_change_token_backoff(client, module, params, client.update_web_acl)
|
result = run_func_with_change_token_backoff(client, module, params, client.update_web_acl)
|
||||||
|
change_tokens.append(result['ChangeToken'])
|
||||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||||
module.fail_json_aws(e, msg='Could not update Web ACL')
|
module.fail_json_aws(e, msg='Could not update Web ACL')
|
||||||
|
if change_tokens:
|
||||||
|
for token in change_tokens:
|
||||||
|
get_waiter(
|
||||||
|
client, 'change_token_in_sync',
|
||||||
|
).wait(
|
||||||
|
ChangeToken=token
|
||||||
|
)
|
||||||
if changed:
|
if changed:
|
||||||
acl = get_web_acl(client, module, web_acl_id)
|
acl = get_web_acl(client, module, web_acl_id)
|
||||||
return changed, acl
|
return changed, acl
|
||||||
|
@ -261,7 +277,7 @@ def ensure_web_acl_absent(client, module):
|
||||||
if web_acl['Rules']:
|
if web_acl['Rules']:
|
||||||
remove_rules_from_web_acl(client, module, web_acl_id)
|
remove_rules_from_web_acl(client, module, web_acl_id)
|
||||||
try:
|
try:
|
||||||
run_func_with_change_token_backoff(client, module, {'WebACLId': web_acl_id}, client.delete_web_acl)
|
run_func_with_change_token_backoff(client, module, {'WebACLId': web_acl_id}, client.delete_web_acl, wait=True)
|
||||||
return True, {}
|
return True, {}
|
||||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||||
module.fail_json_aws(e, msg='Could not delete Web ACL')
|
module.fail_json_aws(e, msg='Could not delete Web ACL')
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue