- Do you think python is useful?
- Do you manage a lot of routers, switches or firewalls?
- If you answered yes to both questions, use CiscoConfParse. It helps python understand Cisco (and other vendor) text configs
- Assume you're running linux and already have
pip installed
- The next command will download and install the latest version from pypi
pip install --ugprade ciscoconfparse
[mpenning@localhost]$ python
Python 2.7.3 (default, Mar 14 2014, 11:57:14)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> from ciscoconfparse import CiscoConfParse
>>> parse = CiscoConfParse('/path/to/the/config', syntax='ios')
>>>
>>> # Find all interfaces with an access-list...
>>> parse.find_parents_w_child(r'interface', r'access-group')
['interface FastEthernet0/0', 'interface Dialer1']
>>>
[mpenning@localhost]$ python
Python 2.7.3 (default, Mar 14 2014, 11:57:14)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> from ciscoconfparse import CiscoConfParse
>>> parse = CiscoConfParse('/path/to/the/config', syntax='ios')
>>>
>>> # Standardize switchport configs with 0.5% broadcast storm control
>>> parse.replace_children(r'^interface\s\S+?thernet',
... r'broadcast\slevel\s\S+', 'broadcast level 0.5')
...
[' storm-control broadcast level 0.5']
>>>
>>> # Now save the new version...
>>> parse.save_as('/path/to/the/newconfig')
>>> from ciscoconfparse import CiscoConfParse
>>> BASELINE = """!
... interface GigabitEthernet0/1
... ip address 10.0.0.1 255.255.255.0
... !""".splitlines()
>>> REQUIRED_CONFIG = """!
... interface GigabitEthernet0/1
... ip address 172.16.1.1 255.255.255.0
... no ip proxy-arp
... !""".splitlines()
>>> parse = CiscoConfParse(BASELINE)
>>>
>>> # Build diffs to convert the BASELINE to the REQUIRED config
>>> print '\n'.join(parse.sync_diff(REQUIRED_CONFIG, ''))
interface GigabitEthernet0/1
no ip address 10.0.0.1 255.255.255.0
interface GigabitEthernet0/1
ip address 172.16.1.1 255.255.255.0
no ip proxy-arp
>>>
- The diffs use python's standard difflib
- The diffs are kindof dumb...
- We didn't need to delete the ip address before adding another one
- We did't need to configure
interface GigabitEthernet0/1 twice
- CiscoConfParse's diffs assume 'no ...' removes lines (i.e. Cisco-style)
- Proof-reading is encouraged... don't assume CiscoConfParse does what you want
- Is it possible? Yes (after Github issue #17)
- Be aware that this will convert your Junos configuration to a Cisco-IOS style
>>> from ciscoconfparse import CiscoConfParse
>>> parse = CiscoConfParse('configs/sample_01.junos', syntax='junos', comment='#!')
>>> print '\n'.join(parse.ioscfg[0:5])
!# Last commit: 2015-06-28 13:00:59 CST by mpenning
system
host-name TEST01_EX
domain-name pennington.net
domain-search [ pennington.net lab.pennington.net ]
>>>
- Most people need DNS for their router and switch addresses
- Thus, it's helpful to have DNS functionality in CiscoConfParse
- DNS lookup support:
- Use ciscoconfparse.ccp_util.dns_lookup() for IPv4
- Use ciscoconfparse.ccp_util.dns6_lookup() for IPv6
>>> from ciscoconfparse.ccp_util import dns_lookup
>>>
>>> # Return a dictionary with all info...
>>> dns_lookup('extfw')
{'addrs': ['10.10.255.1'], 'name': 'extfw', 'error': ''}
>>> dns_lookup('extfw')['addrs']
['10.10.255.1']
>>>
>>> # Get the first address...
>>> dns_lookup('extfw')['addrs'][0]
'10.10.255.1'
>>>
>>> from ciscoconfparse.ccp_util import reverse_dns_lookup
>>>
>>> # Return a dictionary with all info...
>>> reverse_dns_lookup('10.10.255.1')
{'addr': '10.10.255.1', 'lookup': '1.255.10.10.in-addr.arpa',
'name': 'extfw.pennington.net.', 'error': ''}
>>>
>>> reverse_dns_lookup('10.10.255.1')['name']
'extfw.pennington.net.'
>>>
- By default, CiscoConfParse stores config lines in something called an IOSCfgLine()
IOSCfgLine() knows the line number
IOSCfgLine() knows where the parents and children are
IOSCfgLine() doesn't automatically know details such as what IP address is on an interface, or whether proxy-arp is configured on it
- By default IOSCfgLine() objects don't pre-parse rich information about the config
>>> from ciscoconfparse import CiscoConfParse
>>> parse = CiscoConfParse('/path/to/config')
>>> intf = parse.find_objects('interface GigabitEthernet0/1')[0]
>>> intf
<IOSCfgLine # 0 'interface GigabitEthernet0/1'>
>>> intf.name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'IOSCfgLine' object has no attribute 'name'
>>>
- Parsing with
CiscoConfParse(CONFIG, factory=True) assigns customized objects to some configuration lines
>>> from ciscoconfparse import CiscoConfParse
>>> parse = CiscoConfParse('/path/to/config', factory=True)
>>> intf = parse.find_objects('interface GigabitEthernet0/1')[0]
>>> intf
<IOSIntfLine # 0 'GigabitEthernet0/1' info: '10.0.0.1/24'>
>>> intf.name
'GigabitEthernet0/1'
>>> intf.ipv4_addr
'10.0.0.1'
>>> intf.ipv4_netmask
'255.255.255.0
>>>
- Parsing with
factory=True is BETA functionality
- Read the source code for documentation
- Only Cisco IOS and Cisco ASA have parsers
- Functionality is limited and I'm slowly adding more
factory=True syntax is somewhat unstable
- I might change the APIs whenever I want to
- If you care about things changing then don't use
factory=True