mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-07-17 22:14:25 +02:00
75 lines
3.1 KiB
Python
75 lines
3.1 KiB
Python
import unittest
|
|
import os
|
|
import glob
|
|
import yaml
|
|
import re
|
|
|
|
class TestIncludeImportRoleExistence(unittest.TestCase):
|
|
def setUp(self):
|
|
# Determine project root and roles directory
|
|
tests_dir = os.path.dirname(__file__)
|
|
project_root = os.path.abspath(os.path.join(tests_dir, os.pardir, os.pardir))
|
|
self.roles_dir = os.path.join(project_root, 'roles')
|
|
# Collect all .yml files in project (recursive), excluding .git and tests dirs
|
|
self.files_to_scan = []
|
|
for filepath in glob.glob(os.path.join(project_root, '**', '*.yml'), recursive=True):
|
|
# Skip .git, tests folders
|
|
if '/.git/' in filepath or '/tests/' in filepath:
|
|
continue
|
|
self.files_to_scan.append(filepath)
|
|
|
|
def _collect_includes(self, data):
|
|
"""
|
|
Recursively collect all roles referenced via include_role or import_role.
|
|
Supports scalar, block, and block-list syntax, plus templating and wildcards.
|
|
"""
|
|
roles = []
|
|
if isinstance(data, dict):
|
|
for key, val in data.items():
|
|
if key in ('include_role', 'import_role'):
|
|
# Scalar syntax: include_role: role_name
|
|
if isinstance(val, str):
|
|
roles.append(val)
|
|
# Block syntax: include_role: { name: role_name }
|
|
elif isinstance(val, dict) and 'name' in val:
|
|
roles.append(val['name'])
|
|
# Block-list syntax: include_role:
|
|
# - name: foo
|
|
# - name: bar
|
|
elif isinstance(val, list):
|
|
for item in val:
|
|
if isinstance(item, dict) and 'name' in item:
|
|
roles.append(item['name'])
|
|
else:
|
|
roles.extend(self._collect_includes(val))
|
|
elif isinstance(data, list):
|
|
for item in data:
|
|
roles.extend(self._collect_includes(item))
|
|
return roles
|
|
|
|
def test_include_import_roles_exist(self):
|
|
missing = []
|
|
for file_path in self.files_to_scan:
|
|
with open(file_path) as f:
|
|
try:
|
|
docs = list(yaml.safe_load_all(f))
|
|
except yaml.YAMLError:
|
|
self.fail(f"Failed to parse YAML in {file_path}")
|
|
|
|
for doc in docs:
|
|
for role_name in self._collect_includes(doc):
|
|
# Convert Jinja2 templates and wildcards to glob patterns
|
|
pattern = re.sub(r"\{\{.*?\}\}", '*', role_name)
|
|
glob_path = os.path.join(self.roles_dir, pattern)
|
|
# Check for matching role directories
|
|
matches = [p for p in glob.glob(glob_path) if os.path.isdir(p)]
|
|
if not matches:
|
|
missing.append((file_path, role_name))
|
|
|
|
if missing:
|
|
messages = [f"File '{fp}' references missing role '{rn}'" for fp, rn in missing]
|
|
self.fail("\n".join(messages))
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|