OLD | NEW |
1 # Copyright 2010 Google Inc. | 1 # Copyright 2010 Google Inc. |
| 2 # Copyright (c) 2011, Nexenta Systems Inc. |
2 # | 3 # |
3 # Permission is hereby granted, free of charge, to any person obtaining a | 4 # Permission is hereby granted, free of charge, to any person obtaining a |
4 # copy of this software and associated documentation files (the | 5 # copy of this software and associated documentation files (the |
5 # "Software"), to deal in the Software without restriction, including | 6 # "Software"), to deal in the Software without restriction, including |
6 # without limitation the rights to use, copy, modify, merge, publish, dis- | 7 # 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 # 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 # persons to whom the Software is furnished to do so, subject to the fol- |
9 # lowing conditions: | 10 # lowing conditions: |
10 # | 11 # |
11 # The above copyright notice and this permission notice shall be included | 12 # The above copyright notice and this permission notice shall be included |
12 # in all copies or substantial portions of the Software. | 13 # in all copies or substantial portions of the Software. |
13 # | 14 # |
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | 15 # 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 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
16 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT | 17 # 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 # 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 # 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 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
20 # IN THE SOFTWARE. | 21 # IN THE SOFTWARE. |
21 | 22 |
| 23 import boto |
22 import os | 24 import os |
23 from boto.exception import BotoClientError | 25 from boto.exception import BotoClientError |
24 from boto.exception import InvalidUriError | 26 from boto.exception import InvalidUriError |
25 | 27 |
26 | 28 |
27 class StorageUri(object): | 29 class StorageUri(object): |
28 """ | 30 """ |
29 Base class for representing storage provider-independent bucket and | 31 Base class for representing storage provider-independent bucket and |
30 object name with a shorthand URI-like syntax. | 32 object name with a shorthand URI-like syntax. |
31 | 33 |
32 This is an abstract class: the constructor cannot be called (throws an | 34 This is an abstract class: the constructor cannot be called (throws an |
33 exception if you try). | 35 exception if you try). |
34 """ | 36 """ |
35 | 37 |
36 connection = None | 38 connection = None |
| 39 # Optional args that can be set from one of the concrete subclass |
| 40 # constructors, to change connection behavior (e.g., to override |
| 41 # https_connection_factory). |
| 42 connection_args = None |
37 | 43 |
38 def __init__(self): | 44 def __init__(self): |
39 """Uncallable constructor on abstract base StorageUri class. | 45 """Uncallable constructor on abstract base StorageUri class. |
40 """ | 46 """ |
41 raise BotoClientError('Attempt to instantiate abstract StorageUri ' | 47 raise BotoClientError('Attempt to instantiate abstract StorageUri ' |
42 'class') | 48 'class') |
43 | 49 |
44 def __repr__(self): | 50 def __repr__(self): |
45 """Returns string representation of URI.""" | 51 """Returns string representation of URI.""" |
46 return self.uri | 52 return self.uri |
(...skipping 12 matching lines...) Expand all Loading... |
59 """ | 65 """ |
60 Opens a connection to appropriate provider, depending on provider | 66 Opens a connection to appropriate provider, depending on provider |
61 portion of URI. Requires Credentials defined in boto config file (see | 67 portion of URI. Requires Credentials defined in boto config file (see |
62 boto/pyami/config.py). | 68 boto/pyami/config.py). |
63 @type storage_uri: StorageUri | 69 @type storage_uri: StorageUri |
64 @param storage_uri: StorageUri specifying a bucket or a bucket+object | 70 @param storage_uri: StorageUri specifying a bucket or a bucket+object |
65 @rtype: L{AWSAuthConnection<boto.gs.connection.AWSAuthConnection>} | 71 @rtype: L{AWSAuthConnection<boto.gs.connection.AWSAuthConnection>} |
66 @return: A connection to storage service provider of the given URI. | 72 @return: A connection to storage service provider of the given URI. |
67 """ | 73 """ |
68 | 74 |
| 75 connection_args = dict(self.connection_args or ()) |
| 76 # Use OrdinaryCallingFormat instead of boto-default |
| 77 # SubdomainCallingFormat because the latter changes the hostname |
| 78 # that's checked during cert validation for HTTPS connections, |
| 79 # which will fail cert validation (when cert validation is enabled). |
| 80 # Note: the following import can't be moved up to the start of |
| 81 # this file else it causes a config import failure when run from |
| 82 # the resumable upload/download tests. |
| 83 from boto.s3.connection import OrdinaryCallingFormat |
| 84 connection_args['calling_format'] = OrdinaryCallingFormat() |
| 85 connection_args.update(kwargs) |
69 if not self.connection: | 86 if not self.connection: |
70 if self.scheme == 's3': | 87 if self.scheme == 's3': |
71 from boto.s3.connection import S3Connection | 88 from boto.s3.connection import S3Connection |
72 self.connection = S3Connection(access_key_id, | 89 self.connection = S3Connection(access_key_id, |
73 secret_access_key, **kwargs) | 90 secret_access_key, |
| 91 **connection_args) |
74 elif self.scheme == 'gs': | 92 elif self.scheme == 'gs': |
75 from boto.gs.connection import GSConnection | 93 from boto.gs.connection import GSConnection |
76 self.connection = GSConnection(access_key_id, | 94 self.connection = GSConnection(access_key_id, |
77 secret_access_key, **kwargs) | 95 secret_access_key, |
| 96 **connection_args) |
78 elif self.scheme == 'file': | 97 elif self.scheme == 'file': |
79 from boto.file.connection import FileConnection | 98 from boto.file.connection import FileConnection |
80 self.connection = FileConnection(self) | 99 self.connection = FileConnection(self) |
81 else: | 100 else: |
82 raise InvalidUriError('Unrecognized scheme "%s"' % | 101 raise InvalidUriError('Unrecognized scheme "%s"' % |
83 self.scheme) | 102 self.scheme) |
84 self.connection.debug = self.debug | 103 self.connection.debug = self.debug |
85 return self.connection | 104 return self.connection |
86 | 105 |
87 def delete_key(self, validate=True, headers=None, version_id=None, | 106 def delete_key(self, validate=True, headers=None, version_id=None, |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
149 return canned_acls | 168 return canned_acls |
150 | 169 |
151 | 170 |
152 class BucketStorageUri(StorageUri): | 171 class BucketStorageUri(StorageUri): |
153 """ | 172 """ |
154 StorageUri subclass that handles bucket storage providers. | 173 StorageUri subclass that handles bucket storage providers. |
155 Callers should instantiate this class by calling boto.storage_uri(). | 174 Callers should instantiate this class by calling boto.storage_uri(). |
156 """ | 175 """ |
157 | 176 |
158 def __init__(self, scheme, bucket_name=None, object_name=None, | 177 def __init__(self, scheme, bucket_name=None, object_name=None, |
159 debug=0): | 178 debug=0, connection_args=None): |
160 """Instantiate a BucketStorageUri from scheme,bucket,object tuple. | 179 """Instantiate a BucketStorageUri from scheme,bucket,object tuple. |
161 | 180 |
162 @type scheme: string | 181 @type scheme: string |
163 @param scheme: URI scheme naming the storage provider (gs, s3, etc.) | 182 @param scheme: URI scheme naming the storage provider (gs, s3, etc.) |
164 @type bucket_name: string | 183 @type bucket_name: string |
165 @param bucket_name: bucket name | 184 @param bucket_name: bucket name |
166 @type object_name: string | 185 @type object_name: string |
167 @param object_name: object name | 186 @param object_name: object name |
168 @type debug: int | 187 @type debug: int |
169 @param debug: debug level to pass in to connection (range 0..2) | 188 @param debug: debug level to pass in to connection (range 0..2) |
| 189 @type connection_args: map |
| 190 @param connection_args: optional map containing args to be |
| 191 passed to {S3,GS}Connection constructor (e.g., to override |
| 192 https_connection_factory). |
170 | 193 |
171 After instantiation the components are available in the following | 194 After instantiation the components are available in the following |
172 fields: uri, scheme, bucket_name, object_name. | 195 fields: uri, scheme, bucket_name, object_name. |
173 """ | 196 """ |
174 | 197 |
175 self.scheme = scheme | 198 self.scheme = scheme |
176 self.bucket_name = bucket_name | 199 self.bucket_name = bucket_name |
177 self.object_name = object_name | 200 self.object_name = object_name |
| 201 if connection_args: |
| 202 self.connection_args = connection_args |
178 if self.bucket_name and self.object_name: | 203 if self.bucket_name and self.object_name: |
179 self.uri = ('%s://%s/%s' % (self.scheme, self.bucket_name, | 204 self.uri = ('%s://%s/%s' % (self.scheme, self.bucket_name, |
180 self.object_name)) | 205 self.object_name)) |
181 elif self.bucket_name: | 206 elif self.bucket_name: |
182 self.uri = ('%s://%s/' % (self.scheme, self.bucket_name)) | 207 self.uri = ('%s://%s/' % (self.scheme, self.bucket_name)) |
183 else: | 208 else: |
184 self.uri = ('%s://' % self.scheme) | 209 self.uri = ('%s://' % self.scheme) |
185 self.debug = debug | 210 self.debug = debug |
186 | 211 |
187 def clone_replace_name(self, new_name): | 212 def clone_replace_name(self, new_name): |
(...skipping 12 matching lines...) Expand all Loading... |
200 def get_acl(self, validate=True, headers=None, version_id=None): | 225 def get_acl(self, validate=True, headers=None, version_id=None): |
201 if not self.bucket_name: | 226 if not self.bucket_name: |
202 raise InvalidUriError('get_acl on bucket-less URI (%s)' % self.uri) | 227 raise InvalidUriError('get_acl on bucket-less URI (%s)' % self.uri) |
203 bucket = self.get_bucket(validate, headers) | 228 bucket = self.get_bucket(validate, headers) |
204 # This works for both bucket- and object- level ACLs (former passes | 229 # This works for both bucket- and object- level ACLs (former passes |
205 # key_name=None): | 230 # key_name=None): |
206 acl = bucket.get_acl(self.object_name, headers, version_id) | 231 acl = bucket.get_acl(self.object_name, headers, version_id) |
207 self.check_response(acl, 'acl', self.uri) | 232 self.check_response(acl, 'acl', self.uri) |
208 return acl | 233 return acl |
209 | 234 |
| 235 def get_location(self, validate=True, headers=None): |
| 236 if not self.bucket_name: |
| 237 raise InvalidUriError('get_location on bucket-less URI (%s)' % |
| 238 self.uri) |
| 239 bucket = self.get_bucket(validate, headers) |
| 240 return bucket.get_location() |
| 241 |
| 242 def get_subresource(self, subresource, validate=True, headers=None, |
| 243 version_id=None): |
| 244 if not self.bucket_name: |
| 245 raise InvalidUriError( |
| 246 'get_subresource on bucket-less URI (%s)' % self.uri) |
| 247 bucket = self.get_bucket(validate, headers) |
| 248 return bucket.get_subresource(subresource, self.object_name, headers, |
| 249 version_id) |
| 250 |
210 def add_group_email_grant(self, permission, email_address, recursive=False, | 251 def add_group_email_grant(self, permission, email_address, recursive=False, |
211 validate=True, headers=None): | 252 validate=True, headers=None): |
212 if self.scheme != 'gs': | 253 if self.scheme != 'gs': |
213 raise ValueError('add_group_email_grant() not supported for %s ' | 254 raise ValueError('add_group_email_grant() not supported for %s ' |
214 'URIs.' % self.scheme) | 255 'URIs.' % self.scheme) |
215 if self.object_name: | 256 if self.object_name: |
216 if recursive: | 257 if recursive: |
217 raise ValueError('add_group_email_grant() on key-ful URI cannot ' | 258 raise ValueError('add_group_email_grant() on key-ful URI cannot ' |
218 'specify recursive=True') | 259 'specify recursive=True') |
219 key = self.get_key(validate, headers) | 260 key = self.get_key(validate, headers) |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
311 | 352 |
312 def set_canned_acl(self, acl_str, validate=True, headers=None, | 353 def set_canned_acl(self, acl_str, validate=True, headers=None, |
313 version_id=None): | 354 version_id=None): |
314 if not self.object_name: | 355 if not self.object_name: |
315 raise InvalidUriError('set_canned_acl on object-less URI (%s)' % | 356 raise InvalidUriError('set_canned_acl on object-less URI (%s)' % |
316 self.uri) | 357 self.uri) |
317 key = self.get_key(validate, headers) | 358 key = self.get_key(validate, headers) |
318 self.check_response(key, 'key', self.uri) | 359 self.check_response(key, 'key', self.uri) |
319 key.set_canned_acl(acl_str, headers, version_id) | 360 key.set_canned_acl(acl_str, headers, version_id) |
320 | 361 |
| 362 def set_subresource(self, subresource, value, validate=True, headers=None, |
| 363 version_id=None): |
| 364 if not self.bucket_name: |
| 365 raise InvalidUriError( |
| 366 'set_subresource on bucket-less URI (%s)' % self.uri) |
| 367 bucket = self.get_bucket(validate, headers) |
| 368 bucket.set_subresource(subresource, value, self.object_name, headers, |
| 369 version_id) |
| 370 |
321 def set_contents_from_string(self, s, headers=None, replace=True, | 371 def set_contents_from_string(self, s, headers=None, replace=True, |
322 cb=None, num_cb=10, policy=None, md5=None, | 372 cb=None, num_cb=10, policy=None, md5=None, |
323 reduced_redundancy=False): | 373 reduced_redundancy=False): |
324 key = self.new_key(headers=headers) | 374 key = self.new_key(headers=headers) |
325 key.set_contents_from_string(s, headers, replace, cb, num_cb, policy, | 375 key.set_contents_from_string(s, headers, replace, cb, num_cb, policy, |
326 md5, reduced_redundancy) | 376 md5, reduced_redundancy) |
327 | 377 |
| 378 def enable_logging(self, target_bucket, target_prefix=None, |
| 379 canned_acl=None, validate=True, headers=None, |
| 380 version_id=None): |
| 381 if not self.bucket_name: |
| 382 raise InvalidUriError( |
| 383 'disable_logging on bucket-less URI (%s)' % self.uri) |
| 384 bucket = self.get_bucket(validate, headers) |
| 385 bucket.enable_logging(target_bucket, target_prefix, headers=headers, |
| 386 canned_acl=canned_acl) |
| 387 |
| 388 def disable_logging(self, validate=True, headers=None, version_id=None): |
| 389 if not self.bucket_name: |
| 390 raise InvalidUriError( |
| 391 'disable_logging on bucket-less URI (%s)' % self.uri) |
| 392 bucket = self.get_bucket(validate, headers) |
| 393 bucket.disable_logging(headers=headers) |
| 394 |
328 | 395 |
329 | 396 |
330 class FileStorageUri(StorageUri): | 397 class FileStorageUri(StorageUri): |
331 """ | 398 """ |
332 StorageUri subclass that handles files in the local file system. | 399 StorageUri subclass that handles files in the local file system. |
333 Callers should instantiate this class by calling boto.storage_uri(). | 400 Callers should instantiate this class by calling boto.storage_uri(). |
334 | 401 |
335 See file/README about how we map StorageUri operations onto a file system. | 402 See file/README about how we map StorageUri operations onto a file system. |
336 """ | 403 """ |
337 | 404 |
338 def __init__(self, object_name, debug): | 405 def __init__(self, object_name, debug, is_stream=False): |
339 """Instantiate a FileStorageUri from a path name. | 406 """Instantiate a FileStorageUri from a path name. |
340 | 407 |
341 @type object_name: string | 408 @type object_name: string |
342 @param object_name: object name | 409 @param object_name: object name |
343 @type debug: boolean | 410 @type debug: boolean |
344 @param debug: whether to enable debugging on this StorageUri | 411 @param debug: whether to enable debugging on this StorageUri |
345 | 412 |
346 After instantiation the components are available in the following | 413 After instantiation the components are available in the following |
347 fields: uri, scheme, bucket_name (always blank for this "anonymous" | 414 fields: uri, scheme, bucket_name (always blank for this "anonymous" |
348 bucket), object_name. | 415 bucket), object_name. |
349 """ | 416 """ |
350 | 417 |
351 self.scheme = 'file' | 418 self.scheme = 'file' |
352 self.bucket_name = '' | 419 self.bucket_name = '' |
353 self.object_name = object_name | 420 self.object_name = object_name |
354 self.uri = 'file://' + object_name | 421 self.uri = 'file://' + object_name |
355 self.debug = debug | 422 self.debug = debug |
| 423 self.stream = is_stream |
356 | 424 |
357 def clone_replace_name(self, new_name): | 425 def clone_replace_name(self, new_name): |
358 """Instantiate a FileStorageUri from the current FileStorageUri, | 426 """Instantiate a FileStorageUri from the current FileStorageUri, |
359 but replacing the object_name. | 427 but replacing the object_name. |
360 | 428 |
361 @type new_name: string | 429 @type new_name: string |
362 @param new_name: new object name | 430 @param new_name: new object name |
363 """ | 431 """ |
364 return FileStorageUri(new_name, self.debug) | 432 return FileStorageUri(new_name, self.debug, self.stream) |
365 | 433 |
366 def names_container(self): | 434 def names_container(self): |
367 """Returns True if this URI names a directory. | 435 """Returns True if this URI is not representing input/output stream |
| 436 and names a directory. |
368 """ | 437 """ |
369 return os.path.isdir(self.object_name) | 438 if not self.stream: |
| 439 return os.path.isdir(self.object_name) |
| 440 else: |
| 441 return False |
370 | 442 |
371 def names_singleton(self): | 443 def names_singleton(self): |
372 """Returns True if this URI names a file. | 444 """Returns True if this URI names a file or |
| 445 if URI represents input/output stream. |
373 """ | 446 """ |
374 return os.path.isfile(self.object_name) | 447 if self.stream: |
| 448 return True |
| 449 else: |
| 450 return os.path.isfile(self.object_name) |
375 | 451 |
376 def is_file_uri(self): | 452 def is_file_uri(self): |
377 return True | 453 return True |
378 | 454 |
379 def is_cloud_uri(self): | 455 def is_cloud_uri(self): |
380 return False | 456 return False |
| 457 |
| 458 def is_stream(self): |
| 459 """Retruns True if this URI represents input/output stream. |
| 460 """ |
| 461 return self.stream |
OLD | NEW |