PageRenderTime 52ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/boto-2.5.2/boto/s3/bucket.py

#
Python | 1468 lines | 1437 code | 8 blank | 23 comment | 0 complexity | 32d5d29b8ca53f10325a9b4e6c282c1e MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. # Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
  2. # Copyright (c) 2010, Eucalyptus Systems, Inc.
  3. # All rights reserved.
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a
  6. # copy of this software and associated documentation files (the
  7. # "Software"), to deal in the Software without restriction, including
  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
  10. # persons to whom the Software is furnished to do so, subject to the fol-
  11. # lowing conditions:
  12. #
  13. # The above copyright notice and this permission notice shall be included
  14. # in all copies or substantial portions of the Software.
  15. #
  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-
  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,
  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
  22. # IN THE SOFTWARE.
  23. import boto
  24. from boto import handler
  25. from boto.resultset import ResultSet
  26. from boto.exception import BotoClientError
  27. from boto.s3.acl import Policy, CannedACLStrings, Grant
  28. from boto.s3.key import Key
  29. from boto.s3.prefix import Prefix
  30. from boto.s3.deletemarker import DeleteMarker
  31. from boto.s3.multipart import MultiPartUpload
  32. from boto.s3.multipart import CompleteMultiPartUpload
  33. from boto.s3.multidelete import MultiDeleteResult
  34. from boto.s3.multidelete import Error
  35. from boto.s3.bucketlistresultset import BucketListResultSet
  36. from boto.s3.bucketlistresultset import VersionedBucketListResultSet
  37. from boto.s3.bucketlistresultset import MultiPartUploadListResultSet
  38. from boto.s3.lifecycle import Lifecycle
  39. from boto.s3.bucketlogging import BucketLogging
  40. import boto.jsonresponse
  41. import boto.utils
  42. import xml.sax
  43. import xml.sax.saxutils
  44. import StringIO
  45. import urllib
  46. import re
  47. import base64
  48. from collections import defaultdict
  49. # as per http://goo.gl/BDuud (02/19/2011)
  50. class S3WebsiteEndpointTranslate:
  51. trans_region = defaultdict(lambda :'s3-website-us-east-1')
  52. trans_region['eu-west-1'] = 's3-website-eu-west-1'
  53. trans_region['us-west-1'] = 's3-website-us-west-1'
  54. trans_region['us-west-2'] = 's3-website-us-west-2'
  55. trans_region['sa-east-1'] = 's3-website-sa-east-1'
  56. trans_region['ap-northeast-1'] = 's3-website-ap-northeast-1'
  57. trans_region['ap-southeast-1'] = 's3-website-ap-southeast-1'
  58. @classmethod
  59. def translate_region(self, reg):
  60. return self.trans_region[reg]
  61. S3Permissions = ['READ', 'WRITE', 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL']
  62. class Bucket(object):
  63. LoggingGroup = 'http://acs.amazonaws.com/groups/s3/LogDelivery'
  64. BucketPaymentBody = """<?xml version="1.0" encoding="UTF-8"?>
  65. <RequestPaymentConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  66. <Payer>%s</Payer>
  67. </RequestPaymentConfiguration>"""
  68. VersioningBody = """<?xml version="1.0" encoding="UTF-8"?>
  69. <VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  70. <Status>%s</Status>
  71. <MfaDelete>%s</MfaDelete>
  72. </VersioningConfiguration>"""
  73. WebsiteBody = """<?xml version="1.0" encoding="UTF-8"?>
  74. <WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  75. <IndexDocument><Suffix>%s</Suffix></IndexDocument>
  76. %s
  77. </WebsiteConfiguration>"""
  78. WebsiteErrorFragment = """<ErrorDocument><Key>%s</Key></ErrorDocument>"""
  79. VersionRE = '<Status>([A-Za-z]+)</Status>'
  80. MFADeleteRE = '<MfaDelete>([A-Za-z]+)</MfaDelete>'
  81. def __init__(self, connection=None, name=None, key_class=Key):
  82. self.name = name
  83. self.connection = connection
  84. self.key_class = key_class
  85. def __repr__(self):
  86. return '<Bucket: %s>' % self.name
  87. def __iter__(self):
  88. return iter(BucketListResultSet(self))
  89. def __contains__(self, key_name):
  90. return not (self.get_key(key_name) is None)
  91. def startElement(self, name, attrs, connection):
  92. return None
  93. def endElement(self, name, value, connection):
  94. if name == 'Name':
  95. self.name = value
  96. elif name == 'CreationDate':
  97. self.creation_date = value
  98. else:
  99. setattr(self, name, value)
  100. def set_key_class(self, key_class):
  101. """
  102. Set the Key class associated with this bucket. By default, this
  103. would be the boto.s3.key.Key class but if you want to subclass that
  104. for some reason this allows you to associate your new class with a
  105. bucket so that when you call bucket.new_key() or when you get a listing
  106. of keys in the bucket you will get an instances of your key class
  107. rather than the default.
  108. :type key_class: class
  109. :param key_class: A subclass of Key that can be more specific
  110. """
  111. self.key_class = key_class
  112. def lookup(self, key_name, headers=None):
  113. """
  114. Deprecated: Please use get_key method.
  115. :type key_name: string
  116. :param key_name: The name of the key to retrieve
  117. :rtype: :class:`boto.s3.key.Key`
  118. :returns: A Key object from this bucket.
  119. """
  120. return self.get_key(key_name, headers=headers)
  121. def get_key(self, key_name, headers=None, version_id=None, response_headers=None):
  122. """
  123. Check to see if a particular key exists within the bucket. This
  124. method uses a HEAD request to check for the existance of the key.
  125. Returns: An instance of a Key object or None
  126. :type key_name: string
  127. :param key_name: The name of the key to retrieve
  128. :type response_headers: dict
  129. :param response_headers: A dictionary containing HTTP headers/values
  130. that will override any headers associated with
  131. the stored object in the response.
  132. See http://goo.gl/EWOPb for details.
  133. :rtype: :class:`boto.s3.key.Key`
  134. :returns: A Key object from this bucket.
  135. """
  136. query_args = []
  137. if version_id:
  138. query_args.append('versionId=%s' % version_id)
  139. if response_headers:
  140. for rk, rv in response_headers.iteritems():
  141. query_args.append('%s=%s' % (rk, urllib.quote(rv)))
  142. if query_args:
  143. query_args = '&'.join(query_args)
  144. else:
  145. query_args = None
  146. response = self.connection.make_request('HEAD', self.name, key_name,
  147. headers=headers,
  148. query_args=query_args)
  149. response.read()
  150. # Allow any success status (2xx) - for example this lets us
  151. # support Range gets, which return status 206:
  152. if response.status/100 == 2:
  153. k = self.key_class(self)
  154. provider = self.connection.provider
  155. k.metadata = boto.utils.get_aws_metadata(response.msg, provider)
  156. k.etag = response.getheader('etag')
  157. k.content_type = response.getheader('content-type')
  158. k.content_encoding = response.getheader('content-encoding')
  159. k.content_disposition = response.getheader('content-disposition')
  160. k.content_language = response.getheader('content-language')
  161. k.last_modified = response.getheader('last-modified')
  162. # the following machinations are a workaround to the fact that
  163. # apache/fastcgi omits the content-length header on HEAD
  164. # requests when the content-length is zero.
  165. # See http://goo.gl/0Tdax for more details.
  166. clen = response.getheader('content-length')
  167. if clen:
  168. k.size = int(response.getheader('content-length'))
  169. else:
  170. k.size = 0
  171. k.cache_control = response.getheader('cache-control')
  172. k.name = key_name
  173. k.handle_version_headers(response)
  174. k.handle_encryption_headers(response)
  175. return k
  176. else:
  177. if response.status == 404:
  178. return None
  179. else:
  180. raise self.connection.provider.storage_response_error(
  181. response.status, response.reason, '')
  182. def list(self, prefix='', delimiter='', marker='', headers=None):
  183. """
  184. List key objects within a bucket. This returns an instance of an
  185. BucketListResultSet that automatically handles all of the result
  186. paging, etc. from S3. You just need to keep iterating until
  187. there are no more results.
  188. Called with no arguments, this will return an iterator object across
  189. all keys within the bucket.
  190. The Key objects returned by the iterator are obtained by parsing
  191. the results of a GET on the bucket, also known as the List Objects
  192. request. The XML returned by this request contains only a subset
  193. of the information about each key. Certain metadata fields such
  194. as Content-Type and user metadata are not available in the XML.
  195. Therefore, if you want these additional metadata fields you will
  196. have to do a HEAD request on the Key in the bucket.
  197. :type prefix: string
  198. :param prefix: allows you to limit the listing to a particular
  199. prefix. For example, if you call the method with
  200. prefix='/foo/' then the iterator will only cycle
  201. through the keys that begin with the string '/foo/'.
  202. :type delimiter: string
  203. :param delimiter: can be used in conjunction with the prefix
  204. to allow you to organize and browse your keys
  205. hierarchically. See:
  206. http://docs.amazonwebservices.com/AmazonS3/2006-03-01/
  207. for more details.
  208. :type marker: string
  209. :param marker: The "marker" of where you are in the result set
  210. :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet`
  211. :return: an instance of a BucketListResultSet that handles paging, etc
  212. """
  213. return BucketListResultSet(self, prefix, delimiter, marker, headers)
  214. def list_versions(self, prefix='', delimiter='', key_marker='',
  215. version_id_marker='', headers=None):
  216. """
  217. List version objects within a bucket. This returns an instance of an
  218. VersionedBucketListResultSet that automatically handles all of the result
  219. paging, etc. from S3. You just need to keep iterating until
  220. there are no more results.
  221. Called with no arguments, this will return an iterator object across
  222. all keys within the bucket.
  223. :type prefix: string
  224. :param prefix: allows you to limit the listing to a particular
  225. prefix. For example, if you call the method with
  226. prefix='/foo/' then the iterator will only cycle
  227. through the keys that begin with the string '/foo/'.
  228. :type delimiter: string
  229. :param delimiter: can be used in conjunction with the prefix
  230. to allow you to organize and browse your keys
  231. hierarchically. See:
  232. http://docs.amazonwebservices.com/AmazonS3/2006-03-01/
  233. for more details.
  234. :type marker: string
  235. :param marker: The "marker" of where you are in the result set
  236. :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet`
  237. :return: an instance of a BucketListResultSet that handles paging, etc
  238. """
  239. return VersionedBucketListResultSet(self, prefix, delimiter, key_marker,
  240. version_id_marker, headers)
  241. def list_multipart_uploads(self, key_marker='',
  242. upload_id_marker='',
  243. headers=None):
  244. """
  245. List multipart upload objects within a bucket. This returns an
  246. instance of an MultiPartUploadListResultSet that automatically
  247. handles all of the result paging, etc. from S3. You just need
  248. to keep iterating until there are no more results.
  249. :type marker: string
  250. :param marker: The "marker" of where you are in the result set
  251. :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet`
  252. :return: an instance of a BucketListResultSet that handles paging, etc
  253. """
  254. return MultiPartUploadListResultSet(self, key_marker,
  255. upload_id_marker,
  256. headers)
  257. def _get_all(self, element_map, initial_query_string='',
  258. headers=None, **params):
  259. l = []
  260. for k, v in params.items():
  261. k = k.replace('_', '-')
  262. if k == 'maxkeys':
  263. k = 'max-keys'
  264. if isinstance(v, unicode):
  265. v = v.encode('utf-8')
  266. if v is not None and v != '':
  267. l.append('%s=%s' % (urllib.quote(k), urllib.quote(str(v))))
  268. if len(l):
  269. s = initial_query_string + '&' + '&'.join(l)
  270. else:
  271. s = initial_query_string
  272. response = self.connection.make_request('GET', self.name,
  273. headers=headers,
  274. query_args=s)
  275. body = response.read()
  276. boto.log.debug(body)
  277. if response.status == 200:
  278. rs = ResultSet(element_map)
  279. h = handler.XmlHandler(rs, self)
  280. xml.sax.parseString(body, h)
  281. return rs
  282. else:
  283. raise self.connection.provider.storage_response_error(
  284. response.status, response.reason, body)
  285. def get_all_keys(self, headers=None, **params):
  286. """
  287. A lower-level method for listing contents of a bucket.
  288. This closely models the actual S3 API and requires you to manually
  289. handle the paging of results. For a higher-level method
  290. that handles the details of paging for you, you can use the list method.
  291. :type max_keys: int
  292. :param max_keys: The maximum number of keys to retrieve
  293. :type prefix: string
  294. :param prefix: The prefix of the keys you want to retrieve
  295. :type marker: string
  296. :param marker: The "marker" of where you are in the result set
  297. :type delimiter: string
  298. :param delimiter: If this optional, Unicode string parameter
  299. is included with your request, then keys that
  300. contain the same string between the prefix and
  301. the first occurrence of the delimiter will be
  302. rolled up into a single result element in the
  303. CommonPrefixes collection. These rolled-up keys
  304. are not returned elsewhere in the response.
  305. :rtype: ResultSet
  306. :return: The result from S3 listing the keys requested
  307. """
  308. return self._get_all([('Contents', self.key_class),
  309. ('CommonPrefixes', Prefix)],
  310. '', headers, **params)
  311. def get_all_versions(self, headers=None, **params):
  312. """
  313. A lower-level, version-aware method for listing contents of a bucket.
  314. This closely models the actual S3 API and requires you to manually
  315. handle the paging of results. For a higher-level method
  316. that handles the details of paging for you, you can use the list method.
  317. :type max_keys: int
  318. :param max_keys: The maximum number of keys to retrieve
  319. :type prefix: string
  320. :param prefix: The prefix of the keys you want to retrieve
  321. :type key_marker: string
  322. :param key_marker: The "marker" of where you are in the result set
  323. with respect to keys.
  324. :type version_id_marker: string
  325. :param version_id_marker: The "marker" of where you are in the result
  326. set with respect to version-id's.
  327. :type delimiter: string
  328. :param delimiter: If this optional, Unicode string parameter
  329. is included with your request, then keys that
  330. contain the same string between the prefix and
  331. the first occurrence of the delimiter will be
  332. rolled up into a single result element in the
  333. CommonPrefixes collection. These rolled-up keys
  334. are not returned elsewhere in the response.
  335. :rtype: ResultSet
  336. :return: The result from S3 listing the keys requested
  337. """
  338. return self._get_all([('Version', self.key_class),
  339. ('CommonPrefixes', Prefix),
  340. ('DeleteMarker', DeleteMarker)],
  341. 'versions', headers, **params)
  342. def get_all_multipart_uploads(self, headers=None, **params):
  343. """
  344. A lower-level, version-aware method for listing active
  345. MultiPart uploads for a bucket. This closely models the
  346. actual S3 API and requires you to manually handle the paging
  347. of results. For a higher-level method that handles the
  348. details of paging for you, you can use the list method.
  349. :type max_uploads: int
  350. :param max_uploads: The maximum number of uploads to retrieve.
  351. Default value is 1000.
  352. :type key_marker: string
  353. :param key_marker: Together with upload_id_marker, this parameter
  354. specifies the multipart upload after which listing
  355. should begin. If upload_id_marker is not specified,
  356. only the keys lexicographically greater than the
  357. specified key_marker will be included in the list.
  358. If upload_id_marker is specified, any multipart
  359. uploads for a key equal to the key_marker might
  360. also be included, provided those multipart uploads
  361. have upload IDs lexicographically greater than the
  362. specified upload_id_marker.
  363. :type upload_id_marker: string
  364. :param upload_id_marker: Together with key-marker, specifies
  365. the multipart upload after which listing
  366. should begin. If key_marker is not specified,
  367. the upload_id_marker parameter is ignored.
  368. Otherwise, any multipart uploads for a key
  369. equal to the key_marker might be included
  370. in the list only if they have an upload ID
  371. lexicographically greater than the specified
  372. upload_id_marker.
  373. :rtype: ResultSet
  374. :return: The result from S3 listing the uploads requested
  375. """
  376. return self._get_all([('Upload', MultiPartUpload),
  377. ('CommonPrefixes', Prefix)],
  378. 'uploads', headers, **params)
  379. def new_key(self, key_name=None):
  380. """
  381. Creates a new key
  382. :type key_name: string
  383. :param key_name: The name of the key to create
  384. :rtype: :class:`boto.s3.key.Key` or subclass
  385. :returns: An instance of the newly created key object
  386. """
  387. return self.key_class(self, key_name)
  388. def generate_url(self, expires_in, method='GET', headers=None,
  389. force_http=False, response_headers=None,
  390. expires_in_absolute=False):
  391. return self.connection.generate_url(expires_in, method, self.name,
  392. headers=headers,
  393. force_http=force_http,
  394. response_headers=response_headers,
  395. expires_in_absolute=expires_in_absolute)
  396. def delete_keys(self, keys, quiet=False, mfa_token=None, headers=None):
  397. """
  398. Deletes a set of keys using S3's Multi-object delete API. If a
  399. VersionID is specified for that key then that version is removed.
  400. Returns a MultiDeleteResult Object, which contains Deleted
  401. and Error elements for each key you ask to delete.
  402. :type keys: list
  403. :param keys: A list of either key_names or (key_name, versionid) pairs
  404. or a list of Key instances.
  405. :type quiet: boolean
  406. :param quiet: In quiet mode the response includes only keys where
  407. the delete operation encountered an error. For a
  408. successful deletion, the operation does not return
  409. any information about the delete in the response body.
  410. :type mfa_token: tuple or list of strings
  411. :param mfa_token: A tuple or list consisting of the serial number
  412. from the MFA device and the current value of
  413. the six-digit token associated with the device.
  414. This value is required anytime you are
  415. deleting versioned objects from a bucket
  416. that has the MFADelete option on the bucket.
  417. :returns: An instance of MultiDeleteResult
  418. """
  419. ikeys = iter(keys)
  420. result = MultiDeleteResult(self)
  421. provider = self.connection.provider
  422. query_args = 'delete'
  423. def delete_keys2(hdrs):
  424. hdrs = hdrs or {}
  425. data = u"""<?xml version="1.0" encoding="UTF-8"?>"""
  426. data += u"<Delete>"
  427. if quiet:
  428. data += u"<Quiet>true</Quiet>"
  429. count = 0
  430. while count < 1000:
  431. try:
  432. key = ikeys.next()
  433. except StopIteration:
  434. break
  435. if isinstance(key, basestring):
  436. key_name = key
  437. version_id = None
  438. elif isinstance(key, tuple) and len(key) == 2:
  439. key_name, version_id = key
  440. elif (isinstance(key, Key) or isinstance(key, DeleteMarker)) and key.name:
  441. key_name = key.name
  442. version_id = key.version_id
  443. else:
  444. if isinstance(key, Prefix):
  445. key_name = key.name
  446. code = 'PrefixSkipped' # Don't delete Prefix
  447. else:
  448. key_name = repr(key) # try get a string
  449. code = 'InvalidArgument' # other unknown type
  450. message = 'Invalid. No delete action taken for this object.'
  451. error = Error(key_name, code=code, message=message)
  452. result.errors.append(error)
  453. continue
  454. count += 1
  455. #key_name = key_name.decode('utf-8')
  456. data += u"<Object><Key>%s</Key>" % xml.sax.saxutils.escape(key_name)
  457. if version_id:
  458. data += u"<VersionId>%s</VersionId>" % version_id
  459. data += u"</Object>"
  460. data += u"</Delete>"
  461. if count <= 0:
  462. return False # no more
  463. data = data.encode('utf-8')
  464. fp = StringIO.StringIO(data)
  465. md5 = boto.utils.compute_md5(fp)
  466. hdrs['Content-MD5'] = md5[1]
  467. hdrs['Content-Type'] = 'text/xml'
  468. if mfa_token:
  469. hdrs[provider.mfa_header] = ' '.join(mfa_token)
  470. response = self.connection.make_request('POST', self.name,
  471. headers=hdrs,
  472. query_args=query_args,
  473. data=data)
  474. body = response.read()
  475. if response.status == 200:
  476. h = handler.XmlHandler(result, self)
  477. xml.sax.parseString(body, h)
  478. return count >= 1000 # more?
  479. else:
  480. raise provider.storage_response_error(response.status,
  481. response.reason,
  482. body)
  483. while delete_keys2(headers):
  484. pass
  485. return result
  486. def delete_key(self, key_name, headers=None,
  487. version_id=None, mfa_token=None):
  488. """
  489. Deletes a key from the bucket. If a version_id is provided,
  490. only that version of the key will be deleted.
  491. :type key_name: string
  492. :param key_name: The key name to delete
  493. :type version_id: string
  494. :param version_id: The version ID (optional)
  495. :type mfa_token: tuple or list of strings
  496. :param mfa_token: A tuple or list consisting of the serial number
  497. from the MFA device and the current value of
  498. the six-digit token associated with the device.
  499. This value is required anytime you are
  500. deleting versioned objects from a bucket
  501. that has the MFADelete option on the bucket.
  502. :rtype: :class:`boto.s3.key.Key` or subclass
  503. :returns: A key object holding information on what was deleted.
  504. The Caller can see if a delete_marker was created or
  505. removed and what version_id the delete created or removed.
  506. """
  507. provider = self.connection.provider
  508. if version_id:
  509. query_args = 'versionId=%s' % version_id
  510. else:
  511. query_args = None
  512. if mfa_token:
  513. if not headers:
  514. headers = {}
  515. headers[provider.mfa_header] = ' '.join(mfa_token)
  516. response = self.connection.make_request('DELETE', self.name, key_name,
  517. headers=headers,
  518. query_args=query_args)
  519. body = response.read()
  520. if response.status != 204:
  521. raise provider.storage_response_error(response.status,
  522. response.reason, body)
  523. else:
  524. # return a key object with information on what was deleted.
  525. k = self.key_class(self)
  526. k.name = key_name
  527. k.handle_version_headers(response)
  528. return k
  529. def copy_key(self, new_key_name, src_bucket_name,
  530. src_key_name, metadata=None, src_version_id=None,
  531. storage_class='STANDARD', preserve_acl=False,
  532. encrypt_key=False, headers=None, query_args=None):
  533. """
  534. Create a new key in the bucket by copying another existing key.
  535. :type new_key_name: string
  536. :param new_key_name: The name of the new key
  537. :type src_bucket_name: string
  538. :param src_bucket_name: The name of the source bucket
  539. :type src_key_name: string
  540. :param src_key_name: The name of the source key
  541. :type src_version_id: string
  542. :param src_version_id: The version id for the key. This param
  543. is optional. If not specified, the newest
  544. version of the key will be copied.
  545. :type metadata: dict
  546. :param metadata: Metadata to be associated with new key.
  547. If metadata is supplied, it will replace the
  548. metadata of the source key being copied.
  549. If no metadata is supplied, the source key's
  550. metadata will be copied to the new key.
  551. :type storage_class: string
  552. :param storage_class: The storage class of the new key.
  553. By default, the new key will use the
  554. standard storage class. Possible values are:
  555. STANDARD | REDUCED_REDUNDANCY
  556. :type preserve_acl: bool
  557. :param preserve_acl: If True, the ACL from the source key
  558. will be copied to the destination
  559. key. If False, the destination key
  560. will have the default ACL.
  561. Note that preserving the ACL in the
  562. new key object will require two
  563. additional API calls to S3, one to
  564. retrieve the current ACL and one to
  565. set that ACL on the new object. If
  566. you don't care about the ACL, a value
  567. of False will be significantly more
  568. efficient.
  569. :type encrypt_key: bool
  570. :param encrypt_key: If True, the new copy of the object will
  571. be encrypted on the server-side by S3 and
  572. will be stored in an encrypted form while
  573. at rest in S3.
  574. :type headers: dict
  575. :param headers: A dictionary of header name/value pairs.
  576. :type query_args: string
  577. :param query_args: A string of additional querystring arguments
  578. to append to the request
  579. :rtype: :class:`boto.s3.key.Key` or subclass
  580. :returns: An instance of the newly created key object
  581. """
  582. headers = headers or {}
  583. provider = self.connection.provider
  584. src_key_name = boto.utils.get_utf8_value(src_key_name)
  585. if preserve_acl:
  586. if self.name == src_bucket_name:
  587. src_bucket = self
  588. else:
  589. src_bucket = self.connection.get_bucket(src_bucket_name)
  590. acl = src_bucket.get_xml_acl(src_key_name)
  591. if encrypt_key:
  592. headers[provider.server_side_encryption_header] = 'AES256'
  593. src = '%s/%s' % (src_bucket_name, urllib.quote(src_key_name))
  594. if src_version_id:
  595. src += '?versionId=%s' % src_version_id
  596. headers[provider.copy_source_header] = str(src)
  597. # make sure storage_class_header key exists before accessing it
  598. if provider.storage_class_header and storage_class:
  599. headers[provider.storage_class_header] = storage_class
  600. if metadata:
  601. headers[provider.metadata_directive_header] = 'REPLACE'
  602. headers = boto.utils.merge_meta(headers, metadata, provider)
  603. elif not query_args: # Can't use this header with multi-part copy.
  604. headers[provider.metadata_directive_header] = 'COPY'
  605. response = self.connection.make_request('PUT', self.name, new_key_name,
  606. headers=headers,
  607. query_args=query_args)
  608. body = response.read()
  609. if response.status == 200:
  610. key = self.new_key(new_key_name)
  611. h = handler.XmlHandler(key, self)
  612. xml.sax.parseString(body, h)
  613. if hasattr(key, 'Error'):
  614. raise provider.storage_copy_error(key.Code, key.Message, body)
  615. key.handle_version_headers(response)
  616. if preserve_acl:
  617. self.set_xml_acl(acl, new_key_name)
  618. return key
  619. else:
  620. raise provider.storage_response_error(response.status,
  621. response.reason, body)
  622. def set_canned_acl(self, acl_str, key_name='', headers=None,
  623. version_id=None):
  624. assert acl_str in CannedACLStrings
  625. if headers:
  626. headers[self.connection.provider.acl_header] = acl_str
  627. else:
  628. headers={self.connection.provider.acl_header: acl_str}
  629. query_args = 'acl'
  630. if version_id:
  631. query_args += '&versionId=%s' % version_id
  632. response = self.connection.make_request('PUT', self.name, key_name,
  633. headers=headers, query_args=query_args)
  634. body = response.read()
  635. if response.status != 200:
  636. raise self.connection.provider.storage_response_error(
  637. response.status, response.reason, body)
  638. def get_xml_acl(self, key_name='', headers=None, version_id=None):
  639. query_args = 'acl'
  640. if version_id:
  641. query_args += '&versionId=%s' % version_id
  642. response = self.connection.make_request('GET', self.name, key_name,
  643. query_args=query_args,
  644. headers=headers)
  645. body = response.read()
  646. if response.status != 200:
  647. raise self.connection.provider.storage_response_error(
  648. response.status, response.reason, body)
  649. return body
  650. def set_xml_acl(self, acl_str, key_name='', headers=None, version_id=None,
  651. query_args='acl'):
  652. if version_id:
  653. query_args += '&versionId=%s' % version_id
  654. response = self.connection.make_request('PUT', self.name, key_name,
  655. data=acl_str.encode('UTF-8'),
  656. query_args=query_args,
  657. headers=headers)
  658. body = response.read()
  659. if response.status != 200:
  660. raise self.connection.provider.storage_response_error(
  661. response.status, response.reason, body)
  662. def set_acl(self, acl_or_str, key_name='', headers=None, version_id=None):
  663. if isinstance(acl_or_str, Policy):
  664. self.set_xml_acl(acl_or_str.to_xml(), key_name,
  665. headers, version_id)
  666. else:
  667. self.set_canned_acl(acl_or_str, key_name,
  668. headers, version_id)
  669. def get_acl(self, key_name='', headers=None, version_id=None):
  670. query_args = 'acl'
  671. if version_id:
  672. query_args += '&versionId=%s' % version_id
  673. response = self.connection.make_request('GET', self.name, key_name,
  674. query_args=query_args,
  675. headers=headers)
  676. body = response.read()
  677. if response.status == 200:
  678. policy = Policy(self)
  679. h = handler.XmlHandler(policy, self)
  680. xml.sax.parseString(body, h)
  681. return policy
  682. else:
  683. raise self.connection.provider.storage_response_error(
  684. response.status, response.reason, body)
  685. def set_subresource(self, subresource, value, key_name = '', headers=None,
  686. version_id=None):
  687. """
  688. Set a subresource for a bucket or key.
  689. :type subresource: string
  690. :param subresource: The subresource to set.
  691. :type value: string
  692. :param value: The value of the subresource.
  693. :type key_name: string
  694. :param key_name: The key to operate on, or None to operate on the
  695. bucket.
  696. :type headers: dict
  697. :param headers: Additional HTTP headers to include in the request.
  698. :type src_version_id: string
  699. :param src_version_id: Optional. The version id of the key to operate
  700. on. If not specified, operate on the newest
  701. version.
  702. """
  703. if not subresource:
  704. raise TypeError('set_subresource called with subresource=None')
  705. query_args = subresource
  706. if version_id:
  707. query_args += '&versionId=%s' % version_id
  708. response = self.connection.make_request('PUT', self.name, key_name,
  709. data=value.encode('UTF-8'),
  710. query_args=query_args,
  711. headers=headers)
  712. body = response.read()
  713. if response.status != 200:
  714. raise self.connection.provider.storage_response_error(
  715. response.status, response.reason, body)
  716. def get_subresource(self, subresource, key_name='', headers=None,
  717. version_id=None):
  718. """
  719. Get a subresource for a bucket or key.
  720. :type subresource: string
  721. :param subresource: The subresource to get.
  722. :type key_name: string
  723. :param key_name: The key to operate on, or None to operate on the
  724. bucket.
  725. :type headers: dict
  726. :param headers: Additional HTTP headers to include in the request.
  727. :type src_version_id: string
  728. :param src_version_id: Optional. The version id of the key to operate
  729. on. If not specified, operate on the newest
  730. version.
  731. :rtype: string
  732. :returns: The value of the subresource.
  733. """
  734. if not subresource:
  735. raise TypeError('get_subresource called with subresource=None')
  736. query_args = subresource
  737. if version_id:
  738. query_args += '&versionId=%s' % version_id
  739. response = self.connection.make_request('GET', self.name, key_name,
  740. query_args=query_args,
  741. headers=headers)
  742. body = response.read()
  743. if response.status != 200:
  744. raise self.connection.provider.storage_response_error(
  745. response.status, response.reason, body)
  746. return body
  747. def make_public(self, recursive=False, headers=None):
  748. self.set_canned_acl('public-read', headers=headers)
  749. if recursive:
  750. for key in self:
  751. self.set_canned_acl('public-read', key.name, headers=headers)
  752. def add_email_grant(self, permission, email_address,
  753. recursive=False, headers=None):
  754. """
  755. Convenience method that provides a quick way to add an email grant
  756. to a bucket. This method retrieves the current ACL, creates a new
  757. grant based on the parameters passed in, adds that grant to the ACL
  758. and then PUT's the new ACL back to S3.
  759. :type permission: string
  760. :param permission: The permission being granted. Should be one of:
  761. (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL).
  762. :type email_address: string
  763. :param email_address: The email address associated with the AWS
  764. account your are granting the permission to.
  765. :type recursive: boolean
  766. :param recursive: A boolean value to controls whether the command
  767. will apply the grant to all keys within the bucket
  768. or not. The default value is False. By passing a
  769. True value, the call will iterate through all keys
  770. in the bucket and apply the same grant to each key.
  771. CAUTION: If you have a lot of keys, this could take
  772. a long time!
  773. """
  774. if permission not in S3Permissions:
  775. raise self.connection.provider.storage_permissions_error(
  776. 'Unknown Permission: %s' % permission)
  777. policy = self.get_acl(headers=headers)
  778. policy.acl.add_email_grant(permission, email_address)
  779. self.set_acl(policy, headers=headers)
  780. if recursive:
  781. for key in self:
  782. key.add_email_grant(permission, email_address, headers=headers)
  783. def add_user_grant(self, permission, user_id, recursive=False,
  784. headers=None, display_name=None):
  785. """
  786. Convenience method that provides a quick way to add a canonical
  787. user grant to a bucket. This method retrieves the current ACL,
  788. creates a new grant based on the parameters passed in, adds that
  789. grant to the ACL and then PUT's the new ACL back to S3.
  790. :type permission: string
  791. :param permission: The permission being granted. Should be one of:
  792. (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL).
  793. :type user_id: string
  794. :param user_id: The canonical user id associated with the AWS
  795. account your are granting the permission to.
  796. :type recursive: boolean
  797. :param recursive: A boolean value to controls whether the command
  798. will apply the grant to all keys within the bucket
  799. or not. The default value is False. By passing a
  800. True value, the call will iterate through all keys
  801. in the bucket and apply the same grant to each key.
  802. CAUTION: If you have a lot of keys, this could take
  803. a long time!
  804. :type display_name: string
  805. :param display_name: An option string containing the user's
  806. Display Name. Only required on Walrus.
  807. """
  808. if permission not in S3Permissions:
  809. raise self.connection.provider.storage_permissions_error(
  810. 'Unknown Permission: %s' % permission)
  811. policy = self.get_acl(headers=headers)
  812. policy.acl.add_user_grant(permission, user_id,
  813. display_name=display_name)
  814. self.set_acl(policy, headers=headers)
  815. if recursive:
  816. for key in self:
  817. key.add_user_grant(permission, user_id, headers=headers,
  818. display_name=display_name)
  819. def list_grants(self, headers=None):
  820. policy = self.get_acl(headers=headers)
  821. return policy.acl.grants
  822. def get_location(self):
  823. """
  824. Returns the LocationConstraint for the bucket.
  825. :rtype: str
  826. :return: The LocationConstraint for the bucket or the empty
  827. string if no constraint was specified when bucket
  828. was created.
  829. """
  830. response = self.connection.make_request('GET', self.name,
  831. query_args='location')
  832. body = response.read()
  833. if response.status == 200:
  834. rs = ResultSet(self)
  835. h = handler.XmlHandler(rs, self)
  836. xml.sax.parseString(body, h)
  837. return rs.LocationConstraint
  838. else:
  839. raise self.connection.provider.storage_response_error(
  840. response.status, response.reason, body)
  841. def set_xml_logging(self, logging_str, headers=None):
  842. """
  843. Set logging on a bucket directly to the given xml string.
  844. :type logging_str: unicode string
  845. :param logging_str: The XML for the bucketloggingstatus which will be set.
  846. The string will be converted to utf-8 before it is sent.
  847. Usually, you will obtain this XML from the BucketLogging
  848. object.
  849. :rtype: bool
  850. :return: True if ok or raises an exception.
  851. """
  852. body = logging_str.encode('utf-8')
  853. response = self.connection.make_request('PUT', self.name, data=body,
  854. query_args='logging', headers=headers)
  855. body = response.read()
  856. if response.status == 200:
  857. return True
  858. else:
  859. raise self.connection.provider.storage_response_error(
  860. response.status, response.reason, body)
  861. def enable_logging(self, target_bucket, target_prefix='', grants=None, headers=None):
  862. """
  863. Enable logging on a bucket.
  864. :type target_bucket: bucket or string
  865. :param target_bucket: The bucket to log to.
  866. :type target_prefix: string
  867. :param target_prefix: The prefix which should be prepended to the
  868. generated log files written to the target_bucket.
  869. :type grants: list of Grant objects
  870. :param grants: A list of extra permissions which will be granted on
  871. the log files which are created.
  872. :rtype: bool
  873. :return: True if ok or raises an exception.
  874. """
  875. if isinstance(target_bucket, Bucket):
  876. target_bucket = target_bucket.name
  877. blogging = BucketLogging(target=target_bucket, prefix=target_prefix, grants=grants)
  878. return self.set_xml_logging(blogging.to_xml(), headers=headers)
  879. def disable_logging(self, headers=None):
  880. """
  881. Disable logging on a bucket.
  882. :rtype: bool
  883. :return: True if ok or raises an exception.
  884. """
  885. blogging = BucketLogging()
  886. return self.set_xml_logging(blogging.to_xml(), headers=headers)
  887. def get_logging_status(self, headers=None):
  888. """
  889. Get the logging status for this bucket.
  890. :rtype: :class:`boto.s3.bucketlogging.BucketLogging`
  891. :return: A BucketLogging object for this bucket.
  892. """
  893. response = self.connection.make_request('GET', self.name,
  894. query_args='logging', headers=headers)
  895. body = response.read()
  896. if response.status == 200:
  897. blogging = BucketLogging()
  898. h = handler.XmlHandler(blogging, self)
  899. xml.sax.parseString(body, h)
  900. return blogging
  901. else:
  902. raise self.connection.provider.storage_response_error(
  903. response.status, response.reason, body)
  904. def set_as_logging_target(self, headers=None):
  905. """
  906. Setup the current bucket as a logging target by granting the necessary
  907. permissions to the LogDelivery group to write log files to this bucket.
  908. """
  909. policy = self.get_acl(headers=headers)
  910. g1 = Grant(permission='WRITE', type='Group', uri=self.LoggingGroup)
  911. g2 = Grant(permission='READ_ACP', type='Group', uri=self.LoggingGroup)
  912. policy.acl.add_grant(g1)
  913. policy.acl.add_grant(g2)
  914. self.set_acl(policy, headers=headers)
  915. def get_request_payment(self, headers=None):
  916. response = self.connection.make_request('GET', self.name,
  917. query_args='requestPayment', headers=headers)
  918. body = response.read()
  919. if response.status == 200:
  920. return body
  921. else:
  922. raise self.connection.provider.storage_response_error(
  923. response.status, response.reason, body)
  924. def set_request_payment(self, payer='BucketOwner', headers=None):
  925. body = self.BucketPaymentBody % payer
  926. response = self.connection.make_request('PUT', self.name, data=body,
  927. query_args='requestPayment', headers=headers)
  928. body = response.read()
  929. if response.status == 200:
  930. return True
  931. else:
  932. raise self.connection.provider.storage_response_error(
  933. response.status, response.reason, body)
  934. def configure_versioning(self, versioning, mfa_delete=False,
  935. mfa_token=None, headers=None):
  936. """
  937. Configure versioning for this bucket.
  938. ..note:: This feature is currently in beta.
  939. :type versioning: bool
  940. :param versioning: A boolean indicating whether version is
  941. enabled (True) or disabled (False).
  942. :type mfa_delete: bool
  943. :param mfa_delete: A boolean indicating whether the Multi-Factor
  944. Authentication Delete feature is enabled (True)
  945. or disabled (False). If mfa_delete is enabled
  946. then all Delete operations will require the
  947. token from your MFA device to be passed in
  948. the request.
  949. :type mfa_token: tuple or list of strings
  950. :param mfa_token: A tuple or list consisting of the serial number
  951. from the MFA device and the current value of
  952. the six-digit token associated with the device.
  953. This value is required when you are changing
  954. the status of the MfaDelete property of
  955. the bucket.
  956. """
  957. if versioning:
  958. ver = 'Enabled'
  959. else:
  960. ver = 'Suspended'
  961. if mfa_delete:
  962. mfa = 'Enabled'
  963. else:
  964. mfa = 'Disabled'
  965. body = self.VersioningBody % (ver, mfa)
  966. if mfa_token:
  967. if not headers:
  968. headers = {}
  969. provider = self.connection.provider
  970. headers[provider.mfa_header] = ' '.join(mfa_token)
  971. response = self.connection.make_request('PUT', self.name, data=body,
  972. query_args='versioning', headers=headers)
  973. body = response.read()
  974. if response.status == 200:
  975. return True
  976. else:
  977. raise self.connection.provider.storage_response_error(
  978. response.sta

Large files files are truncated, but you can click here to view the full file