comparison env/lib/python3.9/site-packages/boto/gs/bucket.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
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
22 import re
23 import urllib
24 import xml.sax
25
26 import boto
27 from boto import handler
28 from boto.resultset import ResultSet
29 from boto.exception import GSResponseError
30 from boto.exception import InvalidAclError
31 from boto.gs.acl import ACL, CannedACLStrings
32 from boto.gs.acl import SupportedPermissions as GSPermissions
33 from boto.gs.bucketlistresultset import VersionedBucketListResultSet
34 from boto.gs.cors import Cors
35 from boto.gs.encryptionconfig import EncryptionConfig
36 from boto.gs.lifecycle import LifecycleConfig
37 from boto.gs.key import Key as GSKey
38 from boto.s3.acl import Policy
39 from boto.s3.bucket import Bucket as S3Bucket
40 from boto.utils import get_utf8_value
41 from boto.compat import six
42
43 # constants for http query args
44 DEF_OBJ_ACL = 'defaultObjectAcl'
45 STANDARD_ACL = 'acl'
46 CORS_ARG = 'cors'
47 ENCRYPTION_CONFIG_ARG = 'encryptionConfig'
48 LIFECYCLE_ARG = 'lifecycle'
49 STORAGE_CLASS_ARG='storageClass'
50 ERROR_DETAILS_REGEX = re.compile(r'<Details>(?P<details>.*)</Details>')
51
52 class Bucket(S3Bucket):
53 """Represents a Google Cloud Storage bucket."""
54
55 BillingBody = ('<?xml version="1.0" encoding="UTF-8"?>\n'
56 '<BillingConfiguration>'
57 '<RequesterPays>%s</RequesterPays>'
58 '</BillingConfiguration>')
59 EncryptionConfigBody = (
60 '<?xml version="1.0" encoding="UTF-8"?>\n'
61 '<EncryptionConfiguration>%s</EncryptionConfiguration>')
62 EncryptionConfigDefaultKeyNameFragment = (
63 '<DefaultKmsKeyName>%s</DefaultKmsKeyName>')
64 StorageClassBody = ('<?xml version="1.0" encoding="UTF-8"?>\n'
65 '<StorageClass>%s</StorageClass>')
66 VersioningBody = ('<?xml version="1.0" encoding="UTF-8"?>\n'
67 '<VersioningConfiguration>'
68 '<Status>%s</Status>'
69 '</VersioningConfiguration>')
70 WebsiteBody = ('<?xml version="1.0" encoding="UTF-8"?>\n'
71 '<WebsiteConfiguration>%s%s</WebsiteConfiguration>')
72 WebsiteMainPageFragment = '<MainPageSuffix>%s</MainPageSuffix>'
73 WebsiteErrorFragment = '<NotFoundPage>%s</NotFoundPage>'
74
75 def __init__(self, connection=None, name=None, key_class=GSKey):
76 super(Bucket, self).__init__(connection, name, key_class)
77
78 def startElement(self, name, attrs, connection):
79 return None
80
81 def endElement(self, name, value, connection):
82 if name == 'Name':
83 self.name = value
84 elif name == 'CreationDate':
85 self.creation_date = value
86 else:
87 setattr(self, name, value)
88
89 def get_key(self, key_name, headers=None, version_id=None,
90 response_headers=None, generation=None):
91 """Returns a Key instance for an object in this bucket.
92
93 Note that this method uses a HEAD request to check for the existence of
94 the key.
95
96 :type key_name: string
97 :param key_name: The name of the key to retrieve
98
99 :type response_headers: dict
100 :param response_headers: A dictionary containing HTTP
101 headers/values that will override any headers associated
102 with the stored object in the response. See
103 http://goo.gl/06N3b for details.
104
105 :type version_id: string
106 :param version_id: Unused in this subclass.
107
108 :type generation: int
109 :param generation: A specific generation number to fetch the key at. If
110 not specified, the latest generation is fetched.
111
112 :rtype: :class:`boto.gs.key.Key`
113 :returns: A Key object from this bucket.
114 """
115 query_args_l = []
116 if generation:
117 query_args_l.append('generation=%s' % generation)
118 if response_headers:
119 for rk, rv in six.iteritems(response_headers):
120 query_args_l.append('%s=%s' % (rk, urllib.quote(rv)))
121 try:
122 key, resp = self._get_key_internal(key_name, headers,
123 query_args_l=query_args_l)
124 except GSResponseError as e:
125 if e.status == 403 and 'Forbidden' in e.reason:
126 # If we failed getting an object, let the user know which object
127 # failed rather than just returning a generic 403.
128 e.reason = ("Access denied to 'gs://%s/%s'." %
129 (self.name, key_name))
130 raise
131 return key
132
133 def copy_key(self, new_key_name, src_bucket_name, src_key_name,
134 metadata=None, src_version_id=None, storage_class='STANDARD',
135 preserve_acl=False, encrypt_key=False, headers=None,
136 query_args=None, src_generation=None):
137 """Create a new key in the bucket by copying an existing key.
138
139 :type new_key_name: string
140 :param new_key_name: The name of the new key
141
142 :type src_bucket_name: string
143 :param src_bucket_name: The name of the source bucket
144
145 :type src_key_name: string
146 :param src_key_name: The name of the source key
147
148 :type src_generation: int
149 :param src_generation: The generation number of the source key to copy.
150 If not specified, the latest generation is copied.
151
152 :type metadata: dict
153 :param metadata: Metadata to be associated with new key. If
154 metadata is supplied, it will replace the metadata of the
155 source key being copied. If no metadata is supplied, the
156 source key's metadata will be copied to the new key.
157
158 :type version_id: string
159 :param version_id: Unused in this subclass.
160
161 :type storage_class: string
162 :param storage_class: The storage class of the new key. By
163 default, the new key will use the standard storage class.
164 Possible values are: STANDARD | DURABLE_REDUCED_AVAILABILITY
165
166 :type preserve_acl: bool
167 :param preserve_acl: If True, the ACL from the source key will
168 be copied to the destination key. If False, the
169 destination key will have the default ACL. Note that
170 preserving the ACL in the new key object will require two
171 additional API calls to GCS, one to retrieve the current
172 ACL and one to set that ACL on the new object. If you
173 don't care about the ACL (or if you have a default ACL set
174 on the bucket), a value of False will be significantly more
175 efficient.
176
177 :type encrypt_key: bool
178 :param encrypt_key: Included for compatibility with S3. This argument is
179 ignored.
180
181 :type headers: dict
182 :param headers: A dictionary of header name/value pairs.
183
184 :type query_args: string
185 :param query_args: A string of additional querystring arguments
186 to append to the request
187
188 :rtype: :class:`boto.gs.key.Key`
189 :returns: An instance of the newly created key object
190 """
191 if src_generation:
192 headers = headers or {}
193 headers['x-goog-copy-source-generation'] = str(src_generation)
194 return super(Bucket, self).copy_key(
195 new_key_name, src_bucket_name, src_key_name, metadata=metadata,
196 storage_class=storage_class, preserve_acl=preserve_acl,
197 encrypt_key=encrypt_key, headers=headers, query_args=query_args)
198
199 def list_versions(self, prefix='', delimiter='', marker='',
200 generation_marker='', headers=None):
201 """
202 List versioned objects within a bucket. This returns an
203 instance of an VersionedBucketListResultSet that automatically
204 handles all of the result paging, etc. from GCS. You just need
205 to keep iterating until there are no more results. Called
206 with no arguments, this will return an iterator object across
207 all keys within the bucket.
208
209 :type prefix: string
210 :param prefix: allows you to limit the listing to a particular
211 prefix. For example, if you call the method with
212 prefix='/foo/' then the iterator will only cycle through
213 the keys that begin with the string '/foo/'.
214
215 :type delimiter: string
216 :param delimiter: can be used in conjunction with the prefix
217 to allow you to organize and browse your keys
218 hierarchically. See:
219 https://developers.google.com/storage/docs/reference-headers#delimiter
220 for more details.
221
222 :type marker: string
223 :param marker: The "marker" of where you are in the result set
224
225 :type generation_marker: string
226 :param generation_marker: The "generation marker" of where you are in
227 the result set.
228
229 :type headers: dict
230 :param headers: A dictionary of header name/value pairs.
231
232 :rtype:
233 :class:`boto.gs.bucketlistresultset.VersionedBucketListResultSet`
234 :return: an instance of a BucketListResultSet that handles paging, etc.
235 """
236 return VersionedBucketListResultSet(self, prefix, delimiter,
237 marker, generation_marker,
238 headers)
239
240 def validate_get_all_versions_params(self, params):
241 """
242 See documentation in boto/s3/bucket.py.
243 """
244 self.validate_kwarg_names(params,
245 ['version_id_marker', 'delimiter', 'marker',
246 'generation_marker', 'prefix', 'max_keys'])
247
248 def delete_key(self, key_name, headers=None, version_id=None,
249 mfa_token=None, generation=None):
250 """
251 Deletes a key from the bucket.
252
253 :type key_name: string
254 :param key_name: The key name to delete
255
256 :type headers: dict
257 :param headers: A dictionary of header name/value pairs.
258
259 :type version_id: string
260 :param version_id: Unused in this subclass.
261
262 :type mfa_token: tuple or list of strings
263 :param mfa_token: Unused in this subclass.
264
265 :type generation: int
266 :param generation: The generation number of the key to delete. If not
267 specified, the latest generation number will be deleted.
268
269 :rtype: :class:`boto.gs.key.Key`
270 :returns: A key object holding information on what was
271 deleted.
272 """
273 query_args_l = []
274 if generation:
275 query_args_l.append('generation=%s' % generation)
276 self._delete_key_internal(key_name, headers=headers,
277 version_id=version_id, mfa_token=mfa_token,
278 query_args_l=query_args_l)
279
280 def set_acl(self, acl_or_str, key_name='', headers=None, version_id=None,
281 generation=None, if_generation=None, if_metageneration=None):
282 """Sets or changes a bucket's or key's ACL.
283
284 :type acl_or_str: string or :class:`boto.gs.acl.ACL`
285 :param acl_or_str: A canned ACL string (see
286 :data:`~.gs.acl.CannedACLStrings`) or an ACL object.
287
288 :type key_name: string
289 :param key_name: A key name within the bucket to set the ACL for. If not
290 specified, the ACL for the bucket will be set.
291
292 :type headers: dict
293 :param headers: Additional headers to set during the request.
294
295 :type version_id: string
296 :param version_id: Unused in this subclass.
297
298 :type generation: int
299 :param generation: If specified, sets the ACL for a specific generation
300 of a versioned object. If not specified, the current version is
301 modified.
302
303 :type if_generation: int
304 :param if_generation: (optional) If set to a generation number, the acl
305 will only be updated if its current generation number is this value.
306
307 :type if_metageneration: int
308 :param if_metageneration: (optional) If set to a metageneration number,
309 the acl will only be updated if its current metageneration number is
310 this value.
311 """
312 if isinstance(acl_or_str, Policy):
313 raise InvalidAclError('Attempt to set S3 Policy on GS ACL')
314 elif isinstance(acl_or_str, ACL):
315 self.set_xml_acl(acl_or_str.to_xml(), key_name, headers=headers,
316 generation=generation,
317 if_generation=if_generation,
318 if_metageneration=if_metageneration)
319 else:
320 self.set_canned_acl(acl_or_str, key_name, headers=headers,
321 generation=generation,
322 if_generation=if_generation,
323 if_metageneration=if_metageneration)
324
325 def set_def_acl(self, acl_or_str, headers=None):
326 """Sets or changes a bucket's default ACL.
327
328 :type acl_or_str: string or :class:`boto.gs.acl.ACL`
329 :param acl_or_str: A canned ACL string (see
330 :data:`~.gs.acl.CannedACLStrings`) or an ACL object.
331
332 :type headers: dict
333 :param headers: Additional headers to set during the request.
334 """
335 if isinstance(acl_or_str, Policy):
336 raise InvalidAclError('Attempt to set S3 Policy on GS ACL')
337 elif isinstance(acl_or_str, ACL):
338 self.set_def_xml_acl(acl_or_str.to_xml(), headers=headers)
339 else:
340 self.set_def_canned_acl(acl_or_str, headers=headers)
341
342 def _get_xml_acl_helper(self, key_name, headers, query_args):
343 """Provides common functionality for get_xml_acl and _get_acl_helper."""
344 response = self.connection.make_request('GET', self.name, key_name,
345 query_args=query_args,
346 headers=headers)
347 body = response.read()
348 if response.status != 200:
349 if response.status == 403:
350 match = ERROR_DETAILS_REGEX.search(body)
351 details = match.group('details') if match else None
352 if details:
353 details = (('<Details>%s. Note that Full Control access'
354 ' is required to access ACLs.</Details>') %
355 details)
356 body = re.sub(ERROR_DETAILS_REGEX, details, body)
357 raise self.connection.provider.storage_response_error(
358 response.status, response.reason, body)
359 return body
360
361 def _get_acl_helper(self, key_name, headers, query_args):
362 """Provides common functionality for get_acl and get_def_acl."""
363 body = self._get_xml_acl_helper(key_name, headers, query_args)
364 acl = ACL(self)
365 h = handler.XmlHandler(acl, self)
366 xml.sax.parseString(body, h)
367 return acl
368
369 def get_acl(self, key_name='', headers=None, version_id=None,
370 generation=None):
371 """Returns the ACL of the bucket or an object in the bucket.
372
373 :param str key_name: The name of the object to get the ACL for. If not
374 specified, the ACL for the bucket will be returned.
375
376 :param dict headers: Additional headers to set during the request.
377
378 :type version_id: string
379 :param version_id: Unused in this subclass.
380
381 :param int generation: If specified, gets the ACL for a specific
382 generation of a versioned object. If not specified, the current
383 version is returned. This parameter is only valid when retrieving
384 the ACL of an object, not a bucket.
385
386 :rtype: :class:`.gs.acl.ACL`
387 """
388 query_args = STANDARD_ACL
389 if generation:
390 query_args += '&generation=%s' % generation
391 return self._get_acl_helper(key_name, headers, query_args)
392
393 def get_xml_acl(self, key_name='', headers=None, version_id=None,
394 generation=None):
395 """Returns the ACL string of the bucket or an object in the bucket.
396
397 :param str key_name: The name of the object to get the ACL for. If not
398 specified, the ACL for the bucket will be returned.
399
400 :param dict headers: Additional headers to set during the request.
401
402 :type version_id: string
403 :param version_id: Unused in this subclass.
404
405 :param int generation: If specified, gets the ACL for a specific
406 generation of a versioned object. If not specified, the current
407 version is returned. This parameter is only valid when retrieving
408 the ACL of an object, not a bucket.
409
410 :rtype: str
411 """
412 query_args = STANDARD_ACL
413 if generation:
414 query_args += '&generation=%s' % generation
415 return self._get_xml_acl_helper(key_name, headers, query_args)
416
417 def get_def_acl(self, headers=None):
418 """Returns the bucket's default ACL.
419
420 :param dict headers: Additional headers to set during the request.
421
422 :rtype: :class:`.gs.acl.ACL`
423 """
424 return self._get_acl_helper('', headers, DEF_OBJ_ACL)
425
426 def _set_acl_helper(self, acl_or_str, key_name, headers, query_args,
427 generation, if_generation, if_metageneration,
428 canned=False):
429 """Provides common functionality for set_acl, set_xml_acl,
430 set_canned_acl, set_def_acl, set_def_xml_acl, and
431 set_def_canned_acl()."""
432
433 headers = headers or {}
434 data = ''
435 if canned:
436 headers[self.connection.provider.acl_header] = acl_or_str
437 else:
438 data = acl_or_str
439
440 if generation:
441 query_args += '&generation=%s' % generation
442
443 if if_metageneration is not None and if_generation is None:
444 raise ValueError("Received if_metageneration argument with no "
445 "if_generation argument. A metageneration has no "
446 "meaning without a content generation.")
447 if not key_name and (if_generation or if_metageneration):
448 raise ValueError("Received if_generation or if_metageneration "
449 "parameter while setting the ACL of a bucket.")
450 if if_generation is not None:
451 headers['x-goog-if-generation-match'] = str(if_generation)
452 if if_metageneration is not None:
453 headers['x-goog-if-metageneration-match'] = str(if_metageneration)
454
455 response = self.connection.make_request(
456 'PUT', get_utf8_value(self.name), get_utf8_value(key_name),
457 data=get_utf8_value(data), headers=headers, query_args=query_args)
458 body = response.read()
459 if response.status != 200:
460 raise self.connection.provider.storage_response_error(
461 response.status, response.reason, body)
462
463 def set_xml_acl(self, acl_str, key_name='', headers=None, version_id=None,
464 query_args='acl', generation=None, if_generation=None,
465 if_metageneration=None):
466 """Sets a bucket's or objects's ACL to an XML string.
467
468 :type acl_str: string
469 :param acl_str: A string containing the ACL XML.
470
471 :type key_name: string
472 :param key_name: A key name within the bucket to set the ACL for. If not
473 specified, the ACL for the bucket will be set.
474
475 :type headers: dict
476 :param headers: Additional headers to set during the request.
477
478 :type version_id: string
479 :param version_id: Unused in this subclass.
480
481 :type query_args: str
482 :param query_args: The query parameters to pass with the request.
483
484 :type generation: int
485 :param generation: If specified, sets the ACL for a specific generation
486 of a versioned object. If not specified, the current version is
487 modified.
488
489 :type if_generation: int
490 :param if_generation: (optional) If set to a generation number, the acl
491 will only be updated if its current generation number is this value.
492
493 :type if_metageneration: int
494 :param if_metageneration: (optional) If set to a metageneration number,
495 the acl will only be updated if its current metageneration number is
496 this value.
497 """
498 return self._set_acl_helper(acl_str, key_name=key_name, headers=headers,
499 query_args=query_args,
500 generation=generation,
501 if_generation=if_generation,
502 if_metageneration=if_metageneration)
503
504 def set_canned_acl(self, acl_str, key_name='', headers=None,
505 version_id=None, generation=None, if_generation=None,
506 if_metageneration=None):
507 """Sets a bucket's or objects's ACL using a predefined (canned) value.
508
509 :type acl_str: string
510 :param acl_str: A canned ACL string. See
511 :data:`~.gs.acl.CannedACLStrings`.
512
513 :type key_name: string
514 :param key_name: A key name within the bucket to set the ACL for. If not
515 specified, the ACL for the bucket will be set.
516
517 :type headers: dict
518 :param headers: Additional headers to set during the request.
519
520 :type version_id: string
521 :param version_id: Unused in this subclass.
522
523 :type generation: int
524 :param generation: If specified, sets the ACL for a specific generation
525 of a versioned object. If not specified, the current version is
526 modified.
527
528 :type if_generation: int
529 :param if_generation: (optional) If set to a generation number, the acl
530 will only be updated if its current generation number is this value.
531
532 :type if_metageneration: int
533 :param if_metageneration: (optional) If set to a metageneration number,
534 the acl will only be updated if its current metageneration number is
535 this value.
536 """
537 if acl_str not in CannedACLStrings:
538 raise ValueError("Provided canned ACL string (%s) is not valid."
539 % acl_str)
540 query_args = STANDARD_ACL
541 return self._set_acl_helper(acl_str, key_name, headers, query_args,
542 generation, if_generation,
543 if_metageneration, canned=True)
544
545 def set_def_canned_acl(self, acl_str, headers=None):
546 """Sets a bucket's default ACL using a predefined (canned) value.
547
548 :type acl_str: string
549 :param acl_str: A canned ACL string. See
550 :data:`~.gs.acl.CannedACLStrings`.
551
552 :type headers: dict
553 :param headers: Additional headers to set during the request.
554 """
555 if acl_str not in CannedACLStrings:
556 raise ValueError("Provided canned ACL string (%s) is not valid."
557 % acl_str)
558 query_args = DEF_OBJ_ACL
559 return self._set_acl_helper(acl_str, '', headers, query_args,
560 generation=None, if_generation=None,
561 if_metageneration=None, canned=True)
562
563 def set_def_xml_acl(self, acl_str, headers=None):
564 """Sets a bucket's default ACL to an XML string.
565
566 :type acl_str: string
567 :param acl_str: A string containing the ACL XML.
568
569 :type headers: dict
570 :param headers: Additional headers to set during the request.
571 """
572 return self.set_xml_acl(acl_str, '', headers,
573 query_args=DEF_OBJ_ACL)
574
575 def get_cors(self, headers=None):
576 """Returns a bucket's CORS XML document.
577
578 :param dict headers: Additional headers to send with the request.
579 :rtype: :class:`~.cors.Cors`
580 """
581 response = self.connection.make_request('GET', self.name,
582 query_args=CORS_ARG,
583 headers=headers)
584 body = response.read()
585 if response.status == 200:
586 # Success - parse XML and return Cors object.
587 cors = Cors()
588 h = handler.XmlHandler(cors, self)
589 xml.sax.parseString(body, h)
590 return cors
591 else:
592 raise self.connection.provider.storage_response_error(
593 response.status, response.reason, body)
594
595 def set_cors(self, cors, headers=None):
596 """Sets a bucket's CORS XML document.
597
598 :param str cors: A string containing the CORS XML.
599 :param dict headers: Additional headers to send with the request.
600 """
601 response = self.connection.make_request(
602 'PUT', get_utf8_value(self.name), data=get_utf8_value(cors),
603 query_args=CORS_ARG, headers=headers)
604 body = response.read()
605 if response.status != 200:
606 raise self.connection.provider.storage_response_error(
607 response.status, response.reason, body)
608
609 def get_storage_class(self, headers=None):
610 """
611 Returns the StorageClass for the bucket.
612
613 :rtype: str
614 :return: The StorageClass for the bucket.
615 """
616 response = self.connection.make_request('GET', self.name,
617 query_args=STORAGE_CLASS_ARG,
618 headers=headers)
619 body = response.read()
620 if response.status == 200:
621 rs = ResultSet(self)
622 h = handler.XmlHandler(rs, self)
623 xml.sax.parseString(body, h)
624 return rs.StorageClass
625 else:
626 raise self.connection.provider.storage_response_error(
627 response.status, response.reason, body)
628
629 def set_storage_class(self, storage_class, headers=None):
630 """
631 Sets a bucket's storage class.
632
633 :param str storage_class: A string containing the storage class.
634 :param dict headers: Additional headers to send with the request.
635 """
636 req_body = self.StorageClassBody % (get_utf8_value(storage_class))
637 self.set_subresource(STORAGE_CLASS_ARG, req_body, headers=headers)
638
639 # Method with same signature as boto.s3.bucket.Bucket.add_email_grant(),
640 # to allow polymorphic treatment at application layer.
641 def add_email_grant(self, permission, email_address,
642 recursive=False, headers=None):
643 """
644 Convenience method that provides a quick way to add an email grant
645 to a bucket. This method retrieves the current ACL, creates a new
646 grant based on the parameters passed in, adds that grant to the ACL
647 and then PUT's the new ACL back to GCS.
648
649 :type permission: string
650 :param permission: The permission being granted. Should be one of:
651 (READ, WRITE, FULL_CONTROL).
652
653 :type email_address: string
654 :param email_address: The email address associated with the GS
655 account your are granting the permission to.
656
657 :type recursive: bool
658 :param recursive: A boolean value to controls whether the call
659 will apply the grant to all keys within the bucket
660 or not. The default value is False. By passing a
661 True value, the call will iterate through all keys
662 in the bucket and apply the same grant to each key.
663 CAUTION: If you have a lot of keys, this could take
664 a long time!
665 """
666 if permission not in GSPermissions:
667 raise self.connection.provider.storage_permissions_error(
668 'Unknown Permission: %s' % permission)
669 acl = self.get_acl(headers=headers)
670 acl.add_email_grant(permission, email_address)
671 self.set_acl(acl, headers=headers)
672 if recursive:
673 for key in self:
674 key.add_email_grant(permission, email_address, headers=headers)
675
676 # Method with same signature as boto.s3.bucket.Bucket.add_user_grant(),
677 # to allow polymorphic treatment at application layer.
678 def add_user_grant(self, permission, user_id, recursive=False,
679 headers=None):
680 """
681 Convenience method that provides a quick way to add a canonical user
682 grant to a bucket. This method retrieves the current ACL, creates a new
683 grant based on the parameters passed in, adds that grant to the ACL and
684 then PUTs the new ACL back to GCS.
685
686 :type permission: string
687 :param permission: The permission being granted. Should be one of:
688 (READ|WRITE|FULL_CONTROL)
689
690 :type user_id: string
691 :param user_id: The canonical user id associated with the GS account
692 you are granting the permission to.
693
694 :type recursive: bool
695 :param recursive: A boolean value to controls whether the call
696 will apply the grant to all keys within the bucket
697 or not. The default value is False. By passing a
698 True value, the call will iterate through all keys
699 in the bucket and apply the same grant to each key.
700 CAUTION: If you have a lot of keys, this could take
701 a long time!
702 """
703 if permission not in GSPermissions:
704 raise self.connection.provider.storage_permissions_error(
705 'Unknown Permission: %s' % permission)
706 acl = self.get_acl(headers=headers)
707 acl.add_user_grant(permission, user_id)
708 self.set_acl(acl, headers=headers)
709 if recursive:
710 for key in self:
711 key.add_user_grant(permission, user_id, headers=headers)
712
713 def add_group_email_grant(self, permission, email_address, recursive=False,
714 headers=None):
715 """
716 Convenience method that provides a quick way to add an email group
717 grant to a bucket. This method retrieves the current ACL, creates a new
718 grant based on the parameters passed in, adds that grant to the ACL and
719 then PUT's the new ACL back to GCS.
720
721 :type permission: string
722 :param permission: The permission being granted. Should be one of:
723 READ|WRITE|FULL_CONTROL
724 See http://code.google.com/apis/storage/docs/developer-guide.html#authorization
725 for more details on permissions.
726
727 :type email_address: string
728 :param email_address: The email address associated with the Google
729 Group to which you are granting the permission.
730
731 :type recursive: bool
732 :param recursive: A boolean value to controls whether the call
733 will apply the grant to all keys within the bucket
734 or not. The default value is False. By passing a
735 True value, the call will iterate through all keys
736 in the bucket and apply the same grant to each key.
737 CAUTION: If you have a lot of keys, this could take
738 a long time!
739 """
740 if permission not in GSPermissions:
741 raise self.connection.provider.storage_permissions_error(
742 'Unknown Permission: %s' % permission)
743 acl = self.get_acl(headers=headers)
744 acl.add_group_email_grant(permission, email_address)
745 self.set_acl(acl, headers=headers)
746 if recursive:
747 for key in self:
748 key.add_group_email_grant(permission, email_address,
749 headers=headers)
750
751 # Method with same input signature as boto.s3.bucket.Bucket.list_grants()
752 # (but returning different object type), to allow polymorphic treatment
753 # at application layer.
754 def list_grants(self, headers=None):
755 """Returns the ACL entries applied to this bucket.
756
757 :param dict headers: Additional headers to send with the request.
758 :rtype: list containing :class:`~.gs.acl.Entry` objects.
759 """
760 acl = self.get_acl(headers=headers)
761 return acl.entries
762
763 def disable_logging(self, headers=None):
764 """Disable logging on this bucket.
765
766 :param dict headers: Additional headers to send with the request.
767 """
768 xml_str = '<?xml version="1.0" encoding="UTF-8"?><Logging/>'
769 self.set_subresource('logging', xml_str, headers=headers)
770
771 def enable_logging(self, target_bucket, target_prefix=None, headers=None):
772 """Enable logging on a bucket.
773
774 :type target_bucket: bucket or string
775 :param target_bucket: The bucket to log to.
776
777 :type target_prefix: string
778 :param target_prefix: The prefix which should be prepended to the
779 generated log files written to the target_bucket.
780
781 :param dict headers: Additional headers to send with the request.
782 """
783 if isinstance(target_bucket, Bucket):
784 target_bucket = target_bucket.name
785 xml_str = '<?xml version="1.0" encoding="UTF-8"?><Logging>'
786 xml_str = (xml_str + '<LogBucket>%s</LogBucket>' % target_bucket)
787 if target_prefix:
788 xml_str = (xml_str +
789 '<LogObjectPrefix>%s</LogObjectPrefix>' % target_prefix)
790 xml_str = xml_str + '</Logging>'
791
792 self.set_subresource('logging', xml_str, headers=headers)
793
794 def get_logging_config_with_xml(self, headers=None):
795 """Returns the current status of logging configuration on the bucket as
796 unparsed XML.
797
798 :param dict headers: Additional headers to send with the request.
799
800 :rtype: 2-Tuple
801 :returns: 2-tuple containing:
802
803 1) A dictionary containing the parsed XML response from GCS. The
804 overall structure is:
805
806 * Logging
807
808 * LogObjectPrefix: Prefix that is prepended to log objects.
809 * LogBucket: Target bucket for log objects.
810
811 2) Unparsed XML describing the bucket's logging configuration.
812 """
813 response = self.connection.make_request('GET', self.name,
814 query_args='logging',
815 headers=headers)
816 body = response.read()
817 boto.log.debug(body)
818
819 if response.status != 200:
820 raise self.connection.provider.storage_response_error(
821 response.status, response.reason, body)
822
823 e = boto.jsonresponse.Element()
824 h = boto.jsonresponse.XmlHandler(e, None)
825 h.parse(body)
826 return e, body
827
828 def get_logging_config(self, headers=None):
829 """Returns the current status of logging configuration on the bucket.
830
831 :param dict headers: Additional headers to send with the request.
832
833 :rtype: dict
834 :returns: A dictionary containing the parsed XML response from GCS. The
835 overall structure is:
836
837 * Logging
838
839 * LogObjectPrefix: Prefix that is prepended to log objects.
840 * LogBucket: Target bucket for log objects.
841 """
842 return self.get_logging_config_with_xml(headers)[0]
843
844 def configure_website(self, main_page_suffix=None, error_key=None,
845 headers=None):
846 """Configure this bucket to act as a website
847
848 :type main_page_suffix: str
849 :param main_page_suffix: Suffix that is appended to a request that is
850 for a "directory" on the website endpoint (e.g. if the suffix is
851 index.html and you make a request to samplebucket/images/ the data
852 that is returned will be for the object with the key name
853 images/index.html). The suffix must not be empty and must not
854 include a slash character. This parameter is optional and the
855 property is disabled if excluded.
856
857 :type error_key: str
858 :param error_key: The object key name to use when a 400 error occurs.
859 This parameter is optional and the property is disabled if excluded.
860
861 :param dict headers: Additional headers to send with the request.
862 """
863 if main_page_suffix:
864 main_page_frag = self.WebsiteMainPageFragment % main_page_suffix
865 else:
866 main_page_frag = ''
867
868 if error_key:
869 error_frag = self.WebsiteErrorFragment % error_key
870 else:
871 error_frag = ''
872
873 body = self.WebsiteBody % (main_page_frag, error_frag)
874 response = self.connection.make_request(
875 'PUT', get_utf8_value(self.name), data=get_utf8_value(body),
876 query_args='websiteConfig', headers=headers)
877 body = response.read()
878 if response.status == 200:
879 return True
880 else:
881 raise self.connection.provider.storage_response_error(
882 response.status, response.reason, body)
883
884 def get_website_configuration(self, headers=None):
885 """Returns the current status of website configuration on the bucket.
886
887 :param dict headers: Additional headers to send with the request.
888
889 :rtype: dict
890 :returns: A dictionary containing the parsed XML response from GCS. The
891 overall structure is:
892
893 * WebsiteConfiguration
894
895 * MainPageSuffix: suffix that is appended to request that
896 is for a "directory" on the website endpoint.
897 * NotFoundPage: name of an object to serve when site visitors
898 encounter a 404.
899 """
900 return self.get_website_configuration_with_xml(headers)[0]
901
902 def get_website_configuration_with_xml(self, headers=None):
903 """Returns the current status of website configuration on the bucket as
904 unparsed XML.
905
906 :param dict headers: Additional headers to send with the request.
907
908 :rtype: 2-Tuple
909 :returns: 2-tuple containing:
910
911 1) A dictionary containing the parsed XML response from GCS. The
912 overall structure is:
913
914 * WebsiteConfiguration
915
916 * MainPageSuffix: suffix that is appended to request that is for
917 a "directory" on the website endpoint.
918 * NotFoundPage: name of an object to serve when site visitors
919 encounter a 404
920
921 2) Unparsed XML describing the bucket's website configuration.
922 """
923 response = self.connection.make_request('GET', self.name,
924 query_args='websiteConfig', headers=headers)
925 body = response.read()
926 boto.log.debug(body)
927
928 if response.status != 200:
929 raise self.connection.provider.storage_response_error(
930 response.status, response.reason, body)
931
932 e = boto.jsonresponse.Element()
933 h = boto.jsonresponse.XmlHandler(e, None)
934 h.parse(body)
935 return e, body
936
937 def delete_website_configuration(self, headers=None):
938 """Remove the website configuration from this bucket.
939
940 :param dict headers: Additional headers to send with the request.
941 """
942 self.configure_website(headers=headers)
943
944 def get_versioning_status(self, headers=None):
945 """Returns the current status of versioning configuration on the bucket.
946
947 :rtype: bool
948 """
949 response = self.connection.make_request('GET', self.name,
950 query_args='versioning',
951 headers=headers)
952 body = response.read()
953 boto.log.debug(body)
954 if response.status != 200:
955 raise self.connection.provider.storage_response_error(
956 response.status, response.reason, body)
957 resp_json = boto.jsonresponse.Element()
958 boto.jsonresponse.XmlHandler(resp_json, None).parse(body)
959 resp_json = resp_json['VersioningConfiguration']
960 return ('Status' in resp_json) and (resp_json['Status'] == 'Enabled')
961
962 def configure_versioning(self, enabled, headers=None):
963 """Configure versioning for this bucket.
964
965 :param bool enabled: If set to True, enables versioning on this bucket.
966 If set to False, disables versioning.
967
968 :param dict headers: Additional headers to send with the request.
969 """
970 if enabled == True:
971 req_body = self.VersioningBody % ('Enabled')
972 else:
973 req_body = self.VersioningBody % ('Suspended')
974 self.set_subresource('versioning', req_body, headers=headers)
975
976 def get_lifecycle_config(self, headers=None):
977 """
978 Returns the current lifecycle configuration on the bucket.
979
980 :rtype: :class:`boto.gs.lifecycle.LifecycleConfig`
981 :returns: A LifecycleConfig object that describes all current
982 lifecycle rules in effect for the bucket.
983 """
984 response = self.connection.make_request('GET', self.name,
985 query_args=LIFECYCLE_ARG, headers=headers)
986 body = response.read()
987 boto.log.debug(body)
988 if response.status == 200:
989 lifecycle_config = LifecycleConfig()
990 h = handler.XmlHandler(lifecycle_config, self)
991 xml.sax.parseString(body, h)
992 return lifecycle_config
993 else:
994 raise self.connection.provider.storage_response_error(
995 response.status, response.reason, body)
996
997 def configure_lifecycle(self, lifecycle_config, headers=None):
998 """
999 Configure lifecycle for this bucket.
1000
1001 :type lifecycle_config: :class:`boto.gs.lifecycle.LifecycleConfig`
1002 :param lifecycle_config: The lifecycle configuration you want
1003 to configure for this bucket.
1004 """
1005 xml = lifecycle_config.to_xml()
1006 response = self.connection.make_request(
1007 'PUT', get_utf8_value(self.name), data=get_utf8_value(xml),
1008 query_args=LIFECYCLE_ARG, headers=headers)
1009 body = response.read()
1010 if response.status == 200:
1011 return True
1012 else:
1013 raise self.connection.provider.storage_response_error(
1014 response.status, response.reason, body)
1015
1016 def get_billing_config(self, headers=None):
1017 """Returns the current status of billing configuration on the bucket.
1018
1019 :param dict headers: Additional headers to send with the request.
1020
1021 :rtype: dict
1022 :returns: A dictionary containing the parsed XML response from GCS. The
1023 overall structure is:
1024
1025 * BillingConfiguration
1026
1027 * RequesterPays: Enabled/Disabled.
1028 """
1029 return self.get_billing_configuration_with_xml(headers)[0]
1030
1031 def get_billing_configuration_with_xml(self, headers=None):
1032 """Returns the current status of billing configuration on the bucket as
1033 unparsed XML.
1034
1035 :param dict headers: Additional headers to send with the request.
1036
1037 :rtype: 2-Tuple
1038 :returns: 2-tuple containing:
1039
1040 1) A dictionary containing the parsed XML response from GCS. The
1041 overall structure is:
1042
1043 * BillingConfiguration
1044
1045 * RequesterPays: Enabled/Disabled.
1046
1047 2) Unparsed XML describing the bucket's website configuration.
1048 """
1049 response = self.connection.make_request('GET', self.name,
1050 query_args='billing',
1051 headers=headers)
1052 body = response.read()
1053 boto.log.debug(body)
1054
1055 if response.status != 200:
1056 raise self.connection.provider.storage_response_error(
1057 response.status, response.reason, body)
1058
1059 e = boto.jsonresponse.Element()
1060 h = boto.jsonresponse.XmlHandler(e, None);
1061 h.parse(body)
1062 return e, body
1063
1064 def configure_billing(self, requester_pays=False, headers=None):
1065 """Configure billing for this bucket.
1066
1067 :param bool requester_pays: If set to True, enables requester pays on
1068 this bucket. If set to False, disables requester pays.
1069
1070 :param dict headers: Additional headers to send with the request.
1071 """
1072 if requester_pays == True:
1073 req_body = self.BillingBody % ('Enabled')
1074 else:
1075 req_body = self.BillingBody % ('Disabled')
1076 self.set_subresource('billing', req_body, headers=headers)
1077
1078 def get_encryption_config(self, headers=None):
1079 """Returns a bucket's EncryptionConfig.
1080
1081 :param dict headers: Additional headers to send with the request.
1082 :rtype: :class:`~.encryption_config.EncryptionConfig`
1083 """
1084 response = self.connection.make_request(
1085 'GET', self.name, query_args=ENCRYPTION_CONFIG_ARG, headers=headers)
1086 body = response.read()
1087 if response.status == 200:
1088 # Success - parse XML and return EncryptionConfig object.
1089 encryption_config = EncryptionConfig()
1090 h = handler.XmlHandler(encryption_config, self)
1091 xml.sax.parseString(body, h)
1092 return encryption_config
1093 else:
1094 raise self.connection.provider.storage_response_error(
1095 response.status, response.reason, body)
1096
1097 def _construct_encryption_config_xml(self, default_kms_key_name=None):
1098 """Creates an XML document for setting a bucket's EncryptionConfig.
1099
1100 This method is internal as it's only here for testing purposes. As
1101 managing Cloud KMS resources for testing is complex, we settle for
1102 testing that we're creating correctly-formed XML for setting a bucket's
1103 encryption configuration.
1104
1105 :param str default_kms_key_name: A string containing a fully-qualified
1106 Cloud KMS key name.
1107 :rtype: str
1108 """
1109 if default_kms_key_name:
1110 default_kms_key_name_frag = (
1111 self.EncryptionConfigDefaultKeyNameFragment %
1112 default_kms_key_name)
1113 else:
1114 default_kms_key_name_frag = ''
1115
1116 return self.EncryptionConfigBody % default_kms_key_name_frag
1117
1118
1119 def set_encryption_config(self, default_kms_key_name=None, headers=None):
1120 """Sets a bucket's EncryptionConfig XML document.
1121
1122 :param str default_kms_key_name: A string containing a fully-qualified
1123 Cloud KMS key name.
1124 :param dict headers: Additional headers to send with the request.
1125 """
1126 body = self._construct_encryption_config_xml(
1127 default_kms_key_name=default_kms_key_name)
1128 response = self.connection.make_request(
1129 'PUT', get_utf8_value(self.name), data=get_utf8_value(body),
1130 query_args=ENCRYPTION_CONFIG_ARG, headers=headers)
1131 body = response.read()
1132 if response.status != 200:
1133 raise self.connection.provider.storage_response_error(
1134 response.status, response.reason, body)