From fe5a68e43f106290b956318e1e9dd4a07b595b56 Mon Sep 17 00:00:00 2001 From: mattia-eleuteri Date: Fri, 5 Jun 2026 12:37:40 +0200 Subject: [PATCH] nocloud: accept dict-of-strings public-keys format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some sources serialise public-keys as a dict of {name: key-string} rather than {name: {'openssh-key': key-string}}. KubeVirt's accessCredentials sshPublicKey noCloud propagation does this, defining NoCloudMetadata.PublicSSHKeys as map[string]string [1] and assigning it directly in readCloudInitNoCloudMetaData [2]. The current third branch of get_public_keys() then calls .get('openssh-key') on a string value and raises "'str' object has no attribute 'get'". Accept both shapes. [1] https://github.com/kubevirt/kubevirt/blob/623bf15e7553875860ece1a382a9e35bfbd61a26/pkg/cloud-init/cloud-init.go#L84 [2] https://github.com/kubevirt/kubevirt/blob/623bf15e7553875860ece1a382a9e35bfbd61a26/pkg/cloud-init/cloud-init.go#L402-L408 AI-assisted contribution: I reviewed the change and submit it under the DCO terms at https://github.com/cloudbase/cloudbase-init/blob/master/DCO#L16 (clause (a) — the contribution is mine). Signed-off-by: mattia-eleuteri --- cloudbaseinit/metadata/services/nocloudservice.py | 8 +++++++- .../tests/metadata/services/test_nocloudservice.py | 13 +++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/cloudbaseinit/metadata/services/nocloudservice.py b/cloudbaseinit/metadata/services/nocloudservice.py index 70190d01..78af542d 100644 --- a/cloudbaseinit/metadata/services/nocloudservice.py +++ b/cloudbaseinit/metadata/services/nocloudservice.py @@ -695,7 +695,13 @@ def get_public_keys(self): if isinstance(raw_ssh_keys, list): return raw_ssh_keys - return [raw_ssh_keys[key].get('openssh-key') for key in raw_ssh_keys] + keys = [] + for value in raw_ssh_keys.values(): + if isinstance(value, str): + keys.append(value) + else: + keys.append(value.get('openssh-key')) + return keys def get_network_details(self): debian_net_config = self._get_meta_data().get('network-interfaces') diff --git a/cloudbaseinit/tests/metadata/services/test_nocloudservice.py b/cloudbaseinit/tests/metadata/services/test_nocloudservice.py index d3266116..00a2d1c0 100644 --- a/cloudbaseinit/tests/metadata/services/test_nocloudservice.py +++ b/cloudbaseinit/tests/metadata/services/test_nocloudservice.py @@ -516,6 +516,19 @@ def test_get_public_keys_alt_fmt(self, mock_get_metadata): result = self._config_drive.get_public_keys() self.assertEqual(result, expected_result) + @mock.patch(MODULE_PATH + '.NoCloudConfigDriveService._get_meta_data') + def test_get_public_keys_dict_of_strings(self, mock_get_metadata): + fake_key0 = 'ssh-rsa AAAA0 user0@host' + fake_key1 = 'ssh-rsa AAAA1 user1@host' + mock_get_metadata.return_value = { + 'public-keys': { + 'key0': fake_key0, + 'key1': fake_key1, + } + } + result = sorted(self._config_drive.get_public_keys()) + self.assertEqual(result, sorted([fake_key0, fake_key1])) + @ddt.data(('', ('V2 network metadata is empty', None)), ('1', ('V2 network metadata is not a dictionary', None)), ('{}', ('V2 network metadata is empty', None)),