mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-22 21:00:22 -07:00
* stabilize ec2_vpc_subnet module * Add waiters for ec2_vpc_subnet Clean up integration tests * Reenable CI for stabilized ec2_vpc_subnet tests * rename waiters * Use module_json_aws where applicable Handle WaiterError first if waiting failed * Fix traceback when tagging with keys/values that look like booleans * Fix check mode with tags * Add integration tests for tags that look like booleans and check mode * Add waiter for deleting subnet * Sleep a few seconds after using aws command line
This commit is contained in:
parent
95c15757a2
commit
873a9ddf8d
4 changed files with 626 additions and 113 deletions
|
@ -209,9 +209,11 @@ import traceback
|
|||
try:
|
||||
import botocore
|
||||
except ImportError:
|
||||
pass # caught by imported boto3
|
||||
pass # caught by AnsibleAWSModule
|
||||
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||
from ansible.module_utils.aws.waiters import get_waiter
|
||||
from ansible.module_utils.ec2 import (ansible_dict_to_boto3_filter_list, ansible_dict_to_boto3_tag_list,
|
||||
ec2_argument_spec, camel_dict_to_snake_dict, get_aws_connection_info,
|
||||
boto3_conn, boto3_tag_list_to_ansible_dict, compare_aws_tags, AWSRetry)
|
||||
|
@ -251,7 +253,24 @@ def describe_subnets_with_backoff(client, **params):
|
|||
return client.describe_subnets(**params)
|
||||
|
||||
|
||||
def create_subnet(conn, module, vpc_id, cidr, ipv6_cidr=None, az=None):
|
||||
def wait_config(wait_timeout, start_time):
|
||||
remaining_wait_timeout = int(wait_timeout + start_time - time.time())
|
||||
return {'Delay': 5, 'MaxAttempts': remaining_wait_timeout // 5}
|
||||
|
||||
|
||||
def handle_waiter(conn, module, waiter_name, params, start_time):
|
||||
params['WaiterConfig'] = wait_config(module.params['wait_timeout'], start_time)
|
||||
try:
|
||||
get_waiter(conn, waiter_name).wait(
|
||||
**params
|
||||
)
|
||||
except botocore.exceptions.WaiterError as e:
|
||||
module.fail_json_aws(e, "Failed to wait for updates to complete")
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, "An exception happened while trying to wait for updates")
|
||||
|
||||
|
||||
def create_subnet(conn, module, vpc_id, cidr, ipv6_cidr=None, az=None, start_time=None):
|
||||
wait = module.params['wait']
|
||||
wait_timeout = module.params['wait_timeout']
|
||||
|
||||
|
@ -273,20 +292,20 @@ def create_subnet(conn, module, vpc_id, cidr, ipv6_cidr=None, az=None):
|
|||
# new subnets's id to do things like create tags results in
|
||||
# exception.
|
||||
if wait and subnet.get('state') != 'available':
|
||||
delay = 5
|
||||
max_attempts = wait_timeout / delay
|
||||
waiter_config = dict(Delay=delay, MaxAttempts=max_attempts)
|
||||
waiter = conn.get_waiter('subnet_available')
|
||||
handle_waiter(conn, module, 'subnet_exists', {'SubnetIds': [subnet['id']]}, start_time)
|
||||
try:
|
||||
waiter.wait(SubnetIds=[subnet['id']], WaiterConfig=waiter_config)
|
||||
conn.get_waiter('subnet_available').wait(
|
||||
SubnetIds=[subnet['id']],
|
||||
WaiterConfig=wait_config(wait_timeout, start_time)
|
||||
)
|
||||
subnet['state'] = 'available'
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json(msg="Create subnet action timed out waiting for Subnet to become available.")
|
||||
module.fail_json_aws(e, "Create subnet action timed out waiting for subnet to become available")
|
||||
|
||||
return subnet
|
||||
|
||||
|
||||
def ensure_tags(conn, module, subnet, tags, purge_tags):
|
||||
def ensure_tags(conn, module, subnet, tags, purge_tags, start_time):
|
||||
changed = False
|
||||
|
||||
filters = ansible_dict_to_boto3_filter_list({'resource-id': subnet['id'], 'resource-type': 'subnet'})
|
||||
|
@ -300,7 +319,12 @@ def ensure_tags(conn, module, subnet, tags, purge_tags):
|
|||
if to_update:
|
||||
try:
|
||||
if not module.check_mode:
|
||||
conn.create_tags(Resources=[subnet['id']], Tags=ansible_dict_to_boto3_tag_list(to_update))
|
||||
AWSRetry.exponential_backoff(
|
||||
catch_extra_error_codes=['InvalidSubnetID.NotFound']
|
||||
)(conn.create_tags)(
|
||||
Resources=[subnet['id']],
|
||||
Tags=ansible_dict_to_boto3_tag_list(to_update)
|
||||
)
|
||||
|
||||
changed = True
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
|
@ -313,16 +337,24 @@ def ensure_tags(conn, module, subnet, tags, purge_tags):
|
|||
for key in to_delete:
|
||||
tags_list.append({'Key': key})
|
||||
|
||||
conn.delete_tags(Resources=[subnet['id']], Tags=tags_list)
|
||||
AWSRetry.exponential_backoff(
|
||||
catch_extra_error_codes=['InvalidSubnetID.NotFound']
|
||||
)(conn.delete_tags)(Resources=[subnet['id']], Tags=tags_list)
|
||||
|
||||
changed = True
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't delete tags")
|
||||
|
||||
if module.params['wait'] and not module.check_mode:
|
||||
# Wait for tags to be updated
|
||||
filters = [{'Name': 'tag:{0}'.format(k), 'Values': [v]} for k, v in tags.items()]
|
||||
handle_waiter(conn, module, 'subnet_exists',
|
||||
{'SubnetIds': [subnet['id']], 'Filters': filters}, start_time)
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def ensure_map_public(conn, module, subnet, map_public, check_mode):
|
||||
def ensure_map_public(conn, module, subnet, map_public, check_mode, start_time):
|
||||
if check_mode:
|
||||
return
|
||||
try:
|
||||
|
@ -330,20 +362,35 @@ def ensure_map_public(conn, module, subnet, map_public, check_mode):
|
|||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't modify subnet attribute")
|
||||
|
||||
if module.params['wait']:
|
||||
if map_public:
|
||||
handle_waiter(conn, module, 'subnet_has_map_public',
|
||||
{'SubnetIds': [subnet['id']]}, start_time)
|
||||
else:
|
||||
handle_waiter(conn, module, 'subnet_no_map_public',
|
||||
{'SubnetIds': [subnet['id']]}, start_time)
|
||||
|
||||
def ensure_assign_ipv6_on_create(conn, module, subnet, assign_instances_ipv6, check_mode):
|
||||
|
||||
def ensure_assign_ipv6_on_create(conn, module, subnet, assign_instances_ipv6, check_mode, start_time):
|
||||
if check_mode:
|
||||
return
|
||||
|
||||
try:
|
||||
conn.modify_subnet_attribute(SubnetId=subnet['id'], AssignIpv6AddressOnCreation={'Value': assign_instances_ipv6})
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't modify subnet attribute")
|
||||
|
||||
if module.params['wait']:
|
||||
if assign_instances_ipv6:
|
||||
handle_waiter(conn, module, 'subnet_has_assign_ipv6',
|
||||
{'SubnetIds': [subnet['id']]}, start_time)
|
||||
else:
|
||||
handle_waiter(conn, module, 'subnet_no_assign_ipv6',
|
||||
{'SubnetIds': [subnet['id']]}, start_time)
|
||||
|
||||
def disassociate_ipv6_cidr(conn, module, subnet):
|
||||
|
||||
def disassociate_ipv6_cidr(conn, module, subnet, start_time):
|
||||
if subnet.get('assign_ipv6_address_on_creation'):
|
||||
ensure_assign_ipv6_on_create(conn, module, subnet, False, False)
|
||||
ensure_assign_ipv6_on_create(conn, module, subnet, False, False, start_time)
|
||||
|
||||
try:
|
||||
conn.disassociate_subnet_cidr_block(AssociationId=subnet['ipv6_association_id'])
|
||||
|
@ -351,13 +398,23 @@ def disassociate_ipv6_cidr(conn, module, subnet):
|
|||
module.fail_json_aws(e, msg="Couldn't disassociate ipv6 cidr block id {0} from subnet {1}"
|
||||
.format(subnet['ipv6_association_id'], subnet['id']))
|
||||
|
||||
# Wait for cidr block to be disassociated
|
||||
if module.params['wait']:
|
||||
filters = ansible_dict_to_boto3_filter_list(
|
||||
{'ipv6-cidr-block-association.state': ['disassociated'],
|
||||
'vpc-id': subnet['vpc_id']}
|
||||
)
|
||||
handle_waiter(conn, module, 'subnet_exists',
|
||||
{'SubnetIds': [subnet['id']], 'Filters': filters}, start_time)
|
||||
|
||||
def ensure_ipv6_cidr_block(conn, module, subnet, ipv6_cidr, check_mode):
|
||||
|
||||
def ensure_ipv6_cidr_block(conn, module, subnet, ipv6_cidr, check_mode, start_time):
|
||||
wait = module.params['wait']
|
||||
changed = False
|
||||
|
||||
if subnet['ipv6_association_id'] and not ipv6_cidr:
|
||||
if not check_mode:
|
||||
disassociate_ipv6_cidr(conn, module, subnet)
|
||||
disassociate_ipv6_cidr(conn, module, subnet, start_time)
|
||||
changed = True
|
||||
|
||||
if ipv6_cidr:
|
||||
|
@ -374,7 +431,7 @@ def ensure_ipv6_cidr_block(conn, module, subnet, ipv6_cidr, check_mode):
|
|||
|
||||
if subnet['ipv6_association_id']:
|
||||
if not check_mode:
|
||||
disassociate_ipv6_cidr(conn, module, subnet)
|
||||
disassociate_ipv6_cidr(conn, module, subnet, start_time)
|
||||
changed = True
|
||||
|
||||
try:
|
||||
|
@ -383,6 +440,14 @@ def ensure_ipv6_cidr_block(conn, module, subnet, ipv6_cidr, check_mode):
|
|||
changed = True
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't associate ipv6 cidr {0} to {1}".format(ipv6_cidr, subnet['id']))
|
||||
else:
|
||||
if not check_mode and wait:
|
||||
filters = ansible_dict_to_boto3_filter_list(
|
||||
{'ipv6-cidr-block-association.state': ['associated'],
|
||||
'vpc-id': subnet['vpc_id']}
|
||||
)
|
||||
handle_waiter(conn, module, 'subnet_exists',
|
||||
{'SubnetIds': [subnet['id']], 'Filters': filters}, start_time)
|
||||
|
||||
if associate_resp.get('Ipv6CidrBlockAssociation', {}).get('AssociationId'):
|
||||
subnet['ipv6_association_id'] = associate_resp['Ipv6CidrBlockAssociation']['AssociationId']
|
||||
|
@ -411,9 +476,14 @@ def get_matching_subnet(conn, module, vpc_id, cidr):
|
|||
def ensure_subnet_present(conn, module):
|
||||
subnet = get_matching_subnet(conn, module, module.params['vpc_id'], module.params['cidr'])
|
||||
changed = False
|
||||
|
||||
# Initialize start so max time does not exceed the specified wait_timeout for multiple operations
|
||||
start_time = time.time()
|
||||
|
||||
if subnet is None:
|
||||
if not module.check_mode:
|
||||
subnet = create_subnet(conn, module, module.params['vpc_id'], module.params['cidr'], ipv6_cidr=module.params['ipv6_cidr'], az=module.params['az'])
|
||||
subnet = create_subnet(conn, module, module.params['vpc_id'], module.params['cidr'],
|
||||
ipv6_cidr=module.params['ipv6_cidr'], az=module.params['az'], start_time=start_time)
|
||||
changed = True
|
||||
# Subnet will be None when check_mode is true
|
||||
if subnet is None:
|
||||
|
@ -421,21 +491,24 @@ def ensure_subnet_present(conn, module):
|
|||
'changed': changed,
|
||||
'subnet': {}
|
||||
}
|
||||
if module.params['wait']:
|
||||
handle_waiter(conn, module, 'subnet_exists', {'SubnetIds': [subnet['id']]}, start_time)
|
||||
|
||||
if module.params['ipv6_cidr'] != subnet.get('ipv6_cidr_block'):
|
||||
if ensure_ipv6_cidr_block(conn, module, subnet, module.params['ipv6_cidr'], module.check_mode):
|
||||
if ensure_ipv6_cidr_block(conn, module, subnet, module.params['ipv6_cidr'], module.check_mode, start_time):
|
||||
changed = True
|
||||
|
||||
if module.params['map_public'] != subnet['map_public_ip_on_launch']:
|
||||
ensure_map_public(conn, module, subnet, module.params['map_public'], module.check_mode)
|
||||
ensure_map_public(conn, module, subnet, module.params['map_public'], module.check_mode, start_time)
|
||||
changed = True
|
||||
|
||||
if module.params['assign_instances_ipv6'] != subnet.get('assign_ipv6_address_on_creation'):
|
||||
ensure_assign_ipv6_on_create(conn, module, subnet, module.params['assign_instances_ipv6'], module.check_mode)
|
||||
ensure_assign_ipv6_on_create(conn, module, subnet, module.params['assign_instances_ipv6'], module.check_mode, start_time)
|
||||
changed = True
|
||||
|
||||
if module.params['tags'] != subnet['tags']:
|
||||
if ensure_tags(conn, module, subnet, module.params['tags'], module.params['purge_tags']):
|
||||
stringified_tags_dict = dict((to_text(k), to_text(v)) for k, v in module.params['tags'].items())
|
||||
if ensure_tags(conn, module, subnet, stringified_tags_dict, module.params['purge_tags'], start_time):
|
||||
changed = True
|
||||
|
||||
subnet = get_matching_subnet(conn, module, module.params['vpc_id'], module.params['cidr'])
|
||||
|
@ -454,6 +527,8 @@ def ensure_subnet_absent(conn, module):
|
|||
try:
|
||||
if not module.check_mode:
|
||||
conn.delete_subnet(SubnetId=subnet['id'])
|
||||
if module.params['wait']:
|
||||
handle_waiter(conn, module, 'subnet_deleted', {'SubnetIds': [subnet['id']]}, time.time())
|
||||
return {'changed': True}
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't delete subnet")
|
||||
|
@ -495,8 +570,7 @@ def main():
|
|||
elif state == 'absent':
|
||||
result = ensure_subnet_absent(connection, module)
|
||||
except botocore.exceptions.ClientError as e:
|
||||
module.fail_json(msg=e.message, exception=traceback.format_exc(),
|
||||
**camel_dict_to_snake_dict(e.response))
|
||||
module.fail_json_aws(e)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue