def main(): argument_spec = ec2_argument_spec() argument_spec.update( dict(az=dict(default=None, required=False), cidr=dict(default=None, required=True), ipv6_cidr=dict(default='', required=False), state=dict(default='present', choices=['present', 'absent']), tags=dict(default={}, required=False, type='dict', aliases=['resource_tags']), vpc_id=dict(default=None, required=True), map_public=dict(default=False, required=False, type='bool'), assign_instances_ipv6=dict(default=False, required=False, type='bool'), wait=dict(type='bool', default=True), wait_timeout=dict(type='int', default=300, required=False), purge_tags=dict(default=True, type='bool'))) required_if = [('assign_instances_ipv6', True, ['ipv6_cidr'])] module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True, required_if=required_if) if module.params.get( 'assign_instances_ipv6') and not module.params.get('ipv6_cidr'): module.fail_json( msg= "assign_instances_ipv6 is True but ipv6_cidr is None or an empty string" ) if not module.botocore_at_least("1.7.0"): module.warn( "botocore >= 1.7.0 is required to use wait_timeout for custom wait times" ) region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True) connection = boto3_conn(module, conn_type='client', resource='ec2', region=region, endpoint=ec2_url, **aws_connect_params) state = module.params.get('state') try: if state == 'present': result = ensure_subnet_present(connection, module) elif state == 'absent': result = ensure_subnet_absent(connection, module) except botocore.exceptions.ClientError as e: module.fail_json_aws(e) module.exit_json(**result)
def main(): argument_spec = ec2_argument_spec() argument_spec.update( dict( az=dict(default=None, required=False), cidr=dict(default=None, required=True), ipv6_cidr=dict(default='', required=False), state=dict(default='present', choices=['present', 'absent']), tags=dict(default={}, required=False, type='dict', aliases=['resource_tags']), vpc_id=dict(default=None, required=True), map_public=dict(default=False, required=False, type='bool'), assign_instances_ipv6=dict(default=False, required=False, type='bool'), wait=dict(type='bool', default=True), wait_timeout=dict(type='int', default=300, required=False), purge_tags=dict(default=True, type='bool') ) ) required_if = [('assign_instances_ipv6', True, ['ipv6_cidr'])] module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True, required_if=required_if) if module.params.get('assign_instances_ipv6') and not module.params.get('ipv6_cidr'): module.fail_json(msg="assign_instances_ipv6 is True but ipv6_cidr is None or an empty string") if LooseVersion(botocore.__version__) < "1.7.0": module.warn("botocore >= 1.7.0 is required to use wait_timeout for custom wait times") region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True) connection = boto3_conn(module, conn_type='client', resource='ec2', region=region, endpoint=ec2_url, **aws_connect_params) state = module.params.get('state') try: if state == 'present': result = ensure_subnet_present(connection, module) elif state == 'absent': result = ensure_subnet_absent(connection, module) except botocore.exceptions.ClientError as e: module.fail_json_aws(e) module.exit_json(**result)
def main(): argument_spec = dict( name=dict(required=True), cidr_block=dict(type='list', required=True), ipv6_cidr=dict(type='bool', default=False), tenancy=dict(choices=['default', 'dedicated'], default='default'), dns_support=dict(type='bool', default=True), dns_hostnames=dict(type='bool', default=True), dhcp_opts_id=dict(), tags=dict(type='dict', aliases=['resource_tags']), state=dict(choices=['present', 'absent'], default='present'), multi_ok=dict(type='bool', default=False), purge_cidrs=dict(type='bool', default=False), ) module = AnsibleAWSModule( argument_spec=argument_spec, supports_check_mode=True ) name = module.params.get('name') cidr_block = get_cidr_network_bits(module, module.params.get('cidr_block')) ipv6_cidr = module.params.get('ipv6_cidr') purge_cidrs = module.params.get('purge_cidrs') tenancy = module.params.get('tenancy') dns_support = module.params.get('dns_support') dns_hostnames = module.params.get('dns_hostnames') dhcp_id = module.params.get('dhcp_opts_id') tags = module.params.get('tags') state = module.params.get('state') multi = module.params.get('multi_ok') changed = False connection = module.client( 'ec2', retry_decorator=AWSRetry.jittered_backoff( retries=8, delay=3, catch_extra_error_codes=['InvalidVpcID.NotFound'] ) ) if dns_hostnames and not dns_support: module.fail_json(msg='In order to enable DNS Hostnames you must also enable DNS support') if state == 'present': # Check if VPC exists vpc_id = vpc_exists(module, connection, name, cidr_block, multi) if vpc_id is None: vpc_id = create_vpc(connection, module, cidr_block[0], tenancy) changed = True vpc_obj = get_vpc(module, connection, vpc_id) associated_cidrs = dict((cidr['CidrBlock'], cidr['AssociationId']) for cidr in vpc_obj.get('CidrBlockAssociationSet', []) if cidr['CidrBlockState']['State'] != 'disassociated') to_add = [cidr for cidr in cidr_block if cidr not in associated_cidrs] to_remove = [associated_cidrs[cidr] for cidr in associated_cidrs if cidr not in cidr_block] expected_cidrs = [cidr for cidr in associated_cidrs if associated_cidrs[cidr] not in to_remove] + to_add if len(cidr_block) > 1: for cidr in to_add: changed = True try: connection.associate_vpc_cidr_block(CidrBlock=cidr, VpcId=vpc_id) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, "Unable to associate CIDR {0}.".format(ipv6_cidr)) if ipv6_cidr: if 'Ipv6CidrBlockAssociationSet' in vpc_obj.keys(): module.warn("Only one IPv6 CIDR is permitted per VPC, {0} already has CIDR {1}".format( vpc_id, vpc_obj['Ipv6CidrBlockAssociationSet'][0]['Ipv6CidrBlock'])) else: try: connection.associate_vpc_cidr_block(AmazonProvidedIpv6CidrBlock=ipv6_cidr, VpcId=vpc_id) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, "Unable to associate CIDR {0}.".format(ipv6_cidr)) if purge_cidrs: for association_id in to_remove: changed = True try: connection.disassociate_vpc_cidr_block(AssociationId=association_id) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, "Unable to disassociate {0}. You must detach or delete all gateways and resources that " "are associated with the CIDR block before you can disassociate it.".format(association_id)) if dhcp_id is not None: try: if update_dhcp_opts(connection, module, vpc_obj, dhcp_id): changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, "Failed to update DHCP options") if tags is not None or name is not None: try: if update_vpc_tags(connection, module, vpc_id, tags, name): changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to update tags") current_dns_enabled = connection.describe_vpc_attribute(Attribute='enableDnsSupport', VpcId=vpc_id, aws_retry=True)['EnableDnsSupport']['Value'] current_dns_hostnames = connection.describe_vpc_attribute(Attribute='enableDnsHostnames', VpcId=vpc_id, aws_retry=True)['EnableDnsHostnames']['Value'] if current_dns_enabled != dns_support: changed = True if not module.check_mode: try: connection.modify_vpc_attribute(VpcId=vpc_id, EnableDnsSupport={'Value': dns_support}) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, "Failed to update enabled dns support attribute") if current_dns_hostnames != dns_hostnames: changed = True if not module.check_mode: try: connection.modify_vpc_attribute(VpcId=vpc_id, EnableDnsHostnames={'Value': dns_hostnames}) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, "Failed to update enabled dns hostnames attribute") # wait for associated cidrs to match if to_add or to_remove: try: connection.get_waiter('vpc_available').wait( VpcIds=[vpc_id], Filters=[{'Name': 'cidr-block-association.cidr-block', 'Values': expected_cidrs}] ) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, "Failed to wait for CIDRs to update") # try to wait for enableDnsSupport and enableDnsHostnames to match wait_for_vpc_attribute(connection, module, vpc_id, 'enableDnsSupport', dns_support) wait_for_vpc_attribute(connection, module, vpc_id, 'enableDnsHostnames', dns_hostnames) final_state = camel_dict_to_snake_dict(get_vpc(module, connection, vpc_id)) final_state['tags'] = boto3_tag_list_to_ansible_dict(final_state.get('tags', [])) final_state['id'] = final_state.pop('vpc_id') module.exit_json(changed=changed, vpc=final_state) elif state == 'absent': # Check if VPC exists vpc_id = vpc_exists(module, connection, name, cidr_block, multi) if vpc_id is not None: try: if not module.check_mode: connection.delete_vpc(VpcId=vpc_id) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to delete VPC {0} You may want to use the ec2_vpc_subnet, ec2_vpc_igw, " "and/or ec2_vpc_route_table modules to ensure the other components are absent.".format(vpc_id)) module.exit_json(changed=changed, vpc={})
def main(): argument_spec = dict( name=dict(), group_id=dict(), description=dict(), vpc_id=dict(), rules=dict(type='list'), rules_egress=dict(type='list'), state=dict(default='present', type='str', choices=['present', 'absent']), purge_rules=dict(default=True, required=False, type='bool'), purge_rules_egress=dict(default=True, required=False, type='bool'), tags=dict(required=False, type='dict', aliases=['resource_tags']), purge_tags=dict(default=True, required=False, type='bool') ) module = AnsibleAWSModule( argument_spec=argument_spec, supports_check_mode=True, required_one_of=[['name', 'group_id']], required_if=[['state', 'present', ['name']]], ) name = module.params['name'] group_id = module.params['group_id'] description = module.params['description'] vpc_id = module.params['vpc_id'] rules = flatten_nested_targets(module, deepcopy(module.params['rules'])) rules_egress = flatten_nested_targets(module, deepcopy(module.params['rules_egress'])) rules = deduplicate_rules_args(rules_expand_sources(rules_expand_ports(rules))) rules_egress = deduplicate_rules_args(rules_expand_sources(rules_expand_ports(rules_egress))) state = module.params.get('state') purge_rules = module.params['purge_rules'] purge_rules_egress = module.params['purge_rules_egress'] tags = module.params['tags'] purge_tags = module.params['purge_tags'] if state == 'present' and not description: module.fail_json(msg='Must provide description when state is present.') changed = False client = module.client('ec2') verify_rules_with_descriptions_permitted(client, module, rules, rules_egress) group, groups = group_exists(client, module, vpc_id, group_id, name) group_created_new = not bool(group) global current_account_id current_account_id = get_aws_account_id(module) before = {} after = {} # Ensure requested group is absent if state == 'absent': if group: # found a match, delete it before = camel_dict_to_snake_dict(group, ignore_list=['Tags']) before['tags'] = boto3_tag_list_to_ansible_dict(before.get('tags', [])) try: if not module.check_mode: client.delete_security_group(GroupId=group['GroupId']) except (BotoCoreError, ClientError) as e: module.fail_json_aws(e, msg="Unable to delete security group '%s'" % group) else: group = None changed = True else: # no match found, no changes required pass # Ensure requested group is present elif state == 'present': if group: # existing group before = camel_dict_to_snake_dict(group, ignore_list=['Tags']) before['tags'] = boto3_tag_list_to_ansible_dict(before.get('tags', [])) if group['Description'] != description: module.warn("Group description does not match existing group. Descriptions cannot be changed without deleting " "and re-creating the security group. Try using state=absent to delete, then rerunning this task.") else: # no match found, create it group = create_security_group(client, module, name, description, vpc_id) changed = True if tags is not None and group is not None: current_tags = boto3_tag_list_to_ansible_dict(group.get('Tags', [])) changed |= update_tags(client, module, group['GroupId'], current_tags, tags, purge_tags) if group: named_tuple_ingress_list = [] named_tuple_egress_list = [] current_ingress = sum([list(rule_from_group_permission(p)) for p in group['IpPermissions']], []) current_egress = sum([list(rule_from_group_permission(p)) for p in group['IpPermissionsEgress']], []) for new_rules, rule_type, named_tuple_rule_list in [(rules, 'in', named_tuple_ingress_list), (rules_egress, 'out', named_tuple_egress_list)]: if new_rules is None: continue for rule in new_rules: target_type, target, target_group_created = get_target_from_rule( module, client, rule, name, group, groups, vpc_id) changed |= target_group_created if rule.get('proto', 'tcp') in ('all', '-1', -1): rule['proto'] = '-1' rule['from_port'] = None rule['to_port'] = None try: int(rule.get('proto', 'tcp')) rule['proto'] = to_text(rule.get('proto', 'tcp')) rule['from_port'] = None rule['to_port'] = None except ValueError: # rule does not use numeric protocol spec pass named_tuple_rule_list.append( Rule( port_range=(rule['from_port'], rule['to_port']), protocol=to_text(rule.get('proto', 'tcp')), target=target, target_type=target_type, description=rule.get('rule_desc'), ) ) # List comprehensions for rules to add, rules to modify, and rule ids to determine purging new_ingress_permissions = [to_permission(r) for r in (set(named_tuple_ingress_list) - set(current_ingress))] new_egress_permissions = [to_permission(r) for r in (set(named_tuple_egress_list) - set(current_egress))] if module.params.get('rules_egress') is None and 'VpcId' in group: # when no egress rules are specified and we're in a VPC, # we add in a default allow all out rule, which was the # default behavior before egress rules were added rule = Rule((None, None), '-1', '0.0.0.0/0', 'ipv4', None) if rule in current_egress: named_tuple_egress_list.append(rule) if rule not in current_egress: current_egress.append(rule) # List comprehensions for rules to add, rules to modify, and rule ids to determine purging present_ingress = list(set(named_tuple_ingress_list).union(set(current_ingress))) present_egress = list(set(named_tuple_egress_list).union(set(current_egress))) if purge_rules: revoke_ingress = [] for p in present_ingress: if not any([rule_cmp(p, b) for b in named_tuple_ingress_list]): revoke_ingress.append(to_permission(p)) else: revoke_ingress = [] if purge_rules_egress and module.params.get('rules_egress') is not None: if module.params.get('rules_egress') is []: revoke_egress = [ to_permission(r) for r in set(present_egress) - set(named_tuple_egress_list) if r != Rule((None, None), '-1', '0.0.0.0/0', 'ipv4', None) ] else: revoke_egress = [] for p in present_egress: if not any([rule_cmp(p, b) for b in named_tuple_egress_list]): revoke_egress.append(to_permission(p)) else: revoke_egress = [] # named_tuple_ingress_list and named_tuple_egress_list got updated by # method update_rule_descriptions, deep copy these two lists to new # variables for the record of the 'desired' ingress and egress sg permissions desired_ingress = deepcopy(named_tuple_ingress_list) desired_egress = deepcopy(named_tuple_egress_list) changed |= update_rule_descriptions(module, group['GroupId'], present_ingress, named_tuple_ingress_list, present_egress, named_tuple_egress_list) # Revoke old rules changed |= remove_old_permissions(client, module, revoke_ingress, revoke_egress, group['GroupId']) rule_msg = 'Revoking {0}, and egress {1}'.format(revoke_ingress, revoke_egress) new_ingress_permissions = [to_permission(r) for r in (set(named_tuple_ingress_list) - set(current_ingress))] new_ingress_permissions = rules_to_permissions(set(named_tuple_ingress_list) - set(current_ingress)) new_egress_permissions = rules_to_permissions(set(named_tuple_egress_list) - set(current_egress)) # Authorize new rules changed |= add_new_permissions(client, module, new_ingress_permissions, new_egress_permissions, group['GroupId']) if group_created_new and module.params.get('rules') is None and module.params.get('rules_egress') is None: # A new group with no rules provided is already being awaited. # When it is created we wait for the default egress rule to be added by AWS security_group = get_security_groups_with_backoff(client, GroupIds=[group['GroupId']])['SecurityGroups'][0] elif changed and not module.check_mode: # keep pulling until current security group rules match the desired ingress and egress rules security_group = wait_for_rule_propagation(module, group, desired_ingress, desired_egress, purge_rules, purge_rules_egress) else: security_group = get_security_groups_with_backoff(client, GroupIds=[group['GroupId']])['SecurityGroups'][0] security_group = camel_dict_to_snake_dict(security_group, ignore_list=['Tags']) security_group['tags'] = boto3_tag_list_to_ansible_dict(security_group.get('tags', [])) else: security_group = {'group_id': None} if module._diff: if module.params['state'] == 'present': after = get_diff_final_resource(client, module, security_group) security_group['diff'] = [{'before': before, 'after': after}] module.exit_json(changed=changed, **security_group)