You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ansible/test/units/inventory/test_data.py

97 lines
3.4 KiB
Python

from __future__ import annotations
import collections.abc as c
import types
import typing as t
from unittest import mock
import pytest
from ansible.inventory.group import Group
from ansible.inventory.host import Host
from ansible._internal._datatag._tags import TrustedAsTemplate
from ansible.inventory.data import InventoryData
from ansible.inventory import helpers
def do_test(exec_str: str) -> None:
hostname = TrustedAsTemplate().tag("hostname")
groupname = TrustedAsTemplate().tag("groupname")
# avoid unused locals
assert hostname
assert groupname
inv = InventoryData()
exec(exec_str)
RecursiveChecker().visit(inv)
@pytest.mark.parametrize("exec_str, trust_removal_required", (
# # cases where trust removal is required
("inv.add_host(hostname, group='all')", True),
("inv.add_group(groupname)", True),
("inv.get_host(TrustedAsTemplate().tag('localhost'))", True),
("inv.groups['all'].add_host(Host(hostname))", True),
("inv.get_host('localhost').set_variable(TrustedAsTemplate().tag('key'), 'value')", True), # trusted key
("inv.get_host('localhost').add_group(Group(groupname))", True),
("inv.groups['all'].set_variable(TrustedAsTemplate().tag('key'), 'value')", True), # trusted key
# cases where trust removal is not required
("inv.add_host('hostname', group=TrustedAsTemplate().tag('all'))", False), # trusted group
("inv.add_child('all', TrustedAsTemplate().tag(inv.add_host('hostname')))", False), # host trusted
("inv.add_child(TrustedAsTemplate().tag('all'), inv.add_host('hostname'))", False), # group trusted
))
def test_ensure_trust_stripping(exec_str: str, trust_removal_required: bool):
do_test(exec_str)
try:
# re-run the test with the remove_trust helper no-opped to ensure that the test fails as expected
with mock.patch.object(helpers, 'remove_trust', lambda o: o):
do_test(exec_str)
except TrustFoundError:
if not trust_removal_required:
pytest.fail("test expected no trust failure; the test is faulty")
else:
if trust_removal_required:
pytest.fail("test expected trust failure; the test is faulty")
class TrustFoundError(Exception):
"""Raised when an object is trusted which should not be."""
def __init__(self, obj: t.Any) -> None:
super().__init__(f'TrustedAsTemplate is tagged on {obj}.')
class RecursiveChecker:
"""Recrursive visitor used to assert that an object is not tagged with, and does not contain, a TrustedAsTemplate tag."""
def __init__(self) -> None:
self.seen: set[int] = set()
def visit(self, obj: t.Any) -> None:
obj_id = id(obj)
if obj_id in self.seen:
return
self.seen.add(obj_id)
if TrustedAsTemplate.is_tagged_on(obj):
raise TrustFoundError(obj)
if isinstance(obj, (str, int, bool, types.NoneType)):
pass # expected scalar type
elif isinstance(obj, (InventoryData, Host, Group)):
self.visit(obj.__dict__)
elif isinstance(obj, c.Mapping):
for key, value in obj.items():
self.visit(key)
self.visit(value)
elif isinstance(obj, c.Iterable):
for item in obj:
self.visit(item)
else:
raise TypeError(f'Checking of {type(obj)} is not supported.') # pragma: nocover