Skip to content

Commit

Permalink
feat(anta): Add Markdown report option to ANTA (#740)
Browse files Browse the repository at this point in the history
  • Loading branch information
carl-baillargeon authored Aug 29, 2024
1 parent bb76a5a commit 7bb4560
Show file tree
Hide file tree
Showing 19 changed files with 1,237 additions and 52 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ repos:
- name: Check and insert license on Markdown files
id: insert-license
files: .*\.md$
# exclude:
exclude: ^tests/data/.*\.md$
args:
- --license-filepath
- .github/license-short.txt
Expand Down
1 change: 1 addition & 0 deletions anta/cli/nrfu/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,4 @@ def nrfu(
nrfu.add_command(commands.json)
nrfu.add_command(commands.text)
nrfu.add_command(commands.tpl_report)
nrfu.add_command(commands.md_report)
24 changes: 20 additions & 4 deletions anta/cli/nrfu/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from anta.cli.utils import exit_with_code

from .utils import print_jinja, print_json, print_table, print_text, run_tests, save_to_csv
from .utils import print_jinja, print_json, print_table, print_text, run_tests, save_markdown_report, save_to_csv

logger = logging.getLogger(__name__)

Expand All @@ -28,7 +28,7 @@
required=False,
)
def table(ctx: click.Context, group_by: Literal["device", "test"] | None) -> None:
"""ANTA command to check network states with table result."""
"""ANTA command to check network state with table results."""
run_tests(ctx)
print_table(ctx, group_by=group_by)
exit_with_code(ctx)
Expand All @@ -45,7 +45,7 @@ def table(ctx: click.Context, group_by: Literal["device", "test"] | None) -> Non
help="Path to save report as a JSON file",
)
def json(ctx: click.Context, output: pathlib.Path | None) -> None:
"""ANTA command to check network state with JSON result."""
"""ANTA command to check network state with JSON results."""
run_tests(ctx)
print_json(ctx, output=output)
exit_with_code(ctx)
Expand All @@ -54,7 +54,7 @@ def json(ctx: click.Context, output: pathlib.Path | None) -> None:
@click.command()
@click.pass_context
def text(ctx: click.Context) -> None:
"""ANTA command to check network states with text result."""
"""ANTA command to check network state with text results."""
run_tests(ctx)
print_text(ctx)
exit_with_code(ctx)
Expand Down Expand Up @@ -105,3 +105,19 @@ def tpl_report(ctx: click.Context, template: pathlib.Path, output: pathlib.Path
run_tests(ctx)
print_jinja(results=ctx.obj["result_manager"], template=template, output=output)
exit_with_code(ctx)


@click.command()
@click.pass_context
@click.option(
"--md-output",
type=click.Path(file_okay=True, dir_okay=False, exists=False, writable=True, path_type=pathlib.Path),
show_envvar=True,
required=True,
help="Path to save the report as a Markdown file",
)
def md_report(ctx: click.Context, md_output: pathlib.Path) -> None:
"""ANTA command to check network state with Markdown report."""
run_tests(ctx)
save_markdown_report(ctx, md_output=md_output)
exit_with_code(ctx)
17 changes: 17 additions & 0 deletions anta/cli/nrfu/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from anta.models import AntaTest
from anta.reporter import ReportJinja, ReportTable
from anta.reporter.csv_reporter import ReportCsv
from anta.reporter.md_reporter import MDReportGenerator
from anta.runner import main

if TYPE_CHECKING:
Expand Down Expand Up @@ -141,6 +142,22 @@ def save_to_csv(ctx: click.Context, csv_file: pathlib.Path) -> None:
ctx.exit(ExitCode.USAGE_ERROR)


def save_markdown_report(ctx: click.Context, md_output: pathlib.Path) -> None:
"""Save the markdown report to a file.
Parameters
----------
ctx: Click context containing the result manager.
md_output: Path to save the markdown report.
"""
try:
MDReportGenerator.generate(results=_get_result_manager(ctx), md_filename=md_output)
console.print(f"Markdown report saved to {md_output} ✅", style="cyan")
except OSError:
console.print(f"Failed to save Markdown report to {md_output} ❌", style="cyan")
ctx.exit(ExitCode.USAGE_ERROR)


# Adding our own ANTA spinner - overriding rich SPINNERS for our own
# so ignore warning for redefinition
rich.spinner.SPINNERS = { # type: ignore[attr-defined]
Expand Down
19 changes: 19 additions & 0 deletions anta/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright (c) 2023-2024 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
"""Constants used in ANTA."""

from __future__ import annotations

ACRONYM_CATEGORIES: set[str] = {"aaa", "mlag", "snmp", "bgp", "ospf", "vxlan", "stp", "igmp", "ip", "lldp", "ntp", "bfd", "ptp", "lanz", "stun", "vlan"}
"""A set of network protocol or feature acronyms that should be represented in uppercase."""

MD_REPORT_TOC = """**Table of Contents:**
- [ANTA Report](#anta-report)
- [Test Results Summary](#test-results-summary)
- [Summary Totals](#summary-totals)
- [Summary Totals Device Under Test](#summary-totals-device-under-test)
- [Summary Totals Per Category](#summary-totals-per-category)
- [Test Results](#test-results)"""
"""Table of Contents for the Markdown report."""
36 changes: 12 additions & 24 deletions anta/reporter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,21 +154,15 @@ def report_summary_tests(
self.Headers.list_of_error_nodes,
]
table = self._build_headers(headers=headers, table=table)
for test in manager.get_tests():
for test, stats in sorted(manager.test_stats.items()):
if tests is None or test in tests:
results = manager.filter_by_tests({test}).results
nb_failure = len([result for result in results if result.result == "failure"])
nb_error = len([result for result in results if result.result == "error"])
list_failure = [result.name for result in results if result.result in ["failure", "error"]]
nb_success = len([result for result in results if result.result == "success"])
nb_skipped = len([result for result in results if result.result == "skipped"])
table.add_row(
test,
str(nb_success),
str(nb_skipped),
str(nb_failure),
str(nb_error),
str(list_failure),
str(stats.devices_success_count),
str(stats.devices_skipped_count),
str(stats.devices_failure_count),
str(stats.devices_error_count),
", ".join(stats.devices_failure),
)
return table

Expand Down Expand Up @@ -202,21 +196,15 @@ def report_summary_devices(
self.Headers.list_of_error_tests,
]
table = self._build_headers(headers=headers, table=table)
for device in manager.get_devices():
for device, stats in sorted(manager.device_stats.items()):
if devices is None or device in devices:
results = manager.filter_by_devices({device}).results
nb_failure = len([result for result in results if result.result == "failure"])
nb_error = len([result for result in results if result.result == "error"])
list_failure = [result.test for result in results if result.result in ["failure", "error"]]
nb_success = len([result for result in results if result.result == "success"])
nb_skipped = len([result for result in results if result.result == "skipped"])
table.add_row(
device,
str(nb_success),
str(nb_skipped),
str(nb_failure),
str(nb_error),
str(list_failure),
str(stats.tests_success_count),
str(stats.tests_skipped_count),
str(stats.tests_failure_count),
str(stats.tests_error_count),
", ".join(stats.tests_failure),
)
return table

Expand Down
Loading

0 comments on commit 7bb4560

Please sign in to comment.