Skip to main content

Command Palette

Search for a command to run...

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

Published
9 min read
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

  1. 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

  1. 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
  1. 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
  1. 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