Ensure the DynamoDB table exists. Table throughput can be updated after
    table creation.

    Global secondary indexes (GSIs) are managed with some exceptions:

    - If a GSI deletion is detected, a failure will occur (deletes should be
      done manually in the AWS console).

    - If multiple GSIs are added in a single Salt call, a failure will occur
      (boto supports one creation at a time). Note that this only applies after
      table creation; multiple GSIs can be created during table creation.

    - Updates to existing GSIs are limited to read/write capacity only
      (DynamoDB limitation).

    name
        Name of the DynamoDB table

    table_name
        Name of the DynamoDB table (deprecated)

    region
        Region to connect to.

    key
        Secret key to be used.

    keyid
        Access key to be used.

    profile
        A dict with region, key and keyid, or a pillar key (string)
        that contains a dict with region, key and keyid.

    read_capacity_units
        The read throughput for this table

    write_capacity_units
        The write throughput for this table

    hash_key
        The name of the attribute that will be used as the hash key
        for this table

    hash_key_data_type
        The DynamoDB datatype of the hash key

    range_key
        The name of the attribute that will be used as the range key
        for this table

    range_key_data_type
        The DynamoDB datatype of the range key

    local_indexes
        The local indexes you would like to create

    global_indexes
        The global indexes you would like to create

    backup_configs_from_pillars
        Pillars to use to configure DataPipeline backups

def present(name=None,
            table_name=None,
            region=None,
            key=None,
            keyid=None,
            profile=None,
            read_capacity_units=None,
            write_capacity_units=None,
            alarms=None,
            alarms_from_pillar="boto_dynamodb_alarms",
            hash_key=None,
            hash_key_data_type=None,
            range_key=None,
            range_key_data_type=None,
            local_indexes=None,
            global_indexes=None,
            backup_configs_from_pillars='boto_dynamodb_backup_configs'):
    '''
    Ensure the DynamoDB table exists. Table throughput can be updated after
    table creation.

    Global secondary indexes (GSIs) are managed with some exceptions:

    - If a GSI deletion is detected, a failure will occur (deletes should be
      done manually in the AWS console).

    - If multiple GSIs are added in a single Salt call, a failure will occur
      (boto supports one creation at a time). Note that this only applies after
      table creation; multiple GSIs can be created during table creation.

    - Updates to existing GSIs are limited to read/write capacity only
      (DynamoDB limitation).

    name
        Name of the DynamoDB table

    table_name
        Name of the DynamoDB table (deprecated)

    region
        Region to connect to.

    key
        Secret key to be used.

    keyid
        Access key to be used.

    profile
        A dict with region, key and keyid, or a pillar key (string)
        that contains a dict with region, key and keyid.

    read_capacity_units
        The read throughput for this table

    write_capacity_units
        The write throughput for this table

    hash_key
        The name of the attribute that will be used as the hash key
        for this table

    hash_key_data_type
        The DynamoDB datatype of the hash key

    range_key
        The name of the attribute that will be used as the range key
        for this table

    range_key_data_type
        The DynamoDB datatype of the range key

    local_indexes
        The local indexes you would like to create

    global_indexes
        The global indexes you would like to create

    backup_configs_from_pillars
        Pillars to use to configure DataPipeline backups
    '''
    ret = {'name': name, 'result': True, 'comment': '', 'changes': {}}
    if table_name:
        ret['warnings'] = ['boto_dynamodb.present: `table_name` is deprecated.'
                           ' Please use `name` instead.']
        ret['name'] = table_name
        name = table_name

    comments = []
    changes_old = {}
    changes_new = {}

    # Ensure DynamoDB table exists
    table_exists = __salt__['boto_dynamodb.exists'](
        name,
        region,
        key,
        keyid,
        profile
    )
    if not table_exists:
        if __opts__['test']:
            ret['result'] = None
            ret['comment'] = 'DynamoDB table {0} would be created.'.format(name)
            return ret
        else:
            is_created = __salt__['boto_dynamodb.create_table'](
                name,
                region,
                key,
                keyid,
                profile,
                read_capacity_units,
                write_capacity_units,
                hash_key,
                hash_key_data_type,
                range_key,
                range_key_data_type,
                local_indexes,
                global_indexes
            )
            if not is_created:
                ret['result'] = False
                ret['comment'] = 'Failed to create table {0}'.format(name)
                _add_changes(ret, changes_old, changes_new)
                return ret

            comments.append('DynamoDB table {0} was successfully created'.format(name))
            changes_new['table'] = name
            changes_new['read_capacity_units'] = read_capacity_units
            changes_new['write_capacity_units'] = write_capacity_units
            changes_new['hash_key'] = hash_key
            changes_new['hash_key_data_type'] = hash_key_data_type
            changes_new['range_key'] = range_key
            changes_new['range_key_data_type'] = range_key_data_type
            changes_new['local_indexes'] = local_indexes
            changes_new['global_indexes'] = global_indexes
    else:
        comments.append('DynamoDB table {0} exists'.format(name))

    # Ensure DynamoDB table provisioned throughput matches
    description = __salt__['boto_dynamodb.describe'](
        name,
        region,
        key,
        keyid,
        profile
    )
    provisioned_throughput = description.get('Table', {}).get('ProvisionedThroughput', {})
    current_write_capacity_units = provisioned_throughput.get('WriteCapacityUnits')
    current_read_capacity_units = provisioned_throughput.get('ReadCapacityUnits')
    throughput_matches = (current_write_capacity_units == write_capacity_units and
                          current_read_capacity_units == read_capacity_units)
    if not throughput_matches:
        if __opts__['test']:
            ret['result'] = None
            comments.append('DynamoDB table {0} is set to be updated.'.format(name))
        else:
            is_updated = __salt__['boto_dynamodb.update'](
                name,
                throughput={
                    'read': read_capacity_units,
                    'write': write_capacity_units,
                },
                region=region,
                key=key,
                keyid=keyid,
                profile=profile,
            )
            if not is_updated:
                ret['result'] = False
                ret['comment'] = 'Failed to update table {0}'.format(name)
                _add_changes(ret, changes_old, changes_new)
                return ret

            comments.append('DynamoDB table {0} was successfully updated'.format(name))
            changes_old['read_capacity_units'] = current_read_capacity_units,
            changes_old['write_capacity_units'] = current_write_capacity_units,
            changes_new['read_capacity_units'] = read_capacity_units,
            changes_new['write_capacity_units'] = write_capacity_units,
    else:
        comments.append('DynamoDB table {0} throughput matches'.format(name))

    provisioned_indexes = description.get('Table', {}).get('GlobalSecondaryIndexes', [])

    _ret = _global_indexes_present(provisioned_indexes, global_indexes, changes_old,
                                   changes_new, comments, name, region, key, keyid,
                                   profile)
    if not _ret['result']:
        comments.append(_ret['comment'])
        ret['result'] = _ret['result']
        if ret['result'] is False:
            ret['comment'] = ',\n'.join(comments)
            _add_changes(ret, changes_old, changes_new)
            return ret

    _ret = _alarms_present(name, alarms, alarms_from_pillar,
                           write_capacity_units, read_capacity_units,
                           region, key, keyid, profile)
    ret['changes'] = dictupdate.update(ret['changes'], _ret['changes'])
    comments.append(_ret['comment'])
    if not _ret['result']:
        ret['result'] = _ret['result']
        if ret['result'] is False:
            ret['comment'] = ',\n'.join(comments)
            _add_changes(ret, changes_old, changes_new)
            return ret

    # Ensure backup datapipeline is present
    datapipeline_configs = copy.deepcopy(
        __salt__['pillar.get'](backup_configs_from_pillars, [])
    )
    for config in datapipeline_configs:
        datapipeline_ret = _ensure_backup_datapipeline_present(
            name=name,
            schedule_name=config['name'],
            period=config['period'],
            utc_hour=config['utc_hour'],
            s3_base_location=config['s3_base_location'],
        )
        # Add comments and changes if successful changes were made (True for live mode,
        # None for test mode).
        if datapipeline_ret['result'] in [True, None]:
            ret['result'] = datapipeline_ret['result']
            comments.append(datapipeline_ret['comment'])
            if datapipeline_ret.get('changes'):
                ret['changes']['backup_datapipeline_{0}'.format(config['name'])] = \
                    datapipeline_ret.get('changes'),
        else:
            ret['comment'] = ',\n'.join([ret['comment'], datapipeline_ret['comment']])
            _add_changes(ret, changes_old, changes_new)
            return ret

    ret['comment'] = ',\n'.join(comments)
    _add_changes(ret, changes_old, changes_new)
    return ret