From 551c4eb318c8d38cb1c7700d77cf4812885503c2 Mon Sep 17 00:00:00 2001 From: Adrian Vladu Date: Thu, 12 Dec 2019 13:18:49 +0200 Subject: [PATCH] Add metadata interface for instance data Some plugins, like the userdata execution plugin, need a standard model for the instance data, which can be applied by templating engines like jinja to the userdata. This way, a user can use this feature to customize her userdata scripts with values that are specific to that instance runtime env or cloud platform. The instance data structure is based on the cloud-init specifications: https://cloudinit.readthedocs.io/en/latest/topics/instancedata.html The v1 namespace contains a subset of the cloud-init specs for the instance data. The ds.meta_data namespace contains all the values the v1 namespace contains, in order to be compatible with cloud-init, plus a subset of other instance data. Change-Id: I5c529498f06fe3c86f7fa3c20fdf3091840c4041 --- cloudbaseinit/metadata/services/base.py | 40 +++++++++++++++++++ .../tests/metadata/services/test_base.py | 32 +++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/cloudbaseinit/metadata/services/base.py b/cloudbaseinit/metadata/services/base.py index 8bf31e8c..8f80ec85 100644 --- a/cloudbaseinit/metadata/services/base.py +++ b/cloudbaseinit/metadata/services/base.py @@ -14,6 +14,7 @@ import abc +import copy import gzip import io import time @@ -221,6 +222,45 @@ class BaseMetadataService(object): def get_ephemeral_disk_data_loss_warning(self): raise NotExistingMetadataException() + def get_instance_data(self): + """Returns a dictionary with instance data from the metadata source + + The instance data structure is based on the cloud-init specifications: + https://cloudinit.readthedocs.io/en/latest/topics/instancedata.html + + The v1 namespace contains a subset of the cloud-init standard + for the instance data. In the future, it should reach parity with the + cloud-init standard. + + The ds.meta_data namespace contains all the values the v1 namespace + contains, in order to be compatible with cloud-init, plus a subset of + other instance data. + The ds namespace can change without prior notice and should not be + used in production. + """ + + instance_id = self.get_instance_id() + hostname = self.get_host_name() + + v1_data = { + "instance_id": instance_id, + "local_hostname": hostname, + "public_ssh_keys": self.get_public_keys() + } + + # Copy the v1 data to the ds.meta_data and add more fields + ds_meta_data = copy.deepcopy(v1_data) + ds_meta_data.update({ + "hostname": hostname + }) + + return { + "v1": v1_data, + "ds": { + "meta_data": ds_meta_data, + } + } + class BaseHTTPMetadataService(BaseMetadataService): diff --git a/cloudbaseinit/tests/metadata/services/test_base.py b/cloudbaseinit/tests/metadata/services/test_base.py index ad47e87b..d8c77653 100644 --- a/cloudbaseinit/tests/metadata/services/test_base.py +++ b/cloudbaseinit/tests/metadata/services/test_base.py @@ -61,6 +61,38 @@ class TestBase(unittest.TestCase): result = self._service.get_user_pwd_encryption_key() self.assertEqual(result, mock_get_public_keys.return_value[0]) + @mock.patch('cloudbaseinit.metadata.services.base.' + 'BaseMetadataService.get_public_keys') + @mock.patch('cloudbaseinit.metadata.services.base.' + 'BaseMetadataService.get_host_name') + @mock.patch('cloudbaseinit.metadata.services.base.' + 'BaseMetadataService.get_instance_id') + def test_get_instance_data(self, mock_instance_id, mock_hostname, + mock_public_keys): + fake_instance_id = 'id' + mock_instance_id.return_value = fake_instance_id + fake_hostname = 'host' + mock_hostname.return_value = fake_hostname + fake_keys = ['ssh1', 'ssh2'] + mock_public_keys.return_value = fake_keys + + expected_response = { + 'v1': { + "instance_id": fake_instance_id, + "local_hostname": fake_hostname, + "public_ssh_keys": fake_keys + }, + 'ds': { + 'meta_data': { + "instance_id": fake_instance_id, + "local_hostname": fake_hostname, + "public_ssh_keys": fake_keys, + "hostname": fake_hostname + }, + } + } + self.assertEqual(expected_response, self._service.get_instance_data()) + class TestBaseHTTPMetadataService(unittest.TestCase):