Skip to content

Welcome to the Jinja Lab

Inventory File and Ansible Playbook

While we won't cover the inventory file or Ansible playbooks in depth in this section, as they will be covered in Ansible, it is important to mention their relationship with Jinja. When using Ansible to automate and render configurations, how does it know what devices I want to create configurations for? This is accomplished with the Ansible inventory file. This will be covered more thoroughly, however, for the purpose of the following examples, we will assume we have an inventory file with six hosts, spine1, spine2, leaf1, leaf2, leaf3, and leaf4. Some examples may show output for all devices, and some may show output for just one, depending on the complexity of the example. Also, every example we show will use the following Ansible Playbook so you understand the destination filename syntax. There are more parts to this playbook, but they will be shown at the end and covered in the Ansible presentation:

Lab Inventory File

---
# Inventory File
all:
    spine1:
    spine2:
    leaf1:
    leaf2:
    leaf3:
    leaf4:

Ansible Playbook

---
- hosts: all
  gather_facts: false
  tasks:

  - name: Ensure directory exists
    file:
      path: "{{lookup('env','PWD')}}/configs/full_config/"
      state: directory
    delegate_to: localhost
    run_once: true

  - name: Create configs
    template:
      src: "{{lookup('env','PWD')}}/templates/full_config.j2"
      dest: "{{lookup('env','PWD')}}/configs/full_config/{{inventory_hostname}}_config.cfg"
    delegate_to: localhost

Files To Modify

Since this section of the lab involves modifying and/or creating Jinja templates, the files we will modify all exist within the templates directory.

Here is a listing of the files and which YAML file the Jinja templates in the files will reference.

Jinja File YAML File
aaa.j2 all.yml
interfaces.j2 any host_vars .yml
management.j2 all.yml
vlans.j2 any host_vars .yml

aaa.j2 Lab Steps

First, let's work through the aaa.j2 Jinja file, which configures the aaa specific configurations.

  1. Create the Jinja template necessary to configure aaa authorization for all switches.

    Hint - AAA Authorization

    Looking at the aaa authentication section above, we can create a similar template:

    aaa authorization exec default {{ aaa_authorization.exec.default }}
    
  2. Create the Jinja template necessary to configure however many RADIUS servers exist in our data model (think: loop).

    Hint - RADIUS Servers

    Since we have two entries in our data model, and could have n number more, we will need to create a for loop. Since this isn't a multi-level nested dictionary, we can get away without using the .items() method.

    {% for rsrv in radius_servers %}
    radius-server host {{ rsrv.host }} vrf {{ rsrv.vrf }} key {{ rsrv.key }}
    {% endfor %}
    

management.j2 Lab Steps

Next, lets work through the management.j2 Jinja file, which contains the rest of the management configurations from our all.yml file.

  1. Create the Jinja template necessary to render the configuration for the RADIUS source interfaces.

    Hint - RADIUS Source Interface

    While we only have one interface defined, there may be more in different vrfs, so we will want to use a for loop. Again, this isn't a multi-level nested dictionary, we can get away without using the .items() method. We can also use the HTTP source interface template to give us a good idea.

    {% for radsrc in ip_radius_source_interfaces %}
    ip radius vrf {{ radsrc.vrf }} source-interface {{ radsrc.name }}
    {% endfor %}
    
  2. Create the Jinja template necessary to render the configuration for any defined DNS servers.

    Hint - DNS Servers

    This is a simple list, so once again a basic for loop will do.

    {% for dns in name_servers %}
    ip name-server {{ dns }}
    {% endfor %}
    
  3. Create the Jinja template necessary to render the clock timezone configuration.

    Hint - Clock Timezone

    This is a simple variable substitution.

    clock timezone {{ clock.timezone }}
    

interfaces.j2 Lab Steps

This section will only have one step. However, it will be more complex as it will combine the .items() loop method and a conditional in the same template.

  1. Create the Jinja template necessary to render the configuration for all interfaces defined in each host_vars/<node>.yml file and any attributes. This is important because, as you will remember from the data model, if an interface is a trunk, we are not specifying VLANs; however, if it's an access port, we are specifying the VLAN. The syntax we seek is written as a comment in the interfaces.j2 file, but it's shown again here.

    interface <intf name>
    description <description>
    switchport mode <mode>
    (if access port) switchport access vlan <vlan>
    
    Hint - Interface Configuration

    This uses both the .items() method in the for loop since we have a multi-level deep nested dictionary and an if conditional to apply the interface VLAN config if the interface is configured as mode access.

    {% for intf, intf_data in interfaces.items() %}
    interface {{ intf }}
    description {{ intf_data.descr }}
    switchport mode {{ intf_data.mode }}
    {% if intf_data.mode == 'access' %}
    switchport access vlan {{ intf_data.vlan }}
    {% endif %}
    {% endfor %}
    

vlans.j2 Lab Steps

For these two steps, we will modify the vlans.j2 Jinja file and create the templates needed to configure the layer 2 VLANs and layer 3 VLAN SVIs where needed.

  1. Create the Jinja templates necessary to render the layer 2 VLAN configurations. The syntax is specified in the comments but is also shown again here.

    vlan <vlanID>
    name <vlanName>
    
    Hint - L2 VLANs

    The data model for this was written to look a little more complex than the Jinja template required to configure it. While we have a nested dictionary, because each key has data, we can use a simple for loop to access the attributes. We will also use a conditional to perform our checks to see if the variable is defined.

    {% if vlans is defined %}
    {% for l2vlan in vlans %}
    vlan {{ l2vlan.id }}
    name {{ l2vlan.name }}
    {% endfor %}
    {% endif %}
    
  2. Create the Jinja templates necessary to render the layer 3 VLAN SVIs where needed. The syntax is specified in the comments but is also shown again here.

    interface vlan <vlanID>
    description <description>
    ip address <host_ip>
    ip virtual-router address <vip>
    
    Hint - L3 VLAN SVIs

    Again, the data model for this was written in a way to look a little more complex. Therefore, we can use a simple for loop to access the attributes with a conditional for variable definition checking.

    {% if svis is defined %}
    {% for l3vlan in svis %}
    interface vlan {{ l3vlan.id }}
    description {{ l3vlan.desc }}
    ip address {{ l3vlan.host_ip }}
    ip virtual-router address {{ l3vlan.vip }}
    {% endfor %}
    {% endif %}
    

Final Test

Now that we have completed the YAML data model and the Jinja templates, we need to build our device configurations; let's see if our work is correct.

From the terminal window, lets switch to the jinjayml directory:

cd jinjayaml

Then, let's issue the following command to run our playbook and build our configs:

make build

If everything was successful, you should have a new directory and sub-directory listed named: configs/full_config/.

Browsing to that directory, we should see 6 files in there, named <hostname>_config.cfg.

Lab Complete!

Congrats working through the Jinja-YAML Content and Labs!