Using Ansible to Fetch Information from IOS Devices


In this article, we’ll look at various Ansible modules that can be used to fetch information from Cisco IOS devices: ios_facts, snmp_facts and ios_command. Regardless of the used module, we’ll store the output in a JSON file that can easily be used in other tools.

ios_facts

ios_facts is the obvious choice. It connects to the target using SSH and gathers information with CLI commands. Here’s a sample playbook:

 $ cat fetch_iosfacts.yml
- hosts: ios
  connection: local
  vars:
  tasks:
    - name: ssh facts
      register: iosfacts_out
      ios_facts:
        provider: "{{ credentials }}"
    - copy: content="{{ iosfacts_out | to_nice_json }}" dest="out/{{inventory_hostname}}_iosfacts.json"

The resulting file is very similar to ordinary Ansible facts you’d get from any other non-IOS host:

{
    "ansible_facts": {
        "ansible_net_all_ipv4_addresses": [
            "192.168.43.19"
        ],
        "ansible_net_all_ipv6_addresses": [],
        "ansible_net_filesystems": [
            "fstage:",
            "flash:",
            "system:",
            "tmpsys:",
        ],
        "ansible_net_hostname": "nealab19",
        "ansible_net_image": "flash:/c3750e-universalk9-mz.150-2.SE6/c3750e-universalk9-mz.150-2.SE6.bin",
        "ansible_net_interfaces": {
            "FastEthernet0": {
                "bandwidth": 100000,
                "description": null,
                "duplex": null,
                "ipv4": null,
                "lineprotocol": "down ",
 ... 

snmp_facts

snmp_facts are very similar to ios_facts, but as the name suggests, obtained by walking various SNMP MIBs. There is generally a bit less information, but with the upside that this module will work for non-Cisco stuff and devices without SSH access.

The sample playbook looks very similar, but of course we need to supply the appropriate SNMP v2 or v3 parameters:

 $ cat fetch_snmpfacts.yml
- hosts: ios
  connection: local
  vars:
  tasks:
    - snmp_facts:
        host: "{{ credentials.host }}"
        version: "{{ credentials.snmpversion }}"
        community: "{{ credentials.snmpcommunity }}"
      register: snmpfacts_out
    - copy: content="{{ snmpfacts_out | to_nice_json }}" dest="out/{{inventory_hostname}}_snmpfacts.json"

The output is comparable as well. You get some additional information from  the MIB-2 System MIB, at the cost of having the interfaces indexed by OID instead of their name.

{
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "192.168.43.19"
        ],
        "ansible_interfaces": {
            "14502": {
                "adminstatus": "up",
                "description": "",
                "ifindex": "14502",
                "mac": "f0f742abee40",
                "mtu": "1500",
                "name": "FastEthernet0",
                "operstatus": "down",
                "speed": "1500"
            },
            ...
        },
        "ansible_sysdescr": "Cisco IOS Software, C3750E Software...
        "ansible_syslocation": "",
        "ansible_sysname": "nealab19.netnea.com",
        "ansible_sysobjectid": "1.3.6.1.4.1.9.1.516",
        "ansible_sysuptime": "208066918"
    }...

ios_command

A big shortcoming of the previous modules is that the list of discovered attributes is not extendable. To collect custom data, you can resort to the ios_command module. However to get nicely formatted values in your JSON, some extraction work on the command output is required. In this sample playbook, we extract all SNMP trap destinations and format them nicely as array:

 $ cat fetch_command.yml
- hosts: ios
  connection: local
  vars:
    trapdests: []
  tasks:
    - name: show snmp hosts
      register: command_out
      ios_command:
        commands: "show snmp host  | include Notification host"
        provider: "{{ credentials }}"

    - name: extract values
      set_fact:
        trapdests: "{{trapdests + [item.split()[2]]}}"
      with_items: "{{command_out.stdout_lines}}"
    - copy: content="{{ trapdests | to_nice_json }}" dest="out/{{inventory_hostname}}_trapdests.json"

The set_fact block loops over all output lines, then extracts only the destination IP and appends it to the trapdests array. So from the raw command output

nealab19#show snmp host  | include Notification host
Notification host: 192.168.25.8          udp-port: 162      type: trap
Notification host: 192.168.25.9          udp-port: 162      type: trap

we arrive at this JSON array:

[
    "192.168.25.8",
    "192.168.25.9"
]

Running the sample playbooks

To run these samples yourself, you’ll need a few additional files the weren’t mentioned yet. First, we need an inventory defining the ios group and all devices we’d like to collect data from:

 $ cat inventory
[ios]
nealab19
nealab23

Also we have used the credentials structure all over the place, which is centrally defined in our group vars:

 $ cat group_vars/ios
ansible_python_interpreter: /opt/ansible/pyenv/bin/python

credentials:
  host: "{{ inventory_hostname }}"
  username: "demo"
  password: "demo"
  snmpversion: v2c
  snmpcommunity: demo

Of course storing passwords and communities in plain text is generally a bad idea. For production usage, we recommend using the vault. In case you’re using another python than /usr/bin/python to execute Ansible modules, you’ll need to set the ansible_python_interpreter as well.
Once all of this is in place, you should be able to run the playbooks like e.g.

$ ansible-playbook -i inventory fetch_command.yml

Please note that you need at least Ansible 2.2 to have all of the modules.

IOS-XR, NX-OS and others

For these IOS variants, you can find specialized versions of the ios_-modules like iosxr_facts, nxos_command and many more. For a full list of Ansible networking-related functionality, check out this page. Happy hacking!