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