Config Template Rendering: Option for jinja2.StrictUndefined #10839

Closed
opened 2025-12-29 21:36:31 +01:00 by adam · 7 comments
Owner

Originally created by @moonrail on GitHub (Mar 4, 2025).

Originally assigned to: @jeremystretch on GitHub.

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

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

Originally created by @moonrail on GitHub (Mar 4, 2025). Originally assigned to: @jeremystretch on GitHub. ### 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](https://github.com/user-attachments/assets/cb85cb20-11f8-47ad-84d0-b9610709bf0f) ### 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_
adam added the status: acceptedtype: featurecomplexity: mediumtopic: templating labels 2025-12-29 21:36:31 +01:00
adam closed this issue 2025-12-29 21:36:32 +01:00
Author
Owner

@jeremystretch commented on GitHub (Mar 4, 2025):

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 commented on GitHub (Mar 4, 2025): > 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"`?
Author
Owner

@moonrail commented on GitHub (Mar 4, 2025):

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 commented on GitHub (Mar 4, 2025): > > 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?
Author
Owner

@jeremystretch commented on GitHub (Mar 4, 2025):

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 commented on GitHub (Mar 4, 2025): I mean referencing the desired class by its path, so that the config can be stored as JSON. For example: ```json { "trim_blocks": true, "undefined": "jinja2.StrictUndefined" } ```
Author
Owner

@moonrail commented on GitHub (Mar 4, 2025):

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 commented on GitHub (Mar 4, 2025): 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](https://github.com/user-attachments/assets/793b9c54-3555-497e-9320-725d01d6d38e)
Author
Owner

@jeremystretch commented on GitHub (Mar 4, 2025):

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 commented on GitHub (Mar 4, 2025): 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.
Author
Owner

@alehaa commented on GitHub (Mar 4, 2025):

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 commented on GitHub (Mar 4, 2025): FYI I solved this using a custom Jina2 filter: ```Python 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, } ``` ```Jinja {{ foo | required('Foo is very important') }} ``` Having something built into NetBox would be even better to reduce maintenance. ;)
Author
Owner

@github-actions[bot] commented on GitHub (Jun 12, 2025):

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] commented on GitHub (Jun 12, 2025): 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](https://github.com/netbox-community/netbox/blob/main/CONTRIBUTING.md).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#10839