PageRenderTime 56ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/boto/gs/bucket.py

https://gitlab.com/smoke.torez/python-mysql-dump-to-amazon-s3
Python | 988 lines | 892 code | 21 blank | 75 comment | 17 complexity | d9467dc448036c8a9af3cb37d8b9ee8d MD5 | raw file
  1. # Copyright 2010 Google Inc.
  2. #
  3. # Permission is hereby granted, free of charge, to any person obtaining a
  4. # copy of this software and associated documentation files (the
  5. # "Software"), to deal in the Software without restriction, including
  6. # without limitation the rights to use, copy, modify, merge, publish, dis-
  7. # tribute, sublicense, and/or sell copies of the Software, and to permit
  8. # persons to whom the Software is furnished to do so, subject to the fol-
  9. # lowing conditions:
  10. #
  11. # The above copyright notice and this permission notice shall be included
  12. # in all copies or substantial portions of the Software.
  13. #
  14. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  15. # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
  16. # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
  17. # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  18. # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. # IN THE SOFTWARE.
  21. import re
  22. import urllib
  23. import xml.sax
  24. import boto
  25. from boto import handler
  26. from boto.resultset import ResultSet
  27. from boto.exception import GSResponseError
  28. from boto.exception import InvalidAclError
  29. from boto.gs.acl import ACL, CannedACLStrings
  30. from boto.gs.acl import SupportedPermissions as GSPermissions
  31. from boto.gs.bucketlistresultset import VersionedBucketListResultSet
  32. from boto.gs.cors import Cors
  33. from boto.gs.lifecycle import LifecycleConfig
  34. from boto.gs.key import Key as GSKey
  35. from boto.s3.acl import Policy
  36. from boto.s3.bucket import Bucket as S3Bucket
  37. from boto.utils import get_utf8_value
  38. # constants for http query args
  39. DEF_OBJ_ACL = 'defaultObjectAcl'
  40. STANDARD_ACL = 'acl'
  41. CORS_ARG = 'cors'
  42. LIFECYCLE_ARG = 'lifecycle'
  43. ERROR_DETAILS_REGEX = re.compile(r'<Details>(?P<details>.*)</Details>')
  44. class Bucket(S3Bucket):
  45. """Represents a Google Cloud Storage bucket."""
  46. VersioningBody = ('<?xml version="1.0" encoding="UTF-8"?>\n'
  47. '<VersioningConfiguration><Status>%s</Status>'
  48. '</VersioningConfiguration>')
  49. WebsiteBody = ('<?xml version="1.0" encoding="UTF-8"?>\n'
  50. '<WebsiteConfiguration>%s%s</WebsiteConfiguration>')
  51. WebsiteMainPageFragment = '<MainPageSuffix>%s</MainPageSuffix>'
  52. WebsiteErrorFragment = '<NotFoundPage>%s</NotFoundPage>'
  53. def __init__(self, connection=None, name=None, key_class=GSKey):
  54. super(Bucket, self).__init__(connection, name, key_class)
  55. def startElement(self, name, attrs, connection):
  56. return None
  57. def endElement(self, name, value, connection):
  58. if name == 'Name':
  59. self.name = value
  60. elif name == 'CreationDate':
  61. self.creation_date = value
  62. else:
  63. setattr(self, name, value)
  64. def get_key(self, key_name, headers=None, version_id=None,
  65. response_headers=None, generation=None):
  66. """Returns a Key instance for an object in this bucket.
  67. Note that this method uses a HEAD request to check for the existence of
  68. the key.
  69. :type key_name: string
  70. :param key_name: The name of the key to retrieve
  71. :type response_headers: dict
  72. :param response_headers: A dictionary containing HTTP
  73. headers/values that will override any headers associated
  74. with the stored object in the response. See
  75. http://goo.gl/06N3b for details.
  76. :type version_id: string
  77. :param version_id: Unused in this subclass.
  78. :type generation: int
  79. :param generation: A specific generation number to fetch the key at. If
  80. not specified, the latest generation is fetched.
  81. :rtype: :class:`boto.gs.key.Key`
  82. :returns: A Key object from this bucket.
  83. """
  84. query_args_l = []
  85. if generation:
  86. query_args_l.append('generation=%s' % generation)
  87. if response_headers:
  88. for rk, rv in response_headers.iteritems():
  89. query_args_l.append('%s=%s' % (rk, urllib.quote(rv)))
  90. try:
  91. key, resp = self._get_key_internal(key_name, headers,
  92. query_args_l=query_args_l)
  93. except GSResponseError, e:
  94. if e.status == 403 and 'Forbidden' in e.reason:
  95. # If we failed getting an object, let the user know which object
  96. # failed rather than just returning a generic 403.
  97. e.reason = ("Access denied to 'gs://%s/%s'." %
  98. (self.name, key_name))
  99. raise
  100. return key
  101. def copy_key(self, new_key_name, src_bucket_name, src_key_name,
  102. metadata=None, src_version_id=None, storage_class='STANDARD',
  103. preserve_acl=False, encrypt_key=False, headers=None,
  104. query_args=None, src_generation=None):
  105. """Create a new key in the bucket by copying an existing key.
  106. :type new_key_name: string
  107. :param new_key_name: The name of the new key
  108. :type src_bucket_name: string
  109. :param src_bucket_name: The name of the source bucket
  110. :type src_key_name: string
  111. :param src_key_name: The name of the source key
  112. :type src_generation: int
  113. :param src_generation: The generation number of the source key to copy.
  114. If not specified, the latest generation is copied.
  115. :type metadata: dict
  116. :param metadata: Metadata to be associated with new key. If
  117. metadata is supplied, it will replace the metadata of the
  118. source key being copied. If no metadata is supplied, the
  119. source key's metadata will be copied to the new key.
  120. :type version_id: string
  121. :param version_id: Unused in this subclass.
  122. :type storage_class: string
  123. :param storage_class: The storage class of the new key. By
  124. default, the new key will use the standard storage class.
  125. Possible values are: STANDARD | DURABLE_REDUCED_AVAILABILITY
  126. :type preserve_acl: bool
  127. :param preserve_acl: If True, the ACL from the source key will
  128. be copied to the destination key. If False, the
  129. destination key will have the default ACL. Note that
  130. preserving the ACL in the new key object will require two
  131. additional API calls to GCS, one to retrieve the current
  132. ACL and one to set that ACL on the new object. If you
  133. don't care about the ACL (or if you have a default ACL set
  134. on the bucket), a value of False will be significantly more
  135. efficient.
  136. :type encrypt_key: bool
  137. :param encrypt_key: Included for compatibility with S3. This argument is
  138. ignored.
  139. :type headers: dict
  140. :param headers: A dictionary of header name/value pairs.
  141. :type query_args: string
  142. :param query_args: A string of additional querystring arguments
  143. to append to the request
  144. :rtype: :class:`boto.gs.key.Key`
  145. :returns: An instance of the newly created key object
  146. """
  147. if src_generation:
  148. headers = headers or {}
  149. headers['x-goog-copy-source-generation'] = str(src_generation)
  150. return super(Bucket, self).copy_key(
  151. new_key_name, src_bucket_name, src_key_name, metadata=metadata,
  152. storage_class=storage_class, preserve_acl=preserve_acl,
  153. encrypt_key=encrypt_key, headers=headers, query_args=query_args)
  154. def list_versions(self, prefix='', delimiter='', marker='',
  155. generation_marker='', headers=None):
  156. """
  157. List versioned objects within a bucket. This returns an
  158. instance of an VersionedBucketListResultSet that automatically
  159. handles all of the result paging, etc. from GCS. You just need
  160. to keep iterating until there are no more results. Called
  161. with no arguments, this will return an iterator object across
  162. all keys within the bucket.
  163. :type prefix: string
  164. :param prefix: allows you to limit the listing to a particular
  165. prefix. For example, if you call the method with
  166. prefix='/foo/' then the iterator will only cycle through
  167. the keys that begin with the string '/foo/'.
  168. :type delimiter: string
  169. :param delimiter: can be used in conjunction with the prefix
  170. to allow you to organize and browse your keys
  171. hierarchically. See:
  172. https://developers.google.com/storage/docs/reference-headers#delimiter
  173. for more details.
  174. :type marker: string
  175. :param marker: The "marker" of where you are in the result set
  176. :type generation_marker: string
  177. :param generation_marker: The "generation marker" of where you are in
  178. the result set.
  179. :type headers: dict
  180. :param headers: A dictionary of header name/value pairs.
  181. :rtype:
  182. :class:`boto.gs.bucketlistresultset.VersionedBucketListResultSet`
  183. :return: an instance of a BucketListResultSet that handles paging, etc.
  184. """
  185. return VersionedBucketListResultSet(self, prefix, delimiter,
  186. marker, generation_marker,
  187. headers)
  188. def validate_get_all_versions_params(self, params):
  189. """
  190. See documentation in boto/s3/bucket.py.
  191. """
  192. self.validate_kwarg_names(params,
  193. ['version_id_marker', 'delimiter', 'marker',
  194. 'generation_marker', 'prefix', 'max_keys'])
  195. def delete_key(self, key_name, headers=None, version_id=None,
  196. mfa_token=None, generation=None):
  197. """
  198. Deletes a key from the bucket.
  199. :type key_name: string
  200. :param key_name: The key name to delete
  201. :type headers: dict
  202. :param headers: A dictionary of header name/value pairs.
  203. :type version_id: string
  204. :param version_id: Unused in this subclass.
  205. :type mfa_token: tuple or list of strings
  206. :param mfa_token: Unused in this subclass.
  207. :type generation: int
  208. :param generation: The generation number of the key to delete. If not
  209. specified, the latest generation number will be deleted.
  210. :rtype: :class:`boto.gs.key.Key`
  211. :returns: A key object holding information on what was
  212. deleted.
  213. """
  214. query_args_l = []
  215. if generation:
  216. query_args_l.append('generation=%s' % generation)
  217. self._delete_key_internal(key_name, headers=headers,
  218. version_id=version_id, mfa_token=mfa_token,
  219. query_args_l=query_args_l)
  220. def set_acl(self, acl_or_str, key_name='', headers=None, version_id=None,
  221. generation=None, if_generation=None, if_metageneration=None):
  222. """Sets or changes a bucket's or key's ACL.
  223. :type acl_or_str: string or :class:`boto.gs.acl.ACL`
  224. :param acl_or_str: A canned ACL string (see
  225. :data:`~.gs.acl.CannedACLStrings`) or an ACL object.
  226. :type key_name: string
  227. :param key_name: A key name within the bucket to set the ACL for. If not
  228. specified, the ACL for the bucket will be set.
  229. :type headers: dict
  230. :param headers: Additional headers to set during the request.
  231. :type version_id: string
  232. :param version_id: Unused in this subclass.
  233. :type generation: int
  234. :param generation: If specified, sets the ACL for a specific generation
  235. of a versioned object. If not specified, the current version is
  236. modified.
  237. :type if_generation: int
  238. :param if_generation: (optional) If set to a generation number, the acl
  239. will only be updated if its current generation number is this value.
  240. :type if_metageneration: int
  241. :param if_metageneration: (optional) If set to a metageneration number,
  242. the acl will only be updated if its current metageneration number is
  243. this value.
  244. """
  245. if isinstance(acl_or_str, Policy):
  246. raise InvalidAclError('Attempt to set S3 Policy on GS ACL')
  247. elif isinstance(acl_or_str, ACL):
  248. self.set_xml_acl(acl_or_str.to_xml(), key_name, headers=headers,
  249. generation=generation,
  250. if_generation=if_generation,
  251. if_metageneration=if_metageneration)
  252. else:
  253. self.set_canned_acl(acl_or_str, key_name, headers=headers,
  254. generation=generation,
  255. if_generation=if_generation,
  256. if_metageneration=if_metageneration)
  257. def set_def_acl(self, acl_or_str, headers=None):
  258. """Sets or changes a bucket's default ACL.
  259. :type acl_or_str: string or :class:`boto.gs.acl.ACL`
  260. :param acl_or_str: A canned ACL string (see
  261. :data:`~.gs.acl.CannedACLStrings`) or an ACL object.
  262. :type headers: dict
  263. :param headers: Additional headers to set during the request.
  264. """
  265. if isinstance(acl_or_str, Policy):
  266. raise InvalidAclError('Attempt to set S3 Policy on GS ACL')
  267. elif isinstance(acl_or_str, ACL):
  268. self.set_def_xml_acl(acl_or_str.to_xml(), headers=headers)
  269. else:
  270. self.set_def_canned_acl(acl_or_str, headers=headers)
  271. def _get_xml_acl_helper(self, key_name, headers, query_args):
  272. """Provides common functionality for get_xml_acl and _get_acl_helper."""
  273. response = self.connection.make_request('GET', self.name, key_name,
  274. query_args=query_args,
  275. headers=headers)
  276. body = response.read()
  277. if response.status != 200:
  278. if response.status == 403:
  279. match = ERROR_DETAILS_REGEX.search(body)
  280. details = match.group('details') if match else None
  281. if details:
  282. details = (('<Details>%s. Note that Full Control access'
  283. ' is required to access ACLs.</Details>') %
  284. details)
  285. body = re.sub(ERROR_DETAILS_REGEX, details, body)
  286. raise self.connection.provider.storage_response_error(
  287. response.status, response.reason, body)
  288. return body
  289. def _get_acl_helper(self, key_name, headers, query_args):
  290. """Provides common functionality for get_acl and get_def_acl."""
  291. body = self._get_xml_acl_helper(key_name, headers, query_args)
  292. acl = ACL(self)
  293. h = handler.XmlHandler(acl, self)
  294. xml.sax.parseString(body, h)
  295. return acl
  296. def get_acl(self, key_name='', headers=None, version_id=None,
  297. generation=None):
  298. """Returns the ACL of the bucket or an object in the bucket.
  299. :param str key_name: The name of the object to get the ACL for. If not
  300. specified, the ACL for the bucket will be returned.
  301. :param dict headers: Additional headers to set during the request.
  302. :type version_id: string
  303. :param version_id: Unused in this subclass.
  304. :param int generation: If specified, gets the ACL for a specific
  305. generation of a versioned object. If not specified, the current
  306. version is returned. This parameter is only valid when retrieving
  307. the ACL of an object, not a bucket.
  308. :rtype: :class:`.gs.acl.ACL`
  309. """
  310. query_args = STANDARD_ACL
  311. if generation:
  312. query_args += '&generation=%s' % generation
  313. return self._get_acl_helper(key_name, headers, query_args)
  314. def get_xml_acl(self, key_name='', headers=None, version_id=None,
  315. generation=None):
  316. """Returns the ACL string of the bucket or an object in the bucket.
  317. :param str key_name: The name of the object to get the ACL for. If not
  318. specified, the ACL for the bucket will be returned.
  319. :param dict headers: Additional headers to set during the request.
  320. :type version_id: string
  321. :param version_id: Unused in this subclass.
  322. :param int generation: If specified, gets the ACL for a specific
  323. generation of a versioned object. If not specified, the current
  324. version is returned. This parameter is only valid when retrieving
  325. the ACL of an object, not a bucket.
  326. :rtype: str
  327. """
  328. query_args = STANDARD_ACL
  329. if generation:
  330. query_args += '&generation=%s' % generation
  331. return self._get_xml_acl_helper(key_name, headers, query_args)
  332. def get_def_acl(self, headers=None):
  333. """Returns the bucket's default ACL.
  334. :param dict headers: Additional headers to set during the request.
  335. :rtype: :class:`.gs.acl.ACL`
  336. """
  337. return self._get_acl_helper('', headers, DEF_OBJ_ACL)
  338. def _set_acl_helper(self, acl_or_str, key_name, headers, query_args,
  339. generation, if_generation, if_metageneration,
  340. canned=False):
  341. """Provides common functionality for set_acl, set_xml_acl,
  342. set_canned_acl, set_def_acl, set_def_xml_acl, and
  343. set_def_canned_acl()."""
  344. headers = headers or {}
  345. data = ''
  346. if canned:
  347. headers[self.connection.provider.acl_header] = acl_or_str
  348. else:
  349. data = acl_or_str
  350. if generation:
  351. query_args += '&generation=%s' % generation
  352. if if_metageneration is not None and if_generation is None:
  353. raise ValueError("Received if_metageneration argument with no "
  354. "if_generation argument. A metageneration has no "
  355. "meaning without a content generation.")
  356. if not key_name and (if_generation or if_metageneration):
  357. raise ValueError("Received if_generation or if_metageneration "
  358. "parameter while setting the ACL of a bucket.")
  359. if if_generation is not None:
  360. headers['x-goog-if-generation-match'] = str(if_generation)
  361. if if_metageneration is not None:
  362. headers['x-goog-if-metageneration-match'] = str(if_metageneration)
  363. response = self.connection.make_request(
  364. 'PUT', get_utf8_value(self.name), get_utf8_value(key_name),
  365. data=get_utf8_value(data), headers=headers, query_args=query_args)
  366. body = response.read()
  367. if response.status != 200:
  368. raise self.connection.provider.storage_response_error(
  369. response.status, response.reason, body)
  370. def set_xml_acl(self, acl_str, key_name='', headers=None, version_id=None,
  371. query_args='acl', generation=None, if_generation=None,
  372. if_metageneration=None):
  373. """Sets a bucket's or objects's ACL to an XML string.
  374. :type acl_str: string
  375. :param acl_str: A string containing the ACL XML.
  376. :type key_name: string
  377. :param key_name: A key name within the bucket to set the ACL for. If not
  378. specified, the ACL for the bucket will be set.
  379. :type headers: dict
  380. :param headers: Additional headers to set during the request.
  381. :type version_id: string
  382. :param version_id: Unused in this subclass.
  383. :type query_args: str
  384. :param query_args: The query parameters to pass with the request.
  385. :type generation: int
  386. :param generation: If specified, sets the ACL for a specific generation
  387. of a versioned object. If not specified, the current version is
  388. modified.
  389. :type if_generation: int
  390. :param if_generation: (optional) If set to a generation number, the acl
  391. will only be updated if its current generation number is this value.
  392. :type if_metageneration: int
  393. :param if_metageneration: (optional) If set to a metageneration number,
  394. the acl will only be updated if its current metageneration number is
  395. this value.
  396. """
  397. return self._set_acl_helper(acl_str, key_name=key_name, headers=headers,
  398. query_args=query_args,
  399. generation=generation,
  400. if_generation=if_generation,
  401. if_metageneration=if_metageneration)
  402. def set_canned_acl(self, acl_str, key_name='', headers=None,
  403. version_id=None, generation=None, if_generation=None,
  404. if_metageneration=None):
  405. """Sets a bucket's or objects's ACL using a predefined (canned) value.
  406. :type acl_str: string
  407. :param acl_str: A canned ACL string. See
  408. :data:`~.gs.acl.CannedACLStrings`.
  409. :type key_name: string
  410. :param key_name: A key name within the bucket to set the ACL for. If not
  411. specified, the ACL for the bucket will be set.
  412. :type headers: dict
  413. :param headers: Additional headers to set during the request.
  414. :type version_id: string
  415. :param version_id: Unused in this subclass.
  416. :type generation: int
  417. :param generation: If specified, sets the ACL for a specific generation
  418. of a versioned object. If not specified, the current version is
  419. modified.
  420. :type if_generation: int
  421. :param if_generation: (optional) If set to a generation number, the acl
  422. will only be updated if its current generation number is this value.
  423. :type if_metageneration: int
  424. :param if_metageneration: (optional) If set to a metageneration number,
  425. the acl will only be updated if its current metageneration number is
  426. this value.
  427. """
  428. if acl_str not in CannedACLStrings:
  429. raise ValueError("Provided canned ACL string (%s) is not valid."
  430. % acl_str)
  431. query_args = STANDARD_ACL
  432. return self._set_acl_helper(acl_str, key_name, headers, query_args,
  433. generation, if_generation,
  434. if_metageneration, canned=True)
  435. def set_def_canned_acl(self, acl_str, headers=None):
  436. """Sets a bucket's default ACL using a predefined (canned) value.
  437. :type acl_str: string
  438. :param acl_str: A canned ACL string. See
  439. :data:`~.gs.acl.CannedACLStrings`.
  440. :type headers: dict
  441. :param headers: Additional headers to set during the request.
  442. """
  443. if acl_str not in CannedACLStrings:
  444. raise ValueError("Provided canned ACL string (%s) is not valid."
  445. % acl_str)
  446. query_args = DEF_OBJ_ACL
  447. return self._set_acl_helper(acl_str, '', headers, query_args,
  448. generation=None, if_generation=None,
  449. if_metageneration=None, canned=True)
  450. def set_def_xml_acl(self, acl_str, headers=None):
  451. """Sets a bucket's default ACL to an XML string.
  452. :type acl_str: string
  453. :param acl_str: A string containing the ACL XML.
  454. :type headers: dict
  455. :param headers: Additional headers to set during the request.
  456. """
  457. return self.set_xml_acl(acl_str, '', headers,
  458. query_args=DEF_OBJ_ACL)
  459. def get_cors(self, headers=None):
  460. """Returns a bucket's CORS XML document.
  461. :param dict headers: Additional headers to send with the request.
  462. :rtype: :class:`~.cors.Cors`
  463. """
  464. response = self.connection.make_request('GET', self.name,
  465. query_args=CORS_ARG,
  466. headers=headers)
  467. body = response.read()
  468. if response.status == 200:
  469. # Success - parse XML and return Cors object.
  470. cors = Cors()
  471. h = handler.XmlHandler(cors, self)
  472. xml.sax.parseString(body, h)
  473. return cors
  474. else:
  475. raise self.connection.provider.storage_response_error(
  476. response.status, response.reason, body)
  477. def set_cors(self, cors, headers=None):
  478. """Sets a bucket's CORS XML document.
  479. :param str cors: A string containing the CORS XML.
  480. :param dict headers: Additional headers to send with the request.
  481. """
  482. response = self.connection.make_request(
  483. 'PUT', get_utf8_value(self.name), data=get_utf8_value(cors),
  484. query_args=CORS_ARG, headers=headers)
  485. body = response.read()
  486. if response.status != 200:
  487. raise self.connection.provider.storage_response_error(
  488. response.status, response.reason, body)
  489. def get_storage_class(self):
  490. """
  491. Returns the StorageClass for the bucket.
  492. :rtype: str
  493. :return: The StorageClass for the bucket.
  494. """
  495. response = self.connection.make_request('GET', self.name,
  496. query_args='storageClass')
  497. body = response.read()
  498. if response.status == 200:
  499. rs = ResultSet(self)
  500. h = handler.XmlHandler(rs, self)
  501. xml.sax.parseString(body, h)
  502. return rs.StorageClass
  503. else:
  504. raise self.connection.provider.storage_response_error(
  505. response.status, response.reason, body)
  506. # Method with same signature as boto.s3.bucket.Bucket.add_email_grant(),
  507. # to allow polymorphic treatment at application layer.
  508. def add_email_grant(self, permission, email_address,
  509. recursive=False, headers=None):
  510. """
  511. Convenience method that provides a quick way to add an email grant
  512. to a bucket. This method retrieves the current ACL, creates a new
  513. grant based on the parameters passed in, adds that grant to the ACL
  514. and then PUT's the new ACL back to GCS.
  515. :type permission: string
  516. :param permission: The permission being granted. Should be one of:
  517. (READ, WRITE, FULL_CONTROL).
  518. :type email_address: string
  519. :param email_address: The email address associated with the GS
  520. account your are granting the permission to.
  521. :type recursive: bool
  522. :param recursive: A boolean value to controls whether the call
  523. will apply the grant to all keys within the bucket
  524. or not. The default value is False. By passing a
  525. True value, the call will iterate through all keys
  526. in the bucket and apply the same grant to each key.
  527. CAUTION: If you have a lot of keys, this could take
  528. a long time!
  529. """
  530. if permission not in GSPermissions:
  531. raise self.connection.provider.storage_permissions_error(
  532. 'Unknown Permission: %s' % permission)
  533. acl = self.get_acl(headers=headers)
  534. acl.add_email_grant(permission, email_address)
  535. self.set_acl(acl, headers=headers)
  536. if recursive:
  537. for key in self:
  538. key.add_email_grant(permission, email_address, headers=headers)
  539. # Method with same signature as boto.s3.bucket.Bucket.add_user_grant(),
  540. # to allow polymorphic treatment at application layer.
  541. def add_user_grant(self, permission, user_id, recursive=False,
  542. headers=None):
  543. """
  544. Convenience method that provides a quick way to add a canonical user
  545. grant to a bucket. This method retrieves the current ACL, creates a new
  546. grant based on the parameters passed in, adds that grant to the ACL and
  547. then PUTs the new ACL back to GCS.
  548. :type permission: string
  549. :param permission: The permission being granted. Should be one of:
  550. (READ|WRITE|FULL_CONTROL)
  551. :type user_id: string
  552. :param user_id: The canonical user id associated with the GS account
  553. you are granting the permission to.
  554. :type recursive: bool
  555. :param recursive: A boolean value to controls whether the call
  556. will apply the grant to all keys within the bucket
  557. or not. The default value is False. By passing a
  558. True value, the call will iterate through all keys
  559. in the bucket and apply the same grant to each key.
  560. CAUTION: If you have a lot of keys, this could take
  561. a long time!
  562. """
  563. if permission not in GSPermissions:
  564. raise self.connection.provider.storage_permissions_error(
  565. 'Unknown Permission: %s' % permission)
  566. acl = self.get_acl(headers=headers)
  567. acl.add_user_grant(permission, user_id)
  568. self.set_acl(acl, headers=headers)
  569. if recursive:
  570. for key in self:
  571. key.add_user_grant(permission, user_id, headers=headers)
  572. def add_group_email_grant(self, permission, email_address, recursive=False,
  573. headers=None):
  574. """
  575. Convenience method that provides a quick way to add an email group
  576. grant to a bucket. This method retrieves the current ACL, creates a new
  577. grant based on the parameters passed in, adds that grant to the ACL and
  578. then PUT's the new ACL back to GCS.
  579. :type permission: string
  580. :param permission: The permission being granted. Should be one of:
  581. READ|WRITE|FULL_CONTROL
  582. See http://code.google.com/apis/storage/docs/developer-guide.html#authorization
  583. for more details on permissions.
  584. :type email_address: string
  585. :param email_address: The email address associated with the Google
  586. Group to which you are granting the permission.
  587. :type recursive: bool
  588. :param recursive: A boolean value to controls whether the call
  589. will apply the grant to all keys within the bucket
  590. or not. The default value is False. By passing a
  591. True value, the call will iterate through all keys
  592. in the bucket and apply the same grant to each key.
  593. CAUTION: If you have a lot of keys, this could take
  594. a long time!
  595. """
  596. if permission not in GSPermissions:
  597. raise self.connection.provider.storage_permissions_error(
  598. 'Unknown Permission: %s' % permission)
  599. acl = self.get_acl(headers=headers)
  600. acl.add_group_email_grant(permission, email_address)
  601. self.set_acl(acl, headers=headers)
  602. if recursive:
  603. for key in self:
  604. key.add_group_email_grant(permission, email_address,
  605. headers=headers)
  606. # Method with same input signature as boto.s3.bucket.Bucket.list_grants()
  607. # (but returning different object type), to allow polymorphic treatment
  608. # at application layer.
  609. def list_grants(self, headers=None):
  610. """Returns the ACL entries applied to this bucket.
  611. :param dict headers: Additional headers to send with the request.
  612. :rtype: list containing :class:`~.gs.acl.Entry` objects.
  613. """
  614. acl = self.get_acl(headers=headers)
  615. return acl.entries
  616. def disable_logging(self, headers=None):
  617. """Disable logging on this bucket.
  618. :param dict headers: Additional headers to send with the request.
  619. """
  620. xml_str = '<?xml version="1.0" encoding="UTF-8"?><Logging/>'
  621. self.set_subresource('logging', xml_str, headers=headers)
  622. def enable_logging(self, target_bucket, target_prefix=None, headers=None):
  623. """Enable logging on a bucket.
  624. :type target_bucket: bucket or string
  625. :param target_bucket: The bucket to log to.
  626. :type target_prefix: string
  627. :param target_prefix: The prefix which should be prepended to the
  628. generated log files written to the target_bucket.
  629. :param dict headers: Additional headers to send with the request.
  630. """
  631. if isinstance(target_bucket, Bucket):
  632. target_bucket = target_bucket.name
  633. xml_str = '<?xml version="1.0" encoding="UTF-8"?><Logging>'
  634. xml_str = (xml_str + '<LogBucket>%s</LogBucket>' % target_bucket)
  635. if target_prefix:
  636. xml_str = (xml_str +
  637. '<LogObjectPrefix>%s</LogObjectPrefix>' % target_prefix)
  638. xml_str = xml_str + '</Logging>'
  639. self.set_subresource('logging', xml_str, headers=headers)
  640. def get_logging_config_with_xml(self, headers=None):
  641. """Returns the current status of logging configuration on the bucket as
  642. unparsed XML.
  643. :param dict headers: Additional headers to send with the request.
  644. :rtype: 2-Tuple
  645. :returns: 2-tuple containing:
  646. 1) A dictionary containing the parsed XML response from GCS. The
  647. overall structure is:
  648. * Logging
  649. * LogObjectPrefix: Prefix that is prepended to log objects.
  650. * LogBucket: Target bucket for log objects.
  651. 2) Unparsed XML describing the bucket's logging configuration.
  652. """
  653. response = self.connection.make_request('GET', self.name,
  654. query_args='logging',
  655. headers=headers)
  656. body = response.read()
  657. boto.log.debug(body)
  658. if response.status != 200:
  659. raise self.connection.provider.storage_response_error(
  660. response.status, response.reason, body)
  661. e = boto.jsonresponse.Element()
  662. h = boto.jsonresponse.XmlHandler(e, None)
  663. h.parse(body)
  664. return e, body
  665. def get_logging_config(self, headers=None):
  666. """Returns the current status of logging configuration on the bucket.
  667. :param dict headers: Additional headers to send with the request.
  668. :rtype: dict
  669. :returns: A dictionary containing the parsed XML response from GCS. The
  670. overall structure is:
  671. * Logging
  672. * LogObjectPrefix: Prefix that is prepended to log objects.
  673. * LogBucket: Target bucket for log objects.
  674. """
  675. return self.get_logging_config_with_xml(headers)[0]
  676. def configure_website(self, main_page_suffix=None, error_key=None,
  677. headers=None):
  678. """Configure this bucket to act as a website
  679. :type main_page_suffix: str
  680. :param main_page_suffix: Suffix that is appended to a request that is
  681. for a "directory" on the website endpoint (e.g. if the suffix is
  682. index.html and you make a request to samplebucket/images/ the data
  683. that is returned will be for the object with the key name
  684. images/index.html). The suffix must not be empty and must not
  685. include a slash character. This parameter is optional and the
  686. property is disabled if excluded.
  687. :type error_key: str
  688. :param error_key: The object key name to use when a 400 error occurs.
  689. This parameter is optional and the property is disabled if excluded.
  690. :param dict headers: Additional headers to send with the request.
  691. """
  692. if main_page_suffix:
  693. main_page_frag = self.WebsiteMainPageFragment % main_page_suffix
  694. else:
  695. main_page_frag = ''
  696. if error_key:
  697. error_frag = self.WebsiteErrorFragment % error_key
  698. else:
  699. error_frag = ''
  700. body = self.WebsiteBody % (main_page_frag, error_frag)
  701. response = self.connection.make_request(
  702. 'PUT', get_utf8_value(self.name), data=get_utf8_value(body),
  703. query_args='websiteConfig', headers=headers)
  704. body = response.read()
  705. if response.status == 200:
  706. return True
  707. else:
  708. raise self.connection.provider.storage_response_error(
  709. response.status, response.reason, body)
  710. def get_website_configuration(self, headers=None):
  711. """Returns the current status of website configuration on the bucket.
  712. :param dict headers: Additional headers to send with the request.
  713. :rtype: dict
  714. :returns: A dictionary containing the parsed XML response from GCS. The
  715. overall structure is:
  716. * WebsiteConfiguration
  717. * MainPageSuffix: suffix that is appended to request that
  718. is for a "directory" on the website endpoint.
  719. * NotFoundPage: name of an object to serve when site visitors
  720. encounter a 404.
  721. """
  722. return self.get_website_configuration_with_xml(headers)[0]
  723. def get_website_configuration_with_xml(self, headers=None):
  724. """Returns the current status of website configuration on the bucket as
  725. unparsed XML.
  726. :param dict headers: Additional headers to send with the request.
  727. :rtype: 2-Tuple
  728. :returns: 2-tuple containing:
  729. 1) A dictionary containing the parsed XML response from GCS. The
  730. overall structure is:
  731. * WebsiteConfiguration
  732. * MainPageSuffix: suffix that is appended to request that is for
  733. a "directory" on the website endpoint.
  734. * NotFoundPage: name of an object to serve when site visitors
  735. encounter a 404
  736. 2) Unparsed XML describing the bucket's website configuration.
  737. """
  738. response = self.connection.make_request('GET', self.name,
  739. query_args='websiteConfig', headers=headers)
  740. body = response.read()
  741. boto.log.debug(body)
  742. if response.status != 200:
  743. raise self.connection.provider.storage_response_error(
  744. response.status, response.reason, body)
  745. e = boto.jsonresponse.Element()
  746. h = boto.jsonresponse.XmlHandler(e, None)
  747. h.parse(body)
  748. return e, body
  749. def delete_website_configuration(self, headers=None):
  750. """Remove the website configuration from this bucket.
  751. :param dict headers: Additional headers to send with the request.
  752. """
  753. self.configure_website(headers=headers)
  754. def get_versioning_status(self, headers=None):
  755. """Returns the current status of versioning configuration on the bucket.
  756. :rtype: bool
  757. """
  758. response = self.connection.make_request('GET', self.name,
  759. query_args='versioning',
  760. headers=headers)
  761. body = response.read()
  762. boto.log.debug(body)
  763. if response.status != 200:
  764. raise self.connection.provider.storage_response_error(
  765. response.status, response.reason, body)
  766. resp_json = boto.jsonresponse.Element()
  767. boto.jsonresponse.XmlHandler(resp_json, None).parse(body)
  768. resp_json = resp_json['VersioningConfiguration']
  769. return ('Status' in resp_json) and (resp_json['Status'] == 'Enabled')
  770. def configure_versioning(self, enabled, headers=None):
  771. """Configure versioning for this bucket.
  772. :param bool enabled: If set to True, enables versioning on this bucket.
  773. If set to False, disables versioning.
  774. :param dict headers: Additional headers to send with the request.
  775. """
  776. if enabled == True:
  777. req_body = self.VersioningBody % ('Enabled')
  778. else:
  779. req_body = self.VersioningBody % ('Suspended')
  780. self.set_subresource('versioning', req_body, headers=headers)
  781. def get_lifecycle_config(self, headers=None):
  782. """
  783. Returns the current lifecycle configuration on the bucket.
  784. :rtype: :class:`boto.gs.lifecycle.LifecycleConfig`
  785. :returns: A LifecycleConfig object that describes all current
  786. lifecycle rules in effect for the bucket.
  787. """
  788. response = self.connection.make_request('GET', self.name,
  789. query_args=LIFECYCLE_ARG, headers=headers)
  790. body = response.read()
  791. boto.log.debug(body)
  792. if response.status == 200:
  793. lifecycle_config = LifecycleConfig()
  794. h = handler.XmlHandler(lifecycle_config, self)
  795. xml.sax.parseString(body, h)
  796. return lifecycle_config
  797. else:
  798. raise self.connection.provider.storage_response_error(
  799. response.status, response.reason, body)
  800. def configure_lifecycle(self, lifecycle_config, headers=None):
  801. """
  802. Configure lifecycle for this bucket.
  803. :type lifecycle_config: :class:`boto.gs.lifecycle.LifecycleConfig`
  804. :param lifecycle_config: The lifecycle configuration you want
  805. to configure for this bucket.
  806. """
  807. xml = lifecycle_config.to_xml()
  808. response = self.connection.make_request(
  809. 'PUT', get_utf8_value(self.name), data=get_utf8_value(xml),
  810. query_args=LIFECYCLE_ARG, headers=headers)
  811. body = response.read()
  812. if response.status == 200:
  813. return True
  814. else:
  815. raise self.connection.provider.storage_response_error(
  816. response.status, response.reason, body)