published: 18th of August 2017
Jinja2 is a templating language built in python and loosely based on the Django templating language. Jinja2 is used in many projects as a templating engine with some notable examples: Ansible , Salt and Flask .
Jinja2 aims to implement some of the most common features of python right into the templating system allowing for the programatic creation of static file contents.
Jinja2 is hosted on PyPi so can be installed with pip . I prefer to use virtual environments to manage python projects.
# create virtual environment
python3.6 -m venv ~/envs/jinja2-env/
# activate virtual environment
source ~/envs/jinja2-env/bin/activate
# install jinja2
pip install jinja2
For reference the following software versions are used in this blog.
Jinja2 templates have the following delimiters which define functionality to be executed by the templating engine.
Variables are defined with the {{ some_variable }} syntax.
{{ some_variable }}
A for loop makes it possible for blocks of text to be repeated without having to copy and paste them manually.
# example
{% for stuff in things %}
{{ stuff }}
{% endfor %} # note: for loops need to be ended
Conditionals render content based on when a condition is either True for False
# cisco
{% if stuff in things %}
{{ stuff }}
{% endif %} # note: if statements need to be ended
{% if stuff not in things %}
no stuff here
{% endif %} # note: if statements need to be ended
The Jinja2 workflow has 3 main steps
Lets start with a simple example. Define some interface variables as a python dictionary.
# cisco
interface = {
'name': 'gigabitethernet0/0',
'description': 'Uplink to WAN',
'ip_address': '10.10.10.1 255.255.255.0',
}
Create an interface_template defining variables that will be replaced with values from the interface variable.
# cisco interface template
interface_template = '''
interface {{ interface.name }}
description {{ interface.description }}
ip address {{ interface.ip_address }}
no shutdown
!
'''
Note: Jinja2 uses the '.' syntax to access a dictionaries keys. For example interface.name refers to the interface variable and the name key. It is also possible to use python syntax to access the dictionary keys IE: interface['name'] . More info can be found in the docs .
If we go ahead and render the interface_template you wil get the following result.
from jinja2 import Template
template = Template(interface_template)
template.render(interface=interface)
# output
interface gigabitethernet0/0
description Uplink to WAN
ip address 10.10.10.1 255.255.255.0
no shutdown
!
We managed to render a single interface configuration, not terribly exciting. Lets add more interfaces and expand out our template with a for loop.
Because we are adding multiple interfaces create a list of dictionaries for each interface to be configured.
interfaces = [
{'description': 'Uplink to WAN',
'ip_address': '10.10.10.1 255.255.255.0',
'name': 'gigabitethernet0/0'},
{'description': 'Crosslink to R2',
'ip_address': '10.10.20.1 255.255.255.0',
'name': 'gigabitethernet0/1'}
]
Add a for loop to the previously configured template.
# cisco interface template
interfaces_template = '''
{% for interface in interfaces %}
interface {{ interface.name }}
description {{ interface.description }}
ip address {{ interface.ip_address }}
no shutdown
!
{% endfor %}
Lets render the template now and see how that looks.
template = Template(interfaces_template)
template.render(interfaces=interfaces)
# output
interface gigabitethernet0/0
description Uplink to WAN
ip address 10.10.10.1 255.255.255.0
no shutdown
!
interface gigabitethernet0/1
description Crosslink to R2
ip address 10.10.20.1 255.255.255.0
no shutdown
!
Lets say we swap the device out with a Juniper router. This is where the advantages of configuration templating really show their value.
# juniper interface template
interfaces_template = '''
interfaces {
{% for interface in interfaces %}
{{ interface.name }} {
description {{ interface.description }};
unit 0 {
family inet {
address {{ interface.ip_address }};
}
}
}
{% endfor %}
}
'''
Juniper and Cisco have different interface naming standards so change the name and the ip_address values.
interfaces = [
{'description': 'Uplink to WAN',
'ip_address': '10.10.10.1/24',
'name': 'ge-0/0/0'},
{'description': 'Crosslink to R2',
'ip_address': '10.10.20.1/24',
'name': 'ge-0/0/1'}
]
Now render the interface template.
template = Template(interfaces_template)
template.render(interfaces=interfaces)
# output
interfaces {
ge-0/0/0 {
description Uplink to WAN;
unit 0 {
family inet {
address 10.10.10.1/24;
}
}
}
ge-0/0/1 {
description Crosslink to R2;
unit 0 {
family inet {
address 10.10.20.1/24;
}
}
}
}
Jinja2 is a powerful tool that allows you to build repeatable configuration templates. I have just scratched the surface in this post, Jinja2 has many features that make it an excellent tool for creating device configuration templates.