From 89a10701ecf09b8fa8daa3abc153a052559276bd Mon Sep 17 00:00:00 2001 From: gmuloc Date: Thu, 18 Apr 2024 15:09:56 +0200 Subject: [PATCH] Refactor(anta): Emit log message when failing on Ansible vault variable --- anta/cli/get/commands.py | 6 ++- anta/cli/get/utils.py | 9 ++++ docs/cli/inv-from-ansible.md | 25 ++++++++--- tests/data/ansible_inventory_with_vault.yml | 50 +++++++++++++++++++++ tests/units/cli/get/test_utils.py | 22 +++++++-- 5 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 tests/data/ansible_inventory_with_vault.yml diff --git a/anta/cli/get/commands.py b/anta/cli/get/commands.py index 2f686fa7e..a4125db67 100644 --- a/anta/cli/get/commands.py +++ b/anta/cli/get/commands.py @@ -76,7 +76,11 @@ def from_cvp(ctx: click.Context, output: Path, host: str, username: str, passwor required=True, ) def from_ansible(ctx: click.Context, output: Path, ansible_group: str, ansible_inventory: Path) -> None: - """Build ANTA inventory from an ansible inventory YAML file.""" + """Build ANTA inventory from an ansible inventory YAML file. + + NOTE: This command does not support inline vaulted variables. Make sure to comment them out. + + """ logger.info("Building inventory from ansible file '%s'", ansible_inventory) try: create_inventory_from_ansible( diff --git a/anta/cli/get/utils.py b/anta/cli/get/utils.py index af9b99d8c..1d56cfa23 100644 --- a/anta/cli/get/utils.py +++ b/anta/cli/get/utils.py @@ -154,6 +154,15 @@ def create_inventory_from_ansible(inventory: Path, output: Path, ansible_group: try: with inventory.open(encoding="utf-8") as inv: ansible_inventory = yaml.safe_load(inv) + except yaml.constructor.ConstructorError as exc: + if exc.problem and "!vault" in exc.problem: + logger.error( + "`anta get from-ansible` does not support inline vaulted variables, comment them out to generate your inventory. " + "If the vaulted variable is necessary to build the inventory (e.g. `ansible_host`), it needs to be unvaulted for " + "`from-ansible` command to work." + ) + msg = f"Could not parse {inventory}." + raise ValueError(msg) from exc except OSError as exc: msg = f"Could not parse {inventory}." raise ValueError(msg) from exc diff --git a/docs/cli/inv-from-ansible.md b/docs/cli/inv-from-ansible.md index ebe7da328..b2672e20a 100644 --- a/docs/cli/inv-from-ansible.md +++ b/docs/cli/inv-from-ansible.md @@ -14,17 +14,28 @@ In large setups, it might be beneficial to construct your inventory based on you $ anta get from-ansible --help Usage: anta get from-ansible [OPTIONS] - Build ANTA inventory from an ansible inventory YAML file + Build ANTA inventory from an ansible inventory YAML file. + + NOTE: This command does not support inline vaulted variables. Make sure to + comment them out. Options: - -g, --ansible-group TEXT Ansible group to filter - --ansible-inventory FILENAME - Path to your ansible inventory file to read - -o, --output FILENAME Path to save inventory file - -d, --inventory-directory PATH Directory to save inventory file - --help Show this message and exit. + -o, --output FILE Path to save inventory file [env var: + ANTA_INVENTORY; required] + --overwrite Do not prompt when overriding current inventory + [env var: ANTA_GET_FROM_ANSIBLE_OVERWRITE] + -g, --ansible-group TEXT Ansible group to filter + --ansible-inventory FILE Path to your ansible inventory file to read + [required] + --help Show this message and exit. ``` +!!! warning + + `anta get from-ansible` does not support inline vaulted variables, comment them out to generate your inventory. + If the vaulted variable is necessary to build the inventory (e.g. `ansible_host`), it needs to be unvaulted for `from-ansible` command to work." + + The output is an inventory where the name of the container is added as a tag for each host: ```yaml diff --git a/tests/data/ansible_inventory_with_vault.yml b/tests/data/ansible_inventory_with_vault.yml new file mode 100644 index 000000000..bf66ce046 --- /dev/null +++ b/tests/data/ansible_inventory_with_vault.yml @@ -0,0 +1,50 @@ + +--- +all: + children: + cv_servers: + hosts: + cv_atd1: + ansible_host: 10.73.1.238 + ansible_user: tom + ansible_password: !vault | + $ANSIBLE_VAULT;1.1;AES256 + OOOOOOOK!YOURAWESOMEVAULTEDPASSWOR!OOOOOOK!EEEEEEEK! + cv_collection: v3 + ATD_LAB: + vars: + ansible_user: arista + ansible_ssh_pass: arista + children: + ATD_FABRIC: + children: + ATD_SPINES: + vars: + type: spine + hosts: + spine1: + ansible_host: 192.168.0.10 + spine2: + ansible_host: 192.168.0.11 + ATD_LEAFS: + vars: + type: l3leaf + children: + pod1: + hosts: + leaf1: + ansible_host: 192.168.0.12 + leaf2: + ansible_host: 192.168.0.13 + pod2: + hosts: + leaf3: + ansible_host: 192.168.0.14 + leaf4: + ansible_host: 192.168.0.15 + ATD_TENANTS_NETWORKS: + children: + ATD_LEAFS: + ATD_SERVERS: + children: + ATD_LEAFS: diff --git a/tests/units/cli/get/test_utils.py b/tests/units/cli/get/test_utils.py index 0dce33581..802ff7ec2 100644 --- a/tests/units/cli/get/test_utils.py +++ b/tests/units/cli/get/test_utils.py @@ -81,14 +81,15 @@ def test_create_inventory_from_cvp(tmp_path: Path, inventory: list[dict[str, Any @pytest.mark.parametrize( - ("inventory_filename", "ansible_group", "expected_raise", "expected_inv_length"), + ("inventory_filename", "ansible_group", "expected_raise", "expected_log", "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", None, nullcontext(), None, 7, id="no group"), + pytest.param("ansible_inventory.yml", "ATD_LEAFS", nullcontext(), None, 4, id="group found"), pytest.param( "ansible_inventory.yml", "DUMMY", pytest.raises(ValueError, match="Group DUMMY not found in Ansible inventory"), + None, 0, id="group not found", ), @@ -96,6 +97,7 @@ def test_create_inventory_from_cvp(tmp_path: Path, inventory: list[dict[str, Any "empty_ansible_inventory.yml", None, pytest.raises(ValueError, match="Ansible inventory .* is empty"), + None, 0, id="empty inventory", ), @@ -103,19 +105,31 @@ def test_create_inventory_from_cvp(tmp_path: Path, inventory: list[dict[str, Any "wrong_ansible_inventory.yml", None, pytest.raises(ValueError, match="Could not parse"), + None, 0, id="os error inventory", ), + pytest.param( + "ansible_inventory_with_vault.yml", + None, + pytest.raises(ValueError, match="Could not parse"), + "`anta get from-ansible` does not support inline vaulted variables", + 0, + id="Vault variable in inventory", + ), ], ) def test_create_inventory_from_ansible( + caplog: pytest.LogCaptureFixture, tmp_path: Path, inventory_filename: Path, ansible_group: str | None, expected_raise: AbstractContextManager[Exception], + expected_log: str | None, expected_inv_length: int, ) -> None: """Test anta.get.utils.create_inventory_from_ansible.""" + # pylint: disable=R0913 target_file = tmp_path / "inventory.yml" inventory_file_path = DATA_DIR / inventory_filename @@ -130,3 +144,5 @@ def test_create_inventory_from_ansible( assert len(inv) == expected_inv_length if not isinstance(expected_raise, nullcontext): assert not target_file.exists() + if expected_log: + assert expected_log in caplog.text