diff --git a/mypy.ini b/mypy.ini index 9819a29a8..6260851d7 100644 --- a/mypy.ini +++ b/mypy.ini @@ -41,7 +41,7 @@ ignore_missing_imports = True [mypy-lxml.*] ignore_missing_imports = True -[mypy-ciscoconfparse] +[mypy-netutils.*] ignore_missing_imports = True [mypy-textfsm] diff --git a/napalm/base/helpers.py b/napalm/base/helpers.py index bf605c333..56a63da1f 100644 --- a/napalm/base/helpers.py +++ b/napalm/base/helpers.py @@ -12,11 +12,11 @@ # third party libs import jinja2 import textfsm -from ciscoconfparse import CiscoConfParse from lxml import etree from netaddr import EUI from netaddr import IPAddress from netaddr import mac_unix +from netutils.config.parser import IOSConfigParser # local modules import napalm.base.exceptions @@ -127,42 +127,84 @@ def load_template( return cls.load_merge_candidate(config=configuration) -def cisco_conf_parse_parents( +def netutils_parse_parents( parent: str, child: str, config: Union[str, List[str]] ) -> List[str]: """ - Use CiscoConfParse to find parent lines that contain a specific child line. + Use Netutils to find parent lines that contain a specific child line. :param parent: The parent line to search for :param child: The child line required under the given parent :param config: The device running/startup config """ - if type(config) == str: - config = config.splitlines() # type: ignore - parse = CiscoConfParse(config) - cfg_obj = parse.find_parents_w_child(parent, child) - return cfg_obj + # Check if the config is a list, if it is a list, then join it to make a string. + if isinstance(config, list): + config = "\n".join(config) + config = config + "\n" + + # Config tree is the entire configuration in a tree format, + # followed by getting the individual lines that has the formats: + # ConfigLine(config_line=' ip address 192.0.2.10 255.255.255.0', + # parents=('interface GigabitEthernet1',)) + # ConfigLine(config_line='Current configuration : 1624 bytes', parents=()) + config_tree = IOSConfigParser(str(config)) + configuration_lines = config_tree.build_config_relationship() + + # Return config is the list that will be returned + return_config = [] + + # Loop over each of the configuration lines + for line in configuration_lines: + # Loop over any line that has a parent line. If there are no parents for a line item then + # the parents is an empty tuple. + for parent_line in line.parents: + if ( + child in line.config_line + and re.match(parent, parent_line) is not None + and parent_line not in return_config + ): + return_config.append(parent_line) + + return return_config -def cisco_conf_parse_objects( +def netutils_parse_objects( cfg_section: str, config: Union[str, List[str]] ) -> List[str]: """ - Use CiscoConfParse to find and return a section of Cisco IOS config. + Use Netutils to find and return a section of Cisco IOS config. Similar to "show run | section " :param cfg_section: The section of the config to return eg. "router bgp" :param config: The running/startup config of the device to parse """ + # Check if the config is a list, if it is a list, then join it to make a string. + if isinstance(config, list): + config = "\n".join(config) + config = config + "\n" + + # Config tree is the entire configuration in a tree format, + # followed by getting the individual lines that has the formats: + # ConfigLine(config_line=' ip address 192.0.2.10 255.255.255.0', + # parents=('interface GigabitEthernet1',)) + # ConfigLine(config_line='Current configuration : 1624 bytes', parents=()) + config_tree = IOSConfigParser(str(config)) + lines = config_tree.build_config_relationship() + + # Return config is the list that will be returned return_config = [] - if type(config) is str: - config = config.splitlines() # type: ignore - parse = CiscoConfParse(config) - cfg_obj = parse.find_objects(cfg_section) - for parent in cfg_obj: - return_config.append(parent.text) - for child in parent.all_children: - return_config.append(child.text) + for line in lines: + # The parent configuration is expected on the function that this is replacing, + # add the parent line to the base of the return_config + if cfg_section in line.config_line: + return_config.append(line.config_line) + # Check if the tuple is greater than 0 + if len(line.parents) > 0: + # Check the eldest parent, if that is part of the config section, then append + # the current line being checked to it. + if cfg_section in line.parents[0]: + return_config.append(line.config_line) + return return_config diff --git a/napalm/ios/ios.py b/napalm/ios/ios.py index 00f52d4da..e8a2dd0b5 100644 --- a/napalm/ios/ios.py +++ b/napalm/ios/ios.py @@ -1333,21 +1333,20 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): } return prefix_limit - # Get BGP config using ciscoconfparse because some old devices dont support "| sec bgp" + # Get BGP config using netutils because some old devices dont support "| sec bgp" cfg = self.get_config(retrieve="running") - cfg = cfg["running"].splitlines() - bgp_config_text = napalm.base.helpers.cisco_conf_parse_objects( - "router bgp", cfg + bgp_config_list = napalm.base.helpers.netutils_parse_objects( + "router bgp", cfg["running"] ) bgp_asn = napalm.base.helpers.regex_find_txt( - r"router bgp (\d+)", bgp_config_text, default=0 + r"router bgp (\d+)", bgp_config_list, default=0 ) # Get a list of all neighbors and groups in the config all_neighbors = set() all_groups = set() bgp_group_neighbors = {} all_groups.add("_") - for line in bgp_config_text: + for line in bgp_config_list: if " neighbor " in line: if re.search(IP_ADDR_REGEX, line) is not None: all_neighbors.add(re.search(IP_ADDR_REGEX, line).group()) @@ -1364,8 +1363,8 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): if neighbor: if bgp_neighbor != neighbor: continue - afi_list = napalm.base.helpers.cisco_conf_parse_parents( - r"\s+address-family.*", bgp_neighbor, bgp_config_text + afi_list = napalm.base.helpers.netutils_parse_parents( + r"\s+address-family.*", bgp_neighbor, bgp_config_list ) try: afi = afi_list[0] @@ -1376,8 +1375,8 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): if "vrf" in str(afi_list): continue else: - neighbor_config = napalm.base.helpers.cisco_conf_parse_objects( - bgp_neighbor, bgp_config_text + neighbor_config = napalm.base.helpers.netutils_parse_objects( + bgp_neighbor, bgp_config_list ) # For group_name- use peer-group name, else VRF name, else "_" for no group group_name = napalm.base.helpers.regex_find_txt( @@ -1467,16 +1466,16 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): "neighbors": bgp_group_neighbors.get("_", {}), } continue - neighbor_config = napalm.base.helpers.cisco_conf_parse_objects( - group_name, bgp_config_text + neighbor_config = napalm.base.helpers.netutils_parse_objects( + group_name, bgp_config_list ) multipath = False - afi_list = napalm.base.helpers.cisco_conf_parse_parents( - r"\s+address-family.*", group_name, neighbor_config + afi_list = napalm.base.helpers.netutils_parse_parents( + r"\s+address-family.*", group_name, bgp_config_list ) for afi in afi_list: - afi_config = napalm.base.helpers.cisco_conf_parse_objects( - afi, bgp_config_text + afi_config = napalm.base.helpers.netutils_parse_objects( + afi, bgp_config_list ) multipath = bool( napalm.base.helpers.regex_find_txt(r" multipath", str(afi_config)) diff --git a/requirements.txt b/requirements.txt index 539088f0d..e937ad0e6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ pyYAML pyeapi>=0.8.2 netmiko>=3.3.0,<4.0.0 junos-eznc>=2.2.1 -ciscoconfparse scp lxml>=4.3.0 ncclient +netutils>=1.0.0