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:
show version
show interfaces
show running-config
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:
version: "14.22.0F"
hostname: "hostname"
mgmt_interface_name: "int 1/1"
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.

Leave a Reply

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