Add and implement get_key_pair method. Also add tests and update documentation.
Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/c62c472b Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/c62c472b Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/c62c472b Branch: refs/heads/trunk Commit: c62c472b0887077ef3402ace51adb511e2b3cf60 Parents: 56ad850 Author: Tomaz Muraus <[email protected]> Authored: Sat Dec 7 15:20:57 2013 +0100 Committer: Tomaz Muraus <[email protected]> Committed: Sun Dec 8 13:45:43 2013 +0100 ---------------------------------------------------------------------- docs/compute/api.rst | 3 ++ docs/compute/key_pair_management.rst | 2 + libcloud/compute/base.py | 12 ++++++ libcloud/compute/drivers/cloudstack.py | 14 +++++++ libcloud/compute/drivers/ec2.py | 41 ++++++++++++++++---- libcloud/compute/drivers/openstack.py | 23 +++++++++-- libcloud/compute/types.py | 20 ++++++++++ .../cloudstack/listSSHKeyPairs_get_one.json | 1 + .../listSSHKeyPairs_get_one_doesnt_exist.json | 1 + .../ec2/describe_key_pairs_doesnt_exist.xml | 2 + .../openstack_v1.1/_os_keypairs_get_one.json | 1 + .../openstack_v1.1/_os_keypairs_not_found.json | 1 + libcloud/test/compute/test_cloudstack.py | 12 ++++++ libcloud/test/compute/test_ec2.py | 24 ++++++++++++ libcloud/test/compute/test_openstack.py | 29 +++++++++++++- 15 files changed, 173 insertions(+), 13 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/c62c472b/docs/compute/api.rst ---------------------------------------------------------------------- diff --git a/docs/compute/api.rst b/docs/compute/api.rst index c45bbbe..3754360 100644 --- a/docs/compute/api.rst +++ b/docs/compute/api.rst @@ -36,3 +36,6 @@ Error Classes .. autoclass:: libcloud.compute.types.DeploymentError :members: + +.. autoclass:: libcloud.compute.types.KeyPairDoesNotExistError + :members: http://git-wip-us.apache.org/repos/asf/libcloud/blob/c62c472b/docs/compute/key_pair_management.rst ---------------------------------------------------------------------- diff --git a/docs/compute/key_pair_management.rst b/docs/compute/key_pair_management.rst index 03fe6b7..107f8b5 100644 --- a/docs/compute/key_pair_management.rst +++ b/docs/compute/key_pair_management.rst @@ -14,6 +14,8 @@ This includes the following functionality: * listing all the available key pairs on your account (:func:`libcloud.compute.base.NodeDriver.list_key_pairs`) +* retrieve information (fingerprint, public key) about a sigle key pair + (:func:`libcloud.compute.base.NodeDriver.get_key_pair`) * creating a new key pair (:func:`libcloud.compute.base.NodeDriver.create_key_pair`) * importing an existing public key http://git-wip-us.apache.org/repos/asf/libcloud/blob/c62c472b/libcloud/compute/base.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/base.py b/libcloud/compute/base.py index d96e280..3c5a4b9 100644 --- a/libcloud/compute/base.py +++ b/libcloud/compute/base.py @@ -1070,6 +1070,18 @@ class NodeDriver(BaseDriver): raise NotImplementedError( 'list_key_pairs not implemented for this driver') + def get_key_pair(self, name): + """ + Retrieve a single key pair. + + :param name: Name of the key pair to retrieve. + :type name: ``str`` + + :rtype: :class:`.KeyPair` + """ + raise NotImplementedError( + 'get_key_pair not implemented for this driver') + def create_key_pair(self, name): """ Create a new key pair object. http://git-wip-us.apache.org/repos/asf/libcloud/blob/c62c472b/libcloud/compute/drivers/cloudstack.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/cloudstack.py b/libcloud/compute/drivers/cloudstack.py index e9742fa..98a06ee 100644 --- a/libcloud/compute/drivers/cloudstack.py +++ b/libcloud/compute/drivers/cloudstack.py @@ -27,6 +27,7 @@ from libcloud.compute.base import Node, NodeDriver, NodeImage, NodeLocation from libcloud.compute.base import NodeSize, StorageVolume from libcloud.compute.base import KeyPair from libcloud.compute.types import NodeState, LibcloudError +from libcloud.compute.types import KeyPairDoesNotExistError from libcloud.utils.networking import is_private_subnet @@ -705,6 +706,19 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver): key_pairs = self._to_key_pairs(data=key_pairs) return key_pairs + def get_key_pair(self, name): + params = {'name': name} + res = self._sync_request(command='listSSHKeyPairs', + params=params, + method='GET') + key_pairs = res.get('sshkeypair', []) + + if len(key_pairs) == 0: + raise KeyPairDoesNotExistError(name=name, driver=self) + + key_pair = self._to_key_pair(data=key_pairs[0]) + return key_pair + def create_key_pair(self, name, **kwargs): """ Create a new key pair object. http://git-wip-us.apache.org/repos/asf/libcloud/blob/c62c472b/libcloud/compute/drivers/ec2.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py index 3db3c35..ff0dd83 100644 --- a/libcloud/compute/drivers/ec2.py +++ b/libcloud/compute/drivers/ec2.py @@ -17,6 +17,7 @@ Amazon EC2, Eucalyptus and Nimbus drivers. """ +import re import sys import base64 import copy @@ -34,10 +35,10 @@ from libcloud.common.aws import AWSBaseResponse, SignedAWSConnection from libcloud.common.types import (InvalidCredsError, MalformedResponseError, LibcloudError) from libcloud.compute.providers import Provider -from libcloud.compute.types import NodeState from libcloud.compute.base import Node, NodeDriver, NodeLocation, NodeSize from libcloud.compute.base import NodeImage, StorageVolume, VolumeSnapshot from libcloud.compute.base import KeyPair +from libcloud.compute.types import NodeState, KeyPairDoesNotExistError API_VERSION = '2010-08-31' NAMESPACE = 'http://ec2.amazonaws.com/doc/%s/' % (API_VERSION) @@ -444,18 +445,29 @@ class EC2Response(AWSBaseResponse): for err in body.findall('Errors/Error'): code, message = err.getchildren() - err_list.append("%s: %s" % (code.text, message.text)) - if code.text == "InvalidClientTokenId": + err_list.append('%s: %s' % (code.text, message.text)) + if code.text == 'InvalidClientTokenId': raise InvalidCredsError(err_list[-1]) - if code.text == "SignatureDoesNotMatch": + if code.text == 'SignatureDoesNotMatch': raise InvalidCredsError(err_list[-1]) - if code.text == "AuthFailure": + if code.text == 'AuthFailure': raise InvalidCredsError(err_list[-1]) - if code.text == "OptInRequired": + if code.text == 'OptInRequired': raise InvalidCredsError(err_list[-1]) - if code.text == "IdempotentParameterMismatch": + if code.text == 'IdempotentParameterMismatch': raise IdempotentParamError(err_list[-1]) - return "\n".join(err_list) + if code.text == 'InvalidKeyPair.NotFound': + # TODO: Use connection context instead + match = re.match(r'.*\'(.+?)\'.*', message.text) + + if match: + name = match.groups()[0] + else: + name = None + + raise KeyPairDoesNotExistError(name=name, + driver=self.connection.driver) + return '\n'.join(err_list) class EC2Connection(SignedAWSConnection): @@ -941,6 +953,19 @@ class BaseEC2NodeDriver(NodeDriver): key_pairs = self._to_key_pairs(elems=elems) return key_pairs + def get_key_pair(self, name): + params = { + 'Action': 'DescribeKeyPairs', + 'KeyName': name + } + + response = self.connection.request(self.path, params=params) + elems = findall(element=response.object, xpath='keySet/item', + namespace=NAMESPACE) + + key_pair = self._to_key_pairs(elems=elems)[0] + return key_pair + def create_key_pair(self, name): params = { 'Action': 'CreateKeyPair', http://git-wip-us.apache.org/repos/asf/libcloud/blob/c62c472b/libcloud/compute/drivers/openstack.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py index 1866120..781873c 100644 --- a/libcloud/compute/drivers/openstack.py +++ b/libcloud/compute/drivers/openstack.py @@ -34,10 +34,11 @@ from xml.etree import ElementTree as ET from libcloud.common.openstack import OpenStackBaseConnection from libcloud.common.openstack import OpenStackDriverMixin from libcloud.common.types import MalformedResponseError, ProviderError -from libcloud.compute.types import NodeState, Provider from libcloud.compute.base import NodeSize, NodeImage from libcloud.compute.base import NodeDriver, Node, NodeLocation, StorageVolume from libcloud.compute.base import KeyPair +from libcloud.compute.types import NodeState, Provider +from libcloud.compute.types import KeyPairDoesNotExistError from libcloud.pricing import get_size_price from libcloud.common.base import Response from libcloud.utils.xml import findall @@ -106,12 +107,19 @@ class OpenStackResponse(Response): body = self.parse_body() if self.has_content_type('application/xml'): - text = "; ".join([err.text or '' for err in body.getiterator() + text = '; '.join([err.text or '' for err in body.getiterator() if err.text]) elif self.has_content_type('application/json'): - values = body.values() + values = list(body.values()) - if len(values) > 0 and 'message' in values[0]: + context = self.connection.context + driver = self.connection.driver + key_pair_name = context.get('key_pair_name', None) + + if len(values) > 0 and values[0]['code'] == 404 and key_pair_name: + raise KeyPairDoesNotExistError(name=key_pair_name, + driver=driver) + elif len(values) > 0 and 'message' in values[0]: text = ';'.join([fault_data['message'] for fault_data in values]) else: @@ -1726,6 +1734,13 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver): key_pairs = self._to_key_pairs(response.object) return key_pairs + def get_key_pair(self, name): + self.connection.set_context({'key_pair_name': name}) + + response = self.connection.request('/os-keypairs/%s' % (name)) + key_pair = self._to_key_pair(response.object['keypair']) + return key_pair + def create_key_pair(self, name): data = {'keypair': {'name': name}} response = self.connection.request('/os-keypairs', method='POST', http://git-wip-us.apache.org/repos/asf/libcloud/blob/c62c472b/libcloud/compute/types.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/types.py b/libcloud/compute/types.py index bc74b1d..dc83a03 100644 --- a/libcloud/compute/types.py +++ b/libcloud/compute/types.py @@ -216,5 +216,25 @@ class DeploymentError(LibcloudError): % (self.node.id, str(self.value), str(self.driver)))) +class KeyPairError(LibcloudError): + error_type = 'KeyPairError' + + def __init__(self, name, driver): + self.name = name + self.value = 'Key pair with name %s does not exist' % (name) + super(KeyPairError, self).__init__(value=self.value, driver=driver) + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return ('<%s name=%s, value=%s, driver=%s>' % + (self.error_type, self.name, self.value, self.driver.name)) + + +class KeyPairDoesNotExistError(KeyPairError): + error_type = 'KeyPairDoesNotExistError' + + """Deprecated alias of :class:`DeploymentException`""" DeploymentException = DeploymentError http://git-wip-us.apache.org/repos/asf/libcloud/blob/c62c472b/libcloud/test/compute/fixtures/cloudstack/listSSHKeyPairs_get_one.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/cloudstack/listSSHKeyPairs_get_one.json b/libcloud/test/compute/fixtures/cloudstack/listSSHKeyPairs_get_one.json new file mode 100644 index 0000000..d7728aa --- /dev/null +++ b/libcloud/test/compute/fixtures/cloudstack/listSSHKeyPairs_get_one.json @@ -0,0 +1 @@ +{"listsshkeypairsresponse":{"count":1,"sshkeypair":[{"name":"cs-keypair","fingerprint":"00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"}]}} http://git-wip-us.apache.org/repos/asf/libcloud/blob/c62c472b/libcloud/test/compute/fixtures/cloudstack/listSSHKeyPairs_get_one_doesnt_exist.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/cloudstack/listSSHKeyPairs_get_one_doesnt_exist.json b/libcloud/test/compute/fixtures/cloudstack/listSSHKeyPairs_get_one_doesnt_exist.json new file mode 100644 index 0000000..dc7b1c9 --- /dev/null +++ b/libcloud/test/compute/fixtures/cloudstack/listSSHKeyPairs_get_one_doesnt_exist.json @@ -0,0 +1 @@ +{ "listsshkeypairsresponse" : { } } http://git-wip-us.apache.org/repos/asf/libcloud/blob/c62c472b/libcloud/test/compute/fixtures/ec2/describe_key_pairs_doesnt_exist.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/ec2/describe_key_pairs_doesnt_exist.xml b/libcloud/test/compute/fixtures/ec2/describe_key_pairs_doesnt_exist.xml new file mode 100644 index 0000000..d5a9cae --- /dev/null +++ b/libcloud/test/compute/fixtures/ec2/describe_key_pairs_doesnt_exist.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Response><Errors><Error><Code>InvalidKeyPair.NotFound</Code><Message>The key pair 'test-key-pair' does not exist</Message></Error></Errors><RequestID>31b97300-eb8e-405e-9567-b0f57b791fed</RequestID></Response> http://git-wip-us.apache.org/repos/asf/libcloud/blob/c62c472b/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_get_one.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_get_one.json b/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_get_one.json new file mode 100644 index 0000000..6b0135d --- /dev/null +++ b/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_get_one.json @@ -0,0 +1 @@ +{"keypair": {"public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDkRJ+f6VibLJJRPtuVM3g5ZCporVfoFJhRt8vcBbD4x/1h8OSBvWwuj9tKoZha0ijpGJIRW5HylRKYZPFL7gxlmqS9LM/lewx3c/fZItmP4kDYuXX2Dn9XwHFLS/bSy/JHVgnrHopHUH/2a57iUNe+QRrngEGz13N1S9If3EGDxIhZuO8S1BRLWK3SqtHjOQ6mWZOF6xAs3nwKaBNJTWVp6XUshzlcwWUA5nFysN9MVXX7t/J1qo+xcSAwt/ew8v6dZJcCQM+y30bQhPJzSN8LepN5tSTI4iEN0Y+LtNQDtCEYacr4qEFkAxj3CcSAeQVMaT/a7ps0xiHg9GnCbGsV Generated by Nova\n", "user_id": "1234", "name": "test-key-pair", "deleted": false, "created_at": "2013-12-07T15:17:16.000000", "updated_at": null, "fingerprint": "a9:55:e8:b8:49:45:7b:aa:a9:33:fb:97:86:79:2c:1b", "deleted_at": null, "id": 4567}} http://git-wip-us.apache.org/repos/asf/libcloud/blob/c62c472b/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_not_found.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_not_found.json b/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_not_found.json new file mode 100644 index 0000000..fa3927a --- /dev/null +++ b/libcloud/test/compute/fixtures/openstack_v1.1/_os_keypairs_not_found.json @@ -0,0 +1 @@ +{"itemNotFound": {"message": "The resource could not be found.", "code": 404}} http://git-wip-us.apache.org/repos/asf/libcloud/blob/c62c472b/libcloud/test/compute/test_cloudstack.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_cloudstack.py b/libcloud/test/compute/test_cloudstack.py index 65a0c28..c9adbdb 100644 --- a/libcloud/test/compute/test_cloudstack.py +++ b/libcloud/test/compute/test_cloudstack.py @@ -27,6 +27,7 @@ except ImportError: from libcloud.compute.drivers.cloudstack import CloudStackNodeDriver from libcloud.compute.types import LibcloudError, Provider, InvalidCredsError +from libcloud.compute.types import KeyPairDoesNotExistError from libcloud.compute.providers import get_driver from libcloud.test import unittest @@ -290,6 +291,17 @@ class CloudStackCommonTestCase(TestCaseMixin): keypairs = self.driver.list_key_pairs() self.assertEqual(keypairs, []) + def test_get_key_pair(self): + CloudStackMockHttp.fixture_tag = 'get_one' + key_pair = self.driver.get_key_pair(name='cs-keypair') + self.assertEqual(key_pair.name, 'cs-keypair') + + def test_get_key_pair_doesnt_exist(self): + CloudStackMockHttp.fixture_tag = 'get_one_doesnt_exist' + + self.assertRaises(KeyPairDoesNotExistError, self.driver.get_key_pair, + name='does-not-exist') + def test_create_keypair(self): key_pair = self.driver.create_key_pair(name='test-keypair') http://git-wip-us.apache.org/repos/asf/libcloud/blob/c62c472b/libcloud/test/compute/test_ec2.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_ec2.py b/libcloud/test/compute/test_ec2.py index c32bffe..f516059 100644 --- a/libcloud/test/compute/test_ec2.py +++ b/libcloud/test/compute/test_ec2.py @@ -35,6 +35,7 @@ from libcloud.compute.drivers.ec2 import REGION_DETAILS from libcloud.compute.drivers.ec2 import ExEC2AvailabilityZone from libcloud.compute.base import Node, NodeImage, NodeSize, NodeLocation from libcloud.compute.base import StorageVolume, VolumeSnapshot +from libcloud.compute.types import KeyPairDoesNotExistError from libcloud.test import MockHttpTestCase, LibcloudTestCase from libcloud.test.compute import TestCaseMixin @@ -389,6 +390,18 @@ class EC2Tests(LibcloudTestCase, TestCaseMixin): self.assertEqual(keypairs[0]['keyName'], 'gsg-keypair') self.assertEqual(keypairs[0]['keyFingerprint'], null_fingerprint) + def test_get_key_pair(self): + EC2MockHttp.type = 'get_one' + + key_pair = self.driver.get_key_pair(name='gsg-keypair') + self.assertEqual(key_pair.name, 'gsg-keypair') + + def test_get_key_pair_does_not_exist(self): + EC2MockHttp.type = 'doesnt_exist' + + self.assertRaises(KeyPairDoesNotExistError, self.driver.get_key_pair, + name='test-key-pair') + def test_create_key_pair(self): key_pair = self.driver.create_key_pair(name='test-keypair') @@ -842,6 +855,17 @@ class EC2MockHttp(MockHttpTestCase): body = self.fixtures.load('describe_key_pairs.xml') return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + def _get_one_DescribeKeyPairs(self, method, url, body, headers): + self.assertUrlContainsQueryParams(url, {'KeyName': 'gsg-keypair'}) + + body = self.fixtures.load('describe_key_pairs.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _doesnt_exist_DescribeKeyPairs(self, method, url, body, headers): + body = self.fixtures.load('describe_key_pairs_doesnt_exist.xml') + return (httplib.BAD_REQUEST, body, {}, + httplib.responses[httplib.BAD_REQUEST]) + def _CreateKeyPair(self, method, url, body, headers): body = self.fixtures.load('create_key_pair.xml') return (httplib.OK, body, {}, httplib.responses[httplib.OK]) http://git-wip-us.apache.org/repos/asf/libcloud/blob/c62c472b/libcloud/test/compute/test_openstack.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_openstack.py b/libcloud/test/compute/test_openstack.py index 3c8377b..4f59711 100644 --- a/libcloud/test/compute/test_openstack.py +++ b/libcloud/test/compute/test_openstack.py @@ -36,7 +36,7 @@ from libcloud.common.types import InvalidCredsError, MalformedResponseError, \ from libcloud.common.openstack import OpenStackBaseConnection from libcloud.common.openstack import OpenStackAuthConnection from libcloud.common.openstack import AUTH_TOKEN_EXPIRES_GRACE_SECONDS -from libcloud.compute.types import Provider +from libcloud.compute.types import Provider, KeyPairDoesNotExistError from libcloud.compute.providers import get_driver from libcloud.compute.drivers.openstack import ( OpenStack_1_0_NodeDriver, OpenStack_1_0_Response, @@ -1337,6 +1337,16 @@ class OpenStack_1_1_Tests(unittest.TestCase, TestCaseMixin): self.assertTrue(len(keypair.public_key) > 10) self.assertEqual(keypair.private_key, None) + def test_get_key_pair(self): + key_pair = self.driver.get_key_pair(name='test-key-pair') + + self.assertEqual(key_pair.name, 'test-key-pair') + + def test_get_key_pair_doesnt_exist(self): + self.assertRaises(KeyPairDoesNotExistError, + self.driver.get_key_pair, + name='doesnt-exist') + def test_create_key_pair(self): name = 'key0' keypair = self.driver.create_key_pair(name=name) @@ -1692,6 +1702,23 @@ class OpenStack_1_1_MockHttp(MockHttpTestCase): return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) + def _v1_1_slug_os_keypairs_test_key_pair(self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('_os_keypairs_get_one.json') + else: + raise NotImplementedError() + + return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) + + def _v1_1_slug_os_keypairs_doesnt_exist(self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('_os_keypairs_not_found.json') + else: + raise NotImplementedError() + + return (httplib.NOT_FOUND, body, self.json_content_headers, + httplib.responses[httplib.NOT_FOUND]) + def _v1_1_slug_os_keypairs_key1(self, method, url, body, headers): if method == "DELETE": return (httplib.ACCEPTED, "", {}, httplib.responses[httplib.ACCEPTED])
