Getting Config Values with CiscoConfParse

Let’s tackle a common problem. How do you get specific values from a configuration?

For sure, you could use traditional Python techniques, such as Python’s re module; however, the re module is rather cumbersome when you’re retrieving a lot of config values.

First let’s cover some details of how ciscoconfparse2 represents configuration commands.

Intro to BaseCfgLine

ciscoconfparse2 uses BaseCfgLine instances to represent all configuration commands. You can see that in the python interpreter.

>>> from ciscoconfparse2.models_cisco import IOSCfgLine
>>> from ciscoconfparse2.ccp_abc import BaseCfgLine
>>> from ciscoconfparse2 import CiscoConfParse
>>>
>>> parse = CiscoConfParse(['hostname Foo'], syntax='ios')
>>> cmd = parse.find_objects('hostname')[0]
>>>
>>> cmd
<IOSCfgLine # 0 'hostname Foo'>
>>>
>>> issubclass(IOSCfgLine, BaseCfgLine)
True
>>>

We can see above that:

That means all the things you can do with BaseCfgLine can be done with IOSCfgLine.

BaseCfgLine has .parent and .children attributes

BaseCfgLine also keep information about command parents and children. Consider this config:

>>> from ciscoconfparse2 import CiscoConfParse
>>>
>>> config = """!
... interface Ethernet1/1
...   ip address 192.0.2.1 255.255.255.0
...   shutdown
... !"""
>>>
>>> parse = CiscoConfParse(config.splitlines())
>>>
>>> intf_cmd = parse.find_objects('interface Ethernet1/1')[0]
>>> intf_cmd.linenum
1
>>> intf_cmd.parent
<IOSCfgLine # 1 'interface Ethernet1/1'>
>>> intf_cmd.children
[<IOSCfgLine # 2 '  ip address 192.0.2.1 255.255.255.0' (parent is # 1)>, <IOSCfgLine # 3 '  shutdown' (parent is # 1)>]
>>>
>>>
>>> addr_cmd = parse.find_objects('ip address 192.0.2.1')[0]
>>> addr_cmd.linenum
2
>>> addr_cmd.parent
<IOSCfgLine # 1 'interface Ethernet1/1'>
>>> addr_cmd.children
[]
>>>

We see the .parent and .children attributes are linked to other IOSCfgLine instances in the configuration.

String methods

BaseCfgLine is the building-block for all ciscoconfparse2 commands. In some ways a BaseCfgLine acts like a string, but it can also do more than just a string… for instance, it holds the line number of the command it represents.

>>> from ciscoconfparse2 import CiscoConfParse
>>> parse = CiscoConfParse(['hostname Foo'], syntax='ios')
>>> cmd = parse.find_objects('hostname')[0]
>>>
>>> cmd
<IOSCfgLine # 0 'hostname Foo'>
>>> cmd.linenum
0
>>>
>>> # Use cmd with a sub-string match
>>> 'Foo' in cmd
True
>>>
>>> # Split cmd on whitespace like a string
>>> cmd.split()
['hostname', 'Foo']
>>>
>>> # Get the hostname using string manipulation
>>> hostname = cmd.split()[-1]
>>> hostname
'Foo'
>>>

Regular expressions

CiscoConfParse uses certain methods directly on CiscoConfParse objects enhance how we get values from a configuration:

Note

Some of the examples below could extract values with simple string manipulation, but we use regex to illustrate other ways to find commands and extract values.

For the next examples, we will use this configuration…

! Filename: short.conf
!
hostname IAHS1MDF-AR01A
!
vlan 10
 name 192.0.2.0_24_HoustonUsers_S1_Bldg_MDF
vlan 20
 name 128.66.0.0_24_HoustonPrinters_S1_Bldg_MDF
!
interface Vlan10
 description Connection to Houston office LAN switches
 ip address 192.0.2.2 255.255.255.0
 ip helper-address 198.51.100.12
 ip helper-address 203.0.113.12
 standby 10 ip 192.0.2.1
 standby 10 priority 110
 arp timeout 240
 no ip proxy-arp
!
interface Vlan20
 description Connection to Houston printer subnet
 ip address 128.66.0.2 255.255.255.0
 standby 20 ip 128.66.01
 standby 20 priority 110
!

re_match_typed(): Get a value from an object

Let’s suppose we want the hostname of short.conf above. One approach is to use find_objects() and then use re_match_typed() to get the hostname:

>>> from ciscoconfparse2 import CiscoConfParse
>>> parse = CiscoConfParse('short.conf')
>>> cmd = parse.find_objects(r'^hostname')[0]
>>> hostname = cmd.re_match_typed(r'^hostname\s+(\S+)', default='')
>>> hostname
'IAHS1MDF-AR01A'
>>>

Take note of the regex we used: r'hostname\s+(\S+)'. This regex has a capture group (bounded by the parenthesis), which re_match_typed() requires. re_match_typed() uses the contents of this capture group to return the value.

This technique is fine, but we have to tell Python to iterate over all config objects with find_objects() and then we extract the hostname from that object.

What if there was a way to get the hostname without calling find_objects()? As it happens, re_match_iter_typed() does it for you.

re_match_iter_typed(): Iterate over all children and get a value

re_match_iter_typed() iterates over child objects and returns the first value it finds. This is very useful because re_match_iter_typed() does all the finding / iteration for us.

>>> from ciscoconfparse2 import CiscoConfParse
>>> parse = CiscoConfParse('short.conf')
>>> hostname = parse.re_match_iter_typed(r'^hostname\s+(\S+)', default='')
>>> hostname
'IAHS1MDF-AR01A'
>>>

Take note of the regex we used: r'hostname\s+(\S+)'. This regex has a capture group (bounded by the parenthesis), which re_match_iter_typed() requires. re_match_iter_typed() uses the contents of this capture group to return the value.

This code is better than the previous example, because it eliminates the call to find_objects() that we used above.

However, there are still times when you need to call find_objects(); one example is when you need to get the HSRP address from an interface.

>>> from ciscoconfparse2 import CiscoConfParse
>>> parse = CiscoConfParse('short.conf')
>>> intf_cmd = parse.find_objects(r'^interface\s+Vlan10$')[0]
>>> hsrp_ip = intf_cmd.re_match_iter_typed(r'standby\s10\sip\s(\S+)',
...     default='')
>>> hsrp_ip
'192.0.2.1'
>>>

The reason we had to call find_objects() is so we can get the specific inteface object that contains the HSRP address in question.

You may be wondering, “Why does this method have typed in its name?”. This is because re_match_iter_typed() can return the value cast as a python type. By default, all return values are cast as a Python str.

The following example looks for the ARP timeout on interface Vlan10, and returns it cast as a Python int.

>>> from ciscoconfparse2 import CiscoConfParse
>>> parse = CiscoConfParse('short.conf')
>>> intf_cmd = parse.find_objects(r'^interface\s+Vlan10$')[0]
>>> arp_timeout = intf_cmd.re_match_iter_typed(r'arp\s+timeout\s+(\d+)',
...     result_type=int, default=4*3600)
>>> arp_timeout
240
>>>

Finally, let’s talk about two more re_match_iter_typed() keywords: default and untyped_default.

re_match_iter_typed() has a default keyword, which specifies what the default value should be if the regular expression doesn’t match the configuration. The value in default is automatically cast as the result_type.

However, there may be times when you don’t want default’s value to be cast as result_type. If you find yourself in that situation, you can call re_match_iter_typed() with untyped_default=True.

>>> from ciscoconfparse2 import CiscoConfParse
>>> parse = CiscoConfParse('short.conf')
>>> intf_cmd = parse.find_objects(r'^interface\s+Vlan20$')[0]
>>> arp_timeout = intf_cmd.re_match_iter_typed(r'arp\s+timeout\s+(\d+)',
...     result_type=int,
        untyped_default=True, default='__no_explicit_value__')
>>> arp_timeout
'__no_explicit_value__'
>>>

Getting multiple values from an interface with re_list_iter_typed()

re_match_typed() and re_match_iter_typed() do not` return mutliple values.

However, re_list_iter_typed() will return multiple values.

Suppose we want to get all the DHCP helper-addresses from an interface. The best way to do this is to manually iterate over the children and append the values we want to a list.

This script will get all the DHCP helper-addresses from Vlan10:

>>> from ciscoconfparse2 import CiscoConfParse
>>> parse = CiscoConfParse('short.conf')
>>>
>>> # Iterate over matching interfaces
>>> intf_cmd = parse.find_objects(r'^interface\s+Vlan10$')[0]
>>> retval = intf_cmd.re_list_iter_typed("ip helper-address (\S.*)")
>>> retval
['198.51.100.12', '203.0.113.12']
>>>