Closed jeremystretch closed 1 year ago
@jeremystretch I think it would be nice to have a "composable" environment for configuration templates, for example:
when deploying a device you can have:
That way having several base templates, and a configuration template that is a composable of the bases may be useful
Another point is how to add variables inside that template, in such way it could be used to render some configuration to deploy a new device or inside a script.
@carlbuchmann, @titom73, @ClausHolbechArista
There might be some good synergies here with ansible avd if you have thoughts.
@renatoalmeidaoliveira my intent here is to determine per device the appropriate base template, which can pull in other templates via {% include %}
tags. These inclusion templates can be reused by many templates. This approach has worked very well for me in the past, and mimics NetBox's own template hierarchy.
Intresting, very similar to the templates environment, that way the user gonna have several "base" templates and for a full configuration he build a template import the base ones. It seens interesting, but how you gonna import "as a template" some model attribute?
So far, I've got the template rendering working as planned, however I'm getting a little hung up on deciding how best to relate devices to specific templates. To review, each device should reference exactly one (if any) configuration template, which can be rendered using context data to produce the desired configuration of that device.
It's entirely possible to designate a single global base template for all devices, and employ logic to evaluate a device's role, platform, etc. to determine which other relevant template to pull in (via {% include %}
tags). However, this is probably not ideal, and I expect that people will want some mechanism native to NetBox for selecting which of several base templates to use for a device. I see a few options, detailed below.
template
ForeignKey to DeviceThis is pretty straightforward: Just add a ForeignKey to the Device model so that each device can point to its appropriate ConfigTemplate. This allows for very efficient queries at the cost of requiring explicit assignment for each device. It's also the safest option, as a misconfiguration will affect only the single device on which it's made.
template
ForeignKey to DeviceRole and/or PlatformRather than specifying templates per device, we could specify them per device role and/or platform. This would require much less explicit configuration than option A, however we run into an issue if a device's role and platform each indicate a different template. It's also likely users may want to classify devices by some other attribute, such as assigned tag, for the purpose of determining which template to render.
(This option could be combined with option A, where a device's individually-defined template would take precedence over that associated with its role or platform.)
This approaches the problem from a different direction, associating each template with a set of device roles and/or platforms. To determine the appropriate template for a device, a query is made for a template with matching role & platform. (This is similar in concept to how config contexts are retrieved.) This grants a great deal of flexibility, however it would also require some form of tie-breaking mechanism in case multiple matching templates are found.
For example, suppose you associate template A with device role X and template B with platform Y. A device having role X and platform Y would match on both templates. We would need to include a factor such as numeric weighting to prefer one over the other; or return an error.
As with option B, there's a danger here in that a minor change to a template (e.g. removing an assigned role) can break template rendering for a large number of devices; this may be an acceptable drawback considering the flexibility afforded. However, it also complicates retrieving associated device templates in bulk.
This approach could also be combined with option A, which would provide an alternative, explicit assignment for more conservative deployments.
Fond of Option B but allowing option A to override the role/platform per device seems like the sweet spot to me.
Option C sounds good on paper but seems like excess complexity.
Note that we wrote our own config generator using Netbox, and may have further feedback on this based on our experiences once we see what data is available to the template. Will chime in with more details on that when the time is appropriate.
I concur that option C, while powerful, could get very confusing. Given that the penalty is overwriting the config of a production device and taking it down, simple seems better.
A mixture of options A and B with a slight tweak seems best to me as well. Being able to set defaults via a combination of platform (so we can differentiate between IOS and Junos, for example) and role while retaining the ability to override on a per-device basis by directly assigning a template would be reasonably simple but also allow enough escape hatches to solve slightly trickier situations. I do think it's necessary to allow the combination of platform and role to determine the template - many networks are multi-platform. Or, if you're on Cisco, you're not very unlikely to run multiple dialects between legacy and newer devices.
I would like to be able to run multiple templates to a device.
An ofcourse, each template has to be valid for that vendor/syntax.
imo, this should only create a config out of a template, but never deploy it direct. That does not seems like the task of NetBox
I would like to be able to run multiple templates to a device.
You can do this by creating a base template which includes other (reusable) templates, in the desired order and subject to any logic asserting specific device attributes. For example:
{% include 'common/system.jinja2' %}
{% include 'common/interfaces.jinja2' %}
{% include 'common/access-lists.jinja2' %}
{% if device.role.slug == 'some-special-role' %}
{% include 'special/other-stuff.jinja2' %}
{% endif %}
You'll likely have different base templates for each role/platform.
I also want to point out that you do not need to create a corresponding ConfigTemplate instance in NetBox to accomplish this: Included templates can be populated directly from other remotely-sourced data files. You'll only need to create a ConfigTemplate for each template that you want to render directly.
imo, this should only create a config out of a template, but never deploy it direct.
100% :+1: - There are plenty of other tools for that.
Not trying to pile on, but just share a current workflow to broaden an understanding of how users currently approach.
I currently do this off box, and my templates get applied to a "function" and platform. Functions are assigned to device roles (the netbox model). Here are a few examples:
I think map my made up functions to NetBox Device Roles like:
device_role_definitions:
border_leaf:
- base (applies the device platform's base)
- leaf_peering (for lack of a better name, it includes the templates for the ibgp, mlag, lacp, and other similar leaf peer config)
- wan (bgp, prefix-lists, community-lists, route-maps, circuit terminations w/descriptions, bfd, etc.)
- underlay_spine_leaf (fiber uplinks, bgp peering, etc.)
- evpn_overlay (evpn, bgp, etc)
- vxlan
- border_leaf (specific border_leaf only config)
- storage_leaf (specific config if a location is so small that storage_leaf role is consolidated on this device)
storage_leaf:
- base (applies the device platform's base)
- leaf_peering (for lack of a better name, it includes the templates for the ibgp, mlag, lacp, and other similar leaf peer config)
- underlay_spine_leaf (fiber uplinks, bgp peering, etc.)
- evpn_overlay (evpn, bgp, etc)
- vxlan
- storage_leaf (storage network specific items)
spine:
- base (applies the device platform's base)
- underlay_spine_leaf (fiber uplinks, bgp peering, etc.)
- evpn_overlay (evpn, bgp, etc)
- spine (specific spine only config like summary networks)
All of this is defined by using include templates like @jeremystretch mentioned. "Functions" are not really modeled in netbox, but in the templates that define each device role. All of the above templates are created as needed, and if I bring up a JUNOS border_leaf in the future, the template would fail (as it should) until platform specific templates are created for all the boarder_leaf functions.
I probably would continue to do this off box because I produce additional artifacts and upload all to git:
Oh I forgot to add, I feel like the Option C, similar to how config_context can be applied ultimate gives use the most flexibility, but its likely a trade off with complexity/confusion.
For now, I've just added the foreign key from Device to ConfigTemplate, since it seems like we'll almost certainly want the ability to specify an explicit assignment regardless. Making really great progress with this already!
This is really awesome progress! Any chance that could be modified to be Device to Many? We have J2 templates that are CLI for Day 0 / out-of-band / sneakernet, then NETCONF for Day 2 use cases; which gets rendered is based on device status. Perhaps this is covered under the use case for multiple component templates, but we are already using includes.
Pretty happy with where this ended up. No doubt we'll continue to refine & improve the implementation from here, but I'm calling it done for the purposes of this particular FR.
Any chance that could be modified to be Device to Many?
You'll be able to implement conditional logic within the template itself, which could (for example) check for the presence of a flag indicating which type of config to render, and pull in the relevant subordinate template files.
This is great.
Late to the party, but would like to share an idea.
It would be good to associate config templates with services as well. Say service L3VPN for customer assigned to the device, service has template associated matching device platform to produce requied configuration.
@jeremystretch : Do you have an overview of the device and interface object, which you can use in the Jinja2 Configuration Templates?
For example:
{{ device.name }} --> working: print device name {{ device.url }} --> not working: should print url? {{ device.serial }} --> working: print sn
{% for interface in device.interfaces.all() %} {{ interface }} --> working: print interface name {{ interface.enabled }} --> working: print interface enabled value {% endfor %}
Can you also access the custom fields of an interface / device?
I try serveral methods to get an overview of all attributes / values / keys of the object ... but not working {{ device.keys() }} / {{ device.values() }} / {{ device.attr() }}
I think it would also be a very nice feature, if you can access the complete netbox data via jinja2 templates. For example:
Maybe it is already possible and I dont know how?
NetBox version
v3.4.3
Feature type
New functionality
Proposed functionality
Render device configurations directly from NetBox, using a new
ConfigurationTemplate
model (detailed below) and both locally- and remotely-sourced context data (see also #11558). Users will be able to fetch rendered configuration templates via both the NetBox UI and APIs.Identifying the appropriate configuration template for a specific device/VM may need further exploration, but an approach similar to what we currently do for config contexts is likely needed. It should also be possible to explicitly define the primary template for any supported object (e.g. by adding a
config_template
ForeignKey to the Device and VirtualMachine models).Inclusion tags within a template should work relative to the source file's path within its parent source. We'll need to provide a custom environment overloading the
get_template
method to return the appropriate DataFile instance.(I originally considered taking this on as a plugin, however IMO there's a fairly strong case for implementing this functionality natively under the under the "source of truth" umbrella.)
Use case
One of the core functions of network automation involves rendering device configurations from a template (typically written in Jinja2 or a similar language) and contextual data from one or more sources of truth (e.g. NetBox). While this can be accomplished using provisioning tools like Ansible, offering this ability natively within NetBox can simplify the toolchain needed.
Database changes
This proposal would entail the creation of at least one new model, to represent each template. (The
DataFile
model referenced below is detailed in #11558.)ConfigurationTemplate
name
- User-configured namedescription
language
- Template language (Jinja2, possibly others)data_file
- Foreign key toDataFile
(optional)content
- Stores template content if not sourced from a DataFileExternal dependencies
N/A