From 725d2e00af7df0f85ccf89a3c1c1d284232709d7 Mon Sep 17 00:00:00 2001 From: Michael Galaxy Date: Mon, 7 Aug 2023 11:53:05 -0400 Subject: [PATCH] Updated Linode (Akamai Connected Cloud) support (including cloud-init) 1. cloud-init support is a new feature available in 2023. 2. The main entry point (create_node) had an arrangement of non-keyword parameters that were not consistent with other libcloud drivers. This has been fixed. 3. One remaining function (already available in the API) was exposed to also be consistent with other drivers. --- libcloud/compute/drivers/linode.py | 80 ++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/libcloud/compute/drivers/linode.py b/libcloud/compute/drivers/linode.py index bd213935d0..9f3e796523 100644 --- a/libcloud/compute/drivers/linode.py +++ b/libcloud/compute/drivers/linode.py @@ -41,6 +41,7 @@ NodeImage, NodeDriver, NodeLocation, + KeyPair, StorageVolume, NodeAuthSSHKey, NodeAuthPassword, @@ -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 @@ -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. + # 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, @@ -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 + 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` """ @@ -1014,6 +1061,9 @@ def create_node( "backups_enabled": ex_backups_enabled, } + if ex_userdata : + attr["metadata"] = {"user_data" : ex_userdata} + if image is not None: if root_pass is None: raise LinodeExceptionV4("root password required " "when providing an image") @@ -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. @@ -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"],