| OLD | NEW |
| 1 # Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/ | 1 # Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/ |
| 2 # Copyright (c) 2010, Eucalyptus Systems, Inc. | 2 # Copyright (c) 2010, Eucalyptus Systems, Inc. |
| 3 # All rights reserved. | 3 # All rights reserved. |
| 4 # | 4 # |
| 5 # Permission is hereby granted, free of charge, to any person obtaining a | 5 # Permission is hereby granted, free of charge, to any person obtaining a |
| 6 # copy of this software and associated documentation files (the | 6 # copy of this software and associated documentation files (the |
| 7 # "Software"), to deal in the Software without restriction, including | 7 # "Software"), to deal in the Software without restriction, including |
| 8 # without limitation the rights to use, copy, modify, merge, publish, dis- | 8 # without limitation the rights to use, copy, modify, merge, publish, dis- |
| 9 # tribute, sublicense, and/or sell copies of the Software, and to permit | 9 # tribute, sublicense, and/or sell copies of the Software, and to permit |
| 10 # persons to whom the Software is furnished to do so, subject to the fol- | 10 # persons to whom the Software is furnished to do so, subject to the fol- |
| 11 # lowing conditions: | 11 # lowing conditions: |
| 12 # | 12 # |
| 13 # The above copyright notice and this permission notice shall be included | 13 # The above copyright notice and this permission notice shall be included |
| 14 # in all copies or substantial portions of the Software. | 14 # in all copies or substantial portions of the Software. |
| 15 # | 15 # |
| 16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | 16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| 17 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- | 17 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
| 18 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT | 18 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
| 19 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | 19 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 20 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 20 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | 21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 22 # IN THE SOFTWARE. | 22 # IN THE SOFTWARE. |
| 23 | 23 |
| 24 import boto | 24 import boto |
| 25 from boto import handler | 25 from boto import handler |
| 26 from boto.provider import Provider | |
| 27 from boto.resultset import ResultSet | 26 from boto.resultset import ResultSet |
| 28 from boto.s3.acl import ACL, Policy, CannedACLStrings, Grant | 27 from boto.exception import BotoClientError |
| 28 from boto.s3.acl import Policy, CannedACLStrings, Grant |
| 29 from boto.s3.key import Key | 29 from boto.s3.key import Key |
| 30 from boto.s3.prefix import Prefix | 30 from boto.s3.prefix import Prefix |
| 31 from boto.s3.deletemarker import DeleteMarker | 31 from boto.s3.deletemarker import DeleteMarker |
| 32 from boto.s3.user import User | |
| 33 from boto.s3.multipart import MultiPartUpload | 32 from boto.s3.multipart import MultiPartUpload |
| 34 from boto.s3.multipart import CompleteMultiPartUpload | 33 from boto.s3.multipart import CompleteMultiPartUpload |
| 34 from boto.s3.multidelete import MultiDeleteResult |
| 35 from boto.s3.multidelete import Error |
| 35 from boto.s3.bucketlistresultset import BucketListResultSet | 36 from boto.s3.bucketlistresultset import BucketListResultSet |
| 36 from boto.s3.bucketlistresultset import VersionedBucketListResultSet | 37 from boto.s3.bucketlistresultset import VersionedBucketListResultSet |
| 37 from boto.s3.bucketlistresultset import MultiPartUploadListResultSet | 38 from boto.s3.bucketlistresultset import MultiPartUploadListResultSet |
| 39 from boto.s3.lifecycle import Lifecycle |
| 40 from boto.s3.bucketlogging import BucketLogging |
| 38 import boto.jsonresponse | 41 import boto.jsonresponse |
| 39 import boto.utils | 42 import boto.utils |
| 40 import xml.sax | 43 import xml.sax |
| 44 import xml.sax.saxutils |
| 45 import StringIO |
| 41 import urllib | 46 import urllib |
| 42 import re | 47 import re |
| 48 import base64 |
| 43 from collections import defaultdict | 49 from collections import defaultdict |
| 44 | 50 |
| 45 # as per http://goo.gl/BDuud (02/19/2011) | 51 # as per http://goo.gl/BDuud (02/19/2011) |
| 46 class S3WebsiteEndpointTranslate: | 52 class S3WebsiteEndpointTranslate: |
| 47 trans_region = defaultdict(lambda :'s3-website-us-east-1') | 53 trans_region = defaultdict(lambda :'s3-website-us-east-1') |
| 48 | 54 |
| 49 trans_region['EU'] = 's3-website-eu-west-1' | 55 trans_region['eu-west-1'] = 's3-website-eu-west-1' |
| 50 trans_region['us-west-1'] = 's3-website-us-west-1' | 56 trans_region['us-west-1'] = 's3-website-us-west-1' |
| 57 trans_region['us-west-2'] = 's3-website-us-west-2' |
| 58 trans_region['sa-east-1'] = 's3-website-sa-east-1' |
| 51 trans_region['ap-northeast-1'] = 's3-website-ap-northeast-1' | 59 trans_region['ap-northeast-1'] = 's3-website-ap-northeast-1' |
| 52 trans_region['ap-southeast-1'] = 's3-website-ap-southeast-1' | 60 trans_region['ap-southeast-1'] = 's3-website-ap-southeast-1' |
| 53 | 61 |
| 54 @classmethod | 62 @classmethod |
| 55 def translate_region(self, reg): | 63 def translate_region(self, reg): |
| 56 return self.trans_region[reg] | 64 return self.trans_region[reg] |
| 57 | 65 |
| 58 S3Permissions = ['READ', 'WRITE', 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL'] | 66 S3Permissions = ['READ', 'WRITE', 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL'] |
| 59 | 67 |
| 60 class Bucket(object): | 68 class Bucket(object): |
| 61 | 69 |
| 62 BucketLoggingBody = """<?xml version="1.0" encoding="UTF-8"?> | |
| 63 <BucketLoggingStatus xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> | |
| 64 <LoggingEnabled> | |
| 65 <TargetBucket>%s</TargetBucket> | |
| 66 <TargetPrefix>%s</TargetPrefix> | |
| 67 </LoggingEnabled> | |
| 68 </BucketLoggingStatus>""" | |
| 69 | |
| 70 EmptyBucketLoggingBody = """<?xml version="1.0" encoding="UTF-8"?> | |
| 71 <BucketLoggingStatus xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> | |
| 72 </BucketLoggingStatus>""" | |
| 73 | |
| 74 LoggingGroup = 'http://acs.amazonaws.com/groups/s3/LogDelivery' | 70 LoggingGroup = 'http://acs.amazonaws.com/groups/s3/LogDelivery' |
| 75 | 71 |
| 76 BucketPaymentBody = """<?xml version="1.0" encoding="UTF-8"?> | 72 BucketPaymentBody = """<?xml version="1.0" encoding="UTF-8"?> |
| 77 <RequestPaymentConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-0
1/"> | 73 <RequestPaymentConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-0
1/"> |
| 78 <Payer>%s</Payer> | 74 <Payer>%s</Payer> |
| 79 </RequestPaymentConfiguration>""" | 75 </RequestPaymentConfiguration>""" |
| 80 | 76 |
| 81 VersioningBody = """<?xml version="1.0" encoding="UTF-8"?> | 77 VersioningBody = """<?xml version="1.0" encoding="UTF-8"?> |
| 82 <VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> | 78 <VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> |
| 83 <Status>%s</Status> | 79 <Status>%s</Status> |
| (...skipping 16 matching lines...) Expand all Loading... |
| 100 self.connection = connection | 96 self.connection = connection |
| 101 self.key_class = key_class | 97 self.key_class = key_class |
| 102 | 98 |
| 103 def __repr__(self): | 99 def __repr__(self): |
| 104 return '<Bucket: %s>' % self.name | 100 return '<Bucket: %s>' % self.name |
| 105 | 101 |
| 106 def __iter__(self): | 102 def __iter__(self): |
| 107 return iter(BucketListResultSet(self)) | 103 return iter(BucketListResultSet(self)) |
| 108 | 104 |
| 109 def __contains__(self, key_name): | 105 def __contains__(self, key_name): |
| 110 return not (self.get_key(key_name) is None) | 106 return not (self.get_key(key_name) is None) |
| 111 | 107 |
| 112 def startElement(self, name, attrs, connection): | 108 def startElement(self, name, attrs, connection): |
| 113 return None | 109 return None |
| 114 | 110 |
| 115 def endElement(self, name, value, connection): | 111 def endElement(self, name, value, connection): |
| 116 if name == 'Name': | 112 if name == 'Name': |
| 117 self.name = value | 113 self.name = value |
| 118 elif name == 'CreationDate': | 114 elif name == 'CreationDate': |
| 119 self.creation_date = value | 115 self.creation_date = value |
| 120 else: | 116 else: |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 181 # requests when the content-length is zero. | 177 # requests when the content-length is zero. |
| 182 # See http://goo.gl/0Tdax for more details. | 178 # See http://goo.gl/0Tdax for more details. |
| 183 clen = response.getheader('content-length') | 179 clen = response.getheader('content-length') |
| 184 if clen: | 180 if clen: |
| 185 k.size = int(response.getheader('content-length')) | 181 k.size = int(response.getheader('content-length')) |
| 186 else: | 182 else: |
| 187 k.size = 0 | 183 k.size = 0 |
| 188 k.cache_control = response.getheader('cache-control') | 184 k.cache_control = response.getheader('cache-control') |
| 189 k.name = key_name | 185 k.name = key_name |
| 190 k.handle_version_headers(response) | 186 k.handle_version_headers(response) |
| 187 k.handle_encryption_headers(response) |
| 191 return k | 188 return k |
| 192 else: | 189 else: |
| 193 if response.status == 404: | 190 if response.status == 404: |
| 194 response.read() | 191 response.read() |
| 195 return None | 192 return None |
| 196 else: | 193 else: |
| 197 raise self.connection.provider.storage_response_error( | 194 raise self.connection.provider.storage_response_error( |
| 198 response.status, response.reason, '') | 195 response.status, response.reason, '') |
| 199 | 196 |
| 200 def list(self, prefix='', delimiter='', marker='', headers=None): | 197 def list(self, prefix='', delimiter='', marker='', headers=None): |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 283 :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet` | 280 :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet` |
| 284 :return: an instance of a BucketListResultSet that handles paging, etc | 281 :return: an instance of a BucketListResultSet that handles paging, etc |
| 285 """ | 282 """ |
| 286 return MultiPartUploadListResultSet(self, key_marker, | 283 return MultiPartUploadListResultSet(self, key_marker, |
| 287 upload_id_marker, | 284 upload_id_marker, |
| 288 headers) | 285 headers) |
| 289 | 286 |
| 290 def _get_all(self, element_map, initial_query_string='', | 287 def _get_all(self, element_map, initial_query_string='', |
| 291 headers=None, **params): | 288 headers=None, **params): |
| 292 l = [] | 289 l = [] |
| 293 for k,v in params.items(): | 290 for k, v in params.items(): |
| 294 k = k.replace('_', '-') | 291 k = k.replace('_', '-') |
| 295 if k == 'maxkeys': | 292 if k == 'maxkeys': |
| 296 k = 'max-keys' | 293 k = 'max-keys' |
| 297 if isinstance(v, unicode): | 294 if isinstance(v, unicode): |
| 298 v = v.encode('utf-8') | 295 v = v.encode('utf-8') |
| 299 if v is not None and v != '': | 296 if v is not None and v != '': |
| 300 l.append('%s=%s' % (urllib.quote(k), urllib.quote(str(v)))) | 297 l.append('%s=%s' % (urllib.quote(k), urllib.quote(str(v)))) |
| 301 if len(l): | 298 if len(l): |
| 302 s = initial_query_string + '&' + '&'.join(l) | 299 s = initial_query_string + '&' + '&'.join(l) |
| 303 else: | 300 else: |
| 304 s = initial_query_string | 301 s = initial_query_string |
| 305 response = self.connection.make_request('GET', self.name, | 302 response = self.connection.make_request('GET', self.name, |
| 306 headers=headers, query_args=s) | 303 headers=headers, |
| 304 query_args=s) |
| 307 body = response.read() | 305 body = response.read() |
| 308 boto.log.debug(body) | 306 boto.log.debug(body) |
| 309 if response.status == 200: | 307 if response.status == 200: |
| 310 rs = ResultSet(element_map) | 308 rs = ResultSet(element_map) |
| 311 h = handler.XmlHandler(rs, self) | 309 h = handler.XmlHandler(rs, self) |
| 312 xml.sax.parseString(body, h) | 310 xml.sax.parseString(body, h) |
| 313 return rs | 311 return rs |
| 314 else: | 312 else: |
| 315 raise self.connection.provider.storage_response_error( | 313 raise self.connection.provider.storage_response_error( |
| 316 response.status, response.reason, body) | 314 response.status, response.reason, body) |
| (...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 421 equal to the key_marker might be included | 419 equal to the key_marker might be included |
| 422 in the list only if they have an upload ID | 420 in the list only if they have an upload ID |
| 423 lexicographically greater than the specified | 421 lexicographically greater than the specified |
| 424 upload_id_marker. | 422 upload_id_marker. |
| 425 | 423 |
| 426 | 424 |
| 427 :rtype: ResultSet | 425 :rtype: ResultSet |
| 428 :return: The result from S3 listing the uploads requested | 426 :return: The result from S3 listing the uploads requested |
| 429 | 427 |
| 430 """ | 428 """ |
| 431 return self._get_all([('Upload', MultiPartUpload)], | 429 return self._get_all([('Upload', MultiPartUpload), |
| 430 ('CommonPrefixes', Prefix)], |
| 432 'uploads', headers, **params) | 431 'uploads', headers, **params) |
| 433 | 432 |
| 434 def new_key(self, key_name=None): | 433 def new_key(self, key_name=None): |
| 435 """ | 434 """ |
| 436 Creates a new key | 435 Creates a new key |
| 437 | 436 |
| 438 :type key_name: string | 437 :type key_name: string |
| 439 :param key_name: The name of the key to create | 438 :param key_name: The name of the key to create |
| 440 | 439 |
| 441 :rtype: :class:`boto.s3.key.Key` or subclass | 440 :rtype: :class:`boto.s3.key.Key` or subclass |
| 442 :returns: An instance of the newly created key object | 441 :returns: An instance of the newly created key object |
| 443 """ | 442 """ |
| 444 return self.key_class(self, key_name) | 443 return self.key_class(self, key_name) |
| 445 | 444 |
| 446 def generate_url(self, expires_in, method='GET', headers=None, | 445 def generate_url(self, expires_in, method='GET', headers=None, |
| 447 force_http=False, response_headers=None): | 446 force_http=False, response_headers=None, |
| 447 expires_in_absolute=False): |
| 448 return self.connection.generate_url(expires_in, method, self.name, | 448 return self.connection.generate_url(expires_in, method, self.name, |
| 449 headers=headers, | 449 headers=headers, |
| 450 force_http=force_http, | 450 force_http=force_http, |
| 451 response_headers=response_headers) | 451 response_headers=response_headers, |
| 452 expires_in_absolute=expires_in_absol
ute) |
| 453 |
| 454 def delete_keys(self, keys, quiet=False, mfa_token=None, headers=None): |
| 455 """ |
| 456 Deletes a set of keys using S3's Multi-object delete API. If a |
| 457 VersionID is specified for that key then that version is removed. |
| 458 Returns a MultiDeleteResult Object, which contains Deleted |
| 459 and Error elements for each key you ask to delete. |
| 460 |
| 461 :type keys: list |
| 462 :param keys: A list of either key_names or (key_name, versionid) pairs |
| 463 or a list of Key instances. |
| 464 |
| 465 :type quiet: boolean |
| 466 :param quiet: In quiet mode the response includes only keys where |
| 467 the delete operation encountered an error. For a |
| 468 successful deletion, the operation does not return |
| 469 any information about the delete in the response body. |
| 470 |
| 471 :type mfa_token: tuple or list of strings |
| 472 :param mfa_token: A tuple or list consisting of the serial number |
| 473 from the MFA device and the current value of |
| 474 the six-digit token associated with the device. |
| 475 This value is required anytime you are |
| 476 deleting versioned objects from a bucket |
| 477 that has the MFADelete option on the bucket. |
| 478 |
| 479 :returns: An instance of MultiDeleteResult |
| 480 """ |
| 481 ikeys = iter(keys) |
| 482 result = MultiDeleteResult(self) |
| 483 provider = self.connection.provider |
| 484 query_args = 'delete' |
| 485 def delete_keys2(hdrs): |
| 486 hdrs = hdrs or {} |
| 487 data = u"""<?xml version="1.0" encoding="UTF-8"?>""" |
| 488 data += u"<Delete>" |
| 489 if quiet: |
| 490 data += u"<Quiet>true</Quiet>" |
| 491 count = 0 |
| 492 while count < 1000: |
| 493 try: |
| 494 key = ikeys.next() |
| 495 except StopIteration: |
| 496 break |
| 497 if isinstance(key, basestring): |
| 498 key_name = key |
| 499 version_id = None |
| 500 elif isinstance(key, tuple) and len(key) == 2: |
| 501 key_name, version_id = key |
| 502 elif (isinstance(key, Key) or isinstance(key, DeleteMarker)) and
key.name: |
| 503 key_name = key.name |
| 504 version_id = key.version_id |
| 505 else: |
| 506 if isinstance(key, Prefix): |
| 507 key_name = key.name |
| 508 code = 'PrefixSkipped' # Don't delete Prefix |
| 509 else: |
| 510 key_name = repr(key) # try get a string |
| 511 code = 'InvalidArgument' # other unknown type |
| 512 message = 'Invalid. No delete action taken for this object.' |
| 513 error = Error(key_name, code=code, message=message) |
| 514 result.errors.append(error) |
| 515 continue |
| 516 count += 1 |
| 517 #key_name = key_name.decode('utf-8') |
| 518 data += u"<Object><Key>%s</Key>" % xml.sax.saxutils.escape(key_n
ame) |
| 519 if version_id: |
| 520 data += u"<VersionId>%s</VersionId>" % version_id |
| 521 data += u"</Object>" |
| 522 data += u"</Delete>" |
| 523 if count <= 0: |
| 524 return False # no more |
| 525 data = data.encode('utf-8') |
| 526 fp = StringIO.StringIO(data) |
| 527 md5 = boto.utils.compute_md5(fp) |
| 528 hdrs['Content-MD5'] = md5[1] |
| 529 hdrs['Content-Type'] = 'text/xml' |
| 530 if mfa_token: |
| 531 hdrs[provider.mfa_header] = ' '.join(mfa_token) |
| 532 response = self.connection.make_request('POST', self.name, |
| 533 headers=hdrs, |
| 534 query_args=query_args, |
| 535 data=data) |
| 536 body = response.read() |
| 537 if response.status == 200: |
| 538 h = handler.XmlHandler(result, self) |
| 539 xml.sax.parseString(body, h) |
| 540 return count >= 1000 # more? |
| 541 else: |
| 542 raise provider.storage_response_error(response.status, |
| 543 response.reason, |
| 544 body) |
| 545 while delete_keys2(headers): |
| 546 pass |
| 547 return result |
| 452 | 548 |
| 453 def delete_key(self, key_name, headers=None, | 549 def delete_key(self, key_name, headers=None, |
| 454 version_id=None, mfa_token=None): | 550 version_id=None, mfa_token=None): |
| 455 """ | 551 """ |
| 456 Deletes a key from the bucket. If a version_id is provided, | 552 Deletes a key from the bucket. If a version_id is provided, |
| 457 only that version of the key will be deleted. | 553 only that version of the key will be deleted. |
| 458 | 554 |
| 459 :type key_name: string | 555 :type key_name: string |
| 460 :param key_name: The key name to delete | 556 :param key_name: The key name to delete |
| 461 | 557 |
| 462 :type version_id: string | 558 :type version_id: string |
| 463 :param version_id: The version ID (optional) | 559 :param version_id: The version ID (optional) |
| 464 | 560 |
| 465 :type mfa_token: tuple or list of strings | 561 :type mfa_token: tuple or list of strings |
| 466 :param mfa_token: A tuple or list consisting of the serial number | 562 :param mfa_token: A tuple or list consisting of the serial number |
| 467 from the MFA device and the current value of | 563 from the MFA device and the current value of |
| 468 the six-digit token associated with the device. | 564 the six-digit token associated with the device. |
| 469 This value is required anytime you are | 565 This value is required anytime you are |
| 470 deleting versioned objects from a bucket | 566 deleting versioned objects from a bucket |
| 471 that has the MFADelete option on the bucket. | 567 that has the MFADelete option on the bucket. |
| 568 |
| 569 :rtype: :class:`boto.s3.key.Key` or subclass |
| 570 :returns: A key object holding information on what was deleted. |
| 571 The Caller can see if a delete_marker was created or |
| 572 removed and what version_id the delete created or removed. |
| 472 """ | 573 """ |
| 473 provider = self.connection.provider | 574 provider = self.connection.provider |
| 474 if version_id: | 575 if version_id: |
| 475 query_args = 'versionId=%s' % version_id | 576 query_args = 'versionId=%s' % version_id |
| 476 else: | 577 else: |
| 477 query_args = None | 578 query_args = None |
| 478 if mfa_token: | 579 if mfa_token: |
| 479 if not headers: | 580 if not headers: |
| 480 headers = {} | 581 headers = {} |
| 481 headers[provider.mfa_header] = ' '.join(mfa_token) | 582 headers[provider.mfa_header] = ' '.join(mfa_token) |
| 482 response = self.connection.make_request('DELETE', self.name, key_name, | 583 response = self.connection.make_request('DELETE', self.name, key_name, |
| 483 headers=headers, | 584 headers=headers, |
| 484 query_args=query_args) | 585 query_args=query_args) |
| 485 body = response.read() | 586 body = response.read() |
| 486 if response.status != 204: | 587 if response.status != 204: |
| 487 raise provider.storage_response_error(response.status, | 588 raise provider.storage_response_error(response.status, |
| 488 response.reason, body) | 589 response.reason, body) |
| 590 else: |
| 591 # return a key object with information on what was deleted. |
| 592 k = self.key_class(self) |
| 593 k.name = key_name |
| 594 k.handle_version_headers(response) |
| 595 return k |
| 489 | 596 |
| 490 def copy_key(self, new_key_name, src_bucket_name, | 597 def copy_key(self, new_key_name, src_bucket_name, |
| 491 src_key_name, metadata=None, src_version_id=None, | 598 src_key_name, metadata=None, src_version_id=None, |
| 492 storage_class='STANDARD', preserve_acl=False): | 599 storage_class='STANDARD', preserve_acl=False, |
| 600 encrypt_key=False, headers=None, query_args=None): |
| 493 """ | 601 """ |
| 494 Create a new key in the bucket by copying another existing key. | 602 Create a new key in the bucket by copying another existing key. |
| 495 | 603 |
| 496 :type new_key_name: string | 604 :type new_key_name: string |
| 497 :param new_key_name: The name of the new key | 605 :param new_key_name: The name of the new key |
| 498 | 606 |
| 499 :type src_bucket_name: string | 607 :type src_bucket_name: string |
| 500 :param src_bucket_name: The name of the source bucket | 608 :param src_bucket_name: The name of the source bucket |
| 501 | 609 |
| 502 :type src_key_name: string | 610 :type src_key_name: string |
| (...skipping 24 matching lines...) Expand all Loading... |
| 527 will have the default ACL. | 635 will have the default ACL. |
| 528 Note that preserving the ACL in the | 636 Note that preserving the ACL in the |
| 529 new key object will require two | 637 new key object will require two |
| 530 additional API calls to S3, one to | 638 additional API calls to S3, one to |
| 531 retrieve the current ACL and one to | 639 retrieve the current ACL and one to |
| 532 set that ACL on the new object. If | 640 set that ACL on the new object. If |
| 533 you don't care about the ACL, a value | 641 you don't care about the ACL, a value |
| 534 of False will be significantly more | 642 of False will be significantly more |
| 535 efficient. | 643 efficient. |
| 536 | 644 |
| 645 :type encrypt_key: bool |
| 646 :param encrypt_key: If True, the new copy of the object will |
| 647 be encrypted on the server-side by S3 and |
| 648 will be stored in an encrypted form while |
| 649 at rest in S3. |
| 650 |
| 651 :type headers: dict |
| 652 :param headers: A dictionary of header name/value pairs. |
| 653 |
| 654 :type query_args: string |
| 655 :param query_args: A string of additional querystring arguments |
| 656 to append to the request |
| 657 |
| 537 :rtype: :class:`boto.s3.key.Key` or subclass | 658 :rtype: :class:`boto.s3.key.Key` or subclass |
| 538 :returns: An instance of the newly created key object | 659 :returns: An instance of the newly created key object |
| 539 """ | 660 """ |
| 540 | 661 headers = headers or {} |
| 662 provider = self.connection.provider |
| 541 src_key_name = boto.utils.get_utf8_value(src_key_name) | 663 src_key_name = boto.utils.get_utf8_value(src_key_name) |
| 542 if preserve_acl: | 664 if preserve_acl: |
| 543 if self.name == src_bucket_name: | 665 if self.name == src_bucket_name: |
| 544 src_bucket = self | 666 src_bucket = self |
| 545 else: | 667 else: |
| 546 src_bucket = self.connection.get_bucket(src_bucket_name) | 668 src_bucket = self.connection.get_bucket(src_bucket_name) |
| 547 acl = src_bucket.get_xml_acl(src_key_name) | 669 acl = src_bucket.get_xml_acl(src_key_name) |
| 670 if encrypt_key: |
| 671 headers[provider.server_side_encryption_header] = 'AES256' |
| 548 src = '%s/%s' % (src_bucket_name, urllib.quote(src_key_name)) | 672 src = '%s/%s' % (src_bucket_name, urllib.quote(src_key_name)) |
| 549 if src_version_id: | 673 if src_version_id: |
| 550 src += '?version_id=%s' % src_version_id | 674 src += '?versionId=%s' % src_version_id |
| 551 provider = self.connection.provider | 675 headers[provider.copy_source_header] = str(src) |
| 552 headers = {provider.copy_source_header : str(src)} | 676 # make sure storage_class_header key exists before accessing it |
| 553 if storage_class != 'STANDARD': | 677 if provider.storage_class_header and storage_class: |
| 554 headers[provider.storage_class_header] = storage_class | 678 headers[provider.storage_class_header] = storage_class |
| 555 if metadata: | 679 if metadata: |
| 556 headers[provider.metadata_directive_header] = 'REPLACE' | 680 headers[provider.metadata_directive_header] = 'REPLACE' |
| 557 headers = boto.utils.merge_meta(headers, metadata, provider) | 681 headers = boto.utils.merge_meta(headers, metadata, provider) |
| 558 else: | 682 elif not query_args: # Can't use this header with multi-part copy. |
| 559 headers[provider.metadata_directive_header] = 'COPY' | 683 headers[provider.metadata_directive_header] = 'COPY' |
| 560 response = self.connection.make_request('PUT', self.name, new_key_name, | 684 response = self.connection.make_request('PUT', self.name, new_key_name, |
| 561 headers=headers) | 685 headers=headers, |
| 686 query_args=query_args) |
| 562 body = response.read() | 687 body = response.read() |
| 563 if response.status == 200: | 688 if response.status == 200: |
| 564 key = self.new_key(new_key_name) | 689 key = self.new_key(new_key_name) |
| 565 h = handler.XmlHandler(key, self) | 690 h = handler.XmlHandler(key, self) |
| 566 xml.sax.parseString(body, h) | 691 xml.sax.parseString(body, h) |
| 567 if hasattr(key, 'Error'): | 692 if hasattr(key, 'Error'): |
| 568 raise provider.storage_copy_error(key.Code, key.Message, body) | 693 raise provider.storage_copy_error(key.Code, key.Message, body) |
| 569 key.handle_version_headers(response) | 694 key.handle_version_headers(response) |
| 570 if preserve_acl: | 695 if preserve_acl: |
| 571 self.set_xml_acl(acl, new_key_name) | 696 self.set_xml_acl(acl, new_key_name) |
| 572 return key | 697 return key |
| 573 else: | 698 else: |
| 574 raise provider.storage_response_error(response.status, response.reas
on, body) | 699 raise provider.storage_response_error(response.status, |
| 700 response.reason, body) |
| 575 | 701 |
| 576 def set_canned_acl(self, acl_str, key_name='', headers=None, | 702 def set_canned_acl(self, acl_str, key_name='', headers=None, |
| 577 version_id=None): | 703 version_id=None): |
| 578 assert acl_str in CannedACLStrings | 704 assert acl_str in CannedACLStrings |
| 579 | 705 |
| 580 if headers: | 706 if headers: |
| 581 headers[self.connection.provider.acl_header] = acl_str | 707 headers[self.connection.provider.acl_header] = acl_str |
| 582 else: | 708 else: |
| 583 headers={self.connection.provider.acl_header: acl_str} | 709 headers={self.connection.provider.acl_header: acl_str} |
| 584 | 710 |
| 585 query_args='acl' | 711 query_args = 'acl' |
| 586 if version_id: | 712 if version_id: |
| 587 query_args += '&versionId=%s' % version_id | 713 query_args += '&versionId=%s' % version_id |
| 588 response = self.connection.make_request('PUT', self.name, key_name, | 714 response = self.connection.make_request('PUT', self.name, key_name, |
| 589 headers=headers, query_args=query_args) | 715 headers=headers, query_args=query_args) |
| 590 body = response.read() | 716 body = response.read() |
| 591 if response.status != 200: | 717 if response.status != 200: |
| 592 raise self.connection.provider.storage_response_error( | 718 raise self.connection.provider.storage_response_error( |
| 593 response.status, response.reason, body) | 719 response.status, response.reason, body) |
| 594 | 720 |
| 595 def get_xml_acl(self, key_name='', headers=None, version_id=None): | 721 def get_xml_acl(self, key_name='', headers=None, version_id=None): |
| 596 query_args = 'acl' | 722 query_args = 'acl' |
| 597 if version_id: | 723 if version_id: |
| 598 query_args += '&versionId=%s' % version_id | 724 query_args += '&versionId=%s' % version_id |
| 599 response = self.connection.make_request('GET', self.name, key_name, | 725 response = self.connection.make_request('GET', self.name, key_name, |
| 600 query_args=query_args, | 726 query_args=query_args, |
| 601 headers=headers) | 727 headers=headers) |
| 602 body = response.read() | 728 body = response.read() |
| 603 if response.status != 200: | 729 if response.status != 200: |
| 604 raise self.connection.provider.storage_response_error( | 730 raise self.connection.provider.storage_response_error( |
| 605 response.status, response.reason, body) | 731 response.status, response.reason, body) |
| 606 return body | 732 return body |
| 607 | 733 |
| 608 def set_xml_acl(self, acl_str, key_name='', headers=None, version_id=None): | 734 def set_xml_acl(self, acl_str, key_name='', headers=None, version_id=None, |
| 609 query_args = 'acl' | 735 query_args='acl'): |
| 610 if version_id: | 736 if version_id: |
| 611 query_args += '&versionId=%s' % version_id | 737 query_args += '&versionId=%s' % version_id |
| 612 response = self.connection.make_request('PUT', self.name, key_name, | 738 response = self.connection.make_request('PUT', self.name, key_name, |
| 613 data=acl_str.encode('ISO-8859-1'
), | 739 data=acl_str.encode('ISO-8859-1'
), |
| 614 query_args=query_args, | 740 query_args=query_args, |
| 615 headers=headers) | 741 headers=headers) |
| 616 body = response.read() | 742 body = response.read() |
| 617 if response.status != 200: | 743 if response.status != 200: |
| 618 raise self.connection.provider.storage_response_error( | 744 raise self.connection.provider.storage_response_error( |
| 619 response.status, response.reason, body) | 745 response.status, response.reason, body) |
| (...skipping 16 matching lines...) Expand all Loading... |
| 636 body = response.read() | 762 body = response.read() |
| 637 if response.status == 200: | 763 if response.status == 200: |
| 638 policy = Policy(self) | 764 policy = Policy(self) |
| 639 h = handler.XmlHandler(policy, self) | 765 h = handler.XmlHandler(policy, self) |
| 640 xml.sax.parseString(body, h) | 766 xml.sax.parseString(body, h) |
| 641 return policy | 767 return policy |
| 642 else: | 768 else: |
| 643 raise self.connection.provider.storage_response_error( | 769 raise self.connection.provider.storage_response_error( |
| 644 response.status, response.reason, body) | 770 response.status, response.reason, body) |
| 645 | 771 |
| 772 def set_subresource(self, subresource, value, key_name = '', headers=None, |
| 773 version_id=None): |
| 774 """ |
| 775 Set a subresource for a bucket or key. |
| 776 |
| 777 :type subresource: string |
| 778 :param subresource: The subresource to set. |
| 779 |
| 780 :type value: string |
| 781 :param value: The value of the subresource. |
| 782 |
| 783 :type key_name: string |
| 784 :param key_name: The key to operate on, or None to operate on the |
| 785 bucket. |
| 786 |
| 787 :type headers: dict |
| 788 :param headers: Additional HTTP headers to include in the request. |
| 789 |
| 790 :type src_version_id: string |
| 791 :param src_version_id: Optional. The version id of the key to operate |
| 792 on. If not specified, operate on the newest |
| 793 version. |
| 794 """ |
| 795 if not subresource: |
| 796 raise TypeError('set_subresource called with subresource=None') |
| 797 query_args = subresource |
| 798 if version_id: |
| 799 query_args += '&versionId=%s' % version_id |
| 800 response = self.connection.make_request('PUT', self.name, key_name, |
| 801 data=value.encode('UTF-8'), |
| 802 query_args=query_args, |
| 803 headers=headers) |
| 804 body = response.read() |
| 805 if response.status != 200: |
| 806 raise self.connection.provider.storage_response_error( |
| 807 response.status, response.reason, body) |
| 808 |
| 809 def get_subresource(self, subresource, key_name='', headers=None, |
| 810 version_id=None): |
| 811 """ |
| 812 Get a subresource for a bucket or key. |
| 813 |
| 814 :type subresource: string |
| 815 :param subresource: The subresource to get. |
| 816 |
| 817 :type key_name: string |
| 818 :param key_name: The key to operate on, or None to operate on the |
| 819 bucket. |
| 820 |
| 821 :type headers: dict |
| 822 :param headers: Additional HTTP headers to include in the request. |
| 823 |
| 824 :type src_version_id: string |
| 825 :param src_version_id: Optional. The version id of the key to operate |
| 826 on. If not specified, operate on the newest |
| 827 version. |
| 828 |
| 829 :rtype: string |
| 830 :returns: The value of the subresource. |
| 831 """ |
| 832 if not subresource: |
| 833 raise TypeError('get_subresource called with subresource=None') |
| 834 query_args = subresource |
| 835 if version_id: |
| 836 query_args += '&versionId=%s' % version_id |
| 837 response = self.connection.make_request('GET', self.name, key_name, |
| 838 query_args=query_args, |
| 839 headers=headers) |
| 840 body = response.read() |
| 841 if response.status != 200: |
| 842 raise self.connection.provider.storage_response_error( |
| 843 response.status, response.reason, body) |
| 844 return body |
| 845 |
| 646 def make_public(self, recursive=False, headers=None): | 846 def make_public(self, recursive=False, headers=None): |
| 647 self.set_canned_acl('public-read', headers=headers) | 847 self.set_canned_acl('public-read', headers=headers) |
| 648 if recursive: | 848 if recursive: |
| 649 for key in self: | 849 for key in self: |
| 650 self.set_canned_acl('public-read', key.name, headers=headers) | 850 self.set_canned_acl('public-read', key.name, headers=headers) |
| 651 | 851 |
| 652 def add_email_grant(self, permission, email_address, | 852 def add_email_grant(self, permission, email_address, |
| 653 recursive=False, headers=None): | 853 recursive=False, headers=None): |
| 654 """ | 854 """ |
| 655 Convenience method that provides a quick way to add an email grant | 855 Convenience method that provides a quick way to add an email grant |
| (...skipping 21 matching lines...) Expand all Loading... |
| 677 if permission not in S3Permissions: | 877 if permission not in S3Permissions: |
| 678 raise self.connection.provider.storage_permissions_error( | 878 raise self.connection.provider.storage_permissions_error( |
| 679 'Unknown Permission: %s' % permission) | 879 'Unknown Permission: %s' % permission) |
| 680 policy = self.get_acl(headers=headers) | 880 policy = self.get_acl(headers=headers) |
| 681 policy.acl.add_email_grant(permission, email_address) | 881 policy.acl.add_email_grant(permission, email_address) |
| 682 self.set_acl(policy, headers=headers) | 882 self.set_acl(policy, headers=headers) |
| 683 if recursive: | 883 if recursive: |
| 684 for key in self: | 884 for key in self: |
| 685 key.add_email_grant(permission, email_address, headers=headers) | 885 key.add_email_grant(permission, email_address, headers=headers) |
| 686 | 886 |
| 687 def add_user_grant(self, permission, user_id, | 887 def add_user_grant(self, permission, user_id, recursive=False, |
| 688 recursive=False, headers=None): | 888 headers=None, display_name=None): |
| 689 """ | 889 """ |
| 690 Convenience method that provides a quick way to add a canonical | 890 Convenience method that provides a quick way to add a canonical |
| 691 user grant to a bucket. This method retrieves the current ACL, | 891 user grant to a bucket. This method retrieves the current ACL, |
| 692 creates a new grant based on the parameters passed in, adds that | 892 creates a new grant based on the parameters passed in, adds that |
| 693 grant to the ACL and then PUT's the new ACL back to S3. | 893 grant to the ACL and then PUT's the new ACL back to S3. |
| 694 | 894 |
| 695 :type permission: string | 895 :type permission: string |
| 696 :param permission: The permission being granted. Should be one of: | 896 :param permission: The permission being granted. Should be one of: |
| 697 (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL). | 897 (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL). |
| 698 | 898 |
| 699 :type user_id: string | 899 :type user_id: string |
| 700 :param user_id: The canonical user id associated with the AWS | 900 :param user_id: The canonical user id associated with the AWS |
| 701 account your are granting the permission to. | 901 account your are granting the permission to. |
| 702 | 902 |
| 703 :type recursive: boolean | 903 :type recursive: boolean |
| 704 :param recursive: A boolean value to controls whether the command | 904 :param recursive: A boolean value to controls whether the command |
| 705 will apply the grant to all keys within the bucket | 905 will apply the grant to all keys within the bucket |
| 706 or not. The default value is False. By passing a | 906 or not. The default value is False. By passing a |
| 707 True value, the call will iterate through all keys | 907 True value, the call will iterate through all keys |
| 708 in the bucket and apply the same grant to each key. | 908 in the bucket and apply the same grant to each key. |
| 709 CAUTION: If you have a lot of keys, this could take | 909 CAUTION: If you have a lot of keys, this could take |
| 710 a long time! | 910 a long time! |
| 911 |
| 912 :type display_name: string |
| 913 :param display_name: An option string containing the user's |
| 914 Display Name. Only required on Walrus. |
| 711 """ | 915 """ |
| 712 if permission not in S3Permissions: | 916 if permission not in S3Permissions: |
| 713 raise self.connection.provider.storage_permissions_error( | 917 raise self.connection.provider.storage_permissions_error( |
| 714 'Unknown Permission: %s' % permission) | 918 'Unknown Permission: %s' % permission) |
| 715 policy = self.get_acl(headers=headers) | 919 policy = self.get_acl(headers=headers) |
| 716 policy.acl.add_user_grant(permission, user_id) | 920 policy.acl.add_user_grant(permission, user_id, |
| 921 display_name=display_name) |
| 717 self.set_acl(policy, headers=headers) | 922 self.set_acl(policy, headers=headers) |
| 718 if recursive: | 923 if recursive: |
| 719 for key in self: | 924 for key in self: |
| 720 key.add_user_grant(permission, user_id, headers=headers) | 925 key.add_user_grant(permission, user_id, headers=headers, |
| 926 display_name=display_name) |
| 721 | 927 |
| 722 def list_grants(self, headers=None): | 928 def list_grants(self, headers=None): |
| 723 policy = self.get_acl(headers=headers) | 929 policy = self.get_acl(headers=headers) |
| 724 return policy.acl.grants | 930 return policy.acl.grants |
| 725 | 931 |
| 726 def get_location(self): | 932 def get_location(self): |
| 727 """ | 933 """ |
| 728 Returns the LocationConstraint for the bucket. | 934 Returns the LocationConstraint for the bucket. |
| 729 | 935 |
| 730 :rtype: str | 936 :rtype: str |
| 731 :return: The LocationConstraint for the bucket or the empty | 937 :return: The LocationConstraint for the bucket or the empty |
| 732 string if no constraint was specified when bucket | 938 string if no constraint was specified when bucket |
| 733 was created. | 939 was created. |
| 734 """ | 940 """ |
| 735 response = self.connection.make_request('GET', self.name, | 941 response = self.connection.make_request('GET', self.name, |
| 736 query_args='location') | 942 query_args='location') |
| 737 body = response.read() | 943 body = response.read() |
| 738 if response.status == 200: | 944 if response.status == 200: |
| 739 rs = ResultSet(self) | 945 rs = ResultSet(self) |
| 740 h = handler.XmlHandler(rs, self) | 946 h = handler.XmlHandler(rs, self) |
| 741 xml.sax.parseString(body, h) | 947 xml.sax.parseString(body, h) |
| 742 return rs.LocationConstraint | 948 return rs.LocationConstraint |
| 743 else: | 949 else: |
| 744 raise self.connection.provider.storage_response_error( | 950 raise self.connection.provider.storage_response_error( |
| 745 response.status, response.reason, body) | 951 response.status, response.reason, body) |
| 746 | 952 |
| 747 def enable_logging(self, target_bucket, target_prefix='', headers=None): | 953 def set_xml_logging(self, logging_str, headers=None): |
| 748 if isinstance(target_bucket, Bucket): | 954 """ |
| 749 target_bucket = target_bucket.name | 955 Set logging on a bucket directly to the given xml string. |
| 750 body = self.BucketLoggingBody % (target_bucket, target_prefix) | 956 |
| 957 :type logging_str: unicode string |
| 958 :param logging_str: The XML for the bucketloggingstatus which will be se
t. |
| 959 The string will be converted to utf-8 before it is s
ent. |
| 960 Usually, you will obtain this XML from the BucketLog
ging |
| 961 object. |
| 962 |
| 963 :rtype: bool |
| 964 :return: True if ok or raises an exception. |
| 965 """ |
| 966 body = logging_str.encode('utf-8') |
| 751 response = self.connection.make_request('PUT', self.name, data=body, | 967 response = self.connection.make_request('PUT', self.name, data=body, |
| 752 query_args='logging', headers=headers) | 968 query_args='logging', headers=headers) |
| 753 body = response.read() | 969 body = response.read() |
| 754 if response.status == 200: | 970 if response.status == 200: |
| 755 return True | 971 return True |
| 756 else: | 972 else: |
| 757 raise self.connection.provider.storage_response_error( | 973 raise self.connection.provider.storage_response_error( |
| 758 response.status, response.reason, body) | 974 response.status, response.reason, body) |
| 759 | 975 |
| 976 def enable_logging(self, target_bucket, target_prefix='', grants=None, heade
rs=None): |
| 977 """ |
| 978 Enable logging on a bucket. |
| 979 |
| 980 :type target_bucket: bucket or string |
| 981 :param target_bucket: The bucket to log to. |
| 982 |
| 983 :type target_prefix: string |
| 984 :param target_prefix: The prefix which should be prepended to the |
| 985 generated log files written to the target_bucket. |
| 986 |
| 987 :type grants: list of Grant objects |
| 988 :param grants: A list of extra permissions which will be granted on |
| 989 the log files which are created. |
| 990 |
| 991 :rtype: bool |
| 992 :return: True if ok or raises an exception. |
| 993 """ |
| 994 if isinstance(target_bucket, Bucket): |
| 995 target_bucket = target_bucket.name |
| 996 blogging = BucketLogging(target=target_bucket, prefix=target_prefix, gra
nts=grants) |
| 997 return self.set_xml_logging(blogging.to_xml(), headers=headers) |
| 998 |
| 760 def disable_logging(self, headers=None): | 999 def disable_logging(self, headers=None): |
| 761 body = self.EmptyBucketLoggingBody | 1000 """ |
| 762 response = self.connection.make_request('PUT', self.name, data=body, | 1001 Disable logging on a bucket. |
| 763 query_args='logging', headers=headers) | 1002 |
| 764 body = response.read() | 1003 :rtype: bool |
| 765 if response.status == 200: | 1004 :return: True if ok or raises an exception. |
| 766 return True | 1005 """ |
| 767 else: | 1006 blogging = BucketLogging() |
| 768 raise self.connection.provider.storage_response_error( | 1007 return self.set_xml_logging(blogging.to_xml(), headers=headers) |
| 769 response.status, response.reason, body) | |
| 770 | 1008 |
| 771 def get_logging_status(self, headers=None): | 1009 def get_logging_status(self, headers=None): |
| 1010 """ |
| 1011 Get the logging status for this bucket. |
| 1012 |
| 1013 :rtype: :class:`boto.s3.bucketlogging.BucketLogging` |
| 1014 :return: A BucketLogging object for this bucket. |
| 1015 """ |
| 772 response = self.connection.make_request('GET', self.name, | 1016 response = self.connection.make_request('GET', self.name, |
| 773 query_args='logging', headers=headers) | 1017 query_args='logging', headers=headers) |
| 774 body = response.read() | 1018 body = response.read() |
| 775 if response.status == 200: | 1019 if response.status == 200: |
| 776 return body | 1020 blogging = BucketLogging() |
| 1021 h = handler.XmlHandler(blogging, self) |
| 1022 xml.sax.parseString(body, h) |
| 1023 return blogging |
| 777 else: | 1024 else: |
| 778 raise self.connection.provider.storage_response_error( | 1025 raise self.connection.provider.storage_response_error( |
| 779 response.status, response.reason, body) | 1026 response.status, response.reason, body) |
| 780 | 1027 |
| 781 def set_as_logging_target(self, headers=None): | 1028 def set_as_logging_target(self, headers=None): |
| 1029 """ |
| 1030 Setup the current bucket as a logging target by granting the necessary |
| 1031 permissions to the LogDelivery group to write log files to this bucket. |
| 1032 """ |
| 782 policy = self.get_acl(headers=headers) | 1033 policy = self.get_acl(headers=headers) |
| 783 g1 = Grant(permission='WRITE', type='Group', uri=self.LoggingGroup) | 1034 g1 = Grant(permission='WRITE', type='Group', uri=self.LoggingGroup) |
| 784 g2 = Grant(permission='READ_ACP', type='Group', uri=self.LoggingGroup) | 1035 g2 = Grant(permission='READ_ACP', type='Group', uri=self.LoggingGroup) |
| 785 policy.acl.add_grant(g1) | 1036 policy.acl.add_grant(g1) |
| 786 policy.acl.add_grant(g2) | 1037 policy.acl.add_grant(g2) |
| 787 self.set_acl(policy, headers=headers) | 1038 self.set_acl(policy, headers=headers) |
| 788 | 1039 |
| 789 def get_request_payment(self, headers=None): | 1040 def get_request_payment(self, headers=None): |
| 790 response = self.connection.make_request('GET', self.name, | 1041 response = self.connection.make_request('GET', self.name, |
| 791 query_args='requestPayment', headers=headers) | 1042 query_args='requestPayment', headers=headers) |
| (...skipping 13 matching lines...) Expand all Loading... |
| 805 return True | 1056 return True |
| 806 else: | 1057 else: |
| 807 raise self.connection.provider.storage_response_error( | 1058 raise self.connection.provider.storage_response_error( |
| 808 response.status, response.reason, body) | 1059 response.status, response.reason, body) |
| 809 | 1060 |
| 810 def configure_versioning(self, versioning, mfa_delete=False, | 1061 def configure_versioning(self, versioning, mfa_delete=False, |
| 811 mfa_token=None, headers=None): | 1062 mfa_token=None, headers=None): |
| 812 """ | 1063 """ |
| 813 Configure versioning for this bucket. | 1064 Configure versioning for this bucket. |
| 814 | 1065 |
| 815 ..note:: This feature is currently in beta release and is available | 1066 ..note:: This feature is currently in beta. |
| 816 only in the Northern California region. | |
| 817 | 1067 |
| 818 :type versioning: bool | 1068 :type versioning: bool |
| 819 :param versioning: A boolean indicating whether version is | 1069 :param versioning: A boolean indicating whether version is |
| 820 enabled (True) or disabled (False). | 1070 enabled (True) or disabled (False). |
| 821 | 1071 |
| 822 :type mfa_delete: bool | 1072 :type mfa_delete: bool |
| 823 :param mfa_delete: A boolean indicating whether the Multi-Factor | 1073 :param mfa_delete: A boolean indicating whether the Multi-Factor |
| 824 Authentication Delete feature is enabled (True) | 1074 Authentication Delete feature is enabled (True) |
| 825 or disabled (False). If mfa_delete is enabled | 1075 or disabled (False). If mfa_delete is enabled |
| 826 then all Delete operations will require the | 1076 then all Delete operations will require the |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 880 if ver: | 1130 if ver: |
| 881 d['Versioning'] = ver.group(1) | 1131 d['Versioning'] = ver.group(1) |
| 882 mfa = re.search(self.MFADeleteRE, body) | 1132 mfa = re.search(self.MFADeleteRE, body) |
| 883 if mfa: | 1133 if mfa: |
| 884 d['MfaDelete'] = mfa.group(1) | 1134 d['MfaDelete'] = mfa.group(1) |
| 885 return d | 1135 return d |
| 886 else: | 1136 else: |
| 887 raise self.connection.provider.storage_response_error( | 1137 raise self.connection.provider.storage_response_error( |
| 888 response.status, response.reason, body) | 1138 response.status, response.reason, body) |
| 889 | 1139 |
| 1140 def configure_lifecycle(self, lifecycle_config, headers=None): |
| 1141 """ |
| 1142 Configure lifecycle for this bucket. |
| 1143 |
| 1144 :type lifecycle_config: :class:`boto.s3.lifecycle.Lifecycle` |
| 1145 :param lifecycle_config: The lifecycle configuration you want |
| 1146 to configure for this bucket. |
| 1147 """ |
| 1148 fp = StringIO.StringIO(lifecycle_config.to_xml()) |
| 1149 md5 = boto.utils.compute_md5(fp) |
| 1150 if headers is None: |
| 1151 headers = {} |
| 1152 headers['Content-MD5'] = md5[1] |
| 1153 headers['Content-Type'] = 'text/xml' |
| 1154 response = self.connection.make_request('PUT', self.name, |
| 1155 data=fp.getvalue(), |
| 1156 query_args='lifecycle', |
| 1157 headers=headers) |
| 1158 body = response.read() |
| 1159 if response.status == 200: |
| 1160 return True |
| 1161 else: |
| 1162 raise self.connection.provider.storage_response_error( |
| 1163 response.status, response.reason, body) |
| 1164 |
| 1165 def get_lifecycle_config(self, headers=None): |
| 1166 """ |
| 1167 Returns the current lifecycle configuration on the bucket. |
| 1168 |
| 1169 :rtype: :class:`boto.s3.lifecycle.Lifecycle` |
| 1170 :returns: A LifecycleConfig object that describes all current |
| 1171 lifecycle rules in effect for the bucket. |
| 1172 """ |
| 1173 response = self.connection.make_request('GET', self.name, |
| 1174 query_args='lifecycle', headers=headers) |
| 1175 body = response.read() |
| 1176 boto.log.debug(body) |
| 1177 if response.status == 200: |
| 1178 lifecycle = Lifecycle() |
| 1179 h = handler.XmlHandler(lifecycle, self) |
| 1180 xml.sax.parseString(body, h) |
| 1181 return lifecycle |
| 1182 else: |
| 1183 raise self.connection.provider.storage_response_error( |
| 1184 response.status, response.reason, body) |
| 1185 |
| 1186 def delete_lifecycle_configuration(self, headers=None): |
| 1187 """ |
| 1188 Removes all lifecycle configuration from the bucket. |
| 1189 """ |
| 1190 response = self.connection.make_request('DELETE', self.name, |
| 1191 query_args='lifecycle', |
| 1192 headers=headers) |
| 1193 body = response.read() |
| 1194 boto.log.debug(body) |
| 1195 if response.status == 204: |
| 1196 return True |
| 1197 else: |
| 1198 raise self.connection.provider.storage_response_error( |
| 1199 response.status, response.reason, body) |
| 1200 |
| 890 def configure_website(self, suffix, error_key='', headers=None): | 1201 def configure_website(self, suffix, error_key='', headers=None): |
| 891 """ | 1202 """ |
| 892 Configure this bucket to act as a website | 1203 Configure this bucket to act as a website |
| 893 | 1204 |
| 894 :type suffix: str | 1205 :type suffix: str |
| 895 :param suffix: Suffix that is appended to a request that is for a | 1206 :param suffix: Suffix that is appended to a request that is for a |
| 896 "directory" on the website endpoint (e.g. if the suffix | 1207 "directory" on the website endpoint (e.g. if the suffix |
| 897 is index.html and you make a request to | 1208 is index.html and you make a request to |
| 898 samplebucket/images/ the data that is returned will | 1209 samplebucket/images/ the data that is returned will |
| 899 be for the object with the key name images/index.html). | 1210 be for the object with the key name images/index.html). |
| (...skipping 21 matching lines...) Expand all Loading... |
| 921 response.status, response.reason, body) | 1232 response.status, response.reason, body) |
| 922 | 1233 |
| 923 def get_website_configuration(self, headers=None): | 1234 def get_website_configuration(self, headers=None): |
| 924 """ | 1235 """ |
| 925 Returns the current status of website configuration on the bucket. | 1236 Returns the current status of website configuration on the bucket. |
| 926 | 1237 |
| 927 :rtype: dict | 1238 :rtype: dict |
| 928 :returns: A dictionary containing a Python representation | 1239 :returns: A dictionary containing a Python representation |
| 929 of the XML response from S3. The overall structure is: | 1240 of the XML response from S3. The overall structure is: |
| 930 | 1241 |
| 931 * WebsiteConfiguration | 1242 * WebsiteConfiguration |
| 932 | 1243 |
| 933 * IndexDocument | 1244 * IndexDocument |
| 934 | 1245 |
| 935 * Suffix : suffix that is appended to request that | 1246 * Suffix : suffix that is appended to request that |
| 936 is for a "directory" on the website endpoint | 1247 is for a "directory" on the website endpoint |
| 937 * ErrorDocument | 1248 * ErrorDocument |
| 938 | 1249 |
| 939 * Key : name of object to serve when an error occurs | 1250 * Key : name of object to serve when an error occurs |
| 940 """ | 1251 """ |
| 941 response = self.connection.make_request('GET', self.name, | 1252 response = self.connection.make_request('GET', self.name, |
| 942 query_args='website', headers=headers) | 1253 query_args='website', headers=headers) |
| 943 body = response.read() | 1254 body = response.read() |
| 944 boto.log.debug(body) | 1255 boto.log.debug(body) |
| 945 if response.status == 200: | 1256 if response.status == 200: |
| 946 e = boto.jsonresponse.Element() | 1257 e = boto.jsonresponse.Element() |
| 947 h = boto.jsonresponse.XmlHandler(e, None) | 1258 h = boto.jsonresponse.XmlHandler(e, None) |
| 948 h.parse(body) | 1259 h.parse(body) |
| 949 return e | 1260 return e |
| (...skipping 20 matching lines...) Expand all Loading... |
| 970 Returns the fully qualified hostname to use is you want to access this | 1281 Returns the fully qualified hostname to use is you want to access this |
| 971 bucket as a website. This doesn't validate whether the bucket has | 1282 bucket as a website. This doesn't validate whether the bucket has |
| 972 been correctly configured as a website or not. | 1283 been correctly configured as a website or not. |
| 973 """ | 1284 """ |
| 974 l = [self.name] | 1285 l = [self.name] |
| 975 l.append(S3WebsiteEndpointTranslate.translate_region(self.get_location()
)) | 1286 l.append(S3WebsiteEndpointTranslate.translate_region(self.get_location()
)) |
| 976 l.append('.'.join(self.connection.host.split('.')[-2:])) | 1287 l.append('.'.join(self.connection.host.split('.')[-2:])) |
| 977 return '.'.join(l) | 1288 return '.'.join(l) |
| 978 | 1289 |
| 979 def get_policy(self, headers=None): | 1290 def get_policy(self, headers=None): |
| 1291 """ |
| 1292 Returns the JSON policy associated with the bucket. The policy |
| 1293 is returned as an uninterpreted JSON string. |
| 1294 """ |
| 980 response = self.connection.make_request('GET', self.name, | 1295 response = self.connection.make_request('GET', self.name, |
| 981 query_args='policy', headers=headers) | 1296 query_args='policy', headers=headers) |
| 982 body = response.read() | 1297 body = response.read() |
| 983 if response.status == 200: | 1298 if response.status == 200: |
| 984 return body | 1299 return body |
| 985 else: | 1300 else: |
| 986 raise self.connection.provider.storage_response_error( | 1301 raise self.connection.provider.storage_response_error( |
| 987 response.status, response.reason, body) | 1302 response.status, response.reason, body) |
| 988 | 1303 |
| 989 def set_policy(self, policy, headers=None): | 1304 def set_policy(self, policy, headers=None): |
| 1305 """ |
| 1306 Add or replace the JSON policy associated with the bucket. |
| 1307 |
| 1308 :type policy: str |
| 1309 :param policy: The JSON policy as a string. |
| 1310 """ |
| 990 response = self.connection.make_request('PUT', self.name, | 1311 response = self.connection.make_request('PUT', self.name, |
| 991 data=policy, | 1312 data=policy, |
| 992 query_args='policy', | 1313 query_args='policy', |
| 993 headers=headers) | 1314 headers=headers) |
| 994 body = response.read() | 1315 body = response.read() |
| 995 if response.status >= 200 and response.status <= 204: | 1316 if response.status >= 200 and response.status <= 204: |
| 996 return True | 1317 return True |
| 997 else: | 1318 else: |
| 998 raise self.connection.provider.storage_response_error( | 1319 raise self.connection.provider.storage_response_error( |
| 999 response.status, response.reason, body) | 1320 response.status, response.reason, body) |
| 1000 | 1321 |
| 1322 def delete_policy(self, headers=None): |
| 1323 response = self.connection.make_request('DELETE', self.name, |
| 1324 data='/?policy', |
| 1325 query_args='policy', |
| 1326 headers=headers) |
| 1327 body = response.read() |
| 1328 if response.status >= 200 and response.status <= 204: |
| 1329 return True |
| 1330 else: |
| 1331 raise self.connection.provider.storage_response_error( |
| 1332 response.status, response.reason, body) |
| 1333 |
| 1334 |
| 1001 def initiate_multipart_upload(self, key_name, headers=None, | 1335 def initiate_multipart_upload(self, key_name, headers=None, |
| 1002 reduced_redundancy=False, metadata=None): | 1336 reduced_redundancy=False, |
| 1337 metadata=None, encrypt_key=False): |
| 1003 """ | 1338 """ |
| 1004 Start a multipart upload operation. | 1339 Start a multipart upload operation. |
| 1005 | 1340 |
| 1006 :type key_name: string | 1341 :type key_name: string |
| 1007 :param key_name: The name of the key that will ultimately result from | 1342 :param key_name: The name of the key that will ultimately result from |
| 1008 this multipart upload operation. This will be exactly | 1343 this multipart upload operation. This will be exactly |
| 1009 as the key appears in the bucket after the upload | 1344 as the key appears in the bucket after the upload |
| 1010 process has been completed. | 1345 process has been completed. |
| 1011 | 1346 |
| 1012 :type headers: dict | 1347 :type headers: dict |
| 1013 :param headers: Additional HTTP headers to send and store with the | 1348 :param headers: Additional HTTP headers to send and store with the |
| 1014 resulting key in S3. | 1349 resulting key in S3. |
| 1015 | 1350 |
| 1016 :type reduced_redundancy: boolean | 1351 :type reduced_redundancy: boolean |
| 1017 :param reduced_redundancy: In multipart uploads, the storage class is | 1352 :param reduced_redundancy: In multipart uploads, the storage class is |
| 1018 specified when initiating the upload, | 1353 specified when initiating the upload, |
| 1019 not when uploading individual parts. So | 1354 not when uploading individual parts. So |
| 1020 if you want the resulting key to use the | 1355 if you want the resulting key to use the |
| 1021 reduced redundancy storage class set this | 1356 reduced redundancy storage class set this |
| 1022 flag when you initiate the upload. | 1357 flag when you initiate the upload. |
| 1023 | 1358 |
| 1024 :type metadata: dict | 1359 :type metadata: dict |
| 1025 :param metadata: Any metadata that you would like to set on the key | 1360 :param metadata: Any metadata that you would like to set on the key |
| 1026 that results from the multipart upload. | 1361 that results from the multipart upload. |
| 1362 |
| 1363 :type encrypt_key: bool |
| 1364 :param encrypt_key: If True, the new copy of the object will |
| 1365 be encrypted on the server-side by S3 and |
| 1366 will be stored in an encrypted form while |
| 1367 at rest in S3. |
| 1027 """ | 1368 """ |
| 1028 query_args = 'uploads' | 1369 query_args = 'uploads' |
| 1370 provider = self.connection.provider |
| 1029 if headers is None: | 1371 if headers is None: |
| 1030 headers = {} | 1372 headers = {} |
| 1031 if reduced_redundancy: | 1373 if reduced_redundancy: |
| 1032 storage_class_header = self.connection.provider.storage_class_header | 1374 storage_class_header = provider.storage_class_header |
| 1033 if storage_class_header: | 1375 if storage_class_header: |
| 1034 headers[storage_class_header] = 'REDUCED_REDUNDANCY' | 1376 headers[storage_class_header] = 'REDUCED_REDUNDANCY' |
| 1035 # TODO: what if the provider doesn't support reduced redundancy? | 1377 # TODO: what if the provider doesn't support reduced redundancy? |
| 1036 # (see boto.s3.key.Key.set_contents_from_file) | 1378 # (see boto.s3.key.Key.set_contents_from_file) |
| 1379 if encrypt_key: |
| 1380 headers[provider.server_side_encryption_header] = 'AES256' |
| 1037 if metadata is None: | 1381 if metadata is None: |
| 1038 metadata = {} | 1382 metadata = {} |
| 1039 | 1383 |
| 1040 headers = boto.utils.merge_meta(headers, metadata, | 1384 headers = boto.utils.merge_meta(headers, metadata, |
| 1041 self.connection.provider) | 1385 self.connection.provider) |
| 1042 response = self.connection.make_request('POST', self.name, key_name, | 1386 response = self.connection.make_request('POST', self.name, key_name, |
| 1043 query_args=query_args, | 1387 query_args=query_args, |
| 1044 headers=headers) | 1388 headers=headers) |
| 1045 body = response.read() | 1389 body = response.read() |
| 1046 boto.log.debug(body) | 1390 boto.log.debug(body) |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1088 query_args=query_args, | 1432 query_args=query_args, |
| 1089 headers=headers) | 1433 headers=headers) |
| 1090 body = response.read() | 1434 body = response.read() |
| 1091 boto.log.debug(body) | 1435 boto.log.debug(body) |
| 1092 if response.status != 204: | 1436 if response.status != 204: |
| 1093 raise self.connection.provider.storage_response_error( | 1437 raise self.connection.provider.storage_response_error( |
| 1094 response.status, response.reason, body) | 1438 response.status, response.reason, body) |
| 1095 | 1439 |
| 1096 def delete(self, headers=None): | 1440 def delete(self, headers=None): |
| 1097 return self.connection.delete_bucket(self.name, headers=headers) | 1441 return self.connection.delete_bucket(self.name, headers=headers) |
| 1098 | |
| OLD | NEW |