Configuration & Template Automation
Loops and Conditions for Repeated Configuration
What you'll learn
- How {% for %} generates one configuration block per item in a list
- How {% if %} / {% else %} produces different configuration based on data
- A real Jinja2 whitespace gotcha, and the Environment options that fix it
- How one template renders correctly-different configuration for many devices
Listen to a guided explanation of this lesson.
Spoken narration isn't supported in this browser.
Replacing manual indexing with a loop
Lesson 3 left off with an awkward pattern: device.vlans[0], device.vlans[1], device.vlans[2], hand-written for exactly the number of VLANs one device happened to have. Jinja2's {% for %} tag is the same idea as Python's for loop — "do this once per item" — applied inside a template:
from jinja2 import Template
vlans = [
{"id": 10, "name": "USERS"},
{"id": 20, "name": "VOICE"},
]
template = Template("""{% for vlan in vlans %}
vlan {{ vlan.id }}
name {{ vlan.name }}
{% endfor %}""")
print(template.render(vlans=vlans)){% for vlan in vlans %} ... {% endfor %} runs the block between the tags once per item in vlans, with vlan bound to the current item each time — exactly like Python's for vlan in vlans:.
A real gotcha: stray blank lines
Running the example above produces this — worth looking at closely:
vlan 10
name USERS
vlan 20
name VOICE
Notice the blank line before every 'vlan' block. The {% for %} and {% endfor %} tags each sit on their own line in the template — and by default, Jinja2 keeps the newline after each tag as part of the output.
That's not a mistake in the VLAN data — it's what Jinja2 does by default with block tags on their own line. For CLI configuration text, stray blank lines are usually harmless to a device, but they make diffs, validation, and reviewed output noisier than they need to be.
Verified, not assumed
This is real, reproducible Jinja2 behavior — worth confirming for yourself with the exact code above rather than taking it on faith.
The fix is two Environment options:
from jinja2 import Environment
env = Environment(trim_blocks=True, lstrip_blocks=True)
template = env.from_string("""{% for vlan in vlans %}
vlan {{ vlan.id }}
name {{ vlan.name }}
{% endfor %}""")
print(template.render(vlans=vlans))trim_blocks removes the newline immediately after a block tag; lstrip_blocks removes leading whitespace before a block tag on its own line. Together they produce clean output with no template text changes.
vlan 10
name USERS
vlan 20
name VOICESame template text, same data — only the Environment options changed. No blank lines.
Conditional configuration
{% if %} works the same way {% for %} does — the same concept as Python's if, applied inside a template:
interfaces = [
{"name": "GigabitEthernet1/0/1", "description": "Uplink", "enabled": True},
{"name": "GigabitEthernet1/0/2", "description": "Unused port", "enabled": False},
]
template = env.from_string("""{% for iface in interfaces %}
interface {{ iface.name }}
description {{ iface.description }}
{% if not iface.enabled %}
shutdown
{% endif %}
{% endfor %}""")
print(template.render(interfaces=interfaces)){% if not iface.enabled %} ... {% endif %} only includes the 'shutdown' line for interfaces where enabled is False — the same Environment(trim_blocks=True, lstrip_blocks=True) keeps this clean too.
This renders:
interface GigabitEthernet1/0/1
description Uplink
interface GigabitEthernet1/0/2
description Unused port
shutdownThe first interface (enabled: True) has no shutdown line. The second (enabled: False) does — the same template produced structurally different output per item, driven entirely by data.
One template, many devices
Loops and conditionals together are what make a single template file usable across an entire fleet — the same template renders correctly-different output for every device, because every device supplies its own data:
- One template filewritten once — vlan_config.j2
- Device 1's datarenders Device 1's VLANs
- Device 2's datasame template, renders Device 2's VLANs
- Device 3's datasame template, renders Device 3's VLANs
Key takeaways
- {% for item in list %} ... {% endfor %} renders its contents once per item — the same concept as Python's for loop, inside a template.
- {% if condition %} ... {% endif %} conditionally includes template content — the same concept as Python's if, inside a template.
- Jinja2's default behavior leaves stray blank lines around block tags on their own line — Environment(trim_blocks=True, lstrip_blocks=True) fixes this, verified directly rather than assumed.
- A loop is what actually lets one template serve devices with a different number of VLANs each — no per-device template editing required.
- Loops and conditionals together are the mechanism behind 'one template, many devices' — data drives every difference in the output.
Done with this lesson?
Next lesson
Rendering, Validating, and Reviewing Configuration Safely