diff --git a/netutils/config/parser.py b/netutils/config/parser.py index 0f3227e1..72bad867 100644 --- a/netutils/config/parser.py +++ b/netutils/config/parser.py @@ -331,6 +331,83 @@ def build_config_relationship(self) -> t.List[ConfigLine]: self._update_config_lines(line) return self.config_lines + @staticmethod + def _match_type_check(line: str, pattern: str, match_type: str) -> bool: + """Checks pattern for exact match or regex.""" + if match_type == "exact" and line == pattern: + return True + if match_type == "startswith" and line.startswith(pattern): + return True + if match_type == "endswith" and line.endswith(pattern): + return True + if match_type == "regex" and re.match(pattern, line): + return True + return False + + def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]: + """Returns configuration part for a specific pattern not including parents. + + Args: + pattern: pattern that describes parent. + match_type (optional): Exact or regex. Defaults to "exact". + + Returns: + configuration under that parent pattern. + Example: + >>> config = ''' + ... router bgp 45000 + ... address-family ipv4 unicast + ... neighbor 192.168.1.2 activate + ... network 172.17.1.0 mask''' + >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith") + >>> print(bgp_conf) + ['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask'] + """ + config = [] + for cfg_line in self.build_config_relationship(): + parents = cfg_line.parents[0] if cfg_line.parents else None + if ( + parents + and self._match_type_check(parents, pattern, match_type) + or self._match_type_check(cfg_line.config_line, pattern, match_type) + ): + config.append(cfg_line.config_line) + return config + + def find_children_w_parents( + self, parent_pattern: str, child_pattern: str, match_type: str = "exact" + ) -> t.List[str]: + """Returns configuration part for a specific pattern including parents and children. + + Args: + parent_pattern: pattern that describes parent. + child_pattern: pattern that describes child. + match_type (optional): Exact or regex. Defaults to "exact". + + Returns: + configuration under that parent pattern. + Example: + >>> config = ''' + ... router bgp 45000 + ... address-family ipv4 unicast + ... neighbor 192.168.1.2 activate + ... network 172.17.1.0 mask''' + >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern=" address-family", match_type="regex") + >>> print(bgp_conf) + [' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask'] + """ + config = [] + potential_parents = [ + elem.parents[0] + for elem in self.build_config_relationship() + if self._match_type_check(elem.config_line, child_pattern, match_type) + ] + for cfg_line in self.build_config_relationship(): + parents = cfg_line.parents[0] if cfg_line.parents else None + if parents in potential_parents and self._match_type_check(parents, parent_pattern, match_type): + config.append(cfg_line.config_line) + return config + class BaseBraceConfigParser(BaseConfigParser): """Base parser class for config syntax that demarcates using braces.""" diff --git a/tests/unit/mock/config/parser/find_children/cisco_ios/certificate.txt b/tests/unit/mock/config/parser/find_children/cisco_ios/certificate.txt new file mode 100644 index 00000000..fc57c973 --- /dev/null +++ b/tests/unit/mock/config/parser/find_children/cisco_ios/certificate.txt @@ -0,0 +1,14 @@ +crypto pki trustpoint TP-self-signed-1088426642 + enrollment selfsigned + subject-name cn=IOS-Self-Signed-Certificate-1088426642 + revocation-check none + rsakeypair TP-self-signed-1088426642 +crypto pki trustpoint SLA-TrustPoint + enrollment pkcs12 + revocation-check crl +crypto pki certificate chain TP-self-signed-1088426642 + certificate self-signed 01 + quit +crypto pki certificate chain SLA-TrustPoint + certificate ca 01 + quit \ No newline at end of file diff --git a/tests/unit/mock/config/parser/find_children/cisco_ios/full_config.txt b/tests/unit/mock/config/parser/find_children/cisco_ios/full_config.txt new file mode 100644 index 00000000..c5776a40 --- /dev/null +++ b/tests/unit/mock/config/parser/find_children/cisco_ios/full_config.txt @@ -0,0 +1,279 @@ +! +version 17.1 +service timestamps debug datetime msec +service timestamps log datetime msec +! Call-home is enabled by Smart-Licensing. +service call-home +platform qfp utilization monitor load 80 +platform punt-keepalive disable-kernel-core +platform console serial +! +hostname jcy-bb-01 +! +boot-start-marker +boot-end-marker +! +! +vrf definition MANAGEMENT + ! + address-family ipv4 + exit-address-family + ! + address-family ipv6 + exit-address-family +! +logging userinfo +! +no aaa new-model +call-home + ! If contact email address in call-home is configured as sch-smart-licensing@cisco.com + ! the email address configured in Cisco Smart License Portal will be used as contact email address to send SCH notifications. + contact-email-addr sch-smart-licensing@cisco.com + profile "CiscoTAC-1" + active + destination transport-method http +! +! +! +! +! +! +! +no ip domain lookup +ip domain name infra.ntc.com +! +! +! +login on-success log +! +! +! +! +! +! +! +subscriber templating +! +! +! +! +! +! +multilink bundle-name authenticated +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +crypto pki trustpoint TP-self-signed-1088426642 + enrollment selfsigned + subject-name cn=IOS-Self-Signed-Certificate-1088426642 + revocation-check none + rsakeypair TP-self-signed-1088426642 +! +crypto pki trustpoint SLA-TrustPoint + enrollment pkcs12 + revocation-check crl +! +! +crypto pki certificate chain TP-self-signed-1088426642 + certificate self-signed 01 + quit +crypto pki certificate chain SLA-TrustPoint + certificate ca 01 + quit +! +license udi pid CSR1000V sn 9SAGBHTUEE9 +diagnostic bootup level minimal +archive + path bootflash:archive +memory free low-watermark processor 72107 +! +! +spanning-tree extend system-id +! +username ntc privilege 15 password 0 ntc123 +! +redundancy +! +! +! +! +! +lldp run +cdp run +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +interface Loopback0 + ip address 10.0.10.3 255.255.255.255 +! +interface GigabitEthernet1 + description MANAGEMENT_DO_NOT_CHANGE + ip address 10.0.0.15 255.255.255.0 + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet2 + ip address 10.10.0.6 255.255.255.252 + ip access-group BLOCK_TRANSIT_LINKS in + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet3 + ip address 10.10.0.14 255.255.255.252 + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet4 + description backbone-to-vmx3-ge0/0/3 + ip address 10.10.0.17 255.255.255.252 + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet5 + no ip address + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet6 + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet7 + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet8 + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet9 + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid +! +router bgp 65251 + bgp router-id 10.0.10.3 + bgp log-neighbor-changes + redistribute connected + neighbor 10.10.0.5 remote-as 65251 + neighbor 10.10.0.13 remote-as 65251 + neighbor 10.10.0.18 remote-as 65252 +! +! +virtual-service csr_mgmt +! +ip forward-protocol nd +ip http server +ip http authentication local +ip http secure-server +! +ip route 0.0.0.0 0.0.0.0 10.0.0.2 +ip scp server enable +! +! +logging origin-id hostname +logging host 10.125.1.171 transport udp port 7004 +! +! +snmp-server community ntc-public RO +snmp-server community ntc-private RW +snmp-server community networktocode RO +snmp-server community secure RW +snmp-server location Network to Code - NYC | NY +snmp-server contact John Smith +snmp-server host 10.1.1.1 version 2c networktocode +! +! +! +control-plane +! +! +! +! +banner exec ^C +************************************************************************** +* IOSv is strictly limited to use for evaluation, demonstration and IOS * +* education. IOSv is provided as-is and is not supported by Cisco's * +* Technical Advisory Center. Any use or disclosure, in whole or in part, * +* of the IOSv Software or Documentation to any third party for any * +* purposes is expressly prohibited except as otherwise authorized by * +* Cisco in writing. * +**************************************************************************^C +banner incoming ^C +************************************************************************** +* IOSv is strictly limited to use for evaluation, demonstration and IOS * +* education. IOSv is provided as-is and is not supported by Cisco's * +* Technical Advisory Center. Any use or disclosure, in whole or in part, * +* of the IOSv Software or Documentation to any third party for any * +* purposes is expressly prohibited except as otherwise authorized by * +* Cisco in writing. * +**************************************************************************^C +! +alias exec ntcclear clear platform software vnic-if nv +! +line con 0 + stopbits 1 +line vty 0 4 + privilege level 15 + login local + transport preferred ssh + transport input all +line vty 5 15 + privilege level 15 + login local + transport preferred ssh + transport input all +! +ntp server 10.1.1.1 +ntp server 10.2.2.2 prefer +! +! +! +! +! +netconf-yang +restconf +end \ No newline at end of file diff --git a/tests/unit/mock/config/parser/find_children/cisco_ios/interface.txt b/tests/unit/mock/config/parser/find_children/cisco_ios/interface.txt new file mode 100644 index 00000000..232b74d2 --- /dev/null +++ b/tests/unit/mock/config/parser/find_children/cisco_ios/interface.txt @@ -0,0 +1,24 @@ + no ip address + negotiation auto + no mop enabled + no mop sysid + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid \ No newline at end of file diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py index 68ccd3fb..16e702f1 100644 --- a/tests/unit/test_parser.py +++ b/tests/unit/test_parser.py @@ -4,9 +4,14 @@ import pytest from netutils.config import compliance +from netutils.config.parser import IOSConfigParser MOCK_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "mock", "config", "parser") +MOCK_GETPATH_DIR = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "mock", "config", "parser", "find_children" +) TXT_FILE = "_sent.txt" +CONFIG_FILE = "full_config.txt" parameters = [] for network_os in list(compliance.parser_map.keys()): @@ -14,6 +19,29 @@ parameters.append([_file, network_os]) +find_all_children_parameters = [] +find_all_children_test_cases = [ + ("crypto pki", "certificate.txt"), +] +for network_os in list(compliance.parser_map.keys()): + for _file in glob.glob(f"{MOCK_GETPATH_DIR}/{network_os}/{CONFIG_FILE}"): + for test_case in find_all_children_test_cases: + find_all_children_parameters.append( + (_file, test_case[0], f"{MOCK_GETPATH_DIR}/{network_os}/{test_case[1]}") + ) + +find_children_parents_parameters = [] +find_children_parents_test_cases = [ + ("interface", " no ip", "interface.txt"), +] +for network_os in list(compliance.parser_map.keys()): + for _file in glob.glob(f"{MOCK_GETPATH_DIR}/{network_os}/{CONFIG_FILE}"): + for test_case in find_children_parents_test_cases: + find_children_parents_parameters.append( + (_file, test_case[0], test_case[1], f"{MOCK_GETPATH_DIR}/{network_os}/{test_case[2]}") + ) + + @pytest.mark.parametrize("_file, network_os", parameters) def test_parser(_file, network_os, get_text_data, get_python_data): # pylint: disable=redefined-outer-name truncate_file = os.path.join(MOCK_DIR, _file[: -len(TXT_FILE)]) @@ -37,3 +65,27 @@ def test_incorrect_banner_ios(): ) with pytest.raises(ValueError): compliance.parser_map["cisco_ios"](banner_cfg).config_lines # pylint: disable=expression-not-assigned + + +@pytest.mark.parametrize("_file, pattern, expected", find_all_children_parameters) +def test_find_all_children(_file, pattern, expected, get_text_data): + """Tests get_path method.""" + device_cfg = get_text_data(os.path.join(MOCK_DIR, _file)) + config_tree = IOSConfigParser(str(device_cfg)) + returned_path = config_tree.find_all_children(pattern=pattern, match_type="regex") + expected_path = get_text_data(os.path.join(MOCK_DIR, expected)) + + assert returned_path == expected_path.split("\n") + + +@pytest.mark.parametrize("_file, parent_pattern, child_pattern, expected", find_children_parents_parameters) +def test_find_children_w_parents(_file, parent_pattern, child_pattern, expected, get_text_data): + """Tests get_path_with_children method.""" + device_cfg = get_text_data(os.path.join(MOCK_DIR, _file)) + config_tree = IOSConfigParser(str(device_cfg)) + returned_path = config_tree.find_children_w_parents( + parent_pattern=parent_pattern, child_pattern=child_pattern, match_type="regex" + ) + expected_path = get_text_data(os.path.join(MOCK_DIR, expected)) + + assert returned_path == expected_path.split("\n")