Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ Compute
(#1906)
[Ross Vandegrift - @rvandegrift]

- [LINODE] Add support for cloud-init metadata support to create_node()
Add new functions ``create_key_pair``, ``list_key_pairs``, and ``get_image``
(#1946)
[Michael Galaxy - @mraygalaxy2]

Storage
~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion docs/compute/_supported_methods_image_management.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Provider list images get image create image delete image c
`KTUCloud`_ yes no no no no
`kubevirt`_ yes no no no no
`Libvirt`_ no no no no no
`Linode`_ yes no yes yes no
`Linode`_ yes yes yes yes no
`Maxihost`_ yes no no no no
`Nimbus`_ yes yes yes yes yes
`NTTAmerica`_ yes no no no no
Expand Down
2 changes: 1 addition & 1 deletion docs/compute/_supported_methods_key_pair_management.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Provider list key pairs get key pair create key pair impor
`KTUCloud`_ yes yes yes yes no yes
`kubevirt`_ no no no no no no
`Libvirt`_ no no no no no no
`Linode`_ no no no no no no
`Linode`_ yes no yes no no no
`Maxihost`_ yes no yes no no no
`Nimbus`_ yes yes yes yes no yes
`NTTAmerica`_ no no no no no no
Expand Down
6 changes: 6 additions & 0 deletions docs/upgrade_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ This page describes how to upgrade from a previous version to a new version
which contains backward incompatible or semi-incompatible changes and how to
preserve the old behavior when this is possible.

Libcloud 3.8.0
--------------
- [LINODE API v4] Order of arguments to create_node() was changed. The order of the
arguments for name and size were not consistent with the rest of the codebase.
This is possibly a breaking change for anyone using a previous version.

Libcloud 3.7.0
--------------

Expand Down
80 changes: 77 additions & 3 deletions libcloud/compute/drivers/linode.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from libcloud.utils.py3 import httplib
from libcloud.compute.base import (
Node,
KeyPair,
NodeSize,
NodeImage,
NodeDriver,
Expand Down Expand Up @@ -861,6 +862,33 @@ def list_images(self):
data = self._paginated_request("/v4/images", "data")
return [self._to_image(obj) for obj in data]

def create_key_pair(self, name, public_key=""):
"""
Creates an SSH keypair

:param name: The name to be given to the keypair (required).\
:type name: `str`

:keyword public_key: Contents of the public key the the SSH key pair
:type public_key: `str`

:rtype: :class: `KeyPair`
"""
attr = {"label": name, "ssh_key": public_key}
response = self.connection.request(
"/v4/profile/sshkeys", data=json.dumps(attr), method="POST"
).object
return self._to_key_pair(response)

def list_key_pairs(self):
"""
Provide a list of all the SSH keypairs in your account.

:rtype: ``list`` of :class: `KeyPair`
"""
data = self._paginated_request("/v4/profile/sshkeys", "data")
return [self._to_key_pair(obj) for obj in data]

def list_locations(self):
"""
Lists the Regions available for Linode services
Expand Down Expand Up @@ -945,15 +973,28 @@ def reboot_node(self, node):
def create_node(
self,
location,
size,
image=None,
name=None,
# Previously, the following 3 parameters did not match the rest of the libcloud
# codebase drivers. They should be in the same order as other compute drivers.
# Previously, it looked like this:
# size,
# image=None,
# name=None,
#
# Comments welcome on how backwards compatibility (if any) should work here.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to leave it as-in and document this backward incompatible / breaking change in the upgrade notes file (https://github.com/apache/libcloud/blob/trunk/docs/upgrade_notes.rst), ideally with some concrete before / after code examples.

# Since it was not compatible with other drivers, it is not clear to me if this
# would break anyone's codebase if they were not using any other libcloud drivers
# to other cloud providers in the first place. If they were not, that seems to
# kind of defeat the purpose of using libcloud.
name, # Can be None
size, # Can be None
image, # Can be None
root_pass=None,
ex_authorized_keys=None,
ex_authorized_users=None,
ex_tags=None,
ex_backups_enabled=False,
ex_private_ip=False,
ex_userdata=False,
):
"""Creates a Linode Instance.
In order for this request to complete successfully,
Expand Down Expand Up @@ -997,6 +1038,12 @@ def create_node(
:keyword ex_private_ip: whether or not to request a private IP
:type ex_private_ip: ``bool``

:keyword ex_userdata: add cloud-config compatible userdata to be
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would actually be better to let user pass in a raw string and then we can take care of base64 encoding the string inside the method ourselves.

This way the method abstracts away this implementation detail from the end user (we already do a similar thing in a bunch of places).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Dorthu Would you like me to make that change? ^^^

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it would be more convenient for the library to handle the encoding, especially if there's precedent elsewhere in libcloud, as long as that behavior is featured prominently in the documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do! Thanks guys.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okie dokie @Kami @Dorthu I have pushed that change (and tested it against Linode).

processed by cloud-init inside the Linode instance. NOTE: the
contents of this string must be base64 encoded before passing
it to this function.
:type ex_userdata: ``str``

:return: Node representing the newly-created node
:rtype: :class:`Node`
"""
Expand All @@ -1014,6 +1061,9 @@ def create_node(
"backups_enabled": ex_backups_enabled,
}

if ex_userdata:
attr["metadata"] = {"user_data": binascii.b2a_base64(bytes(ex_userdata.encode("utf-8"))).decode("ascii").strip()}

if image is not None:
if root_pass is None:
raise LinodeExceptionV4("root password required " "when providing an image")
Expand Down Expand Up @@ -1369,6 +1419,18 @@ def ex_get_volume(self, volume_id):
response = self.connection.request("/v4/volumes/%s" % volume_id).object
return self._to_volume(response)

def get_image(self, image):
"""
Lookup a Linode image

:param image: The name to image to be looked up (required).\
:type name: `str`

:rtype: :class: `NodeImage`
"""
response = self.connection.request("/v4/images/%s" % image, method="GET")
return self._to_image(response.object)

def create_image(self, disk, name=None, description=None):
"""Creates a private image from a LinodeDisk.
Images are limited to three per account.
Expand Down Expand Up @@ -1554,6 +1616,18 @@ def ex_rename_node(self, node, name):

return self._to_node(response)

def _to_key_pair(self, data):
extra = {"id": data["id"]}

return KeyPair(
name=data["label"],
fingerprint=None,
public_key=data["ssh_key"],
private_key=None,
driver=self,
extra=extra,
)

def _to_node(self, data):
extra = {
"tags": data["tags"],
Expand Down
6 changes: 6 additions & 0 deletions libcloud/test/compute/fixtures/linode_v4/create_key_pair.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"created": "2018-01-01T00:01:01",
"id": 42,
"label": "My SSH Key",
"ssh_key": "ssh-rsa AAAA_valid_public_ssh_key_123456785== user@their-computer"
}
13 changes: 13 additions & 0 deletions libcloud/test/compute/fixtures/linode_v4/list_key_pairs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"data": [
{
"created": "2018-01-01T00:01:01",
"id": 42,
"label": "My SSH Key",
"ssh_key": "ssh-rsa AAAA_valid_public_ssh_key_123456785== user@their-computer"
}
],
"page": 1,
"pages": 1,
"results": 1
}
43 changes: 32 additions & 11 deletions libcloud/test/compute/test_linode_v4.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from libcloud.test import MockHttp
from libcloud.utils.py3 import httplib
from libcloud.common.types import LibcloudError, InvalidCredsError
from libcloud.compute.base import Node, NodeImage, NodeState, StorageVolume
from libcloud.compute.base import Node, KeyPair, NodeImage, NodeState, StorageVolume
from libcloud.test.compute import TestCaseMixin
from libcloud.common.linode import LinodeDisk, LinodeIPAddress, LinodeExceptionV4
from libcloud.test.file_fixtures import ComputeFileFixtures
Expand Down Expand Up @@ -64,6 +64,19 @@ def test_list_images(self):
self.assertIsInstance(image.extra["size"], int)
self.assertTrue(image.extra["is_public"])

def test_list_key_pairs(self):
keypairs = self.driver.list_key_pairs()
self.assertIsInstance(keypairs, list)
self.assertEqual(len(keypairs), 1)
self.assertEqual(keypairs[0].extra["id"], 42)

def test_create_key_pair(self):
keypair = self.driver.create_key_pair(
"mykey", public_key="ssh-rsa AAAA_valid_public_ssh_key_123456785== user@their-computer"
)
self.assertIsInstance(keypair, KeyPair)
self.assertEqual(keypair.extra["id"], 42)

def test_list_locations(self):
locations = self.driver.list_locations()
self.assertEqual(len(locations), 10)
Expand All @@ -78,11 +91,11 @@ def test_create_node_response(self):
image = self.driver.list_images()[0]
location = self.driver.list_locations()[0]
node = self.driver.create_node(
location=location,
name="node-name",
location,
"node-name",
size=size,
image=image,
root_pass="test123456",
size=size,
)
self.assertTrue(isinstance(node, Node))

Expand Down Expand Up @@ -114,9 +127,9 @@ def test_create_node(self):

node = self.driver.create_node(
location,
"TestNode",
size,
image=image,
name="TestNode",
root_pass="test123456",
ex_backups_enabled=True,
ex_tags=["testing123"],
Expand All @@ -134,13 +147,13 @@ def test_create_node_no_root_pass(self):
location = self.driver.list_locations()[0]

with self.assertRaises(LinodeExceptionV4):
self.driver.create_node(location, size, image=image, name="TestNode")
self.driver.create_node(location, "TestNode", size, image=image)

def test_create_node_no_image(self):
size = self.driver.list_sizes()[0]
location = self.driver.list_locations()[0]
LinodeMockHttpV4.type = "NO_IMAGE"
node = self.driver.create_node(location, size, name="TestNode", ex_tags=["testing123"])
node = self.driver.create_node(location, "TestNode", size, None, ex_tags=["testing123"])

self.assertIsNone(node.image)
self.assertEqual(node.name, "TestNode")
Expand All @@ -153,13 +166,13 @@ def test_create_node_invalid_name(self):
location = self.driver.list_locations()[0]

with self.assertRaises(LinodeExceptionV4):
self.driver.create_node(location, size, name="Test__Node")
self.driver.create_node(location, "Test__Node", size, None)
with self.assertRaises(LinodeExceptionV4):
self.driver.create_node(location, size, name="Test Node")
self.driver.create_node(location, "Test Node", size, None)
with self.assertRaises(LinodeExceptionV4):
self.driver.create_node(location, size, name="Test--Node")
self.driver.create_node(location, "Test--Node", size, None)
with self.assertRaises(LinodeExceptionV4):
self.driver.create_node(location, size, name="Test..Node")
self.driver.create_node(location, "Test..Node", size, None)

def test_reboot_node(self):
node = Node("22344420", None, None, None, None, driver=self.driver)
Expand Down Expand Up @@ -424,6 +437,14 @@ def test__paginated_request_two_pages(self):
class LinodeMockHttpV4(MockHttp):
fixtures = ComputeFileFixtures("linode_v4")

def _v4_profile_sshkeys(self, method, url, body, headers):
if method == "GET":
body = self.fixtures.load("list_key_pairs.json")
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
if method == "POST":
body = self.fixtures.load("create_key_pair.json")
return (httplib.OK, body, {}, httplib.responses[httplib.OK])

def _v4_regions(self, method, url, body, headers):
body = self.fixtures.load("list_locations.json")
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
Expand Down