Giter Site home page Giter Site logo

tdorssers / confparser Goto Github PK

View Code? Open in Web Editor NEW
30.0 3.0 12.0 90 KB

Parse Cisco-like configuration files into a JSON-formattable structure using YAML-defined dissectors

License: MIT License

Python 100.00%
python json yaml cisco-ios cisco-ios-xr configuration-file parser cisco-nx-os

confparser's Introduction

published

confparser

This Python module parses a block style document into a dict using dissectors. The main goal is to parse Cisco configuration files, but any vendor is supported as long as the configuration format is block style. The dissector uses indentation or end-of-block markers to determine the hierarchical level of each line.

Overview

A dissector is a YAML formatted nested list of dicts with any of these keys:

Key Description
match A regular expression to match at the beginning of a line. The first unnamed capture group can be used as key or as value. Named capture groups can be used to match more than one group.
search Scan through a line to find a match.
child A nested dissector for a child block. The first unnamed capture group is used as key and the parsed child block as value. The named groups become key values of the child block.
name Specifies the key name. If omitted, the first capture group or whole match is used as a key. Not to be used if only named groups are used.
value Specifies the value. Not to be used if only named groups are used.
parent Forces insertion of a parent dict using specified key.
grouping Consider all named groups as a single branch. Not valid when 'child', 'name' or 'value' are used.
key Index of unnamed group as root of tree of unnamed groups. Named capture groups are added as leaves. 'action' is applied to the root. 'actionall' is applied to other groups. 'child', 'name', 'value' and 'grouping' are ignored.
action Perform specified action on first capture group. Not valid when 'child' is used.
actionall Perform specified action on all named capture groups.

Supported actions are:

Value Description
expand Convert number ranges with hyphens and commas into list of numbers
expand_f Convert Foundry-style port ranges into list of ports
expand_h Convert Huawei-style port ranges into list of ports
split Split string into list of words
list Convert string to list unconditionally
cidr Convert netmask to prefix length in IP address string
cidr_l Convert string to CIDR format and then to list unconditionally
bool Sets the value to False if the line starts with 'no' or else to True

Supported groupings are:

Value Description
implicit Create list of branches when key is duplicate.
explicit Create list of branches unconditionally.

Existing values are not overwritten but will be extended as lists.

Default parameters allow parsing of most configuration files, these are examples of exceptions:

Dialect Parameters
NXOS indent=2
VSP indent=0, eob='exit'

The dissector returns a Tree object which is a nested dict with a parser property that references the dissector used. A dissector can be created from a string or a file and can parse an iterable or a file. Multiple dissectors can be registered to the AutoDissector which uses hints to match a dissector to a file. A hint is a regular expression that is unique to the first 15 lines of a file.

Module contents

confparser.Dissector(stream, name=None) Return a new Dissector object that takes a YAML formatted dissector to parse block style documents. An open file or string object is accepted. The dissector can be optionally named using the name parameter.

confparser.Dissector.from_file(filename, name=None) Alternate constructor that loads a dissector from the specified file.

confparser.Dissector.parse(lines, indent=1, eob=None) Return a new Tree object that contains the parsed document from iterable lines. The number of spaces for an indent is specified by the parameter indent. If a document doesn't use indentation, an end-of-block marker string can be specified.

confparser.Dissector.parse_str(string, indent=1, eob=None) Return a new Tree object that contains the parsed string string.

confparser.Dissector.parse_file(filepath, indent=1, eob=None) Return a new Tree object that contains the parsed file filepath contents.

confparser.AutoDissector(raise_no_match=True) Return a new AutoDissector object that handles automatic selection of parsers based on hints. Set raise_no_match to False to prevent AutoDissector.from_file from raising ValueError when no matching parser is found.

confparser.AutoDissector.register(dissector, hint, **kwarg) Register Dissector object with hint regex and parser keyword arguments.

confparser.AutoDissector.register_map(dissector, function, hint, **kwarg) Register Dissector object with hint regex and parser keyword arguments with function to apply to the parser iteratable, like the map() built-in function.

confparser.AutoDissector.from_file(filename) Return a new Tree object from matching parser for specified file filename. Raises ValueError when no matching parser is found.

confparser.AutoDissector.from_str(string) Return a new Tree object from matching parser for specified string. Raises ValueError when no matching parser is found.

confparser.Tree(parent=None) Subclass of dict. Return a new Tree object.

confparser.Tree.merge_retain(other) Update Tree with dict other and concatenate values in lists of existing keys.

Usage

Basic usage of the module:

import confparser

doc = '''
- match: interface (\S+)
  parent: interface
  child:
  - match: ip address (.*)
    name: ipv4
    action: cidr
  - match: standby (\d+) ip (?P<ip>\S+)
    parent: standby
  - match: switchport trunk allowed vlan (\S+)
    name: allowed_vlan
    action: expand
'''
cfg = '''
interface GigabitEthernet1/1/1
 switchport trunk allowed vlan 10-12,15
!
interface Vlan10
 ip address 10.10.10.2 255.255.255.0
 standby 10 ip 10.10.10.1
!
'''
print(confparser.Dissector(doc).parse_str(cfg))

Output:

{
    "interface": {
        "GigabitEthernet1/1/1": {
            "allowed_vlan": [
                "10",
                "11",
                "12",
                "15"
            ]
        },
        "Vlan10": {
            "ipv4": "10.10.10.2/24",
            "standby": {
                "10": {
                    "ip": "10.10.10.1"
                }
            }
        }
    }
}

Advanced usage

The dissector can also be easily loaded from a file and a configuration file can be parsed directly without opening a file:

import confparser

dissector = confparser.Dissector.from_file('ios.yaml')
print(dissector.parse_file('sample.cfg'))

The following example loads multiple dissectors from file and registers them to the AutoDissector with regex hints. All configuration files in the current directory are parsed using all available processor cores and the result is written to a JSON formatted file. Note that Python 3.3+ is required to use Pool.map with instance methods.

import confparser
import multiprocessing
import glob
import json

if __name__ == '__main__':
    auto = confparser.AutoDissector()
    auto.register(confparser.Dissector.from_file('ios.yaml'), 'version \d+.\d+$')
    auto.register(confparser.Dissector.from_file('nxos.yaml'), 'version \d+.\d+\(\d+\)', indent=2)
    auto.register(confparser.Dissector.from_file('iosxr.yaml'), '!! IOS XR Configuration')
    pool = multiprocessing.Pool()
    result = pool.map(auto.from_file, glob.glob('*.cfg'), 1)
    with open('output.json', 'w') as f:
        json.dump({tree.source:tree for tree in result if tree}, f, indent=4)

The AutoDissector sets Tree.source to the filename of the parsed file and is used as key in the dictionary comprehention.

Installation

Make sure you have Python 2.7+ or 3.2+ installed on your system. Install ipaddress if you are using a 2.7 or 3.2 version. Install PyYAML as this module depends on it using PIP on Linux or macOS:

pip install pyyaml

or on Windows as follows:

python -m pip install pyyaml

or on Ubuntu as follows:

sudo apt-get install python-yaml

To start using this module and run the usage examples above, just copy the directory confparser and the files ios.yaml, nxos.yaml, iosxr.yaml and sample.cfg to your machine.

confparser's People

Contributors

mymodemsux2 avatar mzsombor avatar tdorssers avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

confparser's Issues

Multiple actions - cidr with list

Hello.

First of all thanks for the work you did on this project. I've tried other config parser libraries but your's for some reason is really straightforward to use.

I would like to know if it is possible to apply both the cidr and list actions?

Currently I'm extracting ip addresses from cisco configs using the following dissector:

  - match: ip address (?!.*secondary)(?P<ips>.*)
    actionall: cidr
  - match: ip address (?P<ips>.*) secondary
    actionall: cidr

In the case of a single primary ip address, without secondary, the result is a string and not a list with a single element.
Currently I have some additional code that checks if the result is a list and if not I convert it, but I thought that this is what the list action does so it would make sense to use it together with the cidr action.

Thank you.

Feature: Use matched value as key

Currently trying to parse through BGP running information. I want to be able to have the key as the network address for a neighbor, then use it in another match.

Is there a method to already do this that I'm missing?

I want to be able to com

router bgp 64563
 bgp router-id 10.158.0.130
 neighbor 2.2.2.2 remote-as 64633
 neighbor 2.2.2.2 description connected WHATEVER
 neighbor 2.2.2.2 update-source Loopback0
 neighbor 2.2.2.2 timers 3 9
 neighbor 2.2.2.2 next-hop-self
 neighbor 2.2.2.2 soft-reconfiguration inbound
 neighbor 2.2.2.2 route-map WHATEVER out
 neighbor 3.3.3.3 remote-as 64534
 neighbor 3.3.3.3 description WHATEVER
 neighbor 3.3.3.3 password 7 129840781247012749
 neighbor 3.3.3.3 timers 5 15

So the output would look something like:

{
    "neighbors": {
        "2.2.2.2": {"remote-as": 64633, "description": "connected WHATEVER",},
        "3.3.3.3": {"remote-as": 64633, "description": "connected WHATEVER",},
    }
}

Parsing Trunk

when parsing an interface where exact one vlan is not allowed, i got a huge list of vlans which blows up the json file.

cfg

interface GigabitEthernet3
description LAN-Link-Customer LI:L
switchport trunk allowed vlan 1-665,667-4094
switchport mode trunk
no ip address

json

    "GigabitEthernet3": {
        "description": "LAN-Link-Customer <LI:L>",
        "allowed_vlan": [
            "1",
            "2",
            "3",
	...
            "664",
            "665",
            "667",
            "668",
	...
            "4092",
            "4093",
            "4094"
        ],
        "mode": "trunk",
        "ipv4": false
    },

is there a way to handle / structure this more intelligent ?

Peer groups and peers are siblings

How could one separate peer groups from peers in a config section where they are similarly declared (neighbor ...) and at the same level of the hierarchy.
With a parser configuration like the Cisco_ios the peer groups and the actual member neighbors appear as siblings.
However it would be nicer if the peer group can be a container for the peer-group settings and children for each member peer.
This sample config:

router bgp 100
 router-id 0.0.1.1
 neighbor IBGP peer group
 neighbor IBGP remote-as 64500
 neighbor 172.16.255.12 peer group IBGP
 neighbor 172.16.255.12 description GW2

Decodes as:

{
    "bgp": {
        "local_as": "100",
        "router_id": "0.0.1.1",
        "neighbor": {
            "peer_group": "IBGP",
            "IBGP": {
                "remote_as": "64500"
            },
            "172.16.255.12": {
                "peer_group": "IBGP",
                "description": "GW2"
            }
        }
    }
}

The peer group is at the same level as the neighbor --which is how the config represents it. The peer group name (alpha string) can be matched by a separate rule but is it possible to write rules that would place peers into the peer group containers.

thank you

AttributeError: module 'confparser' has no attribute 'Dissector'

maybe a noob question?
i get this:

AttributeError: module 'confparser' has no attribute 'Dissector'
Traceback:
File "/root/.local/share/virtualenvs/streamlit-pCsZlEh6/lib/python3.9/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 565, in _run_script
    exec(code, module.__dict__)
File "/var/streamlit/pages/Config_conversion.py", line 16, in <module>
    dissector = confparser.Dissector.from_file("iosxr.yaml")

for this code:

sys.path.append('/var/streamlit/pages/confparser/')
import confparser
import multiprocessing
import glob
import json

dissector = confparser.Dissector.from_file("iosxr.yaml")

Unexpected value when parsing config

I am trying to pare Arista EOS config, which is mostly Cisco like, however getting unexpected results:
#!/usr/bin/python3
import sys
import confparser

doc = '''

  • match: interface (\S+)
    parent: interface
    child:
    • match: load-interval (\S+)
      name: load-interval
    • match: switchport (\S+)
      parent: switchport
      child:
      • match: trunk (\S+)
        parent: trunk
        child:
        • match: native (\S+)
    • match: mlag (\S+)
      name: mlag
      '''
      cfg = '''
      interface Port-Channel1
      load-interval 30
      switchport trunk native vlan 100
      switchport trunk allowed vlan 100,200,300-303,400,500
      switchport mode trunk
      mlag 1
      spanning-tree portfast
      !
      interface Port-Channel2
      description et-50/1 Core-1-port-0
      load-interval 30
      switchport trunk native vlan 100
      switchport trunk allowed vlan 100,200,300-303,400,500
      switchport mode trunk
      mlag 2
      spanning-tree portfast
      !
      interface Port-Channel3
      description et-0/0/52-to-Core-2-port-0
      load-interval 30
      switchport trunk native vlan 100
      switchport trunk allowed vlan 100,200,300-303,400,500
      switchport mode trunk
      mlag 3
      spanning-tree portfast
      !
      '''
      print(confparser.Dissector(doc).parse_str(cfg, indent=3))

What I see is this:
{
"interface": {
"Port-Channel1": {
"load-interval": "30",
"switchport": {
"trunk": {},
"mode": {}
},
"mlag": "1"
},
"Port-Channel2": {
"load-interval": "30",
"switchport": {
"trunk": {},
"mode": {}
},
"mlag": "2"
},
"Port-Channel3": {
"load-interval": "30",
"switchport": {
"trunk": {},
"mode": {}
},
"mlag": "3"
}
}
}
However I would expect that trunk block should became a key, "native" subkey and "vlan 100" as a value
{
"interface": {
"Port-Channel1": {
"load-interval": "30",
"switchport": {
"trunk": {
"native": "vlan 100"
},
"mode": "trunk"
},
"mlag": "1"
},
Sorry I am exhausted with ideas, maybe tried all what is possible ))

IOS-XR, global static-routes

i want to parse global and vrf static-routes in ios-xr config. With the parser (iosxr.yaml) its only possible to parse static-routes in vrfs.
It is possible to parse both static-routes ?

Example for ios-xr config:

router static
address-family ipv4 unicast : here begins the global static routes
10.2.1.128/26 172.24.63.44
10.2.4.0/24 172.24.63.44
!
vrf abcdef : here begins the vrf static routes
address-family ipv4 unicast
10.1.192.0/24 172.28.49.140
10.1.194.128/26 172.28.49.140
10.1.195.0/25 172.28.49.140
10.1.196.0/25 172.28.49.140

Regards

Option to force list when duplicate key rather than merging

Example Config:

router static
 address-family ipv4 unicast
  192.0.2.1/32 GigabitEthernet0/0/1/1 1.1.1.1 tag 666 description foo1
  192.0.2.1/32 GigabitEthernet0/0/1/2 2.2.2.2 tag 666 220 description foo2

Dissector:

- match: router static
  name: static
  child:
    - match: address-family (\S+)
      child:
        - match: '([^! ]+)(?: (?=[A-Tn])(?P<interface>\S+))?(?: (?=(?:\S*[\.:])+)(?P<nexthop>\S+))?(?: tag (?P<tag>\d+))?(?: (?P<distance>\d+))?(?: description (?P<description>\S+))?'
    - match: vrf (\S+)
      parent: vrf
      child:
        - match: address-family (\S+)
          child:
          - match: '([^! ]+)(?: vrf (?P<vrf>\S+))?(?: (?=[A-Tn])(?P<interface>\S+))?(?: (?=(?:\S*[\.:])+)(?P<nexthop>\S+))?(?: tag (?P<tag>\d+))?(?: (?P<distance>\d+))?(?: description (?P<description>\S+))?'

Output:

    "static": {
        "ipv4": {
            "192.0.2.1/32": {
                "interface": [
                    "GigabitEthernet0/0/1/1",
                    "GigabitEthernet0/0/1/2"
                ],
                "nexthop": [
                    "1.1.1.1",
                    "2.2.2.2"
                ],
                "tag": [
                    "13",
                    "13"
                ],
                "description": [
                    "foo1",
                    "foo2"
                ],
                "distance": "220"
            },

Expected output:

    "static": {
        "ipv4": {
            "192.0.2.1/32": [
                {
                    "interface": "GigabitEthernet0/0/1/1",
                    "nexthop": "1.1.1.1",
                    "tag": "666",
                    "description": "foo1",
                },
                {
                    "interface": "GigabitEthernet0/0/1/2",
                    "nexthop": "2.2.2.2",
                    "tag": "666",
                    "description": "foo2",
                    "distance": "220",
                },
            ],            

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.