Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(160)

Side by Side Diff: third_party/gsutil/boto/gs/bucket.py

Issue 12042069: Scripts to download files from google storage based on sha1 sums (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Removed gsutil/tests and gsutil/docs Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 boto
23 from boto import handler
24 from boto.resultset import ResultSet
25 from boto.exception import InvalidAclError
26 from boto.gs.acl import ACL, CannedACLStrings
27 from boto.gs.acl import SupportedPermissions as GSPermissions
28 from boto.gs.bucketlistresultset import VersionedBucketListResultSet
29 from boto.gs.cors import Cors
30 from boto.gs.key import Key as GSKey
31 from boto.s3.acl import Policy
32 from boto.s3.bucket import Bucket as S3Bucket
33 import xml.sax
34
35 # constants for http query args
36 DEF_OBJ_ACL = 'defaultObjectAcl'
37 STANDARD_ACL = 'acl'
38 CORS_ARG = 'cors'
39
40 class Bucket(S3Bucket):
41 VersioningBody = ('<?xml version="1.0" encoding="UTF-8"?>\n'
42 '<VersioningConfiguration><Status>%s</Status>'
43 '</VersioningConfiguration>')
44 WebsiteBody = ('<?xml version="1.0" encoding="UTF-8"?>\n'
45 '<WebsiteConfiguration>%s%s</WebsiteConfiguration>')
46 WebsiteMainPageFragment = '<MainPageSuffix>%s</MainPageSuffix>'
47 WebsiteErrorFragment = '<NotFoundPage>%s</NotFoundPage>'
48
49 def __init__(self, connection=None, name=None, key_class=GSKey):
50 super(Bucket, self).__init__(connection, name, key_class)
51
52 def startElement(self, name, attrs, connection):
53 return None
54
55 def endElement(self, name, value, connection):
56 if name == 'Name':
57 self.name = value
58 elif name == 'CreationDate':
59 self.creation_date = value
60 else:
61 setattr(self, name, value)
62
63 def get_key(self, key_name, headers=None, version_id=None,
64 response_headers=None, generation=None):
65 """
66 Check to see if a particular key exists within the bucket. This
67 method uses a HEAD request to check for the existance of the key.
68 Returns: An instance of a Key object or None
69
70 :type key_name: string
71 :param key_name: The name of the key to retrieve
72
73 :type response_headers: dict
74 :param response_headers: A dictionary containing HTTP
75 headers/values that will override any headers associated
76 with the stored object in the response. See
77 http://goo.gl/06N3b for details.
78
79 :rtype: :class:`boto.s3.key.Key`
80 :returns: A Key object from this bucket.
81 """
82 query_args_l = []
83 if generation:
84 query_args_l.append('generation=%s' % generation)
85 if response_headers:
86 for rk, rv in response_headers.iteritems():
87 query_args_l.append('%s=%s' % (rk, urllib.quote(rv)))
88
89 key, resp = self._get_key_internal(key_name, headers,
90 query_args_l=query_args_l)
91 if key:
92 key.meta_generation = resp.getheader('x-goog-meta-generation')
93 key.generation = resp.getheader('x-goog-generation')
94 return key
95
96 def copy_key(self, new_key_name, src_bucket_name, src_key_name,
97 metadata=None, src_version_id=None, storage_class='STANDARD',
98 preserve_acl=False, encrypt_key=False, headers=None,
99 query_args=None, src_generation=None):
100 if src_generation:
101 headers['x-goog-copy-source-generation'] = src_generation
102 super(Bucket, self).copy_key(new_key_name, src_bucket_name,
103 src_key_name, metadata=metadata,
104 storage_class=storage_class,
105 preserve_acl=preserve_acl,
106 encrypt_key=encrypt_key, headers=headers,
107 query_args=query_args)
108
109 def list_versions(self, prefix='', delimiter='', marker='',
110 generation_marker='', headers=None):
111 """
112 List versioned objects within a bucket. This returns an
113 instance of an VersionedBucketListResultSet that automatically
114 handles all of the result paging, etc. from GCS. You just need
115 to keep iterating until there are no more results. Called
116 with no arguments, this will return an iterator object across
117 all keys within the bucket.
118
119 :type prefix: string
120 :param prefix: allows you to limit the listing to a particular
121 prefix. For example, if you call the method with
122 prefix='/foo/' then the iterator will only cycle through
123 the keys that begin with the string '/foo/'.
124
125 :type delimiter: string
126 :param delimiter: can be used in conjunction with the prefix
127 to allow you to organize and browse your keys
128 hierarchically. See:
129 https://developers.google.com/storage/docs/reference-headers#delimit er
130 for more details.
131
132 :type marker: string
133 :param marker: The "marker" of where you are in the result set
134
135 :type generation_marker: string
136 :param marker: The "generation marker" of where you are in the result
137 set
138
139 :rtype:
140 :class:`boto.gs.bucketlistresultset.VersionedBucketListResultSet`
141 :return: an instance of a BucketListResultSet that handles paging, etc
142 """
143 return VersionedBucketListResultSet(self, prefix, delimiter,
144 marker, generation_marker,
145 headers)
146
147 def delete_key(self, key_name, headers=None, version_id=None,
148 mfa_token=None, generation=None):
149 query_args_l = []
150 if generation:
151 query_args_l.append('generation=%s' % generation)
152 self._delete_key_internal(key_name, headers=headers,
153 version_id=version_id, mfa_token=mfa_token,
154 query_args_l=query_args_l)
155
156 def set_acl(self, acl_or_str, key_name='', headers=None, version_id=None,
157 generation=None):
158 """Sets or changes a bucket's or key's ACL. The generation argument can
159 be used to specify an object version, else we will modify the current
160 version."""
161 key_name = key_name or ''
162 query_args = STANDARD_ACL
163 if generation:
164 query_args += '&generation=%d' % generation
165 if isinstance(acl_or_str, Policy):
166 raise InvalidAclError('Attempt to set S3 Policy on GS ACL')
167 elif isinstance(acl_or_str, ACL):
168 self.set_xml_acl(acl_or_str.to_xml(), key_name, headers=headers,
169 query_args=query_args)
170 else:
171 self.set_canned_acl(acl_or_str, key_name, headers=headers,
172 generation=generation)
173
174 def set_def_acl(self, acl_or_str, key_name='', headers=None):
175 """sets or changes a bucket's default object acl. The key_name argument
176 is ignored since keys have no default ACL property."""
177 if isinstance(acl_or_str, Policy):
178 raise InvalidAclError('Attempt to set S3 Policy on GS ACL')
179 elif isinstance(acl_or_str, ACL):
180 self.set_def_xml_acl(acl_or_str.to_xml(), '', headers=headers)
181 else:
182 self.set_def_canned_acl(acl_or_str, '', headers=headers)
183
184 def get_acl_helper(self, key_name, headers, query_args):
185 """provides common functionality for get_acl() and get_def_acl()"""
186 response = self.connection.make_request('GET', self.name, key_name,
187 query_args=query_args,
188 headers=headers)
189 body = response.read()
190 if response.status == 200:
191 acl = ACL(self)
192 h = handler.XmlHandler(acl, self)
193 xml.sax.parseString(body, h)
194 return acl
195 else:
196 raise self.connection.provider.storage_response_error(
197 response.status, response.reason, body)
198
199 def get_acl(self, key_name='', headers=None, version_id=None,
200 generation=None):
201 """returns a bucket's acl. We include a version_id argument
202 to support a polymorphic interface for callers, however,
203 version_id is not relevant for Google Cloud Storage buckets
204 and is therefore ignored here."""
205 query_args = STANDARD_ACL
206 if generation:
207 query_args += '&generation=%d' % generation
208 return self.get_acl_helper(key_name, headers, query_args)
209
210 def get_def_acl(self, key_name='', headers=None):
211 """returns a bucket's default object acl. The key_name argument is
212 ignored since keys have no default ACL property."""
213 return self.get_acl_helper('', headers, DEF_OBJ_ACL)
214
215 def set_canned_acl_helper(self, acl_str, key_name, headers, query_args):
216 """provides common functionality for set_canned_acl() and
217 set_def_canned_acl()"""
218 assert acl_str in CannedACLStrings
219
220 if headers:
221 headers[self.connection.provider.acl_header] = acl_str
222 else:
223 headers={self.connection.provider.acl_header: acl_str}
224
225 response = self.connection.make_request('PUT', self.name, key_name,
226 headers=headers, query_args=query_args)
227 body = response.read()
228 if response.status != 200:
229 raise self.connection.provider.storage_response_error(
230 response.status, response.reason, body)
231
232 def set_canned_acl(self, acl_str, key_name='', headers=None,
233 version_id=None, generation=None):
234 """sets or changes a bucket's acl to a predefined (canned) value.
235 We include a version_id argument to support a polymorphic
236 interface for callers, however, version_id is not relevant for
237 Google Cloud Storage buckets and is therefore ignored here."""
238 query_args = STANDARD_ACL
239 if generation:
240 query_args += '&generation=%d' % generation
241 return self.set_canned_acl_helper(acl_str, key_name, headers,
242 query_args=query_args)
243
244 def set_def_canned_acl(self, acl_str, key_name='', headers=None):
245 """sets or changes a bucket's default object acl to a predefined
246 (canned) value. The key_name argument is ignored since keys have no
247 default ACL property."""
248 return self.set_canned_acl_helper(acl_str, '', headers,
249 query_args=DEF_OBJ_ACL)
250
251 def set_def_xml_acl(self, acl_str, key_name='', headers=None):
252 """sets or changes a bucket's default object ACL. The key_name argument
253 is ignored since keys have no default ACL property."""
254 return self.set_xml_acl(acl_str, '', headers,
255 query_args=DEF_OBJ_ACL)
256
257 def get_cors(self, headers=None):
258 """returns a bucket's CORS XML"""
259 response = self.connection.make_request('GET', self.name,
260 query_args=CORS_ARG,
261 headers=headers)
262 body = response.read()
263 if response.status == 200:
264 # Success - parse XML and return Cors object.
265 cors = Cors()
266 h = handler.XmlHandler(cors, self)
267 xml.sax.parseString(body, h)
268 return cors
269 else:
270 raise self.connection.provider.storage_response_error(
271 response.status, response.reason, body)
272
273 def set_cors(self, cors, headers=None):
274 """sets or changes a bucket's CORS XML."""
275 cors_xml = cors.encode('UTF-8')
276 response = self.connection.make_request('PUT', self.name,
277 data=cors_xml,
278 query_args=CORS_ARG,
279 headers=headers)
280 body = response.read()
281 if response.status != 200:
282 raise self.connection.provider.storage_response_error(
283 response.status, response.reason, body)
284
285 def get_storage_class(self):
286 """
287 Returns the StorageClass for the bucket.
288
289 :rtype: str
290 :return: The StorageClass for the bucket.
291 """
292 response = self.connection.make_request('GET', self.name,
293 query_args='storageClass')
294 body = response.read()
295 if response.status == 200:
296 rs = ResultSet(self)
297 h = handler.XmlHandler(rs, self)
298 xml.sax.parseString(body, h)
299 return rs.StorageClass
300 else:
301 raise self.connection.provider.storage_response_error(
302 response.status, response.reason, body)
303
304
305 # Method with same signature as boto.s3.bucket.Bucket.add_email_grant(),
306 # to allow polymorphic treatment at application layer.
307 def add_email_grant(self, permission, email_address,
308 recursive=False, headers=None):
309 """
310 Convenience method that provides a quick way to add an email grant
311 to a bucket. This method retrieves the current ACL, creates a new
312 grant based on the parameters passed in, adds that grant to the ACL
313 and then PUT's the new ACL back to GCS.
314
315 :type permission: string
316 :param permission: The permission being granted. Should be one of:
317 (READ, WRITE, FULL_CONTROL).
318
319 :type email_address: string
320 :param email_address: The email address associated with the GS
321 account your are granting the permission to.
322
323 :type recursive: boolean
324 :param recursive: A boolean value to controls whether the call
325 will apply the grant to all keys within the bucket
326 or not. The default value is False. By passing a
327 True value, the call will iterate through all keys
328 in the bucket and apply the same grant to each key.
329 CAUTION: If you have a lot of keys, this could take
330 a long time!
331 """
332 if permission not in GSPermissions:
333 raise self.connection.provider.storage_permissions_error(
334 'Unknown Permission: %s' % permission)
335 acl = self.get_acl(headers=headers)
336 acl.add_email_grant(permission, email_address)
337 self.set_acl(acl, headers=headers)
338 if recursive:
339 for key in self:
340 key.add_email_grant(permission, email_address, headers=headers)
341
342 # Method with same signature as boto.s3.bucket.Bucket.add_user_grant(),
343 # to allow polymorphic treatment at application layer.
344 def add_user_grant(self, permission, user_id, recursive=False, headers=None) :
345 """
346 Convenience method that provides a quick way to add a canonical user
347 grant to a bucket. This method retrieves the current ACL, creates a new
348 grant based on the parameters passed in, adds that grant to the ACL and
349 then PUTs the new ACL back to GCS.
350
351 :type permission: string
352 :param permission: The permission being granted. Should be one of:
353 (READ|WRITE|FULL_CONTROL)
354
355 :type user_id: string
356 :param user_id: The canonical user id associated with the GS account
357 you are granting the permission to.
358
359 :type recursive: bool
360 :param recursive: A boolean value to controls whether the call
361 will apply the grant to all keys within the bucket
362 or not. The default value is False. By passing a
363 True value, the call will iterate through all keys
364 in the bucket and apply the same grant to each key.
365 CAUTION: If you have a lot of keys, this could take
366 a long time!
367 """
368 if permission not in GSPermissions:
369 raise self.connection.provider.storage_permissions_error(
370 'Unknown Permission: %s' % permission)
371 acl = self.get_acl(headers=headers)
372 acl.add_user_grant(permission, user_id)
373 self.set_acl(acl, headers=headers)
374 if recursive:
375 for key in self:
376 key.add_user_grant(permission, user_id, headers=headers)
377
378 def add_group_email_grant(self, permission, email_address, recursive=False,
379 headers=None):
380 """
381 Convenience method that provides a quick way to add an email group
382 grant to a bucket. This method retrieves the current ACL, creates a new
383 grant based on the parameters passed in, adds that grant to the ACL and
384 then PUT's the new ACL back to GCS.
385
386 :type permission: string
387 :param permission: The permission being granted. Should be one of:
388 READ|WRITE|FULL_CONTROL
389 See http://code.google.com/apis/storage/docs/developer-guide.html#au thorization
390 for more details on permissions.
391
392 :type email_address: string
393 :param email_address: The email address associated with the Google
394 Group to which you are granting the permission.
395
396 :type recursive: bool
397 :param recursive: A boolean value to controls whether the call
398 will apply the grant to all keys within the bucket
399 or not. The default value is False. By passing a
400 True value, the call will iterate through all keys
401 in the bucket and apply the same grant to each key.
402 CAUTION: If you have a lot of keys, this could take
403 a long time!
404 """
405 if permission not in GSPermissions:
406 raise self.connection.provider.storage_permissions_error(
407 'Unknown Permission: %s' % permission)
408 acl = self.get_acl(headers=headers)
409 acl.add_group_email_grant(permission, email_address)
410 self.set_acl(acl, headers=headers)
411 if recursive:
412 for key in self:
413 key.add_group_email_grant(permission, email_address,
414 headers=headers)
415
416 # Method with same input signature as boto.s3.bucket.Bucket.list_grants()
417 # (but returning different object type), to allow polymorphic treatment
418 # at application layer.
419 def list_grants(self, headers=None):
420 acl = self.get_acl(headers=headers)
421 return acl.entries
422
423 def disable_logging(self, headers=None):
424 xml_str = '<?xml version="1.0" encoding="UTF-8"?><Logging/>'
425 self.set_subresource('logging', xml_str, headers=headers)
426
427 def enable_logging(self, target_bucket, target_prefix=None, headers=None):
428 if isinstance(target_bucket, Bucket):
429 target_bucket = target_bucket.name
430 xml_str = '<?xml version="1.0" encoding="UTF-8"?><Logging>'
431 xml_str = (xml_str + '<LogBucket>%s</LogBucket>' % target_bucket)
432 if target_prefix:
433 xml_str = (xml_str +
434 '<LogObjectPrefix>%s</LogObjectPrefix>' % target_prefix)
435 xml_str = xml_str + '</Logging>'
436
437 self.set_subresource('logging', xml_str, headers=headers)
438
439 def configure_website(self, main_page_suffix=None, error_key=None,
440 headers=None):
441 """
442 Configure this bucket to act as a website
443
444 :type suffix: str
445 :param suffix: Suffix that is appended to a request that is for a
446 "directory" on the website endpoint (e.g. if the suffix
447 is index.html and you make a request to
448 samplebucket/images/ the data that is returned will
449 be for the object with the key name images/index.html).
450 The suffix must not be empty and must not include a
451 slash character. This parameter is optional and the
452 property is disabled if excluded.
453
454
455 :type error_key: str
456 :param error_key: The object key name to use when a 400
457 error occurs. This parameter is optional and the
458 property is disabled if excluded.
459
460 """
461 if main_page_suffix:
462 main_page_frag = self.WebsiteMainPageFragment % main_page_suffix
463 else:
464 main_page_frag = ''
465
466 if error_key:
467 error_frag = self.WebsiteErrorFragment % error_key
468 else:
469 error_frag = ''
470
471 body = self.WebsiteBody % (main_page_frag, error_frag)
472 response = self.connection.make_request('PUT', self.name, data=body,
473 query_args='websiteConfig',
474 headers=headers)
475 body = response.read()
476 if response.status == 200:
477 return True
478 else:
479 raise self.connection.provider.storage_response_error(
480 response.status, response.reason, body)
481
482 def get_website_configuration(self, headers=None):
483 """
484 Returns the current status of website configuration on the bucket.
485
486 :rtype: dict
487 :returns: A dictionary containing a Python representation
488 of the XML response from GCS. The overall structure is:
489
490 * WebsiteConfiguration
491 * MainPageSuffix: suffix that is appended to request that
492 is for a "directory" on the website endpoint
493 * NotFoundPage: name of an object to serve when site visitors
494 encounter a 404
495 """
496 return self.get_website_configuration_xml(self, headers)[0]
497
498 def get_website_configuration_with_xml(self, headers=None):
499 """
500 Returns the current status of website configuration on the bucket as
501 unparsed XML.
502
503 :rtype: 2-Tuple
504 :returns: 2-tuple containing:
505 1) A dictionary containing a Python representation
506 of the XML response from GCS. The overall structure is:
507 * WebsiteConfiguration
508 * MainPageSuffix: suffix that is appended to request that
509 is for a "directory" on the website endpoint
510 * NotFoundPage: name of an object to serve when site visitors
511 encounter a 404
512 2) unparsed XML describing the bucket's website configuration.
513 """
514 response = self.connection.make_request('GET', self.name,
515 query_args='websiteConfig', headers=headers)
516 body = response.read()
517 boto.log.debug(body)
518
519 if response.status != 200:
520 raise self.connection.provider.storage_response_error(
521 response.status, response.reason, body)
522
523 e = boto.jsonresponse.Element()
524 h = boto.jsonresponse.XmlHandler(e, None)
525 h.parse(body)
526 return e, body
527
528 def delete_website_configuration(self, headers=None):
529 self.configure_website(headers=headers)
530
531 def get_versioning_status(self, headers=None):
532 """
533 Returns the current status of versioning configuration on the bucket.
534
535 :rtype: boolean
536 :returns: boolean indicating whether or not versioning is enabled.
537 """
538 response = self.connection.make_request('GET', self.name,
539 query_args='versioning',
540 headers=headers)
541 body = response.read()
542 boto.log.debug(body)
543 if response.status != 200:
544 raise self.connection.provider.storage_response_error(
545 response.status, response.reason, body)
546 resp_json = boto.jsonresponse.Element()
547 boto.jsonresponse.XmlHandler(resp_json, None).parse(body)
548 resp_json = resp_json['VersioningConfiguration']
549 return ('Status' in resp_json) and (resp_json['Status'] == 'Enabled')
550
551 def configure_versioning(self, enabled, headers=None):
552 if enabled == True:
553 req_body = self.VersioningBody % ('Enabled')
554 else:
555 req_body = self.VersioningBody % ('Suspended')
556 self.set_subresource('versioning', req_body, headers=headers)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698