1
# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
2
# Licenced under the txaws licence available at /LICENSE in the txaws source.
4
"""EC2 client support."""
6
__all__ = ['EC2Client']
8
from base64 import b64encode
9
from urllib import quote
11
from twisted.web.client import getPage
13
from txaws import credentials
14
from txaws.util import iso8601time, XML
17
class Instance(object):
18
"""An Amazon EC2 Instance.
20
:attrib instanceId: The instance ID of this instance.
21
:attrib instanceState: The state of this instance.
24
def __init__(self, instanceId, instanceState):
25
self.instanceId = instanceId
26
self.instanceState = instanceState
29
class EC2Client(object):
30
"""A client for EC2."""
32
NS = '{http://ec2.amazonaws.com/doc/2008-12-01/}'
34
def __init__(self, creds=None, query_factory=None):
35
"""Create an EC2Client.
37
:param creds: Explicit credentials to use. If None, credentials are
38
inferred as per txaws.credentials.AWSCredentials.
41
self.creds = credentials.AWSCredentials()
44
if query_factory is None:
45
self.query_factory = Query
47
self.query_factory = query_factory
49
def describe_instances(self):
50
"""Describe current instances."""
51
q = self.query_factory('DescribeInstances', self.creds)
53
return d.addCallback(self._parse_Reservation)
55
def _parse_Reservation(self, xml_bytes):
58
# May be a more elegant way to do this:
59
for reservation in root.find(self.NS + 'reservationSet'):
60
for instance in reservation.find(self.NS + 'instancesSet'):
61
instanceId = instance.findtext(self.NS + 'instanceId')
62
instanceState = instance.find(
63
self.NS + 'instanceState').findtext(self.NS + 'name')
64
result.append(Instance(instanceId, instanceState))
69
"""A query that may be submitted to EC2."""
71
def __init__(self, action, creds, other_params=None, time_tuple=None):
72
"""Create a Query to submit to EC2."""
73
# Require params (2008-12-01 API):
74
# Version, SignatureVersion, SignatureMethod, Action, AWSAccessKeyId,
75
# Timestamp || Expires, Signature,
76
self.params = {'Version': '2008-12-01',
77
'SignatureVersion': '2',
78
'SignatureMethod': 'HmacSHA1',
80
'AWSAccessKeyId': creds.access_key,
81
'Timestamp': iso8601time(time_tuple),
84
self.params.update(other_params)
86
self.host = 'ec2.amazonaws.com'
90
def canonical_query_params(self):
91
"""Return the canonical query params (used in signing)."""
93
for key, value in self.sorted_params():
94
result.append('%s=%s' % (self.encode(key), self.encode(value)))
95
return '&'.join(result)
97
def encode(self, a_string):
98
"""Encode a_string as per the canonicalisation encoding rules.
100
See the AWS dev reference page 90 (2008-12-01 version).
101
:return: a_string encoded.
103
return quote(a_string, safe='~')
105
def signing_text(self):
106
"""Return the text to be signed when signing the query."""
107
result = "%s\n%s\n%s\n%s" % (self.method, self.host, self.uri,
108
self.canonical_query_params())
112
"""Sign this query using its built in credentials.
114
This prepares it to be sent, and should be done as the last step before
115
submitting the query. Signing is done automatically - this is a public
116
method to facilitate testing.
118
self.params['Signature'] = self.creds.sign(self.signing_text())
120
def sorted_params(self):
121
"""Return the query params sorted appropriately for signing."""
122
return sorted(self.params.items())
125
"""Submit this query.
127
:return: A deferred from twisted.web.client.getPage
130
url = 'http://%s%s?%s' % (self.host, self.uri,
131
self.canonical_query_params())
132
return getPage(url, method=self.method)