Support Ignition for userdata
Fedora CoreOS will replace Fedora Atomic being the next generation container OS. So it would be nice to support Fedora CoreOS in Heat. In Fedora CoreOS, the cloud-init will be replaced with Ignition[1], so the changes proposed in this patch are mainly focusing on how to support Ignition when using Heat SOFTWARE_CONFIG with Ignition. Task: 36671 Story: 2006566 Change-Id: I11df2431634de7d8b584b1a2ac733d43959e34fc
This commit is contained in:
parent
bb44f779fb
commit
f6249c0110
@ -308,7 +308,7 @@ class NovaClientPlugin(microversion_mixin.MicroversionMixin,
|
||||
|
||||
def build_userdata(self, metadata, userdata=None, instance_user=None,
|
||||
user_data_format='HEAT_CFNTOOLS'):
|
||||
"""Build multipart data blob for CloudInit.
|
||||
"""Build multipart data blob for CloudInit and Ignition.
|
||||
|
||||
Data blob includes user-supplied Metadata, user data, and the required
|
||||
Heat in-instance configuration.
|
||||
@ -330,6 +330,10 @@ class NovaClientPlugin(microversion_mixin.MicroversionMixin,
|
||||
is_cfntools = user_data_format == 'HEAT_CFNTOOLS'
|
||||
is_software_config = user_data_format == 'SOFTWARE_CONFIG'
|
||||
|
||||
if (is_software_config and
|
||||
NovaClientPlugin.is_ignition_format(userdata)):
|
||||
return NovaClientPlugin.build_ignition_data(metadata, userdata)
|
||||
|
||||
def make_subpart(content, filename, subtype=None):
|
||||
if subtype is None:
|
||||
subtype = os.path.splitext(filename)[0]
|
||||
@ -428,6 +432,46 @@ echo -e '%s\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers
|
||||
|
||||
return mime_blob.as_string()
|
||||
|
||||
@staticmethod
|
||||
def is_ignition_format(userdata):
|
||||
try:
|
||||
payload = jsonutils.loads(userdata)
|
||||
ig = payload.get("ignition")
|
||||
return True if ig and ig.get("version") else False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def build_ignition_data(metadata, userdata):
|
||||
if not metadata:
|
||||
return userdata
|
||||
|
||||
payload = jsonutils.loads(userdata)
|
||||
encoded_metadata = urlparse.quote(jsonutils.dumps(metadata))
|
||||
cfn_init_data = {
|
||||
"filesystem": "root",
|
||||
"group": {"name": "root"},
|
||||
"path": "/var/lib/os-collect-config/local-data",
|
||||
"user": {"name": "root"},
|
||||
"contents": {
|
||||
"source": "data:," + encoded_metadata,
|
||||
"verification": {}},
|
||||
"mode": 0o640
|
||||
}
|
||||
|
||||
storage = payload.setdefault('storage', {})
|
||||
try:
|
||||
files = storage.setdefault('files', [])
|
||||
except AttributeError:
|
||||
raise ValueError('Ignition "storage" section must be a map')
|
||||
else:
|
||||
try:
|
||||
files.append(cfn_init_data)
|
||||
except AttributeError:
|
||||
raise ValueError('Ignition "files" section must be a list')
|
||||
|
||||
return jsonutils.dumps(payload)
|
||||
|
||||
def check_delete_server_complete(self, server_id):
|
||||
"""Wait for server to disappear from Nova."""
|
||||
try:
|
||||
|
@ -520,7 +520,9 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
|
||||
'the user_data is passed to Nova unmodified. '
|
||||
'For SOFTWARE_CONFIG user_data is bundled as part of the '
|
||||
'software config data, and metadata is derived from any '
|
||||
'associated SoftwareDeployment resources.'),
|
||||
'associated SoftwareDeployment resources. And if the '
|
||||
'user_data is in CoreOS ignition(json) format, the metadata '
|
||||
'will be injected into the user_data automatically by Heat.'),
|
||||
default=cfg.CONF.default_user_data_format,
|
||||
constraints=[
|
||||
constraints.AllowedValues(_SOFTWARE_CONFIG_FORMATS),
|
||||
@ -556,9 +558,10 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
|
||||
),
|
||||
USER_DATA: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('User data script to be executed by cloud-init. Changes cause '
|
||||
'replacement of the resource by default, but can be ignored '
|
||||
'altogether by setting the `user_data_update_policy` property.'),
|
||||
_('User data script to be executed by cloud-init or CoreOS '
|
||||
'ignition. Changes cause replacement of the resource '
|
||||
'by default, but can be ignored altogether by setting the '
|
||||
'`user_data_update_policy` property.'),
|
||||
default='',
|
||||
update_allowed=True
|
||||
),
|
||||
|
@ -394,6 +394,32 @@ class NovaClientPluginUserdataTest(NovaClientPluginTestCase):
|
||||
self.assertIn('useradd', data)
|
||||
self.assertIn('ec2-user', data)
|
||||
|
||||
def test_build_userdata_with_ignition(self):
|
||||
metadata = {"os-collect-config": {"heat": {"password": "***"}}}
|
||||
userdata = '{"ignition": {"version": "3.0"}, "storage": {"files": []}}'
|
||||
ud_format = 'SOFTWARE_CONFIG'
|
||||
data = self.nova_plugin.build_userdata(metadata,
|
||||
userdata=userdata,
|
||||
user_data_format=ud_format)
|
||||
ig = json.loads(data)
|
||||
self.assertEqual("/var/lib/os-collect-config/local-data",
|
||||
ig["storage"]["files"][0]["path"])
|
||||
self.assertEqual("data:,%7B%22os-collect-config%22%3A%20%7B%22heat"
|
||||
"%22%3A%20%7B%22password%22%3A%20%22%2A%2A%2A%22"
|
||||
"%7D%7D%7D",
|
||||
ig["storage"]["files"][0]["contents"]["source"])
|
||||
|
||||
def test_build_userdata_with_invalid_ignition(self):
|
||||
metadata = {"os-collect-config": {"heat": {"password": "***"}}}
|
||||
userdata = '{"ignition": {"version": "3.0"}, "storage": []}'
|
||||
ud_format = 'SOFTWARE_CONFIG'
|
||||
|
||||
self.assertRaises(ValueError,
|
||||
self.nova_plugin.build_userdata,
|
||||
metadata,
|
||||
userdata=userdata,
|
||||
user_data_format=ud_format)
|
||||
|
||||
|
||||
class NovaClientPluginMetadataTest(NovaClientPluginTestCase):
|
||||
|
||||
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Heat can now support software deployments with CoreOS by passing
|
||||
a CoreOS Ignition config in the ``user_data`` property for an
|
||||
``OS::Nova::Server`` resource when the ``user_data_format`` is
|
||||
set to ``SOFTWARE_CONFIG``.
|
Loading…
Reference in New Issue
Block a user