def find_and_update_web_acl(client, module, web_acl_id): acl = get_web_acl(client, module, web_acl_id) rule_lookup = create_rule_lookup(client, module) existing_rules = acl['Rules'] desired_rules = [{ 'RuleId': rule_lookup[rule['name']]['RuleId'], 'Priority': rule['priority'], 'Action': { 'Type': rule['action'].upper() }, 'Type': rule.get('type', 'regular').upper() } for rule in module.params['rules']] missing = [rule for rule in desired_rules if rule not in existing_rules] extras = [] if module.params['purge_rules']: extras = [rule for rule in existing_rules if rule not in desired_rules] insertions = [format_for_update(rule, 'INSERT') for rule in missing] deletions = [format_for_update(rule, 'DELETE') for rule in extras] changed = bool(insertions + deletions) # Purge rules before adding new ones in case a deletion shares the same # priority as an insertion. params = { 'WebACLId': acl['WebACLId'], 'DefaultAction': acl['DefaultAction'] } change_tokens = [] if deletions: try: params['Updates'] = deletions 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: module.fail_json_aws(e, msg='Could not update Web ACL') if insertions: try: params['Updates'] = insertions 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: 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: acl = get_web_acl(client, module, web_acl_id) return changed, acl
def handle_waiter(conn, module, waiter_name, params, start_time): try: get_waiter(conn, waiter_name).wait( **waiter_params(module, params, start_time) ) 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 run_func_with_change_token_backoff(client, module, params, func, wait=False): params['ChangeToken'] = get_change_token(client, module) result = func(**params) if wait: get_waiter( client, 'change_token_in_sync', ).wait( ChangeToken=result['ChangeToken'] ) return result
def find_and_update_web_acl(client, module, web_acl_id): acl = get_web_acl(client, module, web_acl_id) rule_lookup = create_rule_lookup(client, module) existing_rules = acl['Rules'] desired_rules = [{'RuleId': rule_lookup[rule['name']]['RuleId'], 'Priority': rule['priority'], 'Action': {'Type': rule['action'].upper()}, 'Type': rule.get('type', 'regular').upper()} for rule in module.params['rules']] missing = [rule for rule in desired_rules if rule not in existing_rules] extras = [] if module.params['purge_rules']: extras = [rule for rule in existing_rules if rule not in desired_rules] insertions = [format_for_update(rule, 'INSERT') for rule in missing] deletions = [format_for_update(rule, 'DELETE') for rule in extras] changed = bool(insertions + deletions) # Purge rules before adding new ones in case a deletion shares the same # priority as an insertion. params = { 'WebACLId': acl['WebACLId'], 'DefaultAction': acl['DefaultAction'] } change_tokens = [] if deletions: try: params['Updates'] = deletions 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: module.fail_json_aws(e, msg='Could not update Web ACL') if insertions: try: params['Updates'] = insertions 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: 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: acl = get_web_acl(client, module, web_acl_id) return changed, acl
def wait_until_cluster_active(client, module): name = module.params.get('name') wait_timeout = module.params.get('wait_timeout') waiter = get_waiter(client, 'cluster_active') attempts = 1 + int(wait_timeout / waiter.config.delay) waiter.wait(name=name, WaiterConfig={'MaxAttempts': attempts})
def wait(client, db_instance_id, waiter_name, extra_retry_codes): retry = AWSRetry.jittered_backoff(catch_extra_error_codes=extra_retry_codes) try: waiter = client.get_waiter(waiter_name) except ValueError: # using a waiter in ansible.module_utils.aws.waiters waiter = get_waiter(client, waiter_name) waiter.wait(WaiterConfig={'Delay': 60, 'MaxAttempts': 60}, DBInstanceIdentifier=db_instance_id)
def create_vgw(client, module): params = dict() params['Type'] = module.params.get('type') try: response = client.create_vpn_gateway(Type=params['Type']) get_waiter(client, 'vpn_gateway_exists').wait( VpnGatewayIds=[response['VpnGateway']['VpnGatewayId']]) except botocore.exceptions.WaiterError as e: module.fail_json( msg="Failed to wait for Vpn Gateway {0} to be available".format( response['VpnGateway']['VpnGatewayId']), exception=traceback.format_exc()) except botocore.exceptions.ClientError as e: module.fail_json(msg=to_native(e), exception=traceback.format_exc()) result = response return result
def create_vgw(client, module): params = dict() params['Type'] = module.params.get('type') try: response = client.create_vpn_gateway(Type=params['Type']) get_waiter( client, 'vpn_gateway_exists' ).wait( VpnGatewayIds=[response['VpnGateway']['VpnGatewayId']] ) except botocore.exceptions.WaiterError as e: module.fail_json(msg="Failed to wait for Vpn Gateway {0} to be available".format(response['VpnGateway']['VpnGatewayId']), exception=traceback.format_exc()) except botocore.exceptions.ClientError as e: module.fail_json(msg=to_native(e), exception=traceback.format_exc()) result = response return result
def wait_for_cluster_status(client, module, db_cluster_id, waiter_name): try: waiter = get_waiter(client, waiter_name).wait(DBClusterIdentifier=db_cluster_id) except WaiterError as e: if waiter_name == 'cluster_deleted': msg = "Failed to wait for DB cluster {0} to be deleted".format(db_cluster_id) else: msg = "Failed to wait for DB cluster {0} to be available".format(db_cluster_id) module.fail_json_aws(e, msg=msg) except (BotoCoreError, ClientError) as e: module.fail_json_aws(e, msg="Failed with an unexpected error while waiting for the DB cluster {0}".format(db_cluster_id))
def create_vgw(client, module): params = dict() params['Type'] = module.params.get('type') if module.params.get('asn'): params['AmazonSideAsn'] = module.params.get('asn') try: response = client.create_vpn_gateway(**params) get_waiter( client, 'vpn_gateway_exists' ).wait( VpnGatewayIds=[response['VpnGateway']['VpnGatewayId']] ) except botocore.exceptions.WaiterError as e: module.fail_json(msg="Failed to wait for Vpn Gateway {0} to be available".format(response['VpnGateway']['VpnGatewayId']), exception=traceback.format_exc()) except is_boto3_error_code('VpnGatewayLimitExceeded'): module.fail_json(msg="Too many VPN gateways exist in this account.", exception=traceback.format_exc()) except botocore.exceptions.ClientError as e: # pylint: disable=duplicate-except module.fail_json(msg=to_native(e), exception=traceback.format_exc()) result = response return result
def get_target_from_rule(module, client, rule, name, group, groups, vpc_id): """ Returns tuple of (target_type, target, group_created) after validating rule params. rule: Dict describing a rule. name: Name of the security group being managed. groups: Dict of all available security groups. AWS accepts an ip range or a security group as target of a rule. This function validate the rule specification and return either a non-None group_id or a non-None ip range. """ FOREIGN_SECURITY_GROUP_REGEX = r'^([^/]+)/?(sg-\S+)?/(\S+)' group_id = None group_name = None target_group_created = False validate_rule(module, rule) if rule.get('group_id') and re.match(FOREIGN_SECURITY_GROUP_REGEX, rule['group_id']): # this is a foreign Security Group. Since you can't fetch it you must create an instance of it owner_id, group_id, group_name = re.match(FOREIGN_SECURITY_GROUP_REGEX, rule['group_id']).groups() group_instance = dict(UserId=owner_id, GroupId=group_id, GroupName=group_name) groups[group_id] = group_instance groups[group_name] = group_instance # group_id/group_name are mutually exclusive - give group_id more precedence as it is more specific if group_id and group_name: group_name = None return 'group', (owner_id, group_id, group_name), False elif 'group_id' in rule: return 'group', rule['group_id'], False elif 'group_name' in rule: group_name = rule['group_name'] if group_name == name: group_id = group['GroupId'] groups[group_id] = group groups[group_name] = group elif group_name in groups and group.get('VpcId') and groups[group_name].get('VpcId'): # both are VPC groups, this is ok group_id = groups[group_name]['GroupId'] elif group_name in groups and not (group.get('VpcId') or groups[group_name].get('VpcId')): # both are EC2 classic, this is ok group_id = groups[group_name]['GroupId'] else: auto_group = None filters = {'group-name': group_name} if vpc_id: filters['vpc-id'] = vpc_id # if we got here, either the target group does not exist, or there # is a mix of EC2 classic + VPC groups. Mixing of EC2 classic + VPC # is bad, so we have to create a new SG because no compatible group # exists if not rule.get('group_desc', '').strip(): # retry describing the group once try: auto_group = get_security_groups_with_backoff(client, Filters=ansible_dict_to_boto3_filter_list(filters)).get('SecurityGroups', [])[0] except (is_boto3_error_code('InvalidGroup.NotFound'), IndexError): module.fail_json(msg="group %s will be automatically created by rule %s but " "no description was provided" % (group_name, rule)) except ClientError as e: # pylint: disable=duplicate-except module.fail_json_aws(e) elif not module.check_mode: params = dict(GroupName=group_name, Description=rule['group_desc']) if vpc_id: params['VpcId'] = vpc_id try: auto_group = client.create_security_group(**params) get_waiter( client, 'security_group_exists', ).wait( GroupIds=[auto_group['GroupId']], ) except is_boto3_error_code('InvalidGroup.Duplicate'): # The group exists, but didn't show up in any of our describe-security-groups calls # Try searching on a filter for the name, and allow a retry window for AWS to update # the model on their end. try: auto_group = get_security_groups_with_backoff(client, Filters=ansible_dict_to_boto3_filter_list(filters)).get('SecurityGroups', [])[0] except IndexError as e: module.fail_json(msg="Could not create or use existing group '{0}' in rule. Make sure the group exists".format(group_name)) except ClientError as e: module.fail_json_aws( e, msg="Could not create or use existing group '{0}' in rule. Make sure the group exists".format(group_name)) if auto_group is not None: group_id = auto_group['GroupId'] groups[group_id] = auto_group groups[group_name] = auto_group target_group_created = True return 'group', group_id, target_group_created elif 'cidr_ip' in rule: return 'ipv4', validate_ip(module, rule['cidr_ip']), False elif 'cidr_ipv6' in rule: return 'ipv6', validate_ip(module, rule['cidr_ipv6']), False elif 'ip_prefix' in rule: return 'ip_prefix', rule['ip_prefix'], False module.fail_json(msg="Could not match target for rule {0}".format(rule), failed_rule=rule)
def ensure_vgw_present(client, module): # If an existing vgw name and type matches our args, then a match is considered to have been # found and we will not create another vgw. changed = False params = dict() result = dict() params['Name'] = module.params.get('name') params['VpcId'] = module.params.get('vpc_id') params['Type'] = module.params.get('type') params['Tags'] = module.params.get('tags') params['VpnGatewayIds'] = module.params.get('vpn_gateway_id') # check that the vpc_id exists. If not, an exception is thrown if params['VpcId']: vpc = find_vpc(client, module) # check if a gateway matching our module args already exists existing_vgw = find_vgw(client, module) if existing_vgw != []: vpn_gateway_id = existing_vgw[0]['VpnGatewayId'] vgw, changed = check_tags(client, module, existing_vgw, vpn_gateway_id) # if a vpc_id was provided, check if it exists and if it's attached if params['VpcId']: current_vpc_attachments = existing_vgw[0]['VpcAttachments'] if current_vpc_attachments != [] and current_vpc_attachments[0]['State'] == 'attached': if current_vpc_attachments[0]['VpcId'] != params['VpcId'] or current_vpc_attachments[0]['State'] != 'attached': # detach the existing vpc from the virtual gateway vpc_to_detach = current_vpc_attachments[0]['VpcId'] detach_vgw(client, module, vpn_gateway_id, vpc_to_detach) get_waiter(client, 'vpn_gateway_detached').wait(VpnGatewayIds=[vpn_gateway_id]) attached_vgw = attach_vgw(client, module, vpn_gateway_id) changed = True else: # attach the vgw to the supplied vpc attached_vgw = attach_vgw(client, module, vpn_gateway_id) changed = True # if params['VpcId'] is not provided, check the vgw is attached to a vpc. if so, detach it. else: existing_vgw = find_vgw(client, module, [vpn_gateway_id]) if existing_vgw[0]['VpcAttachments'] != []: if existing_vgw[0]['VpcAttachments'][0]['State'] == 'attached': # detach the vpc from the vgw vpc_to_detach = existing_vgw[0]['VpcAttachments'][0]['VpcId'] detach_vgw(client, module, vpn_gateway_id, vpc_to_detach) changed = True else: # create a new vgw new_vgw = create_vgw(client, module) changed = True vpn_gateway_id = new_vgw['VpnGateway']['VpnGatewayId'] # tag the new virtual gateway create_tags(client, module, vpn_gateway_id) # if a vpc-id was supplied, attempt to attach it to the vgw if params['VpcId']: attached_vgw = attach_vgw(client, module, vpn_gateway_id) changed = True # return current state of the vgw vgw = find_vgw(client, module, [vpn_gateway_id]) result = get_vgw_info(vgw) return changed, result
def ensure_route_table_present(connection, module): lookup = module.params.get('lookup') propagating_vgw_ids = module.params.get('propagating_vgw_ids') purge_routes = module.params.get('purge_routes') purge_subnets = module.params.get('purge_subnets') purge_tags = module.params.get('purge_tags') route_table_id = module.params.get('route_table_id') subnets = module.params.get('subnets') tags = module.params.get('tags') vpc_id = module.params.get('vpc_id') routes = create_route_spec(connection, module, vpc_id) changed = False tags_valid = False if lookup == 'tag': if tags is not None: try: route_table = get_route_table_by_tags(connection, module, vpc_id, tags) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws( e, msg="Error finding route table with lookup 'tag'") else: route_table = None elif lookup == 'id': try: route_table = get_route_table_by_id(connection, module, route_table_id) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws( e, msg="Error finding route table with lookup 'id'") # If no route table returned then create new route table if route_table is None: changed = True if not module.check_mode: try: route_table = connection.create_route_table( VpcId=vpc_id)['RouteTable'] # try to wait for route table to be present before moving on get_waiter(connection, 'route_table_exists').wait( RouteTableIds=[route_table['RouteTableId']], ) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Error creating route table") else: route_table = { "id": "rtb-xxxxxxxx", "route_table_id": "rtb-xxxxxxxx", "vpc_id": vpc_id } module.exit_json(changed=changed, route_table=route_table) if routes is not None: result = ensure_routes(connection=connection, module=module, route_table=route_table, route_specs=routes, propagating_vgw_ids=propagating_vgw_ids, check_mode=module.check_mode, purge_routes=purge_routes) changed = changed or result['changed'] if propagating_vgw_ids is not None: result = ensure_propagation(connection=connection, module=module, route_table=route_table, propagating_vgw_ids=propagating_vgw_ids, check_mode=module.check_mode) changed = changed or result['changed'] if not tags_valid and tags is not None: result = ensure_tags(connection=connection, module=module, resource_id=route_table['RouteTableId'], tags=tags, purge_tags=purge_tags, check_mode=module.check_mode) route_table['Tags'] = result['tags'] changed = changed or result['changed'] if subnets is not None: associated_subnets = find_subnets(connection, module, vpc_id, subnets) result = ensure_subnet_associations(connection=connection, module=module, route_table=route_table, subnets=associated_subnets, check_mode=module.check_mode, purge_subnets=purge_subnets) changed = changed or result['changed'] if changed: # pause to allow route table routes/subnets/associations to be updated before exiting with final state sleep(5) module.exit_json(changed=changed, route_table=get_route_table_info(connection, module, route_table))
def ensure_route_table_present(connection, module): lookup = module.params.get('lookup') propagating_vgw_ids = module.params.get('propagating_vgw_ids') purge_routes = module.params.get('purge_routes') purge_subnets = module.params.get('purge_subnets') purge_tags = module.params.get('purge_tags') route_table_id = module.params.get('route_table_id') subnets = module.params.get('subnets') tags = module.params.get('tags') vpc_id = module.params.get('vpc_id') routes = create_route_spec(connection, module, vpc_id) changed = False tags_valid = False if lookup == 'tag': if tags is not None: try: route_table = get_route_table_by_tags(connection, module, vpc_id, tags) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Error finding route table with lookup 'tag'") else: route_table = None elif lookup == 'id': try: route_table = get_route_table_by_id(connection, module, route_table_id) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Error finding route table with lookup 'id'") # If no route table returned then create new route table if route_table is None: changed = True if not module.check_mode: try: route_table = connection.create_route_table(VpcId=vpc_id)['RouteTable'] # try to wait for route table to be present before moving on get_waiter( connection, 'route_table_exists' ).wait( RouteTableIds=[route_table['RouteTableId']], ) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Error creating route table") else: route_table = {"id": "rtb-xxxxxxxx", "route_table_id": "rtb-xxxxxxxx", "vpc_id": vpc_id} module.exit_json(changed=changed, route_table=route_table) if routes is not None: result = ensure_routes(connection=connection, module=module, route_table=route_table, route_specs=routes, propagating_vgw_ids=propagating_vgw_ids, check_mode=module.check_mode, purge_routes=purge_routes) changed = changed or result['changed'] if propagating_vgw_ids is not None: result = ensure_propagation(connection=connection, module=module, route_table=route_table, propagating_vgw_ids=propagating_vgw_ids, check_mode=module.check_mode) changed = changed or result['changed'] if not tags_valid and tags is not None: result = ensure_tags(connection=connection, module=module, resource_id=route_table['RouteTableId'], tags=tags, purge_tags=purge_tags, check_mode=module.check_mode) route_table['Tags'] = result['tags'] changed = changed or result['changed'] if subnets is not None: associated_subnets = find_subnets(connection, module, vpc_id, subnets) result = ensure_subnet_associations(connection=connection, module=module, route_table=route_table, subnets=associated_subnets, check_mode=module.check_mode, purge_subnets=purge_subnets) changed = changed or result['changed'] if changed: # pause to allow route table routes/subnets/associations to be updated before exiting with final state sleep(5) module.exit_json(changed=changed, route_table=get_route_table_info(connection, module, route_table))