Intro to Jinja2


 
 

What is Jinja2

Jinja2 is a templating language that was originally used as part of the Flask python web framework. From the Jinja2 website

Jinja2 is a full featured template engine for Python. It has full unicode support, an optional integrated sandboxed execution environment, widely used and BSD licensed

It was originally developed to help automatically generate HTML dynamically as part of the flask framework, more on that in another post, but it can also easily be used to help us generate our configuration files for our infrastructure devices.

This is going to be a very simple introduction to a few of the basic concepts of that jinja uses which, hopefully, will help to understand how Jinja can be used as a first step down the road of gaining automation skills.

We’ll take a look at a developing some intuition on how Jinja2 can be used to create basic network infrastructure device configurations. This is definitly not the modern method of interfacing directly into the control/data/management plane of devices using APIs, but it’s definitely a step in the right direction on understanding how a bit of code can help make your life better.

Prereqs

I’m assuming you’ve already got python installed on your system. You’re also going to need to run the pip install jinja2 command from a terminal window to get the latest version of jinja2 which should work just fine here.

 

Learning by Example

In this section we’ll start with a small example on how to create a few VLANs using the typical syntax from a modern networking OS. In this case, I used the HPE Comware syntax, but it would be easy enough to create this using a Cisco or Juniper configuration and you’re encouraged to try to get this working with your own network vendor.

 

Import required libraries

First We’ll import the required modules from the Jinja2 library. This is pretty much stolen directly from the jinja2 docs.

In [1]:
from jinja2 import Environment, FileSystemLoader, Template
 

Creating the VLANS

For this example, we’re going to create a python list of dicts which contains six different VLANS as listed in the tabel below.

A python dictionary is just a key-value pair, where the value for a specific key in the dictionary can be accessed using the key name.

  • Name: Name of the VLAN
  • Description: Descrition of the VLAN
  • VLAN ID: Dot1q VLAN ID.
Name Description VLAN ID
Management Management VLAN 10
Users Users VLAN 15
Phones Phones VLAN 16
Servers Servers VLAN 20
Mobility Mobility VLAN 30
Guest Guest VLAN 40
In [5]:
vlans_list = [{'name': 'management', 'description': 'management vlan', 'id': '10'},
         {'name': 'users', 'description': 'users vlan', 'id': '15'},
         {'name': 'phones', 'description': 'phones vlan', 'id': '16'},
         {'name': 'servers', 'description': 'servers vlan', 'id': '20'},
         {'name': 'mobility', 'description': 'mobility vlan', 'id': '30'},
         {'name': 'guest', 'description': 'guest vlan', 'id': '40'},
         {'name': 'rob', 'description': 'guest vlan', 'id': '45'}
         ]
 

VLAN Jinja2 Template

In this step, we’re going to create a variable called text_file which will contain the content of a jinja2 template. This is a basic python string object which means, at this point, it’s just a bunch of text.

In normal circunstances, we would actually be reading this template from a file located on the hard drive, but for our purposes today, we’ll just put the templatein by hand.

What makes Jinja2 powerful is the control structures that allow it to perform programatic operations. In this example, we’re creating a For loop.

Following the code we will each vlan in the vlans object we created above and then render the template using the ‘id’ key for the first variable, the ‘name’ key for the second variable, and the ‘description’ key for the last variable.

Hopfully, this makes sense, but if not, just hold on and it should become clear before the end.

In [6]:
text_file = ('''
#vlan config
{% for vlan in vlans -%}
vlan {{ vlan['id'] }}
 name {{ vlan['name'] }}
 description {{ vlan['description'] }}
{% endfor %}''')
 

If I was to write the same as a traditional python iterator it would look something like this. You can see how they are related I hope?

In [7]:
for vlan in vlans_list:
    print ('''vlan ''' +vlan['id']+
           '''\n name '''+vlan['name']+
           '''\n description '''+vlan['description'])
 
vlan 10
    name management
    description management vlan
vlan 15
    name users
    description users vlan
vlan 16
    name phones
    description phones vlan
vlan 20
    name servers
    description servers vlan
vlan 30
    name mobility
    description mobility vlan
vlan 40
    name guest
    description guest vlan
vlan 45
    name rob
    description guest vlan
 

That’s a lot of work typing isn’t it?

You could ask

That’s more typing than I would do by hand? Why would I use this?

Great question. The point of automating anything is to cut down on the repetitive effort it takes to accomplish a given goal. In this case, we can simply count the number of key strokes it would take to create a single new VLAN on a switch.

In [6]:
count_chars = "vlan', 'name', 'description"
keystrokes = len(count_chars)
print (keystrokes)
 
27
 

Now let’s pretend we had to type that 10 times

In [7]:
keystrokes * 10
Out[7]:
270
 

Or maybe we had to create 100 VLANs.

In [10]:
keystrokes * 100
Out[10]:
2700

Ir maybe we had to create the full 4094 VLANs available

In [11]:
keystrokes * 4094
Out[11]:
110538
 

Not sure about you, but if I don’t have to type 110,000 keystrokes, my fingers will love me at the end of the day. Not to mention the fact that it’s also repeated perfectly every single time, not a single typo in there.

 

Create the Template Object

Now that we’ve created the text_file string object, we need to transform it into a jinj2 template which will allow us to then render it. We will create a new object called vlan_template and assign an instance of the Template class using the text_file contents as the input.

In [12]:
vlan_template = Template(text_file)
 

Make the Magic Happen

We will now use the render method on the vlan_template that we created above. We have a single argument to pass into the function. In this case we are passing the vlans_list list of dictionaries we create above in to the function as the vlans variable.

In [13]:
vlan_template.render(vlans=vlans_list)
Out[13]:
'\n#vlan config\nvlan 10\n    name management\n    description management vlan\nvlan 15\n    name users\n    description users vlan\nvlan 16\n    name phones\n    description phones vlan\nvlan 20\n    name servers\n    description servers vlan\nvlan 30\n    name mobility\n    description mobility vlan\nvlan 40\n    name guest\n    description guest vlan\n'
 

Hmmm What happened there?

That doesn’t look like a configuration file does it? The output of this file is actually a python string object. In python, we need someway to represent a carriage return (enter-key) and the \n just happens to have that honour.

Instead of running the template rendering directly, we can instead capture the output into a string object which we will then pass to the print command.

In [14]:
rendered_template = vlan_template.render(vlans=vlans_list)
In [15]:
print (rendered_template)
 
#vlan config
vlan 10
    name management
    description management vlan
vlan 15
    name users
    description users vlan
vlan 16
    name phones
    description phones vlan
vlan 20
    name servers
    description servers vlan
vlan 30
    name mobility
    description mobility vlan
vlan 40
    name guest
    description guest vlan

 

Clear?

Hopefully, this has shown you a bit of how a basic jinja control structure, like a For loop, can be used to cut down on a lot of key strokes, increase the accuracy of the configurations and help to streamline the operations.

In the next post, I’ll look at loading YAML files directly into python and using their contents as input into some more advanced jinja2 templates.

 

Leave a comment