Updated doc warnings, updated AWS request signer to support POST.
Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/9507a634 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/9507a634 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/9507a634 Branch: refs/heads/trunk Commit: 9507a634d0453bac7227a362b1d972dd81abc260 Parents: 0999e52 Author: anthony-shaw <[email protected]> Authored: Sun Jan 3 20:42:07 2016 +1100 Committer: anthony-shaw <[email protected]> Committed: Sun Jan 3 20:42:07 2016 +1100 ---------------------------------------------------------------------- docs/container/api.rst | 2 +- docs/container/drivers/docker.rst | 4 +-- docs/container/drivers/ecs.rst | 6 +++- docs/container/drivers/index.rst | 2 +- docs/container/drivers/joyent.rst | 4 +-- libcloud/common/aws.py | 59 +++++++++++++++++++++++----------- libcloud/common/base.py | 1 + libcloud/container/drivers/ecs.py | 40 +++++++++++++---------- 8 files changed, 76 insertions(+), 42 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/9507a634/docs/container/api.rst ---------------------------------------------------------------------- diff --git a/docs/container/api.rst b/docs/container/api.rst index a3ee225..f9f7dd4 100644 --- a/docs/container/api.rst +++ b/docs/container/api.rst @@ -1,7 +1,7 @@ :orphan: Container Base API -============ +================== .. autoclass:: libcloud.container.base.ContainerDriver :members: http://git-wip-us.apache.org/repos/asf/libcloud/blob/9507a634/docs/container/drivers/docker.rst ---------------------------------------------------------------------- diff --git a/docs/container/drivers/docker.rst b/docs/container/drivers/docker.rst index 7bb729c..32cbd2c 100644 --- a/docs/container/drivers/docker.rst +++ b/docs/container/drivers/docker.rst @@ -1,5 +1,5 @@ Docker Container Driver Documentation -================================== +===================================== `Docker`_ containers wrap up a piece of software in a complete filesystem that contains everything it needs to run: code, runtime, system tools, system libraries â anything you can install on a server. This guarantees that it will always run the same, @@ -11,7 +11,7 @@ regardless of the environment it is running in. :target: http://docker.io/ Instantiating the driver -------------------------------------- +------------------------ .. literalinclude:: /examples/container/docker/instantiate_driver.py :language: python http://git-wip-us.apache.org/repos/asf/libcloud/blob/9507a634/docs/container/drivers/ecs.rst ---------------------------------------------------------------------- diff --git a/docs/container/drivers/ecs.rst b/docs/container/drivers/ecs.rst index 45cc2f8..089fae6 100644 --- a/docs/container/drivers/ecs.rst +++ b/docs/container/drivers/ecs.rst @@ -8,8 +8,12 @@ Elastic Container Service is a container-as-a-service feature of `AWS`_. :width: 300 :target: http://aws.amazon.com/ +To provide API key access, you should apply one of the roles: +* AmazonEC2ContainerServiceFullAccess +* AmazonEC2ContainerServiceReadOnlyAccess + Instantiating the driver -------------------------------------- +------------------------ .. literalinclude:: /examples/container/ecs/instantiate_driver.py :language: python http://git-wip-us.apache.org/repos/asf/libcloud/blob/9507a634/docs/container/drivers/index.rst ---------------------------------------------------------------------- diff --git a/docs/container/drivers/index.rst b/docs/container/drivers/index.rst index be5db22..fd1145e 100644 --- a/docs/container/drivers/index.rst +++ b/docs/container/drivers/index.rst @@ -1,7 +1,7 @@ :orphan: Container Drivers Documentation -============================ +=============================== This chapter includes links to driver (provider) specific documentation pages. http://git-wip-us.apache.org/repos/asf/libcloud/blob/9507a634/docs/container/drivers/joyent.rst ---------------------------------------------------------------------- diff --git a/docs/container/drivers/joyent.rst b/docs/container/drivers/joyent.rst index fc43916..f0e3da7 100644 --- a/docs/container/drivers/joyent.rst +++ b/docs/container/drivers/joyent.rst @@ -1,5 +1,5 @@ Joyent Triton Container Driver Documentation -================================== +============================================ `Joyent Triton`_ is a Docker hosting service, provided by service provider `Joyent`_. Docker-native tools and elastic hosts make deploying on Triton as easy as running Docker on your laptop. @@ -12,7 +12,7 @@ Mix Docker containers with container-native Linux to extend the benefits of cont :target: http://joyent.com/ Instantiating the driver -------------------------------------- +------------------------ Download the script:: http://git-wip-us.apache.org/repos/asf/libcloud/blob/9507a634/libcloud/common/aws.py ---------------------------------------------------------------------- diff --git a/libcloud/common/aws.py b/libcloud/common/aws.py index 4d7523e..b7153e3 100644 --- a/libcloud/common/aws.py +++ b/libcloud/common/aws.py @@ -21,6 +21,11 @@ import time from hashlib import sha256 try: + import simplejson as json +except ImportError: + import json + +try: from lxml import etree as ET except ImportError: from xml.etree import ElementTree as ET @@ -180,7 +185,8 @@ class AWSRequestSigner(object): def get_request_params(self, params, method='GET', path='/'): return params - def get_request_headers(self, params, headers, method='GET', path='/'): + def get_request_headers(self, params, headers, method='GET', path='/', + data=None): return params, headers @@ -237,27 +243,32 @@ class AWSRequestSignerAlgorithmV2(AWSRequestSigner): class AWSRequestSignerAlgorithmV4(AWSRequestSigner): def get_request_params(self, params, method='GET', path='/'): - params['Version'] = self.version + if method == 'GET': + params['Version'] = self.version return params - def get_request_headers(self, params, headers, method='GET', path='/'): + def get_request_headers(self, params, headers, method='GET', path='/', + data=None): now = datetime.utcnow() headers['X-AMZ-Date'] = now.strftime('%Y%m%dT%H%M%SZ') headers['Authorization'] = \ self._get_authorization_v4_header(params=params, headers=headers, - dt=now, method=method, path=path) + dt=now, method=method, path=path, + data=data) return params, headers def _get_authorization_v4_header(self, params, headers, dt, method='GET', - path='/'): - assert method in ['GET', 'POST'], 'AWS Signature V4 not implemented for ' \ - 'other methods than GET and POST' + path='/', data=None): + assert method in ['GET', 'POST'], 'AWS Signature V4 ' \ + 'not implemented for ' \ + 'other methods than GET and POST' credentials_scope = self._get_credential_scope(dt=dt) signed_headers = self._get_signed_headers(headers=headers) signature = self._get_signature(params=params, headers=headers, - dt=dt, method=method, path=path) + dt=dt, method=method, path=path, + data=data) return 'AWS4-HMAC-SHA256 Credential=%(u)s/%(c)s, ' \ 'SignedHeaders=%(sh)s, Signature=%(s)s' % { @@ -267,11 +278,12 @@ class AWSRequestSignerAlgorithmV4(AWSRequestSigner): 's': signature } - def _get_signature(self, params, headers, dt, method, path): + def _get_signature(self, params, headers, dt, method, path, data): key = self._get_key_to_sign_with(dt) string_to_sign = self._get_string_to_sign(params=params, headers=headers, dt=dt, - method=method, path=path) + method=method, path=path, + data=data) return _sign(key=key, msg=string_to_sign, hex=True) def _get_key_to_sign_with(self, dt): @@ -284,11 +296,12 @@ class AWSRequestSignerAlgorithmV4(AWSRequestSigner): self.connection.service_name), 'aws4_request') - def _get_string_to_sign(self, params, headers, dt, method, path): + def _get_string_to_sign(self, params, headers, dt, method, path, data): canonical_request = self._get_canonical_request(params=params, headers=headers, method=method, - path=path) + path=path, + data=data) return '\n'.join(['AWS4-HMAC-SHA256', dt.strftime('%Y%m%dT%H%M%SZ'), @@ -308,8 +321,11 @@ class AWSRequestSignerAlgorithmV4(AWSRequestSigner): return '\n'.join([':'.join([k.lower(), v.strip()]) for k, v in sorted(headers.items())]) + '\n' - def _get_payload_hash(self): - return _hash('') + def _get_payload_hash(self, method, data=None): + if method == 'GET': + return _hash('') + elif method == 'POST': + return _hash(data) def _get_request_params(self, params): # For self.method == GET @@ -317,14 +333,14 @@ class AWSRequestSignerAlgorithmV4(AWSRequestSigner): (urlquote(k, safe=''), urlquote(str(v), safe='~')) for k, v in sorted(params.items())]) - def _get_canonical_request(self, params, headers, method, path): + def _get_canonical_request(self, params, headers, method, path, data): return '\n'.join([ method, path, self._get_request_params(params), self._get_canonical_headers(headers), self._get_signed_headers(headers), - self._get_payload_hash() + self._get_payload_hash(method, data) ]) @@ -365,7 +381,8 @@ class SignedAWSConnection(AWSTokenConnection): params, headers = self.signer.get_request_headers(params=params, headers=headers, method=self.method, - path=self.action) + path=self.action, + data=self.data) return params, headers @@ -374,7 +391,13 @@ class AWSJsonResponse(JsonResponse): Amazon ECS response class. ECS API uses JSON unlike the s3, elb drivers """ - + def parse_error(self): + response = json.loads(self.body) + code = response['__type'] + message = response['Message'] + + return ('%s: %s' % (code, message)) + def _sign(key, msg, hex=False): if hex: http://git-wip-us.apache.org/repos/asf/libcloud/blob/9507a634/libcloud/common/base.py ---------------------------------------------------------------------- diff --git a/libcloud/common/base.py b/libcloud/common/base.py index d457e24..a25dcfc 100644 --- a/libcloud/common/base.py +++ b/libcloud/common/base.py @@ -709,6 +709,7 @@ class Connection(object): action = self.morph_action_hook(action) self.action = action self.method = method + self.data = data # Extend default parameters params = self.add_default_params(params) http://git-wip-us.apache.org/repos/asf/libcloud/blob/9507a634/libcloud/container/drivers/ecs.py ---------------------------------------------------------------------- diff --git a/libcloud/container/drivers/ecs.py b/libcloud/container/drivers/ecs.py index 1485027..746c176 100644 --- a/libcloud/container/drivers/ecs.py +++ b/libcloud/container/drivers/ecs.py @@ -13,16 +13,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -__all__ = [ - 'ElasticContainerDriver' -] - +try: + import simplejson as json +except ImportError: + import json from libcloud.container.base import (ContainerDriver, Container, ContainerCluster, ContainerImage) from libcloud.container.types import ContainerState from libcloud.common.aws import SignedAWSConnection, AWSJsonResponse +__all__ = [ + 'ElasticContainerDriver' +] + + VERSION = '2014-11-13' HOST = 'ecs.%s.amazonaws.com' ROOT = '/' @@ -67,6 +72,7 @@ class ElasticContainerDriver(ContainerDriver): data = self.connection.request( ROOT, method='POST', + data=json.dumps({}), headers=self._get_headers(params['Action']) ).object return self._to_clusters(data) @@ -87,7 +93,7 @@ class ElasticContainerDriver(ContainerDriver): response = self.connection.request( ROOT, method='POST', - data=request, + data=json.dumps(request), headers=self._get_headers('CreateCluster') ).object return self._to_cluster(response['cluster']) @@ -103,7 +109,7 @@ class ElasticContainerDriver(ContainerDriver): data = self.connection.request( ROOT, method='POST', - data=request, + data=json.dumps(request), headers=self._get_headers('DeleteCluster') ).object return data['cluster']['status'] == 'INACTIVE' @@ -142,15 +148,15 @@ class ElasticContainerDriver(ContainerDriver): :rtype: ``list`` of :class:`Container` """ - request = None + request = {'cluster': 'default'} if cluster is not None: - request = {'cluster': cluster.id} + request['cluster'] = cluster.id if image is not None: request['family'] = image.name list_response = self.connection.request( ROOT, method='POST', - data=request, + data=json.dumps(request), headers=self._get_headers('ListTasks') ).object containers = self.ex_list_containers_for_task( @@ -204,7 +210,7 @@ class ElasticContainerDriver(ContainerDriver): response = self.connection.request( ROOT, method='POST', - data=data, + data=json.dumps(data), headers=self._get_headers('RegisterTaskDefinition') ).object if start: @@ -263,7 +269,7 @@ class ElasticContainerDriver(ContainerDriver): response = self.connection.request( ROOT, method='POST', - data=request, + data=json.dumps(request), headers=self._get_headers('StopTask') ).object containers = [] @@ -313,7 +319,7 @@ class ElasticContainerDriver(ContainerDriver): response = self.connection.request( ROOT, method='POST', - data=request, + data=json.dumps(request), headers=self._get_headers('RunTask') ).object containers = [] @@ -334,7 +340,7 @@ class ElasticContainerDriver(ContainerDriver): descripe_response = self.connection.request( ROOT, method='POST', - data=describe_request, + data=json.dumps(describe_request), headers=self._get_headers('DescribeTasks') ).object containers = [] @@ -375,7 +381,7 @@ class ElasticContainerDriver(ContainerDriver): response = self.connection.request( ROOT, method='POST', - data=request, + data=json.dumps(request), headers=self._get_headers('CreateService') ).object return response @@ -395,7 +401,7 @@ class ElasticContainerDriver(ContainerDriver): response = self.connection.request( ROOT, method='POST', - data=request, + data=json.dumps(request), headers=self._get_headers('ListServices') ).object return response['serviceArns'] @@ -419,7 +425,7 @@ class ElasticContainerDriver(ContainerDriver): response = self.connection.request( ROOT, method='POST', - data=request, + data=json.dumps(request), headers=self._get_headers('DescribeServices') ).object return response['services'][0] @@ -440,7 +446,7 @@ class ElasticContainerDriver(ContainerDriver): response = self.connection.request( ROOT, method='POST', - data=request, + data=json.dumps(request), headers=self._get_headers('DeleteService') ).object return response
