diff --git a/hyperglass/parsing/mikrotik.py b/hyperglass/parsing/mikrotik.py deleted file mode 100644 index de63bf5..0000000 --- a/hyperglass/parsing/mikrotik.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Mikrotik Output Parsing Functions.""" - -# Standard Library -import re - -END_COLUMNS = ("DISTANCE", "STATUS") - - -def parse_mikrotik(output: str): - """Parse Mikrotik output to remove garbage.""" - if output.split()[-1] in END_COLUMNS: - # Mikrotik shows the columns with no rows if there is no data. - # Rather than send back an empty table, send back an empty - # response which is handled with a warning message. - output = "" - else: - remove_lines = () - all_lines = output.splitlines() - # Starting index for rows (after the column row). - start = 1 - # Extract the column row. - column_line = " ".join(all_lines[0].split()) - - for i, line in enumerate(all_lines[1:]): - # Remove all the newline characters (which differ line to - # line) for comparison purposes. - normalized = " ".join(line.split()) - - # Remove ansii characters that aren't caught by Netmiko. - normalized = re.sub(r"\\x1b\[\S{2}\s", "", normalized) - - if column_line in normalized: - # Mikrotik often re-inserts the column row in the output, - # effectively 'starting over'. In that case, re-assign - # the column row and starting index to that point. - column_line = re.sub(r"\[\S{2}\s", "", line) - start = i + 2 - - if "[Q quit|D dump|C-z pause]" in normalized: - # Remove Mikrotik's unhelpful helpers from the output. - remove_lines += (i + 1,) - - # Combine the column row and the data rows from the starting - # index onward. - lines = [column_line, *all_lines[start:]] - - # Remove any lines marked for removal and re-join with a single - # newline character. - lines = [line for idx, line in enumerate(lines) if idx not in remove_lines] - output = "\n".join(lines) - - return output diff --git a/hyperglass/parsing/nos.py b/hyperglass/parsing/nos.py index eefed69..1362142 100644 --- a/hyperglass/parsing/nos.py +++ b/hyperglass/parsing/nos.py @@ -3,7 +3,6 @@ # Local from .arista import parse_arista from .juniper import parse_juniper -from .mikrotik import parse_mikrotik structured_parsers = { "juniper": { @@ -17,20 +16,3 @@ structured_parsers = { "bgp_community": parse_arista, }, } - -scrape_parsers = { - "mikrotik_routeros": { - "bgp_route": parse_mikrotik, - "bgp_aspath": parse_mikrotik, - "bgp_community": parse_mikrotik, - "ping": parse_mikrotik, - "traceroute": parse_mikrotik, - }, - "mikrotik_switchos": { - "bgp_route": parse_mikrotik, - "bgp_aspath": parse_mikrotik, - "bgp_community": parse_mikrotik, - "ping": parse_mikrotik, - "traceroute": parse_mikrotik, - }, -} diff --git a/hyperglass/plugins/_builtin/__init__.py b/hyperglass/plugins/_builtin/__init__.py index 75ebd6e..4a36861 100644 --- a/hyperglass/plugins/_builtin/__init__.py +++ b/hyperglass/plugins/_builtin/__init__.py @@ -4,9 +4,11 @@ from .remove_command import RemoveCommand from .bgp_route_arista import BGPRoutePluginArista from .bgp_route_juniper import BGPRoutePluginJuniper +from .mikrotik_garbage_output import MikrotikGarbageOutput __all__ = ( - "RemoveCommand", - "BGPRoutePluginJuniper", "BGPRoutePluginArista", + "BGPRoutePluginJuniper", + "MikrotikGarbageOutput", + "RemoveCommand", ) diff --git a/hyperglass/plugins/_builtin/mikrotik_garbage_output.py b/hyperglass/plugins/_builtin/mikrotik_garbage_output.py new file mode 100644 index 0000000..2f9eeca --- /dev/null +++ b/hyperglass/plugins/_builtin/mikrotik_garbage_output.py @@ -0,0 +1,82 @@ +"""Remove anything before the command if found in output.""" + +# Standard Library +import re +import typing as t + +# Third Party +from pydantic import PrivateAttr + +# Project +from hyperglass.types import Series + +# Local +from .._output import OutputType, OutputPlugin + +if t.TYPE_CHECKING: + # Project + from hyperglass.models.api.query import Query + + +class MikrotikGarbageOutput(OutputPlugin): + """Parse Mikrotik output to remove garbage.""" + + __hyperglass_builtin__: bool = PrivateAttr(True) + platforms: t.Sequence[str] = ("mikrotik_routeros", "mikrotik_switchos") + directives: t.Sequence[str] = ( + "__hyperglass_mikrotik_bgp_aspath__", + "__hyperglass_mikrotik_bgp_community__", + "__hyperglass_mikrotik_bgp_route__", + "__hyperglass_mikrotik_ping__", + "__hyperglass_mikrotik_traceroute__", + ) + + def process(self, *, output: OutputType, query: "Query") -> Series[str]: + """Parse Mikrotik output to remove garbage.""" + + result = () + + for each_output in output: + + if each_output.split()[-1] in ("DISTANCE", "STATUS"): + # Mikrotik shows the columns with no rows if there is no data. + # Rather than send back an empty table, send back an empty + # response which is handled with a warning message. + each_output = "" + else: + remove_lines = () + all_lines = each_output.splitlines() + # Starting index for rows (after the column row). + start = 1 + # Extract the column row. + column_line = " ".join(all_lines[0].split()) + + for i, line in enumerate(all_lines[1:]): + # Remove all the newline characters (which differ line to + # line) for comparison purposes. + normalized = " ".join(line.split()) + + # Remove ansii characters that aren't caught by Netmiko. + normalized = re.sub(r"\\x1b\[\S{2}\s", "", normalized) + + if column_line in normalized: + # Mikrotik often re-inserts the column row in the output, + # effectively 'starting over'. In that case, re-assign + # the column row and starting index to that point. + column_line = re.sub(r"\[\S{2}\s", "", line) + start = i + 2 + + if "[Q quit|D dump|C-z pause]" in normalized: + # Remove Mikrotik's unhelpful helpers from the output. + remove_lines += (i + 1,) + + # Combine the column row and the data rows from the starting + # index onward. + lines = [column_line, *all_lines[start:]] + + # Remove any lines marked for removal and re-join with a single + # newline character. + lines = [line for idx, line in enumerate(lines) if idx not in remove_lines] + result += ("\n".join(lines),) + + return result