Added Gandi keypair management Closes #534
Signed-off-by: Tomaz Muraus <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/3fe88d07 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/3fe88d07 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/3fe88d07 Branch: refs/heads/trunk Commit: 3fe88d073351555909b71818483735a09854a53d Parents: 562e4d2 Author: ZuluPro <[email protected]> Authored: Wed May 27 18:26:34 2015 -0400 Committer: Tomaz Muraus <[email protected]> Committed: Sat Jun 13 20:31:58 2015 +0800 ---------------------------------------------------------------------- libcloud/compute/drivers/gandi.py | 59 +++++++++++++++++--- .../test/compute/fixtures/gandi/ssh_delete.xml | 8 +++ .../test/compute/fixtures/gandi/ssh_info.xml | 25 +++++++++ .../test/compute/fixtures/gandi/ssh_list.xml | 23 ++++++++ libcloud/test/compute/test_gandi.py | 33 +++++++++++ 5 files changed, 141 insertions(+), 7 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/3fe88d07/libcloud/compute/drivers/gandi.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/gandi.py b/libcloud/compute/drivers/gandi.py index 7a54ada..420d25d 100644 --- a/libcloud/compute/drivers/gandi.py +++ b/libcloud/compute/drivers/gandi.py @@ -215,12 +215,15 @@ class GandiNodeDriver(BaseGandiDriver, NodeDriver): :keyword inet_family: version of ip to use, default 4 (optional) :type inet_family: ``int`` + :keyword keypairs: IDs of keypairs or Keypairs object + :type keypairs: ``int`` or :class:`.KeyPair` + :rtype: :class:`Node` """ - if kwargs.get('login') is None or kwargs.get('password') is None: - raise GandiException( - 1020, 'login and password must be defined for node creation') + if not kwargs.get('login') and not kwargs.get('keypairs'): + raise GandiException(1020, "Login and password or ssh keypair " + "must be defined for node creation") location = kwargs.get('location') if location and isinstance(location, NodeLocation): @@ -234,6 +237,12 @@ class GandiNodeDriver(BaseGandiDriver, NodeDriver): raise GandiException( 1022, 'size must be a subclass of NodeSize') + keypairs = kwargs.get('keypairs', []) + keypair_ids = [ + k if isinstance(k, int) else k.extra['id'] + for k in keypairs + ] + # If size name is in INSTANCE_TYPE we use new rating model instance = INSTANCE_TYPES.get(size.id) cores = instance['cpu'] if instance else int(size.id) @@ -248,14 +257,20 @@ class GandiNodeDriver(BaseGandiDriver, NodeDriver): vm_spec = { 'datacenter_id': dc_id, 'hostname': kwargs['name'], - 'login': kwargs['login'], - 'password': kwargs['password'], # TODO : use NodeAuthPassword 'memory': int(size.ram), 'cores': cores, 'bandwidth': int(size.bandwidth), 'ip_version': kwargs.get('inet_family', 4), } + if kwargs.get('login') and kwargs.get('password'): + vm_spec.update({ + 'login': kwargs['login'], + 'password': kwargs['password'], # TODO : use NodeAuthPassword + }) + if keypair_ids: + vm_spec['keys'] = keypair_ids + # Call create_from helper api. Return 3 operations : disk_create, # iface_create,vm_create (op_disk, op_iface, op_vm) = self.connection.request( @@ -535,7 +550,6 @@ class GandiNodeDriver(BaseGandiDriver, NodeDriver): :param node: Node which should be used :type node: :class:`Node` - :param iface: Network interface which should be used :type iface: :class:`GandiNetworkInterface` @@ -554,7 +568,6 @@ class GandiNodeDriver(BaseGandiDriver, NodeDriver): :param node: Node which should be used :type node: :class:`Node` - :param iface: Network interface which should be used :type iface: :class:`GandiNetworkInterface` @@ -652,3 +665,35 @@ class GandiNodeDriver(BaseGandiDriver, NodeDriver): filter_params = {'name': name} kps = self.connection.request('hosting.ssh.list', filter_params).object return self._to_key_pair(kps[0]) + + def import_key_pair_from_string(self, name, key_material): + """ + Create a new key pair object. + + :param name: Key pair name. + :type name: ``str`` + + :param key_material: Public key material. + :type key_material: ``str`` + + :return: Imported key pair object. + :rtype: :class:`.KeyPair` + """ + params = {'name': name, 'value': key_material} + kp = self.connection.request('hosting.ssh.create', params).object + return self._to_key_pair(kp) + + def delete_key_pair(self, key_pair): + """ + Delete an existing key pair. + + :param key_pair: Key pair object or ID. + :type key_pair: :class.KeyPair` or ``int`` + + :return: True of False based on success of Keypair deletion + :rtype: ``bool`` + """ + key_id = key_pair if isinstance(key_pair, int) \ + else key_pair.extra['id'] + success = self.connection.request('hosting.ssh.delete', key_id).object + return success http://git-wip-us.apache.org/repos/asf/libcloud/blob/3fe88d07/libcloud/test/compute/fixtures/gandi/ssh_delete.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/gandi/ssh_delete.xml b/libcloud/test/compute/fixtures/gandi/ssh_delete.xml new file mode 100644 index 0000000..6dbecc1 --- /dev/null +++ b/libcloud/test/compute/fixtures/gandi/ssh_delete.xml @@ -0,0 +1,8 @@ +<?xml version='1.0' encoding='UTF-8'?> +<methodResponse> +<params> +<param> +<value><boolean>1</boolean></value> +</param> +</params> +</methodResponse> http://git-wip-us.apache.org/repos/asf/libcloud/blob/3fe88d07/libcloud/test/compute/fixtures/gandi/ssh_info.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/gandi/ssh_info.xml b/libcloud/test/compute/fixtures/gandi/ssh_info.xml new file mode 100644 index 0000000..b48bace --- /dev/null +++ b/libcloud/test/compute/fixtures/gandi/ssh_info.xml @@ -0,0 +1,25 @@ +<?xml version='1.0' encoding='UTF-8'?> +<methodResponse> +<params> +<param> +<value><struct> +<member> +<name>fingerprint</name> +<value><string>a6:1f:b8:b4:19:91:99:d8:af:ab:d6:17:72:8b:d1:6c</string></value> +</member> +<member> +<name>name</name> +<value><string>testkey</string></value> +</member> +<member> +<name>value</name> +<value><string>ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCaCXFxl0cPZa+PkXSaux/9Sfn4J81eNJ4f/ZkjdIlmLJVYFUKbpC16eEwXYEfw/QBAZFPODCDQOFAZdgajO572y9scp09F7L7Rhwrw7DYu8STMIBz0XBIO8eOUyu5hVRpxaZGDih9B99e1hITTGFg+BveAmrdB8CPtygKo/fUmaamrocZBrD1betaLTC0i6/DVz7YAbR0CleZLlaBogqVhqmS0TB4J67aG2vvq1MjyOixQY5Ab4aXo4Dz1jd7oqCGCKCO9oKAG0ok94foxkfnCmfRrnfWzOA7SFWjUs65SOrGYZghspDcbJ9vA4ZkUuWJXPPvLVgsI8aHwkezJPD8Th root@testhost</string></value> +</member> +<member> +<name>id</name> +<value><int>10</int></value> +</member> +</struct></value> +</param> +</params> +</methodResponse> http://git-wip-us.apache.org/repos/asf/libcloud/blob/3fe88d07/libcloud/test/compute/fixtures/gandi/ssh_list.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/gandi/ssh_list.xml b/libcloud/test/compute/fixtures/gandi/ssh_list.xml new file mode 100644 index 0000000..003e546 --- /dev/null +++ b/libcloud/test/compute/fixtures/gandi/ssh_list.xml @@ -0,0 +1,23 @@ +<?xml version='1.0' encoding='UTF-8'?> +<methodResponse> +<params> +<param> +<value><array><data> +<value><struct> +<member> +<name>fingerprint</name> +<value><string>a6:1f:b8:b4:19:91:99:d8:af:ab:d6:17:72:8b:d1:6c</string></value> +</member> +<member> +<name>id</name> +<value><int>10</int></value> +</member> +<member> +<name>name</name> +<value><string>testkey</string></value> +</member> +</struct></value> +</data></array></value> +</param> +</params> +</methodResponse> http://git-wip-us.apache.org/repos/asf/libcloud/blob/3fe88d07/libcloud/test/compute/test_gandi.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_gandi.py b/libcloud/test/compute/test_gandi.py index e4a11e8..5336820 100644 --- a/libcloud/test/compute/test_gandi.py +++ b/libcloud/test/compute/test_gandi.py @@ -154,6 +154,23 @@ class GandiTests(unittest.TestCase): disks = self.driver.list_volumes() self.assertTrue(self.driver.ex_update_disk(disks[0], new_size=4096)) + def test_list_key_pairs(self): + keys = self.driver.list_key_pairs() + self.assertGreater(len(keys), 0) + + def test_get_key_pair(self): + key = self.driver.get_key_pair(10) + self.assertEqual(key.name, 'testkey') + + def test_import_key_pair_from_string(self): + key = self.driver.import_key_pair_from_string('testkey', '12345') + self.assertEqual(key.name, 'testkey') + self.assertEqual(key.extra['id'], 10) + + def test_delete_key_pair(self): + response = self.driver.delete_key_pair(10) + self.assertTrue(response) + class GandiRatingTests(unittest.TestCase): @@ -285,6 +302,22 @@ class GandiMockHttp(BaseGandiMockHttp): body = self.fixtures.load('disk_delete.xml') return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + def _xmlrpc__hosting_ssh_info(self, method, url, body, headers): + body = self.fixtures.load('ssh_info.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _xmlrpc__hosting_ssh_list(self, method, url, body, headers): + body = self.fixtures.load('ssh_list.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _xmlrpc__hosting_ssh_create(self, method, url, body, headers): + body = self.fixtures.load('ssh_info.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _xmlrpc__hosting_ssh_delete(self, method, url, body, headers): + body = self.fixtures.load('ssh_delete.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + class GandiMockRatingHttp(BaseGandiMockHttp):
