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

refactor(anta): Emit log message when failing on Ansible vault variable #638

Merged
merged 1 commit into from
Apr 18, 2024
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
6 changes: 5 additions & 1 deletion anta/cli/get/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
9 changes: 9 additions & 0 deletions anta/cli/get/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 18 additions & 7 deletions docs/cli/inv-from-ansible.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 50 additions & 0 deletions tests/data/ansible_inventory_with_vault.yml
Original file line number Diff line number Diff line change
@@ -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:
22 changes: 19 additions & 3 deletions tests/units/cli/get/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,41 +81,55 @@ 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",
),
pytest.param(
"empty_ansible_inventory.yml",
None,
pytest.raises(ValueError, match="Ansible inventory .* is empty"),
None,
0,
id="empty inventory",
),
pytest.param(
"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

Expand All @@ -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
Loading