Lab 03 - Parsing Show Commands with Ansible¶
In this lab, we'll introduce a few different methodologies for parsing show commands with Ansible looking at several different built-in Jinja filters. They are regex_search, regex_findall and parse_cli_textfsm.
Task 1 - Use the parse_cli_textfsm Filter¶
In the first task, we'll parse show data on IOS using a pre-built TextFSM template for the "show version" and "show interfaces"command called parse_cli_textfsm. A benefit of using the TextFSM filter and associated templates as opposed to other options is that Network to Code has developed a robust list of parsing templates which render all major vendors and all major show commands into structured data.
The only remaining effort for you is to map the template to the associated show command. Other 3rd party modules will do this for you, but
using Ansible Core ios_command and the parse_cli_textfsm filter requires you to tell Ansible which parsing template to use.
This really provides auto-magic parsing against output for legacy devices which do not have an API giving output with data in a data structure.
Step 1¶
In your terminal on the lab workstation, change into the /home/ntc/labs/lab03 folder, creating it if it doesn't exist.
ntc@ntc-training:~$ mkdir -p /home/ntc/labs/lab03
ntc@ntc-training:~$ cd /home/ntc/labs/lab03
ntc@ntc-training:lab03$
Step 2¶
Create a new file called inventory in the /home/ntc/labs/lab03 directory with the following contents:
[iosxe]
csr[1:3]
[iosxe:vars]
ansible_user=ntc
ansible_password=ntc123
ansible_connection=network_cli
ansible_network_os=ios
Step 3¶
Create a new playbook called parse-ios.yml in the /home/ntc/labs/lab03 directory.
Use the following playbook to gather show version and show interfaces for the IOS devices.
---
- name: PARSING SHOW COMMANDS
hosts: csr1
connection: network_cli
vars:
template_path: "/etc/ntc/ansible/library/ntc-ansible/ntc-templates/ntc_templates/templates/"
show_version_path: "{{ template_path }}cisco_ios_show_version.textfsm"
show_interface_path: "{{ template_path }}cisco_ios_show_interfaces.textfsm"
tasks:
- name: GET SHOW COMMANDS
ios_command:
commands:
- show version
- show interfaces
register: config_data
Note: we've also defined three variables to know the path and file that should be used for the parsing.
Feel free to open the TextFSM templates so you can review them.
Step 4¶
Add two new tasks:
- One that will parse the "show version" and "show interfaces" response using the pre-built TextFSM template and save them as new variables using the
set_factmodule. - One that will debug the new variables.
- name: PARSE CLI TextFSM SHOW INTERFACE
set_fact:
show_version: "{{ config_data.stdout[0] | parse_cli_textfsm(show_version_path) }}"
show_interface: "{{ config_data.stdout[1] | parse_cli_textfsm(show_interface_path) }}"
- name: DISPLAY PARSED DATA
debug:
var: "{{ item }}"
loop:
- show_interface
- show_version
Step 5¶
Execute the playbook. Looking at the relevant debug output, you should see the following:
ntc@ntc-training:lab03$ ansible-playbook -i inventory parse-ios.yml
PLAY [PARSING SHOW COMMANDS] ***************************************************
TASK [GET SHOW COMMANDS] *******************************************************
ok: [csr1]
TASK [PARSE CLI TextFSM SHOW INTERFACE] ****************************************
ok: [csr1]
TASK [DISPLAY PARSED DATA] *****************************************************
ok: [csr1] => (item=show_interface) => {
"ansible_loop_var": "item",
"item": "show_interface",
"show_interface": [
{
"ABORT": "",
"ADDRESS": "5254.0077.3e00",
"BANDWIDTH": "1000000 Kbit",
"BIA": "5254.0077.3e00",
"CRC": "0",
"DELAY": "10 usec",
"DESCRIPTION": "MANAGEMENT_DO_NOT_CHANGE",
"DUPLEX": "Full Duplex",
"ENCAPSULATION": "ARPA",
"HARDWARE_TYPE": "CSR vNIC",
"INPUT_ERRORS": "0",
"INPUT_PACKETS": "92503",
"INPUT_RATE": "0",
"INTERFACE": "GigabitEthernet1",
"IP_ADDRESS": "10.0.0.15/24",
"LAST_INPUT": "00:00:00",
"LAST_OUTPUT": "00:00:00",
"LAST_OUTPUT_HANG": "never",
"LINK_STATUS": "up",
"MEDIA_TYPE": "Virtual",
"MTU": "1500",
"OUTPUT_ERRORS": "0",
"OUTPUT_PACKETS": "87007",
"OUTPUT_RATE": "0",
"PROTOCOL_STATUS": "up",
"QUEUE_STRATEGY": "fifo",
"SPEED": "1000Mbps"
},
.......Output omitted
{
"ABORT": "",
"ADDRESS": "5254.00a6.2d0f",
"BANDWIDTH": "1000000 Kbit",
"BIA": "5254.00a6.2d0f",
"CRC": "0",
"DELAY": "10 usec",
"DESCRIPTION": "",
"DUPLEX": "Full Duplex",
"ENCAPSULATION": "ARPA",
"HARDWARE_TYPE": "CSR vNIC",
"INPUT_ERRORS": "0",
"INPUT_PACKETS": "0",
"INPUT_RATE": "0",
"INTERFACE": "GigabitEthernet9",
"IP_ADDRESS": "",
"LAST_INPUT": "never",
"LAST_OUTPUT": "never",
"LAST_OUTPUT_HANG": "never",
"LINK_STATUS": "administratively down",
"MEDIA_TYPE": "Virtual",
"MTU": "1500",
"OUTPUT_ERRORS": "0",
"OUTPUT_PACKETS": "0",
"OUTPUT_RATE": "0",
"PROTOCOL_STATUS": "down",
"QUEUE_STRATEGY": "fifo",
"SPEED": "1000Mbps"
}
]
}
ok: [csr1] => (item=show_version) => {
"ansible_loop_var": "item",
"item": "show_version",
"show_version": [
{
"CONFIG_REGISTER": "0x2102",
"HARDWARE": [
"CSR1000V"
],
"HOSTNAME": "csr1",
"MAC": [],
"RELOAD_REASON": "reload",
"ROMMON": "IOS-XE",
"RUNNING_IMAGE": "packages.conf",
"SERIAL": [
"9SAGBHTUEE9"
],
"UPTIME": "3 days, 18 hours, 43 minutes",
"VERSION": "17.1.1"
}
]
}
PLAY RECAP *********************************************************************
csr1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
This is incredibly useful data now that it is provided with a list of dictionaries, rather just a big string with no structure to look up a particular piece of data.
Task 2 - Use the regex_ Filters¶
Instead of using TextFSM templates to automatically parse the raw output into a list of dictionaries, you can also use Jinja Filters which search through the show commmand output for particular patterns. This approach requires more effort, but can be useful if you are looking for a specific piece of information in the output and there is no TextFSM template available for your command output to use.
Since we already have the show command output stored in the config_data variable, we can reference the first command output (show version) by looking up list index [0] of config_data and then applying a Jinja Filter to search in that text for a particular pattern using Python regular expression syntax.
In order to find the version information in the show version output, we need to define a regular expression pattern to match our expected output. The expected output of an IOS version output is three sets of one to two integers, separated by a period ., with a potential of having letters at the end, such as 16.08.01a. You can use regular expression helper websites to build out the pattern matching, such as RegEx101. In this lab, the pattern is provided for you, but feel free to explore that site with the raw show version output in the bottom and a pattern in the top field.
First we will add a task using set_fact to transform the config_data variable combined with some key and index lookups along with a Jinja filter into a new variable.
The regex_search filter will allow us to find the specified string applied on the regular expression pattern we have defined to search for the IOS Version string. regex_search will return empty if it cannot find the pattern. In our task, we want the new variable show_version_search to have the IOS version taken out of the big string of command output. We will then debug print out the output of the new fact.
Step 1¶
Add the following tasks to your playbook:
- name: PARSING WITH REGEX_SEARCH
set_fact:
show_version_search: "{{ config_data.stdout[0] | regex_search('(\\d+\\.\\S+)') }}"
- name: DISPLAY REGEX_SEARCH FOR IOS VERSION
debug:
msg: "The device version is {{ show_version_search }}"
Save and execute the playbook.
Step 2¶
Looking at the relevant debug output, you should see the following:
.......Output omitted
TASK [PARSING WITH REGEX_SEARCH] *********************************************************
ok: [csr1]
TASK [DISPLAY REGEX_SEARCH FOR IOS VERSION] **********************************************
ok: [csr1] => {}
MSG:
The device version is 17.01.01
PLAY RECAP *******************************************************************************
csr1 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Step 3¶
Rather than finding one specific string in an output, we may want to find multiple strings according to a regex pattern. For example, when we have the show interface output, we may want to find the names of all the interfaces present.
The regex_findall filter allows us to find all matches of a particular pattern. Now applying this filter against the previous show command output of show interfaces allows us to find all interface names, assuming we have a regular expression pattern which matches the interface names we are expecting. Once we have all the interface names we will debug print that list element by element using a loop to the screen with a simple message.
Add the following tasks to your playbook:
- name: PARSING WITH REGEX_FINDALL
set_fact:
show_interface_find_all: "{{ config_data.stdout[1] | regex_findall('Gig\\w+|Loopback\\w+') }}"
- name: DISPLAY REGEX_FINDALL
debug:
msg: " An interface on this device is {{ item }}"
loop: "{{ show_interface_find_all }}"
Save and execute the playbook.
Step 4¶
Looking at the relevant debug output, you should see the following:
.......Output omitted
TASK [PARSING WITH REGEX_FINDALL] ********************************************************
ok: [csr1]
TASK [DISPLAY REGEX_FINDALL] *************************************************************
ok: [csr1] => (item=GigabitEthernet1) => {}
MSG:
An interface on this device is GigabitEthernet1
ok: [csr1] => (item=GigabitEthernet2) => {}
MSG:
An interface on this device is GigabitEthernet2
ok: [csr1] => (item=GigabitEthernet3) => {}
MSG:
An interface on this device is GigabitEthernet3
ok: [csr1] => (item=GigabitEthernet4) => {}
MSG:
An interface on this device is GigabitEthernet4
ok: [csr1] => (item=GigabitEthernet5) => {}
MSG:
An interface on this device is GigabitEthernet5
ok: [csr1] => (item=GigabitEthernet6) => {}
MSG:
An interface on this device is GigabitEthernet6
ok: [csr1] => (item=GigabitEthernet7) => {}
MSG:
An interface on this device is GigabitEthernet7
ok: [csr1] => (item=GigabitEthernet8) => {}
MSG:
An interface on this device is GigabitEthernet8
ok: [csr1] => (item=GigabitEthernet9) => {}
MSG:
An interface on this device is GigabitEthernet9
PLAY RECAP *******************************************************************************
csr1 : ok=7 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Step 5¶
For reference, the full playbook should look like the following:
---
- name: PARSING SHOW COMMANDS
hosts: csr1
connection: network_cli
vars:
template_path: "/etc/ntc/ansible/library/ntc-ansible/ntc-templates/ntc_templates/templates/"
show_version_path: "{{ template_path }}cisco_ios_show_version.textfsm"
show_interface_path: "{{ template_path }}cisco_ios_show_interfaces.textfsm"
tasks:
- name: GET SHOW COMMANDS
ios_command:
commands:
- show version
- show interfaces
register: config_data
- name: PARSE CLI TextFSM SHOW INTERFACE
set_fact:
show_version: "{{ config_data.stdout[0] | parse_cli_textfsm(show_version_path) }}"
show_interface: "{{ config_data.stdout[1] | parse_cli_textfsm(show_interface_path) }}"
- name: DISPLAY PARSED DATA
debug:
var: "{{ item }}"
loop:
- show_interface
- show_version
- name: PARSING WITH REGEX_SEARCH
set_fact:
show_version_search: "{{ config_data.stdout[0] | regex_search('(\\d+\\.\\S+)') }}"
- name: DISPLAY REGEX_SEARCH FOR IOS VERSION
debug:
msg: "The device version is {{ show_version_search }}"
- name: PARSING WITH REGEX_FINDALL
set_fact:
show_interface_find_all: "{{ config_data.stdout[1] | regex_findall('Gig\\w+|Loopback\\w+') }}"
- name: DISPLAY REGEX_FINDALL
debug:
msg: " An interface on this device is {{ item }}"
loop: "{{ show_interface_find_all }}"