Add tech_docs/Jinja2_guide.md
This commit is contained in:
214
tech_docs/Jinja2_guide.md
Normal file
214
tech_docs/Jinja2_guide.md
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
This is **exactly** the kind of tactical, network-focused Jinja2 guide I wish I had when I started automating configs. Let’s sharpen it further with battle-tested patterns and CLI-ready examples. Here’s your playbook:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **1. Immediate Wins: Stop Hand-Editing Configs**
|
||||||
|
#### **A. Interface Templating (Multi-Vendor)**
|
||||||
|
**Problem:** Vendor-specific syntax for the same logical interface.
|
||||||
|
**Solution:** One YAML → Cisco/Juniper/Arista templates.
|
||||||
|
|
||||||
|
**`vars/interface.yml`**
|
||||||
|
```yaml
|
||||||
|
interfaces:
|
||||||
|
- name: Eth1/1
|
||||||
|
description: "Server Farm Uplink"
|
||||||
|
mode: trunk
|
||||||
|
vlans: [100, 200]
|
||||||
|
mtu: 9214
|
||||||
|
```
|
||||||
|
|
||||||
|
**`templates/cisco_interface.j2`**
|
||||||
|
```jinja
|
||||||
|
interface {{ interface.name }}
|
||||||
|
description {{ interface.description }}
|
||||||
|
{% if interface.mode == 'trunk' %}
|
||||||
|
switchport mode trunk
|
||||||
|
switchport trunk allowed vlan {{ interface.vlans | join(',') }}
|
||||||
|
{% endif %}
|
||||||
|
mtu {{ interface.mtu }}
|
||||||
|
```
|
||||||
|
|
||||||
|
**`templates/juniper_interface.j2`**
|
||||||
|
```jinja
|
||||||
|
interfaces {
|
||||||
|
{{ interface.name | replace('Eth','ge-') }} {
|
||||||
|
description "{{ interface.description }}";
|
||||||
|
{% if interface.mode == 'trunk' %}
|
||||||
|
unit 0 {
|
||||||
|
family ethernet-switching {
|
||||||
|
vlan members [ {{ interface.vlans | join(' ') }} ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
mtu {{ interface.mtu }};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Render Both:**
|
||||||
|
```bash
|
||||||
|
# Cisco
|
||||||
|
jinja2 templates/cisco_interface.j2 vars/interface.yml
|
||||||
|
# Juniper
|
||||||
|
jinja2 templates/juniper_interface.j2 vars/interface.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **B. BGP Config Generation (With Error-Prone Logic)**
|
||||||
|
**Problem:** Complex BGP configs with neighbor policies.
|
||||||
|
**Solution:** Template + YAML with validation.
|
||||||
|
|
||||||
|
**`vars/bgp.yml`**
|
||||||
|
```yaml
|
||||||
|
bgp:
|
||||||
|
asn: 65001
|
||||||
|
neighbors:
|
||||||
|
- ip: 10.0.0.2
|
||||||
|
remote_as: 65002
|
||||||
|
policies: [ "PREVENT_LEAK" ]
|
||||||
|
- ip: 192.168.1.1
|
||||||
|
remote_as: 65123
|
||||||
|
policies: [ "CUSTOMER_ROUTES" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
**`templates/bgp.j2`**
|
||||||
|
```jinja
|
||||||
|
router bgp {{ bgp.asn }}
|
||||||
|
{% for neighbor in bgp.neighbors %}
|
||||||
|
neighbor {{ neighbor.ip }} remote-as {{ neighbor.remote_as }}
|
||||||
|
{% if "PREVENT_LEAK" in neighbor.policies %}
|
||||||
|
neighbor {{ neighbor.ip }} route-map BLOCK_DEFAULT in
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Trick:** Use `| selectattr` to filter neighbors:
|
||||||
|
```jinja
|
||||||
|
{% for neighbor in bgp.neighbors | selectattr("policies", "contains", "CUSTOMER_ROUTES") %}
|
||||||
|
...customer-specific config...
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **2. Advanced: Network-as-Code Patterns**
|
||||||
|
#### **A. Generate Device-Specific Configs from NetBox API**
|
||||||
|
**Problem:** NetBox has device data, but CLI configs are manual.
|
||||||
|
**Solution:** Fetch NetBox data → Jinja2.
|
||||||
|
|
||||||
|
**`fetch_netbox_data.py`**
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
devices = requests.get("https://netbox/api/dcim/devices/").json()
|
||||||
|
with open('vars/netbox_devices.yml', 'w') as f:
|
||||||
|
json.dump(devices, f)
|
||||||
|
```
|
||||||
|
|
||||||
|
**`templates/netbox_cisco.j2`**
|
||||||
|
```jinja
|
||||||
|
hostname {{ device.name }}
|
||||||
|
{% for iface in device.interfaces %}
|
||||||
|
interface {{ iface.name }}
|
||||||
|
description {{ iface.description }}
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Render All Devices:**
|
||||||
|
```bash
|
||||||
|
python fetch_netbox_data.py
|
||||||
|
jinja2 templates/netbox_cisco.j2 vars/netbox_devices.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **B. Auto-Generate Port-Channel Configs (LACP)**
|
||||||
|
**Problem:** Port-channel members change frequently.
|
||||||
|
**Solution:** Dynamic YAML + template.
|
||||||
|
|
||||||
|
**`vars/portchannel.yml`**
|
||||||
|
```yaml
|
||||||
|
portchannel:
|
||||||
|
id: 10
|
||||||
|
members: [ "Eth1/1", "Eth1/2" ]
|
||||||
|
mode: "active"
|
||||||
|
vlan: 100
|
||||||
|
```
|
||||||
|
|
||||||
|
**`templates/portchannel.j2`**
|
||||||
|
```jinja
|
||||||
|
interface Port-channel{{ portchannel.id }}
|
||||||
|
channel-group {{ portchannel.id }} mode {{ portchannel.mode }}
|
||||||
|
switchport access vlan {{ portchannel.vlan }}
|
||||||
|
|
||||||
|
{% for member in portchannel.members %}
|
||||||
|
interface {{ member }}
|
||||||
|
channel-group {{ portchannel.id }} mode {{ portchannel.mode }}
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **3. Pro Tooling: Network-Specific Tricks**
|
||||||
|
#### **A. Ansible + Jinja2 for Zero-Touch Deployment**
|
||||||
|
**`ansible_playbook.yml`**
|
||||||
|
```yaml
|
||||||
|
- name: Push Interface Configs
|
||||||
|
hosts: switches
|
||||||
|
tasks:
|
||||||
|
- name: Generate Config
|
||||||
|
template:
|
||||||
|
src: templates/cisco_interface.j2
|
||||||
|
dest: /tmp/{{ inventory_hostname }}.cfg
|
||||||
|
- name: Deploy Config
|
||||||
|
cisco.ios.ios_config:
|
||||||
|
src: /tmp/{{ inventory_hostname }}.cfg
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **B. Validate Configs Before Deployment**
|
||||||
|
```bash
|
||||||
|
# Check for missing variables
|
||||||
|
jinja2 --format vars:yaml templates/interface.j2 vars/device.yml
|
||||||
|
|
||||||
|
# Dry-run with Ansible
|
||||||
|
ansible-playbook playbook.yml --check
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **C. Secret Management (Ansible Vault)**
|
||||||
|
```yaml
|
||||||
|
# Encrypted vars.yml
|
||||||
|
ansible-vault encrypt vars/secrets.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Template:**
|
||||||
|
```jinja
|
||||||
|
username {{ vaulted_username }}
|
||||||
|
password {{ vaulted_password }}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **4. Your Battle Plan**
|
||||||
|
1. **Start Tonight:**
|
||||||
|
- Pick 1 repetitive config (VLANs/ACLs) → template it.
|
||||||
|
- Render with `jinja2-cli` locally.
|
||||||
|
|
||||||
|
2. **Next Week:**
|
||||||
|
- Integrate with NetBox/Python to auto-generate configs.
|
||||||
|
- Use Ansible to push to devices.
|
||||||
|
|
||||||
|
3. **Go Pro:**
|
||||||
|
- Build a GitLab pipeline that auto-generates configs on NetBox changes.
|
||||||
|
|
||||||
|
**Example Repo Structure:**
|
||||||
|
```
|
||||||
|
network-automation/
|
||||||
|
├── templates/ # .j2 files
|
||||||
|
├── vars/ # .yml device vars
|
||||||
|
├── scripts/ # Python/Ansible helpers
|
||||||
|
└── outputs/ # Rendered configs
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Takeaway:** Jinja2 turns network configs from **manual CLI work** into **version-controlled code**. The more you template now, the more time you save later.
|
||||||
Reference in New Issue
Block a user