~0x44/nova/bug838466

« back to all changes in this revision

Viewing changes to vendor/boto/boto/s3/bucket.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
 
2
#
 
3
# Permission is hereby granted, free of charge, to any person obtaining a
 
4
# copy of this software and associated documentation files (the
 
5
# "Software"), to deal in the Software without restriction, including
 
6
# without limitation the rights to use, copy, modify, merge, publish, dis-
 
7
# tribute, sublicense, and/or sell copies of the Software, and to permit
 
8
# persons to whom the Software is furnished to do so, subject to the fol-
 
9
# lowing conditions:
 
10
#
 
11
# The above copyright notice and this permission notice shall be included
 
12
# in all copies or substantial portions of the Software.
 
13
#
 
14
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 
15
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
 
16
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
 
17
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 
18
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
19
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 
20
# IN THE SOFTWARE.
 
21
 
 
22
import boto
 
23
from boto import handler
 
24
from boto.resultset import ResultSet
 
25
from boto.s3.acl import Policy, CannedACLStrings, Grant
 
26
from boto.s3.key import Key
 
27
from boto.s3.prefix import Prefix
 
28
from boto.s3.deletemarker import DeleteMarker
 
29
from boto.exception import S3ResponseError, S3PermissionsError, S3CopyError
 
30
from boto.s3.bucketlistresultset import BucketListResultSet
 
31
from boto.s3.bucketlistresultset import VersionedBucketListResultSet
 
32
import boto.utils
 
33
import xml.sax
 
34
import urllib
 
35
import re
 
36
 
 
37
S3Permissions = ['READ', 'WRITE', 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL']
 
38
 
 
39
class Bucket:
 
40
 
 
41
    BucketLoggingBody = """<?xml version="1.0" encoding="UTF-8"?>
 
42
       <BucketLoggingStatus xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
 
43
         <LoggingEnabled>
 
44
           <TargetBucket>%s</TargetBucket>
 
45
           <TargetPrefix>%s</TargetPrefix>
 
46
         </LoggingEnabled>
 
47
       </BucketLoggingStatus>"""
 
48
    
 
49
    EmptyBucketLoggingBody = """<?xml version="1.0" encoding="UTF-8"?>
 
50
       <BucketLoggingStatus xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
 
51
       </BucketLoggingStatus>"""
 
52
 
 
53
    LoggingGroup = 'http://acs.amazonaws.com/groups/s3/LogDelivery'
 
54
 
 
55
    BucketPaymentBody = """<?xml version="1.0" encoding="UTF-8"?>
 
56
       <RequestPaymentConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
 
57
         <Payer>%s</Payer>
 
58
       </RequestPaymentConfiguration>"""
 
59
 
 
60
    VersioningBody = """<?xml version="1.0" encoding="UTF-8"?>
 
61
       <VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
 
62
         <Status>%s</Status>
 
63
         <MfaDelete>%s</MfaDelete>
 
64
       </VersioningConfiguration>"""
 
65
 
 
66
    VersionRE = '<Status>([A-Za-z]+)</Status>'
 
67
    MFADeleteRE = '<MfaDelete>([A-Za-z]+)</MfaDelete>'
 
68
 
 
69
    def __init__(self, connection=None, name=None, key_class=Key):
 
70
        self.name = name
 
71
        self.connection = connection
 
72
        self.key_class = key_class
 
73
 
 
74
    def __repr__(self):
 
75
        return '<Bucket: %s>' % self.name
 
76
 
 
77
    def __iter__(self):
 
78
        return iter(BucketListResultSet(self))
 
79
 
 
80
    def __contains__(self, key_name):
 
81
       return not (self.get_key(key_name) is None)
 
82
 
 
83
    def startElement(self, name, attrs, connection):
 
84
        return None
 
85
 
 
86
    def endElement(self, name, value, connection):
 
87
        if name == 'Name':
 
88
            self.name = value
 
89
        elif name == 'CreationDate':
 
90
            self.creation_date = value
 
91
        else:
 
92
            setattr(self, name, value)
 
93
 
 
94
    def set_key_class(self, key_class):
 
95
        """
 
96
        Set the Key class associated with this bucket.  By default, this
 
97
        would be the boto.s3.key.Key class but if you want to subclass that
 
98
        for some reason this allows you to associate your new class with a
 
99
        bucket so that when you call bucket.new_key() or when you get a listing
 
100
        of keys in the bucket you will get an instances of your key class
 
101
        rather than the default.
 
102
        
 
103
        :type key_class: class
 
104
        :param key_class: A subclass of Key that can be more specific
 
105
        """
 
106
        self.key_class = key_class
 
107
 
 
108
    def lookup(self, key_name, headers=None):
 
109
        """
 
110
        Deprecated: Please use get_key method.
 
111
        
 
112
        :type key_name: string
 
113
        :param key_name: The name of the key to retrieve
 
114
        
 
115
        :rtype: :class:`boto.s3.key.Key`
 
116
        :returns: A Key object from this bucket.
 
117
        """
 
118
        return self.get_key(key_name, headers=headers)
 
119
        
 
120
    def get_key(self, key_name, headers=None, version_id=None):
 
121
        """
 
122
        Check to see if a particular key exists within the bucket.  This
 
123
        method uses a HEAD request to check for the existance of the key.
 
124
        Returns: An instance of a Key object or None
 
125
        
 
126
        :type key_name: string
 
127
        :param key_name: The name of the key to retrieve
 
128
        
 
129
        :rtype: :class:`boto.s3.key.Key`
 
130
        :returns: A Key object from this bucket.
 
131
        """
 
132
        if version_id:
 
133
            query_args = 'versionId=%s' % version_id
 
134
        else:
 
135
            query_args = None
 
136
        response = self.connection.make_request('HEAD', self.name, key_name,
 
137
                                                headers=headers,
 
138
                                                query_args=query_args)
 
139
        if response.status == 200:
 
140
            response.read()
 
141
            k = self.key_class(self)
 
142
            k.metadata = boto.utils.get_aws_metadata(response.msg)
 
143
            k.etag = response.getheader('etag')
 
144
            k.content_type = response.getheader('content-type')
 
145
            k.content_encoding = response.getheader('content-encoding')
 
146
            k.last_modified = response.getheader('last-modified')
 
147
            k.size = int(response.getheader('content-length'))
 
148
            k.name = key_name
 
149
            k.handle_version_headers(response)
 
150
            return k
 
151
        else:
 
152
            if response.status == 404:
 
153
                response.read()
 
154
                return None
 
155
            else:
 
156
                raise S3ResponseError(response.status, response.reason, '')
 
157
 
 
158
    def list(self, prefix='', delimiter='', marker='', headers=None):
 
159
        """
 
160
        List key objects within a bucket.  This returns an instance of an
 
161
        BucketListResultSet that automatically handles all of the result
 
162
        paging, etc. from S3.  You just need to keep iterating until
 
163
        there are no more results.
 
164
        Called with no arguments, this will return an iterator object across
 
165
        all keys within the bucket.
 
166
        
 
167
        :type prefix: string
 
168
        :param prefix: allows you to limit the listing to a particular
 
169
                        prefix.  For example, if you call the method with
 
170
                        prefix='/foo/' then the iterator will only cycle
 
171
                        through the keys that begin with the string '/foo/'.
 
172
                        
 
173
        :type delimiter: string
 
174
        :param delimiter: can be used in conjunction with the prefix
 
175
                        to allow you to organize and browse your keys
 
176
                        hierarchically. See:
 
177
                        http://docs.amazonwebservices.com/AmazonS3/2006-03-01/
 
178
                        for more details.
 
179
                        
 
180
        :type marker: string
 
181
        :param marker: The "marker" of where you are in the result set
 
182
        
 
183
        :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet`
 
184
        :return: an instance of a BucketListResultSet that handles paging, etc
 
185
        """
 
186
        return BucketListResultSet(self, prefix, delimiter, marker, headers)
 
187
 
 
188
    def list_versions(self, prefix='', delimiter='', key_marker='',
 
189
                      version_id_marker='', headers=None):
 
190
        """
 
191
        List key objects within a bucket.  This returns an instance of an
 
192
        BucketListResultSet that automatically handles all of the result
 
193
        paging, etc. from S3.  You just need to keep iterating until
 
194
        there are no more results.
 
195
        Called with no arguments, this will return an iterator object across
 
196
        all keys within the bucket.
 
197
        
 
198
        :type prefix: string
 
199
        :param prefix: allows you to limit the listing to a particular
 
200
                        prefix.  For example, if you call the method with
 
201
                        prefix='/foo/' then the iterator will only cycle
 
202
                        through the keys that begin with the string '/foo/'.
 
203
                        
 
204
        :type delimiter: string
 
205
        :param delimiter: can be used in conjunction with the prefix
 
206
                        to allow you to organize and browse your keys
 
207
                        hierarchically. See:
 
208
                        http://docs.amazonwebservices.com/AmazonS3/2006-03-01/
 
209
                        for more details.
 
210
                        
 
211
        :type marker: string
 
212
        :param marker: The "marker" of where you are in the result set
 
213
        
 
214
        :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet`
 
215
        :return: an instance of a BucketListResultSet that handles paging, etc
 
216
        """
 
217
        return VersionedBucketListResultSet(self, prefix, delimiter, key_marker,
 
218
                                            version_id_marker, headers)
 
219
 
 
220
    def _get_all(self, element_map, initial_query_string='',
 
221
                 headers=None, **params):
 
222
        l = []
 
223
        for k,v in params.items():
 
224
            k = k.replace('_', '-')
 
225
            if  k == 'maxkeys':
 
226
                k = 'max-keys'
 
227
            if isinstance(v, unicode):
 
228
                v = v.encode('utf-8')
 
229
            if v is not None and v != '':
 
230
                l.append('%s=%s' % (urllib.quote(k), urllib.quote(str(v))))
 
231
        if len(l):
 
232
            s = initial_query_string + '&' + '&'.join(l)
 
233
        else:
 
234
            s = initial_query_string
 
235
        response = self.connection.make_request('GET', self.name,
 
236
                headers=headers, query_args=s)
 
237
        body = response.read()
 
238
        boto.log.debug(body)
 
239
        if response.status == 200:
 
240
            rs = ResultSet(element_map)
 
241
            h = handler.XmlHandler(rs, self)
 
242
            xml.sax.parseString(body, h)
 
243
            return rs
 
244
        else:
 
245
            raise S3ResponseError(response.status, response.reason, body)
 
246
 
 
247
    def get_all_keys(self, headers=None, **params):
 
248
        """
 
249
        A lower-level method for listing contents of a bucket.
 
250
        This closely models the actual S3 API and requires you to manually
 
251
        handle the paging of results.  For a higher-level method
 
252
        that handles the details of paging for you, you can use the list method.
 
253
        
 
254
        :type max_keys: int
 
255
        :param max_keys: The maximum number of keys to retrieve
 
256
        
 
257
        :type prefix: string
 
258
        :param prefix: The prefix of the keys you want to retrieve
 
259
        
 
260
        :type marker: string
 
261
        :param marker: The "marker" of where you are in the result set
 
262
        
 
263
        :type delimiter: string 
 
264
        :param delimiter: If this optional, Unicode string parameter
 
265
                          is included with your request, then keys that
 
266
                          contain the same string between the prefix and
 
267
                          the first occurrence of the delimiter will be
 
268
                          rolled up into a single result element in the
 
269
                          CommonPrefixes collection. These rolled-up keys
 
270
                          are not returned elsewhere in the response.
 
271
 
 
272
        :rtype: ResultSet
 
273
        :return: The result from S3 listing the keys requested
 
274
        
 
275
        """
 
276
        return self._get_all([('Contents', self.key_class),
 
277
                              ('CommonPrefixes', Prefix)],
 
278
                             '', headers, **params)
 
279
 
 
280
    def get_all_versions(self, headers=None, **params):
 
281
        """
 
282
        A lower-level, version-aware method for listing contents of a bucket.
 
283
        This closely models the actual S3 API and requires you to manually
 
284
        handle the paging of results.  For a higher-level method
 
285
        that handles the details of paging for you, you can use the list method.
 
286
        
 
287
        :type max_keys: int
 
288
        :param max_keys: The maximum number of keys to retrieve
 
289
        
 
290
        :type prefix: string
 
291
        :param prefix: The prefix of the keys you want to retrieve
 
292
        
 
293
        :type key_marker: string
 
294
        :param key_marker: The "marker" of where you are in the result set
 
295
                           with respect to keys.
 
296
        
 
297
        :type version_id_marker: string
 
298
        :param version_id_marker: The "marker" of where you are in the result
 
299
                                  set with respect to version-id's.
 
300
        
 
301
        :type delimiter: string 
 
302
        :param delimiter: If this optional, Unicode string parameter
 
303
                          is included with your request, then keys that
 
304
                          contain the same string between the prefix and
 
305
                          the first occurrence of the delimiter will be
 
306
                          rolled up into a single result element in the
 
307
                          CommonPrefixes collection. These rolled-up keys
 
308
                          are not returned elsewhere in the response.
 
309
 
 
310
        :rtype: ResultSet
 
311
        :return: The result from S3 listing the keys requested
 
312
        
 
313
        """
 
314
        return self._get_all([('Version', self.key_class),
 
315
                              ('CommonPrefixes', Prefix),
 
316
                              ('DeleteMarker', DeleteMarker)],
 
317
                             'versions', headers, **params)
 
318
 
 
319
    def new_key(self, key_name=None):
 
320
        """
 
321
        Creates a new key
 
322
        
 
323
        :type key_name: string
 
324
        :param key_name: The name of the key to create
 
325
        
 
326
        :rtype: :class:`boto.s3.key.Key` or subclass
 
327
        :returns: An instance of the newly created key object
 
328
        """
 
329
        return self.key_class(self, key_name)
 
330
 
 
331
    def generate_url(self, expires_in, method='GET',
 
332
                     headers=None, force_http=False):
 
333
        return self.connection.generate_url(expires_in, method, self.name,
 
334
                                            headers=headers,
 
335
                                            force_http=force_http)
 
336
 
 
337
    def delete_key(self, key_name, headers=None,
 
338
                   version_id=None, mfa_token=None):
 
339
        """
 
340
        Deletes a key from the bucket.  If a version_id is provided,
 
341
        only that version of the key will be deleted.
 
342
        
 
343
        :type key_name: string
 
344
        :param key_name: The key name to delete
 
345
 
 
346
        :type version_id: string
 
347
        :param version_id: The version ID (optional)
 
348
        
 
349
        :type mfa_token: tuple or list of strings
 
350
        :param mfa_token: A tuple or list consisting of the serial number
 
351
                          from the MFA device and the current value of
 
352
                          the six-digit token associated with the device.
 
353
                          This value is required anytime you are
 
354
                          deleting versioned objects from a bucket
 
355
                          that has the MFADelete option on the bucket.
 
356
        """
 
357
        if version_id:
 
358
            query_args = 'versionId=%s' % version_id
 
359
        else:
 
360
            query_args = None
 
361
        if mfa_token:
 
362
            if not headers:
 
363
                headers = {}
 
364
            headers['x-amz-mfa'] = ' '.join(mfa_token)
 
365
        response = self.connection.make_request('DELETE', self.name, key_name,
 
366
                                                headers=headers,
 
367
                                                query_args=query_args)
 
368
        body = response.read()
 
369
        if response.status != 204:
 
370
            raise S3ResponseError(response.status, response.reason, body)
 
371
 
 
372
    def copy_key(self, new_key_name, src_bucket_name,
 
373
                 src_key_name, metadata=None, src_version_id=None):
 
374
        """
 
375
        Create a new key in the bucket by copying another existing key.
 
376
 
 
377
        :type new_key_name: string
 
378
        :param new_key_name: The name of the new key
 
379
 
 
380
        :type src_bucket_name: string
 
381
        :param src_bucket_name: The name of the source bucket
 
382
 
 
383
        :type src_key_name: string
 
384
        :param src_key_name: The name of the source key
 
385
 
 
386
        :type src_version_id: string
 
387
        :param src_version_id: The version id for the key.  This param
 
388
                               is optional.  If not specified, the newest
 
389
                               version of the key will be copied.
 
390
 
 
391
        :type metadata: dict
 
392
        :param metadata: Metadata to be associated with new key.
 
393
                         If metadata is supplied, it will replace the
 
394
                         metadata of the source key being copied.
 
395
                         If no metadata is supplied, the source key's
 
396
                         metadata will be copied to the new key.
 
397
 
 
398
        :rtype: :class:`boto.s3.key.Key` or subclass
 
399
        :returns: An instance of the newly created key object
 
400
        """
 
401
        src = '%s/%s' % (src_bucket_name, urllib.quote(src_key_name))
 
402
        if src_version_id:
 
403
            src += '?version_id=%s' % src_version_id
 
404
        if metadata:
 
405
            headers = {'x-amz-copy-source' : src,
 
406
                       'x-amz-metadata-directive' : 'REPLACE'}
 
407
            headers = boto.utils.merge_meta(headers, metadata)
 
408
        else:
 
409
            headers = {'x-amz-copy-source' : src,
 
410
                       'x-amz-metadata-directive' : 'COPY'}
 
411
        response = self.connection.make_request('PUT', self.name, new_key_name,
 
412
                                                headers=headers)
 
413
        body = response.read()
 
414
        if response.status == 200:
 
415
            key = self.new_key(new_key_name)
 
416
            h = handler.XmlHandler(key, self)
 
417
            xml.sax.parseString(body, h)
 
418
            if hasattr(key, 'Error'):
 
419
                raise S3CopyError(key.Code, key.Message, body)
 
420
            key.handle_version_headers(response)
 
421
            return key
 
422
        else:
 
423
            raise S3ResponseError(response.status, response.reason, body)
 
424
 
 
425
    def set_canned_acl(self, acl_str, key_name='', headers=None,
 
426
                       version_id=None):
 
427
        assert acl_str in CannedACLStrings
 
428
 
 
429
        if headers:
 
430
            headers['x-amz-acl'] = acl_str
 
431
        else:
 
432
            headers={'x-amz-acl': acl_str}
 
433
 
 
434
        query_args='acl'
 
435
        if version_id:
 
436
            query_args += '&versionId=%s' % version_id
 
437
        response = self.connection.make_request('PUT', self.name, key_name,
 
438
                headers=headers, query_args=query_args)
 
439
        body = response.read()
 
440
        if response.status != 200:
 
441
            raise S3ResponseError(response.status, response.reason, body)
 
442
 
 
443
    def get_xml_acl(self, key_name='', headers=None, version_id=None):
 
444
        query_args = 'acl'
 
445
        if version_id:
 
446
            query_args += '&versionId=%s' % version_id
 
447
        response = self.connection.make_request('GET', self.name, key_name,
 
448
                                                query_args=query_args,
 
449
                                                headers=headers)
 
450
        body = response.read()
 
451
        if response.status != 200:
 
452
            raise S3ResponseError(response.status, response.reason, body)
 
453
        return body
 
454
 
 
455
    def set_xml_acl(self, acl_str, key_name='', headers=None, version_id=None):
 
456
        query_args = 'acl'
 
457
        if version_id:
 
458
            query_args += '&versionId=%s' % version_id
 
459
        response = self.connection.make_request('PUT', self.name, key_name,
 
460
                                                data=acl_str,
 
461
                                                query_args=query_args,
 
462
                                                headers=headers)
 
463
        body = response.read()
 
464
        if response.status != 200:
 
465
            raise S3ResponseError(response.status, response.reason, body)
 
466
 
 
467
    def set_acl(self, acl_or_str, key_name='', headers=None, version_id=None):
 
468
        if isinstance(acl_or_str, Policy):
 
469
            self.set_xml_acl(acl_or_str.to_xml(), key_name,
 
470
                             headers, version_id)
 
471
        else:
 
472
            self.set_canned_acl(acl_or_str, key_name,
 
473
                                headers, version_id)
 
474
 
 
475
    def get_acl(self, key_name='', headers=None, version_id=None):
 
476
        query_args = 'acl'
 
477
        if version_id:
 
478
            query_args += '&versionId=%s' % version_id
 
479
        response = self.connection.make_request('GET', self.name, key_name,
 
480
                                                query_args=query_args,
 
481
                                                headers=headers)
 
482
        body = response.read()
 
483
        if response.status == 200:
 
484
            policy = Policy(self)
 
485
            h = handler.XmlHandler(policy, self)
 
486
            xml.sax.parseString(body, h)
 
487
            return policy
 
488
        else:
 
489
            raise S3ResponseError(response.status, response.reason, body)
 
490
 
 
491
    def make_public(self, recursive=False, headers=None):
 
492
        self.set_canned_acl('public-read', headers=headers)
 
493
        if recursive:
 
494
            for key in self:
 
495
                self.set_canned_acl('public-read', key.name, headers=headers)
 
496
 
 
497
    def add_email_grant(self, permission, email_address,
 
498
                        recursive=False, headers=None):
 
499
        """
 
500
        Convenience method that provides a quick way to add an email grant
 
501
        to a bucket. This method retrieves the current ACL, creates a new
 
502
        grant based on the parameters passed in, adds that grant to the ACL
 
503
        and then PUT's the new ACL back to S3.
 
504
        
 
505
        :type permission: string
 
506
        :param permission: The permission being granted. Should be one of:
 
507
                           (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL).
 
508
        
 
509
        :type email_address: string
 
510
        :param email_address: The email address associated with the AWS
 
511
                              account your are granting the permission to.
 
512
        
 
513
        :type recursive: boolean
 
514
        :param recursive: A boolean value to controls whether the command
 
515
                          will apply the grant to all keys within the bucket
 
516
                          or not.  The default value is False.  By passing a
 
517
                          True value, the call will iterate through all keys
 
518
                          in the bucket and apply the same grant to each key.
 
519
                          CAUTION: If you have a lot of keys, this could take
 
520
                          a long time!
 
521
        """
 
522
        if permission not in S3Permissions:
 
523
            raise S3PermissionsError('Unknown Permission: %s' % permission)
 
524
        policy = self.get_acl(headers=headers)
 
525
        policy.acl.add_email_grant(permission, email_address)
 
526
        self.set_acl(policy, headers=headers)
 
527
        if recursive:
 
528
            for key in self:
 
529
                key.add_email_grant(permission, email_address, headers=headers)
 
530
 
 
531
    def add_user_grant(self, permission, user_id, recursive=False, headers=None):
 
532
        """
 
533
        Convenience method that provides a quick way to add a canonical user grant to a bucket.
 
534
        This method retrieves the current ACL, creates a new grant based on the parameters
 
535
        passed in, adds that grant to the ACL and then PUT's the new ACL back to S3.
 
536
        
 
537
        :type permission: string
 
538
        :param permission: The permission being granted. Should be one of:
 
539
                           (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL).
 
540
        
 
541
        :type user_id: string
 
542
        :param user_id:     The canonical user id associated with the AWS account your are granting
 
543
                            the permission to.
 
544
                            
 
545
        :type recursive: boolean
 
546
        :param recursive: A boolean value to controls whether the command
 
547
                          will apply the grant to all keys within the bucket
 
548
                          or not.  The default value is False.  By passing a
 
549
                          True value, the call will iterate through all keys
 
550
                          in the bucket and apply the same grant to each key.
 
551
                          CAUTION: If you have a lot of keys, this could take
 
552
                          a long time!
 
553
        """
 
554
        if permission not in S3Permissions:
 
555
            raise S3PermissionsError('Unknown Permission: %s' % permission)
 
556
        policy = self.get_acl(headers=headers)
 
557
        policy.acl.add_user_grant(permission, user_id)
 
558
        self.set_acl(policy, headers=headers)
 
559
        if recursive:
 
560
            for key in self:
 
561
                key.add_user_grant(permission, user_id, headers=headers)
 
562
 
 
563
    def list_grants(self, headers=None):
 
564
        policy = self.get_acl(headers=headers)
 
565
        return policy.acl.grants
 
566
 
 
567
    def get_location(self):
 
568
        """
 
569
        Returns the LocationConstraint for the bucket.
 
570
 
 
571
        :rtype: str
 
572
        :return: The LocationConstraint for the bucket or the empty string if
 
573
                 no constraint was specified when bucket was created.
 
574
        """
 
575
        response = self.connection.make_request('GET', self.name,
 
576
                                                query_args='location')
 
577
        body = response.read()
 
578
        if response.status == 200:
 
579
            rs = ResultSet(self)
 
580
            h = handler.XmlHandler(rs, self)
 
581
            xml.sax.parseString(body, h)
 
582
            return rs.LocationConstraint
 
583
        else:
 
584
            raise S3ResponseError(response.status, response.reason, body)
 
585
 
 
586
    def enable_logging(self, target_bucket, target_prefix='', headers=None):
 
587
        if isinstance(target_bucket, Bucket):
 
588
            target_bucket = target_bucket.name
 
589
        body = self.BucketLoggingBody % (target_bucket, target_prefix)
 
590
        response = self.connection.make_request('PUT', self.name, data=body,
 
591
                query_args='logging', headers=headers)
 
592
        body = response.read()
 
593
        if response.status == 200:
 
594
            return True
 
595
        else:
 
596
            raise S3ResponseError(response.status, response.reason, body)
 
597
        
 
598
    def disable_logging(self, headers=None):
 
599
        body = self.EmptyBucketLoggingBody
 
600
        response = self.connection.make_request('PUT', self.name, data=body,
 
601
                query_args='logging', headers=headers)
 
602
        body = response.read()
 
603
        if response.status == 200:
 
604
            return True
 
605
        else:
 
606
            raise S3ResponseError(response.status, response.reason, body)
 
607
 
 
608
    def get_logging_status(self, headers=None):
 
609
        response = self.connection.make_request('GET', self.name,
 
610
                query_args='logging', headers=headers)
 
611
        body = response.read()
 
612
        if response.status == 200:
 
613
            return body
 
614
        else:
 
615
            raise S3ResponseError(response.status, response.reason, body)
 
616
 
 
617
    def set_as_logging_target(self, headers=None):
 
618
        policy = self.get_acl(headers=headers)
 
619
        g1 = Grant(permission='WRITE', type='Group', uri=self.LoggingGroup)
 
620
        g2 = Grant(permission='READ_ACP', type='Group', uri=self.LoggingGroup)
 
621
        policy.acl.add_grant(g1)
 
622
        policy.acl.add_grant(g2)
 
623
        self.set_acl(policy, headers=headers)
 
624
 
 
625
    def get_request_payment(self, headers=None):
 
626
        response = self.connection.make_request('GET', self.name,
 
627
                query_args='requestPayment', headers=headers)
 
628
        body = response.read()
 
629
        if response.status == 200:
 
630
            return body
 
631
        else:
 
632
            raise S3ResponseError(response.status, response.reason, body)
 
633
 
 
634
    def set_request_payment(self, payer='BucketOwner', headers=None):
 
635
        body = self.BucketPaymentBody % payer
 
636
        response = self.connection.make_request('PUT', self.name, data=body,
 
637
                query_args='requestPayment', headers=headers)
 
638
        body = response.read()
 
639
        if response.status == 200:
 
640
            return True
 
641
        else:
 
642
            raise S3ResponseError(response.status, response.reason, body)
 
643
        
 
644
    def configure_versioning(self, versioning, mfa_delete=False,
 
645
                             mfa_token=None, headers=None):
 
646
        """
 
647
        Configure versioning for this bucket.
 
648
        Note: This feature is currently in beta release and is available
 
649
              only in the Northern California region.
 
650
 
 
651
        :type versioning: bool
 
652
        :param versioning: A boolean indicating whether version is
 
653
                           enabled (True) or disabled (False).
 
654
 
 
655
        :type mfa_delete: bool
 
656
        :param mfa_delete: A boolean indicating whether the Multi-Factor
 
657
                           Authentication Delete feature is enabled (True)
 
658
                           or disabled (False).  If mfa_delete is enabled
 
659
                           then all Delete operations will require the
 
660
                           token from your MFA device to be passed in
 
661
                           the request.
 
662
 
 
663
        :type mfa_token: tuple or list of strings
 
664
        :param mfa_token: A tuple or list consisting of the serial number
 
665
                          from the MFA device and the current value of
 
666
                          the six-digit token associated with the device.
 
667
                          This value is required when you are changing
 
668
                          the status of the MfaDelete property of
 
669
                          the bucket.
 
670
        """
 
671
        if versioning:
 
672
            ver = 'Enabled'
 
673
        else:
 
674
            ver = 'Suspended'
 
675
        if mfa_delete:
 
676
            mfa = 'Enabled'
 
677
        else:
 
678
            mfa = 'Disabled'
 
679
        body = self.VersioningBody % (ver, mfa)
 
680
        if mfa_token:
 
681
            if not headers:
 
682
                headers = {}
 
683
            headers['x-amz-mfa'] = ' '.join(mfa_token)
 
684
        response = self.connection.make_request('PUT', self.name, data=body,
 
685
                query_args='versioning', headers=headers)
 
686
        body = response.read()
 
687
        if response.status == 200:
 
688
            return True
 
689
        else:
 
690
            raise S3ResponseError(response.status, response.reason, body)
 
691
        
 
692
    def get_versioning_status(self, headers=None):
 
693
        """
 
694
        Returns the current status of versioning on the bucket.
 
695
 
 
696
        :rtype: dict
 
697
        :returns: A dictionary containing a key named 'Versioning'
 
698
                  that can have a value of either Enabled, Disabled,
 
699
                  or Suspended. Also, if MFADelete has ever been enabled
 
700
                  on the bucket, the dictionary will contain a key
 
701
                  named 'MFADelete' which will have a value of either
 
702
                  Enabled or Suspended.
 
703
        """
 
704
        response = self.connection.make_request('GET', self.name,
 
705
                query_args='versioning', headers=headers)
 
706
        body = response.read()
 
707
        boto.log.debug(body)
 
708
        if response.status == 200:
 
709
            d = {}
 
710
            ver = re.search(self.VersionRE, body)
 
711
            if ver:
 
712
                d['Versioning'] = ver.group(1)
 
713
            mfa = re.search(self.MFADeleteRE, body)
 
714
            if mfa:
 
715
                d['MfaDelete'] = mfa.group(1)
 
716
            return d
 
717
        else:
 
718
            raise S3ResponseError(response.status, response.reason, body)
 
719
 
 
720
    def delete(self, headers=None):
 
721
        return self.connection.delete_bucket(self.name, headers=headers)