diff --git a/tech_docs/Jinja2_guide.md b/tech_docs/Jinja2_guide.md index 809e7f3..9557e5f 100644 --- a/tech_docs/Jinja2_guide.md +++ b/tech_docs/Jinja2_guide.md @@ -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 5–10 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 < 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 Ansible’s `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 you’re 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. Let’s sharpen it further with battle-tested patterns and CLI-ready examples. Here’s your playbook: ---