コード例 #1
0
def delete_group(module=None, iam=None, name=None):
    changed = False
    try:
        iam.delete_group(name)
    except boto.exception.BotoServerError as err:
        error_msg = boto_exception(err)
        if ('must delete policies first') in error_msg:
            for policy in iam.get_all_group_policies(
                    name).list_group_policies_result.policy_names:
                iam.delete_group_policy(name, policy)
            try:
                iam.delete_group(name)
            except boto.exception.BotoServerError as err:
                error_msg = boto_exception(err)
                if ('must delete policies first') in error_msg:
                    module.fail_json(
                        changed=changed,
                        msg=
                        "All inline policies have been removed. Though it appears"
                        "that %s has Managed Polices. This is not "
                        "currently supported by boto. Please detach the policies "
                        "through the console and try again." % name)
                else:
                    module.fail_json(changed=changed, msg=str(error_msg))
            else:
                changed = True
        else:
            module.fail_json(changed=changed, msg=str(error_msg))
    else:
        changed = True
    return changed, name
コード例 #2
0
def delete_role(module, iam, name, role_list, prof_list):
    changed = False
    iam_role_result = None
    instance_profile_result = None
    try:
        if name in role_list:
            cur_ins_prof = [
                rp['instance_profile_name']
                for rp in iam.list_instance_profiles_for_role(name).
                list_instance_profiles_for_role_result.instance_profiles
            ]
            for profile in cur_ins_prof:
                iam.remove_role_from_instance_profile(profile, name)
            try:
                iam.delete_role(name)
            except boto.exception.BotoServerError as err:
                error_msg = boto_exception(err)
                if ('must detach all policies first') in error_msg:
                    for policy in iam.list_role_policies(
                            name).list_role_policies_result.policy_names:
                        iam.delete_role_policy(name, policy)
                try:
                    iam_role_result = iam.delete_role(name)
                except boto.exception.BotoServerError as err:
                    error_msg = boto_exception(err)
                    if ('must detach all policies first') in error_msg:
                        module.fail_json(
                            changed=changed,
                            msg=
                            "All inline policies have been removed. Though it appears"
                            "that %s has Managed Polices. This is not "
                            "currently supported by boto. Please detach the policies "
                            "through the console and try again." % name)
                    else:
                        module.fail_json(changed=changed, msg=str(err))
                else:
                    changed = True

            else:
                changed = True

        for prof in prof_list:
            if name == prof:
                instance_profile_result = iam.delete_instance_profile(name)
    except boto.exception.BotoServerError as err:
        module.fail_json(changed=changed, msg=str(err))
    else:
        updated_role_list = list_all_roles(iam)
    return changed, updated_role_list, iam_role_result, instance_profile_result
コード例 #3
0
def get_stack_events(cfn, stack_name, events_limit, token_filter=None):
    '''This event data was never correct, it worked as a side effect. So the v2.3 format is different.'''
    ret = {'events': [], 'log': []}

    try:
        pg = cfn.get_paginator('describe_stack_events').paginate(
            StackName=stack_name, PaginationConfig={'MaxItems': events_limit})
        if token_filter is not None:
            events = list(
                pg.search("StackEvents[?ClientRequestToken == '{0}']".format(
                    token_filter)))
        else:
            events = list(pg.search("StackEvents[*]"))
    except (botocore.exceptions.ValidationError,
            botocore.exceptions.ClientError) as err:
        error_msg = boto_exception(err)
        if 'does not exist' in error_msg:
            # missing stack, don't bail.
            ret['log'].append('Stack does not exist.')
            return ret
        ret['log'].append('Unknown error: ' + str(error_msg))
        return ret

    for e in events:
        eventline = 'StackEvent {ResourceType} {LogicalResourceId} {ResourceStatus}'.format(
            **e)
        ret['events'].append(eventline)

        if e['ResourceStatus'].endswith('FAILED'):
            failline = '{ResourceType} {LogicalResourceId} {ResourceStatus}: {ResourceStatusReason}'.format(
                **e)
            ret['log'].append(failline)

    return ret
コード例 #4
0
def main():
    argument_spec = ec2_argument_spec()
    argument_spec.update(dict(
        mode=dict(choices=['push'], default='push'),
        file_change_strategy=dict(choices=['force', 'date_size', 'checksum'], default='date_size'),
        bucket=dict(required=True),
        key_prefix=dict(required=False, default=''),
        file_root=dict(required=True, type='path'),
        permission=dict(required=False, choices=['private', 'public-read', 'public-read-write', 'authenticated-read',
                                                 'aws-exec-read', 'bucket-owner-read', 'bucket-owner-full-control']),
        retries=dict(required=False, removed_in_version='2.14'),
        mime_map=dict(required=False, type='dict'),
        exclude=dict(required=False, default=".*"),
        include=dict(required=False, default="*"),
        cache_control=dict(required=False, default=''),
        delete=dict(required=False, type='bool', default=False),
        # future options: encoding, metadata, storage_class, retries
    )
    )

    module = AnsibleModule(
        argument_spec=argument_spec,
    )

    if not HAS_DATEUTIL:
        module.fail_json(msg='dateutil required for this module')

    if not HAS_BOTO3:
        module.fail_json(msg='boto3 required for this module')

    result = {}
    mode = module.params['mode']

    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module, boto3=True)
    if not region:
        module.fail_json(msg="Region must be specified")
    s3 = boto3_conn(module, conn_type='client', resource='s3', region=region, endpoint=ec2_url, **aws_connect_kwargs)

    if mode == 'push':
        try:
            result['filelist_initial'] = gather_files(module.params['file_root'], exclude=module.params['exclude'], include=module.params['include'])
            result['filelist_typed'] = determine_mimetypes(result['filelist_initial'], module.params.get('mime_map'))
            result['filelist_s3'] = calculate_s3_path(result['filelist_typed'], module.params['key_prefix'])
            result['filelist_local_etag'] = calculate_local_etag(result['filelist_s3'])
            result['filelist_actionable'] = filter_list(s3, module.params['bucket'], result['filelist_local_etag'], module.params['file_change_strategy'])
            result['uploads'] = upload_files(s3, module.params['bucket'], result['filelist_actionable'], module.params)

            if module.params['delete']:
                result['removed'] = remove_files(s3, result['filelist_local_etag'], module.params)

            # mark changed if we actually upload something.
            if result.get('uploads') or result.get('removed'):
                result['changed'] = True
            # result.update(filelist=actionable_filelist)
        except botocore.exceptions.ClientError as err:
            error_msg = boto_exception(err)
            module.fail_json(msg=error_msg, exception=traceback.format_exc(), **camel_dict_to_snake_dict(err.response))

    module.exit_json(**result)
コード例 #5
0
def create_stack(module, stack_params, cfn, events_limit):
    if 'TemplateBody' not in stack_params and 'TemplateURL' not in stack_params:
        module.fail_json(
            msg=
            "Either 'template', 'template_body' or 'template_url' is required when the stack does not exist."
        )

    # 'DisableRollback', 'TimeoutInMinutes', 'EnableTerminationProtection' and
    # 'OnFailure' only apply on creation, not update.
    #
    # 'OnFailure' and 'DisableRollback' are incompatible with each other, so
    # throw error if both are defined
    if module.params.get('on_create_failure') is None:
        stack_params['DisableRollback'] = module.params['disable_rollback']
    else:
        if module.params['disable_rollback']:
            module.fail_json(
                msg=
                "You can specify either 'on_create_failure' or 'disable_rollback', but not both."
            )
        stack_params['OnFailure'] = module.params['on_create_failure']
    if module.params.get('create_timeout') is not None:
        stack_params['TimeoutInMinutes'] = module.params['create_timeout']
    if module.params.get('termination_protection') is not None:
        if boto_supports_termination_protection(cfn):
            stack_params['EnableTerminationProtection'] = bool(
                module.params.get('termination_protection'))
        else:
            module.fail_json(
                msg=
                "termination_protection parameter requires botocore >= 1.7.18")

    try:
        response = cfn.create_stack(**stack_params)
        # Use stack ID to follow stack state in case of on_create_failure = DELETE
        result = stack_operation(cfn, response['StackId'], 'CREATE',
                                 events_limit,
                                 stack_params.get('ClientRequestToken', None))
    except Exception as err:
        error_msg = boto_exception(err)
        module.fail_json(msg="Failed to create stack {0}: {1}.".format(
            stack_params.get('StackName'), error_msg),
                         exception=traceback.format_exc())
    if not result:
        module.fail_json(msg="empty result")
    return result
コード例 #6
0
def update_termination_protection(module, cfn, stack_name,
                                  desired_termination_protection_state):
    '''updates termination protection of a stack'''
    if not boto_supports_termination_protection(cfn):
        module.fail_json(
            msg="termination_protection parameter requires botocore >= 1.7.18")
    stack = get_stack_facts(cfn, stack_name)
    if stack:
        if stack[
                'EnableTerminationProtection'] is not desired_termination_protection_state:
            try:
                cfn.update_termination_protection(
                    EnableTerminationProtection=
                    desired_termination_protection_state,
                    StackName=stack_name)
            except botocore.exceptions.ClientError as e:
                module.fail_json(msg=boto_exception(e),
                                 exception=traceback.format_exc())
コード例 #7
0
def set_users_groups(module, iam, name, groups, updated=None, new_name=None):
    """ Sets groups for a user, will purge groups not explicitly passed, while
        retaining pre-existing groups that also are in the new list.
    """
    changed = False

    if updated:
        name = new_name

    try:
        orig_users_groups = [
            og['group_name'] for og in iam.get_groups_for_user(
                name).list_groups_for_user_result.groups
        ]
        remove_groups = [
            rg for rg in frozenset(orig_users_groups).difference(groups)
        ]
        new_groups = [
            ng for ng in frozenset(groups).difference(orig_users_groups)
        ]
    except boto.exception.BotoServerError as err:
        module.fail_json(changed=changed, msg=str(err))
    else:
        if len(orig_users_groups) > 0:
            for new in new_groups:
                iam.add_user_to_group(new, name)
            for rm in remove_groups:
                iam.remove_user_from_group(rm, name)
        else:
            for group in groups:
                try:
                    iam.add_user_to_group(group, name)
                except boto.exception.BotoServerError as err:
                    error_msg = boto_exception(err)
                    if ('The group with name %s cannot be found.' %
                            group) in error_msg:
                        module.fail_json(changed=False,
                                         msg="Group %s doesn't exist" % group)

    if len(remove_groups) > 0 or len(new_groups) > 0:
        changed = True

    return (groups, changed)
コード例 #8
0
def get_stack_facts(cfn, stack_name):
    try:
        stack_response = cfn.describe_stacks(StackName=stack_name)
        stack_info = stack_response['Stacks'][0]
    except (botocore.exceptions.ValidationError,
            botocore.exceptions.ClientError) as err:
        error_msg = boto_exception(err)
        if 'does not exist' in error_msg:
            # missing stack, don't bail.
            return None

        # other error, bail.
        raise err

    if stack_response and stack_response.get('Stacks', None):
        stacks = stack_response['Stacks']
        if len(stacks):
            stack_info = stacks[0]

    return stack_info
コード例 #9
0
def update_stack(module, stack_params, cfn, events_limit):
    if 'TemplateBody' not in stack_params and 'TemplateURL' not in stack_params:
        stack_params['UsePreviousTemplate'] = True

    # if the state is present and the stack already exists, we try to update it.
    # AWS will tell us if the stack template and parameters are the same and
    # don't need to be updated.
    try:
        cfn.update_stack(**stack_params)
        result = stack_operation(cfn, stack_params['StackName'], 'UPDATE',
                                 events_limit,
                                 stack_params.get('ClientRequestToken', None))
    except Exception as err:
        error_msg = boto_exception(err)
        if 'No updates are to be performed.' in error_msg:
            result = dict(changed=False, output='Stack is already up-to-date.')
        else:
            module.fail_json(msg="Failed to update stack {0}: {1}".format(
                stack_params.get('StackName'), error_msg),
                             exception=traceback.format_exc())
    if not result:
        module.fail_json(msg="empty result")
    return result
コード例 #10
0
def check_mode_changeset(module, stack_params, cfn):
    """Create a change set, describe it and delete it before returning check mode outputs."""
    stack_params['ChangeSetName'] = build_changeset_name(stack_params)
    # changesets don't accept ClientRequestToken parameters
    stack_params.pop('ClientRequestToken', None)

    try:
        change_set = cfn.create_change_set(**stack_params)
        for i in range(60):  # total time 5 min
            description = cfn.describe_change_set(
                ChangeSetName=change_set['Id'])
            if description['Status'] in ('CREATE_COMPLETE', 'FAILED'):
                break
            time.sleep(5)
        else:
            # if the changeset doesn't finish in 5 mins, this `else` will trigger and fail
            module.fail_json(msg="Failed to create change set %s" %
                             stack_params['ChangeSetName'])

        cfn.delete_change_set(ChangeSetName=change_set['Id'])

        reason = description.get('StatusReason')

        if description[
                'Status'] == 'FAILED' and "didn't contain changes" in description[
                    'StatusReason']:
            return {
                'changed': False,
                'msg': reason,
                'meta': description['StatusReason']
            }
        return {'changed': True, 'msg': reason, 'meta': description['Changes']}

    except (botocore.exceptions.ValidationError,
            botocore.exceptions.ClientError) as err:
        error_msg = boto_exception(err)
        module.fail_json(msg=error_msg, exception=traceback.format_exc())
コード例 #11
0
def update_user(module, iam, name, new_name, new_path, key_state, key_count,
                keys, pwd, updated):
    changed = False
    name_change = False
    if updated and new_name:
        name = new_name
    try:
        current_keys = [
            ck['access_key_id'] for ck in iam.get_all_access_keys(
                name).list_access_keys_result.access_key_metadata
        ]
        status = [
            ck['status'] for ck in iam.get_all_access_keys(
                name).list_access_keys_result.access_key_metadata
        ]
        key_qty = len(current_keys)
    except boto.exception.BotoServerError as err:
        error_msg = boto_exception(err)
        if 'cannot be found' in error_msg and updated:
            current_keys = [
                ck['access_key_id'] for ck in iam.get_all_access_keys(
                    new_name).list_access_keys_result.access_key_metadata
            ]
            status = [
                ck['status'] for ck in iam.get_all_access_keys(
                    new_name).list_access_keys_result.access_key_metadata
            ]
            name = new_name
        else:
            module.fail_json(changed=False, msg=str(err))

    updated_key_list = {}

    if new_name or new_path:
        c_path = iam.get_user(name).get_user_result.user['path']
        if (name != new_name) or (c_path != new_path):
            changed = True
            try:
                if not updated:
                    user = iam.update_user(
                        name, new_user_name=new_name, new_path=new_path
                    ).update_user_response.response_metadata
                else:
                    user = iam.update_user(
                        name, new_path=new_path
                    ).update_user_response.response_metadata
                user['updates'] = dict(old_username=name,
                                       new_username=new_name,
                                       old_path=c_path,
                                       new_path=new_path)
            except boto.exception.BotoServerError as err:
                error_msg = boto_exception(err)
                module.fail_json(changed=False, msg=str(err))
            else:
                if not updated:
                    name_change = True

    if pwd:
        try:
            iam.update_login_profile(name, pwd)
            changed = True
        except boto.exception.BotoServerError:
            try:
                iam.create_login_profile(name, pwd)
                changed = True
            except boto.exception.BotoServerError as err:
                error_msg = boto_exception(str(err))
                if 'Password does not conform to the account password policy' in error_msg:
                    module.fail_json(changed=False,
                                     msg="Password doesn't conform to policy")
                else:
                    module.fail_json(msg=error_msg)

    try:
        current_keys = [
            ck['access_key_id'] for ck in iam.get_all_access_keys(
                name).list_access_keys_result.access_key_metadata
        ]
        status = [
            ck['status'] for ck in iam.get_all_access_keys(
                name).list_access_keys_result.access_key_metadata
        ]
        key_qty = len(current_keys)
    except boto.exception.BotoServerError as err:
        error_msg = boto_exception(err)
        if 'cannot be found' in error_msg and updated:
            current_keys = [
                ck['access_key_id'] for ck in iam.get_all_access_keys(
                    new_name).list_access_keys_result.access_key_metadata
            ]
            status = [
                ck['status'] for ck in iam.get_all_access_keys(
                    new_name).list_access_keys_result.access_key_metadata
            ]
            name = new_name
        else:
            module.fail_json(changed=False, msg=str(err))

    new_keys = []
    if key_state == 'create':
        try:
            while key_count > key_qty:
                new_keys.append(
                    iam.create_access_key(
                        user_name=name).create_access_key_response.
                    create_access_key_result.access_key)
                key_qty += 1
                changed = True

        except boto.exception.BotoServerError as err:
            module.fail_json(changed=False, msg=str(err))

    if keys and key_state:
        for access_key in keys:
            if key_state in ('active', 'inactive'):
                if access_key in current_keys:
                    for current_key, current_key_state in zip(
                            current_keys, status):
                        if key_state != current_key_state.lower():
                            try:
                                iam.update_access_key(access_key,
                                                      key_state.capitalize(),
                                                      user_name=name)
                                changed = True
                            except boto.exception.BotoServerError as err:
                                module.fail_json(changed=False, msg=str(err))
                else:
                    module.fail_json(msg="Supplied keys not found for %s. "
                                     "Current keys: %s. "
                                     "Supplied key(s): %s" %
                                     (name, current_keys, keys))

            if key_state == 'remove':
                if access_key in current_keys:
                    try:
                        iam.delete_access_key(access_key, user_name=name)
                    except boto.exception.BotoServerError as err:
                        module.fail_json(changed=False, msg=str(err))
                    else:
                        changed = True

    try:
        final_keys, final_key_status = \
            [ck['access_key_id'] for ck in
             iam.get_all_access_keys(name).
             list_access_keys_result.
             access_key_metadata],\
            [ck['status'] for ck in
                iam.get_all_access_keys(name).
                list_access_keys_result.
                access_key_metadata]
    except boto.exception.BotoServerError as err:
        module.fail_json(changed=changed, msg=str(err))

    for fk, fks in zip(final_keys, final_key_status):
        updated_key_list.update({fk: fks})

    return name_change, updated_key_list, changed, new_keys
コード例 #12
0
def delete_dependencies_first(module, iam, name):
    changed = False
    # try to delete any keys
    try:
        current_keys = [
            ck['access_key_id'] for ck in iam.get_all_access_keys(
                name).list_access_keys_result.access_key_metadata
        ]
        for key in current_keys:
            iam.delete_access_key(key, name)
        changed = True
    except boto.exception.BotoServerError as err:
        module.fail_json(changed=changed,
                         msg="Failed to delete keys: %s" % err,
                         exception=traceback.format_exc())

    # try to delete login profiles
    try:
        login_profile = iam.get_login_profiles(name).get_login_profile_response
        iam.delete_login_profile(name)
        changed = True
    except boto.exception.BotoServerError as err:
        error_msg = boto_exception(err)
        if 'Login Profile for User ' + name + ' cannot be found.' not in error_msg:
            module.fail_json(changed=changed,
                             msg="Failed to delete login profile: %s" % err,
                             exception=traceback.format_exc())

    # try to detach policies
    try:
        for policy in iam.get_all_user_policies(
                name).list_user_policies_result.policy_names:
            iam.delete_user_policy(name, policy)
        changed = True
    except boto.exception.BotoServerError as err:
        error_msg = boto_exception(err)
        if 'must detach all policies first' in error_msg:
            module.fail_json(
                changed=changed,
                msg="All inline policies have been removed. Though it appears"
                "that %s has Managed Polices. This is not "
                "currently supported by boto. Please detach the policies "
                "through the console and try again." % name)
        module.fail_json(changed=changed,
                         msg="Failed to delete policies: %s" % err,
                         exception=traceback.format_exc())

    # try to deactivate associated MFA devices
    try:
        mfa_devices = iam.get_all_mfa_devices(name).get(
            'list_mfa_devices_response', {}).get('list_mfa_devices_result',
                                                 {}).get('mfa_devices', [])
        for device in mfa_devices:
            iam.deactivate_mfa_device(name, device['serial_number'])
        changed = True
    except boto.exception.BotoServerError as err:
        module.fail_json(
            changed=changed,
            msg="Failed to deactivate associated MFA devices: %s" % err,
            exception=traceback.format_exc())

    return changed
コード例 #13
0
def main():
    argument_spec = ec2_argument_spec()
    argument_spec.update(
        dict(stack_name=dict(required=True),
             template_parameters=dict(required=False, type='dict', default={}),
             state=dict(default='present', choices=['present', 'absent']),
             template=dict(default=None, required=False, type='path'),
             notification_arns=dict(default=None, required=False),
             stack_policy=dict(default=None, required=False),
             disable_rollback=dict(default=False, type='bool'),
             on_create_failure=dict(
                 default=None,
                 required=False,
                 choices=['DO_NOTHING', 'ROLLBACK', 'DELETE']),
             create_timeout=dict(default=None, type='int'),
             template_url=dict(default=None, required=False),
             template_body=dict(default=None, required=False),
             template_format=dict(removed_in_version='2.14'),
             create_changeset=dict(default=False, type='bool'),
             changeset_name=dict(default=None, required=False),
             role_arn=dict(default=None, required=False),
             tags=dict(default=None, type='dict'),
             termination_protection=dict(default=None, type='bool'),
             events_limit=dict(default=200, type='int'),
             backoff_retries=dict(type='int', default=10, required=False),
             backoff_delay=dict(type='int', default=3, required=False),
             backoff_max_delay=dict(type='int', default=30, required=False),
             capabilities=dict(
                 type='list',
                 default=['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'])))

    module = AnsibleModule(
        argument_spec=argument_spec,
        mutually_exclusive=[['template_url', 'template', 'template_body']],
        supports_check_mode=True)
    if not HAS_BOTO3:
        module.fail_json(msg='boto3 and botocore are required for this module')

    invalid_capabilities = []
    user_capabilities = module.params.get('capabilities')
    for user_cap in user_capabilities:
        if user_cap not in [
                'CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM',
                'CAPABILITY_AUTO_EXPAND'
        ]:
            invalid_capabilities.append(user_cap)

    if invalid_capabilities:
        module.fail_json(msg="Specified capabilities are invalid : %r,"
                         " please check documentation for valid capabilities" %
                         invalid_capabilities)

    # collect the parameters that are passed to boto3. Keeps us from having so many scalars floating around.
    stack_params = {
        'Capabilities': user_capabilities,
        'ClientRequestToken': to_native(uuid.uuid4()),
    }
    state = module.params['state']
    stack_params['StackName'] = module.params['stack_name']

    if module.params['template'] is not None:
        with open(module.params['template'], 'r') as template_fh:
            stack_params['TemplateBody'] = template_fh.read()
    elif module.params['template_body'] is not None:
        stack_params['TemplateBody'] = module.params['template_body']
    elif module.params['template_url'] is not None:
        stack_params['TemplateURL'] = module.params['template_url']

    if module.params.get('notification_arns'):
        stack_params['NotificationARNs'] = module.params[
            'notification_arns'].split(',')
    else:
        stack_params['NotificationARNs'] = []

    # can't check the policy when verifying.
    if module.params[
            'stack_policy'] is not None and not module.check_mode and not module.params[
                'create_changeset']:
        with open(module.params['stack_policy'], 'r') as stack_policy_fh:
            stack_params['StackPolicyBody'] = stack_policy_fh.read()

    template_parameters = module.params['template_parameters']

    stack_params['Parameters'] = []
    for k, v in template_parameters.items():
        if isinstance(v, dict):
            # set parameter based on a dict to allow additional CFN Parameter Attributes
            param = dict(ParameterKey=k)

            if 'value' in v:
                param['ParameterValue'] = str(v['value'])

            if 'use_previous_value' in v and bool(v['use_previous_value']):
                param['UsePreviousValue'] = True
                param.pop('ParameterValue', None)

            stack_params['Parameters'].append(param)
        else:
            # allow default k/v configuration to set a template parameter
            stack_params['Parameters'].append({
                'ParameterKey': k,
                'ParameterValue': str(v)
            })

    if isinstance(module.params.get('tags'), dict):
        stack_params['Tags'] = ansible_dict_to_boto3_tag_list(
            module.params['tags'])

    if module.params.get('role_arn'):
        stack_params['RoleARN'] = module.params['role_arn']

    result = {}

    try:
        region, ec2_url, aws_connect_kwargs = get_aws_connection_info(
            module, boto3=True)
        cfn = boto3_conn(module,
                         conn_type='client',
                         resource='cloudformation',
                         region=region,
                         endpoint=ec2_url,
                         **aws_connect_kwargs)
    except botocore.exceptions.NoCredentialsError as e:
        module.fail_json(msg=boto_exception(e))

    # Wrap the cloudformation client methods that this module uses with
    # automatic backoff / retry for throttling error codes
    backoff_wrapper = AWSRetry.jittered_backoff(
        retries=module.params.get('backoff_retries'),
        delay=module.params.get('backoff_delay'),
        max_delay=module.params.get('backoff_max_delay'))
    cfn.describe_stack_events = backoff_wrapper(cfn.describe_stack_events)
    cfn.create_stack = backoff_wrapper(cfn.create_stack)
    cfn.list_change_sets = backoff_wrapper(cfn.list_change_sets)
    cfn.create_change_set = backoff_wrapper(cfn.create_change_set)
    cfn.update_stack = backoff_wrapper(cfn.update_stack)
    cfn.describe_stacks = backoff_wrapper(cfn.describe_stacks)
    cfn.list_stack_resources = backoff_wrapper(cfn.list_stack_resources)
    cfn.delete_stack = backoff_wrapper(cfn.delete_stack)
    if boto_supports_termination_protection(cfn):
        cfn.update_termination_protection = backoff_wrapper(
            cfn.update_termination_protection)

    stack_info = get_stack_facts(cfn, stack_params['StackName'])

    if module.check_mode:
        if state == 'absent' and stack_info:
            module.exit_json(changed=True,
                             msg='Stack would be deleted',
                             meta=[])
        elif state == 'absent' and not stack_info:
            module.exit_json(changed=False,
                             msg='Stack doesn\'t exist',
                             meta=[])
        elif state == 'present' and not stack_info:
            module.exit_json(changed=True,
                             msg='New stack would be created',
                             meta=[])
        else:
            module.exit_json(**check_mode_changeset(module, stack_params, cfn))

    if state == 'present':
        if not stack_info:
            result = create_stack(module, stack_params, cfn,
                                  module.params.get('events_limit'))
        elif module.params.get('create_changeset'):
            result = create_changeset(module, stack_params, cfn,
                                      module.params.get('events_limit'))
        else:
            if module.params.get('termination_protection') is not None:
                update_termination_protection(
                    module, cfn, stack_params['StackName'],
                    bool(module.params.get('termination_protection')))
            result = update_stack(module, stack_params, cfn,
                                  module.params.get('events_limit'))

        # format the stack output

        stack = get_stack_facts(cfn, stack_params['StackName'])
        if stack is not None:
            if result.get('stack_outputs') is None:
                # always define stack_outputs, but it may be empty
                result['stack_outputs'] = {}
            for output in stack.get('Outputs', []):
                result['stack_outputs'][
                    output['OutputKey']] = output['OutputValue']
            stack_resources = []
            reslist = cfn.list_stack_resources(
                StackName=stack_params['StackName'])
            for res in reslist.get('StackResourceSummaries', []):
                stack_resources.append({
                    "logical_resource_id":
                    res['LogicalResourceId'],
                    "physical_resource_id":
                    res.get('PhysicalResourceId', ''),
                    "resource_type":
                    res['ResourceType'],
                    "last_updated_time":
                    res['LastUpdatedTimestamp'],
                    "status":
                    res['ResourceStatus'],
                    "status_reason":
                    res.get('ResourceStatusReason')  # can be blank, apparently
                })
            result['stack_resources'] = stack_resources

    elif state == 'absent':
        # absent state is different because of the way delete_stack works.
        # problem is it it doesn't give an error if stack isn't found
        # so must describe the stack first

        try:
            stack = get_stack_facts(cfn, stack_params['StackName'])
            if not stack:
                result = {'changed': False, 'output': 'Stack not found.'}
            else:
                if stack_params.get('RoleARN') is None:
                    cfn.delete_stack(StackName=stack_params['StackName'])
                else:
                    cfn.delete_stack(StackName=stack_params['StackName'],
                                     RoleARN=stack_params['RoleARN'])
                result = stack_operation(
                    cfn, stack_params['StackName'], 'DELETE',
                    module.params.get('events_limit'),
                    stack_params.get('ClientRequestToken', None))
        except Exception as err:
            module.fail_json(msg=boto_exception(err),
                             exception=traceback.format_exc())

    module.exit_json(**result)
コード例 #14
0
def create_changeset(module, stack_params, cfn, events_limit):
    if 'TemplateBody' not in stack_params and 'TemplateURL' not in stack_params:
        module.fail_json(
            msg="Either 'template' or 'template_url' is required.")
    if module.params['changeset_name'] is not None:
        stack_params['ChangeSetName'] = module.params['changeset_name']

    # changesets don't accept ClientRequestToken parameters
    stack_params.pop('ClientRequestToken', None)

    try:
        changeset_name = build_changeset_name(stack_params)
        stack_params['ChangeSetName'] = changeset_name

        # Determine if this changeset already exists
        pending_changesets = list_changesets(cfn, stack_params['StackName'])
        if changeset_name in pending_changesets:
            warning = 'WARNING: %d pending changeset(s) exist(s) for this stack!' % len(
                pending_changesets)
            result = dict(changed=False,
                          output='ChangeSet %s already exists.' %
                          changeset_name,
                          warnings=[warning])
        else:
            cs = cfn.create_change_set(**stack_params)
            # Make sure we don't enter an infinite loop
            time_end = time.time() + 600
            while time.time() < time_end:
                try:
                    newcs = cfn.describe_change_set(ChangeSetName=cs['Id'])
                except botocore.exceptions.BotoCoreError as err:
                    error_msg = boto_exception(err)
                    module.fail_json(msg=error_msg)
                if newcs['Status'] == 'CREATE_PENDING' or newcs[
                        'Status'] == 'CREATE_IN_PROGRESS':
                    time.sleep(1)
                elif newcs[
                        'Status'] == 'FAILED' and "The submitted information didn't contain changes" in newcs[
                            'StatusReason']:
                    cfn.delete_change_set(ChangeSetName=cs['Id'])
                    result = dict(
                        changed=False,
                        output=
                        'The created Change Set did not contain any changes to this stack and was deleted.'
                    )
                    # a failed change set does not trigger any stack events so we just want to
                    # skip any further processing of result and just return it directly
                    return result
                else:
                    break
                # Lets not hog the cpu/spam the AWS API
                time.sleep(1)
            result = stack_operation(cfn, stack_params['StackName'],
                                     'CREATE_CHANGESET', events_limit)
            result['change_set_id'] = cs['Id']
            result['warnings'] = [
                'Created changeset named %s for stack %s' %
                (changeset_name, stack_params['StackName']),
                'You can execute it using: aws cloudformation execute-change-set --change-set-name %s'
                % cs['Id'],
                'NOTE that dependencies on this stack might fail due to pending changes!'
            ]
    except Exception as err:
        error_msg = boto_exception(err)
        if 'No updates are to be performed.' in error_msg:
            result = dict(changed=False, output='Stack is already up-to-date.')
        else:
            module.fail_json(
                msg="Failed to create change set: {0}".format(error_msg),
                exception=traceback.format_exc())

    if not result:
        module.fail_json(msg="empty result")
    return result
コード例 #15
0
def run(ecr, params):
    # type: (EcsEcr, dict, int) -> Tuple[bool, dict]
    result = {}
    try:
        name = params['name']
        state = params['state']
        policy_text = params['policy']
        purge_policy = params['purge_policy']
        registry_id = params['registry_id']
        force_set_policy = params['force_set_policy']
        image_tag_mutability = params['image_tag_mutability'].upper()
        lifecycle_policy_text = params['lifecycle_policy']
        purge_lifecycle_policy = params['purge_lifecycle_policy']

        # Parse policies, if they are given
        try:
            policy = policy_text and json.loads(policy_text)
        except ValueError:
            result['policy'] = policy_text
            result['msg'] = 'Could not parse policy'
            return False, result

        try:
            lifecycle_policy = \
                lifecycle_policy_text and json.loads(lifecycle_policy_text)
        except ValueError:
            result['lifecycle_policy'] = lifecycle_policy_text
            result['msg'] = 'Could not parse lifecycle_policy'
            return False, result

        result['state'] = state
        result['created'] = False

        repo = ecr.get_repository(registry_id, name)

        if state == 'present':
            result['created'] = False

            if not repo:
                repo = ecr.create_repository(registry_id, name,
                                             image_tag_mutability)
                result['changed'] = True
                result['created'] = True
            else:
                repo = ecr.put_image_tag_mutability(registry_id, name,
                                                    image_tag_mutability)
            result['repository'] = repo

            if purge_lifecycle_policy:
                original_lifecycle_policy = \
                    ecr.get_lifecycle_policy(registry_id, name)

                result['lifecycle_policy'] = None

                if original_lifecycle_policy:
                    ecr.purge_lifecycle_policy(registry_id, name)
                    result['changed'] = True

            elif lifecycle_policy_text is not None:
                try:
                    lifecycle_policy = sort_json_policy_dict(lifecycle_policy)
                    result['lifecycle_policy'] = lifecycle_policy

                    original_lifecycle_policy = ecr.get_lifecycle_policy(
                        registry_id, name)

                    if original_lifecycle_policy:
                        original_lifecycle_policy = sort_json_policy_dict(
                            original_lifecycle_policy)

                    if original_lifecycle_policy != lifecycle_policy:
                        ecr.put_lifecycle_policy(registry_id, name,
                                                 lifecycle_policy_text)
                        result['changed'] = True
                except Exception:
                    # Some failure w/ the policy. It's helpful to know what the
                    # policy is.
                    result['lifecycle_policy'] = lifecycle_policy_text
                    raise

            if purge_policy:
                original_policy = ecr.get_repository_policy(registry_id, name)

                result['policy'] = None

                if original_policy:
                    ecr.delete_repository_policy(registry_id, name)
                    result['changed'] = True

            elif policy_text is not None:
                try:
                    # Sort any lists containing only string types
                    policy = sort_lists_of_strings(policy)

                    result['policy'] = policy

                    original_policy = ecr.get_repository_policy(
                        registry_id, name)
                    if original_policy:
                        original_policy = sort_lists_of_strings(
                            original_policy)

                    if compare_policies(original_policy, policy):
                        ecr.set_repository_policy(registry_id, name,
                                                  policy_text,
                                                  force_set_policy)
                        result['changed'] = True
                except Exception:
                    # Some failure w/ the policy. It's helpful to know what the
                    # policy is.
                    result['policy'] = policy_text
                    raise

        elif state == 'absent':
            result['name'] = name
            if repo:
                ecr.delete_repository(registry_id, name)
                result['changed'] = True

    except Exception as err:
        msg = str(err)
        if isinstance(err, ClientError):
            msg = boto_exception(err)
        result['msg'] = msg
        result['exception'] = traceback.format_exc()
        return False, result

    if ecr.skipped:
        result['skipped'] = True

    if ecr.changed:
        result['changed'] = True

    return True, result