NetAuto Academy

Configuration & Template Automation

Loops and Conditions for Repeated Configuration

Intermediate14 min read

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
🎓 Teach This Lesson

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:

vlan_loop.py
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:.

Quick checkConcept check
Inside {% for vlan in vlans %} ... {% endfor %}, what does vlan refer to on each pass?

A real gotcha: stray blank lines

Running the example above produces this — worth looking at closely:

output (naive)

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:

vlan_loop_fixed.py
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.

output (fixed)
vlan 10
name USERS
vlan 20
name VOICE

Same template text, same data — only the Environment options changed. No blank lines.

Quick checkPredict output
With trim_blocks=True and lstrip_blocks=True, does the exact same template text from the naive example above still produce blank lines between VLAN blocks?

Conditional configuration

{% if %} works the same way {% for %} does — the same concept as Python's if, applied inside a template:

conditional_interfaces.py
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:

output
interface GigabitEthernet1/0/1
description Uplink
interface GigabitEthernet1/0/2
description Unused port
shutdown

The 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.

Quick checkIdentify the correct code
Which condition correctly adds a 'shutdown' line only for interfaces where enabled is False?

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:

  1. One template filewritten once — vlan_config.j2
  2. Device 1's datarenders Device 1's VLANs
  3. Device 2's datasame template, renders Device 2's VLANs
  4. Device 3's datasame template, renders Device 3's VLANs
Quick checkScenario
Three devices each have a different number of VLANs (2, 4, and 1). How many different templates are needed to render all three correctly?

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.