Automating Networks with Ansible – Part 3

In part 1, we covered why to use Ansible. In part 2, we covered how to start using Ansible. So far, we’ve installed Ansible, setup a network inventory, and ran a playbook that gathers info and “facts” from our network inventory.

But what practical things can we actually do with all of this info we have now?

Good news! We can use our fact collection role as the foundation for everything we do and build next. Everything that Ansible can be configured or orchestrated to do will always involve variables, and we’ve just given ourselves a literal dictionary worth of automation logic to use!

Ansible Fact Gathering

Let’s take a step back for a moment and talk about what this fact gathering thing is all about.

The fact role I use does two things. First, I use Ansible’s native configuration parsers, Network Resource Modules (more on that later), to parse the raw device config. Second, I use custom facts that I set from running ad-hoc commands.

In a mixed version/device environment where fact modules can’t run against all devices, or if you just need to expand your playbook functionality, you can parse the running config to set custom facts.

As an example, the ios_command module will send commands, register the CLI output, find a specific string, and set it to a custom fact. Command modules are used to send arbitrary commands and return info (e.g., show run, description) — they cannot make changes the running config.

---
- name: collect output from ios device
  ios_command:
  commands: 
    show version
    show interfaces
    show running-config
    show ip interface brief | include {{ ansible_host }}
  register: output

- name: set version fact
  set_fact:
    cacheable: true
    version: "{{ output.stdout[0] | regex_search('Version (\S+)', '\1') | first }}"

- name: set hostname fact
  set_fact:
    cacheable: true
    hostname: "{{ output.stdout[2] | regex_search('\nhostname (.+)', '\1') | first }}"

- name: set management interface name fact
  set_fact:
    cacheable: true
    mgmt_interface_name: "{{ output.stdout[3].split()[0] }}"

- name: set config_lines fact
  set_fact:
    config_lines: "{{ output.stdout_lines[1] }}"

This playbook will run four commands against an IOS host:

1. show version
2. show interfaces
3. show running-config
4. show ip interface brief | include {{ hostname }}

Ansible will then search, parse, split, or otherwise strip out the interesting information, to give you the following facts:

1. version: "14.22.0F"
2. hostname: "hostname"
3. mgmt_interface_name: "int 1/1"
4. config_lines: "full running config ..."

Using Facts as Logic and Conditionals

Let’s take a look at a real world example. Jinja templates are the bread and butter of Ansible configuration, and we can use device variables to determine how which devices get which configs. Everything we picked up during our fact collection run is fair game.

For example, our fact collection playbook gathered this fact:

ansible_net_version: 14.22.0F

We can use that fact in a playbook, to determine whether to place a specific configuration based on the firmware version.

Here’s an example of a Cisco AAA config template that uses the OS/firmware version as the primary way to determine which commands to send:

{% if ansible_net_version.split('.')[0]|int < 15 %}
  aaa authentication login default group tacacs+ line enable
  aaa authentication login securid group tacacs+ line enable
  aaa authentication enable default group tacacs+ enable
  ...
{% endif %}

{% if ansible_net_version.split('.')[0]|int >= 15 %}
  {% if site == "pacific" %}
    aaa authentication login default group pst line enable
    ...
   {% elif site == "mountain" %}
    aaa authentication login default group mst line enable
    ...
   {% endif %}
 {% endif %}

In the playbook above, our first if statement is splitting the firmware version (ansible_net_version) variable into groups at decimals, registering the first group of numbers ([0]) as integers, and determining if that number is less than 15. Version 14 will match the first config stanza, and it will apply that group of configuration lines to that device.

However, if our firmware version matches 15 and above, then Ansible will apply the second config stanza instead. In this case, this scenario tackles the different configuration and command syntaxes that differ between newer and older devices.

Depending on the complexity of your particular network, logic and conditional checks like this will be come invaluable. And if this all makes sense up to this point, then congratulations, you’re well on your way to automating your network!

One Reply to “Automating Networks with Ansible – Part 3”

Leave a Reply

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