Add metalsmith_instances instance option config_drive

This has sub-options ``cloud_config` and ``meta_data`` and allows
customization of data packaged in the config-drive to be consumed by
cloud-init.

This is needed by TripleO to provide an equivalent to the current
heat/nova based firstboot customization support.

Change-Id: Ie384292a3310cb06e908dd9027e9300ca108e7dd
This commit is contained in:
Steve Baker 2020-11-09 14:58:04 +13:00
parent 1c06881c32
commit 22a4e4071c
7 changed files with 86 additions and 18 deletions

View File

@ -35,11 +35,16 @@ class GenericConfig(object):
:ivar ssh_keys: List of SSH public keys.
:ivar user_data: User data as a string.
:ivar meta_data: Dict of data to add to the generated ``meta_data``
"""
def __init__(self, ssh_keys=None, user_data=None):
def __init__(self, ssh_keys=None, user_data=None, meta_data=None):
self.ssh_keys = ssh_keys or []
self.user_data = user_data
if meta_data and not isinstance(meta_data, dict):
raise TypeError('Custom meta_data must be a dictionary, '
'got %r' % meta_data)
self.meta_data = meta_data
def generate(self, node, hostname=None):
"""Generate the config drive information.
@ -62,17 +67,24 @@ class GenericConfig(object):
else:
ssh_keys = self.ssh_keys
metadata = {'public_keys': ssh_keys,
'uuid': node.id,
'name': node.name,
'hostname': hostname,
'launch_index': 0,
'availability_zone': '',
'files': [],
'meta': {}}
meta_data = {}
if self.meta_data:
meta_data.update(self.meta_data)
meta_data.update({
'public_keys': ssh_keys,
'uuid': node.id,
'name': node.name,
'hostname': hostname
})
meta_data.setdefault('launch_index', 0)
meta_data.setdefault('availability_zone', '')
meta_data.setdefault('files', [])
meta_data.setdefault('meta', {})
user_data = self.populate_user_data()
return {'meta_data': metadata,
return {'meta_data': meta_data,
'user_data': user_data}
def populate_user_data(self):
@ -91,15 +103,16 @@ class CloudInitConfig(GenericConfig):
Compared to :class:`GenericConfig`, this adds support for managing users.
:ivar ssh_keys: List of SSH public keys.
:ivar user_data: Cloud-init script as a dictionary.
:ivar users: Users to add on first boot.
:ivar user_data: Cloud-init cloud-config data as a dictionary.
:ivar meta_data: Dict of data to add to the generated ``meta_data``
"""
def __init__(self, ssh_keys=None, user_data=None):
def __init__(self, ssh_keys=None, user_data=None, meta_data=None):
if user_data is not None and not isinstance(user_data, dict):
raise TypeError('Custom user data must be a dictionary for '
'CloudInitConfig, got %r' % user_data)
super(CloudInitConfig, self).__init__(ssh_keys, user_data or {})
super(CloudInitConfig, self).__init__(ssh_keys, user_data or {},
meta_data=meta_data)
self.users = []
def add_user(self, name, admin=True, password_hash=None, sudo=False,

View File

@ -78,6 +78,13 @@ class TestGenericConfig(unittest.TestCase):
config = self.CLASS(user_data='{"answer": 42}')
self._check(config, {}, {"answer": 42}, cloud_init=False)
def test_custom_metadata(self):
config = self.CLASS(meta_data={"foo": "bar"})
self._check(config, {"foo": "bar"}, cloud_init=False)
def test_custom_metadata_not_dict(self):
self.assertRaises(TypeError, self.CLASS, meta_data="foobar")
class TestCloudInitConfig(TestGenericConfig):
CLASS = instance_config.CloudInitConfig

View File

@ -126,7 +126,11 @@ class TestMetalsmithInstances(unittest.TestCase):
'netboot': True,
'ssh_public_keys': 'abcd',
'user_name': 'centos',
'passwordless_sudo': False
'passwordless_sudo': False,
'config_drive': {
'meta_data': {'foo': 'bar'},
'cloud_config': {'bootcmd': ['echo henlo world']}
}
}, {
'name': 'node-3',
'hostname': 'overcloud-controller-3',
@ -191,8 +195,10 @@ class TestMetalsmithInstances(unittest.TestCase):
),
])
mock_config.assert_has_calls([
mock.call(ssh_keys=None),
mock.call(ssh_keys='abcd'),
mock.call(ssh_keys=None, user_data=None, meta_data=None),
mock.call(ssh_keys='abcd',
user_data={'bootcmd': ['echo henlo world']},
meta_data={'foo': 'bar'})
])
config.add_user.assert_called_once_with(
'centos', admin=True, sudo=False)

View File

@ -73,6 +73,7 @@ def transform(module, instances, defaults):
value(src, 'image_checksum', image, 'checksum')
value(src, 'image_kernel', image, 'kernel')
value(src, 'image_ramdisk', image, 'ramdisk')
value(src, 'config_drive', dest)
# keys in metalsmith_instances not currently in metalsmith_deployment:
# passwordless_sudo

View File

@ -151,6 +151,23 @@ options:
- Allow password-less sudo for the user
default: yes
type: bool
config_drive:
description:
- Extra data to add to the boot config-drive cloud-init
type: dict
suboptions:
cloud_config:
description:
- Dict of cloud-init cloud-config data for tasks to run on node
boot. The 'users' directive can be used to configure extra
users other than the 'user_name' admin user.
type: dict
meta_data:
description:
- Extra metadata to include with the config-drive cloud-init
metadata. This will be added to the generated metadata
'public_keys', 'uuid', 'name', and 'hostname'.
type: dict
clean_up:
description:
- Clean up resources on failure
@ -330,7 +347,12 @@ def _provision_instance(provisioner, instance, nodes, timeout, wait):
image = _get_source(instance)
ssh_keys = instance.get('ssh_public_keys')
config = instance_config.CloudInitConfig(ssh_keys=ssh_keys)
config_drive = instance.get('config_drive', {})
cloud_config = config_drive.get('cloud_config')
meta_data = config_drive.get('meta_data')
config = instance_config.CloudInitConfig(ssh_keys=ssh_keys,
user_data=cloud_config,
meta_data=meta_data)
if instance.get('user_name'):
config.add_user(instance.get('user_name'), admin=True,
sudo=instance.get('passwordless_sudo', True))

View File

@ -60,6 +60,18 @@ Each instances has the following attributes:
list of nodes (UUIDs or names) to be considered for deployment.
``capabilities`` (defaults to ``metalsmith_capabilities``)
node capabilities to request when scheduling.
``config_drive``
extra data to add to the boot config-drive cloud-init:
``cloud_config``
Dict of cloud-init cloud-config data for tasks to run on node
boot. The 'users' directive can be used to configure extra
users other than the 'user_name' admin user.
``meta_data``
Extra metadata to include with the config-drive cloud-init
metadata. This will be added to the generated metadata
``public_keys``, ``uuid``, ``name``, and ``hostname``.
``conductor_group`` (defaults to ``metalsmith_conductor_group``)
conductor group to pick nodes from.

View File

@ -0,0 +1,7 @@
---
features:
- |
Add new metalsmith_instances instance option ``config_drive``.
This has sub-options ``cloud_config` and ``meta_data`` and allows
customization of data packaged in the config-drive to be consumed by
cloud-init.