From 45624037b1df417170d397ad9ebaf5a7ef971acf Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Fri, 18 Jul 2025 19:35:44 +0200 Subject: [PATCH] Added shadow option to tree for mig --- cli/build/tree.py | 15 ++++- .../unit/cli/{generate => build}/__init__.py | 0 .../conditional_role_include/__init__.py | 0 .../test_conditional_role_include.py | 0 .../{generate => build}/defaults/__init__.py | 0 .../defaults/test_applications.py | 0 .../defaults/test_applications_and_users.py | 0 .../defaults/test_users.py | 0 .../unit/cli/build/test_tree_shadow_folder.py | 66 +++++++++++++++++++ 9 files changed, 80 insertions(+), 1 deletion(-) rename tests/unit/cli/{generate => build}/__init__.py (100%) rename tests/unit/cli/{generate => build}/conditional_role_include/__init__.py (100%) rename tests/unit/cli/{generate => build}/conditional_role_include/test_conditional_role_include.py (100%) rename tests/unit/cli/{generate => build}/defaults/__init__.py (100%) rename tests/unit/cli/{generate => build}/defaults/test_applications.py (100%) rename tests/unit/cli/{generate => build}/defaults/test_applications_and_users.py (100%) rename tests/unit/cli/{generate => build}/defaults/test_users.py (100%) create mode 100644 tests/unit/cli/build/test_tree_shadow_folder.py diff --git a/cli/build/tree.py b/cli/build/tree.py index 05a20934..b154987c 100644 --- a/cli/build/tree.py +++ b/cli/build/tree.py @@ -45,6 +45,12 @@ def main(): action='store_true', help="Preview graphs to console instead of writing files" ) + parser.add_argument( + '-s', '--shadow-folder', + type=str, + default=None, + help="If set, writes tree.json to this shadow folder instead of the role's actual meta/ folder" + ) parser.add_argument( '-v', '--verbose', action='store_true', @@ -57,6 +63,7 @@ def main(): print(f"Max depth: {args.depth}") print(f"Output format: {args.output}") print(f"Preview mode: {args.preview}") + print(f"Shadow folder: {args.shadow_folder}") for role_name, role_path in find_roles(args.role_dir): if args.verbose: @@ -74,7 +81,13 @@ def main(): print(f"Previewing graph '{key}' for role '{role_name}'") output_graph(data, 'console', role_name, key) else: - tree_file = os.path.join(role_path, 'meta', 'tree.json') + # Decide on output folder + if args.shadow_folder: + tree_file = os.path.join( + args.shadow_folder, role_name, 'meta', 'tree.json' + ) + else: + tree_file = os.path.join(role_path, 'meta', 'tree.json') os.makedirs(os.path.dirname(tree_file), exist_ok=True) with open(tree_file, 'w') as f: json.dump(graphs, f, indent=2) diff --git a/tests/unit/cli/generate/__init__.py b/tests/unit/cli/build/__init__.py similarity index 100% rename from tests/unit/cli/generate/__init__.py rename to tests/unit/cli/build/__init__.py diff --git a/tests/unit/cli/generate/conditional_role_include/__init__.py b/tests/unit/cli/build/conditional_role_include/__init__.py similarity index 100% rename from tests/unit/cli/generate/conditional_role_include/__init__.py rename to tests/unit/cli/build/conditional_role_include/__init__.py diff --git a/tests/unit/cli/generate/conditional_role_include/test_conditional_role_include.py b/tests/unit/cli/build/conditional_role_include/test_conditional_role_include.py similarity index 100% rename from tests/unit/cli/generate/conditional_role_include/test_conditional_role_include.py rename to tests/unit/cli/build/conditional_role_include/test_conditional_role_include.py diff --git a/tests/unit/cli/generate/defaults/__init__.py b/tests/unit/cli/build/defaults/__init__.py similarity index 100% rename from tests/unit/cli/generate/defaults/__init__.py rename to tests/unit/cli/build/defaults/__init__.py diff --git a/tests/unit/cli/generate/defaults/test_applications.py b/tests/unit/cli/build/defaults/test_applications.py similarity index 100% rename from tests/unit/cli/generate/defaults/test_applications.py rename to tests/unit/cli/build/defaults/test_applications.py diff --git a/tests/unit/cli/generate/defaults/test_applications_and_users.py b/tests/unit/cli/build/defaults/test_applications_and_users.py similarity index 100% rename from tests/unit/cli/generate/defaults/test_applications_and_users.py rename to tests/unit/cli/build/defaults/test_applications_and_users.py diff --git a/tests/unit/cli/generate/defaults/test_users.py b/tests/unit/cli/build/defaults/test_users.py similarity index 100% rename from tests/unit/cli/generate/defaults/test_users.py rename to tests/unit/cli/build/defaults/test_users.py diff --git a/tests/unit/cli/build/test_tree_shadow_folder.py b/tests/unit/cli/build/test_tree_shadow_folder.py new file mode 100644 index 00000000..9eb9ca7e --- /dev/null +++ b/tests/unit/cli/build/test_tree_shadow_folder.py @@ -0,0 +1,66 @@ +import os +import sys +import json +import tempfile +import shutil +import unittest +from unittest.mock import patch + +# Import the script as a module (assumes the script is named tree.py) +SCRIPT_PATH = os.path.abspath( + os.path.join(os.path.dirname(__file__), "../../../../cli/build/tree.py") +) + +class TestTreeShadowFolder(unittest.TestCase): + def setUp(self): + # Create temp roles dir and a dummy role + self.roles_dir = tempfile.mkdtemp() + self.role_name = "dummyrole" + self.role_path = os.path.join(self.roles_dir, self.role_name) + os.makedirs(os.path.join(self.role_path, "meta")) + + # Prepare shadow dir + self.shadow_dir = tempfile.mkdtemp() + + # Patch sys.argv for the script + self.orig_argv = sys.argv[:] + sys.argv = [ + SCRIPT_PATH, + "-d", self.roles_dir, + "-s", self.shadow_dir, + "-o", "json" + ] + + def tearDown(self): + sys.argv = self.orig_argv + shutil.rmtree(self.roles_dir) + shutil.rmtree(self.shadow_dir) + + @patch("cli.build.tree.build_mappings") + @patch("cli.build.tree.output_graph") + def test_tree_json_written_to_shadow_folder(self, mock_output_graph, mock_build_mappings): + # Prepare dummy graph + dummy_graph = {"dummy": {"test": 42}} + mock_build_mappings.return_value = dummy_graph + + # Run the script (as __main__) + import runpy + runpy.run_path(SCRIPT_PATH, run_name="__main__") + + # Check file in shadow folder + expected_tree_path = os.path.join( + self.shadow_dir, self.role_name, "meta", "tree.json" + ) + self.assertTrue(os.path.isfile(expected_tree_path), "tree.json not found in shadow folder") + + # Check contents + with open(expected_tree_path) as f: + data = json.load(f) + self.assertEqual(data, dummy_graph, "tree.json content mismatch") + + # Ensure nothing was written to original meta/ + original_tree_path = os.path.join(self.role_path, "meta", "tree.json") + self.assertFalse(os.path.isfile(original_tree_path), "tree.json should NOT be in role's meta/") + +if __name__ == "__main__": + unittest.main()