Bookmark this page

Guided Exercise: Generating Configuration Settings from Jinja2 Templates

Generate device configurations using Jinja2 templates, and use templates to manage existing devices.

Outcomes

  • Create Jinja2 templates from existing configuration backups by using variables and facts.

  • Create a playbook to apply the configuration templates on managed network nodes.

As the student user on the workstation machine, use the lab command to prepare your system for this exercise, and to ensure that all required resources are available. This command also creates a project directory with the files needed for the exercise.

[student@workstation ~]$ lab start network-templates

Instructions

You want to use configuration backups as a template to deploy the same configuration across multiple managed network nodes, without the need to manually edit the configuration for each individual node. To do this, you need to inspect an existing configuration file and replace node-specific values with variables and facts.

  1. Open the /home/student/network-templates directory in VS Code. Examine the host variables and group variables for the managed Arista EOS nodes.

    1. Open VS Code and click FileOpen Folder.

    2. Navigate to Homenetwork-templates and click Open.

      Note

      If prompted, select Trust the authors of all files in the parent folder 'student', and then click Yes, I trust the authors.

    3. Inspect the interfaces variable in the inventory/host_vars/arista1.lab.example.com/interfaces.yml file:

      ---
      interfaces:
        - name: Loopback100
          address: 10.10.10.100/32
        - name: Loopback101
          address: 10.10.10.101/32
    4. Inspect the interfaces variable in the inventory/host_vars/arista2.lab.example.com/interfaces.yml file:

      ---
      interfaces:
        - name: Loopback100
          address: 10.10.10.102/32
        - name: Loopback101
          address: 10.10.10.103/32
    5. Inspect the acls variable in the inventory/group_vars/all/acls.yml file:

      ---
      acls:
        - id: 10
          host: 172.25.251.20
        - id: 20
          host: 172.25.251.21
        - id: 30
          host: 172.25.251.22
        - id: 40
          host: 172.25.251.23
  2. Inspect the eos_config.cfg configuration backup file and make a copy of this file called templates/eos_config.j2. Convert the templates/eos_config.j2 file into a Jinja2 template by replacing the node-specific information with expressions using variables and facts.

    1. Inspect the eos_config.cfg file. Note all the instances of the hostname, interfaces, and access lists.

      ! Command: show running-config
      ! device: arista-host01 (vEOS-lab, EOS-4.30.1F)
      !
      hostname arista-host01
      !
      interface Loopback100
         description Loopback100 Interface
         ip address 10.0.30.2/32
      !
      interface Loopback101
         description Loopback101 Interface
         ip address 10.0.31.2/32
      !
      ip access-list test
         10 permit ip host 172.25.202.7 any log
         20 permit ip host 172.25.210.7 any log
      !
    2. Switch to the Terminal tab in VS Code, or change to the /home/student/network-templates directory in a GNOME terminal. Use the cp command to create a copy of the eos_config.cfg configuration backup as the templates/eos_config.j2 template:

      [student@workstation ~]$ cd ~/network-templates
      [student@workstation network-templates]$ cp -v eos_config.cfg \
      templates/eos_config.j2
      'eos_config.cfg' -> 'templates/eos_config.j2'
    3. The hostname is different for every managed Arista EOS node. Replace any instance of the hostname in the templates/eos_config.j2 template with the hostname variable:

      ...output omitted...
      ! device: {{ hostname }} (vEOS-lab, EOS-4.30.1F)
      !
      hostname {{ hostname }}
      ...output omitted...
    4. Every managed Arista EOS node has a different IP address, and the interface names might not always be the same. Use a for statement to replace any instances of interface names and associated IP addresses in the templates/eos_config.j2 template using the host variables for the Arista EOS hosts. Include a comment to state the purpose of the for statement.

      ...output omitted...
      !
      {# loop through each interface #}
      {% for INTERFACE in interfaces %}
      interface {{ INTERFACE['name'] }}
         description {{ INTERFACE['name'] }} Interface
         ip address {{ INTERFACE['address'] }}
      !
      {% endfor %}
      ...output omitted...

      Important

      This Jinja2 template matches the format of the existing eos_config.cfg file where the interface description uses the name of the interface. If the interfaces variable includes a description for each interface, then the description line might be written as description {{ INTERFACES['description'] }} or even description {{ INTERFACES['description'] | default(INTERFACES['name'] + ' Interface') }}.

    5. In this example, the access lists are the same across all managed nodes. Use a for statement to replace the Access Control Entries in the templates/eos_config.j2 template using the group variables for the all group. Include a comment to state the purpose of the for statement.

      ...output omitted...
      ip access-list templates-project
      {# loop through each access list #}
      {% for ACL in acls %}
         {{ ACL['id'] }} permit ip host {{ ACL['host'] }} any log
      {% endfor %}
      !
      end

      Note

      In this example all managed network nodes use the same access list configuration. The access lists could be hard coded into the template file in this instance, but it is still a good idea to use variables. Later in this exercise the same access list variables are used in a template to configure Cisco IOS devices. Using the group variables for the access lists prevents the need to edit hard-coded access lists across multiple template files in a multi-vendor environment.

    6. The final templates/eos_config.j2 template must consist of the following content:

      ! Command: show running-config
      ! device: {{ hostname }} (vEOS-lab, EOS-4.30.1F)
      !
      hostname {{ hostname }}
      !
      {# loop through each interface #}
      {% for INTERFACE in interfaces %}
      interface {{ INTERFACE['name'] }}
         description {{ INTERFACE['name'] }} Interface
         ip address {{ INTERFACE['address'] }}
      !
      {% endfor %}
      ip access-list templates-project
      {# loop through each access list #}
      {% for ACL in acls %}
         {{ ACL['id'] }} permit ip host {{ ACL['host'] }} any log
      {% endfor %}
      !
      end
  3. Examine the host variables for the managed Cisco IOS nodes.

    1. Inspect the interfaces variable in the inventory/host_vars/iosxe1.lab.example.com/interfaces.yml file:

      ---
      interfaces:
        - name: Loopback100
          address: 10.10.10.104
          netmask: 255.255.255.255
        - name: Loopback101
          address: 10.10.10.105
          netmask: 255.255.255.255
    2. Inspect the interfaces variable in the inventory/host_vars/iosxe2.lab.example.com/interfaces.yml file:

      ---
      interfaces:
        - name: Loopback100
          address: 10.10.10.106
          netmask: 255.255.255.255
        - name: Loopback101
          address: 10.10.10.107
          netmask: 255.255.255.255
  4. Inspect the ios_config.cfg configuration backup file and make a copy of this file called templates/ios_config.j2. Convert the templates/ios_config.j2 file into a Jinja2 template by replacing the node-specific information with expressions using variables and facts.

    1. Inspect the ios_config.cfg file. Note all the instances of the hostname, interfaces, and access lists.

      hostname iosxe-host01
      !
      interface Loopback100
       description Loopback100 Interface
       ip address 10.0.32.2 255.255.255.255
      !
      interface Loopback101
       description Loopback101 Interface
       ip address 10.0.33.2 255.255.255.255
      !
      ip access-list extended templates-project
       10 permit ip host 172.25.203.7 any log
       20 permit ip host 172.25.211.7 any log
      !
      end
    2. Use the cp command to create a copy of the ios_config.cfg configuration backup as the templates/ios_config.j2 template:

      [student@workstation network-templates]$ cp -v ios_config.cfg \
      templates/ios_config.j2
      'ios_config.cfg' -> 'templates/ios_config.j2'
    3. The hostname is different for every managed Cisco IOS node. Replace any instances of the hostname in the templates/ios_config.j2 template with the hostname variable.

      ...output omitted...
      hostname {{ hostname }}
      ...output omitted...
    4. Every managed Cisco IOS node has a different IP address, and the interface names might not always be the same. Use a for statement to replace any instances of interface names and associated IP addresses in the templates/ios_config.j2 template using the host variables for the Cisco IOS hosts. Include a comment to state the purpose of the for statement.

      ...output omitted...
      !
      {# loop through each interface #}
      {% for INTERFACE in interfaces %}
      interface {{ INTERFACE['name'] }}
       description {{ INTERFACE['name'] }} Interface
       ip address {{ INTERFACE['address'] }} {{ INTERFACE['netmask'] }}
      !
      {% endfor %}
      ...output omitted...
    5. In this example, the access lists are the same across all managed nodes. Use a for statement to replace the Access Control Entries in the templates/ios_config.j2 template using the group variables for the all group. Include a comment to state the purpose of the for statement.

      ...output omitted...
      ip access-list extended templates-project
      {# loop through each access list #}
      {% for ACL in acls %}
       {{ ACL['id'] }} permit ip host {{ ACL['host'] }} any log
      {% endfor %}
      !
      end
    6. The final templates/ios_config.j2 template must consist of the following content:

      hostname {{ hostname }}
      !
      {# loop through each interface #}
      {% for INTERFACE in interfaces %}
      interface {{ INTERFACE['name'] }}
       description {{ INTERFACE['name'] }} Interface
       ip address {{ INTERFACE['address'] }} {{ INTERFACE['netmask'] }}
      !
      {% endfor %}
      ip access-list extended templates-project
      {# loop through each access list #}
      {% for ACL in acls %}
       {{ ACL['id'] }} permit ip host {{ ACL['host'] }} any log
      {% endfor %}
      !
      end
  5. Create the generate_files.yml playbook to generate the configuration files from the templates.

    1. Begin the playbook with a play that gathers facts on the eos and ios groups:

      ---
      - name: Gather minimal facts
        hosts:
          - eos
          - ios
        gather_facts: true
        tasks: []
    2. Create a second play that generates the configuration files from the templates on the workstation.lab.example.com host:

      ...output omitted...
      - name: Generate configuration files from templates
        hosts: workstation.lab.example.com
        become: true
        become_user: student
        gather_facts: false
        vars:
          config_dir: "{{ playbook_dir }}/configs"
        tasks:
    3. Create a task in the second play that creates the config directory in the network-templates project directory:

      ...output omitted...
          - name: Create the '{{ config_dir }}' directory
            ansible.builtin.file:
              path: "{{ config_dir }}"
              state: directory
    4. Create a second task in the second play that generates the configuration files for the managed Arista nodes:

      ...output omitted...
          - name: Generate Arista configuration files
            vars:
              hostname: "{{ hostvars[item]['ansible_facts']['net_hostname'] }}"
              interfaces: "{{ hostvars[item]['interfaces'] }}"
              acls: "{{ hostvars[item]['acls'] }}"
            ansible.builtin.template:
              src: templates/eos_config.j2
              dest: "{{ config_dir }}/{{ item }}.cfg"
            loop: "{{ groups['eos'] }}"
    5. Create a third task in the second play that generates the configuration files for the managed Cisco nodes. The completed generate_files.yml playbook must consist of the following content:

      ---
      - name: Gather minimal facts
        hosts:
          - eos
          - ios
        gather_facts: true
        tasks: []
      
      - name: Generate configuration files from templates
        hosts: workstation.lab.example.com
        become: true
        become_user: student
        gather_facts: false
        vars:
          config_dir: "{{ playbook_dir }}/configs"
        tasks:
          - name: Create the '{{ config_dir }}' directory
            ansible.builtin.file:
              path: "{{ config_dir }}"
              state: directory
      
          - name: Generate Arista configuration files
            vars:
              hostname: "{{ hostvars[item]['ansible_facts']['net_hostname'] }}"
              interfaces: "{{ hostvars[item]['interfaces'] }}"
              acls: "{{ hostvars[item]['acls'] }}"
            ansible.builtin.template:
              src: templates/eos_config.j2
              dest: "{{ config_dir }}/{{ item }}.cfg"
            loop: "{{ groups['eos'] }}"
      
          - name: Generate Cisco configuration files
            vars:
              hostname: "{{ hostvars[item]['ansible_facts']['net_hostname'] }}"
              interfaces: "{{ hostvars[item]['interfaces'] }}"
              acls: "{{ hostvars[item]['acls'] }}"
            ansible.builtin.template:
              src: templates/ios_config.j2
              dest: "{{ config_dir }}/{{ item }}.cfg"
            loop: "{{ groups['ios'] }}"
  6. Run the generate_files.yml playbook to generate the configuration files from the templates.

    1. Use the ansible-navigator run command to run the generate_files.yml playbook:

      [student@workstation network-templates]$ ansible-navigator run generate_files.yml
      
      PLAY [Gather minimal facts] ******************************************************
      
      TASK [Gathering Facts] ***********************************************************
      ok: [iosxe2.lab.example.com]
      ok: [iosxe1.lab.example.com]
      ok: [arista1.lab.example.com]
      ok: [arista2.lab.example.com]
      
      PLAY [Generate configuration files from templates] *******************************
      
      TASK [Create the '/home/student/Dev/network-templates/configs' directory] ********
      changed: [workstation.lab.example.com]
      
      TASK [Generate Arista configuration files] ***************************************
      changed: [workstation.lab.example.com] => (item=arista1.lab.example.com)
      changed: [workstation.lab.example.com] => (item=arista2.lab.example.com)
      
      TASK [Generate Cisco configuration files] ****************************************
      changed: [workstation.lab.example.com] => (item=iosxe1.lab.example.com)
      changed: [workstation.lab.example.com] => (item=iosxe2.lab.example.com)
      
      PLAY RECAP ***********************************************************************
      arista1.lab.example.com      : ok=1    changed=0    unreachable=0    failed=0 ...
      arista2.lab.example.com      : ok=1    changed=0    unreachable=0    failed=0 ...
      iosxe1.lab.example.com       : ok=1    changed=0    unreachable=0    failed=0 ...
      iosxe2.lab.example.com       : ok=1    changed=0    unreachable=0    failed=0 ...
      workstation.lab.example.com  : ok=3    changed=3    unreachable=0    failed=0 ...
    2. Examine the configs/arista1.lab.example.com.cfg file:

      ! Command: show running-config
      ! device: arista1.lab.example.com (vEOS-lab, EOS-4.30.1F)
      !
      hostname arista1.lab.example.com
      !
      interface Loopback100
         description Loopback100 Interface
         ip address 10.10.10.100/32
      !
      interface Loopback101
         description Loopback101 Interface
         ip address 10.10.10.101/32
      !
      ip access-list templates-project
         10 permit ip host 172.25.251.20 any log
         20 permit ip host 172.25.251.21 any log
         30 permit ip host 172.25.251.22 any log
         40 permit ip host 172.25.251.23 any log
      !
      end
    3. Examine the configs/iosxe1.lab.example.com.cfg file:

      hostname iosxe1.lab.example.com
      !
      interface Loopback100
       description Loopback100 Interface
       ip address 10.10.10.104 255.255.255.255
      !
      interface Loopback101
       description Loopback101 Interface
       ip address 10.10.10.105 255.255.255.255
      !
      ip access-list extended templates-project
       10 permit ip host 172.25.251.20 any log
       20 permit ip host 172.25.251.21 any log
       30 permit ip host 172.25.251.22 any log
       40 permit ip host 172.25.251.23 any log
      !
      end
  7. Create the deploy_config.yml playbook that uses the appropriate *_config modules to deploy the configuration files to the managed nodes. Match the appropriate configuration file to the managed nodes with the correct corresponding network OS.

    1. Create the deploy_config.yml playbook with the following content:

      ---
      - name: Configure access lists
        hosts:
          - eos
          - ios
        gather_facts: false
        tasks:
          - name: Deploy Arista configuration
            when: ansible_network_os == 'arista.eos.eos'
            arista.eos.eos_config:
              src: configs/{{ inventory_hostname }}.cfg
      
          - name: Deploy Cisco configuration
            when: ansible_network_os == 'cisco.ios.ios'
            cisco.ios.ios_config:
              src: configs/{{ inventory_hostname }}.cfg
  8. Run the deploy_config.yml playbook to configure the managed network nodes.

    1. Use the ansible-navigator run command to run the deploy_config.yml playbook:

      [student@workstation network-templates]$ ansible-navigator run deploy_config.yml
      
      PLAY [Configure access lists] ****************************************************
      
      TASK [Deploy Arista configuration] ***********************************************
      skipping: [iosxe1.lab.example.com]
      skipping: [iosxe2.lab.example.com]
      [WARNING]: To ensure idempotency and correct diff the input configuration lines
      should be similar to how they appear if present in the running configuration on
      device including the indentation
      changed: [arista2.lab.example.com]
      changed: [arista1.lab.example.com]
      
      TASK [Deploy Cisco configuration] ************************************************
      skipping: [arista1.lab.example.com]
      skipping: [arista2.lab.example.com]
      changed: [iosxe1.lab.example.com]
      changed: [iosxe2.lab.example.com]
      
      PLAY RECAP ***********************************************************************
      arista1.lab.example.com   : ok=1     changed=1     unreachable=0     failed=0 ...
      arista2.lab.example.com   : ok=1     changed=1     unreachable=0     failed=0 ...
      iosxe1.lab.example.com    : ok=1     changed=1     unreachable=0     failed=0 ...
      iosxe2.lab.example.com    : ok=1     changed=1     unreachable=0     failed=0 ...
  9. Run the show_run.yml playbook to view the running configurations of the devices. Examine the output of the playbook to verify the templates were applied.

    1. Use the ansible-navigator run command to run the show_run.yml playbook. Note that the output of the running configuration for each device reflects the hostname, interfaces, and acls variables used in the Jinja2 templates.

      [student@workstation network-templates]$ ansible-navigator run show_run.yml
      ...output omitted...
      ok: [arista1.lab.example.com] => {
      ...output omitted...
                      "! device: arista1.lab.example.com (vEOS-lab, EOS-4.30.1F)",
      ...output omitted...
                      "hostname arista1.lab.example.com",
      ...output omitted...
                      "interface Loopback100",
                      "   description Loopback100 Interface",
                      "   ip address 10.10.10.100/32",
                      "!",
                      "interface Loopback101",
                      "   description Loopback101 Interface",
                      "   ip address 10.10.10.101/32",
      ...output omitted...
                      "ip access-list templates-project",
                      "   10 permit ip host 172.25.251.20 any log",
                      "   20 permit ip host 172.25.251.21 any log",
                      "   30 permit ip host 172.25.251.22 any log",
                      "   40 permit ip host 172.25.251.23 any log",
      ...output omitted...
      ok: [iosxe1.lab.example.com] => {
      ...output omitted...
                      "hostname iosxe1.lab.example.com",
      ...output omitted...
                      "interface Loopback100",
                      " description Loopback100 Interface",
                      " ip address 10.10.10.104 255.255.255.255",
                      "!",
                      "interface Loopback101",
                      " description Loopback101 Interface",
                      " ip address 10.10.10.105 255.255.255.255",
      ...output omitted...
                      "ip access-list extended templates-project",
                      " 10 permit ip host 172.25.251.20 any log",
                      " 20 permit ip host 172.25.251.21 any log",
                      " 30 permit ip host 172.25.251.22 any log",
                      " 40 permit ip host 172.25.251.23 any log",
      ...output omitted...
  10. Close the /home/student/network-templates directory in VS Code. If you are using the GNOME terminal, return to the /home/student directory.

    1. Click FileClose Folder in VS Code to close the /home/student/network-templates directory.

    2. If you are using the GNOME terminal, run the cd command to return to the student home directory:

      [student@workstation network-templates]$ cd

Finish

On the workstation machine, use the lab command to complete this exercise. This step is important to ensure that resources from previous exercises do not impact upcoming exercises.

[student@workstation ~]$ lab finish network-templates

Revision: do457-2.3-7cfa22a