Adding upstream version 0.13.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
c0ae77e0f6
commit
ecf5ca3300
272 changed files with 33172 additions and 0 deletions
3
tests/units/cli/get/__init__.py
Normal file
3
tests/units/cli/get/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
# 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.
|
30
tests/units/cli/get/test__init__.py
Normal file
30
tests/units/cli/get/test__init__.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
# 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.
|
||||
"""
|
||||
Tests for anta.cli.get
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from click.testing import CliRunner
|
||||
|
||||
from anta.cli import anta
|
||||
from anta.cli.utils import ExitCode
|
||||
|
||||
|
||||
def test_anta_get(click_runner: CliRunner) -> None:
|
||||
"""
|
||||
Test anta get
|
||||
"""
|
||||
result = click_runner.invoke(anta, ["get"])
|
||||
assert result.exit_code == ExitCode.OK
|
||||
assert "Usage: anta get" in result.output
|
||||
|
||||
|
||||
def test_anta_get_help(click_runner: CliRunner) -> None:
|
||||
"""
|
||||
Test anta get --help
|
||||
"""
|
||||
result = click_runner.invoke(anta, ["get", "--help"])
|
||||
assert result.exit_code == ExitCode.OK
|
||||
assert "Usage: anta get" in result.output
|
204
tests/units/cli/get/test_commands.py
Normal file
204
tests/units/cli/get/test_commands.py
Normal file
|
@ -0,0 +1,204 @@
|
|||
# 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.
|
||||
"""
|
||||
Tests for anta.cli.get.commands
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import filecmp
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
from unittest.mock import ANY, patch
|
||||
|
||||
import pytest
|
||||
from cvprac.cvp_client import CvpClient
|
||||
from cvprac.cvp_client_errors import CvpApiError
|
||||
|
||||
from anta.cli import anta
|
||||
from anta.cli.utils import ExitCode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from click.testing import CliRunner
|
||||
|
||||
DATA_DIR: Path = Path(__file__).parents[3].resolve() / "data"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"cvp_container, cvp_connect_failure",
|
||||
[
|
||||
pytest.param(None, False, id="all devices"),
|
||||
pytest.param("custom_container", False, id="custom container"),
|
||||
pytest.param(None, True, id="cvp connect failure"),
|
||||
],
|
||||
)
|
||||
def test_from_cvp(
|
||||
tmp_path: Path,
|
||||
click_runner: CliRunner,
|
||||
cvp_container: str | None,
|
||||
cvp_connect_failure: bool,
|
||||
) -> None:
|
||||
"""
|
||||
Test `anta get from-cvp`
|
||||
|
||||
This test verifies that username and password are NOT mandatory to run this command
|
||||
"""
|
||||
output: Path = tmp_path / "output.yml"
|
||||
cli_args = ["get", "from-cvp", "--output", str(output), "--host", "42.42.42.42", "--username", "anta", "--password", "anta"]
|
||||
|
||||
if cvp_container is not None:
|
||||
cli_args.extend(["--container", cvp_container])
|
||||
|
||||
def mock_cvp_connect(self: CvpClient, *args: str, **kwargs: str) -> None:
|
||||
# pylint: disable=unused-argument
|
||||
if cvp_connect_failure:
|
||||
raise CvpApiError(msg="mocked CvpApiError")
|
||||
|
||||
# always get a token
|
||||
with patch("anta.cli.get.commands.get_cv_token", return_value="dummy_token"), patch(
|
||||
"cvprac.cvp_client.CvpClient.connect", autospec=True, side_effect=mock_cvp_connect
|
||||
) as mocked_cvp_connect, patch("cvprac.cvp_client.CvpApi.get_inventory", autospec=True, return_value=[]) as mocked_get_inventory, patch(
|
||||
"cvprac.cvp_client.CvpApi.get_devices_in_container", autospec=True, return_value=[]
|
||||
) as mocked_get_devices_in_container:
|
||||
result = click_runner.invoke(anta, cli_args)
|
||||
|
||||
if not cvp_connect_failure:
|
||||
assert output.exists()
|
||||
|
||||
mocked_cvp_connect.assert_called_once()
|
||||
if not cvp_connect_failure:
|
||||
assert "Connected to CloudVision" in result.output
|
||||
if cvp_container is not None:
|
||||
mocked_get_devices_in_container.assert_called_once_with(ANY, cvp_container)
|
||||
else:
|
||||
mocked_get_inventory.assert_called_once()
|
||||
assert result.exit_code == ExitCode.OK
|
||||
else:
|
||||
assert "Error connecting to CloudVision" in result.output
|
||||
assert result.exit_code == ExitCode.USAGE_ERROR
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"ansible_inventory, ansible_group, expected_exit, expected_log",
|
||||
[
|
||||
pytest.param("ansible_inventory.yml", None, ExitCode.OK, None, id="no group"),
|
||||
pytest.param("ansible_inventory.yml", "ATD_LEAFS", ExitCode.OK, None, id="group found"),
|
||||
pytest.param("ansible_inventory.yml", "DUMMY", ExitCode.USAGE_ERROR, "Group DUMMY not found in Ansible inventory", id="group not found"),
|
||||
pytest.param("empty_ansible_inventory.yml", None, ExitCode.USAGE_ERROR, "is empty", id="empty inventory"),
|
||||
],
|
||||
)
|
||||
def test_from_ansible(
|
||||
tmp_path: Path,
|
||||
click_runner: CliRunner,
|
||||
ansible_inventory: Path,
|
||||
ansible_group: str | None,
|
||||
expected_exit: int,
|
||||
expected_log: str | None,
|
||||
) -> None:
|
||||
"""
|
||||
Test `anta get from-ansible`
|
||||
|
||||
This test verifies:
|
||||
* the parsing of an ansible-inventory
|
||||
* the ansible_group functionaliy
|
||||
|
||||
The output path is ALWAYS set to a non existing file.
|
||||
"""
|
||||
output: Path = tmp_path / "output.yml"
|
||||
ansible_inventory_path = DATA_DIR / ansible_inventory
|
||||
# Init cli_args
|
||||
cli_args = ["get", "from-ansible", "--output", str(output), "--ansible-inventory", str(ansible_inventory_path)]
|
||||
|
||||
# Set --ansible-group
|
||||
if ansible_group is not None:
|
||||
cli_args.extend(["--ansible-group", ansible_group])
|
||||
|
||||
result = click_runner.invoke(anta, cli_args)
|
||||
|
||||
assert result.exit_code == expected_exit
|
||||
|
||||
if expected_exit != ExitCode.OK:
|
||||
assert expected_log
|
||||
assert expected_log in result.output
|
||||
else:
|
||||
assert output.exists()
|
||||
# TODO check size of generated inventory to validate the group functionality!
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"env_set, overwrite, is_tty, prompt, expected_exit, expected_log",
|
||||
[
|
||||
pytest.param(True, False, True, "y", ExitCode.OK, "", id="no-overwrite-tty-init-prompt-yes"),
|
||||
pytest.param(True, False, True, "N", ExitCode.INTERNAL_ERROR, "Aborted", id="no-overwrite-tty-init-prompt-no"),
|
||||
pytest.param(
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
None,
|
||||
ExitCode.USAGE_ERROR,
|
||||
"Conversion aborted since destination file is not empty (not running in interactive TTY)",
|
||||
id="no-overwrite-no-tty-init",
|
||||
),
|
||||
pytest.param(False, False, True, None, ExitCode.OK, "", id="no-overwrite-tty-no-init"),
|
||||
pytest.param(False, False, False, None, ExitCode.OK, "", id="no-overwrite-no-tty-no-init"),
|
||||
pytest.param(True, True, True, None, ExitCode.OK, "", id="overwrite-tty-init"),
|
||||
pytest.param(True, True, False, None, ExitCode.OK, "", id="overwrite-no-tty-init"),
|
||||
pytest.param(False, True, True, None, ExitCode.OK, "", id="overwrite-tty-no-init"),
|
||||
pytest.param(False, True, False, None, ExitCode.OK, "", id="overwrite-no-tty-no-init"),
|
||||
],
|
||||
)
|
||||
def test_from_ansible_overwrite(
|
||||
tmp_path: Path,
|
||||
click_runner: CliRunner,
|
||||
temp_env: dict[str, str | None],
|
||||
env_set: bool,
|
||||
overwrite: bool,
|
||||
is_tty: bool,
|
||||
prompt: str | None,
|
||||
expected_exit: int,
|
||||
expected_log: str | None,
|
||||
) -> None:
|
||||
# pylint: disable=too-many-arguments
|
||||
"""
|
||||
Test `anta get from-ansible` overwrite mechanism
|
||||
|
||||
The test uses a static ansible-inventory and output as these are tested in other functions
|
||||
|
||||
This test verifies:
|
||||
* that overwrite is working as expected with or without init data in the target file
|
||||
* that when the target file is not empty and a tty is present, the user is prompt with confirmation
|
||||
* Check the behavior when the prompt is filled
|
||||
|
||||
The initial content of the ANTA inventory is set using init_anta_inventory, if it is None, no inventory is set.
|
||||
|
||||
* With overwrite True, the expectation is that the from-ansible command succeeds
|
||||
* With no init (init_anta_inventory == None), the expectation is also that command succeeds
|
||||
"""
|
||||
ansible_inventory_path = DATA_DIR / "ansible_inventory.yml"
|
||||
expected_anta_inventory_path = DATA_DIR / "expected_anta_inventory.yml"
|
||||
tmp_output = tmp_path / "output.yml"
|
||||
cli_args = ["get", "from-ansible", "--ansible-inventory", str(ansible_inventory_path)]
|
||||
|
||||
if env_set:
|
||||
tmp_inv = Path(str(temp_env["ANTA_INVENTORY"]))
|
||||
else:
|
||||
temp_env["ANTA_INVENTORY"] = None
|
||||
tmp_inv = tmp_output
|
||||
cli_args.extend(["--output", str(tmp_output)])
|
||||
|
||||
if overwrite:
|
||||
cli_args.append("--overwrite")
|
||||
|
||||
# Verify initial content is different
|
||||
if tmp_inv.exists():
|
||||
assert not filecmp.cmp(tmp_inv, expected_anta_inventory_path)
|
||||
|
||||
with patch("sys.stdin.isatty", return_value=is_tty):
|
||||
result = click_runner.invoke(anta, cli_args, env=temp_env, input=prompt)
|
||||
|
||||
assert result.exit_code == expected_exit
|
||||
if expected_exit == ExitCode.OK:
|
||||
assert filecmp.cmp(tmp_inv, expected_anta_inventory_path)
|
||||
elif expected_exit == ExitCode.INTERNAL_ERROR:
|
||||
assert expected_log
|
||||
assert expected_log in result.output
|
115
tests/units/cli/get/test_utils.py
Normal file
115
tests/units/cli/get/test_utils.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
# 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.
|
||||
"""
|
||||
Tests for anta.cli.get.utils
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import nullcontext
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from anta.cli.get.utils import create_inventory_from_ansible, create_inventory_from_cvp, get_cv_token
|
||||
from anta.inventory import AntaInventory
|
||||
|
||||
DATA_DIR: Path = Path(__file__).parents[3].resolve() / "data"
|
||||
|
||||
|
||||
def test_get_cv_token() -> None:
|
||||
"""
|
||||
Test anta.get.utils.get_cv_token
|
||||
"""
|
||||
ip = "42.42.42.42"
|
||||
username = "ant"
|
||||
password = "formica"
|
||||
|
||||
with patch("anta.cli.get.utils.requests.request") as patched_request:
|
||||
mocked_ret = MagicMock(autospec=requests.Response)
|
||||
mocked_ret.json.return_value = {"sessionId": "simple"}
|
||||
patched_request.return_value = mocked_ret
|
||||
res = get_cv_token(ip, username, password)
|
||||
patched_request.assert_called_once_with(
|
||||
"POST",
|
||||
"https://42.42.42.42/cvpservice/login/authenticate.do",
|
||||
headers={"Content-Type": "application/json", "Accept": "application/json"},
|
||||
data='{"userId": "ant", "password": "formica"}',
|
||||
verify=False,
|
||||
timeout=10,
|
||||
)
|
||||
assert res == "simple"
|
||||
|
||||
|
||||
# truncated inventories
|
||||
CVP_INVENTORY = [
|
||||
{
|
||||
"hostname": "device1",
|
||||
"containerName": "DC1",
|
||||
"ipAddress": "10.20.20.97",
|
||||
},
|
||||
{
|
||||
"hostname": "device2",
|
||||
"containerName": "DC2",
|
||||
"ipAddress": "10.20.20.98",
|
||||
},
|
||||
{
|
||||
"hostname": "device3",
|
||||
"containerName": "",
|
||||
"ipAddress": "10.20.20.99",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"inventory",
|
||||
[
|
||||
pytest.param(CVP_INVENTORY, id="some container"),
|
||||
pytest.param([], id="empty_inventory"),
|
||||
],
|
||||
)
|
||||
def test_create_inventory_from_cvp(tmp_path: Path, inventory: list[dict[str, Any]]) -> None:
|
||||
"""
|
||||
Test anta.get.utils.create_inventory_from_cvp
|
||||
"""
|
||||
output = tmp_path / "output.yml"
|
||||
|
||||
create_inventory_from_cvp(inventory, output)
|
||||
|
||||
assert output.exists()
|
||||
# This validate the file structure ;)
|
||||
inv = AntaInventory.parse(str(output), "user", "pass")
|
||||
assert len(inv) == len(inventory)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"inventory_filename, ansible_group, expected_raise, expected_inv_length",
|
||||
[
|
||||
pytest.param("ansible_inventory.yml", None, nullcontext(), 7, id="no group"),
|
||||
pytest.param("ansible_inventory.yml", "ATD_LEAFS", nullcontext(), 4, id="group found"),
|
||||
pytest.param("ansible_inventory.yml", "DUMMY", pytest.raises(ValueError, match="Group DUMMY not found in Ansible inventory"), 0, id="group not found"),
|
||||
pytest.param("empty_ansible_inventory.yml", None, pytest.raises(ValueError, match="Ansible inventory .* is empty"), 0, id="empty inventory"),
|
||||
pytest.param("wrong_ansible_inventory.yml", None, pytest.raises(ValueError, match="Could not parse"), 0, id="os error inventory"),
|
||||
],
|
||||
)
|
||||
def test_create_inventory_from_ansible(tmp_path: Path, inventory_filename: Path, ansible_group: str | None, expected_raise: Any, expected_inv_length: int) -> None:
|
||||
"""
|
||||
Test anta.get.utils.create_inventory_from_ansible
|
||||
"""
|
||||
target_file = tmp_path / "inventory.yml"
|
||||
inventory_file_path = DATA_DIR / inventory_filename
|
||||
|
||||
with expected_raise:
|
||||
if ansible_group:
|
||||
create_inventory_from_ansible(inventory_file_path, target_file, ansible_group)
|
||||
else:
|
||||
create_inventory_from_ansible(inventory_file_path, target_file)
|
||||
|
||||
assert target_file.exists()
|
||||
inv = AntaInventory().parse(str(target_file), "user", "pass")
|
||||
assert len(inv) == expected_inv_length
|
||||
if not isinstance(expected_raise, nullcontext):
|
||||
assert not target_file.exists()
|
Loading…
Add table
Add a link
Reference in a new issue