Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/+class.deprecated
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Deprecated the redundant HPComwareConfigParser class in lieu of the HPEConfigParser class and updated the compliance parser_map accordingly.
1 change: 1 addition & 0 deletions changes/752.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added custom parsing of HP Network OS devices.
2 changes: 1 addition & 1 deletion docs/dev/include_parser_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
| citrix_netscaler | netutils.config.parser.NetscalerConfigParser |
| extreme_netiron | netutils.config.parser.NetironConfigParser |
| fortinet_fortios | netutils.config.parser.FortinetConfigParser |
| hp_comware | netutils.config.parser.HPComwareConfigParser |
| hp_comware | netutils.config.parser.HPEConfigParser |
| juniper_junos | netutils.config.parser.JunosConfigParser |
| linux | netutils.config.parser.LINUXConfigParser |
| mikrotik_routeros | netutils.config.parser.RouterOSConfigParser |
Expand Down
2 changes: 1 addition & 1 deletion netutils/config/compliance.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"citrix_netscaler": parser.NetscalerConfigParser,
"extreme_netiron": parser.NetironConfigParser,
"fortinet_fortios": parser.FortinetConfigParser,
"hp_comware": parser.HPComwareConfigParser,
"hp_comware": parser.HPEConfigParser,
"juniper_junos": parser.JunosConfigParser,
"linux": parser.LINUXConfigParser,
"mikrotik_routeros": parser.RouterOSConfigParser,
Expand Down
98 changes: 90 additions & 8 deletions netutils/config/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from netutils.banner import normalise_delimiter_caret_c
from netutils.config.conversion import paloalto_panos_brace_to_set
from netutils.config.utils import _deprecated

ConfigLine = namedtuple("ConfigLine", "config_line,parents")

Expand Down Expand Up @@ -1678,14 +1679,99 @@ def config_lines_only(self) -> str:
class HPEConfigParser(BaseSpaceConfigParser):
"""HPE Implementation of ConfigParser Class."""

regex_banner = re.compile(r"^header\s(\w+)\s+(?P<banner_delimiter>\^C|\S?)")
regex_banner = re.compile(r"^\s*header\s(\w+)\s+(?P<banner_delimiter>\^C|\S?)")
banner_start: t.List[str] = ["header "]
comment_chars: t.List[str] = ["#"]

def __init__(self, config: str):
"""Initialize the HPEConfigParser object."""
self.delimiter = ""
self._banner_end: t.Optional[str] = None
super(HPEConfigParser, self).__init__(config)

@property
def config_lines_only(self) -> str:
"""Remove spaces and unwanted lines from config lines, but leave comments.
Returns:
The non-space lines from ``config``.
"""
if self._config is None:
config_lines = (line.rstrip() for line in self.config.splitlines() if line and not line.isspace())
self._config = "\n".join(config_lines)
return self._config

def build_config_relationship(self) -> t.List[ConfigLine]:
r"""This is a custom build method for HPE Network OS.
HP config is a bit different from other network operating systems.
It uses comments (#) to demarcate sections of the config.
Each new section that starts without a leading space is a new section.
That new section may or may not have children.
Each config line that has a leading space but not a parent is just a single config line.
Single lines that have leading spaces also sometimes differs between models (e.g., 59XX vs 79XX series).
We strip the leading spaces from config lines without parents for consistency.
Examples:
>>> from netutils.config.parser import HPEConfigParser, ConfigLine
>>> config = '''#
... version 7.1.045, Release 2418P06
... #
... sysname NTC123456
... #
... vlan 101
... name Test-Vlan-101
... description Test Vlan 101
... #'''
>>> config_tree = HPEConfigParser(config)
>>> config_tree.build_config_relationship() == \
... [
... ConfigLine(config_line="version 7.1.045, Release 2418P06", parents=()),
... ConfigLine(config_line="sysname NTC123456", parents=()),
... ConfigLine(config_line="vlan 101", parents=()),
... ConfigLine(config_line=" name Test-Vlan-101", parents=("vlan 101",)),
... ConfigLine(config_line=" description Test Vlan 101", parents=("vlan 101",)),
... ]
True
>>>
"""
new_section = True
for line in self.generator_config:
if line.startswith(tuple(self.comment_chars)):
# Closing any previous sections
self._current_parents = ()
self.indent_level = 0
new_section = True
continue
if line.strip().startswith(tuple(self.comment_chars)):
# Just ignore comments inside sections
continue
if self.is_banner_start(line):
# Special case for banners
line = line.lstrip()
self._build_banner(line)
continue

current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
if current_spaces == 0:
# Reset current parents and indent level for lines that are not indented.
self._current_parents = ()
self.indent_level = 0
new_section = True
if current_spaces > self.indent_level and not new_section:
previous_config = self.config_lines[-1]
self._current_parents += (previous_config.config_line,)
elif current_spaces < self.indent_level:
self._current_parents = self._remove_parents(line, current_spaces)

if not self._current_parents:
# Standardize lines without parents to remove leading spaces
line = line.lstrip()
new_section = False
self.indent_level = current_spaces
self._update_config_lines(line)
return self.config_lines

def _build_banner(self, config_line: str) -> t.Optional[str]:
"""
Builds a banner configuration based on the given config_line.
Expand Down Expand Up @@ -1763,16 +1849,12 @@ def banner_end(self, banner_start_line: str) -> None:
self._banner_end = self.delimiter


@_deprecated(
"HPComwareConfigParser is deprecated and will be removed in a future version. Use HPEConfigParser instead."
)
class HPComwareConfigParser(HPEConfigParser, BaseSpaceConfigParser):
"""HP Comware Implementation of ConfigParser Class."""

banner_start: t.List[str] = ["header "]
comment_chars: t.List[str] = ["#"]

def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Build a banner from the given config line."""
return super(HPComwareConfigParser, self)._build_banner(config_line)


class NvidiaOnyxConfigParser(BaseConfigParser): # pylint: disable=abstract-method
"""Nvidia Onyx config parser."""
Expand Down
42 changes: 42 additions & 0 deletions netutils/config/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""Utility functions for working with device configurations."""

import typing as t
import warnings
from functools import wraps


def _open_file_config(cfg_path: str) -> str:
"""Open config file from local disk."""
Expand All @@ -8,3 +12,41 @@ def _open_file_config(cfg_path: str) -> str:
device_cfg = filehandler.read()

return device_cfg.strip()


def _deprecated(custom_message: t.Optional[str] = None) -> t.Callable[[t.Any], t.Any]:
"""Deprecate a function or class.
Args:
custom_message: Custom deprecation message. If None, uses default message.
Returns:
Decorator function that issues a deprecation warning when the decorated item is used.
"""
if custom_message is None:
custom_message = "This function or class is deprecated and will be removed in a future version."

def decorator(obj: t.Any) -> t.Any:
"""Decorator that wraps a class or function to issue deprecation warning."""
if isinstance(obj, type):
# For classes, wrap __init__ to issue warning on instantiation
original_init = getattr(obj, "__init__", None)
if original_init is None:
return obj

def __init__(self: t.Any, *args: t.Any, **kwargs: t.Any) -> None:
warnings.warn(custom_message, DeprecationWarning, stacklevel=2)
original_init(self, *args, **kwargs)

setattr(obj, "__init__", __init__)
return obj

# For functions, wrap the function
@wraps(obj)
def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
warnings.warn(custom_message, DeprecationWarning, stacklevel=2)
return obj(*args, **kwargs)

return wrapper

return decorator
Loading
Loading