netbox
netbox copied to clipboard
Config Template Rendering: Option for jinja2.StrictUndefined
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
defaultfilter to work with undefined variables
- which would be enough, as Jinja2 has the
- 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:
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
To enable the User to set
jinja2.StrictUndefinedthere could be
How about accepting it as a dotted path in the config, e.g. "jinja2.StrictUndefined"?
To enable the User to set
jinja2.StrictUndefinedthere could beHow 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?
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"
}
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:
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.
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. ;)
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.