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