Skip to content
Browse files

Checking in merged changes from googlemerge branch. This will hopeful…

…ly provide a unified code base for boto and gsutil. This also forms the basis of the alpha1 release of 2.0. Fixes issues 379,389,394,400.
  • Loading branch information...
1 parent 391bb63 commit b3fc33bbfc6499985459eeaef44386f1e65dad75 Mitch.Garnaat committed Jul 5, 2010
View
37 boto/__init__.py
@@ -1,4 +1,6 @@
-# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2010, Eucalyptus Systems, Inc.
+# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
@@ -26,8 +28,11 @@
import logging.config
from boto.exception import InvalidUriError
-Version = '1.9b'
-UserAgent = 'Boto/%s (%s)' % (Version, sys.platform)
+__verion__ = '2.0a1'
+Version = __version__ # for backware compatibility
+__svn_version__ = '$Rev$'
+
+UserAgent = 'Boto/%s (%s)' % (__version__, sys.platform)
config = Config()
def init_logging():
@@ -306,13 +311,13 @@ def lookup(service, name):
_aws_cache['.'.join((service,name))] = obj
return obj
-def storage_uri(uri_str, default_provider='file', debug=False):
+def storage_uri(uri_str, default_scheme='file', debug=False):
"""Instantiate a StorageUri from a URI string.
:type uri_str: string
:param uri_str: URI naming bucket + optional object.
- :type default_provider: string
- :param default_provider: default provider for provider-less URIs.
+ :type default_scheme: string
+ :param default_scheme: default scheme for scheme-less URIs.
:rtype: :class:`boto.StorageUri` subclass
:return: StorageUri subclass for given URI.
@@ -323,23 +328,23 @@ def storage_uri(uri_str, default_provider='file', debug=False):
gs://bucket
s3://bucket
filename
- The last example uses the default provider ('file', unless overridden)
+ The last example uses the default scheme ('file', unless overridden)
"""
# Manually parse URI components instead of using urlparse.urlparse because
# what we're calling URIs don't really fit the standard syntax for URIs
# (the latter includes an optional host/net location part).
- end_provider_idx = uri_str.find('://')
- if end_provider_idx == -1:
- provider = default_provider.lower()
+ end_scheme_idx = uri_str.find('://')
+ if end_scheme_idx == -1:
+ scheme = default_scheme.lower()
path = uri_str
else:
- provider = uri_str[0:end_provider_idx].lower()
- path = uri_str[end_provider_idx + 3:]
+ scheme = uri_str[0:end_scheme_idx].lower()
+ path = uri_str[end_scheme_idx + 3:]
- if provider not in ['file', 's3', 'gs']:
- raise InvalidUriError('Unrecognized provider "%s"' % provider)
- if provider == 'file':
+ if scheme not in ['file', 's3', 'gs']:
+ raise InvalidUriError('Unrecognized scheme "%s"' % scheme)
+ if scheme == 'file':
# For file URIs we have no bucket name, and use the complete path
# (minus 'file://') as the object name.
return FileStorageUri(path, debug)
@@ -356,4 +361,4 @@ def storage_uri(uri_str, default_provider='file', debug=False):
object_name = ''
if len(path_parts) > 1:
object_name = path_parts[1]
- return BucketStorageUri(provider, bucket_name, object_name, debug)
+ return BucketStorageUri(scheme, bucket_name, object_name, debug)
View
66 boto/connection.py
@@ -1,6 +1,9 @@
-# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2010 Google
# Copyright (c) 2008 rPath, Inc.
# Copyright (c) 2009 The Echo Nest Corporation
+# Copyright (c) 2010, Eucalyptus Systems, Inc.
+# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
@@ -50,8 +53,9 @@
import xml.sax
import Queue
import boto
-from boto.exception import BotoClientError, BotoServerError
+from boto.exception import AWSConnectionError, BotoClientError, BotoServerError
from boto.resultset import ResultSet
+from boto.provider import Provider
import boto.utils
from boto import config, UserAgent, handler
@@ -99,35 +103,7 @@ def __getitem__(self, key):
def __repr__(self):
return 'ConnectionPool:%s' % ','.join(self._hosts._dict.keys())
-class ProviderCredentials(object):
-
- ProviderCredentialMap = {
- 'aws' : ('aws_access_key_id', 'aws_secret_access_key'),
- 'google' : ('gs_access_key_id', 'gs_secret_access_key'),
- }
-
- def __init__(self, provider, access_key=None, secret_key=None):
- self.provider = provider
- self.access_key = None
- self.secret_key = None
- provider_map = self.ProviderCredentialMap[self.provider]
- access_key_name, secret_key_name = self.ProviderCredentialMap[provider]
- if access_key:
- self.access_key = access_key
- elif os.environ.has_key(access_key_name.upper()):
- self.access_key = os.environ[access_key_name.upper()]
- elif config.has_option('Credentials', access_key_name):
- self.access_key = config.get('Credentials', access_key_name)
-
- if secret_key:
- self.secret_key = secret_key
- elif os.environ.has_key(secret_key_name.upper()):
- self.secret_key = os.environ[secret_key_name.upper()]
- elif config.has_option('Credentials', secret_key_name):
- self.secret_key = config.get('Credentials', secret_key_name)
-
class AWSAuthConnection(object):
-
def __init__(self, host, aws_access_key_id=None, aws_secret_access_key=None,
is_secure=True, port=None, proxy=None, proxy_port=None,
proxy_user=None, proxy_pass=None, debug=0,
@@ -168,10 +144,14 @@ def __init__(self, host, aws_access_key_id=None, aws_secret_access_key=None,
"""
self.num_retries = 5
+ # Override passed-in is_secure setting if value was defined in config.
+ if config.has_option('Boto', 'is_secure'):
+ is_secure = config.getboolean('Boto', 'is_secure')
self.is_secure = is_secure
self.handle_proxy(proxy, proxy_port, proxy_user, proxy_pass)
# define exceptions from httplib that we want to catch and retry
- self.http_exceptions = (httplib.HTTPException, socket.error, socket.gaierror)
+ self.http_exceptions = (httplib.HTTPException, socket.error,
+ socket.gaierror)
# define values in socket exceptions we don't want to catch
self.socket_exception_values = (errno.EINTR,)
if https_connection_factory is not None:
@@ -194,10 +174,14 @@ def __init__(self, host, aws_access_key_id=None, aws_secret_access_key=None,
else:
self.port = PORTS_BY_SECURITY[is_secure]
- self.provider_credentials = ProviderCredentials(provider,
- aws_access_key_id,
- aws_secret_access_key)
+ self.provider = Provider(provider,
+ aws_access_key_id,
+ aws_secret_access_key)
+ # allow config file to override default host
+ if self.provider.host:
+ self.host = self.provider.host
+
# initialize an HMAC for signatures, make copies with each request
self.hmac = hmac.new(self.aws_secret_access_key, digestmod=sha)
if sha256:
@@ -225,13 +209,13 @@ def connection(self):
connection = property(connection)
def aws_access_key_id(self):
- return self.provider_credentials.access_key
+ return self.provider.access_key
aws_access_key_id = property(aws_access_key_id)
gs_access_key_id = aws_access_key_id
access_key = aws_access_key_id
def aws_secret_access_key(self):
- return self.provider_credentials.secret_key
+ return self.provider.secret_key
aws_secret_access_key = property(aws_secret_access_key)
gs_secret_access_key = aws_secret_access_key
secret_key = aws_secret_access_key
@@ -497,12 +481,16 @@ def add_aws_auth_header(self, headers, method, path):
headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
time.gmtime())
- c_string = boto.utils.canonical_string(method, path, headers)
+ c_string = boto.utils.canonical_string(method, path, headers,
+ None, self.provider)
boto.log.debug('Canonical: %s' % c_string)
hmac = self.hmac.copy()
hmac.update(c_string)
b64_hmac = base64.encodestring(hmac.digest()).strip()
- headers['Authorization'] = "AWS %s:%s" % (self.aws_access_key_id, b64_hmac)
+ auth_hdr = self.provider.auth_header
+ headers['Authorization'] = ("%s %s:%s" %
+ (auth_hdr,
+ self.aws_access_key_id, b64_hmac))
def close(self):
"""(Optional) Close any open HTTP connections. This is non-destructive,
@@ -511,7 +499,6 @@ def close(self):
boto.log.debug('closing all HTTP connections')
self.connection = None # compat field
-
class AWSQueryConnection(AWSAuthConnection):
APIVersion = ''
@@ -674,4 +661,3 @@ def get_status(self, action, params, path='/', parent=None, verb='GET'):
boto.log.error('%s %s' % (response.status, response.reason))
boto.log.error('%s' % body)
raise self.ResponseError(response.status, response.reason, body)
-
View
5 boto/ec2/autoscale/__init__.py
@@ -53,8 +53,9 @@ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
boto configuration file.
"""
if not region:
- region = EC2RegionInfo(self, self.DefaultRegionName,
- self.DefaultRegionEndpoint)
+ region = RegionInfo(self, self.DefaultRegionName,
+ self.DefaultRegionEndpoint,
+ AutoScaleConnection)
self.region = region
AWSQueryConnection.__init__(self, aws_access_key_id,
aws_secret_access_key,
View
42 boto/ec2/regioninfo.py
@@ -1,4 +1,6 @@
-# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2010, Eucalyptus Systems, Inc.
+# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
@@ -19,42 +21,14 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
-class RegionInfo(object):
+from boto.regioninfo import RegionInfo
+
+class EC2RegionInfo(RegionInfo):
"""
Represents an EC2 Region
"""
def __init__(self, connection=None, name=None, endpoint=None):
- self.connection = connection
- self.name = name
- self.endpoint = endpoint
-
- def __repr__(self):
- return 'RegionInfo:%s' % self.name
-
- def startElement(self, name, attrs, connection):
- return None
-
- def endElement(self, name, value, connection):
- if name == 'regionName':
- self.name = value
- elif name == 'regionEndpoint':
- self.endpoint = value
- else:
- setattr(self, name, value)
-
- def connect(self, **kw_params):
- """
- Connect to this Region's endpoint. Returns an EC2Connection
- object pointing to the endpoint associated with this region.
- You may pass any of the arguments accepted by the EC2Connection
- object's constructor as keyword arguments and they will be
- passed along to the EC2Connection object.
-
- :rtype: :class:`boto.ec2.connection.EC2Connection`
- :return: The connection to this regions endpoint
- """
from boto.ec2.connection import EC2Connection
- return EC2Connection(region=self, **kw_params)
-
-
+ RegionInfo.__init__(self, connection, name, endpoint,
+ EC2Connection)
View
19 boto/exception.py
@@ -1,4 +1,6 @@
-# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2010, Eucalyptus Systems, Inc.
+# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
@@ -26,14 +28,13 @@
import xml.sax
from boto import handler
from boto.resultset import ResultSet
-import base64
class BotoClientError(StandardError):
"""
General Boto Client error (error accessing AWS)
"""
-
+
def __init__(self, reason):
StandardError.__init__(self)
self.reason = reason
@@ -53,9 +54,9 @@ class S3PermissionsError(BotoClientError):
Permissions error when accessing a bucket or key on S3.
"""
pass
-
+
class BotoServerError(StandardError):
-
+
def __init__(self, status, reason, body=None):
StandardError.__init__(self)
self.status = status
@@ -293,10 +294,16 @@ class S3DataError(BotoClientError):
class FPSResponseError(BotoServerError):
pass
-
class InvalidUriError(Exception):
"""Exception raised when URI is invalid."""
def __init__(self, message):
Exception.__init__(self)
self.message = message
+
+class InvalidAclError(Exception):
+ """Exception raised when ACL XML is invalid."""
+
+ def __init__(self, message):
+ Exception.__init__(self)
+ self.message = message
View
14 boto/file/bucket.py
@@ -40,12 +40,19 @@ def __iter__(self):
def __str__(self):
return 'anonymous bucket for file://' + self.contained_key
- def delete_key(self, key_name, headers=None):
+ def delete_key(self, key_name, headers=None,
+ version_id=None, mfa_token=None):
"""
Deletes a key from the bucket.
:type key_name: string
:param key_name: The key name to delete
+
+ :type version_id: string
+ :param version_id: Unused in this subclass.
+
+ :type mfa_token: tuple or list of strings
+ :param mfa_token: Unused in this subclass.
"""
try:
os.remove(key_name)
@@ -64,14 +71,17 @@ def get_all_keys(self, headers=None, **params):
key = Key(self.name, self.contained_key)
return SimpleResultSet([key])
- def get_key(self, key_name, headers=None):
+ def get_key(self, key_name, headers=None, version_id=None):
"""
Check to see if a particular key exists within the bucket.
Returns: An instance of a Key object or None
:type key_name: string
:param key_name: The name of the key to retrieve
+ :type version_id: string
+ :param version_id: Unused in this subclass.
+
:rtype: :class:`boto.file.key.Key`
:returns: A Key object from this bucket.
"""
View
9 boto/gs/__init__.py
@@ -20,12 +20,3 @@
# IN THE SOFTWARE.
#
-import boto
-
-boto.check_extensions(__name__, __path__)
-
-from connection import GSConnection as Connection
-from boto.s3.key import Key
-from boto.s3.bucket import Bucket
-
-__all__ = ['Connection']
View
261 boto/gs/acl.py
@@ -0,0 +1,261 @@
+# Copyright 2010 Google Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.gs.user import User
+from boto.exception import InvalidAclError
+
+ACCESS_CONTROL_LIST = 'AccessControlList'
+ALL_AUTHENTICATED_USERS = 'AllAuthenticatedUsers'
+ALL_USERS = 'AllUsers'
+DOMAIN = 'Domain'
+EMAIL_ADDRESS = 'EmailAddress'
+ENTRY = 'Entry'
+ENTRIES = 'Entries'
+GROUP_BY_DOMAIN = 'GroupByDomain'
+GROUP_BY_EMAIL = 'GroupByEmail'
+GROUP_BY_ID = 'GroupById'
+ID = 'ID'
+NAME = 'Name'
+OWNER = 'Owner'
+PERMISSION = 'Permission'
+SCOPE = 'Scope'
+TYPE = 'type'
+USER_BY_EMAIL = 'UserByEmail'
+USER_BY_ID = 'UserById'
+
+
+CannedACLStrings = ['private', 'public-read',
+ 'public-read-write', 'authenticated-read',
+ 'bucket-owner-read', 'bucket-owner-full-control']
+
+SupportedPermissions = ['READ', 'WRITE', 'FULL_CONTROL']
+
+class ACL:
+
+ def __init__(self, parent=None):
+ self.parent = parent
+ self.entries = []
+
+ def __repr__(self):
+ entries_repr = ['Owner:%s' % self.owner.__repr__()]
+ acl_entries = self.entries
+ if acl_entries:
+ for e in acl_entries.entry_list:
+ entries_repr.append(e.__repr__())
+ return '<%s>' % ', '.join(entries_repr)
+
+ # Method with same signature as boto.s3.acl.ACL.add_email_grant(), to allow
+ # polymorphic treatment at application layer.
+ def add_email_grant(self, permission, email_address):
+ entry = Entry(type=USER_BY_EMAIL, email_address=email_address,
+ permission=permission)
+ self.entries.entry_list.append(entry)
+
+ # Method with same signature as boto.s3.acl.ACL.add_user_grant(), to allow
+ # polymorphic treatment at application layer.
+ def add_user_grant(self, permission, user_id):
+ entry = Entry(permission=permission, type=USER_BY_ID, id=user_id)
+ self.entries.entry_list.append(entry)
+
+ def startElement(self, name, attrs, connection):
+ if name == OWNER:
+ self.owner = User(self)
+ return self.owner
+ elif name == ENTRIES:
+ self.entries = Entries(self)
+ return self.entries
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == OWNER:
+ pass
+ elif name == ENTRIES:
+ pass
+ else:
+ setattr(self, name, value)
+
+ def to_xml(self):
+ s = '<%s>' % ACCESS_CONTROL_LIST
+ s += self.owner.to_xml()
+ acl_entries = self.entries
+ if acl_entries:
+ s += acl_entries.to_xml()
+ s += '</%s>' % ACCESS_CONTROL_LIST
+ return s
+
+
+class Entries:
+
+ def __init__(self, parent=None):
+ self.parent = parent
+ # Entries is the class that represents the same-named XML
+ # element. entry_list is the list within this class that holds the data.
+ self.entry_list = []
+
+ def __repr__(self):
+ entries_repr = []
+ for e in self.entry_list:
+ entries_repr.append(e.__repr__())
+ return '<Entries: %s>' % ', '.join(entries_repr)
+
+ def startElement(self, name, attrs, connection):
+ if name == ENTRY:
+ entry = Entry(self)
+ self.entry_list.append(entry)
+ return entry
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == ENTRY:
+ pass
+ else:
+ setattr(self, name, value)
+
+ def to_xml(self):
+ s = '<%s>' % ENTRIES
+ for entry in self.entry_list:
+ s += entry.to_xml()
+ s += '</%s>' % ENTRIES
+ return s
+
+
+# Class that represents a single (Scope, Permission) entry in an ACL.
+class Entry:
+
+ def __init__(self, scope=None, type=None, id=None, name=None,
+ email_address=None, domain=None, permission=None):
+ if not scope:
+ scope = Scope(self, type, id, name, email_address, domain)
+ self.scope = scope
+ self.permission = permission
+
+ def __repr__(self):
+ return '<%s: %s>' % (self.scope.__repr__(), self.permission.__repr__())
+
+ def startElement(self, name, attrs, connection):
+ if name == SCOPE:
+ if not TYPE in attrs:
+ raise InvalidAclError('Missing "%s" in "%s" part of ACL' %
+ (TYPE, SCOPE))
+ self.scope = Scope(self, attrs[TYPE])
+ return self.scope
+ elif name == PERMISSION:
+ pass
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == SCOPE:
+ pass
+ elif name == PERMISSION:
+ value = value.strip()
+ if not value in SupportedPermissions:
+ raise InvalidAclError('Invalid Permission "%s"' % value)
+ self.permission = value
+ else:
+ setattr(self, name, value)
+
+ def to_xml(self):
+ s = '<%s>' % ENTRY
+ s += self.scope.to_xml()
+ s += '<%s>%s</%s>' % (PERMISSION, self.permission, PERMISSION)
+ s += '</%s>' % ENTRY
+ return s
+
+class Scope:
+
+ # Map from Scope type to list of allowed sub-elems.
+ ALLOWED_SCOPE_TYPE_SUB_ELEMS = {
+ ALL_AUTHENTICATED_USERS : [],
+ ALL_USERS : [],
+ GROUP_BY_DOMAIN : [DOMAIN],
+ GROUP_BY_EMAIL : [EMAIL_ADDRESS, NAME],
+ GROUP_BY_ID : [ID, NAME],
+ USER_BY_EMAIL : [EMAIL_ADDRESS, NAME],
+ USER_BY_ID : [ID, NAME]
+ }
+
+ def __init__(self, parent, type=None, id=None, name=None,
+ email_address=None, domain=None):
+ self.parent = parent
+ self.type = type
+ self.name = name
+ self.id = id
+ self.domain = domain
+ self.email_address = email_address
+ if not self.ALLOWED_SCOPE_TYPE_SUB_ELEMS.has_key(self.type):
+ raise InvalidAclError('Invalid %s %s "%s" ' %
+ (SCOPE, TYPE, self.type))
+
+ def __repr__(self):
+ named_entity = None
+ if self.id:
+ named_entity = self.id
+ elif self.email_address:
+ named_entity = self.email_address
+ elif self.domain:
+ named_entity = self.domain
+ if named_entity:
+ return '<%s: %s>' % (self.type, named_entity)
+ else:
+ return '<%s>' % self.type
+
+ def startElement(self, name, attrs, connection):
+ if not name in self.ALLOWED_SCOPE_TYPE_SUB_ELEMS[self.type]:
+ raise InvalidAclError('Element "%s" not allowed in %s %s "%s" ' %
+ (name, SCOPE, TYPE, self.type))
+ return None
+
+ def endElement(self, name, value, connection):
+ value = value.strip()
+ if name == DOMAIN:
+ self.domain = value
+ elif name == EMAIL_ADDRESS:
+ self.email_address = value
+ elif name == ID:
+ self.id = value
+ elif name == NAME:
+ self.name = value
+ else:
+ setattr(self, name, value)
+
+ def to_xml(self):
+ s = '<%s type="%s">' % (SCOPE, self.type)
+ if self.type == ALL_AUTHENTICATED_USERS or self.type == ALL_USERS:
+ pass
+ elif self.type == GROUP_BY_DOMAIN:
+ s += '<%s>%s</%s>' % (DOMAIN, self.domain, DOMAIN)
+ elif self.type == GROUP_BY_EMAIL or self.type == USER_BY_EMAIL:
+ s += '<%s>%s</%s>' % (EMAIL_ADDRESS, self.email_address,
+ EMAIL_ADDRESS)
+ if self.name:
+ s += '<%s>%s</%s>' % (NAME, self.name, NAME)
+ elif self.type == GROUP_BY_ID or self.type == USER_BY_ID:
+ s += '<%s>%s</%s>' % (ID, self.id, ID)
+ if self.name:
+ s += '<%s>%s</%s>' % (NAME, self.name, NAME)
+ else:
+ raise InvalidAclError('Invalid scope type "%s" ', self.type)
+
+ s += '</%s>' % SCOPE
+ return s
View
132 boto/gs/bucket.py
@@ -0,0 +1,132 @@
+# Copyright 2010 Google Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import boto
+from boto import handler
+from boto.exception import InvalidAclError, S3ResponseError, S3PermissionsError
+from boto.gs.acl import ACL
+from boto.gs.acl import SupportedPermissions as GSPermissions
+from boto.gs.key import Key as GSKey
+from boto.s3.acl import Policy
+from boto.s3.bucket import Bucket as S3Bucket
+import xml.sax
+
+class Bucket(S3Bucket):
+
+ def __init__(self, connection=None, name=None, key_class=GSKey):
+ super(Bucket, self).__init__(connection, name, key_class)
+
+ def set_acl(self, acl_or_str, key_name='', headers=None, version_id=None):
+ if isinstance(acl_or_str, Policy):
+ raise InvalidAclError('Attempt to set S3 Policy on GS ACL')
+ elif isinstance(acl_or_str, ACL):
+ self.set_xml_acl(acl_or_str.to_xml(), key_name, headers=headers)
+ else:
+ self.set_canned_acl(acl_or_str, key_name, headers=headers)
+
+ def get_acl(self, key_name='', headers=None, version_id=None):
+ response = self.connection.make_request('GET', self.name, key_name,
+ query_args='acl', headers=headers)
+ body = response.read()
+ if response.status == 200:
+ acl = ACL(self)
+ h = handler.XmlHandler(acl, self)
+ xml.sax.parseString(body, h)
+ return acl
+ else:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ # Method with same signature as boto.s3.bucket.Bucket.add_email_grant(),
+ # to allow polymorphic treatment at application layer.
+ def add_email_grant(self, permission, email_address,
+ recursive=False, headers=None):
+ """
+ Convenience method that provides a quick way to add an email grant
+ to a bucket. This method retrieves the current ACL, creates a new
+ grant based on the parameters passed in, adds that grant to the ACL
+ and then PUT's the new ACL back to GS.
+
+ :type permission: string
+ :param permission: The permission being granted. Should be one of:
+ (READ, WRITE, FULL_CONTROL).
+
+ :type email_address: string
+ :param email_address: The email address associated with the GS
+ account your are granting the permission to.
+
+ :type recursive: boolean
+ :param recursive: A boolean value to controls whether the command
+ will apply the grant to all keys within the bucket
+ or not. The default value is False. By passing a
+ True value, the call will iterate through all keys
+ in the bucket and apply the same grant to each key.
+ CAUTION: If you have a lot of keys, this could take
+ a long time!
+ """
+ if permission not in GSPermissions:
+ raise S3PermissionsError('Unknown Permission: %s' % permission)
+ acl = self.get_acl(headers=headers)
+ acl.add_email_grant(permission, email_address)
+ self.set_acl(acl, headers=headers)
+ if recursive:
+ for key in self:
+ key.add_email_grant(permission, email_address, headers=headers)
+
+ # Method with same signature as boto.s3.bucket.Bucket.add_user_grant(),
+ # to allow polymorphic treatment at application layer.
+ def add_user_grant(self, permission, user_id, recursive=False, headers=None):
+ """
+ Convenience method that provides a quick way to add a canonical user grant to a bucket.
+ This method retrieves the current ACL, creates a new grant based on the parameters
+ passed in, adds that grant to the ACL and then PUTs the new ACL back to GS.
+
+ :type permission: string
+ :param permission: The permission being granted. Should be one of:
+ (READ|WRITE|FULL_CONTROL)
+
+ :type user_id: string
+ :param user_id: The canonical user id associated with the GS account you are granting
+ the permission to.
+
+ :type recursive: bool
+ :param recursive: A boolean value to controls whether the command
+ will apply the grant to all keys within the bucket
+ or not. The default value is False. By passing a
+ True value, the call will iterate through all keys
+ in the bucket and apply the same grant to each key.
+ CAUTION: If you have a lot of keys, this could take
+ a long time!
+ """
+ if permission not in GSPermissions:
+ raise S3PermissionsError('Unknown Permission: %s' % permission)
+ acl = self.get_acl(headers=headers)
+ acl.add_user_grant(permission, user_id)
+ self.set_acl(acl, headers=headers)
+ if recursive:
+ for key in self:
+ key.add_user_grant(permission, user_id, headers=headers)
+
+ # Method with same input signature as boto.s3.bucket.Bucket.list_grants()
+ # (but returning different object type), to allow polymorphic treatment
+ # at application layer.
+ def list_grants(self, headers=None):
+ acl = self.get_acl(headers=headers)
+ return acl.entries
View
4 boto/gs/connection.py
@@ -19,8 +19,10 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
+import boto
from boto.s3.connection import S3Connection
from boto.s3.connection import SubdomainCallingFormat
+from boto.gs.bucket import Bucket
class GSConnection(S3Connection):
@@ -35,4 +37,4 @@ def __init__(self, gs_access_key_id=None, gs_secret_access_key=None,
S3Connection.__init__(self, gs_access_key_id, gs_secret_access_key,
is_secure, port, proxy, proxy_port, proxy_user, proxy_pass,
host, debug, https_connection_factory, calling_format, path,
- "google")
+ "google", boto.gs.bucket.Bucket)
View
65 boto/gs/key.py
@@ -0,0 +1,65 @@
+# Copyright 2010 Google Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import boto
+from boto.s3.key import Key as S3Key
+
+class Key(S3Key):
+
+ def add_email_grant(self, permission, email_address):
+ """
+ Convenience method that provides a quick way to add an email grant to a key.
+ This method retrieves the current ACL, creates a new grant based on the parameters
+ passed in, adds that grant to the ACL and then PUT's the new ACL back to GS.
+
+ :type permission: string
+ :param permission: The permission being granted. Should be one of:
+ READ|FULL_CONTROL
+ See http://code.google.com/apis/storage/docs/developer-guide.html#authorization
+ for more details on permissions.
+
+ :type email_address: string
+ :param email_address: The email address associated with the AWS account you are granting
+ the permission to.
+ """
+ acl = self.get_acl()
+ acl.add_email_grant(permission, email_address)
+ self.set_acl(acl)
+
+ def add_user_grant(self, permission, user_id):
+ """
+ Convenience method that provides a quick way to add a canonical user grant to a key.
+ This method retrieves the current ACL, creates a new grant based on the parameters
+ passed in, adds that grant to the ACL and then PUT's the new ACL back to GS.
+
+ :type permission: string
+ :param permission: The permission being granted. Should be one of:
+ READ|FULL_CONTROL
+ See http://code.google.com/apis/storage/docs/developer-guide.html#authorization
+ for more details on permissions.
+
+ :type user_id: string
+ :param user_id: The canonical user id associated with the GS account you are granting
+ the permission to.
+ """
+ acl = self.get_acl()
+ acl.add_user_grant(permission, user_id)
+ self.set_acl(acl)
View
54 boto/gs/user.py
@@ -0,0 +1,54 @@
+# Copyright 2010 Google Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+
+class User:
+ def __init__(self, parent=None, id='', name=''):
+ if parent:
+ parent.owner = self
+ self.type = None
+ self.id = id
+ self.name = name
+
+ def __repr__(self):
+ return self.id
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Name':
+ self.name = value
+ elif name == 'ID':
+ self.id = value
+ else:
+ setattr(self, name, value)
+
+ def to_xml(self, element_name='Owner'):
+ if self.type:
+ s = '<%s type="%s">' % (element_name, self.type)
+ else:
+ s = '<%s>' % element_name
+ s += '<ID>%s</ID>' % self.id
+ if self.name:
+ s += '<Name>%s</Name>' % self.name
+ s += '</%s>' % element_name
+ return s
View
162 boto/provider.py
@@ -0,0 +1,162 @@
+# Copyright (c) 2010 Mitch Garnaat http://garnaat.org/
+# Copyright 2010 Google Inc.
+# Copyright (c) 2010, Eucalyptus Systems, Inc.
+# All rights reserved.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+"""
+This class encapsulates the provider-specific header differences.
+"""
+
+import os
+import boto
+from boto import config
+from boto.gs.acl import ACL
+from boto.gs.acl import CannedACLStrings as CannedGSACLStrings
+from boto.s3.acl import CannedACLStrings as CannedS3ACLStrings
+from boto.s3.acl import Policy
+
+HEADER_PREFIX_KEY = 'header_prefix'
+METADATA_PREFIX_KEY = 'metadata_prefix'
+
+AWS_HEADER_PREFIX = 'x-amz-'
+GOOG_HEADER_PREFIX = 'x-goog-'
+
+ACL_HEADER_KEY = 'acl-header'
+AUTH_HEADER_KEY = 'auth-header'
+COPY_SOURCE_HEADER_KEY = 'copy-source-header'
+COPY_SOURCE_VERSION_ID_HEADER_KEY = 'copy-source-version-id-header'
+DELETE_MARKER_HEADER_KEY = 'delete-marker-header'
+DATE_HEADER_KEY = 'date-header'
+METADATA_DIRECTIVE_HEADER_KEY = 'metadata-directive-header'
+SECURITY_TOKEN_HEADER_KEY = 'security-token-header'
+STORAGE_CLASS_HEADER_KEY = 'storage-class'
+MFA_HEADER_KEY = 'mfa-header'
+VERSION_ID_HEADER_KEY = 'version-id-header'
+
+class Provider(object):
+
+ CredentialMap = {
+ 'aws' : ('aws_access_key_id', 'aws_secret_access_key'),
+ 'google' : ('gs_access_key_id', 'gs_secret_access_key'),
+ }
+
+ AclClassMap = {
+ 'aws' : Policy,
+ 'google' : ACL
+ }
+
+ CannedAclsMap = {
+ 'aws' : CannedS3ACLStrings,
+ 'google' : CannedGSACLStrings
+ }
+
+ HostKeyMap = {
+ 'aws' : 's3',
+ 'google' : 'gs'
+ }
+
+ HeaderInfoMap = {
+ 'aws' : {
+ HEADER_PREFIX_KEY : AWS_HEADER_PREFIX,
+ METADATA_PREFIX_KEY : AWS_HEADER_PREFIX + 'meta-',
+ ACL_HEADER_KEY : AWS_HEADER_PREFIX + 'acl',
+ AUTH_HEADER_KEY : 'AWS',
+ COPY_SOURCE_HEADER_KEY : AWS_HEADER_PREFIX + 'copy-source',
+ COPY_SOURCE_VERSION_ID_HEADER_KEY : AWS_HEADER_PREFIX +
+ 'copy-source-version-id',
+ DATE_HEADER_KEY : AWS_HEADER_PREFIX + 'date',
+ DELETE_MARKER_HEADER_KEY : AWS_HEADER_PREFIX + 'delete-marker',
+ METADATA_DIRECTIVE_HEADER_KEY : AWS_HEADER_PREFIX +
+ 'metadata-directive',
+ SECURITY_TOKEN_HEADER_KEY : AWS_HEADER_PREFIX + 'security-token',
+ VERSION_ID_HEADER_KEY : AWS_HEADER_PREFIX + 'version-id',
+ STORAGE_CLASS_HEADER_KEY : AWS_HEADER_PREFIX + 'storage-class',
+ MFA_HEADER_KEY : AWS_HEADER_PREFIX + 'mfa',
+ },
+ 'google' : {
+ HEADER_PREFIX_KEY : GOOG_HEADER_PREFIX,
+ METADATA_PREFIX_KEY : GOOG_HEADER_PREFIX + 'meta-',
+ ACL_HEADER_KEY : GOOG_HEADER_PREFIX + 'acl',
+ AUTH_HEADER_KEY : 'GOOG1',
+ COPY_SOURCE_HEADER_KEY : GOOG_HEADER_PREFIX + 'copy-source',
+ COPY_SOURCE_VERSION_ID_HEADER_KEY : GOOG_HEADER_PREFIX +
+ 'copy-source-version-id',
+ DATE_HEADER_KEY : GOOG_HEADER_PREFIX + 'date',
+ DELETE_MARKER_HEADER_KEY : GOOG_HEADER_PREFIX + 'delete-marker',
+ METADATA_DIRECTIVE_HEADER_KEY : GOOG_HEADER_PREFIX +
+ 'metadata-directive',
+ SECURITY_TOKEN_HEADER_KEY : GOOG_HEADER_PREFIX + 'security-token',
+ VERSION_ID_HEADER_KEY : GOOG_HEADER_PREFIX + 'version-id',
+ STORAGE_CLASS_HEADER_KEY : None,
+ MFA_HEADER_KEY : None,
+ }
+ }
+
+ def __init__(self, name, access_key=None, secret_key=None):
+ self.host = None
+ self.access_key = None
+ self.secret_key = None
+ self.name = name
+ self.acl_class = self.AclClassMap[self.name]
+ self.canned_acls = self.CannedAclsMap[self.name]
+ self.get_credentials(access_key, secret_key)
+ self.configure_headers()
+ # allow config file to override default host
+ host_opt_name = '%s_host' % self.HostKeyMap[self.name]
+ if (config.has_option('Credentials', host_opt_name)):
+ self.host = config.get('Credentials', host_opt_name)
+
+ def get_credentials(self, access_key=None, secret_key=None):
+ access_key_name, secret_key_name = self.CredentialMap[self.name]
+ if access_key:
+ self.access_key = access_key
+ elif os.environ.has_key(access_key_name.upper()):
+ self.access_key = os.environ[access_key_name.upper()]
+ elif config.has_option('Credentials', access_key_name):
+ self.access_key = config.get('Credentials', access_key_name)
+
+ if secret_key:
+ self.secret_key = secret_key
+ elif os.environ.has_key(secret_key_name.upper()):
+ self.secret_key = os.environ[secret_key_name.upper()]
+ elif config.has_option('Credentials', secret_key_name):
+ self.secret_key = config.get('Credentials', secret_key_name)
+
+ def configure_headers(self):
+ header_info_map = self.HeaderInfoMap[self.name]
+ self.metadata_prefix = header_info_map[METADATA_PREFIX_KEY]
+ self.header_prefix = header_info_map[HEADER_PREFIX_KEY]
+ self.acl_header = header_info_map[ACL_HEADER_KEY]
+ self.auth_header = header_info_map[AUTH_HEADER_KEY]
+ self.copy_source_header = header_info_map[COPY_SOURCE_HEADER_KEY]
+ self.copy_source_version_id = header_info_map[COPY_SOURCE_VERSION_ID_HEADER_KEY]
+ self.date_header = header_info_map[DATE_HEADER_KEY]
+ self.delete_marker = header_info_map[DELETE_MARKER_HEADER_KEY]
+ self.metadata_directive_header = (
+ header_info_map[METADATA_DIRECTIVE_HEADER_KEY])
+ self.security_token_header = header_info_map[SECURITY_TOKEN_HEADER_KEY]
+ self.storage_class_header = header_info_map[STORAGE_CLASS_HEADER_KEY]
+ self.version_id = header_info_map[VERSION_ID_HEADER_KEY]
+ self.mfa_header = header_info_map[MFA_HEADER_KEY]
+
+# Static utility method for getting default Provider.
+def get_default():
+ return Provider('aws')
View
64 boto/regioninfo.py
@@ -0,0 +1,64 @@
+# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2010, Eucalyptus Systems, Inc.
+# All rights reserved.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class RegionInfo(object):
+ """
+ Represents an AWS Region
+ """
+
+ def __init__(self, connection=None, name=None, endpoint=None,
+ connection_cls=None):
+ self.connection = connection
+ self.name = name
+ self.endpoint = endpoint
+ self.connection_cls = connection_cls
+
+ def __repr__(self):
+ return 'RegionInfo:%s' % self.name
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'regionName':
+ self.name = value
+ elif name == 'regionEndpoint':
+ self.endpoint = value
+ else:
+ setattr(self, name, value)
+
+ def connect(self, **kw_params):
+ """
+ Connect to this Region's endpoint. Returns an connection
+ object pointing to the endpoint associated with this region.
+ You may pass any of the arguments accepted by the connection
+ class's constructor as keyword arguments and they will be
+ passed along to the connection object.
+
+ :rtype: Connection object
+ :return: The connection to this regions endpoint
+ """
+ if self.connection_cls:
+ return self.connection_cls(region=self, **kw_params)
+
+
View
11 boto/s3/__init__.py
@@ -1,4 +1,6 @@
-# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2010, Eucalyptus Systems, Inc.
+# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
@@ -20,10 +22,3 @@
# IN THE SOFTWARE.
#
-import boto
-
-from connection import S3Connection as Connection
-from key import Key
-from bucket import Bucket
-
-__all__ = [Connection, Key, Bucket]
View
31 boto/s3/bucket.py
@@ -1,4 +1,6 @@
-# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2010, Eucalyptus Systems, Inc.
+# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
@@ -22,10 +24,11 @@
import boto
from boto import handler
from boto.resultset import ResultSet
-from boto.s3.acl import Policy, CannedACLStrings, Grant
+from boto.s3.acl import ACL, Policy, CannedACLStrings, Grant
from boto.s3.key import Key
from boto.s3.prefix import Prefix
from boto.s3.deletemarker import DeleteMarker
+from boto.s3.user import User
from boto.exception import S3ResponseError, S3PermissionsError, S3CopyError
from boto.s3.bucketlistresultset import BucketListResultSet
from boto.s3.bucketlistresultset import VersionedBucketListResultSet
@@ -36,7 +39,7 @@
S3Permissions = ['READ', 'WRITE', 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL']
-class Bucket:
+class Bucket(object):
BucketLoggingBody = """<?xml version="1.0" encoding="UTF-8"?>
<BucketLoggingStatus xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
@@ -139,7 +142,8 @@ def get_key(self, key_name, headers=None, version_id=None):
if response.status == 200:
response.read()
k = self.key_class(self)
- k.metadata = boto.utils.get_aws_metadata(response.msg)
+ provider = self.connection.provider
+ k.metadata = boto.utils.get_aws_metadata(response.msg, provider)
k.etag = response.getheader('etag')
k.content_type = response.getheader('content-type')
k.content_encoding = response.getheader('content-encoding')
@@ -361,7 +365,8 @@ def delete_key(self, key_name, headers=None,
if mfa_token:
if not headers:
headers = {}
- headers['x-amz-mfa'] = ' '.join(mfa_token)
+ provider = self.connection.provider
+ headers[provider.mfa_header] = ' '.join(mfa_token)
response = self.connection.make_request('DELETE', self.name, key_name,
headers=headers,
query_args=query_args)
@@ -424,14 +429,15 @@ def copy_key(self, new_key_name, src_bucket_name,
src = '%s/%s' % (src_bucket_name, urllib.quote(src_key_name))
if src_version_id:
src += '?version_id=%s' % src_version_id
- headers = {'x-amz-copy-source' : src}
+ provider = self.connection.provider
+ headers = {provider.copy_source_header : src}
if storage_class != 'STANDARD':
- headers['x-amz-storage-class'] = storage_class
+ headers[provider.storage_class] = storage_class
if metadata:
- headers['x-amz-metadata-directive'] = 'REPLACE'
+ headers[provider.metadata_directive_header] = 'REPLACE'
headers = boto.utils.merge_meta(headers, metadata)
else:
- headers['x-amz-metadata-directive'] = 'COPY'
+ headers[provider.metadata_directive_header] = 'COPY'
response = self.connection.make_request('PUT', self.name, new_key_name,
headers=headers)
body = response.read()
@@ -453,9 +459,9 @@ def set_canned_acl(self, acl_str, key_name='', headers=None,
assert acl_str in CannedACLStrings
if headers:
- headers['x-amz-acl'] = acl_str
+ headers[self.connection.provider.acl_header] = acl_str
else:
- headers={'x-amz-acl': acl_str}
+ headers={self.connection.provider.acl_header: acl_str}
query_args='acl'
if version_id:
@@ -706,7 +712,8 @@ def configure_versioning(self, versioning, mfa_delete=False,
if mfa_token:
if not headers:
headers = {}
- headers['x-amz-mfa'] = ' '.join(mfa_token)
+ provider = self.connection.provider
+ headers[provider.mfa_header] = ' '.join(mfa_token)
response = self.connection.make_request('PUT', self.name, data=body,
query_args='versioning', headers=headers)
body = response.read()
View
48 boto/s3/connection.py
@@ -1,4 +1,6 @@
-# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2010, Eucalyptus Systems, Inc.
+# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
@@ -63,13 +65,13 @@ def build_path_base(self, bucket, key=''):
return '/%s' % urllib.quote(key)
class SubdomainCallingFormat(_CallingFormat):
-
+
@assert_case_insensitive
def get_bucket_server(self, server, bucket):
return '%s.%s' % (bucket, server)
class VHostCallingFormat(_CallingFormat):
-
+
@assert_case_insensitive
def get_bucket_server(self, server, bucket):
return bucket
@@ -91,7 +93,7 @@ class Location:
USWest = 'us-west-1'
#boto.set_stream_logger('s3')
-
+
class S3Connection(AWSAuthConnection):
DefaultHost = 's3.amazonaws.com'
@@ -101,8 +103,10 @@ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
is_secure=True, port=None, proxy=None, proxy_port=None,
proxy_user=None, proxy_pass=None,
host=DefaultHost, debug=0, https_connection_factory=None,
- calling_format=SubdomainCallingFormat(), path='/', provider='aws'):
+ calling_format=SubdomainCallingFormat(), path='/', provider='aws',
+ bucket_class=Bucket):
self.calling_format = calling_format
+ self.bucket_class = bucket_class
AWSAuthConnection.__init__(self, host,
aws_access_key_id, aws_secret_access_key,
is_secure, port, proxy, proxy_port, proxy_user, proxy_pass,
@@ -116,6 +120,17 @@ def __iter__(self):
def __contains__(self, bucket_name):
return not (self.lookup(bucket_name) is None)
+ def set_bucket_class(self, bucket_class):
+ """
+ Set the Bucket class associated with this bucket. By default, this
+ would be the boto.s3.key.Bucket class but if you want to subclass that
+ for some reason this allows you to associate your new class.
+
+ :type bucket_class: class
+ :param bucket_class: A subclass of Bucket that can be more specific
+ """
+ self.bucket_class = bucket_class
+
def build_post_policy(self, expiration_time, conditions):
"""
Taken from the AWS book Python examples and modified for use with boto
@@ -218,7 +233,7 @@ def build_post_form_args(self, bucket_name, key, expires_in = 6000,
fields.append({"name": "key", "value": key})
# HTTPS protocol will be used if the secure HTTP option is enabled.
- url = '%s://%s.s3.amazonaws.com/' % (http_method, bucket_name)
+ url = '%s://%s.%s/' % (http_method, bucket_name, self.host)
return {"action": url, "fields": fields}
@@ -231,7 +246,8 @@ def generate_url(self, expires_in, method, bucket='', key='',
auth_path = self.calling_format.build_auth_path(bucket, key)
auth_path = self.get_path(auth_path)
canonical_str = boto.utils.canonical_string(method, auth_path,
- headers, expires)
+ headers, expires,
+ self.provider)
hmac_copy = self.hmac.copy()
hmac_copy.update(canonical_str)
b64_hmac = base64.encodestring(hmac_copy.digest()).strip()
@@ -240,8 +256,10 @@ def generate_url(self, expires_in, method, bucket='', key='',
if query_auth:
query_part = '?' + self.QueryString % (encoded_canonical, expires,
self.aws_access_key_id)
- if 'x-amz-security-token' in headers:
- query_part += '&x-amz-security-token=%s' % urllib.quote(headers['x-amz-security-token']);
+ sec_hdr = self.provider.security_token_header
+ if sec_hdr in headers:
+ query_part += ('&%s=%s' % (sec_hdr,
+ urllib.quote(headers[sec_hdr])));
else:
query_part = ''
if force_http:
@@ -258,7 +276,7 @@ def get_all_buckets(self, headers=None):
body = response.read()
if response.status > 300:
raise S3ResponseError(response.status, response.reason, body)
- rs = ResultSet([('Bucket', Bucket)])
+ rs = ResultSet([('Bucket', self.bucket_class)])
h = handler.XmlHandler(rs, self)
xml.sax.parseString(body, h)
return rs
@@ -278,7 +296,7 @@ def get_canonical_user_id(self, headers=None):
return rs.ID
def get_bucket(self, bucket_name, validate=True, headers=None):
- bucket = Bucket(self, bucket_name)
+ bucket = self.bucket_class(self, bucket_name)
if validate:
bucket.get_all_keys(headers, maxkeys=0)
return bucket
@@ -315,9 +333,9 @@ def create_bucket(self, bucket_name, headers=None,
if policy:
if headers:
- headers['x-amz-acl'] = policy
+ headers[self.provider.acl_header] = policy
else:
- headers = {'x-amz-acl' : policy}
+ headers = {self.provider.acl_header : policy}
if location == Location.DEFAULT:
data = ''
else:
@@ -329,7 +347,7 @@ def create_bucket(self, bucket_name, headers=None,
if response.status == 409:
raise S3CreateError(response.status, response.reason, body)
if response.status == 200:
- return Bucket(self, bucket_name)
+ return self.bucket_class(self, bucket_name)
else:
raise S3ResponseError(response.status, response.reason, body)
@@ -341,7 +359,7 @@ def delete_bucket(self, bucket, headers=None):
def make_request(self, method, bucket='', key='', headers=None, data='',
query_args=None, sender=None):
- if isinstance(bucket, Bucket):
+ if isinstance(bucket, self.bucket_class):
bucket = bucket.name
if isinstance(key, Key):
key = key.name
View
36 boto/s3/key.py
@@ -28,7 +28,6 @@
from boto.exception import S3ResponseError, S3DataError, BotoClientError
from boto.s3.user import User
from boto import UserAgent
-
try:
from hashlib import md5
except ImportError:
@@ -45,6 +44,7 @@ def __init__(self, bucket=None, name=None):
self.bucket = bucket
self.name = name
self.metadata = {}
+ self.cache_control = None
self.content_type = self.DefaultContentType
self.content_encoding = None
self.filename = None
@@ -84,9 +84,10 @@ def __iter__(self):
return self
def handle_version_headers(self, resp):
- self.version_id = resp.getheader('x-amz-version-id', None)
- self.source_version_id = resp.getheader('x-amz-copy-source-version-id', None)
- if resp.getheader('x-amz-delete-marker', 'false') == 'true':
+ provider = self.bucket.connection.provider
+ self.version_id = resp.getheader(provider.version_id, None)
+ self.source_version_id = resp.getheader(provider.copy_source_version_id, None)
+ if resp.getheader(provider.delete_marker, 'false') == 'true':
self.delete_marker = True
else:
self.delete_marker = False
@@ -112,7 +113,9 @@ def open_read(self, headers=None, query_args=None):
body = self.resp.read()
raise S3ResponseError(self.resp.status, self.resp.reason, body)
response_headers = self.resp.msg
- self.metadata = boto.utils.get_aws_metadata(response_headers)
+ provider = self.bucket.connection.provider
+ self.metadata = boto.utils.get_aws_metadata(response_headers,
+ provider)
for name,value in response_headers.items():
if name.lower() == 'content-length':
self.size = int(value)
@@ -124,6 +127,8 @@ def open_read(self, headers=None, query_args=None):
self.content_encoding = value
elif name.lower() == 'last-modified':
self.last_modified = value
+ elif name.lower() == 'cache-control':
+ self.cache_control = value
self.handle_version_headers(self.resp)
def open_write(self, headers=None):
@@ -433,9 +438,12 @@ def sender(http_conn, method, path, data, headers):
headers['User-Agent'] = UserAgent
headers['Content-MD5'] = self.base64md5
if self.storage_class != 'STANDARD':
- headers['x-amz-storage-class'] = self.storage_class
+ provider = self.bucket.connection.provider
+ headers[provider.storage_class_header] = self.storage_class
if headers.has_key('Content-Type'):
self.content_type = headers['Content-Type']
+ if headers.has_key('Content-Encoding'):
+ self.content_encoding = headers['Content-Encoding']
elif self.path:
self.content_type = mimetypes.guess_type(self.path)[0]
if self.content_type == None:
@@ -445,7 +453,8 @@ def sender(http_conn, method, path, data, headers):
headers['Content-Type'] = self.content_type
headers['Content-Length'] = str(self.size)
headers['Expect'] = '100-Continue'
- headers = boto.utils.merge_meta(headers, self.metadata)
+ headers = boto.utils.merge_meta(headers, self.metadata,
+ self.bucket.connection.provider)
resp = self.bucket.connection.make_request('PUT', self.bucket.name,
self.name, headers,
sender=sender)
@@ -519,7 +528,6 @@ def set_contents_from_file(self, fp, headers=None, replace=True,
:param md5: If you need to compute the MD5 for any reason prior to upload,
it's silly to have to do it twice so this param, if present, will be
used as the MD5 values of the file. Otherwise, the checksum will be computed.
-
:type reduced_redundancy: bool
:param reduced_redundancy: If True, this will set the storage
class of the new Key to be
@@ -528,13 +536,17 @@ class of the new Key to be
redundancy at lower storage cost.
"""
+ provider = self.bucket.connection.provider
if headers is None:
headers = {}
if policy:
- headers['x-amz-acl'] = policy
+ headers[provider.acl_header] = policy
if reduced_redundancy:
self.storage_class = 'REDUCED_REDUNDANCY'
- headers['x-amz-storage-class'] = self.storage_class
+ if provider.storage_class_header:
+ headers[provider.storage_class_header] = self.storage_class
+ # TODO - What if the provider doesn't support reduced reduncancy?
+ # What if different providers provide different classes?
if hasattr(fp, 'name'):
self.path = fp.name
if self.bucket != None:
@@ -597,7 +609,6 @@ class of the new Key to be
REDUCED_REDUNDANCY. The Reduced Redundancy
Storage (RRS) feature of S3, provides lower
redundancy at lower storage cost.
-
"""
fp = open(filename, 'rb')
self.set_contents_from_file(fp, headers, replace, cb, num_cb,
@@ -648,7 +659,6 @@ class of the new Key to be
REDUCED_REDUNDANCY. The Reduced Redundancy
Storage (RRS) feature of S3, provides lower
redundancy at lower storage cost.
-
"""
fp = StringIO.StringIO(s)
r = self.set_contents_from_file(fp, headers, replace, cb, num_cb,
@@ -693,7 +703,7 @@ def get_file(self, fp, headers=None, cb=None, num_cb=10,
save_debug = self.bucket.connection.debug
if self.bucket.connection.debug == 1:
self.bucket.connection.debug = 0
-
+
query_args = ''
if torrent:
query_args = 'torrent'
View
22 boto/sdb/regioninfo.py
@@ -1,4 +1,6 @@
-# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2010, Eucalyptus Systems, Inc.
+# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
@@ -20,21 +22,11 @@
# IN THE SOFTWARE.
#
-from boto.ec2.regioninfo import RegionInfo
+from boto.regioninfo import RegionInfo
class SDBRegionInfo(RegionInfo):
- def connect(self, **kw_params):
- """
- Connect to this Region's endpoint. Returns an SDBConnection
- object pointing to the endpoint associated with this region.
- You may pass any of the arguments accepted by the SDBConnection
- object's constructor as keyword arguments and they will be
- passed along to the SDBConnection object.
-
- :rtype: :class:`boto.sdb.connection.SDBConnection`
- :return: The connection to this regions endpoint
- """
+ def __init__(self, connection=None, name=None, endpoint=None):
from boto.sdb.connection import SDBConnection
- return SDBConnection(region=self, **kw_params)
-
+ RegionInfo.__init__(self, connection, name, endpoint,
+ SDBConnection)
View
22 boto/sqs/regioninfo.py
@@ -1,4 +1,6 @@
-# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2010, Eucalyptus Systems, Inc.
+# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
@@ -20,21 +22,11 @@
# IN THE SOFTWARE.
#
-from boto.ec2.regioninfo import RegionInfo
+from boto.regioninfo import RegionInfo
class SQSRegionInfo(RegionInfo):
- def connect(self, **kw_params):
- """
- Connect to this Region's endpoint. Returns an SQSConnection
- object pointing to the endpoint associated with this region.
- You may pass any of the arguments accepted by the SQSConnection
- object's constructor as keyword arguments and they will be
- passed along to the SQSConnection object.
-
- :rtype: :class:`boto.sqs.connection.SQSConnection`
- :return: The connection to this regions endpoint
- """
+ def __init__(self, connection=None, name=None, endpoint=None):
from boto.sqs.connection import SQSConnection
- return SQSConnection(region=self, **kw_params)
-
+ RegionInfo.__init__(self, connection, name, endpoint,
+ SQSConnection)
View
142 boto/storage_uri.py
@@ -41,7 +41,7 @@ def __init__(self):
raise BotoClientError('Attempt to instantiate abstract StorageUri '
'class')
- def __str__(self):
+ def __repr__(self):
"""Returns string representation of URI."""
return self.uri
@@ -52,7 +52,7 @@ def equals(self, uri):
def connect(self, access_key_id=None, secret_access_key=None, **kwargs):
"""
Opens a connection to appropriate provider, depending on provider
- portion of URI. Requires Credentials defined in boto config file (see
+ portion of URI. Requires Credentials defined in boto config file (see
boto/pyami/config.py).
@type storage_uri: StorageUri
@param storage_uri: StorageUri specifying a bucket or a bucket+object
@@ -61,32 +61,34 @@ def connect(self, access_key_id=None, secret_access_key=None, **kwargs):
"""
if not self.connection:
- if self.provider == 's3':
+ if self.scheme == 's3':
from boto.s3.connection import S3Connection
self.connection = S3Connection(access_key_id,
secret_access_key, **kwargs)
- elif self.provider == 'gs':
+ elif self.scheme == 'gs':
from boto.gs.connection import GSConnection
self.connection = GSConnection(access_key_id,
secret_access_key, **kwargs)
- elif self.provider == 'file':
+ elif self.scheme == 'file':
from boto.file.connection import FileConnection
self.connection = FileConnection(self)
else:
- raise InvalidUriError('Unrecognized provider "%s"' %
- self.provider)
+ raise InvalidUriError('Unrecognized scheme "%s"' %
+ self.scheme)
self.connection.debug = self.debug
return self.connection
- def delete_key(self, headers=None):
+ def delete_key(self, validate=True, headers=None, version_id=None,
+ mfa_token=None):
if not self.object_name:
raise InvalidUriError('delete_key on object-less URI (%s)' %
self.uri)
- bucket = self.get_bucket()
- return bucket.delete_key(self.object_name, headers)
+ bucket = self.get_bucket(validate, headers)
+ return bucket.delete_key(self.object_name, headers, version_id,
+ mfa_token)
def get_all_keys(self, headers=None, **params):
- bucket = self.get_bucket(headers)
+ bucket = self.get_bucket(validate, headers)
return bucket.get_all_keys(headers, params)
def get_bucket(self, validate=True, headers=None):
@@ -96,25 +98,40 @@ def get_bucket(self, validate=True, headers=None):
conn = self.connect()
return conn.get_bucket(self.bucket_name, validate, headers)
- def get_key(self):
+ def get_key(self, validate=True, headers=None, version_id=None):
if not self.object_name:
raise InvalidUriError('get_key on object-less URI (%s)' % self.uri)
- bucket = self.get_bucket()
- return bucket.get_key(self.object_name)
+ bucket = self.get_bucket(validate, headers)
+ return bucket.get_key(self.object_name, headers, version_id)
- def new_key(self):
+ def new_key(self, validate=True, headers=None):
if not self.object_name:
raise InvalidUriError('new_key on object-less URI (%s)' % self.uri)
- bucket = self.get_bucket()
+ bucket = self.get_bucket(validate, headers)
return bucket.new_key(self.object_name)
- def get_contents_as_string(self, headers=None, cb=None, num_cb=10,
- torrent=False):
+ def get_contents_as_string(self, validate=True, headers=None, cb=None,
+ num_cb=10, torrent=False, version_id=None):
if not self.object_name:
raise InvalidUriError('get_contents_as_string on object-less URI '
'(%s)' % self.uri)
- return self.get_key().get_contents_as_string(headers, cb, num_cb,
- torrent)
+ key = self.get_key(validate, headers)
+ return key.get_contents_as_string(headers, cb, num_cb, torrent,
+ version_id)
+
+ def acl_class(self):
+ if self.bucket_name is None:
+ raise InvalidUriError('acl_class on bucket-less URI (%s)' %
+ self.uri)
+ conn = self.connect()
+ return conn.provider.acl_class
+
+ def canned_acls(self):
+ if self.bucket_name is None:
+ raise InvalidUriError('canned_acls on bucket-less URI (%s)' %
+ self.uri)
+ conn = self.connect()
+ return conn.provider.canned_acls
class BucketStorageUri(StorageUri):
@@ -123,12 +140,12 @@ class BucketStorageUri(StorageUri):
Callers should instantiate this class by calling boto.storage_uri().
"""
- def __init__(self, provider, bucket_name=None, object_name=None,
+ def __init__(self, scheme, bucket_name=None, object_name=None,
debug=False):
- """Instantiate a BucketStorageUri from provider,bucket,object tuple.
+ """Instantiate a BucketStorageUri from scheme,bucket,object tuple.
- @type provider: string
- @param provider: provider name (gs, s3, etc.)
+ @type scheme: string
+ @param scheme: URI scheme naming the storage provider (gs, s3, etc.)
@type bucket_name: string
@param bucket_name: bucket name
@type object_name: string
@@ -137,19 +154,19 @@ def __init__(self, provider, bucket_name=None, object_name=None,
@param debug: whether to turn on debugging on calls to this class
After instantiation the components are available in the following
- fields: uri, provider, bucket_name, object_name.
+ fields: uri, scheme, bucket_name, object_name.
"""
- self.provider = provider
+ self.scheme = scheme
self.bucket_name = bucket_name
self.object_name = object_name
if self.bucket_name and self.object_name:
- self.uri = ('%s://%s/%s' % (self.provider, self.bucket_name,
- self.object_name))
+ self.uri = ('%s://%s/%s' % (self.scheme, self.bucket_name,
+ self.object_name))
elif self.bucket_name:
- self.uri = ('%s://%s' % (self.provider, self.bucket_name))
+ self.uri = ('%s://%s/' % (self.scheme, self.bucket_name))
else:
- self.uri = ('%s://' % self.provider)
+ self.uri = ('%s://' % self.scheme)
self.debug = debug
def clone_replace_name(self, new_name):
@@ -162,16 +179,46 @@ def clone_replace_name(self, new_name):
if not self.bucket_name:
raise InvalidUriError('clone_replace_name() on bucket-less URI %s' %
self.uri)
- return BucketStorageUri(self.provider, self.bucket_name, new_name,
+ return BucketStorageUri(self.scheme, self.bucket_name, new_name,
self.debug)
- def get_acl(self, headers=None):
+ def get_acl(self, validate=True, headers=None, version_id=None):
if not self.bucket_name:
raise InvalidUriError('get_acl on bucket-less URI (%s)' % self.uri)
- bucket = self.get_bucket()
- # This works for both bucket- and object- level ACL (former passes
+ bucket = self.get_bucket(validate, headers)
+ # This works for both bucket- and object- level ACLs (former passes
# key_name=None):
- return bucket.get_acl(self.object_name, headers)
+ return bucket.get_acl(self.object_name, headers, version_id)
+
+ def add_email_grant(self, permission, email_address, recursive=False,
+ validate=True, headers=None):
+ if not self.bucket_name:
+ raise InvalidUriError('add_email_grant on bucket-less URI (%s)' %
+ self.uri)
+ if not self.object_name:
+ bucket = self.get_bucket(validate, headers)
+ bucket.add_email_grant(permission, email_address, recursive,
+ headers)
+ else:
+ key = self.get_key(validate, headers)
+ key.add_email_grant(permission, email_address)
+
+ def add_user_grant(self, permission, user_id, recursive=False,
+ validate=True, headers=None):
+ if not self.bucket_name:
+ raise InvalidUriError('add_user_grant on bucket-less URI (%s)' % self.uri)
+ if not self.object_name:
+ bucket = self.get_bucket(validate, headers)
+ bucket.add_user_grant(permission, user_id, recursive, headers)
+ else:
+ key = self.get_key(validate, headers)
+ key.add_user_grant(permission, user_id)
+
+ def list_grants(self, headers=None):
+ if not self.bucket_name:
+ raise InvalidUriError('list_grants on bucket-less URI (%s)' % self.uri)
+ bucket = self.get_bucket(headers)
+ return bucket.list_grants(headers)
def names_container(self):
"""Returns True if this URI names a bucket (vs. an object).
@@ -181,7 +228,7 @@ def names_container(self):
def names_singleton(self):
"""Returns True if this URI names an object (vs. a bucket).
"""
- return self.object_name is not None and self.object_name != ''
+ return self.object_name is not None and self.object_name != ''
def is_file_uri(self):
return False
@@ -207,18 +254,25 @@ def get_all_buckets(self, headers=None):
conn = self.connect()
return conn.get_all_buckets(headers)
- def set_acl(self, acl_or_str, key_name='', headers=None):
+ def get_provider(self):
+ conn = self.connect()
+ return conn.provider
+
+ def set_acl(self, acl_or_str, key_name='', validate=True, headers=None,
+ version_id=None):
if not self.bucket_name:
raise InvalidUriError('set_acl on bucket-less URI (%s)' %
self.uri)
- self.get_bucket().set_acl(acl_or_str, key_name, headers)
+ self.get_bucket(validate, headers).set_acl(acl_or_str, key_name, headers,
+ version_id)
- def set_canned_acl(self, acl_str, headers=None):
+ def set_canned_acl(self, acl_str, validate=True, headers=None,
+ version_id=None):
if not self.object_name:
raise InvalidUriError('set_canned_acl on object-less URI (%s)' %
self.uri)
- key = self.get_key()
- key.set_canned_acl(acl_str, headers)
+ key = self.get_key(validate, headers)
+ key.set_canned_acl(acl_str, headers, version_id)
class FileStorageUri(StorageUri):
@@ -234,13 +288,15 @@ def __init__(self, object_name, debug):
@type object_name: string
@param object_name: object name
+ @type debug: boolean
+ @param debug: whether to enable debugging on this StorageUri
After instantiation the components are available in the following
- fields: uri, provider, bucket_name (always blank for this "anonymous"
+ fields: uri, scheme, bucket_name (always blank for this "anonymous"
bucket), object_name.
"""
- self.provider = 'file'
+ self.scheme = 'file'
self.bucket_name = ''
self.object_name = object_name
self.uri = 'file://' + object_name
View
5 boto/tests/test.py
@@ -31,12 +31,13 @@
from boto.tests.test_sqsconnection import SQSConnectionTest
from boto.tests.test_s3connection import S3ConnectionTest
from boto.tests.test_s3versioning import S3VersionTest
+from boto.tests.test_gsconnection import GSConnectionTest
from boto.tests.test_ec2connection import EC2ConnectionTest
from boto.tests.test_sdbconnection import SDBConnectionTest
def usage():
print 'test.py [-t testsuite] [-v verbosity]'
- print ' -t run specific testsuite (s3|s3ver|s3nover|sqs|ec2|sdb|all)'
+ print ' -t run specific testsuite (s3|s3ver|s3nover|gs|sqs|ec2|sdb|all)'
print ' -v verbosity (0|1|2)'
def main():
@@ -72,6 +73,8 @@ def main():
suite.addTest(unittest.makeSuite(S3VersionTest))
elif testsuite == 's3nover':
suite.addTest(unittest.makeSuite(S3ConnectionTest))
+ elif testsuite == 'gs':
+ suite.addTest(unittest.makeSuite(GSConnectionTest))
elif testsuite == 'sqs':
suite.addTest(unittest.makeSuite(SQSConnectionTest))
elif testsuite == 'ec2':
View
6 boto/tests/test_ec2connection.py
@@ -1,6 +1,8 @@
#!/usr/bin/env python
-# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2009, Eucalyptus Systems, Inc.
+# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
@@ -113,7 +115,7 @@ def test_1_basic(self):
except socket.error:
pass
# now kill the instance and delete the security group
- instance.stop()
+ instance.terminate()
# unfortunately, I can't delete the sg within this script
#sg.delete()
View
10 boto/tests/test_s3versioning.py
@@ -1,6 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2010 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2010, Eucalyptus Systems, Inc.
+# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
@@ -112,8 +114,12 @@ def test_1_versions(self):
mfa_sn = raw_input('MFA S/N: ')
mfa_code = raw_input('MFA Code: ')
bucket.configure_versioning(True, mfa_delete=True, mfa_token=(mfa_sn, mfa_code))
- time.sleep(5)
- d = bucket.get_versioning_status()
+ i = 0
+ for i in range(1,8):
+ time.sleep(2**i)
+ d = bucket.get_versioning_status()
+ if d['Versioning'] == 'Enabled' and d['MfaDelete'] == 'Enabled':
+ break
assert d['Versioning'] == 'Enabled'
assert d['MfaDelete'] == 'Enabled'
View
10 boto/tests/test_sdbconnection.py
@@ -1,6 +1,8 @@
#!/usr/bin/env python
-# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2010, Eucalyptus Systems, Inc.
+# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
@@ -67,12 +69,14 @@ def test_1_basic(self):
assert item['name2'] == attrs_1['name2']
# try a search or two
- rs = domain.query("['name1'='%s']" % same_value)