Update tech_docs/Jinja2_guide.md

This commit is contained in:
2025-08-03 03:13:34 -05:00
parent 381df60d7f
commit 142c2badb3

View File

@@ -1,3 +1,172 @@
Below is a “cheat-sheet” style upgrade to the playbook you already have.
It is organized by *pain points* I see teams hit once they move past the first 510 devices.
Everything is copy-paste ready (CLI one-liners, tiny Python/Go helpers, Ansible snippets).
If you only have 15 min right now, jump to the “Tonight” section at the end.
────────────────────────────────────────────────────────
1. YAML → Jinja2 → Device in <10 s
────────────────────────────────────────────────────────
Problem: you still open an editor to change a variable file.
Fix: keep YAML inside Git and treat it as the *single source of truth*.
A. Git-based variable loader (avoids where did I save that file?”)
```bash
# vars live in ./inventory/host_vars/$hostname.yml
git ls-files -z '*.yml' | xargs -0 yamllint # fast lint
```
B. One-shot render + diff
```bash
#!/usr/bin/env bash
host=$1
jinja2 templates/ios_base.j2 \
inventory/host_vars/${host}.yml \
--format yaml \
-o /tmp/${host}.cfg
colordiff -u <(ssh $host show run) /tmp/${host}.cfg | less -R
```
C. Atomic push with Ansible deploy_if_changed
```yaml
- name: Deploy only if rendered differs
ios_config:
src: "/tmp/{{ inventory_hostname }}.cfg"
vars:
ansible_check_mode: false
when: >
lookup('pipe', 'diff -q /tmp/{{ inventory_hostname }}.cfg
<(ssh {{ inventory_hostname }} show run)')
is not search('identical')
```
────────────────────────────────────────────────────────
2. Multi-vendor, one variable set
────────────────────────────────────────────────────────
Trick: store *capability* flags instead of vendor syntax.
vars/host_vars/leaf01.yml
```yaml
interfaces:
- name: Ethernet1/1
port_type: access
vlan: 200
poe: true
mtu: 9214
```
templates/shared/interface_capabilities.j2
```jinja2
{# Works on IOS-XE, Junos, EOS #}
{% set vendor = hostvars[inventory_hostname].vendor %}
{% for i in interfaces %}
interface {{ i.name | re_replace('^Eth','') if vendor=='arista' else
i.name | re_replace('^Eth','ge-') if vendor=='juniper' else
i.name }}
{% if i.port_type == 'access' %}
switchport mode access
switchport access vlan {{ i.vlan }}
{% endif %}
{% if i.poe and (vendor == 'cisco' or vendor == 'arista') %}
power inline never
{% endif %}
mtu {{ i.mtu }}
!
{% endfor %}
```
────────────────────────────────────────────────────────
3. NetBox as a live inventory
────────────────────────────────────────────────────────
Inventory plugin (ships with Ansible 2.10) in `ansible.cfg`:
```
[inventory]
enable_plugins = netbox.netbox.nb_inventory
```
Group by site + role:
```yaml
# group_vars/netbox_site_edge.yml
ntp_servers: ["10.0.0.10", "10.0.0.11"]
```
Now zero extra YAML for new sites.
────────────────────────────────────────────────────────
4. Rego in CI to block bad configs
────────────────────────────────────────────────────────
`.gitlab-ci.yml` (runs in <5 s):
```yaml
validate:
image: openpolicyagent/opa:0.59
script:
- opa test policies/
- >
find . -name "*.cfg" -print0 |
xargs -0 -I{} opa eval --fail-defined --format pretty
--data policies/network.rego --input {} "data.network.deny[_]"
```
Example policy (`policies/network.rego`):
```rego
package network
deny[msg] {
input.kind == "interface"
input.mtu > 9216
msg := sprintf("MTU %d too high on %s", [input.mtu, input.name])
}
```
────────────────────────────────────────────────────────
5. Secrets without Ansible Vault pain
────────────────────────────────────────────────────────
Use `sops` + age (age is easier than GPG).
```bash
# encrypt vars/secrets.yml
sops --age $(cat ~/.age/key.txt | grep public | cut -d' ' -f4) \
--encrypt vars/secrets.yml > vars/secrets.enc.yml
```
Ansible automatically decrypts when `ansible_sops_enabled=true`.
────────────────────────────────────────────────────────
6. Tonight Next week Go Pro (15 min each)
────────────────────────────────────────────────────────
Tonight (literally 15 min):
```bash
pipx install jinja2-cli yq
git init network-auto && cd network-auto
mkdir templates vars
cat > vars/lab.yml <<EOF
vlans:
- id: 100
name: STAFF
EOF
cat > templates/ios_vlan.j2 <<'EOF'
{% for v in vlans %}
vlan {{ v.id }}
name {{ v.name }}
{% endfor %}
EOF
jinja2 templates/ios_vlan.j2 vars/lab.yml
```
You now have version-controlled VLANs.
Next week:
- Replace static `vars/lab.yml` with NetBox inventory plugin.
- Add a single playbook that runs against site:lab and pushes the VLANs.
Go Pro:
- Add Rego policy to reject VLAN 1 or duplicate names.
- GitLab CI job auto-pushes when MR merges (use Ansibles `ios_config` with `backup: yes` so you can rollback).
────────────────────────────────────────────────────────
Quick reference card
────────────────────────────────────────────────────────
Render: jinja2 tpl.j2 vars.yml --format yaml
Lint: yamllint vars/
Test: opa test policies/
Encrypt: sops -e vars/secrets.yml
Diff: colordiff -u live.cfg new.cfg
Copy the snippets above into your repo and youre already ahead of most teams.
---
This is **exactly** the kind of tactical, network-focused Jinja2 guide I wish I had when I started automating configs. Lets sharpen it further with battle-tested patterns and CLI-ready examples. Heres your playbook:
---