Package: netcop
Netcop — NETwork COnfig ParserThis Python library helps navigating and querying textual (CLI-style) configs of network devices. It may be useful for solving such problems like:listing a device interfacesextracting IP address or VLAN configurations of a network interfacechecking if a particular option is properly set in all the relevant blocksIt does not support modifying and comparing of configs, as theCiscoConfigParsedoes, but provides a nice and simple query API.InstallationNetcop works with both Python 2.7 and Python 3.To install it as a package, use this command:python3 -m pip install netcopVendor compatibilityNetcop works by parsing hierarchical text configs that use newline-separated statements, whitespace indentation of blocks and keywords prefixes as a config path. Thus, it is not limited to a particular vendor's syntax.In particular, these types of configs are supported by Netcop:Cisco IOS, NX-OS, IOS-XRHuawei VRPJuniper JunOSQuagga / FRRThere should be many more of them, I have not checked others yet.However, Netcop does not have any idea of the config semantics — it can't guess the type of data relying by a given config path whether it is a list, an int, a string or an IP address. It's always a user who knows the semantics and treats a given path to be of a particular type.Usage guideLet's say we have this simple config to parse:c=netcop.Conf('''interface Port-channel1no ip address!interface Port-channel2ip address 10.0.0.2 255.255.0.0ip address 10.1.0.2 255.255.0.0 secondary!interface Loopback0ip address 1.1.1.1 255.255.255.255!''')Below are some examples of processing this config.IndexingThe result of parsing looks very much like a Pythondictand Netcop tries hard to keep its API similar to what you can expect from a dict.The key operation you can do with aConfobject is to get a sub-object by a string key with the[]operator.Then we just use any of the following expressions to get a part (slice) of the config as anotherConfobject that has the same API for subsequent queries:c['interface']c['interface Port-channel1']c['interface Port-channel2 ip address']To illustrate the way a config tree is organized, let's take a look at these three queries that return the same result:c['interface Port-channel2 ip address']c['interface']['Port-channel2 ip']['address']c['interface']['Port-channel2']['ip address']Unlike the dict's[], this operator never causes theKeyErrorexception, it returns an emptyConfobject instead.IteratingTo obtain a sequence of interface names, you just need to use.keys()method:[iforiinc['interface'].keys()]Output:['Port-channel1', 'Port-channel2', 'Loopback0']Just as it is with a dict, you can get the same result by iterating over an object:[iforiinc['interface']]Likewise, to get IP addresses assigned to all the interfaces, use this snippet:forifnameinc['interface']:foripinc['interface'][ifname]['ip address']:print(ifname,ip)Output:Port-channel2 10.0.0.2
Port-channel2 10.1.0.2
Loopback0 1.1.1.1Just as it is with adict, you can use.items()to avoid redundant key lookups (resulting output is the same):forifname,iface_cinc['interface'].items():foripiniface_c['ip address']:print(ifname,ip)Iterating over raw config linesThere're 3 ways of traversing lines of the config, or a sub-part of it:Conf.tails: returns the list of matched lines tails, excluding matched prefix. Does
not return lines from the nested blocks. Lines are trimmed from whitespace.cfg=Conf("""snmp host Asnmp host B""")print(cfg["snmp"].tails)Output:["host A", "host B"]Conf.lines(): Iterates over raw matched lines, like.dump()does. Lines may be
trimmed, if you specified prefix that covers the line partially, and may not be
trimmed if the prefix is not specified.Conf.orig_lines(): Like.lines(), but lines are always in the same form as in the
original config (full and untrimmed), no matter which prefix is specified.CheckingIn a bool context aConfobject returns if it's empty, or in other words, if a specified config path exists.# __bool__ operator works:bool(c)==Truebool(c['interface Loopback0'])==Truebool(c['interface Blah'])==Falsebool(c['interface Port-channel1 no ip address'])==Truebool(c['interface Port-channel2 no ip address'])==False# same for __contains__ operator:('interface Loopback0'inc)==True('interface Blah'inc)==False('interface Port-channel1 no ip address'inc)==True('interface Port-channel2 no ip address'inc)==FalseGetting valuesSo far, we have seen how to iterate over multiple values by a given path. What if we're sure that there is only one value for a path? Then you can use any of the scalar properties of aConfobject:.word- a single string keyword.int- an integer value.ip,.cidr(since Python 3.3) - aIPAddressorIPNetworkobject from theipaddressstandard library.tail- all the tailing keywords as a stringYou should note that in contrast to indexing and iterating operations that can never fail, the scalar getters can raiseKeyErrororTypeErrorif there are no values for a given path, or if there are multiple ones.Here is an example:c['interface Loopback0 ip address'].word=='1.1.1.1'c['interface Loopback0 ip address'].ip==IPv4Address('1.1.1.1')c['interface Loopback0 ip address'].tail=='1.1.1.1 255.55.255.255'c['interface Port-channel2 ip address'].ip# KeyError raisedThere are also list properties.ints,.ipsand.cidrs, which are just type-converting iterators and shorthands for expressions like[int(x) for x in c].Wildcard indexing with .expand()Netcop does not use regular expressions to make index lookups, it requires exact keywords in the config path. However, there are cases when it is useful to specify a config path with a pattern:forifname,ipinc.expand('interface po* ip address *'):print(ifname,ip)Resulting output:Port-channel2 10.0.0.2
Port-channel2 10.1.0.2The.expand()method iterates over all possible paths in a config by a given selector with wildcards using glob syntax. It returns tuples with the length equal to the number of wildcard placeholders in a given key.There's a special trailing glob pattern supported~. It means capture the rest of the line and can only occur at the end of the expand query string, separated by space.
Example:>>>list(c.expand("interface * ip address ~"))[("Port-channel2","10.0.0.2 255.255.0.0"),("Port-channel2","10.1.0.2 255.255.0.0 secondary"),("Loopback0","1.1.1.1 255.255.255.255"),]The number of elements in the returned tuple always equals to the number of caputuring globs in the query string.
If the optionalreturn_conf=Truekwarg is passed, there's the extra trailing element in the resulting tuples with the subsequentConfobject for the matched prefix.