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. --- Great pivot! **Rego** (Open Policy Agent’s policy language) and **Jinja2** serve different but complementary roles in infrastructure automation. Let’s break down how they compare, overlap, and where you’d use each—with gritty examples tailored to infrastructure/security use cases. --- ### **1. Quick Comparison: Jinja2 vs. Rego** | Feature | Jinja2 | Rego | |---------|--------|------| | **Primary Use** | Templating (generate configs/docs) | Policy enforcement (validate inputs) | | **Input** | YAML/JSON/CSV | JSON/YAML (often from APIs) | | **Output** | Rendered text (configs, CLI commands) | Policy decisions (allow/deny + detailed reasons) | | **Context** | "Generate this network config" | "Is this network config compliant?" | | **Key Strength** | Flexibility in text generation | Logic-based evaluation with auditing | --- ### **2. Where Jinja2 and Rego Overlap** Both operate on structured data (YAML/JSON), but solve different problems in the pipeline: #### **Example Workflow: Network Change Automation** 1. **Jinja2**: Generates a candidate BGP config from YAML variables. ```jinja router bgp {{ bgp.asn }} neighbor {{ neighbor.ip }} remote-as {{ neighbor.asn }} ``` 2. **Rego**: Validates the generated config *before* deployment. ```rego # Prevent BGP peers in untrusted ASNs deny[msg] { input.kind == "bgp_config" not input.neighbor.asn in trusted_asns msg := sprintf("BGP peer ASN %v is not trusted", [input.neighbor.asn]) } ``` --- ### **3. Rego Use Cases (Where It Shines Over Jinja2)** #### **A. Pre-Deployment Validation** **Problem**: Ensure Jinja2-generated configs meet security/compliance rules. **Rego Policy** (`policies/networking.rego`): ```rego package networking # Deny firewall rules that allow SSH from the internet deny[msg] { input.kind == "firewall_rule" input.action == "allow" input.port == 22 input.source == "0.0.0.0/0" msg := "SSH must not be open to the internet!" } # Require VLAN descriptions for auditability deny[msg] { input.kind == "vlan_config" not input.description msg := "VLANs must have a description" } ``` **How to Use**: ```bash # Validate a Jinja2-rendered config against Rego opa eval --data policies/networking.rego --input rendered_config.json "data.networking.deny" ``` --- #### **B. Multi-Cloud Policy Enforcement** **Problem**: Enforce tagging standards across AWS/Azure/GCP. **Rego Policy** (`policies/cloud.rego`): ```rego package cloud # Require 'CostCenter' tag on all resources deny[msg] { input.resource.tags["CostCenter"] == "" msg := "All resources must have a CostCenter tag" } # Block public S3 buckets deny[msg] { input.kind == "aws_s3_bucket" input.acl == "public-read" msg := "Public S3 buckets are prohibited" } ``` **Input (JSON from Terraform/Cloud API)**: ```json { "kind": "aws_s3_bucket", "acl": "public-read", "tags": { "CostCenter": "" } } ``` **Output**: ```json { "deny": [ "All resources must have a CostCenter tag", "Public S3 buckets are prohibited" ] } ``` --- #### **C. Network Topology Auditing** **Problem**: Validate that Jinja2-generated network designs follow best practices. **Rego Policy** (`policies/topology.rego`): ```rego package topology # Core switches must have redundant links deny[msg] { input.device.role == "core" count(input.device.links) < 2 msg := "Core devices must have redundant links" } # VLAN 1 should not be used (security best practice) deny[msg] { input.vlan.id == 1 msg := "VLAN 1 is prohibited for security reasons" } ``` --- ### **4. When to Use Jinja2 vs. Rego** | Scenario | Tool | Why | |----------|------|-----| | Generating Cisco/Juniper configs | Jinja2 | Best at text templating. | | Validating configs pre-deploy | Rego | Logic-based policy checks. | | Creating API payloads | Jinja2 | Structure dynamic JSON/YAML. | | Auditing existing infra | Rego | Query live state for violations. | | Documenting network designs | Jinja2 | Render Markdown/HTML. | | Enforcing security rules | Rego | Evaluate against compliance rules. | --- ### **5. Pro Tips for Rego in Infrastructure** 1. **Test Policies Early**: Use `opa test` to unit test your Rego rules. ```bash opa test policies/ -v ``` 2. **Integrate with CI/CD**: ```yaml # GitLab CI example validate_policy: image: openpolicyagent/opa script: - opa eval --data policies/ --input $CHANGES "data.deny" ``` 3. **Debug with `trace`**: ```rego # Add to your policy to see evaluation steps trace(sprintf("Input: %v", [input])) ``` --- ### **6. Example End-to-End Flow** 1. **Jinja2 Generates Config**: ```bash jinja2 bgp_template.j2 vars.yml > bgp_config.json ``` 2. **Rego Validates It**: ```bash opa eval --data policies/ --input bgp_config.json "data.networking.deny" ``` 3. **Only deploy if Rego returns no denials**. --- ### **7. Key Takeaway** - **Jinja2**: Your "build" tool (creates configs/scripts/docs). - **Rego**: Your "guardrail" tool (ensures compliance/safety). Together, they form a **powerful pipeline**: `Jinja2 (generate) → Rego (validate) → Ansible/Terraform (deploy)` Want a deep dive on a specific Rego use case (e.g., network security policies or cloud governance)? I can break it down with real code.