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.s3.acl import Policy, CannedACLStrings, Grant |
29 from boto.s3.key import Key | 28 from boto.s3.key import Key |
30 from boto.s3.prefix import Prefix | 29 from boto.s3.prefix import Prefix |
31 from boto.s3.deletemarker import DeleteMarker | 30 from boto.s3.deletemarker import DeleteMarker |
32 from boto.s3.user import User | |
33 from boto.s3.multipart import MultiPartUpload | 31 from boto.s3.multipart import MultiPartUpload |
34 from boto.s3.multipart import CompleteMultiPartUpload | 32 from boto.s3.multipart import CompleteMultiPartUpload |
35 from boto.s3.bucketlistresultset import BucketListResultSet | 33 from boto.s3.bucketlistresultset import BucketListResultSet |
36 from boto.s3.bucketlistresultset import VersionedBucketListResultSet | 34 from boto.s3.bucketlistresultset import VersionedBucketListResultSet |
37 from boto.s3.bucketlistresultset import MultiPartUploadListResultSet | 35 from boto.s3.bucketlistresultset import MultiPartUploadListResultSet |
38 import boto.jsonresponse | 36 import boto.jsonresponse |
39 import boto.utils | 37 import boto.utils |
40 import xml.sax | 38 import xml.sax |
41 import urllib | 39 import urllib |
42 import re | 40 import re |
43 from collections import defaultdict | 41 from collections import defaultdict |
44 | 42 |
45 # as per http://goo.gl/BDuud (02/19/2011) | 43 # as per http://goo.gl/BDuud (02/19/2011) |
46 class S3WebsiteEndpointTranslate: | 44 class S3WebsiteEndpointTranslate: |
47 trans_region = defaultdict(lambda :'s3-website-us-east-1') | 45 trans_region = defaultdict(lambda :'s3-website-us-east-1') |
48 | 46 |
49 trans_region['EU'] = 's3-website-eu-west-1' | 47 trans_region['EU'] = 's3-website-eu-west-1' |
50 trans_region['us-west-1'] = 's3-website-us-west-1' | 48 trans_region['us-west-1'] = 's3-website-us-west-1' |
| 49 trans_region['ap-northeast-1'] = 's3-website-ap-northeast-1' |
51 trans_region['ap-southeast-1'] = 's3-website-ap-southeast-1' | 50 trans_region['ap-southeast-1'] = 's3-website-ap-southeast-1' |
52 | 51 |
53 @classmethod | 52 @classmethod |
54 def translate_region(self, reg): | 53 def translate_region(self, reg): |
55 return self.trans_region[reg] | 54 return self.trans_region[reg] |
56 | 55 |
57 S3Permissions = ['READ', 'WRITE', 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL'] | 56 S3Permissions = ['READ', 'WRITE', 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL'] |
58 | 57 |
59 class Bucket(object): | 58 class Bucket(object): |
60 | 59 |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
99 self.connection = connection | 98 self.connection = connection |
100 self.key_class = key_class | 99 self.key_class = key_class |
101 | 100 |
102 def __repr__(self): | 101 def __repr__(self): |
103 return '<Bucket: %s>' % self.name | 102 return '<Bucket: %s>' % self.name |
104 | 103 |
105 def __iter__(self): | 104 def __iter__(self): |
106 return iter(BucketListResultSet(self)) | 105 return iter(BucketListResultSet(self)) |
107 | 106 |
108 def __contains__(self, key_name): | 107 def __contains__(self, key_name): |
109 return not (self.get_key(key_name) is None) | 108 return not (self.get_key(key_name) is None) |
110 | 109 |
111 def startElement(self, name, attrs, connection): | 110 def startElement(self, name, attrs, connection): |
112 return None | 111 return None |
113 | 112 |
114 def endElement(self, name, value, connection): | 113 def endElement(self, name, value, connection): |
115 if name == 'Name': | 114 if name == 'Name': |
116 self.name = value | 115 self.name = value |
117 elif name == 'CreationDate': | 116 elif name == 'CreationDate': |
118 self.creation_date = value | 117 self.creation_date = value |
119 else: | 118 else: |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
168 # support Range gets, which return status 206: | 167 # support Range gets, which return status 206: |
169 if response.status/100 == 2: | 168 if response.status/100 == 2: |
170 response.read() | 169 response.read() |
171 k = self.key_class(self) | 170 k = self.key_class(self) |
172 provider = self.connection.provider | 171 provider = self.connection.provider |
173 k.metadata = boto.utils.get_aws_metadata(response.msg, provider) | 172 k.metadata = boto.utils.get_aws_metadata(response.msg, provider) |
174 k.etag = response.getheader('etag') | 173 k.etag = response.getheader('etag') |
175 k.content_type = response.getheader('content-type') | 174 k.content_type = response.getheader('content-type') |
176 k.content_encoding = response.getheader('content-encoding') | 175 k.content_encoding = response.getheader('content-encoding') |
177 k.last_modified = response.getheader('last-modified') | 176 k.last_modified = response.getheader('last-modified') |
178 k.size = int(response.getheader('content-length')) | 177 # the following machinations are a workaround to the fact that |
| 178 # apache/fastcgi omits the content-length header on HEAD |
| 179 # requests when the content-length is zero. |
| 180 # See http://goo.gl/0Tdax for more details. |
| 181 clen = response.getheader('content-length') |
| 182 if clen: |
| 183 k.size = int(response.getheader('content-length')) |
| 184 else: |
| 185 k.size = 0 |
179 k.cache_control = response.getheader('cache-control') | 186 k.cache_control = response.getheader('cache-control') |
180 k.name = key_name | 187 k.name = key_name |
181 k.handle_version_headers(response) | 188 k.handle_version_headers(response) |
| 189 k.handle_encryption_headers(response) |
182 return k | 190 return k |
183 else: | 191 else: |
184 if response.status == 404: | 192 if response.status == 404: |
185 response.read() | 193 response.read() |
186 return None | 194 return None |
187 else: | 195 else: |
188 raise self.connection.provider.storage_response_error( | 196 raise self.connection.provider.storage_response_error( |
189 response.status, response.reason, '') | 197 response.status, response.reason, '') |
190 | 198 |
191 def list(self, prefix='', delimiter='', marker='', headers=None): | 199 def list(self, prefix='', delimiter='', marker='', headers=None): |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
274 :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet` | 282 :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet` |
275 :return: an instance of a BucketListResultSet that handles paging, etc | 283 :return: an instance of a BucketListResultSet that handles paging, etc |
276 """ | 284 """ |
277 return MultiPartUploadListResultSet(self, key_marker, | 285 return MultiPartUploadListResultSet(self, key_marker, |
278 upload_id_marker, | 286 upload_id_marker, |
279 headers) | 287 headers) |
280 | 288 |
281 def _get_all(self, element_map, initial_query_string='', | 289 def _get_all(self, element_map, initial_query_string='', |
282 headers=None, **params): | 290 headers=None, **params): |
283 l = [] | 291 l = [] |
284 for k,v in params.items(): | 292 for k, v in params.items(): |
285 k = k.replace('_', '-') | 293 k = k.replace('_', '-') |
286 if k == 'maxkeys': | 294 if k == 'maxkeys': |
287 k = 'max-keys' | 295 k = 'max-keys' |
288 if isinstance(v, unicode): | 296 if isinstance(v, unicode): |
289 v = v.encode('utf-8') | 297 v = v.encode('utf-8') |
290 if v is not None and v != '': | 298 if v is not None and v != '': |
291 l.append('%s=%s' % (urllib.quote(k), urllib.quote(str(v)))) | 299 l.append('%s=%s' % (urllib.quote(k), urllib.quote(str(v)))) |
292 if len(l): | 300 if len(l): |
293 s = initial_query_string + '&' + '&'.join(l) | 301 s = initial_query_string + '&' + '&'.join(l) |
294 else: | 302 else: |
295 s = initial_query_string | 303 s = initial_query_string |
296 response = self.connection.make_request('GET', self.name, | 304 response = self.connection.make_request('GET', self.name, |
297 headers=headers, query_args=s) | 305 headers=headers, |
| 306 query_args=s) |
298 body = response.read() | 307 body = response.read() |
299 boto.log.debug(body) | 308 boto.log.debug(body) |
300 if response.status == 200: | 309 if response.status == 200: |
301 rs = ResultSet(element_map) | 310 rs = ResultSet(element_map) |
302 h = handler.XmlHandler(rs, self) | 311 h = handler.XmlHandler(rs, self) |
303 xml.sax.parseString(body, h) | 312 xml.sax.parseString(body, h) |
304 return rs | 313 return rs |
305 else: | 314 else: |
306 raise self.connection.provider.storage_response_error( | 315 raise self.connection.provider.storage_response_error( |
307 response.status, response.reason, body) | 316 response.status, response.reason, body) |
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
427 Creates a new key | 436 Creates a new key |
428 | 437 |
429 :type key_name: string | 438 :type key_name: string |
430 :param key_name: The name of the key to create | 439 :param key_name: The name of the key to create |
431 | 440 |
432 :rtype: :class:`boto.s3.key.Key` or subclass | 441 :rtype: :class:`boto.s3.key.Key` or subclass |
433 :returns: An instance of the newly created key object | 442 :returns: An instance of the newly created key object |
434 """ | 443 """ |
435 return self.key_class(self, key_name) | 444 return self.key_class(self, key_name) |
436 | 445 |
437 def generate_url(self, expires_in, method='GET', | 446 def generate_url(self, expires_in, method='GET', headers=None, |
438 headers=None, force_http=False): | 447 force_http=False, response_headers=None): |
439 return self.connection.generate_url(expires_in, method, self.name, | 448 return self.connection.generate_url(expires_in, method, self.name, |
440 headers=headers, | 449 headers=headers, |
441 force_http=force_http) | 450 force_http=force_http, |
| 451 response_headers=response_headers) |
442 | 452 |
443 def delete_key(self, key_name, headers=None, | 453 def delete_key(self, key_name, headers=None, |
444 version_id=None, mfa_token=None): | 454 version_id=None, mfa_token=None): |
445 """ | 455 """ |
446 Deletes a key from the bucket. If a version_id is provided, | 456 Deletes a key from the bucket. If a version_id is provided, |
447 only that version of the key will be deleted. | 457 only that version of the key will be deleted. |
448 | 458 |
449 :type key_name: string | 459 :type key_name: string |
450 :param key_name: The key name to delete | 460 :param key_name: The key name to delete |
451 | 461 |
(...skipping 20 matching lines...) Expand all Loading... |
472 response = self.connection.make_request('DELETE', self.name, key_name, | 482 response = self.connection.make_request('DELETE', self.name, key_name, |
473 headers=headers, | 483 headers=headers, |
474 query_args=query_args) | 484 query_args=query_args) |
475 body = response.read() | 485 body = response.read() |
476 if response.status != 204: | 486 if response.status != 204: |
477 raise provider.storage_response_error(response.status, | 487 raise provider.storage_response_error(response.status, |
478 response.reason, body) | 488 response.reason, body) |
479 | 489 |
480 def copy_key(self, new_key_name, src_bucket_name, | 490 def copy_key(self, new_key_name, src_bucket_name, |
481 src_key_name, metadata=None, src_version_id=None, | 491 src_key_name, metadata=None, src_version_id=None, |
482 storage_class='STANDARD', preserve_acl=False): | 492 storage_class='STANDARD', preserve_acl=False, |
| 493 encrypt_key=False): |
483 """ | 494 """ |
484 Create a new key in the bucket by copying another existing key. | 495 Create a new key in the bucket by copying another existing key. |
485 | 496 |
486 :type new_key_name: string | 497 :type new_key_name: string |
487 :param new_key_name: The name of the new key | 498 :param new_key_name: The name of the new key |
488 | 499 |
489 :type src_bucket_name: string | 500 :type src_bucket_name: string |
490 :param src_bucket_name: The name of the source bucket | 501 :param src_bucket_name: The name of the source bucket |
491 | 502 |
492 :type src_key_name: string | 503 :type src_key_name: string |
(...skipping 24 matching lines...) Expand all Loading... |
517 will have the default ACL. | 528 will have the default ACL. |
518 Note that preserving the ACL in the | 529 Note that preserving the ACL in the |
519 new key object will require two | 530 new key object will require two |
520 additional API calls to S3, one to | 531 additional API calls to S3, one to |
521 retrieve the current ACL and one to | 532 retrieve the current ACL and one to |
522 set that ACL on the new object. If | 533 set that ACL on the new object. If |
523 you don't care about the ACL, a value | 534 you don't care about the ACL, a value |
524 of False will be significantly more | 535 of False will be significantly more |
525 efficient. | 536 efficient. |
526 | 537 |
| 538 :type encrypt_key: bool |
| 539 :param encrypt_key: If True, the new copy of the object will |
| 540 be encrypted on the server-side by S3 and |
| 541 will be stored in an encrypted form while |
| 542 at rest in S3. |
| 543 |
527 :rtype: :class:`boto.s3.key.Key` or subclass | 544 :rtype: :class:`boto.s3.key.Key` or subclass |
528 :returns: An instance of the newly created key object | 545 :returns: An instance of the newly created key object |
529 """ | 546 """ |
| 547 headers = {} |
| 548 provider = self.connection.provider |
| 549 src_key_name = boto.utils.get_utf8_value(src_key_name) |
530 if preserve_acl: | 550 if preserve_acl: |
531 acl = self.get_xml_acl(src_key_name) | 551 if self.name == src_bucket_name: |
| 552 src_bucket = self |
| 553 else: |
| 554 src_bucket = self.connection.get_bucket(src_bucket_name) |
| 555 acl = src_bucket.get_xml_acl(src_key_name) |
| 556 if encrypt_key: |
| 557 headers[provider.server_side_encryption_header] = 'AES256' |
532 src = '%s/%s' % (src_bucket_name, urllib.quote(src_key_name)) | 558 src = '%s/%s' % (src_bucket_name, urllib.quote(src_key_name)) |
533 if src_version_id: | 559 if src_version_id: |
534 src += '?version_id=%s' % src_version_id | 560 src += '?version_id=%s' % src_version_id |
535 provider = self.connection.provider | 561 headers = {provider.copy_source_header : str(src)} |
536 headers = {provider.copy_source_header : src} | 562 headers[provider.storage_class_header] = storage_class |
537 if storage_class != 'STANDARD': | |
538 headers[provider.storage_class_header] = storage_class | |
539 if metadata: | 563 if metadata: |
540 headers[provider.metadata_directive_header] = 'REPLACE' | 564 headers[provider.metadata_directive_header] = 'REPLACE' |
541 headers = boto.utils.merge_meta(headers, metadata) | 565 headers = boto.utils.merge_meta(headers, metadata, provider) |
542 else: | 566 else: |
543 headers[provider.metadata_directive_header] = 'COPY' | 567 headers[provider.metadata_directive_header] = 'COPY' |
544 response = self.connection.make_request('PUT', self.name, new_key_name, | 568 response = self.connection.make_request('PUT', self.name, new_key_name, |
545 headers=headers) | 569 headers=headers) |
546 body = response.read() | 570 body = response.read() |
547 if response.status == 200: | 571 if response.status == 200: |
548 key = self.new_key(new_key_name) | 572 key = self.new_key(new_key_name) |
549 h = handler.XmlHandler(key, self) | 573 h = handler.XmlHandler(key, self) |
550 xml.sax.parseString(body, h) | 574 xml.sax.parseString(body, h) |
551 if hasattr(key, 'Error'): | 575 if hasattr(key, 'Error'): |
552 raise provider.storage_copy_error(key.Code, key.Message, body) | 576 raise provider.storage_copy_error(key.Code, key.Message, body) |
553 key.handle_version_headers(response) | 577 key.handle_version_headers(response) |
554 if preserve_acl: | 578 if preserve_acl: |
555 self.set_xml_acl(acl, new_key_name) | 579 self.set_xml_acl(acl, new_key_name) |
556 return key | 580 return key |
557 else: | 581 else: |
558 raise provider.storage_response_error(response.status, response.reas
on, body) | 582 raise provider.storage_response_error(response.status, |
| 583 response.reason, body) |
559 | 584 |
560 def set_canned_acl(self, acl_str, key_name='', headers=None, | 585 def set_canned_acl(self, acl_str, key_name='', headers=None, |
561 version_id=None): | 586 version_id=None): |
562 assert acl_str in CannedACLStrings | 587 assert acl_str in CannedACLStrings |
563 | 588 |
564 if headers: | 589 if headers: |
565 headers[self.connection.provider.acl_header] = acl_str | 590 headers[self.connection.provider.acl_header] = acl_str |
566 else: | 591 else: |
567 headers={self.connection.provider.acl_header: acl_str} | 592 headers={self.connection.provider.acl_header: acl_str} |
568 | 593 |
569 query_args='acl' | 594 query_args = 'acl' |
570 if version_id: | 595 if version_id: |
571 query_args += '&versionId=%s' % version_id | 596 query_args += '&versionId=%s' % version_id |
572 response = self.connection.make_request('PUT', self.name, key_name, | 597 response = self.connection.make_request('PUT', self.name, key_name, |
573 headers=headers, query_args=query_args) | 598 headers=headers, query_args=query_args) |
574 body = response.read() | 599 body = response.read() |
575 if response.status != 200: | 600 if response.status != 200: |
576 raise self.connection.provider.storage_response_error( | 601 raise self.connection.provider.storage_response_error( |
577 response.status, response.reason, body) | 602 response.status, response.reason, body) |
578 | 603 |
579 def get_xml_acl(self, key_name='', headers=None, version_id=None): | 604 def get_xml_acl(self, key_name='', headers=None, version_id=None): |
580 query_args = 'acl' | 605 query_args = 'acl' |
581 if version_id: | 606 if version_id: |
582 query_args += '&versionId=%s' % version_id | 607 query_args += '&versionId=%s' % version_id |
583 response = self.connection.make_request('GET', self.name, key_name, | 608 response = self.connection.make_request('GET', self.name, key_name, |
584 query_args=query_args, | 609 query_args=query_args, |
585 headers=headers) | 610 headers=headers) |
586 body = response.read() | 611 body = response.read() |
587 if response.status != 200: | 612 if response.status != 200: |
588 raise self.connection.provider.storage_response_error( | 613 raise self.connection.provider.storage_response_error( |
589 response.status, response.reason, body) | 614 response.status, response.reason, body) |
590 return body | 615 return body |
591 | 616 |
592 def set_xml_acl(self, acl_str, key_name='', headers=None, version_id=None): | 617 def set_xml_acl(self, acl_str, key_name='', headers=None, version_id=None): |
593 query_args = 'acl' | 618 query_args = 'acl' |
594 if version_id: | 619 if version_id: |
595 query_args += '&versionId=%s' % version_id | 620 query_args += '&versionId=%s' % version_id |
596 response = self.connection.make_request('PUT', self.name, key_name, | 621 response = self.connection.make_request('PUT', self.name, key_name, |
597 data=acl_str, | 622 data=acl_str.encode('ISO-8859-1'
), |
598 query_args=query_args, | 623 query_args=query_args, |
599 headers=headers) | 624 headers=headers) |
600 body = response.read() | 625 body = response.read() |
601 if response.status != 200: | 626 if response.status != 200: |
602 raise self.connection.provider.storage_response_error( | 627 raise self.connection.provider.storage_response_error( |
603 response.status, response.reason, body) | 628 response.status, response.reason, body) |
604 | 629 |
605 def set_acl(self, acl_or_str, key_name='', headers=None, version_id=None): | 630 def set_acl(self, acl_or_str, key_name='', headers=None, version_id=None): |
606 if isinstance(acl_or_str, Policy): | 631 if isinstance(acl_or_str, Policy): |
607 self.set_xml_acl(acl_or_str.to_xml(), key_name, | 632 self.set_xml_acl(acl_or_str.to_xml(), key_name, |
(...skipping 12 matching lines...) Expand all Loading... |
620 body = response.read() | 645 body = response.read() |
621 if response.status == 200: | 646 if response.status == 200: |
622 policy = Policy(self) | 647 policy = Policy(self) |
623 h = handler.XmlHandler(policy, self) | 648 h = handler.XmlHandler(policy, self) |
624 xml.sax.parseString(body, h) | 649 xml.sax.parseString(body, h) |
625 return policy | 650 return policy |
626 else: | 651 else: |
627 raise self.connection.provider.storage_response_error( | 652 raise self.connection.provider.storage_response_error( |
628 response.status, response.reason, body) | 653 response.status, response.reason, body) |
629 | 654 |
| 655 def set_subresource(self, subresource, value, key_name = '', headers=None, |
| 656 version_id=None): |
| 657 """ |
| 658 Set a subresource for a bucket or key. |
| 659 |
| 660 :type subresource: string |
| 661 :param subresource: The subresource to set. |
| 662 |
| 663 :type value: string |
| 664 :param value: The value of the subresource. |
| 665 |
| 666 :type key_name: string |
| 667 :param key_name: The key to operate on, or None to operate on the |
| 668 bucket. |
| 669 |
| 670 :type headers: dict |
| 671 :param headers: Additional HTTP headers to include in the request. |
| 672 |
| 673 :type src_version_id: string |
| 674 :param src_version_id: Optional. The version id of the key to operate |
| 675 on. If not specified, operate on the newest |
| 676 version. |
| 677 """ |
| 678 if not subresource: |
| 679 raise TypeError('set_subresource called with subresource=None') |
| 680 query_args = subresource |
| 681 if version_id: |
| 682 query_args += '&versionId=%s' % version_id |
| 683 response = self.connection.make_request('PUT', self.name, key_name, |
| 684 data=value.encode('UTF-8'), |
| 685 query_args=query_args, |
| 686 headers=headers) |
| 687 body = response.read() |
| 688 if response.status != 200: |
| 689 raise self.connection.provider.storage_response_error( |
| 690 response.status, response.reason, body) |
| 691 |
| 692 def get_subresource(self, subresource, key_name='', headers=None, |
| 693 version_id=None): |
| 694 """ |
| 695 Get a subresource for a bucket or key. |
| 696 |
| 697 :type subresource: string |
| 698 :param subresource: The subresource to get. |
| 699 |
| 700 :type key_name: string |
| 701 :param key_name: The key to operate on, or None to operate on the |
| 702 bucket. |
| 703 |
| 704 :type headers: dict |
| 705 :param headers: Additional HTTP headers to include in the request. |
| 706 |
| 707 :type src_version_id: string |
| 708 :param src_version_id: Optional. The version id of the key to operate |
| 709 on. If not specified, operate on the newest |
| 710 version. |
| 711 |
| 712 :rtype: string |
| 713 :returns: The value of the subresource. |
| 714 """ |
| 715 if not subresource: |
| 716 raise TypeError('get_subresource called with subresource=None') |
| 717 query_args = subresource |
| 718 if version_id: |
| 719 query_args += '&versionId=%s' % version_id |
| 720 response = self.connection.make_request('GET', self.name, key_name, |
| 721 query_args=query_args, |
| 722 headers=headers) |
| 723 body = response.read() |
| 724 if response.status != 200: |
| 725 raise self.connection.provider.storage_response_error( |
| 726 response.status, response.reason, body) |
| 727 return body |
| 728 |
630 def make_public(self, recursive=False, headers=None): | 729 def make_public(self, recursive=False, headers=None): |
631 self.set_canned_acl('public-read', headers=headers) | 730 self.set_canned_acl('public-read', headers=headers) |
632 if recursive: | 731 if recursive: |
633 for key in self: | 732 for key in self: |
634 self.set_canned_acl('public-read', key.name, headers=headers) | 733 self.set_canned_acl('public-read', key.name, headers=headers) |
635 | 734 |
636 def add_email_grant(self, permission, email_address, | 735 def add_email_grant(self, permission, email_address, |
637 recursive=False, headers=None): | 736 recursive=False, headers=None): |
638 """ | 737 """ |
639 Convenience method that provides a quick way to add an email grant | 738 Convenience method that provides a quick way to add an email grant |
(...skipping 21 matching lines...) Expand all Loading... |
661 if permission not in S3Permissions: | 760 if permission not in S3Permissions: |
662 raise self.connection.provider.storage_permissions_error( | 761 raise self.connection.provider.storage_permissions_error( |
663 'Unknown Permission: %s' % permission) | 762 'Unknown Permission: %s' % permission) |
664 policy = self.get_acl(headers=headers) | 763 policy = self.get_acl(headers=headers) |
665 policy.acl.add_email_grant(permission, email_address) | 764 policy.acl.add_email_grant(permission, email_address) |
666 self.set_acl(policy, headers=headers) | 765 self.set_acl(policy, headers=headers) |
667 if recursive: | 766 if recursive: |
668 for key in self: | 767 for key in self: |
669 key.add_email_grant(permission, email_address, headers=headers) | 768 key.add_email_grant(permission, email_address, headers=headers) |
670 | 769 |
671 def add_user_grant(self, permission, user_id, | 770 def add_user_grant(self, permission, user_id, recursive=False, |
672 recursive=False, headers=None): | 771 headers=None, display_name=None): |
673 """ | 772 """ |
674 Convenience method that provides a quick way to add a canonical | 773 Convenience method that provides a quick way to add a canonical |
675 user grant to a bucket. This method retrieves the current ACL, | 774 user grant to a bucket. This method retrieves the current ACL, |
676 creates a new grant based on the parameters passed in, adds that | 775 creates a new grant based on the parameters passed in, adds that |
677 grant to the ACL and then PUT's the new ACL back to S3. | 776 grant to the ACL and then PUT's the new ACL back to S3. |
678 | 777 |
679 :type permission: string | 778 :type permission: string |
680 :param permission: The permission being granted. Should be one of: | 779 :param permission: The permission being granted. Should be one of: |
681 (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL). | 780 (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL). |
682 | 781 |
683 :type user_id: string | 782 :type user_id: string |
684 :param user_id: The canonical user id associated with the AWS | 783 :param user_id: The canonical user id associated with the AWS |
685 account your are granting the permission to. | 784 account your are granting the permission to. |
686 | 785 |
687 :type recursive: boolean | 786 :type recursive: boolean |
688 :param recursive: A boolean value to controls whether the command | 787 :param recursive: A boolean value to controls whether the command |
689 will apply the grant to all keys within the bucket | 788 will apply the grant to all keys within the bucket |
690 or not. The default value is False. By passing a | 789 or not. The default value is False. By passing a |
691 True value, the call will iterate through all keys | 790 True value, the call will iterate through all keys |
692 in the bucket and apply the same grant to each key. | 791 in the bucket and apply the same grant to each key. |
693 CAUTION: If you have a lot of keys, this could take | 792 CAUTION: If you have a lot of keys, this could take |
694 a long time! | 793 a long time! |
| 794 |
| 795 :type display_name: string |
| 796 :param display_name: An option string containing the user's |
| 797 Display Name. Only required on Walrus. |
695 """ | 798 """ |
696 if permission not in S3Permissions: | 799 if permission not in S3Permissions: |
697 raise self.connection.provider.storage_permissions_error( | 800 raise self.connection.provider.storage_permissions_error( |
698 'Unknown Permission: %s' % permission) | 801 'Unknown Permission: %s' % permission) |
699 policy = self.get_acl(headers=headers) | 802 policy = self.get_acl(headers=headers) |
700 policy.acl.add_user_grant(permission, user_id) | 803 policy.acl.add_user_grant(permission, user_id, |
| 804 display_name=display_name) |
701 self.set_acl(policy, headers=headers) | 805 self.set_acl(policy, headers=headers) |
702 if recursive: | 806 if recursive: |
703 for key in self: | 807 for key in self: |
704 key.add_user_grant(permission, user_id, headers=headers) | 808 key.add_user_grant(permission, user_id, headers=headers, |
| 809 display_name=display_name) |
705 | 810 |
706 def list_grants(self, headers=None): | 811 def list_grants(self, headers=None): |
707 policy = self.get_acl(headers=headers) | 812 policy = self.get_acl(headers=headers) |
708 return policy.acl.grants | 813 return policy.acl.grants |
709 | 814 |
710 def get_location(self): | 815 def get_location(self): |
711 """ | 816 """ |
712 Returns the LocationConstraint for the bucket. | 817 Returns the LocationConstraint for the bucket. |
713 | 818 |
714 :rtype: str | 819 :rtype: str |
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
788 if response.status == 200: | 893 if response.status == 200: |
789 return True | 894 return True |
790 else: | 895 else: |
791 raise self.connection.provider.storage_response_error( | 896 raise self.connection.provider.storage_response_error( |
792 response.status, response.reason, body) | 897 response.status, response.reason, body) |
793 | 898 |
794 def configure_versioning(self, versioning, mfa_delete=False, | 899 def configure_versioning(self, versioning, mfa_delete=False, |
795 mfa_token=None, headers=None): | 900 mfa_token=None, headers=None): |
796 """ | 901 """ |
797 Configure versioning for this bucket. | 902 Configure versioning for this bucket. |
798 Note: This feature is currently in beta release and is available | 903 |
799 only in the Northern California region. | 904 ..note:: This feature is currently in beta. |
800 | 905 |
801 :type versioning: bool | 906 :type versioning: bool |
802 :param versioning: A boolean indicating whether version is | 907 :param versioning: A boolean indicating whether version is |
803 enabled (True) or disabled (False). | 908 enabled (True) or disabled (False). |
804 | 909 |
805 :type mfa_delete: bool | 910 :type mfa_delete: bool |
806 :param mfa_delete: A boolean indicating whether the Multi-Factor | 911 :param mfa_delete: A boolean indicating whether the Multi-Factor |
807 Authentication Delete feature is enabled (True) | 912 Authentication Delete feature is enabled (True) |
808 or disabled (False). If mfa_delete is enabled | 913 or disabled (False). If mfa_delete is enabled |
809 then all Delete operations will require the | 914 then all Delete operations will require the |
810 token from your MFA device to be passed in | 915 token from your MFA device to be passed in |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
902 else: | 1007 else: |
903 raise self.connection.provider.storage_response_error( | 1008 raise self.connection.provider.storage_response_error( |
904 response.status, response.reason, body) | 1009 response.status, response.reason, body) |
905 | 1010 |
906 def get_website_configuration(self, headers=None): | 1011 def get_website_configuration(self, headers=None): |
907 """ | 1012 """ |
908 Returns the current status of website configuration on the bucket. | 1013 Returns the current status of website configuration on the bucket. |
909 | 1014 |
910 :rtype: dict | 1015 :rtype: dict |
911 :returns: A dictionary containing a Python representation | 1016 :returns: A dictionary containing a Python representation |
912 of the XML response from S3. The overall structure is: | 1017 of the XML response from S3. The overall structure is: |
913 | 1018 |
914 * WebsiteConfiguration | 1019 * WebsiteConfiguration |
915 * IndexDocument | 1020 |
916 * Suffix : suffix that is appended to request that | 1021 * IndexDocument |
917 is for a "directory" on the website endpoint | 1022 |
918 * ErrorDocument | 1023 * Suffix : suffix that is appended to request that |
919 * Key : name of object to serve when an error occurs | 1024 is for a "directory" on the website endpoint |
| 1025 * ErrorDocument |
| 1026 |
| 1027 * Key : name of object to serve when an error occurs |
920 """ | 1028 """ |
921 response = self.connection.make_request('GET', self.name, | 1029 response = self.connection.make_request('GET', self.name, |
922 query_args='website', headers=headers) | 1030 query_args='website', headers=headers) |
923 body = response.read() | 1031 body = response.read() |
924 boto.log.debug(body) | 1032 boto.log.debug(body) |
925 if response.status == 200: | 1033 if response.status == 200: |
926 e = boto.jsonresponse.Element() | 1034 e = boto.jsonresponse.Element() |
927 h = boto.jsonresponse.XmlHandler(e, None) | 1035 h = boto.jsonresponse.XmlHandler(e, None) |
928 h.parse(body) | 1036 h.parse(body) |
929 return e | 1037 return e |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
971 data=policy, | 1079 data=policy, |
972 query_args='policy', | 1080 query_args='policy', |
973 headers=headers) | 1081 headers=headers) |
974 body = response.read() | 1082 body = response.read() |
975 if response.status >= 200 and response.status <= 204: | 1083 if response.status >= 200 and response.status <= 204: |
976 return True | 1084 return True |
977 else: | 1085 else: |
978 raise self.connection.provider.storage_response_error( | 1086 raise self.connection.provider.storage_response_error( |
979 response.status, response.reason, body) | 1087 response.status, response.reason, body) |
980 | 1088 |
981 def initiate_multipart_upload(self, key_name, headers=None): | 1089 def delete_policy(self, headers=None): |
| 1090 response = self.connection.make_request('DELETE', self.name, |
| 1091 data='/?policy', |
| 1092 query_args='policy', |
| 1093 headers=headers) |
| 1094 body = response.read() |
| 1095 if response.status >= 200 and response.status <= 204: |
| 1096 return True |
| 1097 else: |
| 1098 raise self.connection.provider.storage_response_error( |
| 1099 response.status, response.reason, body) |
| 1100 |
| 1101 |
| 1102 def initiate_multipart_upload(self, key_name, headers=None, |
| 1103 reduced_redundancy=False, |
| 1104 metadata=None, encrypt_key=False): |
| 1105 """ |
| 1106 Start a multipart upload operation. |
| 1107 |
| 1108 :type key_name: string |
| 1109 :param key_name: The name of the key that will ultimately result from |
| 1110 this multipart upload operation. This will be exactly |
| 1111 as the key appears in the bucket after the upload |
| 1112 process has been completed. |
| 1113 |
| 1114 :type headers: dict |
| 1115 :param headers: Additional HTTP headers to send and store with the |
| 1116 resulting key in S3. |
| 1117 |
| 1118 :type reduced_redundancy: boolean |
| 1119 :param reduced_redundancy: In multipart uploads, the storage class is |
| 1120 specified when initiating the upload, |
| 1121 not when uploading individual parts. So |
| 1122 if you want the resulting key to use the |
| 1123 reduced redundancy storage class set this |
| 1124 flag when you initiate the upload. |
| 1125 |
| 1126 :type metadata: dict |
| 1127 :param metadata: Any metadata that you would like to set on the key |
| 1128 that results from the multipart upload. |
| 1129 |
| 1130 :type encrypt_key: bool |
| 1131 :param encrypt_key: If True, the new copy of the object will |
| 1132 be encrypted on the server-side by S3 and |
| 1133 will be stored in an encrypted form while |
| 1134 at rest in S3. |
| 1135 """ |
982 query_args = 'uploads' | 1136 query_args = 'uploads' |
| 1137 provider = self.connection.provider |
| 1138 if headers is None: |
| 1139 headers = {} |
| 1140 if reduced_redundancy: |
| 1141 storage_class_header = provider.storage_class_header |
| 1142 if storage_class_header: |
| 1143 headers[storage_class_header] = 'REDUCED_REDUNDANCY' |
| 1144 # TODO: what if the provider doesn't support reduced redundancy? |
| 1145 # (see boto.s3.key.Key.set_contents_from_file) |
| 1146 if encrypt_key: |
| 1147 headers[provider.server_side_encryption_header] = 'AES256' |
| 1148 if metadata is None: |
| 1149 metadata = {} |
| 1150 |
| 1151 headers = boto.utils.merge_meta(headers, metadata, |
| 1152 self.connection.provider) |
983 response = self.connection.make_request('POST', self.name, key_name, | 1153 response = self.connection.make_request('POST', self.name, key_name, |
984 query_args=query_args, | 1154 query_args=query_args, |
985 headers=headers) | 1155 headers=headers) |
986 body = response.read() | 1156 body = response.read() |
987 boto.log.debug(body) | 1157 boto.log.debug(body) |
988 if response.status == 200: | 1158 if response.status == 200: |
989 resp = MultiPartUpload(self) | 1159 resp = MultiPartUpload(self) |
990 h = handler.XmlHandler(resp, self) | 1160 h = handler.XmlHandler(resp, self) |
991 xml.sax.parseString(body, h) | 1161 xml.sax.parseString(body, h) |
992 return resp | 1162 return resp |
993 else: | 1163 else: |
994 raise self.connection.provider.storage_response_error( | 1164 raise self.connection.provider.storage_response_error( |
995 response.status, response.reason, body) | 1165 response.status, response.reason, body) |
996 | 1166 |
997 def complete_multipart_upload(self, key_name, upload_id, | 1167 def complete_multipart_upload(self, key_name, upload_id, |
998 xml_body, headers=None): | 1168 xml_body, headers=None): |
| 1169 """ |
| 1170 Complete a multipart upload operation. |
| 1171 """ |
999 query_args = 'uploadId=%s' % upload_id | 1172 query_args = 'uploadId=%s' % upload_id |
1000 if headers is None: | 1173 if headers is None: |
1001 headers = {} | 1174 headers = {} |
1002 headers['Content-Type'] = 'text/xml' | 1175 headers['Content-Type'] = 'text/xml' |
1003 response = self.connection.make_request('POST', self.name, key_name, | 1176 response = self.connection.make_request('POST', self.name, key_name, |
1004 query_args=query_args, | 1177 query_args=query_args, |
1005 headers=headers, data=xml_body) | 1178 headers=headers, data=xml_body) |
| 1179 contains_error = False |
1006 body = response.read() | 1180 body = response.read() |
| 1181 # Some errors will be reported in the body of the response |
| 1182 # even though the HTTP response code is 200. This check |
| 1183 # does a quick and dirty peek in the body for an error element. |
| 1184 if body.find('<Error>') > 0: |
| 1185 contains_error = True |
1007 boto.log.debug(body) | 1186 boto.log.debug(body) |
1008 if response.status == 200: | 1187 if response.status == 200 and not contains_error: |
1009 resp = CompleteMultiPartUpload(self) | 1188 resp = CompleteMultiPartUpload(self) |
1010 h = handler.XmlHandler(resp, self) | 1189 h = handler.XmlHandler(resp, self) |
1011 xml.sax.parseString(body, h) | 1190 xml.sax.parseString(body, h) |
1012 return resp | 1191 return resp |
1013 else: | 1192 else: |
1014 raise self.connection.provider.storage_response_error( | 1193 raise self.connection.provider.storage_response_error( |
1015 response.status, response.reason, body) | 1194 response.status, response.reason, body) |
1016 | 1195 |
1017 def cancel_multipart_upload(self, key_name, upload_id, headers=None): | 1196 def cancel_multipart_upload(self, key_name, upload_id, headers=None): |
1018 query_args = 'uploadId=%s' % upload_id | 1197 query_args = 'uploadId=%s' % upload_id |
1019 response = self.connection.make_request('DELETE', self.name, key_name, | 1198 response = self.connection.make_request('DELETE', self.name, key_name, |
1020 query_args=query_args, | 1199 query_args=query_args, |
1021 headers=headers) | 1200 headers=headers) |
1022 body = response.read() | 1201 body = response.read() |
1023 boto.log.debug(body) | 1202 boto.log.debug(body) |
1024 if response.status != 204: | 1203 if response.status != 204: |
1025 raise self.connection.provider.storage_response_error( | 1204 raise self.connection.provider.storage_response_error( |
1026 response.status, response.reason, body) | 1205 response.status, response.reason, body) |
1027 | 1206 |
1028 def delete(self, headers=None): | 1207 def delete(self, headers=None): |
1029 return self.connection.delete_bucket(self.name, headers=headers) | 1208 return self.connection.delete_bucket(self.name, headers=headers) |
1030 | |
OLD | NEW |