Update AWS modules that use to implicitly retry on NotFound errors (#67369)
* Update AWS modules that expect to retry on exception codes that match the regex '^\w+.NotFound' Modules should intentionally define any extra error codes Use a waiter for ec2_vpc_igw after creating an internet gateway instead of retrying on InvalidInternetGatewayID.NotFound
This commit is contained in:
parent
ad8df69b58
commit
195ded6527
11 changed files with 53 additions and 26 deletions
|
@ -71,18 +71,18 @@ class ACMServiceManager(object):
|
||||||
kwargs['CertificateStatuses'] = statuses
|
kwargs['CertificateStatuses'] = statuses
|
||||||
return paginator.paginate(**kwargs).build_full_result()['CertificateSummaryList']
|
return paginator.paginate(**kwargs).build_full_result()['CertificateSummaryList']
|
||||||
|
|
||||||
@AWSRetry.backoff(tries=5, delay=5, backoff=2.0)
|
@AWSRetry.backoff(tries=5, delay=5, backoff=2.0, catch_extra_error_codes=['ResourceNotFoundException'])
|
||||||
def get_certificate_with_backoff(self, client, certificate_arn):
|
def get_certificate_with_backoff(self, client, certificate_arn):
|
||||||
response = client.get_certificate(CertificateArn=certificate_arn)
|
response = client.get_certificate(CertificateArn=certificate_arn)
|
||||||
# strip out response metadata
|
# strip out response metadata
|
||||||
return {'Certificate': response['Certificate'],
|
return {'Certificate': response['Certificate'],
|
||||||
'CertificateChain': response['CertificateChain']}
|
'CertificateChain': response['CertificateChain']}
|
||||||
|
|
||||||
@AWSRetry.backoff(tries=5, delay=5, backoff=2.0)
|
@AWSRetry.backoff(tries=5, delay=5, backoff=2.0, catch_extra_error_codes=['ResourceNotFoundException'])
|
||||||
def describe_certificate_with_backoff(self, client, certificate_arn):
|
def describe_certificate_with_backoff(self, client, certificate_arn):
|
||||||
return client.describe_certificate(CertificateArn=certificate_arn)['Certificate']
|
return client.describe_certificate(CertificateArn=certificate_arn)['Certificate']
|
||||||
|
|
||||||
@AWSRetry.backoff(tries=5, delay=5, backoff=2.0)
|
@AWSRetry.backoff(tries=5, delay=5, backoff=2.0, catch_extra_error_codes=['ResourceNotFoundException'])
|
||||||
def list_certificate_tags_with_backoff(self, client, certificate_arn):
|
def list_certificate_tags_with_backoff(self, client, certificate_arn):
|
||||||
return client.list_tags_for_certificate(CertificateArn=certificate_arn)['Tags']
|
return client.list_tags_for_certificate(CertificateArn=certificate_arn)['Tags']
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,24 @@ except ImportError:
|
||||||
ec2_data = {
|
ec2_data = {
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"waiters": {
|
"waiters": {
|
||||||
|
"InternetGatewayExists": {
|
||||||
|
"delay": 5,
|
||||||
|
"maxAttempts": 40,
|
||||||
|
"operation": "DescribeInternetGateways",
|
||||||
|
"acceptors": [
|
||||||
|
{
|
||||||
|
"matcher": "path",
|
||||||
|
"expected": True,
|
||||||
|
"argument": "length(InternetGateways) > `0`",
|
||||||
|
"state": "success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "error",
|
||||||
|
"expected": "InvalidInternetGatewayID.NotFound",
|
||||||
|
"state": "retry"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
"RouteTableExists": {
|
"RouteTableExists": {
|
||||||
"delay": 5,
|
"delay": 5,
|
||||||
"maxAttempts": 40,
|
"maxAttempts": 40,
|
||||||
|
@ -280,6 +298,12 @@ def rds_model(name):
|
||||||
|
|
||||||
|
|
||||||
waiters_by_name = {
|
waiters_by_name = {
|
||||||
|
('EC2', 'internet_gateway_exists'): lambda ec2: core_waiter.Waiter(
|
||||||
|
'internet_gateway_exists',
|
||||||
|
ec2_model('InternetGatewayExists'),
|
||||||
|
core_waiter.NormalizedOperationMethod(
|
||||||
|
ec2.describe_internet_gateways
|
||||||
|
)),
|
||||||
('EC2', 'route_table_exists'): lambda ec2: core_waiter.Waiter(
|
('EC2', 'route_table_exists'): lambda ec2: core_waiter.Waiter(
|
||||||
'route_table_exists',
|
'route_table_exists',
|
||||||
ec2_model('RouteTableExists'),
|
ec2_model('RouteTableExists'),
|
||||||
|
|
|
@ -370,8 +370,7 @@ def stack_set_facts(cfn, stack_set_name):
|
||||||
ss['Tags'] = boto3_tag_list_to_ansible_dict(ss['Tags'])
|
ss['Tags'] = boto3_tag_list_to_ansible_dict(ss['Tags'])
|
||||||
return ss
|
return ss
|
||||||
except cfn.exceptions.from_code('StackSetNotFound'):
|
except cfn.exceptions.from_code('StackSetNotFound'):
|
||||||
# catch NotFound error before the retry kicks in to avoid waiting
|
# Return None if the stack doesn't exist
|
||||||
# if the stack does not exist
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@ -429,14 +428,15 @@ def await_stack_instance_completion(module, cfn, stack_set_name, max_wait):
|
||||||
|
|
||||||
|
|
||||||
def await_stack_set_exists(cfn, stack_set_name):
|
def await_stack_set_exists(cfn, stack_set_name):
|
||||||
# AWSRetry will retry on `NotFound` errors for us
|
# AWSRetry will retry on `StackSetNotFound` errors for us
|
||||||
ss = cfn.describe_stack_set(StackSetName=stack_set_name, aws_retry=True)['StackSet']
|
ss = cfn.describe_stack_set(StackSetName=stack_set_name, aws_retry=True)['StackSet']
|
||||||
ss['Tags'] = boto3_tag_list_to_ansible_dict(ss['Tags'])
|
ss['Tags'] = boto3_tag_list_to_ansible_dict(ss['Tags'])
|
||||||
return camel_dict_to_snake_dict(ss, ignore_list=('Tags',))
|
return camel_dict_to_snake_dict(ss, ignore_list=('Tags',))
|
||||||
|
|
||||||
|
|
||||||
def describe_stack_tree(module, stack_set_name, operation_ids=None):
|
def describe_stack_tree(module, stack_set_name, operation_ids=None):
|
||||||
cfn = module.client('cloudformation', retry_decorator=AWSRetry.jittered_backoff(retries=5, delay=3, max_delay=5))
|
jittered_backoff_decorator = AWSRetry.jittered_backoff(retries=5, delay=3, max_delay=5, catch_extra_error_codes=['StackSetNotFound'])
|
||||||
|
cfn = module.client('cloudformation', retry_decorator=jittered_backoff_decorator)
|
||||||
result = dict()
|
result = dict()
|
||||||
result['stack_set'] = camel_dict_to_snake_dict(
|
result['stack_set'] = camel_dict_to_snake_dict(
|
||||||
cfn.describe_stack_set(
|
cfn.describe_stack_set(
|
||||||
|
@ -536,7 +536,8 @@ def main():
|
||||||
|
|
||||||
# Wrap the cloudformation client methods that this module uses with
|
# Wrap the cloudformation client methods that this module uses with
|
||||||
# automatic backoff / retry for throttling error codes
|
# automatic backoff / retry for throttling error codes
|
||||||
cfn = module.client('cloudformation', retry_decorator=AWSRetry.jittered_backoff(retries=10, delay=3, max_delay=30))
|
jittered_backoff_decorator = AWSRetry.jittered_backoff(retries=10, delay=3, max_delay=30, catch_extra_error_codes=['StackSetNotFound'])
|
||||||
|
cfn = module.client('cloudformation', retry_decorator=jittered_backoff_decorator)
|
||||||
existing_stack_set = stack_set_facts(cfn, module.params['name'])
|
existing_stack_set = stack_set_facts(cfn, module.params['name'])
|
||||||
|
|
||||||
operation_uuid = to_native(uuid.uuid4())
|
operation_uuid = to_native(uuid.uuid4())
|
||||||
|
|
|
@ -545,7 +545,7 @@ def rule_from_group_permission(perm):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@AWSRetry.backoff(tries=5, delay=5, backoff=2.0)
|
@AWSRetry.backoff(tries=5, delay=5, backoff=2.0, catch_extra_error_codes=['InvalidGroup.NotFound'])
|
||||||
def get_security_groups_with_backoff(connection, **kwargs):
|
def get_security_groups_with_backoff(connection, **kwargs):
|
||||||
return connection.describe_security_groups(**kwargs)
|
return connection.describe_security_groups(**kwargs)
|
||||||
|
|
||||||
|
|
|
@ -479,7 +479,7 @@ def delete_template(module):
|
||||||
|
|
||||||
|
|
||||||
def create_or_update(module, template_options):
|
def create_or_update(module, template_options):
|
||||||
ec2 = module.client('ec2', retry_decorator=AWSRetry.jittered_backoff())
|
ec2 = module.client('ec2', retry_decorator=AWSRetry.jittered_backoff(catch_extra_error_codes=['InvalidLaunchTemplateId.NotFound']))
|
||||||
template, template_versions = existing_templates(module)
|
template, template_versions = existing_templates(module)
|
||||||
out = {}
|
out = {}
|
||||||
lt_data = params_to_launch_data(module, dict((k, v) for k, v in module.params.items() if k in template_options))
|
lt_data = params_to_launch_data(module, dict((k, v) for k, v in module.params.items() if k in template_options))
|
||||||
|
|
|
@ -213,12 +213,11 @@ class AnsibleEc2TgwInfo(object):
|
||||||
try:
|
try:
|
||||||
response = self._connection.describe_transit_gateways(
|
response = self._connection.describe_transit_gateways(
|
||||||
TransitGatewayIds=transit_gateway_ids, Filters=filters)
|
TransitGatewayIds=transit_gateway_ids, Filters=filters)
|
||||||
except (BotoCoreError, ClientError) as e:
|
except ClientError as e:
|
||||||
if e.response['Error']['Code'] == 'InvalidTransitGatewayID.NotFound':
|
if e.response['Error']['Code'] == 'InvalidTransitGatewayID.NotFound':
|
||||||
self._results['transit_gateways'] = []
|
self._results['transit_gateways'] = []
|
||||||
return
|
return
|
||||||
else:
|
raise
|
||||||
self._module.fail_json_aws(e)
|
|
||||||
|
|
||||||
for transit_gateway in response['TransitGateways']:
|
for transit_gateway in response['TransitGateways']:
|
||||||
transit_gateway_info.append(camel_dict_to_snake_dict(transit_gateway, ignore_list=['Tags']))
|
transit_gateway_info.append(camel_dict_to_snake_dict(transit_gateway, ignore_list=['Tags']))
|
||||||
|
@ -257,7 +256,10 @@ def main():
|
||||||
)
|
)
|
||||||
|
|
||||||
tgwf_manager = AnsibleEc2TgwInfo(module=module, results=results)
|
tgwf_manager = AnsibleEc2TgwInfo(module=module, results=results)
|
||||||
|
try:
|
||||||
tgwf_manager.describe_transit_gateways()
|
tgwf_manager.describe_transit_gateways()
|
||||||
|
except (BotoCoreError, ClientError) as e:
|
||||||
|
module.fail_json_aws(e)
|
||||||
|
|
||||||
module.exit_json(**results)
|
module.exit_json(**results)
|
||||||
|
|
||||||
|
|
|
@ -219,7 +219,7 @@ def retry_not_found(to_call, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
return to_call(*args, **kwargs)
|
return to_call(*args, **kwargs)
|
||||||
except EC2ResponseError as e:
|
except EC2ResponseError as e:
|
||||||
if e.error_code == 'InvalidDhcpOptionID.NotFound':
|
if e.error_code in ['InvalidDhcpOptionID.NotFound', 'InvalidDhcpOptionsID.NotFound']:
|
||||||
sleep(3)
|
sleep(3)
|
||||||
continue
|
continue
|
||||||
raise e
|
raise e
|
||||||
|
|
|
@ -91,6 +91,7 @@ except ImportError:
|
||||||
pass # caught by AnsibleAWSModule
|
pass # caught by AnsibleAWSModule
|
||||||
|
|
||||||
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 (
|
from ansible.module_utils.ec2 import (
|
||||||
AWSRetry,
|
AWSRetry,
|
||||||
camel_dict_to_snake_dict,
|
camel_dict_to_snake_dict,
|
||||||
|
@ -237,6 +238,11 @@ class AnsibleEc2Igw(object):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = self._connection.create_internet_gateway()
|
response = self._connection.create_internet_gateway()
|
||||||
|
|
||||||
|
# Ensure the gateway exists before trying to attach it or add tags
|
||||||
|
waiter = get_waiter(self._connection, 'internet_gateway_exists')
|
||||||
|
waiter.wait(InternetGatewayIds=[response['InternetGateway']['InternetGatewayId']])
|
||||||
|
|
||||||
igw = camel_dict_to_snake_dict(response['InternetGateway'])
|
igw = camel_dict_to_snake_dict(response['InternetGateway'])
|
||||||
self._connection.attach_internet_gateway(InternetGatewayId=igw['internet_gateway_id'], VpcId=vpc_id)
|
self._connection.attach_internet_gateway(InternetGatewayId=igw['internet_gateway_id'], VpcId=vpc_id)
|
||||||
self._results['changed'] = True
|
self._results['changed'] = True
|
||||||
|
|
|
@ -253,10 +253,7 @@ def get_vpc(module, connection, vpc_id):
|
||||||
module.fail_json_aws(e, msg="Unable to wait for VPC {0} to be available.".format(vpc_id))
|
module.fail_json_aws(e, msg="Unable to wait for VPC {0} to be available.".format(vpc_id))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
vpc_obj = AWSRetry.backoff(
|
vpc_obj = connection.describe_vpcs(VpcIds=[vpc_id], aws_retry=True)['Vpcs'][0]
|
||||||
delay=3, tries=8,
|
|
||||||
catch_extra_error_codes=['InvalidVpcID.NotFound'],
|
|
||||||
)(connection.describe_vpcs)(VpcIds=[vpc_id])['Vpcs'][0]
|
|
||||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||||
module.fail_json_aws(e, msg="Failed to describe VPCs")
|
module.fail_json_aws(e, msg="Failed to describe VPCs")
|
||||||
try:
|
try:
|
||||||
|
@ -279,10 +276,7 @@ def update_vpc_tags(connection, module, vpc_id, tags, name):
|
||||||
if tags_to_update:
|
if tags_to_update:
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
tags = ansible_dict_to_boto3_tag_list(tags_to_update)
|
tags = ansible_dict_to_boto3_tag_list(tags_to_update)
|
||||||
vpc_obj = AWSRetry.backoff(
|
vpc_obj = connection.create_tags(Resources=[vpc_id], Tags=tags, aws_retry=True)
|
||||||
delay=1, tries=5,
|
|
||||||
catch_extra_error_codes=['InvalidVpcID.NotFound'],
|
|
||||||
)(connection.create_tags)(Resources=[vpc_id], Tags=tags)
|
|
||||||
|
|
||||||
# Wait for tags to be updated
|
# Wait for tags to be updated
|
||||||
expected_tags = boto3_tag_list_to_ansible_dict(tags)
|
expected_tags = boto3_tag_list_to_ansible_dict(tags)
|
||||||
|
|
|
@ -126,7 +126,7 @@ except ImportError:
|
||||||
HAS_BOTO3 = False
|
HAS_BOTO3 = False
|
||||||
|
|
||||||
|
|
||||||
@AWSRetry.jittered_backoff(retries=10, delay=10)
|
@AWSRetry.jittered_backoff(retries=10, delay=10, catch_extra_error_codes=['TargetGroupNotFound'])
|
||||||
def describe_target_groups_with_backoff(connection, tg_name):
|
def describe_target_groups_with_backoff(connection, tg_name):
|
||||||
return connection.describe_target_groups(Names=[tg_name])
|
return connection.describe_target_groups(Names=[tg_name])
|
||||||
|
|
||||||
|
@ -147,7 +147,7 @@ def convert_tg_name_to_arn(connection, module, tg_name):
|
||||||
return tg_arn
|
return tg_arn
|
||||||
|
|
||||||
|
|
||||||
@AWSRetry.jittered_backoff(retries=10, delay=10)
|
@AWSRetry.jittered_backoff(retries=10, delay=10, catch_extra_error_codes=['TargetGroupNotFound'])
|
||||||
def describe_targets_with_backoff(connection, tg_arn, target):
|
def describe_targets_with_backoff(connection, tg_arn, target):
|
||||||
if target is None:
|
if target is None:
|
||||||
tg = []
|
tg = []
|
||||||
|
|
|
@ -338,7 +338,7 @@ def main():
|
||||||
required_if=[['state', 'present', ['db_instance_identifier']]]
|
required_if=[['state', 'present', ['db_instance_identifier']]]
|
||||||
)
|
)
|
||||||
|
|
||||||
client = module.client('rds', retry_decorator=AWSRetry.jittered_backoff(retries=10))
|
client = module.client('rds', retry_decorator=AWSRetry.jittered_backoff(retries=10, catch_extra_error_codes=['DBSnapshotNotFound']))
|
||||||
|
|
||||||
if module.params['state'] == 'absent':
|
if module.params['state'] == 'absent':
|
||||||
ret_dict = ensure_snapshot_absent(client, module)
|
ret_dict = ensure_snapshot_absent(client, module)
|
||||||
|
|
Loading…
Reference in a new issue