summaryrefslogtreecommitdiff
path: root/ironic/common
diff options
context:
space:
mode:
Diffstat (limited to 'ironic/common')
-rw-r--r--ironic/common/exception.py8
-rw-r--r--ironic/common/pxe_utils.py63
-rw-r--r--ironic/common/utils.py13
3 files changed, 80 insertions, 4 deletions
diff --git a/ironic/common/exception.py b/ironic/common/exception.py
index 4d1eb582c..b0666748e 100644
--- a/ironic/common/exception.py
+++ b/ironic/common/exception.py
@@ -711,6 +711,14 @@ class InvalidDeployTemplate(Invalid):
_msg_fmt = _("Deploy template invalid: %(err)s.")
+class InvalidKickstartTemplate(Invalid):
+ _msg_fmt = _("The kickstart template is missing required variables")
+
+
+class InvalidKickstartFile(Invalid):
+ _msg_fmt = _("The kickstart file is not valid.")
+
+
class IBMCError(DriverOperationError):
_msg_fmt = _("IBMC exception occurred on node %(node)s. Error: %(error)s")
diff --git a/ironic/common/pxe_utils.py b/ironic/common/pxe_utils.py
index 98346d650..5c5d771bb 100644
--- a/ironic/common/pxe_utils.py
+++ b/ironic/common/pxe_utils.py
@@ -16,8 +16,11 @@
import copy
import os
+import tempfile
from ironic_lib import utils as ironic_utils
+import jinja2
+from oslo_concurrency import processutils
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import fileutils
@@ -1064,6 +1067,66 @@ def validate_boot_parameters_for_trusted_boot(node):
raise exception.InvalidParameterValue(msg)
+def validate_kickstart_template(ks_template):
+ """Validate the kickstart template
+
+ :param ks_template: Path to the kickstart template
+ :raises: InvalidKickstartTemplate
+ """
+ ks_options = {'liveimg_url': 'fake_image_url',
+ 'agent_token': 'fake_token',
+ 'heartbeat_url': 'fake_heartbeat_url'}
+ params = {'ks_options': ks_options}
+ try:
+ rendered_tmpl = utils.render_template(ks_template, params, strict=True)
+ except jinja2.exceptions.UndefinedError as exc:
+ msg = (_("The kickstart template includes a variable that is not "
+ "a valid kickstart option. Rendering the template returned "
+ " %(msg)s. The valid options are %(valid_options)s.") %
+ {'msg': exc.message,
+ 'valid_options': ','.join(ks_options.keys())})
+ raise exception.InvalidKickstartTemplate(msg)
+
+ missing_required_options = []
+ for var, value in ks_options.items():
+ if rendered_tmpl.find(value) == -1:
+ missing_required_options.append(var)
+ if missing_required_options:
+ msg = (_("Following required kickstart option variables are missing "
+ "from the kickstart template: %(missing_opts)s.") %
+ {'missing_opts': ','.join(missing_required_options)})
+ raise exception.InvalidKickstartTemplate(msg)
+ return rendered_tmpl
+
+
+def validate_kickstart_file(ks_cfg):
+ """Check if the kickstart file is valid
+
+ :param ks_cfg: Contents of kickstart file to validate
+ :raises: InvalidKickstartFile
+ """
+ if not os.path.isfile('/usr/bin/ksvalidator'):
+ LOG.warning(
+ "Unable to validate the kickstart file as ksvalidator binary is "
+ "missing. Please install pykickstart package to enable "
+ "validation of kickstart file."
+ )
+ return
+
+ with tempfile.NamedTemporaryFile(
+ dir=CONF.tempdir, suffix='.cfg') as ks_file:
+ ks_file.writelines(ks_cfg)
+ try:
+ result = utils.execute(
+ 'ksvalidator', ks_file.name, check_on_exit=[0], attempts=1
+ )
+ except processutils.ProcessExecutionError:
+ msg = _(("The kickstart file generated does not pass validation. "
+ "The ksvalidator tool returned following error(s): %s") %
+ (result))
+ raise exception.InvalidKickstartFile(msg)
+
+
def prepare_instance_pxe_config(task, image_info,
iscsi_boot=False,
ramdisk_boot=False,
diff --git a/ironic/common/utils.py b/ironic/common/utils.py
index ce482f2cc..390fe0305 100644
--- a/ironic/common/utils.py
+++ b/ironic/common/utils.py
@@ -468,13 +468,15 @@ def validate_network_port(port, port_name="Port"):
{'port_name': port_name, 'port': port})
-def render_template(template, params, is_file=True):
+def render_template(template, params, is_file=True, strict=False):
"""Renders Jinja2 template file with given parameters.
:param template: full path to the Jinja2 template file
:param params: dictionary with parameters to use when rendering
:param is_file: whether template is file or string with template itself
- :returns: the rendered template as a string
+ :param strict: Enable strict template rendering. Default is False
+ :returns: Rendered template
+ :raises: jinja2.exceptions.UndefinedError
"""
if is_file:
tmpl_path, tmpl_name = os.path.split(template)
@@ -486,8 +488,11 @@ def render_template(template, params, is_file=True):
# and still complains with B701 for that line
# NOTE(pas-ha) not using default_for_string=False as we set the name
# of the template above for strings too.
- env = jinja2.Environment(loader=loader, # nosec B701
- autoescape=jinja2.select_autoescape())
+ env = jinja2.Environment(
+ loader=loader,
+ autoescape=jinja2.select_autoescape(), # nosec B701
+ undefined=jinja2.StrictUndefined if strict else jinja2.Undefined
+ )
tmpl = env.get_template(tmpl_name)
return tmpl.render(params, enumerate=enumerate)