Parsing Hierarchical Configurations
with ciscoconfparse
There is a Python library named ciscoconfparse that helps you parse Cisco hierarchical configurations. This would include other vendors that are Cisco-like (i.e. configurations that are text-based and that use space-indentation to indicate hierarchy).
For example, consider the following configuration:
interface FastEthernet4
description *** LAN connection (don't change) ***
ip address 10.220.88.20 255.255.255.0
duplex auto
speed auto
The 'description', 'ip address', 'duplex', and 'speed' lines are all configuration items pertaining to interface FastEthernet4. They are inside FastEthernet4 hierarchically.
Programmatic string processing of Cisco configurations can be a pain—ciscoconfparse can meaningfully help with this. It can be used to identify the parent/child relationships and it adds convenient searching capabilities.
Let's look at some examples.
Now one minor note on terminology. There is a ciscoconfparse library which I am going to identify in all lower case. There is also a CiscoConfParse class which I will identify using CamelCase.
First, I install ciscoconfparse using:
pip install ciscoconfparse
Then I have a Cisco configuration file that contains the following:
interface FastEthernet0
no ip address
!
interface FastEthernet1
no ip address
!
interface FastEthernet2
no ip address
!
interface FastEthernet3
no ip address
!
interface FastEthernet4
description *** LAN connection (don't change) ***
ip address 10.220.88.20 255.255.255.0
duplex auto
speed auto
!
interface Vlan1
no ip address
Now let's go into the Python interpreter shell and see what we can do with ciscoconfparse.
First we have to load the CiscoConfParse class.
>>> from ciscoconfparse import CiscoConfParse
Then I use CiscoConfParse to parse my Cisco configuration (which resides in an external file):
>>> cisco_cfg = CiscoConfParse("cisco.txt")
>>> cisco_cfg
<CiscoConfParse: 167 lines / syntax: ios / comment delimiter: '!' / factory: False>
Now that I have a CiscoConfParse object, let's search for all of the config lines that start with the word 'interface'.
>>> rtr_interfaces = cisco_cfg.find_objects(r"^interface")
>>> rtr_interfaces
[<IOSCfgLine # 111 'interface FastEthernet0'>, <IOSCfgLine # 114 'interface FastEthernet1'>, <IOSCfgLine # 117 'interface FastEthernet2'>, <IOSCfgLine # 120 'interface FastEthernet3'>, <IOSCfgLine # 123 'interface FastEthernet4'>, <IOSCfgLine # 129 'interface Vlan1'>]
Notice, I now have a list of six elements matching the six interfaces in the configuration.
Now that I have all these interfaces—what if I want to look at the children of one of them:
>>> intf_fa4 = rtr_interfaces[4]
>>> intf_fa4
<IOSCfgLine # 123 'interface FastEthernet4'>
>>>
>>> intf_fa4.children
[<IOSCfgLine # 124 ' description *** LAN connection (don't change) ***' (parent is # 123)>, <IOSCfgLine # 125 ' ip address 10.220.88.20 255.255.255.0' (parent is # 123)>, <IOSCfgLine # 126 ' duplex auto' (parent is # 123)>, <IOSCfgLine # 127 ' speed auto' (parent is # 123)>]
Since '.children' returns a list, I can iterate over it. This will allow me to view the children in a cleaner way.
>>> for child in intf_fa4.children:
... print child.text
...
description *** LAN connection (don't change) ***
ip address 10.220.88.20 255.255.255.0
duplex auto
speed auto
So that is fairly nice...I could pretty easily search for lines beginning with 'interface' and then retrieve all of the child configuration elements.
You can also directly search for a combination of conditions.
For example, what if I want to find the interfaces that have 'no ip address'.
>>> no_ip_address = cisco_cfg.find_objects_w_child(parentspec=r"^interface", childspec=r"no ip address")
>>> no_ip_address
[<IOSCfgLine # 111 'interface FastEthernet0'>, <IOSCfgLine # 114 'interface FastEthernet1'>, <IOSCfgLine # 117 'interface FastEthernet2'>, <IOSCfgLine # 120 'interface FastEthernet3'>, <IOSCfgLine # 129 'interface Vlan1'>]
I can then print out both the parents and children fairly easily:
>>> for my_int in no_ip_address:
... print my_int.text
... for child in my_int.children:
... print child.text
...
interface FastEthernet0
no ip address
interface FastEthernet1
no ip address
interface FastEthernet2
no ip address
interface FastEthernet3
no ip address
interface Vlan1
no ip address
CiscoConfParse also has a search for parents without a certain child.
For example, what if I want to find all of the interfaces that do NOT have 'no ip address'.
>>> ip_configured = cisco_cfg.find_objects_wo_child(parentspec=r"^interface", childspec=r"no ip address")
>>> ip_configured
[<IOSCfgLine # 123 'interface FastEthernet4'>]
You can also directly look at child and parent relationships.
For example, the 'cisco.txt' file that we loaded contains the following configuration (note, I slightly modified this to hide the key-hash and email address):
ip ssh pubkey-chain
username testuser
key-hash ssh-rsa C0B699C6EAAAE18E9EC099B30F5D01DA invalid@domain.com
Now I can use the find_objects() method to find this configuration element:
>>> ssh_pubkey_chain = cisco_cfg.find_objects(r"^ip ssh pubkey")[0]
>>> ssh_pubkey_chain
<IOSCfgLine # 97 'ip ssh pubkey-chain'>
I can then check if it is a parent and if it is a child:
>>> ssh_pubkey_chain.is_parent
True
>>> ssh_pubkey_chain.is_child
False
I can find its direct children:
>>> ssh_pubkey_chain.children
[<IOSCfgLine # 98 ' username testuser' (parent is # 97)>]
I can find all of its children (note, .children implies a direct child whereas .all_children implies both direct and indirect children).
>>> ssh_pubkey_chain.all_children
[<IOSCfgLine # 98 ' username testuser' (parent is # 97)>, <IOSCfgLine # 99 ' key-hash ssh-rsa C0B699C6EAAAE18E9EC099B30F5D01DA invalid@domain.com' (parent is # 98)>]
There are similar attributes that go the other way i.e. from children to parents (namely 'parent' and 'all_parents').
As you can see (hopefully), the ciscoconfparse library can help you parse Cisco configuration files; it can assist with searching and with establishing parent-child relationships. It is pretty straight-forward to use.
Kirk Byers
@kirkbyers