Repository: aristanetworks/anta (python)

Issue:
Silent exits when providing invalid inventory or catalog files
When providing an invalid inventory or catalog file in ANTA CLI, the program exits silently without any error messages or exceptions.

This can be reproduced with:

1. An invalid inventory file:
```yaml
anta_inventory:
  - host: 172.20.20.101
    name: DC1-SPINE1
    tags: ["SPINE", "DC1"]
```
2. Or an invalid catalog file:
```yaml
- anta.tests.aaa:
  - VerifyTacacsSourceIntf:
      intf: Management0
      vrf: default
      filters:
        tags: ["BL"]
```

Suggested fix is to improve exception handling in:
- https://github.com/aristanetworks/anta/blob/v1.2.0/anta/cli/utils.py#L246
- https://github.com/aristanetworks/anta/blob/v1.2.0/anta/cli/utils.py#L323




Pull request:
feat(anta.cli): Better error message when invalid inventory or catalogs are parsed
# Description

log exceptions in CLI

Fixes #986

# Checklist:

<!-- Delete not relevant items !-->

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have run pre-commit for code linting and typing (`pre-commit run`)
- [x] I have made corresponding changes to the documentation
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes (`tox -e testenv`)


Patch:
diff --git a/anta/cli/utils.py b/anta/cli/utils.py
index e740f8c5..508424dd 100644
--- a/anta/cli/utils.py
+++ b/anta/cli/utils.py
@@ -17,6 +17,7 @@ from yaml import YAMLError
 from anta.catalog import AntaCatalog
 from anta.inventory import AntaInventory
 from anta.inventory.exceptions import InventoryIncorrectSchemaError, InventoryRootKeyError
+from anta.logger import anta_log_exception
 
 if TYPE_CHECKING:
     from click import Option
@@ -242,7 +243,8 @@ def core_options(f: Callable[..., Any]) -> Callable[..., Any]:
                 insecure=insecure,
                 disable_cache=disable_cache,
             )
-        except (TypeError, ValueError, YAMLError, OSError, InventoryIncorrectSchemaError, InventoryRootKeyError):
+        except (TypeError, ValueError, YAMLError, OSError, InventoryIncorrectSchemaError, InventoryRootKeyError) as e:
+            anta_log_exception(e, f"Failed to parse the inventory: {inventory}", logger)
             ctx.exit(ExitCode.USAGE_ERROR)
         return f(*args, inventory=i, **kwargs)
 
@@ -319,7 +321,8 @@ def catalog_options(f: Callable[..., Any]) -> Callable[..., Any]:
         try:
             file_format = catalog_format.lower()
             c = AntaCatalog.parse(catalog, file_format=file_format)  # type: ignore[arg-type]
-        except (TypeError, ValueError, YAMLError, OSError):
+        except (TypeError, ValueError, YAMLError, OSError) as e:
+            anta_log_exception(e, f"Failed to parse the catalog: {catalog}", logger)
             ctx.exit(ExitCode.USAGE_ERROR)
         return f(*args, catalog=c, **kwargs)
 


Test patch:
diff --git a/tests/data/invalid_inventory.yml b/tests/data/invalid_inventory.yml
new file mode 100644
index 00000000..5199a15f
--- /dev/null
+++ b/tests/data/invalid_inventory.yml
@@ -0,0 +1,5 @@
+---
+anta_inventory:
+  - host: 172.20.20.101
+    name: DC1-SPINE1
+    tags: ["SPINE", "DC1"]
diff --git a/tests/units/cli/nrfu/test__init__.py b/tests/units/cli/nrfu/test__init__.py
index f81c67e9..bbc89283 100644
--- a/tests/units/cli/nrfu/test__init__.py
+++ b/tests/units/cli/nrfu/test__init__.py
@@ -5,14 +5,18 @@
 
 from __future__ import annotations
 
+from pathlib import Path
 from typing import TYPE_CHECKING
 
 from anta.cli import anta
 from anta.cli.utils import ExitCode
 
 if TYPE_CHECKING:
+    import pytest
     from click.testing import CliRunner
 
+DATA_DIR: Path = Path(__file__).parents[3].resolve() / "data"
+
 # TODO: write unit tests for ignore-status and ignore-error
 
 
@@ -123,3 +127,19 @@ def test_hide(click_runner: CliRunner) -> None:
     """Test the `--hide` option of the `anta nrfu` command."""
     result = click_runner.invoke(anta, ["nrfu", "--hide", "success", "text"])
     assert "SUCCESS" not in result.output
+
+
+def test_invalid_inventory(caplog: pytest.LogCaptureFixture, click_runner: CliRunner) -> None:
+    """Test invalid inventory."""
+    result = click_runner.invoke(anta, ["nrfu", "--inventory", str(DATA_DIR / "invalid_inventory.yml")])
+    assert "CRITICAL" in caplog.text
+    assert "Failed to parse the inventory" in caplog.text
+    assert result.exit_code == ExitCode.USAGE_ERROR
+
+
+def test_invalid_catalog(caplog: pytest.LogCaptureFixture, click_runner: CliRunner) -> None:
+    """Test invalid catalog."""
+    result = click_runner.invoke(anta, ["nrfu", "--catalog", str(DATA_DIR / "test_catalog_not_a_list.yml")])
+    assert "CRITICAL" in caplog.text
+    assert "Failed to parse the catalog" in caplog.text
+    assert result.exit_code == ExitCode.USAGE_ERROR