From bbe4efc488359167ae77c5b91c31b8902c9d246c Mon Sep 17 00:00:00 2001 From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> Date: Sat, 18 Jul 2026 23:07:38 -0700 Subject: [PATCH 1/2] Fix Azure ARM create_node JSON error with ex_customdata base64.b64encode returns bytes, which is not JSON serializable and raised 'Object of type bytes is not JSON serializable' when ex_customdata was passed to create_node. It also failed outright when ex_customdata was a str. The value is now normalized to bytes before encoding and the base64 result is decoded to a str so the resulting request body is valid JSON. Closes: GITHUB-1893 --- CHANGES.rst | 6 +++ libcloud/compute/drivers/azure_arm.py | 6 ++- libcloud/test/compute/test_azure_arm.py | 51 +++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 0eb55f9543..746658aaeb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -21,6 +21,12 @@ 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] + 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..819e238e0b 100644 --- a/libcloud/compute/drivers/azure_arm.py +++ b/libcloud/compute/drivers/azure_arm.py @@ -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) From d74c50e475f885a268d7abb2cdfe12528ac3697d Mon Sep 17 00:00:00 2001 From: Miguel Caballer Fernandez Date: Mon, 20 Jul 2026 10:01:09 +0200 Subject: [PATCH 2/2] Update ex_customdata docstring type to include bytes --- libcloud/compute/drivers/azure_arm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcloud/compute/drivers/azure_arm.py b/libcloud/compute/drivers/azure_arm.py index 819e238e0b..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