txawsteam team mailing list archive
-
txawsteam team
-
Mailing list archive
-
Message #00056
[Merge] lp:~oubiwann/txaws/416109-arbitrary-endpoints into lp:txaws
Duncan McGreggor has proposed merging lp:~oubiwann/txaws/416109-arbitrary-endpoints into lp:txaws.
Requested reviews:
txAWS Team (txawsteam)
This branch adds support for a service object that manages host endpoints as well as authorization keys (thus obviating the need for the AWSCredential object).
--
https://code.launchpad.net/~oubiwann/txaws/416109-arbitrary-endpoints/+merge/10477
Your team txAWS Team is subscribed to branch lp:txaws.
=== modified file 'txaws/client/gui/gtk.py'
--- txaws/client/gui/gtk.py 2009-08-18 22:53:53 +0000
+++ txaws/client/gui/gtk.py 2009-08-20 19:14:32 +0000
@@ -8,7 +8,7 @@
import gobject
import gtk
-from txaws.credentials import AWSCredentials
+from txaws.ec2.service import EC2Service
__all__ = ['main']
@@ -27,10 +27,10 @@
# Nested import because otherwise we get 'reactor already installed'.
self.password_dialog = None
try:
- creds = AWSCredentials()
+ service = AWSService()
except ValueError:
- creds = self.from_gnomekeyring()
- self.create_client(creds)
+ service = self.from_gnomekeyring()
+ self.create_client(service)
menu = '''
<ui>
<menubar name="Menubar">
@@ -54,10 +54,10 @@
'/Menubar/Menu/Stop instances').props.parent
self.connect('popup-menu', self.on_popup_menu)
- def create_client(self, creds):
+ def create_client(self, service):
from txaws.ec2.client import EC2Client
- if creds is not None:
- self.client = EC2Client(creds=creds)
+ if service is not None:
+ self.client = EC2Client(service=service)
self.on_activate(None)
else:
# waiting on user entered credentials.
@@ -65,7 +65,7 @@
def from_gnomekeyring(self):
# Try for gtk gui specific credentials.
- creds = None
+ service = None
try:
items = gnomekeyring.find_items_sync(
gnomekeyring.ITEM_GENERIC_SECRET,
@@ -78,7 +78,7 @@
return None
else:
key_id, secret_key = items[0].secret.split(':')
- return AWSCredentials(access_key=key_id, secret_key=secret_key)
+ return EC2Service(access_key=key_id, secret_key=secret_key)
def show_a_password_dialog(self):
self.password_dialog = gtk.Dialog(
@@ -133,8 +133,8 @@
content = self.password_dialog.get_content_area()
key_id = content.get_children()[0].get_children()[1].get_text()
secret_key = content.get_children()[1].get_children()[1].get_text()
- creds = AWSCredentials(access_key=key_id, secret_key=secret_key)
- self.create_client(creds)
+ service = EC2Service(access_key=key_id, secret_key=secret_key)
+ self.create_client(service)
gnomekeyring.item_create_sync(
None,
gnomekeyring.ITEM_GENERIC_SECRET,
=== removed file 'txaws/credentials.py'
--- txaws/credentials.py 2009-08-17 11:18:56 +0000
+++ txaws/credentials.py 1970-01-01 00:00:00 +0000
@@ -1,37 +0,0 @@
-# Copyright (C) 2009 Robert Collins <robertc@xxxxxxxxxxxxxxxxx>
-# Licenced under the txaws licence available at /LICENSE in the txaws source.
-
-"""Credentials for accessing AWS services."""
-
-import os
-
-from txaws.util import *
-
-
-__all__ = ['AWSCredentials']
-
-
-class AWSCredentials(object):
-
- def __init__(self, access_key=None, secret_key=None):
- """Create an AWSCredentials object.
-
- :param access_key: The access key to use. If None the environment
- variable AWS_ACCESS_KEY_ID is consulted.
- :param secret_key: The secret key to use. If None the environment
- variable AWS_SECRET_ACCESS_KEY is consulted.
- """
- self.secret_key = secret_key
- if self.secret_key is None:
- self.secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY')
- if self.secret_key is None:
- raise ValueError('Could not find AWS_SECRET_ACCESS_KEY')
- self.access_key = access_key
- if self.access_key is None:
- self.access_key = os.environ.get('AWS_ACCESS_KEY_ID')
- if self.access_key is None:
- raise ValueError('Could not find AWS_ACCESS_KEY_ID')
-
- def sign(self, bytes):
- """Sign some bytes."""
- return hmac_sha1(self.secret_key, bytes)
=== modified file 'txaws/ec2/client.py'
--- txaws/ec2/client.py 2009-08-18 21:56:36 +0000
+++ txaws/ec2/client.py 2009-08-20 16:47:54 +0000
@@ -8,7 +8,7 @@
from twisted.web.client import getPage
-from txaws import credentials
+from txaws.ec2.service import EC2Service
from txaws.util import iso8601time, XML
@@ -46,16 +46,15 @@
name_space = '{http://ec2.amazonaws.com/doc/2008-12-01/}'
- def __init__(self, creds=None, query_factory=None):
+ def __init__(self, service=None, query_factory=None):
"""Create an EC2Client.
- @param creds: Explicit credentials to use. If None, credentials are
- inferred as per txaws.credentials.AWSCredentials.
+ @param service: Explicit service to use.
"""
- if creds is None:
- self.creds = credentials.AWSCredentials()
+ if service is None:
+ self.service = EC2Service()
else:
- self.creds = creds
+ self.service = service
if query_factory is None:
self.query_factory = Query
else:
@@ -63,7 +62,7 @@
def describe_instances(self):
"""Describe current instances."""
- q = self.query_factory('DescribeInstances', self.creds)
+ q = self.query_factory('DescribeInstances', self.service)
d = q.submit()
return d.addCallback(self._parse_instances)
@@ -119,7 +118,7 @@
instanceset = {}
for pos, instance_id in enumerate(instance_ids):
instanceset["InstanceId.%d" % (pos+1)] = instance_id
- q = self.query_factory('TerminateInstances', self.creds, instanceset)
+ q = self.query_factory('TerminateInstances', self.service, instanceset)
d = q.submit()
return d.addCallback(self._parse_terminate_instances)
@@ -142,7 +141,7 @@
class Query(object):
"""A query that may be submitted to EC2."""
- def __init__(self, action, creds, other_params=None, time_tuple=None):
+ def __init__(self, action, service, other_params=None, time_tuple=None):
"""Create a Query to submit to EC2."""
# Require params (2008-12-01 API):
# Version, SignatureVersion, SignatureMethod, Action, AWSAccessKeyId,
@@ -151,15 +150,12 @@
'SignatureVersion': '2',
'SignatureMethod': 'HmacSHA1',
'Action': action,
- 'AWSAccessKeyId': creds.access_key,
+ 'AWSAccessKeyId': service.access_key,
'Timestamp': iso8601time(time_tuple),
}
if other_params:
self.params.update(other_params)
- self.method = 'GET'
- self.host = 'ec2.amazonaws.com'
- self.uri = '/'
- self.creds = creds
+ self.service = service
def canonical_query_params(self):
"""Return the canonical query params (used in signing)."""
@@ -178,18 +174,19 @@
def signing_text(self):
"""Return the text to be signed when signing the query."""
- result = "%s\n%s\n%s\n%s" % (self.method, self.host, self.uri,
- self.canonical_query_params())
+ result = "%s\n%s\n%s\n%s" % (self.service.method, self.service.host,
+ self.service.endpoint,
+ self.canonical_query_params())
return result
def sign(self):
- """Sign this query using its built in credentials.
+ """Sign this query using its built in service.
This prepares it to be sent, and should be done as the last step before
submitting the query. Signing is done automatically - this is a public
method to facilitate testing.
"""
- self.params['Signature'] = self.creds.sign(self.signing_text())
+ self.params['Signature'] = self.service.sign(self.signing_text())
def sorted_params(self):
"""Return the query params sorted appropriately for signing."""
@@ -198,9 +195,8 @@
def submit(self):
"""Submit this query.
- :return: A deferred from twisted.web.client.getPage
+ @return: A deferred from twisted.web.client.getPage
"""
self.sign()
- url = 'http://%s%s?%s' % (self.host, self.uri,
- self.canonical_query_params())
- return getPage(url, method=self.method)
+ url = "%s?%s" % (self.service.get_url(), self.canonical_query_params())
+ return getPage(url, method=self.service.method)
=== modified file 'txaws/ec2/tests/test_client.py'
--- txaws/ec2/tests/test_client.py 2009-08-18 21:56:36 +0000
+++ txaws/ec2/tests/test_client.py 2009-08-20 16:47:54 +0000
@@ -5,8 +5,8 @@
from twisted.internet.defer import succeed
-from txaws.credentials import AWSCredentials
from txaws.ec2 import client
+from txaws.ec2.service import EC2Service, US_EC2_HOST
from txaws.tests import TXAWSTestCase
@@ -91,21 +91,21 @@
self.assertEquals(reservation.groups, ["one", "two"])
-class TestEC2Client(TXAWSTestCase):
+class EC2ClientTestCase(TXAWSTestCase):
def test_init_no_creds(self):
os.environ['AWS_SECRET_ACCESS_KEY'] = 'foo'
os.environ['AWS_ACCESS_KEY_ID'] = 'bar'
ec2 = client.EC2Client()
- self.assertNotEqual(None, ec2.creds)
+ self.assertNotEqual(None, ec2.service)
def test_init_no_creds_non_available_errors(self):
self.assertRaises(ValueError, client.EC2Client)
- def test_init_explicit_creds(self):
- creds = 'foo'
- ec2 = client.EC2Client(creds=creds)
- self.assertEqual(creds, ec2.creds)
+ def test_init_explicit_service(self):
+ service = EC2Service("foo", "bar")
+ ec2 = client.EC2Client(service=service)
+ self.assertEqual(service, ec2.service)
def check_parsed_instances(self, results):
instance = results[0]
@@ -118,33 +118,38 @@
self.assertEquals(group, "default")
def test_parse_reservation(self):
- ec2 = client.EC2Client(creds='foo')
+ service = EC2Service("foo", "bar")
+ ec2 = client.EC2Client(service=service)
results = ec2._parse_instances(sample_describe_instances_result)
self.check_parsed_instances(results)
def test_describe_instances(self):
class StubQuery(object):
- def __init__(stub, action, creds):
+ def __init__(stub, action, service):
self.assertEqual(action, 'DescribeInstances')
- self.assertEqual('foo', creds)
+ self.assertEqual(service.access_key, "foo")
+ self.assertEqual(service.secret_key, "bar")
def submit(self):
return succeed(sample_describe_instances_result)
- ec2 = client.EC2Client(creds='foo', query_factory=StubQuery)
+ service = EC2Service("foo", "bar")
+ ec2 = client.EC2Client(service, query_factory=StubQuery)
d = ec2.describe_instances()
d.addCallback(self.check_parsed_instances)
return d
def test_terminate_instances(self):
class StubQuery(object):
- def __init__(stub, action, creds, other_params):
+ def __init__(stub, action, service, other_params):
self.assertEqual(action, 'TerminateInstances')
- self.assertEqual('foo', creds)
+ self.assertEqual(service.access_key, "foo")
+ self.assertEqual(service.secret_key, "bar")
self.assertEqual(
{'InstanceId.1': 'i-1234', 'InstanceId.2': 'i-5678'},
other_params)
def submit(self):
return succeed(sample_terminate_instances_result)
- ec2 = client.EC2Client(creds='foo', query_factory=StubQuery)
+ service = EC2Service("foo", "bar")
+ ec2 = client.EC2Client(service=service, query_factory=StubQuery)
d = ec2.terminate_instances('i-1234', 'i-5678')
def check_transition(changes):
self.assertEqual([('i-1234', 'running', 'shutting-down'),
@@ -152,14 +157,14 @@
return d
-class TestQuery(TXAWSTestCase):
+class QueryTestCase(TXAWSTestCase):
def setUp(self):
TXAWSTestCase.setUp(self)
- self.creds = AWSCredentials('foo', 'bar')
+ self.service = EC2Service('foo', 'bar')
def test_init_minimum(self):
- query = client.Query('DescribeInstances', self.creds)
+ query = client.Query('DescribeInstances', self.service)
self.assertTrue('Timestamp' in query.params)
del query.params['Timestamp']
self.assertEqual(
@@ -173,11 +178,11 @@
def test_init_requires_action(self):
self.assertRaises(TypeError, client.Query)
- def test_init_requires_creds(self):
+ def test_init_requires_service_with_creds(self):
self.assertRaises(TypeError, client.Query, None)
def test_init_other_args_are_params(self):
- query = client.Query('DescribeInstances', self.creds,
+ query = client.Query('DescribeInstances', self.service,
{'InstanceId.0': '12345'},
time_tuple=(2007,11,12,13,14,15,0,0,0))
self.assertEqual(
@@ -191,7 +196,7 @@
query.params)
def test_sorted_params(self):
- query = client.Query('DescribeInstances', self.creds,
+ query = client.Query('DescribeInstances', self.service,
{'fun': 'games'},
time_tuple=(2007,11,12,13,14,15,0,0,0))
self.assertEqual([
@@ -207,16 +212,16 @@
def test_encode_unreserved(self):
all_unreserved = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
'abcdefghijklmnopqrstuvwxyz0123456789-_.~')
- query = client.Query('DescribeInstances', self.creds)
+ query = client.Query('DescribeInstances', self.service)
self.assertEqual(all_unreserved, query.encode(all_unreserved))
def test_encode_space(self):
"""This may be just 'url encode', but the AWS manual isn't clear."""
- query = client.Query('DescribeInstances', self.creds)
+ query = client.Query('DescribeInstances', self.service)
self.assertEqual('a%20space', query.encode('a space'))
def test_canonical_query(self):
- query = client.Query('DescribeInstances', self.creds,
+ query = client.Query('DescribeInstances', self.service,
{'fu n': 'g/ames', 'argwithnovalue':'',
'InstanceId.1': 'i-1234'},
time_tuple=(2007,11,12,13,14,15,0,0,0))
@@ -228,17 +233,17 @@
self.assertEqual(expected_query, query.canonical_query_params())
def test_signing_text(self):
- query = client.Query('DescribeInstances', self.creds,
+ query = client.Query('DescribeInstances', self.service,
time_tuple=(2007,11,12,13,14,15,0,0,0))
- signing_text = ('GET\nec2.amazonaws.com\n/\n'
+ signing_text = ('GET\n%s\n/\n' % US_EC2_HOST +
'AWSAccessKeyId=foo&Action=DescribeInstances&'
'SignatureMethod=HmacSHA1&SignatureVersion=2&'
'Timestamp=2007-11-12T13%3A14%3A15Z&Version=2008-12-01')
self.assertEqual(signing_text, query.signing_text())
def test_sign(self):
- query = client.Query('DescribeInstances', self.creds,
+ query = client.Query('DescribeInstances', self.service,
time_tuple=(2007,11,12,13,14,15,0,0,0))
query.sign()
- self.assertEqual('4hEtLuZo9i6kuG3TOXvRQNOrE/U=',
+ self.assertEqual('JuCpwFA2H4OVF3Ql/lAQs+V6iMc=',
query.params['Signature'])
=== added file 'txaws/service.py'
--- txaws/service.py 1970-01-01 00:00:00 +0000
+++ txaws/service.py 2009-08-20 19:09:56 +0000
@@ -0,0 +1,70 @@
+# Copyright (C) 2009 Duncan McGreggor <duncan@xxxxxxxxxxxxx>
+# Copyright (C) 2009 Robert Collins <robertc@xxxxxxxxxxxxxxxxx>
+# Licenced under the txaws licence available at /LICENSE in the txaws source.
+
+import os
+
+from twisted.web.client import _parse
+
+from txaws.util import hmac_sha1
+
+DEFAULT_PORT = 80
+ENV_ACCESS_KEY = "AWS_ACCESS_KEY_ID"
+ENV_SECRET_KEY = "AWS_SECRET_ACCESS_KEY"
+
+class AWSService(object):
+ """
+ @param access_key: The access key to use. If None the environment
+ variable AWS_ACCESS_KEY_ID is consulted.
+ @param secret_key: The secret key to use. If None the environment
+ variable AWS_SECRET_ACCESS_KEY is consulted.
+ @param uri: The URL for the service.
+ @param method: The HTTP method used when accessing a service.
+ """
+ default_host = ""
+ default_schema = "https"
+
+ def __init__(self, access_key="", secret_key="", uri="", method="GET"):
+ self.access_key = access_key
+ self.secret_key = secret_key
+ self.schema = ""
+ self.host = ""
+ self.port = DEFAULT_PORT
+ self.endpoint = "/"
+ self.method = method
+ self._process_creds()
+ self._parse_uri(uri)
+ if not self.host:
+ self.host = self.default_host
+ if not self.schema:
+ self.schema = self.default_schema
+
+ def _process_creds(self):
+ # perform checks for access key
+ if not self.access_key:
+ self.access_key = os.environ.get(ENV_ACCESS_KEY)
+ if not self.access_key:
+ raise ValueError("Could not find %s" % ENV_ACCESS_KEY)
+ # perform checks for secret key
+ if not self.secret_key:
+ self.secret_key = os.environ.get(ENV_SECRET_KEY)
+ if not self.secret_key:
+ raise ValueError("Could not find %s" % ENV_SECRET_KEY)
+
+ def _parse_uri(self, uri):
+ scheme, host, port, endpoint = _parse(uri, defaultPort=DEFAULT_PORT)
+ self.schema = scheme
+ self.host = host
+ self.port = port
+ self.endpoint = endpoint
+
+ def get_uri(self):
+ """Get a URL representation of the service."""
+ uri = "%s://%s" % (self.schema, self.host)
+ if self.port and self.port != DEFAULT_PORT:
+ uri = "%s:%s" % (uri, self.port)
+ return uri + self.endpoint
+
+ def sign(self, bytes):
+ """Sign some bytes."""
+ return hmac_sha1(self.secret_key, bytes)
=== modified file 'txaws/storage/client.py'
--- txaws/storage/client.py 2009-08-17 11:18:56 +0000
+++ txaws/storage/client.py 2009-08-20 19:09:56 +0000
@@ -10,178 +10,170 @@
from hashlib import md5
from base64 import b64encode
-
from epsilon.extime import Time
from twisted.web.client import getPage
from twisted.web.http import datetimeToString
-from txaws.credentials import AWSCredentials
-from txaws.util import XML
-
-
-def calculateMD5(data):
- digest = md5(data).digest()
- return b64encode(digest)
+from txaws.util import XML, calculate_md5
+from txaws.service import AWSService
+
+
+name_space = '{http://s3.amazonaws.com/doc/2006-03-01/}'
class S3Request(object):
- def __init__(self, verb, bucket=None, objectName=None, data='',
- contentType=None, metadata={}, rootURI='https://s3.amazonaws.com',
- creds=None):
+
+ def __init__(self, verb, bucket=None, object_name=None, data='',
+ content_type=None, metadata={}, service=None):
self.verb = verb
self.bucket = bucket
- self.objectName = objectName
+ self.object_name = object_name
self.data = data
- self.contentType = contentType
+ self.content_type = content_type
self.metadata = metadata
- self.rootURI = rootURI
- self.creds = creds
+ self.service = service
+ self.service.endpoint = self.get_path()
self.date = datetimeToString()
- def getURIPath(self):
+ def get_path(self):
path = '/'
if self.bucket is not None:
path += self.bucket
- if self.objectName is not None:
- path += '/' + self.objectName
+ if self.object_name is not None:
+ path += '/' + self.object_name
return path
- def getURI(self):
- return self.rootURI + self.getURIPath()
+ def get_uri(self):
+ return self.service.get_uri()
- def getHeaders(self):
+ def get_headers(self):
headers = {'Content-Length': len(self.data),
- 'Content-MD5': calculateMD5(self.data),
+ 'Content-MD5': calculate_md5(self.data),
'Date': self.date}
for key, value in self.metadata.iteritems():
headers['x-amz-meta-' + key] = value
- if self.contentType is not None:
- headers['Content-Type'] = self.contentType
+ if self.content_type is not None:
+ headers['Content-Type'] = self.content_type
- if self.creds is not None:
- signature = self.getSignature(headers)
+ if self.service is not None:
+ signature = self.get_signature(headers)
headers['Authorization'] = 'AWS %s:%s' % (
- self.creds.access_key, signature)
-
+ self.service.access_key, signature)
return headers
- def getCanonicalizedResource(self):
- return self.getURIPath()
+ def get_canonicalized_resource(self):
+ return self.get_path()
- def getCanonicalizedAmzHeaders(self, headers):
+ def get_canonicalized_amz_headers(self, headers):
result = ''
headers = [(name.lower(), value) for name, value in headers.iteritems()
if name.lower().startswith('x-amz-')]
headers.sort()
return ''.join('%s:%s\n' % (name, value) for name, value in headers)
- def getSignature(self, headers):
- text = self.verb + '\n'
- text += headers.get('Content-MD5', '') + '\n'
- text += headers.get('Content-Type', '') + '\n'
- text += headers.get('Date', '') + '\n'
- text += self.getCanonicalizedAmzHeaders(headers)
- text += self.getCanonicalizedResource()
- return self.creds.sign(text)
+ def get_signature(self, headers):
+ text = (self.verb + '\n' +
+ headers.get('Content-MD5', '') + '\n' +
+ headers.get('Content-Type', '') + '\n' +
+ headers.get('Date', '') + '\n' +
+ self.get_canonicalized_amz_headers(headers) +
+ self.get_canonicalized_resource())
+ return self.service.sign(text)
def submit(self):
- return self.getPage(
- url=self.getURI(), method=self.verb, postdata=self.data,
- headers=self.getHeaders())
+ return self.get_page(url=self.get_uri(), method=self.verb,
+ postdata=self.data, headers=self.get_headers())
- def getPage(self, *a, **kw):
+ def get_page(self, *a, **kw):
return getPage(*a, **kw)
-NS = '{http://s3.amazonaws.com/doc/2006-03-01/}'
-
-
class S3(object):
- rootURI = 'https://s3.amazonaws.com/'
- requestFactory = S3Request
-
- def __init__(self, creds):
- self.creds = creds
-
- def makeRequest(self, *a, **kw):
+
+ request_factory = S3Request
+
+ def __init__(self, service):
+ self.service = service
+
+ def make_request(self, *a, **kw):
"""
Create a request with the arguments passed in.
- This uses the requestFactory attribute, adding the credentials to the
+ This uses the request_factory attribute, adding the service to the
arguments passed in.
"""
- return self.requestFactory(creds=self.creds, *a, **kw)
+ return self.request_factory(service=self.service, *a, **kw)
- def _parseBucketList(self, response):
+ def _parse_bucket_list(self, response):
"""
Parse XML bucket list response.
"""
root = XML(response)
- for bucket in root.find(NS + 'Buckets'):
- timeText = bucket.findtext(NS + 'CreationDate')
+ for bucket in root.find(name_space + 'Buckets'):
+ timeText = bucket.findtext(name_space + 'CreationDate')
yield {
- 'name': bucket.findtext(NS + 'Name'),
+ 'name': bucket.findtext(name_space + 'Name'),
'created': Time.fromISO8601TimeAndDate(timeText),
}
- def listBuckets(self):
+ def list_buckets(self):
"""
List all buckets.
Returns a list of all the buckets owned by the authenticated sender of
the request.
"""
- d = self.makeRequest('GET').submit()
- d.addCallback(self._parseBucketList)
- return d
+ deferred = self.make_request('GET').submit()
+ deferred.addCallback(self._parse_bucket_list)
+ return deferred
- def createBucket(self, bucket):
+ def create_bucket(self, bucket):
"""
Create a new bucket.
"""
- return self.makeRequest('PUT', bucket).submit()
+ return self.make_request('PUT', bucket).submit()
- def deleteBucket(self, bucket):
+ def delete_bucket(self, bucket):
"""
Delete a bucket.
The bucket must be empty before it can be deleted.
"""
- return self.makeRequest('DELETE', bucket).submit()
+ return self.make_request('DELETE', bucket).submit()
- def putObject(self, bucket, objectName, data, contentType=None,
- metadata={}):
+ def put_object(self, bucket, object_name, data, content_type=None,
+ metadata={}):
"""
Put an object in a bucket.
Any existing object of the same name will be replaced.
"""
- return self.makeRequest(
- 'PUT', bucket, objectName, data, contentType, metadata).submit()
+ return self.make_request('PUT', bucket, object_name, data,
+ content_type, metadata).submit()
- def getObject(self, bucket, objectName):
+ def get_object(self, bucket, object_name):
"""
Get an object from a bucket.
"""
- return self.makeRequest('GET', bucket, objectName).submit()
+ return self.make_request('GET', bucket, object_name).submit()
- def headObject(self, bucket, objectName):
+ def head_object(self, bucket, object_name):
"""
Retrieve object metadata only.
- This is like getObject, but the object's content is not retrieved.
+ This is like get_object, but the object's content is not retrieved.
Currently the metadata is not returned to the caller either, so this
method is mostly useless, and only provided for completeness.
"""
- return self.makeRequest('HEAD', bucket, objectName).submit()
+ return self.make_request('HEAD', bucket, object_name).submit()
- def deleteObject(self, bucket, objectName):
+ def delete_object(self, bucket, object_name):
"""
Delete an object from a bucket.
Once deleted, there is no method to restore or undelete an object.
"""
- return self.makeRequest('DELETE', bucket, objectName).submit()
+ return self.make_request('DELETE', bucket, object_name).submit()
=== added file 'txaws/storage/service.py'
--- txaws/storage/service.py 1970-01-01 00:00:00 +0000
+++ txaws/storage/service.py 2009-08-20 19:09:56 +0000
@@ -0,0 +1,19 @@
+# Copyright (C) 2009 Duncan McGreggor <duncan@xxxxxxxxxxxxx>
+# Licenced under the txaws licence available at /LICENSE in the txaws source.
+
+from txaws.service import AWSService
+
+
+S3_HOST = "s3.amazonaws.com"
+
+
+class S3Service(AWSService):
+ """
+ This service uses the standard S3 host defined with S3_HOST by default. To
+ override this behaviour, simply pass the desired value in the "host"
+ keyword parameter.
+
+ For more details, see txaws.service.AWSService.
+ """
+ default_host = S3_HOST
+ default_schema = "https"
=== modified file 'txaws/storage/test/test_client.py'
--- txaws/storage/test/test_client.py 2009-08-15 03:28:45 +0000
+++ txaws/storage/test/test_client.py 2009-08-20 19:09:56 +0000
@@ -4,20 +4,23 @@
from twisted.internet.defer import succeed
-from txaws.credentials import AWSCredentials
+from txaws.util import calculate_md5
from txaws.tests import TXAWSTestCase
-from txaws.storage.client import S3, S3Request, calculateMD5
+from txaws.storage.service import S3Service
+from txaws.storage.client import S3, S3Request
+
class StubbedS3Request(S3Request):
- def getPage(self, url, method, postdata, headers):
+
+ def get_page(self, url, method, postdata, headers):
self.getPageArgs = (url, method, postdata, headers)
return succeed('')
-class RequestTests(TXAWSTestCase):
- creds = AWSCredentials(access_key='0PN5J17HBGZHT7JJ3X82',
- secret_key='uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o')
+class RequestTestCase(TXAWSTestCase):
+
+ service = S3Service(access_key='fookeyid', secret_key='barsecretkey')
def test_objectRequest(self):
"""
@@ -26,20 +29,23 @@
DATA = 'objectData'
DIGEST = 'zhdB6gwvocWv/ourYUWMxA=='
- request = S3Request(
- 'PUT', 'somebucket', 'object/name/here', DATA,
- contentType='text/plain', metadata={'foo': 'bar'})
+ request = S3Request('PUT', 'somebucket', 'object/name/here', DATA,
+ content_type='text/plain', metadata={'foo': 'bar'},
+ service=self.service)
+ request.get_signature = lambda headers: "TESTINGSIG="
self.assertEqual(request.verb, 'PUT')
self.assertEqual(
- request.getURI(),
+ request.get_uri(),
'https://s3.amazonaws.com/somebucket/object/name/here')
- headers = request.getHeaders()
+ headers = request.get_headers()
self.assertNotEqual(headers.pop('Date'), '')
- self.assertEqual(headers,
- {'Content-Type': 'text/plain',
- 'Content-Length': len(DATA),
- 'Content-MD5': DIGEST,
- 'x-amz-meta-foo': 'bar'})
+ self.assertEqual(
+ headers, {
+ 'Authorization': 'AWS fookeyid:TESTINGSIG=',
+ 'Content-Type': 'text/plain',
+ 'Content-Length': len(DATA),
+ 'Content-MD5': DIGEST,
+ 'x-amz-meta-foo': 'bar'})
self.assertEqual(request.data, 'objectData')
def test_bucketRequest(self):
@@ -48,42 +54,46 @@
"""
DIGEST = '1B2M2Y8AsgTpgAmY7PhCfg=='
- request = S3Request('GET', 'somebucket')
+ request = S3Request('GET', 'somebucket', service=self.service)
+ request.get_signature = lambda headers: "TESTINGSIG="
self.assertEqual(request.verb, 'GET')
self.assertEqual(
- request.getURI(), 'https://s3.amazonaws.com/somebucket')
- headers = request.getHeaders()
+ request.get_uri(), 'https://s3.amazonaws.com/somebucket')
+ headers = request.get_headers()
self.assertNotEqual(headers.pop('Date'), '')
- self.assertEqual(headers,
- {'Content-Length': 0,
- 'Content-MD5': DIGEST})
+ self.assertEqual(
+ headers, {
+ 'Authorization': 'AWS fookeyid:TESTINGSIG=',
+ 'Content-Length': 0,
+ 'Content-MD5': DIGEST})
self.assertEqual(request.data, '')
def test_submit(self):
"""
Submitting the request should invoke getPage correctly.
"""
- request = StubbedS3Request('GET', 'somebucket')
+ request = StubbedS3Request('GET', 'somebucket', service=self.service)
def _postCheck(result):
self.assertEqual(result, '')
url, method, postdata, headers = request.getPageArgs
- self.assertEqual(url, request.getURI())
+ self.assertEqual(url, request.get_uri())
self.assertEqual(method, request.verb)
self.assertEqual(postdata, request.data)
- self.assertEqual(headers, request.getHeaders())
+ self.assertEqual(headers, request.get_headers())
return request.submit().addCallback(_postCheck)
def test_authenticationTestCases(self):
- req = S3Request('GET', creds=self.creds)
- req.date = 'Wed, 28 Mar 2007 01:29:59 +0000'
+ request = S3Request('GET', service=self.service)
+ request.get_signature = lambda headers: "TESTINGSIG="
+ request.date = 'Wed, 28 Mar 2007 01:29:59 +0000'
- headers = req.getHeaders()
+ headers = request.get_headers()
self.assertEqual(
- headers['Authorization'],
- 'AWS 0PN5J17HBGZHT7JJ3X82:jF7L3z/FTV47vagZzhKupJ9oNig=')
+ headers['Authorization'],
+ 'AWS fookeyid:TESTINGSIG=')
class InertRequest(S3Request):
@@ -110,13 +120,13 @@
"""
Testable version of S3.
- This subclass stubs requestFactory to use InertRequest, making it easy to
+ This subclass stubs request_factory to use InertRequest, making it easy to
assert things about the requests that are created in response to various
operations.
"""
response = None
- def requestFactory(self, *a, **kw):
+ def request_factory(self, *a, **kw):
req = InertRequest(response=self.response, *a, **kw)
self._lastRequest = req
return req
@@ -148,34 +158,34 @@
def setUp(self):
TXAWSTestCase.setUp(self)
- self.creds = AWSCredentials(
+ self.service = S3Service(
access_key='accessKey', secret_key='secretKey')
- self.s3 = TestableS3(creds=self.creds)
+ self.s3 = TestableS3(service=self.service)
- def test_makeRequest(self):
+ def test_make_request(self):
"""
- Test that makeRequest passes in the service credentials.
+ Test that make_request passes in the service object.
"""
marker = object()
def _cb(*a, **kw):
- self.assertEqual(kw['creds'], self.creds)
+ self.assertEqual(kw['service'], self.service)
return marker
- self.s3.requestFactory = _cb
- self.assertIdentical(self.s3.makeRequest('GET'), marker)
+ self.s3.request_factory = _cb
+ self.assertIdentical(self.s3.make_request('GET'), marker)
- def test_listBuckets(self):
+ def test_list_buckets(self):
self.s3.response = samples['ListAllMyBucketsResult']
- d = self.s3.listBuckets()
+ d = self.s3.list_buckets()
req = self.s3._lastRequest
self.assertTrue(req.submitted)
self.assertEqual(req.verb, 'GET')
self.assertEqual(req.bucket, None)
- self.assertEqual(req.objectName, None)
+ self.assertEqual(req.object_name, None)
- def _checkResult(buckets):
+ def _check_result(buckets):
self.assertEqual(
list(buckets),
[{'name': u'quotes',
@@ -184,61 +194,61 @@
{'name': u'samples',
'created': Time.fromDatetime(
datetime(2006, 2, 3, 16, 41, 58))}])
- return d.addCallback(_checkResult)
+ return d.addCallback(_check_result)
- def test_createBucket(self):
- self.s3.createBucket('foo')
+ def test_create_bucket(self):
+ self.s3.create_bucket('foo')
req = self.s3._lastRequest
self.assertTrue(req.submitted)
self.assertEqual(req.verb, 'PUT')
self.assertEqual(req.bucket, 'foo')
- self.assertEqual(req.objectName, None)
+ self.assertEqual(req.object_name, None)
- def test_deleteBucket(self):
- self.s3.deleteBucket('foo')
+ def test_delete_bucket(self):
+ self.s3.delete_bucket('foo')
req = self.s3._lastRequest
self.assertTrue(req.submitted)
self.assertEqual(req.verb, 'DELETE')
self.assertEqual(req.bucket, 'foo')
- self.assertEqual(req.objectName, None)
+ self.assertEqual(req.object_name, None)
- def test_putObject(self):
- self.s3.putObject(
+ def test_put_object(self):
+ self.s3.put_object(
'foobucket', 'foo', 'data', 'text/plain', {'foo': 'bar'})
req = self.s3._lastRequest
self.assertTrue(req.submitted)
self.assertEqual(req.verb, 'PUT')
self.assertEqual(req.bucket, 'foobucket')
- self.assertEqual(req.objectName, 'foo')
+ self.assertEqual(req.object_name, 'foo')
self.assertEqual(req.data, 'data')
- self.assertEqual(req.contentType, 'text/plain')
+ self.assertEqual(req.content_type, 'text/plain')
self.assertEqual(req.metadata, {'foo': 'bar'})
- def test_getObject(self):
- self.s3.getObject('foobucket', 'foo')
+ def test_get_object(self):
+ self.s3.get_object('foobucket', 'foo')
req = self.s3._lastRequest
self.assertTrue(req.submitted)
self.assertEqual(req.verb, 'GET')
self.assertEqual(req.bucket, 'foobucket')
- self.assertEqual(req.objectName, 'foo')
+ self.assertEqual(req.object_name, 'foo')
- def test_headObject(self):
- self.s3.headObject('foobucket', 'foo')
+ def test_head_object(self):
+ self.s3.head_object('foobucket', 'foo')
req = self.s3._lastRequest
self.assertTrue(req.submitted)
self.assertEqual(req.verb, 'HEAD')
self.assertEqual(req.bucket, 'foobucket')
- self.assertEqual(req.objectName, 'foo')
+ self.assertEqual(req.object_name, 'foo')
- def test_deleteObject(self):
- self.s3.deleteObject('foobucket', 'foo')
+ def test_delete_object(self):
+ self.s3.delete_object('foobucket', 'foo')
req = self.s3._lastRequest
self.assertTrue(req.submitted)
self.assertEqual(req.verb, 'DELETE')
self.assertEqual(req.bucket, 'foobucket')
- self.assertEqual(req.objectName, 'foo')
+ self.assertEqual(req.object_name, 'foo')
class MiscellaneousTests(TXAWSTestCase):
def test_contentMD5(self):
- self.assertEqual(calculateMD5('somedata'), 'rvr3UC1SmUw7AZV2NqPN0g==')
+ self.assertEqual(calculate_md5('somedata'), 'rvr3UC1SmUw7AZV2NqPN0g==')
=== removed file 'txaws/tests/test_credentials.py'
--- txaws/tests/test_credentials.py 2009-08-17 11:18:56 +0000
+++ txaws/tests/test_credentials.py 1970-01-01 00:00:00 +0000
@@ -1,41 +0,0 @@
-# Copyright (C) 2009 Robert Collins <robertc@xxxxxxxxxxxxxxxxx>
-# Licenced under the txaws licence available at /LICENSE in the txaws source.
-
-import os
-
-from twisted.trial.unittest import TestCase
-from txaws.tests import TXAWSTestCase
-
-from txaws.credentials import AWSCredentials
-
-
-class TestCredentials(TXAWSTestCase):
-
- def test_no_access_errors(self):
- # Without anything in os.environ, AWSCredentials() blows up
- os.environ['AWS_SECRET_ACCESS_KEY'] = 'foo'
- self.assertRaises(Exception, AWSCredentials)
-
- def test_no_secret_errors(self):
- # Without anything in os.environ, AWSCredentials() blows up
- os.environ['AWS_ACCESS_KEY_ID'] = 'bar'
- self.assertRaises(Exception, AWSCredentials)
-
- def test_found_values_used(self):
- os.environ['AWS_SECRET_ACCESS_KEY'] = 'foo'
- os.environ['AWS_ACCESS_KEY_ID'] = 'bar'
- creds = AWSCredentials()
- self.assertEqual('foo', creds.secret_key)
- self.assertEqual('bar', creds.access_key)
-
- def test_explicit_access_key(self):
- os.environ['AWS_SECRET_ACCESS_KEY'] = 'foo'
- creds = AWSCredentials(access_key='bar')
- self.assertEqual('foo', creds.secret_key)
- self.assertEqual('bar', creds.access_key)
-
- def test_explicit_secret_key(self):
- os.environ['AWS_ACCESS_KEY_ID'] = 'bar'
- creds = AWSCredentials(secret_key='foo')
- self.assertEqual('foo', creds.secret_key)
- self.assertEqual('bar', creds.access_key)
=== added file 'txaws/tests/test_service.py'
--- txaws/tests/test_service.py 1970-01-01 00:00:00 +0000
+++ txaws/tests/test_service.py 2009-08-20 19:09:56 +0000
@@ -0,0 +1,88 @@
+# Copyright (C) 2009 Duncan McGreggor <duncan@xxxxxxxxxxxxx>
+# Licenced under the txaws licence available at /LICENSE in the txaws source.
+
+import os
+
+from txaws.service import AWSService, ENV_ACCESS_KEY, ENV_SECRET_KEY
+from txaws.tests import TXAWSTestCase
+
+
+class AWSServiceTestCase(TXAWSTestCase):
+
+ def setUp(self):
+ self.service = AWSService("fookeyid", "barsecretkey",
+ "http://my.service/da_endpoint")
+ self.addCleanup(self.clean_environment)
+
+ def clean_environment(self):
+ if os.environ.has_key(ENV_ACCESS_KEY):
+ del os.environ[ENV_ACCESS_KEY]
+ if os.environ.has_key(ENV_SECRET_KEY):
+ del os.environ[ENV_SECRET_KEY]
+
+ def test_simple_creation(self):
+ service = AWSService("fookeyid", "barsecretkey")
+ self.assertEquals(service.access_key, "fookeyid")
+ self.assertEquals(service.secret_key, "barsecretkey")
+ self.assertEquals(service.schema, "https")
+ self.assertEquals(service.host, "")
+ self.assertEquals(service.port, 80)
+ self.assertEquals(service.endpoint, "/")
+ self.assertEquals(service.method, "GET")
+
+ def test_no_access_errors(self):
+ # Without anything in os.environ, AWSService() blows up
+ os.environ[ENV_SECRET_KEY] = "bar"
+ self.assertRaises(ValueError, AWSService)
+
+ def test_no_secret_errors(self):
+ # Without anything in os.environ, AWSService() blows up
+ os.environ[ENV_ACCESS_KEY] = "foo"
+ self.assertRaises(ValueError, AWSService)
+
+ def test_found_values_used(self):
+ os.environ[ENV_ACCESS_KEY] = "foo"
+ os.environ[ENV_SECRET_KEY] = "bar"
+ service = AWSService()
+ self.assertEqual("foo", service.access_key)
+ self.assertEqual("bar", service.secret_key)
+ self.clean_environment()
+
+ def test_explicit_access_key(self):
+ os.environ[ENV_SECRET_KEY] = "foo"
+ service = AWSService(access_key="bar")
+ self.assertEqual("foo", service.secret_key)
+ self.assertEqual("bar", service.access_key)
+
+ def test_explicit_secret_key(self):
+ os.environ[ENV_ACCESS_KEY] = "bar"
+ service = AWSService(secret_key="foo")
+ self.assertEqual("foo", service.secret_key)
+ self.assertEqual("bar", service.access_key)
+
+ def test_parse_uri(self):
+ self.assertEquals(self.service.schema, "http")
+ self.assertEquals(self.service.host, "my.service")
+ self.assertEquals(self.service.port, 80)
+ self.assertEquals(self.service.endpoint, "/da_endpoint")
+
+ def test_parse_uri_https_and_custom_port(self):
+ service = AWSService("foo", "bar", "https://my.service:8080/endpoint")
+ self.assertEquals(service.schema, "https")
+ self.assertEquals(service.host, "my.service")
+ self.assertEquals(service.port, 8080)
+ self.assertEquals(service.endpoint, "/endpoint")
+
+ def test_custom_method(self):
+ service = AWSService("foo", "bar", "http://service/endpoint", "PUT")
+ self.assertEquals(service.method, "PUT")
+
+ def test_get_uri(self):
+ uri = self.service.get_uri()
+ self.assertEquals(uri, "http://my.service/da_endpoint")
+
+ def test_get_uri_custom_port(self):
+ uri = "https://my.service:8080/endpoint"
+ service = AWSService("foo", "bar", uri)
+ new_uri = service.get_uri()
+ self.assertEquals(new_uri, uri)
=== modified file 'txaws/util.py'
--- txaws/util.py 2009-08-17 11:18:56 +0000
+++ txaws/util.py 2009-08-18 20:48:59 +0000
@@ -4,10 +4,10 @@
services.
"""
+import time
+import hmac
+from hashlib import sha1, md5
from base64 import b64encode
-from hashlib import sha1
-import hmac
-import time
# Import XML from somwhere; here in one place to prevent duplication.
try:
@@ -19,6 +19,11 @@
__all__ = ['hmac_sha1', 'iso8601time']
+def calculate_md5(data):
+ digest = md5(data).digest()
+ return b64encode(digest)
+
+
def hmac_sha1(secret, data):
digest = hmac.new(secret, data, sha1).digest()
return b64encode(digest)
Follow ups