Jinja Advanced Features
Course Introduction
Welcome to the advanced Jinja templating guide! Building on the basics, we’ll explore conditionals, loops, and filters - the powerful features that make Jinja templates truly dynamic and efficient for FortiGate configuration management.
Chapter 1: Conditionals and Tests
Conditionals allow your templates to make decisions and generate different configurations based on your data.
Basic If Statements
Jinja uses {% if %}
, {% elif %}
, and {% else %}
for conditional logic:
{% if condition %}
{# Configuration when condition is true #}
{% elif other_condition %}
{# Alternative configuration #}
{% else %}
{# Default configuration #}
{% endif %}
Comparison Operators
==
(equal),!=
(not equal)>
,>=
,<
,<=
(comparisons)and
,or
,not
(logical operators)
Hands-On Exercise 1: Version-Based Configuration
Task: Create a template that uses different SSL VPN configurations based on FortiOS version.
JSON Data:
{
"hostname": "FW-BRANCH-01",
"fortios_version": 7.4,
"ssl_vpn_port": 8443,
"users": [
"alice",
"bob",
"charlie"
]
}
Expected Output for FortiOS 7.4:
config system global
set hostname "FW-BRANCH-01"
end
config vpn ssl settings
set port 8443
set tunnel-ip-pools "SSLVPN_TUNNEL_ADDR1"
set tunnel-ipv6-pools "SSLVPN_TUNNEL_IPv6_ADDR1"
set source-interface "port1"
end
Expected Output for FortiOS 7.0:
config system global
set hostname "FW-BRANCH-01"
end
config vpn ssl settings
set port 8443
set tunnel-ip-pools "SSLVPN_TUNNEL_ADDR1"
set source-interface "port1"
end
Your Task: Write a template that includes IPv6 pools only for FortiOS 7.2 and above.
Using Tests
Tests check properties of variables and return True/False:
is defined
- Check if variable existsis string
,is number
- Check data typesis iterable
- Check if can be looped over
Hands-On Exercise 2: Safe Configuration with Tests
Task: Create a template that safely handles optional configuration parameters.
JSON Data:
{
"hostname": "FW-TEST-01",
"timezone": "America/New_York",
"admin_timeout": 480,
"ntp_servers": [
"pool.ntp.org",
"time.google.com"
]
}
Your Task: Write a template that:
- Only configures timezone if it’s defined
- Only configures admin timeout if it’s a number
- Only configures NTP if servers list is not empty
Chapter 2: Loops
Loops let you generate repetitive configurations efficiently using arrays and objects in your JSON data.
Basic For Loop
{% for item in collection %}
{{ item }}
{% endfor %}
Loop Variables
Inside loops, Jinja provides helpful variables:
loop.index
- Current iteration (1-based)loop.index0
- Current iteration (0-based)loop.first
- True on first iterationloop.last
- True on last iteration
Hands-On Exercise 3: Multiple Interface Configuration
Task: Generate configuration for multiple interfaces using a loop.
JSON Data:
{
"interfaces": [
{
"name": "port1",
"ip": "192.168.1.1",
"netmask": "255.255.255.0",
"description": "LAN Interface",
"allowaccess": [
"ping",
"https",
"ssh"
]
},
{
"name": "port2",
"ip": "203.0.113.10",
"netmask": "255.255.255.252",
"description": "WAN Interface",
"allowaccess": [
"ping"
]
},
{
"name": "port3",
"ip": "10.10.10.1",
"netmask": "255.255.255.0",
"description": "DMZ Interface",
"allowaccess": [
"ping",
"https"
]
}
]
}
Your Task: Create a template that configures all interfaces.
Looping Over Dictionaries
When your JSON uses objects instead of arrays, you can loop over key-value pairs:
{% for key, value in dictionary.items() %}
Key: {{ key }}, Value: {{ value }}
{% endfor %}
Hands-On Exercise 4: VLAN Configuration with Dictionary
Task: Configure VLANs using a dictionary structure.
JSON Data:
{
"vlans": {
"10": {
"name": "USERS",
"description": "User workstations",
"interface": "port1"
},
"20": {
"name": "SERVERS",
"description": "Server network",
"interface": "port1"
},
"30": {
"name": "GUESTS",
"description": "Guest network",
"interface": "port1"
}
}
}
Expected Output:
config system interface
edit "USERS"
set vdom "root"
set vlanid 10
set interface "port1"
set description "User workstations"
next
edit "SERVERS"
set vdom "root"
set vlanid 20
set interface "port1"
set description "Server network"
next
edit "GUESTS"
set vdom "root"
set vlanid 30
set interface "port1"
set description "Guest network"
next
end
Loop Filtering
You can filter items during loops using if
:
{% for item in collection if item.status == 'active' %}
{{ item.name }}
{% endfor %}
Hands-On Exercise 5: Conditional Interface Configuration
Task: Configure only interfaces that have IP addresses assigned.
JSON Data:
{
"interfaces": [
{
"name": "port1",
"ip": "192.168.1.1",
"netmask": "255.255.255.0",
"description": "LAN Interface"
},
{
"name": "port2",
"description": "Unused port"
},
{
"name": "port3",
"ip": "10.10.10.1",
"netmask": "255.255.255.0",
"description": "DMZ Interface"
}
]
}
Your Task: Only configure interfaces that have an ip
field defined.
Chapter 3: Filters
Filters transform data in your templates. They’re applied using the pipe |
symbol.
Essential Filters
join
Combines array elements into a string:
{{ servers | join(' ') }}
default
Provides fallback values:
{{ timeout | default(300) }}
length
Gets the count of items:
{% if users | length > 10 %}
upper/lower
Changes text case:
{{ hostname | upper }}
replace
{{ "branch-office-firewall" | replace('-', '_') | upper }}
> BRANCH_OFFICE_FIREWALL
Note
FortiSOAR has documentation of all the available Jinja filters here
Hands-On Exercise 6: Firewall Policy with Filters
Task: Use filters to create firewall policies with proper formatting.
JSON Data:
{
"policies": [
{
"name": "allow_web_traffic",
"srcintf": [
"port1",
"port3"
],
"dstintf": [
"port2"
],
"srcaddr": [
"internal_users",
"dmz_servers"
],
"dstaddr": [
"all"
],
"service": [
"HTTP",
"HTTPS"
],
"action": "accept",
"log": true
},
{
"name": "allow_dns",
"srcintf": [
"port1"
],
"dstintf": [
"port2"
],
"srcaddr": [
"internal_users"
],
"dstaddr": [
"all"
],
"service": [
"DNS"
],
"action": "accept"
}
]
}
Your Task: Create firewall policies using filters to join arrays and provide defaults.
Advanced Filters
map
Extracts specific attributes from objects:
{{ interfaces | map(attribute='name') | join(' ') }}
select/reject
Filters items based on conditions:
{{ vlans | selectattr('active', 'equalto', true) }}
groupby
Groups items by an attribute:
{% for status, interfaces in all_interfaces | groupby('status') %}
Chaining Filters
You can combine multiple filters:
{{ servers | map(attribute='name') | join(', ') }}
Summary
You’ve learned to use Jinja’s most powerful features:
Conditionals:
{% if %}
,{% elif %}
,{% else %}
- Tests like
is defined
,is string
- Comparison and logical operators
Loops:
{% for item in list %}
{% for key, value in dict.items() %}
- Loop filtering with
if
- Loop variables (
loop.index
,loop.first
, etc.)
Filters:
- Basic:
join
,default
,length
,upper/lower
- Advanced:
map
,select/reject
,groupby
- Filter chaining with
|
Next Steps
Practice combining these features to create sophisticated FortiGate configurations. Start simple and gradually add complexity as you become more comfortable with the syntax and concepts.