netbox icon indicating copy to clipboard operation
netbox copied to clipboard

Config Template Rendering: Option for jinja2.StrictUndefined

Open moonrail opened this issue 8 months ago • 7 comments

NetBox version

v4.1.11 (code is untouched since 2023 and still active in v4.2.4)

Feature type

Change to existing functionality

Proposed functionality

tl;dr: Being able to make Jinja2 Template variables StrictUndefined instead of default Undefined, to error out instead of silently rendering an invalid config.

Currently Config Templates for Devices are Jinja2 Templates. On a Config Template Model the User can configure "Environment Parameters" in JSON format. These are passed as kwargs to the Jinja2 Renderer: https://github.com/netbox-community/netbox/blob/v4.2.4/netbox/extras/models/configs.py#L302

However Jinja2 Environment Parameters are not completely compatible to JSON Format, as they're Python. This leads to one of the most important settings not being usable: undefined

See https://jinja.palletsprojects.com/en/stable/api/#jinja2.Environment

  • default: https://jinja.palletsprojects.com/en/stable/api/#jinja2.Undefined
  • alternative: https://jinja.palletsprojects.com/en/stable/api/#jinja2.StrictUndefined

As both are python classes, one would have to properly pass that python class to the environment, which is not possible in JSON format.

To enable the User to set jinja2.StrictUndefined there could be

  • a global setting
    • which would be enough, as Jinja2 has the default filter to work with undefined variables
  • a specifically JSON-parsed & Python-importlib handled Environment Parameter
  • a dedicated attribute on an Config Template

UI can already deal with this, however API cannot, as it tries to provide a nice error message relying on a missing attribute: Image

Use case

One could use NetBox own documentation on the feature as an example: https://netbox.readthedocs.io/en/stable/features/configuration-rendering/#configuration-templates

  • if a device name is None
  • if ntp_servers is undefined

The config would look like:

    system {
        host-name ;
        domain-name example.com;
        time-zone UTC;
        authentication-order [ password radius ];
        ntp {
            
        }
    }

On a hypothetical network device the ntp section would probably not cause an error while an empty host-name would. This is not cleanly predictable.

My Use Case is: I want to know if things are broken and not having to debug the rendered configuration that may well be accepted by the network device, applied and cause an outage.

If a var is missing, it should not render a configuration.

An incomplete workaround is to always test with an {% if x is defined %}...{% endif %} which is very verbose and still does not provide any error-functionality. The rendered configuration will still seem ok unless any parser/the target network device rejects it.

Database changes

No response

External dependencies

No response

moonrail avatar Mar 04 '25 08:03 moonrail

To enable the User to set jinja2.StrictUndefined there could be

How about accepting it as a dotted path in the config, e.g. "jinja2.StrictUndefined"?

jeremystretch avatar Mar 04 '25 13:03 jeremystretch

To enable the User to set jinja2.StrictUndefined there could be

How about accepting it as a dotted path in the config, e.g. "jinja2.StrictUndefined"?

I am not sure what you mean by that - a dotted path as string in which config? Edit: Do you mean a new NetBox setting like JINJA2_UNDEFINED_CLASS that can be used to configure either the class itself or a dotted path as string which is imported later?

moonrail avatar Mar 04 '25 13:03 moonrail

I mean referencing the desired class by its path, so that the config can be stored as JSON. For example:

{
    "trim_blocks": true,
    "undefined": "jinja2.StrictUndefined"
}

jeremystretch avatar Mar 04 '25 15:03 jeremystretch

Aaah, thats what I meant by

a specifically JSON-parsed & Python-importlib handled Environment Parameter

It cannot be passed as string (or other not-python-class-type) to the Jinja2 environment, this I have tested before opening this issue of course.

Passing as string results in: Image

moonrail avatar Mar 04 '25 15:03 moonrail

Right, I mean we'd have to account for it by parsing the string and replacing it with the imported object. But IMO that would be the cleanest approach.

jeremystretch avatar Mar 04 '25 15:03 jeremystretch

FYI I solved this using a custom Jina2 filter:

from typing import Any

from jinja2 import UndefinedError


def check_required(input: Any = None, message: str = None) -> Any:
    """
    Raises an exception, if `input` is not defined or evaluates to `False`.


    :param input: The string variable to check.
    :param message: Error message if `input` is undefined.

    :raises UndefinedError: The value of `input` is either `None` or evaluates
        to `False`.
    """
    if not input:
        raise UndefinedError(message)
    return input


JINJA2_FILTERS = {
    "required": check_required,
}
{{ foo | required('Foo is very important') }}

Having something built into NetBox would be even better to reduce maintenance. ;)

alehaa avatar Mar 04 '25 20:03 alehaa

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. NetBox is governed by a small group of core maintainers which means not all opened issues may receive direct feedback. Do not attempt to circumvent this process by "bumping" the issue; doing so will result in its immediate closure and you may be barred from participating in any future discussions. Please see our contributing guide.

github-actions[bot] avatar Jun 12 '25 04:06 github-actions[bot]