comparison venv/lib/python2.7/site-packages/boto/gs/bucket.py @ 0:d67268158946 draft

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