| 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 |