Mastering Dynamic Configurations: A Beginner's Guide to Jinja2 - Part 2

Having completed Part 1, you now understand Jinja2 and its basic templating constructs, including variables, control structures, and simple filters for generating dynamic text. In Part 2, we will delve deeper into practical techniques that make Jinja2 a powerful tool for configuration management. You can look forward to detailed, practical examples on creating maintainable, reusable templates for router configurations..
Use Case 1: Nokia SR OS Service Configuration
Scenario
You are deploying 50 new customer L3VPN services on Nokia SR OS routers. Each customer requires:
Unique customer ID
Service description
IP interfaces
BGP Peering
Prerequisites
pip install jinja2
pip install pyyaml
pip install netmiko #For deployment (optional)
# Install required libraries t
# It is always essential create a python environment
Jinja2 Template
File: nokia_customer_service.j2
{# Nokia SR OS Customer Service Template #}
{# =================================== #}
{# Customer: {{ customer.name }} #}
{# Date: {{ timestamp }} #}
{# =================================== #}
/configure
#--------------------------------------------------
echo "Creating Customer {{ customer.id }}: {{ customer.name }}"
#--------------------------------------------------
service
customer {{ customer.id }} create
description "{{ customer.name }}"
exit
{% for vprn in customer.vprns %}
vprn {{ vprn.service_id }} customer {{ customer.id }} create
service-name "{{ vprn.name }}"
description "{{ vprn.description }}"
autonomous-system {{ vprn.as_number }}
route-distinguisher {{ vprn.rd }}
{# Configure VPRN Interfaces #}
{% for interface in vprn.interfaces %}
interface "{{ interface.name }}" create
address {{ interface.ip }}/{{ interface.prefix }}
sap {{ interface.sap }} create
description "{{ interface.description }}"
exit
exit
{% endfor %}
{# Configure BGP if present #}
{% if vprn.bgp %}
bgp
group "{{ vprn.bgp.group_name }}"
type {{ vprn.bgp.type }}
peer-as {{ vprn.bgp.peer_as }}
{% for neighbor in vprn.bgp.neighbors %}
neighbor {{ neighbor.ip }}
description "{{ neighbor.description }}"
exit
{% endfor %}
exit
exit
{% endif %}
no shutdown
exit
{% endfor %}
exit
exit
Data YAML file
File: customers_data.yaml
---
customers:
- id: 100
name: CodedNetwork Corp
vprns:
- service_id: 1000
name: CodedN-CORP-VPRN
description: Enterprise Corp Main VPRN
as_number: 65000
rd: 65000:100
interfaces:
- name: to-customer-site-1
ip: 10.100.1.1
prefix: 30
sap: 1/1/c1/1:100
description: Customer Site 1 Connection
- name: to-customer-site-2
ip: 10.100.2.1
prefix: 30
sap: 1/1/c2/1:101
description: Customer Site 2 Connection
bgp:
group_name: customer-bgp
type: external
peer_as: 65100
neighbors:
- ip: 10.100.1.2
description: Site 1 CE Router
- ip: 10.100.2.2
description: Site 2 CE Router
- id: 101
name: Tech Startup Inc
vprns:
- service_id: 1001
name: TECH-STARTUP-VPRN
description: Tech Startup Main VPRN
as_number: 65001
rd: 65000:101
interfaces:
- name: to-startup-hq
ip: 10.101.1.1
prefix: 30
sap: 1/1/c3/1:200
description: Startup HQ Connection
bgp:
group_name: startup-bgp
type: external
peer_as: 65200
neighbors:
- ip: 10.101.1.2
description: HQ CE Router
Nokia SR OS Configuration Generator - Code Overview
#!/usr/bin/env python3
"""
Nokia SR OS Configuration Generator using Jinja2
Generates customer service configurations from YAML data
"""
from jinja2 import Environment, FileSystemLoader
import yaml
from datetime import datetime
import os
def load_data(yaml_file):
"""Load customer data from YAML file"""
with open(yaml_file, 'r') as f:
return yaml.safe_load(f)
def generate_config(template_file, data, output_dir='configs_generated'):
"""
Generate configurations from template and data
Args:
template_file: Jinja2 template file path
data: Dictionary with customer data
output_dir: Directory to save generated configs
"""
# Create output directory
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# Set up Jinja2 environment
env = Environment(
loader=FileSystemLoader('.'),
trim_blocks=True,
lstrip_blocks=True
)
# Load template
template = env.get_template(template_file)
# Generate config for each customer
for customer in data['customers']:
# Add timestamp to data
customer['timestamp'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# Render template with customer data
config = template.render(customer=customer)
# Create filename
filename = f"{output_dir}/nokia_customer_{customer['id']}_{customer['name'].replace(' ', '_')}.cfg"
# Save to file
with open(filename, 'w') as f:
f.write(config)
print(f"✓ Generated: {filename}")
print(f" Customer: {customer['name']} (ID: {customer['id']})")
print(f" VPRNs: {len(customer['vprns'])}")
print()
def main():
"""Main execution"""
print("="*70)
print("Nokia SR OS Configuration Generator")
print("="*70)
print()
# Load data
print("Loading customer data...")
data = load_data('customers_data.yaml')
print(f"✓ Loaded {len(data['customers'])} customers")
print()
# Generate configurations
print("Generating configurations...")
print()
generate_config('nokia_customer_service.j2', data)
print("="*70)
print("Generation complete!")
print("="*70)
if __name__ == "__main__":
main()
Detailed Step-by-Step Explanation
- Imports and Setup
from jinja2 import Environment, FileSystemLoader
import yaml
from datetime import datetime
import os
Purpose of each Import
Jinja2 : Template engine for generating text files
YAML : Reads structured customer data
datetime : Adds Timestamps to configs
os : Creates directories for output files
load_data()Function
def load_data(yaml_file):
with open(yaml_file, 'r') as f:
return yaml.safe_load(f)
# The function reads a YAML file and returns its contents as a Python dictionary
generate_config()Function
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# Creates a folder called `generated_configs` if it doesn't exist
env = Environment(
loader=FileSystemLoader('.'),
trim_blocks=True,
lstrip_blocks=True
)
# Sets up template engine to look for templates in current directory
#`trim_blocks` and `lstrip_blocks` remove extra whitespace for cleaner output
template = env.get_template(template_file
# Loads the Jinja2 template file (contains Nokia router config structure with placeholders)
for customer in data['customers']:
customer['timestamp'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
config = template.render(customer=customer)
# Loops through each customer in the YAML data
# Adds current timestamp to customer data
# Renders the template by replacing placeholders with actual customer values
filename = f"{output_dir}/nokia_customer_{customer['id']}_{customer['name'].replace(' ', '_')}.cfg"
with open(filename, 'w') as f:
f.write(config)
# Writes the rendered configuration to file
main ()Function
def main():
data = load_data('customers_data.yaml')
generate_config('nokia_customer_service.j2', data)
# Loads customer data from YAML file
# Generates all configuration files
To run the script
python3 nokia_config_generator.py
Output:
python3 nokia_config_generator.py
======================================================================
Nokia SR OS Configuration Generator
======================================================================
Loading customer data...
✓ Loaded 2 customers
Generating configurations...
✓ Generated: configs_generated/nokia_customer_100_CodedNetwork_Corp.cfg
Customer: CodedNetwork Corp (ID: 100)
VPRNs: 1
✓ Generated: configs_generated/nokia_customer_101_Tech_Startup_Inc.cfg
Customer: Tech Startup Inc (ID: 101)
VPRNs: 1
======================================================================
Generation complete!
======================================================================
#generated configuration under configs_generated folder
ls -l
nokia_customer_100_CodedNetwork_Corp.cfg
nokia_customer_101_Tech_Startup_Inc.cfg
Generated Configuration Example
File: nokia_customer_100_CodedNetwork_Corp.cfg
cat nokia_customer_100_CodedNetwork_Corp.cfg
/configure
#--------------------------------------------------
echo "Creating Customer 100: CodedNetwork Corp"
#--------------------------------------------------
service
customer 100 create
description "CodedNetwork Corp"
exit
vprn 1000 customer 100 create
service-name "CodedN-CORP-VPRN"
description "Enterprise Corp Main VPRN"
autonomous-system 65000
route-distinguisher 65000:100
interface "to-customer-site-1" create
address 10.100.1.1/30
sap 1/1/c1/1:100 create
description "Customer Site 1 Connection"
exit
exit
interface "to-customer-site-2" create
address 10.100.2.1/30
sap 1/1/c2/1:101 create
description "Customer Site 2 Connection"
exit
exit
bgp
group "customer-bgp"
type external
peer-as 65100
neighbor 10.100.1.2
description "Site 1 CE Router"
exit
neighbor 10.100.2.2
description "Site 2 CE Router"
exit
exit
exit
no shutdown
exit
exit
Use Case 2: Nokia SR OS Interface Configuration
Scenario
Configure 48 access ports on a Nokia 7750 SR with:
Different VLAN assignments
Port descriptions based on location
Speed and duplex settings
Jinja2 Template
File: nokia_interface_config.j2
{# Nokia SR OS Interface Configuration Template #}
/configure
{% for interface in interfaces %}
port {{ interface.port }} create
description "{{ interface.description }}"
ethernet
mode {{ interface.mode }}
{% if interface.speed %}
speed {{ interface.speed }}
{% endif %}
{% if interface.duplex %}
duplex {{ interface.duplex }}
{% endif %}
encap-type {{ interface.encap_type|default('dot1q') }}
exit
no shutdown
exit
{% endfor %}
exit
Data YAML file
File: interfaces_data.yaml
interfaces:
- port: "1/1/c1/1"
description: "Building A - Floor 1 - Room 101"
mode: "access"
speed: "1000"
duplex: "full"
vlan: 100
- port: "1/1/c2/1"
description: "Building A - Floor 1 - Room 102"
mode: "access"
speed: "1000"
duplex: "full"
vlan: 100
- port: "1/1/c3/1"
description: "Building A - Floor 2 - Conference Room"
mode: "access"
speed: "1000"
duplex: "full"
vlan: 200
- port: "1/1/c4/1"
description: "Uplink to Core Switch"
mode: "network"
speed: "10000"
encap_type: "qinq"
Output:
python3 nokia_config_interface_gen.py
======================================================================
Nokia SR OS Configuration Generator
======================================================================
Loading interface data...
✓ Loaded 4 interfaces
Generating configurations...
✓ Generated: configs_generated/nokia_interface_Building_A_-_Floor_1_-_Room_101.cfg
✓ Generated: configs_generated/nokia_interface_Building_A_-_Floor_1_-_Room_102.cfg
✓ Generated: configs_generated/nokia_interface_Building_A_-_Floor_2_-_Conference_Room.cfg
✓ Generated: configs_generated/nokia_interface_Uplink_to_Core_Switch.cfg
======================================================================
Generation complete!
======================================================================
Use Case 3: Multi-vendor BGP Configuration
Scenario
Generate BGP configuration for Nokia SR OS, Cisco IOS XR, and Juniper routers.
Nokia SROS Template
File: nokia_bgp.j2
/configure
router bgp
autonomous-system {{ bgp.local_as }}
{% for neighbor in bgp.neighbors %}
neighbor {{ neighbor.ip }}
peer-as {{ neighbor.remote_as }}
description "{{ neighbor.description }}"
family ipv4
{% if neighbor.password %}
authentication-key "{{ neighbor.password }}"
{% endif %}
exit
{% endfor %}
exit
exit
Cisco IOS XR Template
File: cisco_bgp.j2
router bgp {{ bgp.local_as }}
{% for neighbor in bgp.neighbors %}
neighbor {{ neighbor.ip }}
remote-as {{ neighbor.remote_as }}
description {{ neighbor.description }}
address-family ipv4 unicast
!
{% if neighbor.password %}
password encrypted {{ neighbor.password }}
{% endif %}
!
{% endfor %}
!
Juniper Template
File: juniper_bgp.j2
protocols {
bgp {
group external {
type external;
local-as {{ bgp.local_as }};
{% for neighbor in bgp.neighbors %}
neighbor {{ neighbor.ip }} {
description "{{ neighbor.description }}";
peer-as {{ neighbor.remote_as }};
{% if neighbor.password %}
authentication-key "{{ neighbor.password }}";
{% endif %}
}
{% endfor %}
}
}
}
Unified YAML data file
File: bgp_data.yaml
bgp:
local_as: 65000
neighbors:
- ip: "10.1.1.1"
remote_as: 65001
description: "Peer to ISP1"
password: "secret123"
- ip: "10.1.1.5"
remote_as: 65002
description: "Peer to ISP2"
password: "secret123"
Multivendor Python Script
File: generate_multivendor.py
from jinja2 import Environment, FileSystemLoader
import yaml
import os
VENDORS = {
'nokia': 'nokia_bgp.j2',
'cisco': 'cisco_bgp.j2',
'juniper': 'juniper_bgp.j2'
}
def generate_multivendor_config(data_file, output_dir='multivendor_configs'):
"""Generate configs for all vendors from same data"""
# Load data
with open(data_file, 'r') as f:
data = yaml.safe_load(f)
# Create output directory
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# Set up Jinja2
env = Environment(loader=FileSystemLoader('.'))
# Generate for each vendor
for vendor, template_file in VENDORS.items():
template = env.get_template(template_file)
config = template.render(**data)
filename = f"{output_dir}/{vendor}_bgp_config.cfg"
with open(filename, 'w') as f:
f.write(config)
print(f"✓ Generated {vendor.upper()} configuration: {filename}")
if __name__ == "__main__":
generate_multivendor_config('bgp_data.yaml')
Output:
python3 generate_multivendor.py
✓ Generated NOKIA configuration: multivendor_configs/nokia_bgp_config.cfg
✓ Generated CISCO configuration: multivendor_configs/cisco_bgp_config.cfg
✓ Generated JUNIPER configuration: multivendor_configs/juniper_bgp_config.cfg
Putting It All Together: Complete Workflow
Step 1: Create Templates
Create template files for each configuration type
Step 2: Prepare Data
Create YAML files with customer/site/service data
Step 3: Generate Configurations
python3 generate_multivendor.py
Step 4: Review Generated Configs
Key Takeaways
Separation of Responsibilities - Templates (structure) vs Data (content)
Reusability - One template, many configurations
Consistency - Same structure every time
Speed - Generate hundreds of configs in seconds
Maintainability - Update template once, regenerate all configs
Download the Code
All templates and scripts: network-automation-week-3




