A Simple Jinja2 Template for Configuring Multiple Cisco Interfaces
In today's blog post, we're going to keep things straightforward and easy to understand. We're diving into a basic yet powerful example of using Jinja2, a tool I believe every Network Engineer should have in their toolkit.
The aim here is to keep our example as simple as possible, demonstrating how Jinja2 can make your life easier. While the possibilities with Jinja2 are vast, starting with a basic example helps in grasping its potential. As we walk through this example, you'll get a clearer picture of what Jinja2 is and how it can be a game-changer in network configuration and automation.
When to use Jinja2?
You might be wondering, "When should I use Jinja2?" Well, there isn't a one-size-fits-all answer, but let me start with a common scenario I often encounter.
Imagine you're tasked with making changes to multiple interfaces across several switches. Naturally, you'd want to prepare your configurations in advance, especially if they need to go through a change control process for approval. In such cases, I used to begin by creating a text file and manually adding the configurations. While this method works, it's not without its pitfalls.
The issue with manually preparing configurations is the risk of small, yet costly mistakes. How often have we all copied and pasted configs, only to forget to replace an interface name or VLAN? A single typo can lead to significant issues, which is something we all want to avoid.
This is where Jinja2 shines. Instead of manually typing out each configuration, you create a template. With your data fed into this template, Jinja2 effortlessly generates the full configuration for you. Let's see how this works in practice.
Using Python with Jinja2
Now, you might be wondering, "Why do we need Python in combination with Jinja2?" The key advantage of using Python here is its ability to handle data structures efficiently. In our scenario, we can maintain all our network configuration data in a Python data structure, like a dictionary or a list. This structure holds information about switches, interfaces, VLANs, and any other relevant configuration details.
Once we have our data neatly organized in Python, we can then pass it to our Jinja2 template. This process is akin to giving instructions to a smart machine, we provide the raw data (our Python data structure) and the template (Jinja2), which then processes this information to generate complete configurations.
Let's look at an example
To illustrate how Python and Jinja2 work together, let's walk through an example. We have a Python script and a corresponding Jinja2 template, each playing a crucial role in generating our desired network configurations.
Python Script
from jinja2 import Environment, FileSystemLoader
interfaces = {
"switch-1": ['Gi1/0/8'],
"switch-2": ['Gi1/0/10', 'Gi2/0/5'],
"switch-3": ['Gi2/0/27', 'Gi2/0/28'],
"switch-4": ['Gi1/0/1', 'Gi1/0/20', 'Gi1/0/22'],
"switch-5": ['Gi1/0/9', 'Gi1/0/10'],
"switch-6": ['Gi6/0/22'],
"switch-7": ['Gi1/0/5'],
"switch-8": ['Gi1/0/23', 'Gi1/0/27', 'Gi1/0/31']
}
env = Environment(loader=FileSystemLoader('.'),
trim_blocks=True, lstrip_blocks=True)
template = env.get_template('template.j2')
cisco_config = template.render(interfaces=interfaces)
with open ('config.txt', 'w') as w:
w.write(cisco_config)
Our Python script starts by importing the necessary Jinja2 components. It then defines a data structure, in this case, a dictionary named interfaces
. This dictionary is structured with keys representing switch names and values being lists of interface identifiers. For instance, "switch-1": ['Gi1/0/8']
indicates that switch-1
has an interface Gi1/0/8
that needs modifying.
After defining the data, the script sets up the Jinja2 environment and loads the template file (in our case, template.j2
). The magic happens when the render
method of the template is called with our interfaces
dictionary. This method processes the template with the provided data, generating the full configuration.
While setting up the Jinja2 environment, we use two specific parameters, trim_blocks=True
and lstrip_blocks=True
. These settings are important for controlling whitespace in the generated configuration files, ensuring they are neat and readable.
trim_blocks=True -
This setting prevents the extra newline character after a template tag.lstrip_blocks=True
- This parameter strips leading spaces and tabs from the start of a line to the beginning of a block.
Finally, the script writes this generated configuration to a file called config.txt
.
Jinja2 Template
{% for k,v in interfaces.items() %}
---------
{{ k }}
---------
vlan 10
name users
!
vlan 20
name servers
!
{% for item in v %}
interface {{ item }}
switchport trunk allowed vlan add 10,20
!
{% endfor %}
{% endfor %}
Moving to the Jinja2 template, it's designed to iterate over each item in our interfaces
dictionary. For each switch, it generates the necessary VLAN configuration and then loops through the interfaces of that switch to apply specific settings.
The template uses Jinja2 syntax to control the flow – like iterating with for
loops and using placeholders ({{ }}
) for variables. The result is a dynamic template that adapts based on the data passed from the Python script.
The for
loop plays a crucial role in processing each element of the data provided by the Python script. The syntax for k, v in interfaces.items()
iterates over the interfaces
dictionary passed from the Python script. Here, k
represents the key (the switch name), and v
represents the value (the list of interfaces for that switch).
Within this loop, the template dynamically generates the configuration for each switch and its interfaces. The placeholders {{ }}
are used to insert the values of k
(switch name) and item
(individual interface) into the configuration text. This looping mechanism ensures that every switch and interface in the dictionary is addressed, allowing the template to adapt and generate configurations based on the specific data it receives.
Sign up for Packetswitch
Subscribe and receive updates on Network Automation directly in your inbox.
No spam. Unsubscribe anytime.
Generated config
After running our Python script with the Jinja2 template, we get a series of configurations generated for each switch. Here's a breakdown of what these configurations mean.
---------
switch-1
---------
vlan 10
name users
!
vlan 20
name servers
!
interface Gi1/0/8
switchport trunk allowed vlan add 10,20
!
---------
switch-2
---------
vlan 10
name users
!
vlan 20
name servers
!
interface Gi1/0/10
switchport trunk allowed vlan add 10,20
!
interface Gi2/0/5
switchport trunk allowed vlan add 10,20
!
---------
switch-3
---------
vlan 10
name users
!
vlan 20
name servers
!
interface Gi2/0/27
switchport trunk allowed vlan add 10,20
!
interface Gi2/0/28
switchport trunk allowed vlan add 10,20
!
---------
switch-4
---------
vlan 10
name users
!
vlan 20
name servers
!
interface Gi1/0/1
switchport trunk allowed vlan add 10,20
!
interface Gi1/0/20
switchport trunk allowed vlan add 10,20
!
interface Gi1/0/22
switchport trunk allowed vlan add 10,20
!
---------
switch-5
---------
vlan 10
name users
!
vlan 20
name servers
!
interface Gi1/0/9
switchport trunk allowed vlan add 10,20
!
interface Gi1/0/10
switchport trunk allowed vlan add 10,20
!
---------
switch-6
---------
vlan 10
name users
!
vlan 20
name servers
!
interface Gi6/0/22
switchport trunk allowed vlan add 10,20
!
---------
switch-7
---------
vlan 10
name users
!
vlan 20
name servers
!
interface Gi1/0/5
switchport trunk allowed vlan add 10,20
!
---------
switch-8
---------
vlan 10
name users
!
vlan 20
name servers
!
interface Gi1/0/23
switchport trunk allowed vlan add 10,20
!
interface Gi1/0/27
switchport trunk allowed vlan add 10,20
!
interface Gi1/0/31
switchport trunk allowed vlan add 10,20
!
- Header for Each Switch - Each section of the configuration starts with a header, like
--------- switch-1 ---------
. This clearly identifies which part of the configuration applies to which switch. - VLAN Configuration - For each switch, there's a standard VLAN configuration.
- Interface Configuration - Under each switch, we then configure the specific interfaces. For example, for
switch-1
, the configurationinterface Gi1/0/8 switchport trunk allowed vlan add 10,20
is applied. This command adds VLANs 10 and 20 to the interfaceGi1/0/8
- Consistency Across Switches -The pattern repeats for each switch, with the corresponding interfaces getting the appropriate configuration.
- Error Reduction - By using this automated approach, the chances of making a mistake, like a typo or incorrect VLAN assignment, are significantly reduced. Each interface on every switch is configured consistently and accurately.
- Customizability - While this example uses a specific set of VLANs and interfaces, the template and script can be easily modified to suit different network configurations, making this approach highly versatile.
Closing thoughts
Adding new VLANs or modifying existing ones is a breeze with this setup. All you have to do is amend the Jinja2 template file. For instance, if you want to introduce a new VLAN or change the name of an existing one, you simply update the VLAN section in the template. This change automatically reflects across all the configurations generated for each switch, maintaining consistency and saving you the hassle of manually editing each switch's configuration.
For those looking to take automation a step further, integrating tools like Netmiko is an excellent option. Netmiko can be used to push these configurations directly to your switches. This means you can go from generating configurations to applying them on your network devices, all within a streamlined, automated process.