diff options
Diffstat (limited to 'ironic/common')
-rw-r--r-- | ironic/common/exception.py | 8 | ||||
-rw-r--r-- | ironic/common/pxe_utils.py | 63 | ||||
-rw-r--r-- | ironic/common/utils.py | 13 |
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) |