Index: tools/telemetry/third_party/gsutil/third_party/boto/boto/s3/bucket.py |
diff --git a/tools/telemetry/third_party/gsutil/third_party/boto/boto/s3/bucket.py b/tools/telemetry/third_party/gsutil/third_party/boto/boto/s3/bucket.py |
deleted file mode 100644 |
index 504f24f9bbf4b66c2f4b96a2b615d4229f3ad57b..0000000000000000000000000000000000000000 |
--- a/tools/telemetry/third_party/gsutil/third_party/boto/boto/s3/bucket.py |
+++ /dev/null |
@@ -1,1876 +0,0 @@ |
-# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/ |
-# Copyright (c) 2010, Eucalyptus Systems, Inc. |
-# All rights reserved. |
-# |
-# Permission is hereby granted, free of charge, to any person obtaining a |
-# copy of this software and associated documentation files (the |
-# "Software"), to deal in the Software without restriction, including |
-# without limitation the rights to use, copy, modify, merge, publish, dis- |
-# tribute, sublicense, and/or sell copies of the Software, and to permit |
-# persons to whom the Software is furnished to do so, subject to the fol- |
-# lowing conditions: |
-# |
-# The above copyright notice and this permission notice shall be included |
-# in all copies or substantial portions of the Software. |
-# |
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
-# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
-# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
-# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
-# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
-# IN THE SOFTWARE. |
- |
-import boto |
-from boto import handler |
-from boto.resultset import ResultSet |
-from boto.exception import BotoClientError |
-from boto.s3.acl import Policy, CannedACLStrings, Grant |
-from boto.s3.key import Key |
-from boto.s3.prefix import Prefix |
-from boto.s3.deletemarker import DeleteMarker |
-from boto.s3.multipart import MultiPartUpload |
-from boto.s3.multipart import CompleteMultiPartUpload |
-from boto.s3.multidelete import MultiDeleteResult |
-from boto.s3.multidelete import Error |
-from boto.s3.bucketlistresultset import BucketListResultSet |
-from boto.s3.bucketlistresultset import VersionedBucketListResultSet |
-from boto.s3.bucketlistresultset import MultiPartUploadListResultSet |
-from boto.s3.lifecycle import Lifecycle |
-from boto.s3.tagging import Tags |
-from boto.s3.cors import CORSConfiguration |
-from boto.s3.bucketlogging import BucketLogging |
-from boto.s3 import website |
-import boto.jsonresponse |
-import boto.utils |
-import xml.sax |
-import xml.sax.saxutils |
-import re |
-import base64 |
-from collections import defaultdict |
-from boto.compat import BytesIO, six, StringIO, urllib |
- |
-# as per http://goo.gl/BDuud (02/19/2011) |
- |
- |
-class S3WebsiteEndpointTranslate(object): |
- |
- trans_region = defaultdict(lambda: 's3-website-us-east-1') |
- trans_region['eu-west-1'] = 's3-website-eu-west-1' |
- trans_region['us-west-1'] = 's3-website-us-west-1' |
- trans_region['us-west-2'] = 's3-website-us-west-2' |
- trans_region['sa-east-1'] = 's3-website-sa-east-1' |
- trans_region['ap-northeast-1'] = 's3-website-ap-northeast-1' |
- trans_region['ap-southeast-1'] = 's3-website-ap-southeast-1' |
- trans_region['ap-southeast-2'] = 's3-website-ap-southeast-2' |
- trans_region['cn-north-1'] = 's3-website.cn-north-1' |
- |
- @classmethod |
- def translate_region(self, reg): |
- return self.trans_region[reg] |
- |
-S3Permissions = ['READ', 'WRITE', 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL'] |
- |
- |
-class Bucket(object): |
- |
- LoggingGroup = 'http://acs.amazonaws.com/groups/s3/LogDelivery' |
- |
- BucketPaymentBody = """<?xml version="1.0" encoding="UTF-8"?> |
- <RequestPaymentConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> |
- <Payer>%s</Payer> |
- </RequestPaymentConfiguration>""" |
- |
- VersioningBody = """<?xml version="1.0" encoding="UTF-8"?> |
- <VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> |
- <Status>%s</Status> |
- <MfaDelete>%s</MfaDelete> |
- </VersioningConfiguration>""" |
- |
- VersionRE = '<Status>([A-Za-z]+)</Status>' |
- MFADeleteRE = '<MfaDelete>([A-Za-z]+)</MfaDelete>' |
- |
- def __init__(self, connection=None, name=None, key_class=Key): |
- self.name = name |
- self.connection = connection |
- self.key_class = key_class |
- |
- def __repr__(self): |
- return '<Bucket: %s>' % self.name |
- |
- def __iter__(self): |
- return iter(BucketListResultSet(self)) |
- |
- def __contains__(self, key_name): |
- return not (self.get_key(key_name) is None) |
- |
- def startElement(self, name, attrs, connection): |
- return None |
- |
- def endElement(self, name, value, connection): |
- if name == 'Name': |
- self.name = value |
- elif name == 'CreationDate': |
- self.creation_date = value |
- else: |
- setattr(self, name, value) |
- |
- def set_key_class(self, key_class): |
- """ |
- Set the Key class associated with this bucket. By default, this |
- would be the boto.s3.key.Key class but if you want to subclass that |
- for some reason this allows you to associate your new class with a |
- bucket so that when you call bucket.new_key() or when you get a listing |
- of keys in the bucket you will get an instances of your key class |
- rather than the default. |
- |
- :type key_class: class |
- :param key_class: A subclass of Key that can be more specific |
- """ |
- self.key_class = key_class |
- |
- def lookup(self, key_name, headers=None): |
- """ |
- Deprecated: Please use get_key method. |
- |
- :type key_name: string |
- :param key_name: The name of the key to retrieve |
- |
- :rtype: :class:`boto.s3.key.Key` |
- :returns: A Key object from this bucket. |
- """ |
- return self.get_key(key_name, headers=headers) |
- |
- def get_key(self, key_name, headers=None, version_id=None, |
- response_headers=None, validate=True): |
- """ |
- Check to see if a particular key exists within the bucket. This |
- method uses a HEAD request to check for the existence of the key. |
- Returns: An instance of a Key object or None |
- |
- :param key_name: The name of the key to retrieve |
- :type key_name: string |
- |
- :param headers: The headers to send when retrieving the key |
- :type headers: dict |
- |
- :param version_id: |
- :type version_id: string |
- |
- :param response_headers: A dictionary containing HTTP |
- headers/values that will override any headers associated |
- with the stored object in the response. See |
- http://goo.gl/EWOPb for details. |
- :type response_headers: dict |
- |
- :param validate: Verifies whether the key exists. If ``False``, this |
- will not hit the service, constructing an in-memory object. |
- Default is ``True``. |
- :type validate: bool |
- |
- :rtype: :class:`boto.s3.key.Key` |
- :returns: A Key object from this bucket. |
- """ |
- if validate is False: |
- if headers or version_id or response_headers: |
- raise BotoClientError( |
- "When providing 'validate=False', no other params " + \ |
- "are allowed." |
- ) |
- |
- # This leans on the default behavior of ``new_key`` (not hitting |
- # the service). If that changes, that behavior should migrate here. |
- return self.new_key(key_name) |
- |
- query_args_l = [] |
- if version_id: |
- query_args_l.append('versionId=%s' % version_id) |
- if response_headers: |
- for rk, rv in six.iteritems(response_headers): |
- query_args_l.append('%s=%s' % (rk, urllib.parse.quote(rv))) |
- |
- key, resp = self._get_key_internal(key_name, headers, query_args_l) |
- return key |
- |
- def _get_key_internal(self, key_name, headers, query_args_l): |
- query_args = '&'.join(query_args_l) or None |
- response = self.connection.make_request('HEAD', self.name, key_name, |
- headers=headers, |
- query_args=query_args) |
- response.read() |
- # Allow any success status (2xx) - for example this lets us |
- # support Range gets, which return status 206: |
- if response.status / 100 == 2: |
- k = self.key_class(self) |
- provider = self.connection.provider |
- k.metadata = boto.utils.get_aws_metadata(response.msg, provider) |
- for field in Key.base_fields: |
- k.__dict__[field.lower().replace('-', '_')] = \ |
- response.getheader(field) |
- # the following machinations are a workaround to the fact that |
- # apache/fastcgi omits the content-length header on HEAD |
- # requests when the content-length is zero. |
- # See http://goo.gl/0Tdax for more details. |
- clen = response.getheader('content-length') |
- if clen: |
- k.size = int(response.getheader('content-length')) |
- else: |
- k.size = 0 |
- k.name = key_name |
- k.handle_version_headers(response) |
- k.handle_encryption_headers(response) |
- k.handle_restore_headers(response) |
- k.handle_addl_headers(response.getheaders()) |
- return k, response |
- else: |
- if response.status == 404: |
- return None, response |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, '') |
- |
- def list(self, prefix='', delimiter='', marker='', headers=None, |
- encoding_type=None): |
- """ |
- List key objects within a bucket. This returns an instance of an |
- BucketListResultSet that automatically handles all of the result |
- paging, etc. from S3. You just need to keep iterating until |
- there are no more results. |
- |
- Called with no arguments, this will return an iterator object across |
- all keys within the bucket. |
- |
- The Key objects returned by the iterator are obtained by parsing |
- the results of a GET on the bucket, also known as the List Objects |
- request. The XML returned by this request contains only a subset |
- of the information about each key. Certain metadata fields such |
- as Content-Type and user metadata are not available in the XML. |
- Therefore, if you want these additional metadata fields you will |
- have to do a HEAD request on the Key in the bucket. |
- |
- :type prefix: string |
- :param prefix: allows you to limit the listing to a particular |
- prefix. For example, if you call the method with |
- prefix='/foo/' then the iterator will only cycle through |
- the keys that begin with the string '/foo/'. |
- |
- :type delimiter: string |
- :param delimiter: can be used in conjunction with the prefix |
- to allow you to organize and browse your keys |
- hierarchically. See http://goo.gl/Xx63h for more details. |
- |
- :type marker: string |
- :param marker: The "marker" of where you are in the result set |
- |
- :param encoding_type: Requests Amazon S3 to encode the response and |
- specifies the encoding method to use. |
- |
- An object key can contain any Unicode character; however, XML 1.0 |
- parser cannot parse some characters, such as characters with an |
- ASCII value from 0 to 10. For characters that are not supported in |
- XML 1.0, you can add this parameter to request that Amazon S3 |
- encode the keys in the response. |
- |
- Valid options: ``url`` |
- :type encoding_type: string |
- |
- :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet` |
- :return: an instance of a BucketListResultSet that handles paging, etc |
- """ |
- return BucketListResultSet(self, prefix, delimiter, marker, headers, |
- encoding_type=encoding_type) |
- |
- def list_versions(self, prefix='', delimiter='', key_marker='', |
- version_id_marker='', headers=None, encoding_type=None): |
- """ |
- List version objects within a bucket. This returns an |
- instance of an VersionedBucketListResultSet that automatically |
- handles all of the result paging, etc. from S3. You just need |
- to keep iterating until there are no more results. Called |
- with no arguments, this will return an iterator object across |
- all keys within the bucket. |
- |
- :type prefix: string |
- :param prefix: allows you to limit the listing to a particular |
- prefix. For example, if you call the method with |
- prefix='/foo/' then the iterator will only cycle through |
- the keys that begin with the string '/foo/'. |
- |
- :type delimiter: string |
- :param delimiter: can be used in conjunction with the prefix |
- to allow you to organize and browse your keys |
- hierarchically. See: |
- |
- http://aws.amazon.com/releasenotes/Amazon-S3/213 |
- |
- for more details. |
- |
- :type key_marker: string |
- :param key_marker: The "marker" of where you are in the result set |
- |
- :param encoding_type: Requests Amazon S3 to encode the response and |
- specifies the encoding method to use. |
- |
- An object key can contain any Unicode character; however, XML 1.0 |
- parser cannot parse some characters, such as characters with an |
- ASCII value from 0 to 10. For characters that are not supported in |
- XML 1.0, you can add this parameter to request that Amazon S3 |
- encode the keys in the response. |
- |
- Valid options: ``url`` |
- :type encoding_type: string |
- |
- :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet` |
- :return: an instance of a BucketListResultSet that handles paging, etc |
- """ |
- return VersionedBucketListResultSet(self, prefix, delimiter, |
- key_marker, version_id_marker, |
- headers, |
- encoding_type=encoding_type) |
- |
- def list_multipart_uploads(self, key_marker='', |
- upload_id_marker='', |
- headers=None, encoding_type=None): |
- """ |
- List multipart upload objects within a bucket. This returns an |
- instance of an MultiPartUploadListResultSet that automatically |
- handles all of the result paging, etc. from S3. You just need |
- to keep iterating until there are no more results. |
- |
- :type key_marker: string |
- :param key_marker: The "marker" of where you are in the result set |
- |
- :type upload_id_marker: string |
- :param upload_id_marker: The upload identifier |
- |
- :param encoding_type: Requests Amazon S3 to encode the response and |
- specifies the encoding method to use. |
- |
- An object key can contain any Unicode character; however, XML 1.0 |
- parser cannot parse some characters, such as characters with an |
- ASCII value from 0 to 10. For characters that are not supported in |
- XML 1.0, you can add this parameter to request that Amazon S3 |
- encode the keys in the response. |
- |
- Valid options: ``url`` |
- :type encoding_type: string |
- |
- :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet` |
- :return: an instance of a BucketListResultSet that handles paging, etc |
- """ |
- return MultiPartUploadListResultSet(self, key_marker, |
- upload_id_marker, |
- headers, |
- encoding_type=encoding_type) |
- |
- def _get_all_query_args(self, params, initial_query_string=''): |
- pairs = [] |
- |
- if initial_query_string: |
- pairs.append(initial_query_string) |
- |
- for key, value in sorted(params.items(), key=lambda x: x[0]): |
- if value is None: |
- continue |
- key = key.replace('_', '-') |
- if key == 'maxkeys': |
- key = 'max-keys' |
- if not isinstance(value, six.string_types + (six.binary_type,)): |
- value = six.text_type(value) |
- if not isinstance(value, six.binary_type): |
- value = value.encode('utf-8') |
- if value: |
- pairs.append(u'%s=%s' % ( |
- urllib.parse.quote(key), |
- urllib.parse.quote(value) |
- )) |
- |
- return '&'.join(pairs) |
- |
- def _get_all(self, element_map, initial_query_string='', |
- headers=None, **params): |
- query_args = self._get_all_query_args( |
- params, |
- initial_query_string=initial_query_string |
- ) |
- response = self.connection.make_request('GET', self.name, |
- headers=headers, |
- query_args=query_args) |
- body = response.read() |
- boto.log.debug(body) |
- if response.status == 200: |
- rs = ResultSet(element_map) |
- h = handler.XmlHandler(rs, self) |
- if not isinstance(body, bytes): |
- body = body.encode('utf-8') |
- xml.sax.parseString(body, h) |
- return rs |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def validate_kwarg_names(self, kwargs, names): |
- """ |
- Checks that all named arguments are in the specified list of names. |
- |
- :type kwargs: dict |
- :param kwargs: Dictionary of kwargs to validate. |
- |
- :type names: list |
- :param names: List of possible named arguments. |
- """ |
- for kwarg in kwargs: |
- if kwarg not in names: |
- raise TypeError('Invalid argument "%s"!' % kwarg) |
- |
- def get_all_keys(self, headers=None, **params): |
- """ |
- A lower-level method for listing contents of a bucket. This |
- closely models the actual S3 API and requires you to manually |
- handle the paging of results. For a higher-level method that |
- handles the details of paging for you, you can use the list |
- method. |
- |
- :type max_keys: int |
- :param max_keys: The maximum number of keys to retrieve |
- |
- :type prefix: string |
- :param prefix: The prefix of the keys you want to retrieve |
- |
- :type marker: string |
- :param marker: The "marker" of where you are in the result set |
- |
- :type delimiter: string |
- :param delimiter: If this optional, Unicode string parameter |
- is included with your request, then keys that contain the |
- same string between the prefix and the first occurrence of |
- the delimiter will be rolled up into a single result |
- element in the CommonPrefixes collection. These rolled-up |
- keys are not returned elsewhere in the response. |
- |
- :param encoding_type: Requests Amazon S3 to encode the response and |
- specifies the encoding method to use. |
- |
- An object key can contain any Unicode character; however, XML 1.0 |
- parser cannot parse some characters, such as characters with an |
- ASCII value from 0 to 10. For characters that are not supported in |
- XML 1.0, you can add this parameter to request that Amazon S3 |
- encode the keys in the response. |
- |
- Valid options: ``url`` |
- :type encoding_type: string |
- |
- :rtype: ResultSet |
- :return: The result from S3 listing the keys requested |
- |
- """ |
- self.validate_kwarg_names(params, ['maxkeys', 'max_keys', 'prefix', |
- 'marker', 'delimiter', |
- 'encoding_type']) |
- return self._get_all([('Contents', self.key_class), |
- ('CommonPrefixes', Prefix)], |
- '', headers, **params) |
- |
- def get_all_versions(self, headers=None, **params): |
- """ |
- A lower-level, version-aware method for listing contents of a |
- bucket. This closely models the actual S3 API and requires |
- you to manually handle the paging of results. For a |
- higher-level method that handles the details of paging for |
- you, you can use the list method. |
- |
- :type max_keys: int |
- :param max_keys: The maximum number of keys to retrieve |
- |
- :type prefix: string |
- :param prefix: The prefix of the keys you want to retrieve |
- |
- :type key_marker: string |
- :param key_marker: The "marker" of where you are in the result set |
- with respect to keys. |
- |
- :type version_id_marker: string |
- :param version_id_marker: The "marker" of where you are in the result |
- set with respect to version-id's. |
- |
- :type delimiter: string |
- :param delimiter: If this optional, Unicode string parameter |
- is included with your request, then keys that contain the |
- same string between the prefix and the first occurrence of |
- the delimiter will be rolled up into a single result |
- element in the CommonPrefixes collection. These rolled-up |
- keys are not returned elsewhere in the response. |
- |
- :param encoding_type: Requests Amazon S3 to encode the response and |
- specifies the encoding method to use. |
- |
- An object key can contain any Unicode character; however, XML 1.0 |
- parser cannot parse some characters, such as characters with an |
- ASCII value from 0 to 10. For characters that are not supported in |
- XML 1.0, you can add this parameter to request that Amazon S3 |
- encode the keys in the response. |
- |
- Valid options: ``url`` |
- :type encoding_type: string |
- |
- :rtype: ResultSet |
- :return: The result from S3 listing the keys requested |
- """ |
- self.validate_get_all_versions_params(params) |
- return self._get_all([('Version', self.key_class), |
- ('CommonPrefixes', Prefix), |
- ('DeleteMarker', DeleteMarker)], |
- 'versions', headers, **params) |
- |
- def validate_get_all_versions_params(self, params): |
- """ |
- Validate that the parameters passed to get_all_versions are valid. |
- Overridden by subclasses that allow a different set of parameters. |
- |
- :type params: dict |
- :param params: Parameters to validate. |
- """ |
- self.validate_kwarg_names( |
- params, ['maxkeys', 'max_keys', 'prefix', 'key_marker', |
- 'version_id_marker', 'delimiter', 'encoding_type']) |
- |
- def get_all_multipart_uploads(self, headers=None, **params): |
- """ |
- A lower-level, version-aware method for listing active |
- MultiPart uploads for a bucket. This closely models the |
- actual S3 API and requires you to manually handle the paging |
- of results. For a higher-level method that handles the |
- details of paging for you, you can use the list method. |
- |
- :type max_uploads: int |
- :param max_uploads: The maximum number of uploads to retrieve. |
- Default value is 1000. |
- |
- :type key_marker: string |
- :param key_marker: Together with upload_id_marker, this |
- parameter specifies the multipart upload after which |
- listing should begin. If upload_id_marker is not |
- specified, only the keys lexicographically greater than |
- the specified key_marker will be included in the list. |
- |
- If upload_id_marker is specified, any multipart uploads |
- for a key equal to the key_marker might also be included, |
- provided those multipart uploads have upload IDs |
- lexicographically greater than the specified |
- upload_id_marker. |
- |
- :type upload_id_marker: string |
- :param upload_id_marker: Together with key-marker, specifies |
- the multipart upload after which listing should begin. If |
- key_marker is not specified, the upload_id_marker |
- parameter is ignored. Otherwise, any multipart uploads |
- for a key equal to the key_marker might be included in the |
- list only if they have an upload ID lexicographically |
- greater than the specified upload_id_marker. |
- |
- :type encoding_type: string |
- :param encoding_type: Requests Amazon S3 to encode the response and |
- specifies the encoding method to use. |
- |
- An object key can contain any Unicode character; however, XML 1.0 |
- parser cannot parse some characters, such as characters with an |
- ASCII value from 0 to 10. For characters that are not supported in |
- XML 1.0, you can add this parameter to request that Amazon S3 |
- encode the keys in the response. |
- |
- Valid options: ``url`` |
- |
- :type delimiter: string |
- :param delimiter: Character you use to group keys. |
- All keys that contain the same string between the prefix, if |
- specified, and the first occurrence of the delimiter after the |
- prefix are grouped under a single result element, CommonPrefixes. |
- If you don't specify the prefix parameter, then the substring |
- starts at the beginning of the key. The keys that are grouped |
- under CommonPrefixes result element are not returned elsewhere |
- in the response. |
- |
- :type prefix: string |
- :param prefix: Lists in-progress uploads only for those keys that |
- begin with the specified prefix. You can use prefixes to separate |
- a bucket into different grouping of keys. (You can think of using |
- prefix to make groups in the same way you'd use a folder in a |
- file system.) |
- |
- :rtype: ResultSet |
- :return: The result from S3 listing the uploads requested |
- |
- """ |
- self.validate_kwarg_names(params, ['max_uploads', 'key_marker', |
- 'upload_id_marker', 'encoding_type', |
- 'delimiter', 'prefix']) |
- return self._get_all([('Upload', MultiPartUpload), |
- ('CommonPrefixes', Prefix)], |
- 'uploads', headers, **params) |
- |
- def new_key(self, key_name=None): |
- """ |
- Creates a new key |
- |
- :type key_name: string |
- :param key_name: The name of the key to create |
- |
- :rtype: :class:`boto.s3.key.Key` or subclass |
- :returns: An instance of the newly created key object |
- """ |
- if not key_name: |
- raise ValueError('Empty key names are not allowed') |
- return self.key_class(self, key_name) |
- |
- def generate_url(self, expires_in, method='GET', headers=None, |
- force_http=False, response_headers=None, |
- expires_in_absolute=False): |
- return self.connection.generate_url(expires_in, method, self.name, |
- headers=headers, |
- force_http=force_http, |
- response_headers=response_headers, |
- expires_in_absolute=expires_in_absolute) |
- |
- def delete_keys(self, keys, quiet=False, mfa_token=None, headers=None): |
- """ |
- Deletes a set of keys using S3's Multi-object delete API. If a |
- VersionID is specified for that key then that version is removed. |
- Returns a MultiDeleteResult Object, which contains Deleted |
- and Error elements for each key you ask to delete. |
- |
- :type keys: list |
- :param keys: A list of either key_names or (key_name, versionid) pairs |
- or a list of Key instances. |
- |
- :type quiet: boolean |
- :param quiet: In quiet mode the response includes only keys |
- where the delete operation encountered an error. For a |
- successful deletion, the operation does not return any |
- information about the delete in the response body. |
- |
- :type mfa_token: tuple or list of strings |
- :param mfa_token: A tuple or list consisting of the serial |
- number from the MFA device and the current value of the |
- six-digit token associated with the device. This value is |
- required anytime you are deleting versioned objects from a |
- bucket that has the MFADelete option on the bucket. |
- |
- :returns: An instance of MultiDeleteResult |
- """ |
- ikeys = iter(keys) |
- result = MultiDeleteResult(self) |
- provider = self.connection.provider |
- query_args = 'delete' |
- |
- def delete_keys2(hdrs): |
- hdrs = hdrs or {} |
- data = u"""<?xml version="1.0" encoding="UTF-8"?>""" |
- data += u"<Delete>" |
- if quiet: |
- data += u"<Quiet>true</Quiet>" |
- count = 0 |
- while count < 1000: |
- try: |
- key = next(ikeys) |
- except StopIteration: |
- break |
- if isinstance(key, six.string_types): |
- key_name = key |
- version_id = None |
- elif isinstance(key, tuple) and len(key) == 2: |
- key_name, version_id = key |
- elif (isinstance(key, Key) or isinstance(key, DeleteMarker)) and key.name: |
- key_name = key.name |
- version_id = key.version_id |
- else: |
- if isinstance(key, Prefix): |
- key_name = key.name |
- code = 'PrefixSkipped' # Don't delete Prefix |
- else: |
- key_name = repr(key) # try get a string |
- code = 'InvalidArgument' # other unknown type |
- message = 'Invalid. No delete action taken for this object.' |
- error = Error(key_name, code=code, message=message) |
- result.errors.append(error) |
- continue |
- count += 1 |
- data += u"<Object><Key>%s</Key>" % xml.sax.saxutils.escape(key_name) |
- if version_id: |
- data += u"<VersionId>%s</VersionId>" % version_id |
- data += u"</Object>" |
- data += u"</Delete>" |
- if count <= 0: |
- return False # no more |
- data = data.encode('utf-8') |
- fp = BytesIO(data) |
- md5 = boto.utils.compute_md5(fp) |
- hdrs['Content-MD5'] = md5[1] |
- hdrs['Content-Type'] = 'text/xml' |
- if mfa_token: |
- hdrs[provider.mfa_header] = ' '.join(mfa_token) |
- response = self.connection.make_request('POST', self.name, |
- headers=hdrs, |
- query_args=query_args, |
- data=data) |
- body = response.read() |
- if response.status == 200: |
- h = handler.XmlHandler(result, self) |
- if not isinstance(body, bytes): |
- body = body.encode('utf-8') |
- xml.sax.parseString(body, h) |
- return count >= 1000 # more? |
- else: |
- raise provider.storage_response_error(response.status, |
- response.reason, |
- body) |
- while delete_keys2(headers): |
- pass |
- return result |
- |
- def delete_key(self, key_name, headers=None, version_id=None, |
- mfa_token=None): |
- """ |
- Deletes a key from the bucket. If a version_id is provided, |
- only that version of the key will be deleted. |
- |
- :type key_name: string |
- :param key_name: The key name to delete |
- |
- :type version_id: string |
- :param version_id: The version ID (optional) |
- |
- :type mfa_token: tuple or list of strings |
- :param mfa_token: A tuple or list consisting of the serial |
- number from the MFA device and the current value of the |
- six-digit token associated with the device. This value is |
- required anytime you are deleting versioned objects from a |
- bucket that has the MFADelete option on the bucket. |
- |
- :rtype: :class:`boto.s3.key.Key` or subclass |
- :returns: A key object holding information on what was |
- deleted. The Caller can see if a delete_marker was |
- created or removed and what version_id the delete created |
- or removed. |
- """ |
- if not key_name: |
- raise ValueError('Empty key names are not allowed') |
- return self._delete_key_internal(key_name, headers=headers, |
- version_id=version_id, |
- mfa_token=mfa_token, |
- query_args_l=None) |
- |
- def _delete_key_internal(self, key_name, headers=None, version_id=None, |
- mfa_token=None, query_args_l=None): |
- query_args_l = query_args_l or [] |
- provider = self.connection.provider |
- if version_id: |
- query_args_l.append('versionId=%s' % version_id) |
- query_args = '&'.join(query_args_l) or None |
- if mfa_token: |
- if not headers: |
- headers = {} |
- headers[provider.mfa_header] = ' '.join(mfa_token) |
- response = self.connection.make_request('DELETE', self.name, key_name, |
- headers=headers, |
- query_args=query_args) |
- body = response.read() |
- if response.status != 204: |
- raise provider.storage_response_error(response.status, |
- response.reason, body) |
- else: |
- # return a key object with information on what was deleted. |
- k = self.key_class(self) |
- k.name = key_name |
- k.handle_version_headers(response) |
- k.handle_addl_headers(response.getheaders()) |
- return k |
- |
- def copy_key(self, new_key_name, src_bucket_name, |
- src_key_name, metadata=None, src_version_id=None, |
- storage_class='STANDARD', preserve_acl=False, |
- encrypt_key=False, headers=None, query_args=None): |
- """ |
- Create a new key in the bucket by copying another existing key. |
- |
- :type new_key_name: string |
- :param new_key_name: The name of the new key |
- |
- :type src_bucket_name: string |
- :param src_bucket_name: The name of the source bucket |
- |
- :type src_key_name: string |
- :param src_key_name: The name of the source key |
- |
- :type src_version_id: string |
- :param src_version_id: The version id for the key. This param |
- is optional. If not specified, the newest version of the |
- key will be copied. |
- |
- :type metadata: dict |
- :param metadata: Metadata to be associated with new key. If |
- metadata is supplied, it will replace the metadata of the |
- source key being copied. If no metadata is supplied, the |
- source key's metadata will be copied to the new key. |
- |
- :type storage_class: string |
- :param storage_class: The storage class of the new key. By |
- default, the new key will use the standard storage class. |
- Possible values are: STANDARD | REDUCED_REDUNDANCY |
- |
- :type preserve_acl: bool |
- :param preserve_acl: If True, the ACL from the source key will |
- be copied to the destination key. If False, the |
- destination key will have the default ACL. Note that |
- preserving the ACL in the new key object will require two |
- additional API calls to S3, one to retrieve the current |
- ACL and one to set that ACL on the new object. If you |
- don't care about the ACL, a value of False will be |
- significantly more efficient. |
- |
- :type encrypt_key: bool |
- :param encrypt_key: If True, the new copy of the object will |
- be encrypted on the server-side by S3 and will be stored |
- in an encrypted form while at rest in S3. |
- |
- :type headers: dict |
- :param headers: A dictionary of header name/value pairs. |
- |
- :type query_args: string |
- :param query_args: A string of additional querystring arguments |
- to append to the request |
- |
- :rtype: :class:`boto.s3.key.Key` or subclass |
- :returns: An instance of the newly created key object |
- """ |
- headers = headers or {} |
- provider = self.connection.provider |
- src_key_name = boto.utils.get_utf8_value(src_key_name) |
- if preserve_acl: |
- if self.name == src_bucket_name: |
- src_bucket = self |
- else: |
- src_bucket = self.connection.get_bucket( |
- src_bucket_name, validate=False) |
- acl = src_bucket.get_xml_acl(src_key_name) |
- if encrypt_key: |
- headers[provider.server_side_encryption_header] = 'AES256' |
- src = '%s/%s' % (src_bucket_name, urllib.parse.quote(src_key_name)) |
- if src_version_id: |
- src += '?versionId=%s' % src_version_id |
- headers[provider.copy_source_header] = str(src) |
- # make sure storage_class_header key exists before accessing it |
- if provider.storage_class_header and storage_class: |
- headers[provider.storage_class_header] = storage_class |
- if metadata is not None: |
- headers[provider.metadata_directive_header] = 'REPLACE' |
- headers = boto.utils.merge_meta(headers, metadata, provider) |
- elif not query_args: # Can't use this header with multi-part copy. |
- headers[provider.metadata_directive_header] = 'COPY' |
- response = self.connection.make_request('PUT', self.name, new_key_name, |
- headers=headers, |
- query_args=query_args) |
- body = response.read() |
- if response.status == 200: |
- key = self.new_key(new_key_name) |
- h = handler.XmlHandler(key, self) |
- if not isinstance(body, bytes): |
- body = body.encode('utf-8') |
- xml.sax.parseString(body, h) |
- if hasattr(key, 'Error'): |
- raise provider.storage_copy_error(key.Code, key.Message, body) |
- key.handle_version_headers(response) |
- key.handle_addl_headers(response.getheaders()) |
- if preserve_acl: |
- self.set_xml_acl(acl, new_key_name) |
- return key |
- else: |
- raise provider.storage_response_error(response.status, |
- response.reason, body) |
- |
- def set_canned_acl(self, acl_str, key_name='', headers=None, |
- version_id=None): |
- assert acl_str in CannedACLStrings |
- |
- if headers: |
- headers[self.connection.provider.acl_header] = acl_str |
- else: |
- headers = {self.connection.provider.acl_header: acl_str} |
- |
- query_args = 'acl' |
- if version_id: |
- query_args += '&versionId=%s' % version_id |
- response = self.connection.make_request('PUT', self.name, key_name, |
- headers=headers, query_args=query_args) |
- body = response.read() |
- if response.status != 200: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def get_xml_acl(self, key_name='', headers=None, version_id=None): |
- query_args = 'acl' |
- if version_id: |
- query_args += '&versionId=%s' % version_id |
- response = self.connection.make_request('GET', self.name, key_name, |
- query_args=query_args, |
- headers=headers) |
- body = response.read() |
- if response.status != 200: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- return body |
- |
- def set_xml_acl(self, acl_str, key_name='', headers=None, version_id=None, |
- query_args='acl'): |
- if version_id: |
- query_args += '&versionId=%s' % version_id |
- if not isinstance(acl_str, bytes): |
- acl_str = acl_str.encode('utf-8') |
- response = self.connection.make_request('PUT', self.name, key_name, |
- data=acl_str, |
- query_args=query_args, |
- headers=headers) |
- body = response.read() |
- if response.status != 200: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def set_acl(self, acl_or_str, key_name='', headers=None, version_id=None): |
- if isinstance(acl_or_str, Policy): |
- self.set_xml_acl(acl_or_str.to_xml(), key_name, |
- headers, version_id) |
- else: |
- self.set_canned_acl(acl_or_str, key_name, |
- headers, version_id) |
- |
- def get_acl(self, key_name='', headers=None, version_id=None): |
- query_args = 'acl' |
- if version_id: |
- query_args += '&versionId=%s' % version_id |
- response = self.connection.make_request('GET', self.name, key_name, |
- query_args=query_args, |
- headers=headers) |
- body = response.read() |
- if response.status == 200: |
- policy = Policy(self) |
- h = handler.XmlHandler(policy, self) |
- if not isinstance(body, bytes): |
- body = body.encode('utf-8') |
- xml.sax.parseString(body, h) |
- return policy |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def set_subresource(self, subresource, value, key_name='', headers=None, |
- version_id=None): |
- """ |
- Set a subresource for a bucket or key. |
- |
- :type subresource: string |
- :param subresource: The subresource to set. |
- |
- :type value: string |
- :param value: The value of the subresource. |
- |
- :type key_name: string |
- :param key_name: The key to operate on, or None to operate on the |
- bucket. |
- |
- :type headers: dict |
- :param headers: Additional HTTP headers to include in the request. |
- |
- :type src_version_id: string |
- :param src_version_id: Optional. The version id of the key to |
- operate on. If not specified, operate on the newest |
- version. |
- """ |
- if not subresource: |
- raise TypeError('set_subresource called with subresource=None') |
- query_args = subresource |
- if version_id: |
- query_args += '&versionId=%s' % version_id |
- if not isinstance(value, bytes): |
- value = value.encode('utf-8') |
- response = self.connection.make_request('PUT', self.name, key_name, |
- data=value, |
- query_args=query_args, |
- headers=headers) |
- body = response.read() |
- if response.status != 200: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def get_subresource(self, subresource, key_name='', headers=None, |
- version_id=None): |
- """ |
- Get a subresource for a bucket or key. |
- |
- :type subresource: string |
- :param subresource: The subresource to get. |
- |
- :type key_name: string |
- :param key_name: The key to operate on, or None to operate on the |
- bucket. |
- |
- :type headers: dict |
- :param headers: Additional HTTP headers to include in the request. |
- |
- :type src_version_id: string |
- :param src_version_id: Optional. The version id of the key to |
- operate on. If not specified, operate on the newest |
- version. |
- |
- :rtype: string |
- :returns: The value of the subresource. |
- """ |
- if not subresource: |
- raise TypeError('get_subresource called with subresource=None') |
- query_args = subresource |
- if version_id: |
- query_args += '&versionId=%s' % version_id |
- response = self.connection.make_request('GET', self.name, key_name, |
- query_args=query_args, |
- headers=headers) |
- body = response.read() |
- if response.status != 200: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- return body |
- |
- def make_public(self, recursive=False, headers=None): |
- self.set_canned_acl('public-read', headers=headers) |
- if recursive: |
- for key in self: |
- self.set_canned_acl('public-read', key.name, headers=headers) |
- |
- def add_email_grant(self, permission, email_address, |
- recursive=False, headers=None): |
- """ |
- Convenience method that provides a quick way to add an email grant |
- to a bucket. This method retrieves the current ACL, creates a new |
- grant based on the parameters passed in, adds that grant to the ACL |
- and then PUT's the new ACL back to S3. |
- |
- :type permission: string |
- :param permission: The permission being granted. Should be one of: |
- (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL). |
- |
- :type email_address: string |
- :param email_address: The email address associated with the AWS |
- account your are granting the permission to. |
- |
- :type recursive: boolean |
- :param recursive: A boolean value to controls whether the |
- command will apply the grant to all keys within the bucket |
- or not. The default value is False. By passing a True |
- value, the call will iterate through all keys in the |
- bucket and apply the same grant to each key. CAUTION: If |
- you have a lot of keys, this could take a long time! |
- """ |
- if permission not in S3Permissions: |
- raise self.connection.provider.storage_permissions_error( |
- 'Unknown Permission: %s' % permission) |
- policy = self.get_acl(headers=headers) |
- policy.acl.add_email_grant(permission, email_address) |
- self.set_acl(policy, headers=headers) |
- if recursive: |
- for key in self: |
- key.add_email_grant(permission, email_address, headers=headers) |
- |
- def add_user_grant(self, permission, user_id, recursive=False, |
- headers=None, display_name=None): |
- """ |
- Convenience method that provides a quick way to add a canonical |
- user grant to a bucket. This method retrieves the current ACL, |
- creates a new grant based on the parameters passed in, adds that |
- grant to the ACL and then PUT's the new ACL back to S3. |
- |
- :type permission: string |
- :param permission: The permission being granted. Should be one of: |
- (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL). |
- |
- :type user_id: string |
- :param user_id: The canonical user id associated with the AWS |
- account your are granting the permission to. |
- |
- :type recursive: boolean |
- :param recursive: A boolean value to controls whether the |
- command will apply the grant to all keys within the bucket |
- or not. The default value is False. By passing a True |
- value, the call will iterate through all keys in the |
- bucket and apply the same grant to each key. CAUTION: If |
- you have a lot of keys, this could take a long time! |
- |
- :type display_name: string |
- :param display_name: An option string containing the user's |
- Display Name. Only required on Walrus. |
- """ |
- if permission not in S3Permissions: |
- raise self.connection.provider.storage_permissions_error( |
- 'Unknown Permission: %s' % permission) |
- policy = self.get_acl(headers=headers) |
- policy.acl.add_user_grant(permission, user_id, |
- display_name=display_name) |
- self.set_acl(policy, headers=headers) |
- if recursive: |
- for key in self: |
- key.add_user_grant(permission, user_id, headers=headers, |
- display_name=display_name) |
- |
- def list_grants(self, headers=None): |
- policy = self.get_acl(headers=headers) |
- return policy.acl.grants |
- |
- def get_location(self): |
- """ |
- Returns the LocationConstraint for the bucket. |
- |
- :rtype: str |
- :return: The LocationConstraint for the bucket or the empty |
- string if no constraint was specified when bucket was created. |
- """ |
- response = self.connection.make_request('GET', self.name, |
- query_args='location') |
- body = response.read() |
- if response.status == 200: |
- rs = ResultSet(self) |
- h = handler.XmlHandler(rs, self) |
- if not isinstance(body, bytes): |
- body = body.encode('utf-8') |
- xml.sax.parseString(body, h) |
- return rs.LocationConstraint |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def set_xml_logging(self, logging_str, headers=None): |
- """ |
- Set logging on a bucket directly to the given xml string. |
- |
- :type logging_str: unicode string |
- :param logging_str: The XML for the bucketloggingstatus which |
- will be set. The string will be converted to utf-8 before |
- it is sent. Usually, you will obtain this XML from the |
- BucketLogging object. |
- |
- :rtype: bool |
- :return: True if ok or raises an exception. |
- """ |
- body = logging_str |
- if not isinstance(body, bytes): |
- body = body.encode('utf-8') |
- response = self.connection.make_request('PUT', self.name, data=body, |
- query_args='logging', headers=headers) |
- body = response.read() |
- if response.status == 200: |
- return True |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def enable_logging(self, target_bucket, target_prefix='', |
- grants=None, headers=None): |
- """ |
- Enable logging on a bucket. |
- |
- :type target_bucket: bucket or string |
- :param target_bucket: The bucket to log to. |
- |
- :type target_prefix: string |
- :param target_prefix: The prefix which should be prepended to the |
- generated log files written to the target_bucket. |
- |
- :type grants: list of Grant objects |
- :param grants: A list of extra permissions which will be granted on |
- the log files which are created. |
- |
- :rtype: bool |
- :return: True if ok or raises an exception. |
- """ |
- if isinstance(target_bucket, Bucket): |
- target_bucket = target_bucket.name |
- blogging = BucketLogging(target=target_bucket, prefix=target_prefix, |
- grants=grants) |
- return self.set_xml_logging(blogging.to_xml(), headers=headers) |
- |
- def disable_logging(self, headers=None): |
- """ |
- Disable logging on a bucket. |
- |
- :rtype: bool |
- :return: True if ok or raises an exception. |
- """ |
- blogging = BucketLogging() |
- return self.set_xml_logging(blogging.to_xml(), headers=headers) |
- |
- def get_logging_status(self, headers=None): |
- """ |
- Get the logging status for this bucket. |
- |
- :rtype: :class:`boto.s3.bucketlogging.BucketLogging` |
- :return: A BucketLogging object for this bucket. |
- """ |
- response = self.connection.make_request('GET', self.name, |
- query_args='logging', headers=headers) |
- body = response.read() |
- if response.status == 200: |
- blogging = BucketLogging() |
- h = handler.XmlHandler(blogging, self) |
- if not isinstance(body, bytes): |
- body = body.encode('utf-8') |
- xml.sax.parseString(body, h) |
- return blogging |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def set_as_logging_target(self, headers=None): |
- """ |
- Setup the current bucket as a logging target by granting the necessary |
- permissions to the LogDelivery group to write log files to this bucket. |
- """ |
- policy = self.get_acl(headers=headers) |
- g1 = Grant(permission='WRITE', type='Group', uri=self.LoggingGroup) |
- g2 = Grant(permission='READ_ACP', type='Group', uri=self.LoggingGroup) |
- policy.acl.add_grant(g1) |
- policy.acl.add_grant(g2) |
- self.set_acl(policy, headers=headers) |
- |
- def get_request_payment(self, headers=None): |
- response = self.connection.make_request('GET', self.name, |
- query_args='requestPayment', headers=headers) |
- body = response.read() |
- if response.status == 200: |
- return body |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def set_request_payment(self, payer='BucketOwner', headers=None): |
- body = self.BucketPaymentBody % payer |
- response = self.connection.make_request('PUT', self.name, data=body, |
- query_args='requestPayment', headers=headers) |
- body = response.read() |
- if response.status == 200: |
- return True |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def configure_versioning(self, versioning, mfa_delete=False, |
- mfa_token=None, headers=None): |
- """ |
- Configure versioning for this bucket. |
- |
- ..note:: This feature is currently in beta. |
- |
- :type versioning: bool |
- :param versioning: A boolean indicating whether version is |
- enabled (True) or disabled (False). |
- |
- :type mfa_delete: bool |
- :param mfa_delete: A boolean indicating whether the |
- Multi-Factor Authentication Delete feature is enabled |
- (True) or disabled (False). If mfa_delete is enabled then |
- all Delete operations will require the token from your MFA |
- device to be passed in the request. |
- |
- :type mfa_token: tuple or list of strings |
- :param mfa_token: A tuple or list consisting of the serial |
- number from the MFA device and the current value of the |
- six-digit token associated with the device. This value is |
- required when you are changing the status of the MfaDelete |
- property of the bucket. |
- """ |
- if versioning: |
- ver = 'Enabled' |
- else: |
- ver = 'Suspended' |
- if mfa_delete: |
- mfa = 'Enabled' |
- else: |
- mfa = 'Disabled' |
- body = self.VersioningBody % (ver, mfa) |
- if mfa_token: |
- if not headers: |
- headers = {} |
- provider = self.connection.provider |
- headers[provider.mfa_header] = ' '.join(mfa_token) |
- response = self.connection.make_request('PUT', self.name, data=body, |
- query_args='versioning', headers=headers) |
- body = response.read() |
- if response.status == 200: |
- return True |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def get_versioning_status(self, headers=None): |
- """ |
- Returns the current status of versioning on the bucket. |
- |
- :rtype: dict |
- :returns: A dictionary containing a key named 'Versioning' |
- that can have a value of either Enabled, Disabled, or |
- Suspended. Also, if MFADelete has ever been enabled on the |
- bucket, the dictionary will contain a key named |
- 'MFADelete' which will have a value of either Enabled or |
- Suspended. |
- """ |
- response = self.connection.make_request('GET', self.name, |
- query_args='versioning', headers=headers) |
- body = response.read() |
- if not isinstance(body, six.string_types): |
- body = body.decode('utf-8') |
- boto.log.debug(body) |
- if response.status == 200: |
- d = {} |
- ver = re.search(self.VersionRE, body) |
- if ver: |
- d['Versioning'] = ver.group(1) |
- mfa = re.search(self.MFADeleteRE, body) |
- if mfa: |
- d['MfaDelete'] = mfa.group(1) |
- return d |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def configure_lifecycle(self, lifecycle_config, headers=None): |
- """ |
- Configure lifecycle for this bucket. |
- |
- :type lifecycle_config: :class:`boto.s3.lifecycle.Lifecycle` |
- :param lifecycle_config: The lifecycle configuration you want |
- to configure for this bucket. |
- """ |
- xml = lifecycle_config.to_xml() |
- #xml = xml.encode('utf-8') |
- fp = StringIO(xml) |
- md5 = boto.utils.compute_md5(fp) |
- if headers is None: |
- headers = {} |
- headers['Content-MD5'] = md5[1] |
- headers['Content-Type'] = 'text/xml' |
- response = self.connection.make_request('PUT', self.name, |
- data=fp.getvalue(), |
- query_args='lifecycle', |
- headers=headers) |
- body = response.read() |
- if response.status == 200: |
- return True |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def get_lifecycle_config(self, headers=None): |
- """ |
- Returns the current lifecycle configuration on the bucket. |
- |
- :rtype: :class:`boto.s3.lifecycle.Lifecycle` |
- :returns: A LifecycleConfig object that describes all current |
- lifecycle rules in effect for the bucket. |
- """ |
- response = self.connection.make_request('GET', self.name, |
- query_args='lifecycle', headers=headers) |
- body = response.read() |
- boto.log.debug(body) |
- if response.status == 200: |
- lifecycle = Lifecycle() |
- h = handler.XmlHandler(lifecycle, self) |
- if not isinstance(body, bytes): |
- body = body.encode('utf-8') |
- xml.sax.parseString(body, h) |
- return lifecycle |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def delete_lifecycle_configuration(self, headers=None): |
- """ |
- Removes all lifecycle configuration from the bucket. |
- """ |
- response = self.connection.make_request('DELETE', self.name, |
- query_args='lifecycle', |
- headers=headers) |
- body = response.read() |
- boto.log.debug(body) |
- if response.status == 204: |
- return True |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def configure_website(self, suffix=None, error_key=None, |
- redirect_all_requests_to=None, |
- routing_rules=None, |
- headers=None): |
- """ |
- Configure this bucket to act as a website |
- |
- :type suffix: str |
- :param suffix: Suffix that is appended to a request that is for a |
- "directory" on the website endpoint (e.g. if the suffix is |
- index.html and you make a request to samplebucket/images/ |
- the data that is returned will be for the object with the |
- key name images/index.html). The suffix must not be empty |
- and must not include a slash character. |
- |
- :type error_key: str |
- :param error_key: The object key name to use when a 4XX class |
- error occurs. This is optional. |
- |
- :type redirect_all_requests_to: :class:`boto.s3.website.RedirectLocation` |
- :param redirect_all_requests_to: Describes the redirect behavior for |
- every request to this bucket's website endpoint. If this value is |
- non None, no other values are considered when configuring the |
- website configuration for the bucket. This is an instance of |
- ``RedirectLocation``. |
- |
- :type routing_rules: :class:`boto.s3.website.RoutingRules` |
- :param routing_rules: Object which specifies conditions |
- and redirects that apply when the conditions are met. |
- |
- """ |
- config = website.WebsiteConfiguration( |
- suffix, error_key, redirect_all_requests_to, |
- routing_rules) |
- return self.set_website_configuration(config, headers=headers) |
- |
- def set_website_configuration(self, config, headers=None): |
- """ |
- :type config: boto.s3.website.WebsiteConfiguration |
- :param config: Configuration data |
- """ |
- return self.set_website_configuration_xml(config.to_xml(), |
- headers=headers) |
- |
- |
- def set_website_configuration_xml(self, xml, headers=None): |
- """Upload xml website configuration""" |
- response = self.connection.make_request('PUT', self.name, data=xml, |
- query_args='website', |
- headers=headers) |
- body = response.read() |
- if response.status == 200: |
- return True |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def get_website_configuration(self, headers=None): |
- """ |
- Returns the current status of website configuration on the bucket. |
- |
- :rtype: dict |
- :returns: A dictionary containing a Python representation |
- of the XML response from S3. The overall structure is: |
- |
- * WebsiteConfiguration |
- |
- * IndexDocument |
- |
- * Suffix : suffix that is appended to request that |
- is for a "directory" on the website endpoint |
- * ErrorDocument |
- |
- * Key : name of object to serve when an error occurs |
- |
- """ |
- return self.get_website_configuration_with_xml(headers)[0] |
- |
- def get_website_configuration_obj(self, headers=None): |
- """Get the website configuration as a |
- :class:`boto.s3.website.WebsiteConfiguration` object. |
- """ |
- config_xml = self.get_website_configuration_xml(headers=headers) |
- config = website.WebsiteConfiguration() |
- h = handler.XmlHandler(config, self) |
- xml.sax.parseString(config_xml, h) |
- return config |
- |
- def get_website_configuration_with_xml(self, headers=None): |
- """ |
- Returns the current status of website configuration on the bucket as |
- unparsed XML. |
- |
- :rtype: 2-Tuple |
- :returns: 2-tuple containing: |
- |
- 1) A dictionary containing a Python representation \ |
- of the XML response. The overall structure is: |
- |
- * WebsiteConfiguration |
- |
- * IndexDocument |
- |
- * Suffix : suffix that is appended to request that \ |
- is for a "directory" on the website endpoint |
- |
- * ErrorDocument |
- |
- * Key : name of object to serve when an error occurs |
- |
- |
- 2) unparsed XML describing the bucket's website configuration |
- |
- """ |
- |
- body = self.get_website_configuration_xml(headers=headers) |
- e = boto.jsonresponse.Element() |
- h = boto.jsonresponse.XmlHandler(e, None) |
- h.parse(body) |
- return e, body |
- |
- def get_website_configuration_xml(self, headers=None): |
- """Get raw website configuration xml""" |
- response = self.connection.make_request('GET', self.name, |
- query_args='website', headers=headers) |
- body = response.read().decode('utf-8') |
- boto.log.debug(body) |
- |
- if response.status != 200: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- return body |
- |
- def delete_website_configuration(self, headers=None): |
- """ |
- Removes all website configuration from the bucket. |
- """ |
- response = self.connection.make_request('DELETE', self.name, |
- query_args='website', headers=headers) |
- body = response.read() |
- boto.log.debug(body) |
- if response.status == 204: |
- return True |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def get_website_endpoint(self): |
- """ |
- Returns the fully qualified hostname to use is you want to access this |
- bucket as a website. This doesn't validate whether the bucket has |
- been correctly configured as a website or not. |
- """ |
- l = [self.name] |
- l.append(S3WebsiteEndpointTranslate.translate_region(self.get_location())) |
- l.append('.'.join(self.connection.host.split('.')[-2:])) |
- return '.'.join(l) |
- |
- def get_policy(self, headers=None): |
- """ |
- Returns the JSON policy associated with the bucket. The policy |
- is returned as an uninterpreted JSON string. |
- """ |
- response = self.connection.make_request('GET', self.name, |
- query_args='policy', headers=headers) |
- body = response.read() |
- if response.status == 200: |
- return body |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def set_policy(self, policy, headers=None): |
- """ |
- Add or replace the JSON policy associated with the bucket. |
- |
- :type policy: str |
- :param policy: The JSON policy as a string. |
- """ |
- response = self.connection.make_request('PUT', self.name, |
- data=policy, |
- query_args='policy', |
- headers=headers) |
- body = response.read() |
- if response.status >= 200 and response.status <= 204: |
- return True |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def delete_policy(self, headers=None): |
- response = self.connection.make_request('DELETE', self.name, |
- data='/?policy', |
- query_args='policy', |
- headers=headers) |
- body = response.read() |
- if response.status >= 200 and response.status <= 204: |
- return True |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def set_cors_xml(self, cors_xml, headers=None): |
- """ |
- Set the CORS (Cross-Origin Resource Sharing) for a bucket. |
- |
- :type cors_xml: str |
- :param cors_xml: The XML document describing your desired |
- CORS configuration. See the S3 documentation for details |
- of the exact syntax required. |
- """ |
- fp = StringIO(cors_xml) |
- md5 = boto.utils.compute_md5(fp) |
- if headers is None: |
- headers = {} |
- headers['Content-MD5'] = md5[1] |
- headers['Content-Type'] = 'text/xml' |
- response = self.connection.make_request('PUT', self.name, |
- data=fp.getvalue(), |
- query_args='cors', |
- headers=headers) |
- body = response.read() |
- if response.status == 200: |
- return True |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def set_cors(self, cors_config, headers=None): |
- """ |
- Set the CORS for this bucket given a boto CORSConfiguration |
- object. |
- |
- :type cors_config: :class:`boto.s3.cors.CORSConfiguration` |
- :param cors_config: The CORS configuration you want |
- to configure for this bucket. |
- """ |
- return self.set_cors_xml(cors_config.to_xml()) |
- |
- def get_cors_xml(self, headers=None): |
- """ |
- Returns the current CORS configuration on the bucket as an |
- XML document. |
- """ |
- response = self.connection.make_request('GET', self.name, |
- query_args='cors', headers=headers) |
- body = response.read() |
- boto.log.debug(body) |
- if response.status == 200: |
- return body |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def get_cors(self, headers=None): |
- """ |
- Returns the current CORS configuration on the bucket. |
- |
- :rtype: :class:`boto.s3.cors.CORSConfiguration` |
- :returns: A CORSConfiguration object that describes all current |
- CORS rules in effect for the bucket. |
- """ |
- body = self.get_cors_xml(headers) |
- cors = CORSConfiguration() |
- h = handler.XmlHandler(cors, self) |
- xml.sax.parseString(body, h) |
- return cors |
- |
- def delete_cors(self, headers=None): |
- """ |
- Removes all CORS configuration from the bucket. |
- """ |
- response = self.connection.make_request('DELETE', self.name, |
- query_args='cors', |
- headers=headers) |
- body = response.read() |
- boto.log.debug(body) |
- if response.status == 204: |
- return True |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def initiate_multipart_upload(self, key_name, headers=None, |
- reduced_redundancy=False, |
- metadata=None, encrypt_key=False, |
- policy=None): |
- """ |
- Start a multipart upload operation. |
- |
- .. note:: |
- |
- Note: After you initiate multipart upload and upload one or more |
- parts, you must either complete or abort multipart upload in order |
- to stop getting charged for storage of the uploaded parts. Only |
- after you either complete or abort multipart upload, Amazon S3 |
- frees up the parts storage and stops charging you for the parts |
- storage. |
- |
- :type key_name: string |
- :param key_name: The name of the key that will ultimately |
- result from this multipart upload operation. This will be |
- exactly as the key appears in the bucket after the upload |
- process has been completed. |
- |
- :type headers: dict |
- :param headers: Additional HTTP headers to send and store with the |
- resulting key in S3. |
- |
- :type reduced_redundancy: boolean |
- :param reduced_redundancy: In multipart uploads, the storage |
- class is specified when initiating the upload, not when |
- uploading individual parts. So if you want the resulting |
- key to use the reduced redundancy storage class set this |
- flag when you initiate the upload. |
- |
- :type metadata: dict |
- :param metadata: Any metadata that you would like to set on the key |
- that results from the multipart upload. |
- |
- :type encrypt_key: bool |
- :param encrypt_key: If True, the new copy of the object will |
- be encrypted on the server-side by S3 and will be stored |
- in an encrypted form while at rest in S3. |
- |
- :type policy: :class:`boto.s3.acl.CannedACLStrings` |
- :param policy: A canned ACL policy that will be applied to the |
- new key (once completed) in S3. |
- """ |
- query_args = 'uploads' |
- provider = self.connection.provider |
- headers = headers or {} |
- if policy: |
- headers[provider.acl_header] = policy |
- if reduced_redundancy: |
- storage_class_header = provider.storage_class_header |
- if storage_class_header: |
- headers[storage_class_header] = 'REDUCED_REDUNDANCY' |
- # TODO: what if the provider doesn't support reduced redundancy? |
- # (see boto.s3.key.Key.set_contents_from_file) |
- if encrypt_key: |
- headers[provider.server_side_encryption_header] = 'AES256' |
- if metadata is None: |
- metadata = {} |
- |
- headers = boto.utils.merge_meta(headers, metadata, |
- self.connection.provider) |
- response = self.connection.make_request('POST', self.name, key_name, |
- query_args=query_args, |
- headers=headers) |
- body = response.read() |
- boto.log.debug(body) |
- if response.status == 200: |
- resp = MultiPartUpload(self) |
- h = handler.XmlHandler(resp, self) |
- if not isinstance(body, bytes): |
- body = body.encode('utf-8') |
- xml.sax.parseString(body, h) |
- return resp |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def complete_multipart_upload(self, key_name, upload_id, |
- xml_body, headers=None): |
- """ |
- Complete a multipart upload operation. |
- """ |
- query_args = 'uploadId=%s' % upload_id |
- if headers is None: |
- headers = {} |
- headers['Content-Type'] = 'text/xml' |
- response = self.connection.make_request('POST', self.name, key_name, |
- query_args=query_args, |
- headers=headers, data=xml_body) |
- contains_error = False |
- body = response.read().decode('utf-8') |
- # Some errors will be reported in the body of the response |
- # even though the HTTP response code is 200. This check |
- # does a quick and dirty peek in the body for an error element. |
- if body.find('<Error>') > 0: |
- contains_error = True |
- boto.log.debug(body) |
- if response.status == 200 and not contains_error: |
- resp = CompleteMultiPartUpload(self) |
- h = handler.XmlHandler(resp, self) |
- if not isinstance(body, bytes): |
- body = body.encode('utf-8') |
- xml.sax.parseString(body, h) |
- # Use a dummy key to parse various response headers |
- # for versioning, encryption info and then explicitly |
- # set the completed MPU object values from key. |
- k = self.key_class(self) |
- k.handle_version_headers(response) |
- k.handle_encryption_headers(response) |
- resp.version_id = k.version_id |
- resp.encrypted = k.encrypted |
- return resp |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def cancel_multipart_upload(self, key_name, upload_id, headers=None): |
- """ |
- To verify that all parts have been removed, so you don't get charged |
- for the part storage, you should call the List Parts operation and |
- ensure the parts list is empty. |
- """ |
- query_args = 'uploadId=%s' % upload_id |
- response = self.connection.make_request('DELETE', self.name, key_name, |
- query_args=query_args, |
- headers=headers) |
- body = response.read() |
- boto.log.debug(body) |
- if response.status != 204: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def delete(self, headers=None): |
- return self.connection.delete_bucket(self.name, headers=headers) |
- |
- def get_tags(self): |
- response = self.get_xml_tags() |
- tags = Tags() |
- h = handler.XmlHandler(tags, self) |
- if not isinstance(response, bytes): |
- response = response.encode('utf-8') |
- xml.sax.parseString(response, h) |
- return tags |
- |
- def get_xml_tags(self): |
- response = self.connection.make_request('GET', self.name, |
- query_args='tagging', |
- headers=None) |
- body = response.read() |
- if response.status == 200: |
- return body |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- |
- def set_xml_tags(self, tag_str, headers=None, query_args='tagging'): |
- if headers is None: |
- headers = {} |
- md5 = boto.utils.compute_md5(StringIO(tag_str)) |
- headers['Content-MD5'] = md5[1] |
- headers['Content-Type'] = 'text/xml' |
- if not isinstance(tag_str, bytes): |
- tag_str = tag_str.encode('utf-8') |
- response = self.connection.make_request('PUT', self.name, |
- data=tag_str, |
- query_args=query_args, |
- headers=headers) |
- body = response.read() |
- if response.status != 204: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |
- return True |
- |
- def set_tags(self, tags, headers=None): |
- return self.set_xml_tags(tags.to_xml(), headers=headers) |
- |
- def delete_tags(self, headers=None): |
- response = self.connection.make_request('DELETE', self.name, |
- query_args='tagging', |
- headers=headers) |
- body = response.read() |
- boto.log.debug(body) |
- if response.status == 204: |
- return True |
- else: |
- raise self.connection.provider.storage_response_error( |
- response.status, response.reason, body) |