13. Ansible with jinja2 Template Part 2

Preview

In the previous section we defined the Jinja2 template as a configuration file, but with variables that are replaced by device-specific values during the execution time of the playbook. We also saw that jinja2 template is more than just a variable substitution and we used a loop to substitute a list variable, list of networks in EIGRP configuration mode. In this section we would like to see some other features of jinja2 such as “if conditions” and also some other variable types that can be replaced during execution time.

Ansible with jinja2 Template Part2

access ansible codes of this course in github

this is a video-based training therefore the output of running commands are not show in the text.

I have prepared a template configuration file with various capabilities as well as a variable file of various types that are called in the template file to be replaced at the time of playbook execution time.

There is simple variable substitution, a loop over a list variable, a loop over a dictionary variable, if-condition and also the “in” operator in the template configuration file to see more capabilities of the jinja2 template.

root@debian:~/ansible-project1# cat template.j2
# main reference: https://ttl255.com/

# variable substitution
# loop over list
no router eigrp {{ as }}
router eigrp {{ as }}
 router-id {{ router_id }}
 {% for network in networks %}
  network {{ network }}
 {% endfor %}

# dictionary as a variable; variable substitution
interface {{ interface.name }}
 description {{ interface.description }}
 ip address {{ interface.ip_address }}
 speed {{ interface.speed }}
 mtu {{ interface.mtu }}

# loop over dictionary method 1
{% for intf in interfaces -%}
 interface {{ intf }}
  description {{ interfaces[intf].description }}
  ip address {{ interfaces[intf].ipv4_address }}
{% endfor %}

# loop over dictionary method 2
{% for iname, idata in interfaces.items() -%}
 interface {{ iname }}
  description {{ idata.description }}
  ip address {{ idata.ipv4_address }}
{% endfor %}


# conditional commands
{% if routing_protocol == 'bgp' -%}
router bgp {{ bgp.as }}
  bgp router-id {{ interfaces_lp.Loopback0.ip }}
  network {{ interfaces_lp.Loopback0.ip }} mask {{ interfaces_lp.Loopback0.mask }}
{%- elif routing_protocol == 'ospf' -%}
router ospf {{ ospf.pid }}
  router-id {{ interfaces_lp.Loopback0.ip }}
  network {{ interfaces_lp.Loopback0.ip }} {{ interfaces_lp.Loopback0.wildmask }} area 0
{%- else -%}
  ip route 0.0.0.0/0 {{ default_nh }}
{%- endif %}

# in operator
{%- if 'Loopback0' in interfaces -%}
 ip radius source-interface Loopback0
{%- else %}
 ip radius source-interface GigabitEthernet1
{% endif %}
root@debian:~/ansible-project1# cat host_vars/csr1
---
hostname: csr1
router_id: 1.1.1.1

# list
networks:
  - 192.168.5.0
  - 192.168.6.0
  - 192.168.7.0

# dictionary
interface:
  name: GigabitEthernet3
  ip_address: "10.0.0.1 255.255.254.0"
  description: "Uplink to core"
  speed: 1000
  mtu: 9124

# dictionary. list of dictionary
interfaces:
  GigabitEthernet2:
    description: server1
    ipv4_address: "10.50.0.1 255.255.255.0"
  GigabitEthernet3:
    description: server2
    ipv4_address: "10.50.1.1 255.255.255.0"
  loopback0:
    ipv4_address: "10.11.11.1 255.255.255.255"
    description: "just a test"


# dictionary for conditional test
routing_protocol: bgp
interfaces_lp:
  Loopback0:
    ip: 10.11.12.1
    mask: 255.255.255.255
bgp:
  as: 65001

The first part of the template file is what we saw in the previous section which includes simple variable substitution and also a loop over the list variable. The “router-id” and “as” variables are simple string and integer variables, but the “networks” variable is a list variable that is called through a loop in the template file

In the second example we continue with variable substitution but we’ll use more complex data structure. This time we’ll use “interface” variable which is dictionary. Jinja gives a handy way of accessing dictionary keys using using “dot” notation. However this only works for keys that don’t have special characters in the name. otherwise we should use “[]” instead of “dot”. In this example we access the values with calling “interface.name”, “interface.description”, “interface.ip_address”, “interface.speed” and “interface.mtu” keys.

The next part of the template file is how to iterate over a dictionary instead of list. One advantage of using dictionaries over lists is that we can use names of elements as a reference, this makes retrieving objects and their values much easier. Here “intf” refers to   GigabitEthernet2,   GigabitEthernet3 and Loopback0 keys. To access attributes of each interface we need to use interfaces[intf] notation and then dot and name of the key, description and ipv4_address .

If you add a minus sign (-) to the start or end of a block, a comment, or a variable expression, the whitespaces before or after that block will be removed. I have used here however it is not important since extra spaces are automatically ignored in cisco configuration.

In the next section we try another way of iterating over dictionary. In this method we retrieve key and its value at the same time by using items() method. it seems to be more readable for some of you.

Next example is to check “if condition”. According to the routing protocol enabled in the router, different configuration will be pushed to the device.  i have set routing_protocol as “bgp” in variable file so the first part of the “if condition” will  be applied to the device. It uses “interfaces” variable which is dictionary variable. The ip address of loopback0 will be used in “bgp router-id” command and also in network command in bgp configuration mode.

The last section is to use “in operator” in jinja2 Template. You can use it to test whether an item appears in the list or whether a key is in a dictionary. In the last example I am checking whether Loopback0 is already configured in our interfaces. If already configured, the source IP of the Radius packet is set to loopback0.

Then I prepared a playbook with the name config_with_jinja2.yaml to call our template with the parameter “src”. Let run the playbook and check the result. when I use -vvv in ansible-palybook command, all changes will be displayed with JSON format. As you can see, all the configurations are applied as we configured them in the template file.

root@debian:~/ansible-project1# cat config_with_jinja2.yaml
---
- hosts: csr1
  gather_facts: false
  connection: network_cli

  tasks:
    - name: config with jinja2 Template
      ios_config:
        src: "template.j2"
        match: none

Back to: Ansible for Network Engineers > Ansible with Jinja2

Leave a comment

Your email address will not be published. Required fields are marked *