Ensure bucket exists.

    name
        The name of the state definition

    Bucket
        Name of the bucket.

    LocationConstraint
        'EU'|'eu-west-1'|'us-west-1'|'us-west-2'|'ap-southeast-1'|'ap-southeast-2'|'ap-northeast-1'|'sa-east-1'|'cn-north-1'|'eu-central-1'

    ACL
        The permissions on a bucket using access control lists (ACL).

    CORSRules
        The cors configuration for a bucket.

    LifecycleConfiguration
        Lifecycle configuration for your bucket

    Logging
        The logging parameters for a bucket and to specify permissions for who
        can view and modify the logging parameters.

    NotificationConfiguration
        notifications of specified events for a bucket

    Policy
        Policy on the bucket.  As a special case, if the Policy is set to the
        string `external`, it will not be managed by this state, and can thus
        be safely set in other ways (e.g. by other state calls, or by hand if
        some unusual policy configuration is required).

    Replication
        Replication rules. You can add as many as 1,000 rules.
        Total replication configuration size can be up to 2 MB

    RequestPayment
        The request payment configuration for a bucket. By default, the bucket
        owner pays for downloads from the bucket. This configuration parameter
        enables the bucket owner (only) to specify that the person requesting
        the download will be charged for the download

    Tagging
        A dictionary of tags that should be set on the bucket

    Versioning
        The versioning state of the bucket

    Website
        The website configuration of the bucket

    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.

def present(name, Bucket,
            LocationConstraint=None,
            ACL=None,
            CORSRules=None,
            LifecycleConfiguration=None,
            Logging=None,
            NotificationConfiguration=None,
            Policy=None,
            Replication=None,
            RequestPayment=None,
            Tagging=None,
            Versioning=None,
            Website=None,
            region=None, key=None, keyid=None, profile=None):
    '''
    Ensure bucket exists.

    name
        The name of the state definition

    Bucket
        Name of the bucket.

    LocationConstraint
        'EU'|'eu-west-1'|'us-west-1'|'us-west-2'|'ap-southeast-1'|'ap-southeast-2'|'ap-northeast-1'|'sa-east-1'|'cn-north-1'|'eu-central-1'

    ACL
        The permissions on a bucket using access control lists (ACL).

    CORSRules
        The cors configuration for a bucket.

    LifecycleConfiguration
        Lifecycle configuration for your bucket

    Logging
        The logging parameters for a bucket and to specify permissions for who
        can view and modify the logging parameters.

    NotificationConfiguration
        notifications of specified events for a bucket

    Policy
        Policy on the bucket.  As a special case, if the Policy is set to the
        string `external`, it will not be managed by this state, and can thus
        be safely set in other ways (e.g. by other state calls, or by hand if
        some unusual policy configuration is required).

    Replication
        Replication rules. You can add as many as 1,000 rules.
        Total replication configuration size can be up to 2 MB

    RequestPayment
        The request payment configuration for a bucket. By default, the bucket
        owner pays for downloads from the bucket. This configuration parameter
        enables the bucket owner (only) to specify that the person requesting
        the download will be charged for the download

    Tagging
        A dictionary of tags that should be set on the bucket

    Versioning
        The versioning state of the bucket

    Website
        The website configuration of the bucket

    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.
    '''
    ret = {'name': Bucket,
           'result': True,
           'comment': '',
           'changes': {}
           }

    if ACL is None:
        ACL = {'ACL': 'private'}
    if NotificationConfiguration is None:
        NotificationConfiguration = {}
    if RequestPayment is None:
        RequestPayment = {'Payer': 'BucketOwner'}
    if Policy:
        if isinstance(Policy, six.string_types) and Policy != 'external':
            Policy = salt.utils.json.loads(Policy)
        Policy = __utils__['boto3.ordered'](Policy)

    r = __salt__['boto_s3_bucket.exists'](Bucket=Bucket,
           region=region, key=key, keyid=keyid, profile=profile)

    if 'error' in r:
        ret['result'] = False
        ret['comment'] = 'Failed to create bucket: {0}.'.format(r['error']['message'])
        return ret

    if not r.get('exists'):
        if __opts__['test']:
            ret['comment'] = 'S3 bucket {0} is set to be created.'.format(Bucket)
            ret['result'] = None
            return ret
        r = __salt__['boto_s3_bucket.create'](Bucket=Bucket,
                   LocationConstraint=LocationConstraint,
                   region=region, key=key, keyid=keyid, profile=profile)
        if not r.get('created'):
            ret['result'] = False
            ret['comment'] = 'Failed to create bucket: {0}.'.format(r['error']['message'])
            return ret

        for setter, testval, funcargs in (
                ('put_acl', ACL, ACL),
                ('put_cors', CORSRules, {"CORSRules": CORSRules}),
                ('put_lifecycle_configuration', LifecycleConfiguration, {"Rules": LifecycleConfiguration}),
                ('put_logging', Logging, Logging),
                ('put_notification_configuration', NotificationConfiguration, NotificationConfiguration),
                ('put_policy', Policy, {"Policy": Policy}),
                # versioning must be set before replication
                ('put_versioning', Versioning, Versioning),
                ('put_replication', Replication, Replication),
                ('put_request_payment', RequestPayment, RequestPayment),
                ('put_tagging', Tagging, Tagging),
                ('put_website', Website, Website),
        ):
            if testval is not None:
                r = __salt__['boto_s3_bucket.{0}'.format(setter)](Bucket=Bucket,
                                   region=region, key=key, keyid=keyid, profile=profile,
                                   **funcargs)
                if not r.get('updated'):
                    ret['result'] = False
                    ret['comment'] = 'Failed to create bucket: {0}.'.format(r['error']['message'])
                    return ret

        _describe = __salt__['boto_s3_bucket.describe'](Bucket,
                                   region=region, key=key, keyid=keyid, profile=profile)
        ret['changes']['old'] = {'bucket': None}
        ret['changes']['new'] = _describe
        ret['comment'] = 'S3 bucket {0} created.'.format(Bucket)

        return ret

    # bucket exists, ensure config matches
    ret['comment'] = ' '.join([ret['comment'], 'S3 bucket {0} is present.'.format(Bucket)])
    ret['changes'] = {}
    _describe = __salt__['boto_s3_bucket.describe'](Bucket=Bucket,
                                 region=region, key=key, keyid=keyid, profile=profile)
    if 'error' in _describe:
        ret['result'] = False
        ret['comment'] = 'Failed to update bucket: {0}.'.format(_describe['error']['message'])
        ret['changes'] = {}
        return ret
    _describe = _describe['bucket']

    # Once versioning has been enabled, it can't completely go away, it can
    # only be suspended
    if not bool(Versioning) and bool(_describe.get('Versioning')):
        Versioning = {'Status': 'Suspended'}

    config_items = [
            ('ACL', 'put_acl',
                    _describe.get('ACL'), _compare_acl, ACL,
                    None),
            ('CORS', 'put_cors',
                    _describe.get('CORS'), _compare_json, {"CORSRules": CORSRules} if CORSRules else None,
                   'delete_cors'),
            ('LifecycleConfiguration', 'put_lifecycle_configuration',
                    _describe.get('LifecycleConfiguration'), _compare_json, {"Rules": LifecycleConfiguration} if LifecycleConfiguration else None,
                    'delete_lifecycle_configuration'),
            ('Logging', 'put_logging',
                    _describe.get('Logging', {}).get('LoggingEnabled'), _compare_json, Logging,
                    None),
            ('NotificationConfiguration', 'put_notification_configuration',
                    _describe.get('NotificationConfiguration'), _compare_json, NotificationConfiguration,
                    None),
            ('Policy', 'put_policy',
                    _describe.get('Policy'), _compare_policy, {"Policy": Policy} if Policy else None,
                    'delete_policy'),
            ('RequestPayment', 'put_request_payment',
                    _describe.get('RequestPayment'), _compare_json, RequestPayment,
                    None),
            ('Tagging', 'put_tagging',
                    _describe.get('Tagging'), _compare_json, Tagging,
                    'delete_tagging'),
            ('Website', 'put_website',
                    _describe.get('Website'), _compare_json, Website,
                    'delete_website'),
    ]
    versioning_item = ('Versioning', 'put_versioning',
                    _describe.get('Versioning'), _compare_json, Versioning or {},
                    None)
    # Substitute full ARN into desired state for comparison
    replication_item = ('Replication', 'put_replication',
                    _describe.get('Replication', {}).get('ReplicationConfiguration'), _compare_replication, Replication,
                    'delete_replication')

    # versioning must be turned on before replication can be on, thus replication
    # must be turned off before versioning can be off
    if Replication is not None:
        # replication will be on, must deal with versioning first
        config_items.append(versioning_item)
        config_items.append(replication_item)
    else:
        # replication will be off, deal with it first
        config_items.append(replication_item)
        config_items.append(versioning_item)

    update = False
    changes = {}
    for varname, setter, current, comparator, desired, deleter in config_items:
        if varname == 'Policy':
            if desired == {'Policy': 'external'}:
                # Short-circuit to allow external policy control.
                log.debug('S3 Policy set to `external`, skipping application.')
                continue
            if current is not None:
                temp = current.get('Policy')
                # Policy description is always returned as a JSON string.
                # Convert it to JSON now for ease of comparisons later.
                if isinstance(temp, six.string_types):
                    current = __utils__['boto3.ordered'](
                        {'Policy': salt.utils.json.loads(temp)}
                    )
        if not comparator(current, desired, region, key, keyid, profile):
            update = True
            if varname == 'ACL':
                changes.setdefault('new', {})[varname] = _acl_to_grant(
                        desired, _get_canonical_id(region, key, keyid, profile))
            else:
                changes.setdefault('new', {})[varname] = desired
            changes.setdefault('old', {})[varname] = current

            if not __opts__['test']:
                if deleter and desired is None:
                    # Setting can be deleted, so use that to unset it
                    r = __salt__['boto_s3_bucket.{0}'.format(deleter)](Bucket=Bucket,
                      region=region, key=key, keyid=keyid, profile=profile)
                    if not r.get('deleted'):
                        ret['result'] = False
                        ret['comment'] = 'Failed to update bucket: {0}.'.format(r['error']['message'])
                        return ret
                else:
                    r = __salt__['boto_s3_bucket.{0}'.format(setter)](Bucket=Bucket,
                      region=region, key=key, keyid=keyid, profile=profile,
                      **(desired or {}))
                    if not r.get('updated'):
                        ret['result'] = False
                        ret['comment'] = 'Failed to update bucket: {0}.'.format(r['error']['message'])
                        return ret
    if update and __opts__['test']:
        msg = 'S3 bucket {0} set to be modified.'.format(Bucket)
        ret['comment'] = msg
        ret['result'] = None
        ret['pchanges'] = changes
        return ret

    ret['changes'] = changes
    # Since location can't be changed, try that last so at least the rest of
    # the things are correct by the time we fail here. Fail so the user will
    # notice something mismatches their desired state.
    if _describe.get('Location', {}).get('LocationConstraint') != LocationConstraint:
        msg = 'Bucket {0} location does not match desired configuration, but cannot be changed'.format(LocationConstraint)
        log.warning(msg)
        ret['result'] = False
        ret['comment'] = 'Failed to update bucket: {0}.'.format(msg)
        return ret

    return ret