# 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

```python
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`

```python
{# 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`

```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

```python
#!/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
    

```python
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
    

2.  `load_data()` Function
    

```python
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
```

3.  `generate_config()` Function
    

```python
if not os.path.exists(output_dir):
    os.makedirs(output_dir)

# Creates a folder called `generated_configs` if it doesn't exist
```

```python
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
```

```python
template = env.get_template(template_file 

# Loads the Jinja2 template file (contains Nokia router config structure with placeholders)
```

```python
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
```

```python
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
```

4.  `main ()` Function
    

```python
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

```python
python3 nokia_config_generator.py 
```

### Output:

```python
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`

```python
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`

```python
{# 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`

```python
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:

```python
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`

```python
/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`

```python
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`

```python
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`

```python
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`

```python
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:

```python
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

```python
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](https://gitlab.com/kgosileburu/network-automation-scripts)
