diff --git a/CHANGES.rst b/CHANGES.rst index c9e25e6f3e..bb38bc214e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -21,11 +21,17 @@ Compute (#2135) [Steve Kowalik - @s-t-e-v-e-n-k] +- [Azure ARM] Fix ``create_node`` failing with a JSON serialization error when + ``ex_customdata`` is provided. The base64 encoded custom data is now decoded + to a ``str`` and both ``str`` and ``bytes`` inputs are accepted. + (GITHUB-1893) + [Sanjay Santhanam - @Sanjays2402] + - [UpCloud] Add new functions to complete the Upcloud driver and move it to the last API version 1.3 - (#2152) + (#2147) [Miguel Caballer - @micafer] Changes in Apache Libcloud 3.9.1 diff --git a/libcloud/compute/drivers/azure_arm.py b/libcloud/compute/drivers/azure_arm.py index d540c10820..852c350e5d 100644 --- a/libcloud/compute/drivers/azure_arm.py +++ b/libcloud/compute/drivers/azure_arm.py @@ -609,7 +609,7 @@ def create_node( be placed in the file /var/lib/waagent/CustomData https://azure.microsoft.com/en-us/documentation/ \ articles/virtual-machines-how-to-inject-custom-data/ - :type ex_customdata: ``str`` + :type ex_customdata: ``str`` or ``bytes`` :param ex_use_managed_disks: Enable this feature to have Azure automatically manage the availability of disks to provide data @@ -741,7 +741,11 @@ def create_node( data["properties"]["storageProfile"]["osDisk"].update({"diskSizeGB": ex_disk_size}) if ex_customdata: - data["properties"]["osProfile"]["customData"] = base64.b64encode(ex_customdata) + if isinstance(ex_customdata, str): + ex_customdata = ex_customdata.encode("utf-8") + data["properties"]["osProfile"]["customData"] = base64.b64encode(ex_customdata).decode( + "utf-8" + ) data["properties"]["osProfile"]["adminUsername"] = ex_user_name diff --git a/libcloud/test/compute/test_azure_arm.py b/libcloud/test/compute/test_azure_arm.py index 8efbc3c20a..cd0a255e5a 100644 --- a/libcloud/test/compute/test_azure_arm.py +++ b/libcloud/test/compute/test_azure_arm.py @@ -15,6 +15,7 @@ import sys import json +import base64 import functools from datetime import datetime from unittest import mock @@ -232,6 +233,56 @@ def test_create_node_ex_disk_size(self): }, ) + def test_create_node_ex_customdata(self): + location = NodeLocation("any_location", "", "", self.driver) + size = NodeSize("any_size", "", 0, 0, 0, 0, driver=self.driver) + image = AzureImage("1", "1", "ubuntu", "pub", location.id, self.driver) + auth = NodeAuthPassword("any_password") + + customdata = "#!/bin/bash\necho hello" + expected = base64.b64encode(customdata.encode("utf-8")).decode("utf-8") + + # ex_customdata provided as a str + node = self.driver.create_node( + "test-node-1", + size, + image, + auth, + location=location, + ex_resource_group="000000", + ex_storage_account="000000", + ex_user_name="any_user", + ex_network="000000", + ex_subnet="000000", + ex_use_managed_disks=True, + ex_customdata=customdata, + ) + os_profile = node.extra["properties"]["osProfile"] + self.assertEqual(os_profile["customData"], expected) + # customData must be a JSON-serializable str, not bytes + self.assertIsInstance(os_profile["customData"], str) + json.dumps(node.extra["properties"]) + + # ex_customdata provided as bytes (regression for GH #1893) + node = self.driver.create_node( + "test-node-1", + size, + image, + auth, + location=location, + ex_resource_group="000000", + ex_storage_account="000000", + ex_user_name="any_user", + ex_network="000000", + ex_subnet="000000", + ex_use_managed_disks=True, + ex_customdata=customdata.encode("utf-8"), + ) + os_profile = node.extra["properties"]["osProfile"] + self.assertEqual(os_profile["customData"], expected) + self.assertIsInstance(os_profile["customData"], str) + json.dumps(node.extra["properties"]) + def test_create_node_ex_os_disk_delete(self): location = NodeLocation("any_location", "", "", self.driver) size = NodeSize("any_size", "", 0, 0, 0, 0, driver=self.driver)