예제 #1
0
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)
예제 #2
0
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={})
예제 #4
0
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)