mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-08-26 21:45:20 +02:00
- Added logic to scan each role’s tasks/*.yml files for include_role usage - Supports: * loop/with_items with literal strings → adds each role * patterns with variables inside literals (e.g. svc-db-{{database_type}}) → expanded to glob and matched * pure variable-only names ({{var}}) → ignored * pure literal names → added directly - Merges discovered dependencies under graphs["dependencies"]["include_role"] - Added dedicated unit test covering looped includes, glob patterns, pure literals, and ignoring pure variables See ChatGPT conversation (https://chatgpt.com/share/68a4ace0-7268-800f-bd32-b475c5c9ba1d) for context.
144 lines
4.7 KiB
Python
144 lines
4.7 KiB
Python
import os
|
|
import sys
|
|
import json
|
|
import tempfile
|
|
import shutil
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
# Absoluter Pfad zum tree.py Script (wie im vorhandenen Test)
|
|
SCRIPT_PATH = os.path.abspath(
|
|
os.path.join(os.path.dirname(__file__), "../../../../cli/build/tree.py")
|
|
)
|
|
|
|
class TestTreeIncludeRoleDependencies(unittest.TestCase):
|
|
def setUp(self):
|
|
# Temp roles root
|
|
self.roles_dir = tempfile.mkdtemp()
|
|
|
|
# Producer-Role (die wir scannen) + Zielrollen für Matches
|
|
self.producer = "producer"
|
|
self.producer_path = os.path.join(self.roles_dir, self.producer)
|
|
os.makedirs(os.path.join(self.producer_path, "tasks"))
|
|
os.makedirs(os.path.join(self.producer_path, "meta"))
|
|
|
|
# Rollen, die durch Pattern/Loops gematcht werden sollen
|
|
self.roles_to_create = [
|
|
"sys-ctl-hlth-webserver",
|
|
"sys-ctl-hlth-csp",
|
|
"svc-db-postgres",
|
|
"svc-db-mysql",
|
|
"axb", # für a{{database_type}}b → a*b
|
|
"ayyb", # für a{{database_type}}b → a*b
|
|
"literal-role", # für reinen Literalnamen
|
|
]
|
|
for r in self.roles_to_create:
|
|
os.makedirs(os.path.join(self.roles_dir, r, "meta"), exist_ok=True)
|
|
|
|
# tasks/main.yml mit allen geforderten Varianten
|
|
tasks_yaml = """
|
|
- name: Include health dependencies
|
|
include_role:
|
|
name: "{{ item }}"
|
|
loop:
|
|
- sys-ctl-hlth-webserver
|
|
- sys-ctl-hlth-csp
|
|
|
|
- name: Pattern with literal + var suffix
|
|
include_role:
|
|
name: "svc-db-{{database_type}}"
|
|
|
|
- name: Pattern with literal prefix/suffix around var
|
|
include_role:
|
|
name: "a{{database_type}}b"
|
|
|
|
- name: Pure variable only (should be ignored)
|
|
include_role:
|
|
name: "{{database_type}}"
|
|
|
|
- name: Pure literal include
|
|
include_role:
|
|
name: "literal-role"
|
|
"""
|
|
with open(os.path.join(self.producer_path, "tasks", "main.yml"), "w", encoding="utf-8") as f:
|
|
f.write(tasks_yaml)
|
|
|
|
# shadow folder
|
|
self.shadow_dir = tempfile.mkdtemp()
|
|
|
|
# Patch argv
|
|
self.orig_argv = sys.argv[:]
|
|
sys.argv = [
|
|
SCRIPT_PATH,
|
|
"-d", self.roles_dir,
|
|
"-s", self.shadow_dir,
|
|
"-o", "json",
|
|
]
|
|
|
|
# Ensure project root on sys.path
|
|
project_root = os.path.abspath(
|
|
os.path.join(os.path.dirname(__file__), "../../../../")
|
|
)
|
|
if project_root not in sys.path:
|
|
sys.path.insert(0, project_root)
|
|
|
|
def tearDown(self):
|
|
sys.argv = self.orig_argv
|
|
shutil.rmtree(self.roles_dir)
|
|
shutil.rmtree(self.shadow_dir)
|
|
|
|
@patch("cli.build.tree.output_graph")
|
|
@patch("cli.build.tree.build_mappings")
|
|
def test_include_role_dependencies_detected(self, mock_build_mappings, mock_output_graph):
|
|
# Basis-Graph leer, damit nur unsere Dependencies sichtbar sind
|
|
mock_build_mappings.return_value = {}
|
|
|
|
# Import und Ausführen
|
|
import importlib
|
|
tree_mod = importlib.import_module("cli.build.tree")
|
|
tree_mod.main()
|
|
|
|
# Erwarteter Pfad im Shadow-Folder
|
|
expected_tree_path = os.path.join(
|
|
self.shadow_dir, self.producer, "meta", "tree.json"
|
|
)
|
|
self.assertTrue(
|
|
os.path.isfile(expected_tree_path),
|
|
f"tree.json not found at {expected_tree_path}"
|
|
)
|
|
|
|
# JSON laden und Abhängigkeiten prüfen
|
|
with open(expected_tree_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
# Erwartete include_role-Dependenzen:
|
|
expected = sorted([
|
|
"sys-ctl-hlth-webserver", # aus loop
|
|
"sys-ctl-hlth-csp", # aus loop
|
|
"svc-db-postgres", # aus svc-db-{{database_type}}
|
|
"svc-db-mysql", # aus svc-db-{{database_type}}
|
|
"axb", # aus a{{database_type}}b
|
|
"ayyb", # aus a{{database_type}}b
|
|
"literal-role", # reiner Literalname
|
|
])
|
|
|
|
deps = (
|
|
data
|
|
.get("dependencies", {})
|
|
.get("include_role", [])
|
|
)
|
|
self.assertEqual(deps, expected, "include_role dependencies mismatch")
|
|
|
|
# Sicherstellen, dass der pure Variable-Name "{{database_type}}" NICHT aufgenommen wurde
|
|
self.assertNotIn("{{database_type}}", deps, "pure variable include should be ignored")
|
|
|
|
# Sicherstellen, dass im Original-meta der Producer-Role nichts geschrieben wurde
|
|
original_tree_path = os.path.join(self.producer_path, "meta", "tree.json")
|
|
self.assertFalse(
|
|
os.path.exists(original_tree_path),
|
|
"tree.json should NOT be written to the real meta/ folder"
|
|
)
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|