Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add feature to search config #634

Merged
Merged
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
11 changes: 11 additions & 0 deletions anta/custom_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ def bgp_multiprotocol_capabilities_abbreviations(value: str) -> str:
return value


def validate_regex(value: str) -> str:
"""Validate that the input value is a valid regex format."""
try:
re.compile(value)
except re.error as e:
msg = f"Invalid regex: {e}"
raise ValueError(msg) from e
return value


# ANTA framework
TestStatus = Literal["unset", "success", "failure", "error", "skipped"]

Expand Down Expand Up @@ -129,3 +139,4 @@ def bgp_multiprotocol_capabilities_abbreviations(value: str) -> str:
Revision = Annotated[int, Field(ge=1, le=99)]
Hostname = Annotated[str, Field(pattern=r"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$")]
Port = Annotated[int, Field(ge=1, le=65535)]
RegexString = Annotated[str, AfterValidator(validate_regex)]
56 changes: 56 additions & 0 deletions anta/tests/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
# mypy: disable-error-code=attr-defined
from __future__ import annotations

import re
from typing import TYPE_CHECKING, ClassVar

from anta.custom_types import RegexString
from anta.models import AntaCommand, AntaTest

if TYPE_CHECKING:
Expand Down Expand Up @@ -75,3 +77,57 @@ def test(self) -> None:
self.result.is_success()
else:
self.result.is_failure(command_output)


class VerifyRunningConfigLines(AntaTest):
"""Verifies the given regular expression patterns are present in the running-config.

!!! warning
Since this uses regular expression searches on the whole running-config, it can
drastically impact performance and should only be used if no other test is available.

If possible, try using another ANTA test that is more specific.

Expected Results
----------------
* Success: The test will pass if all the patterns are found in the running-config.
* Failure: The test will fail if any of the patterns are NOT found in the running-config.

Examples
--------
```yaml
anta.tests.configuration:
- VerifyRunningConfigLines:
regex_patterns:
- "^enable password.*$"
- "bla bla"
```
"""

name = "VerifyRunningConfigLines"
description = "Search the Running-Config for the given RegEx patterns."
categories: ClassVar[list[str]] = ["configuration"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show running-config", ofmt="text")]

class Input(AntaTest.Input):
"""Input model for the VerifyRunningConfigLines test."""

regex_patterns: list[RegexString]
"""List of regular expressions."""

@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyRunningConfigLines."""
failure_msgs = []
command_output = self.instance_commands[0].text_output

for pattern in self.inputs.regex_patterns:
re_search = re.compile(pattern, flags=re.MULTILINE)
chetryan marked this conversation as resolved.
Show resolved Hide resolved

if not re_search.search(command_output):
failure_msgs.append(f"'{pattern}'")

if not failure_msgs:
self.result.is_success()
else:
self.result.is_failure("Following patterns were not found: " + ",".join(failure_msgs))
41 changes: 39 additions & 2 deletions tests/units/anta_tests/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from typing import Any

from anta.tests.configuration import VerifyRunningConfigDiffs, VerifyZeroTouch
from anta.tests.configuration import VerifyRunningConfigDiffs, VerifyRunningConfigLines, VerifyZeroTouch
from tests.lib.anta import test # noqa: F401; pylint: disable=W0611

DATA: list[dict[str, Any]] = [
Expand All @@ -32,5 +32,42 @@
"inputs": None,
"expected": {"result": "success"},
},
{"name": "failure", "test": VerifyRunningConfigDiffs, "eos_data": ["blah blah"], "inputs": None, "expected": {"result": "failure", "messages": ["blah blah"]}},
{
"name": "failure",
"test": VerifyRunningConfigDiffs,
"eos_data": ["blah blah"],
"inputs": None,
"expected": {"result": "failure", "messages": ["blah blah"]},
},
{
"name": "success",
"test": VerifyRunningConfigLines,
"eos_data": ["blah blah"],
"inputs": {"regex_patterns": ["blah"]},
"expected": {"result": "success"},
},
{
"name": "success",
"test": VerifyRunningConfigLines,
"eos_data": ["enable password something\nsome other line"],
"inputs": {"regex_patterns": ["^enable password .*$", "^.*other line$"]},
"expected": {"result": "success"},
},
{
"name": "failure",
"test": VerifyRunningConfigLines,
"eos_data": ["enable password something\nsome other line"],
"inputs": {"regex_patterns": ["bla", "bleh"]},
"expected": {"result": "failure", "messages": ["Following patterns were not found: 'bla','bleh'"]},
},
{
"name": "failure-invalid-regex",
"test": VerifyRunningConfigLines,
"eos_data": ["enable password something\nsome other line"],
"inputs": {"regex_patterns": ["["]},
"expected": {
"result": "error",
"messages": ["1 validation error for Input\nregex_patterns.0\n Value error, Invalid regex: unterminated character set at position 0"],
},
},
]
Loading