mirror of
				https://github.com/kevinveenbirkenbach/computer-playbook.git
				synced 2025-10-31 02:10:05 +00:00 
			
		
		
		
	Implemented that schemas are recognized
This commit is contained in:
		| @@ -53,7 +53,8 @@ class DefaultsGenerator: | ||||
|                 continue | ||||
|  | ||||
|             if not config_file.exists(): | ||||
|                 self.log(f"Skipping {role_name}: config/main.yml missing") | ||||
|                 self.log(f"Config missing for {role_name}, adding empty defaults for '{application_id}'") | ||||
|                 result["defaults_applications"][application_id] = {} | ||||
|                 continue | ||||
|  | ||||
|             config_data = load_yaml_file(config_file) | ||||
| @@ -106,4 +107,4 @@ if __name__ == "__main__": | ||||
|     roles_dir = (cwd / args.roles_dir).resolve() | ||||
|     output_file = (cwd / args.output_file).resolve() | ||||
|  | ||||
|     DefaultsGenerator(roles_dir, output_file, args.verbose, args.timeout).run() | ||||
|     DefaultsGenerator(roles_dir, output_file, args.verbose, args.timeout).run() | ||||
|   | ||||
| @@ -18,20 +18,6 @@ vendor_and_product_id: "" | ||||
|  | ||||
| The `vendor_and_product_id` variable is required and should be set to the vendor and product ID of the MSI laptop. | ||||
|  | ||||
| ## Dependencies | ||||
|  | ||||
| - `sys-pgm-aur` | ||||
|  | ||||
| ## Example Playbook | ||||
|  | ||||
| ```yaml | ||||
| - hosts: all | ||||
|   roles: | ||||
|     - keyboard-color | ||||
|   vars: | ||||
|     vendor_and_product_id: "your_vendor_and_product_id" | ||||
| ``` | ||||
|  | ||||
| ## Author | ||||
|  | ||||
| This role was created by [Kevin Veen-Birkenbach](https://github.com/kevinveenbirkenbach). | ||||
|   | ||||
							
								
								
									
										2
									
								
								roles/drv-msi-keyboard-color/Todo.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								roles/drv-msi-keyboard-color/Todo.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| # Todo | ||||
| - Implement schema | ||||
							
								
								
									
										1
									
								
								roles/drv-msi-keyboard-color/schema/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								roles/drv-msi-keyboard-color/schema/main.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| vendor_and_product_id: "" # @todo schema needs to be implemented | ||||
| @@ -1 +1,2 @@ | ||||
| application_id: drv-msi-keyboard-color | ||||
| application_id:         drv-msi-keyboard-color | ||||
| vendor_and_product_id:  "{{ applications | get_app_conf(application_id, 'vendor_and_product_id') }}" | ||||
|   | ||||
| @@ -7,7 +7,7 @@ from pathlib import Path | ||||
|  | ||||
| import yaml  # requires PyYAML | ||||
| from filter_plugins.get_role import get_role | ||||
|  | ||||
| from filter_plugins.get_app_conf import get_app_conf, ConfigEntryNotSetError | ||||
|  | ||||
| class TestGetAppConfPaths(unittest.TestCase): | ||||
|     @classmethod | ||||
| @@ -88,12 +88,19 @@ class TestGetAppConfPaths(unittest.TestCase): | ||||
|             cur = cur[k] | ||||
|  | ||||
|     def test_literal_paths(self): | ||||
|         # Check each literal path exists | ||||
|         # Check each literal path exists or is allowed by schema | ||||
|         for app_id, paths in self.literal_paths.items(): | ||||
|             with self.subTest(app=app_id): | ||||
|                 self.assertIn(app_id, self.defaults_app, f"App '{app_id}' missing in defaults_applications") | ||||
|                 for dotted, occs in paths.items(): | ||||
|                     with self.subTest(path=dotted): | ||||
|                         try: | ||||
|                             # will raise ConfigEntryNotSetError if defined in schema but not set | ||||
|                             get_app_conf(self.defaults_app, app_id, dotted, strict=True) | ||||
|                         except ConfigEntryNotSetError: | ||||
|                             # defined in schema but not set: acceptable | ||||
|                             continue | ||||
|                         # otherwise, perform static validation | ||||
|                         self._validate(app_id, dotted, occs) | ||||
|  | ||||
|     def test_variable_paths(self): | ||||
| @@ -103,6 +110,13 @@ class TestGetAppConfPaths(unittest.TestCase): | ||||
|         for dotted, occs in self.variable_paths.items(): | ||||
|             with self.subTest(path=dotted): | ||||
|                 found = False | ||||
|                 # schema-defined entries: acceptable if defined in any role schema | ||||
|                 for schema in self.role_schemas.values(): | ||||
|                     if isinstance(schema, dict) and dotted in schema: | ||||
|                         found = True | ||||
|                         break | ||||
|                 if found: | ||||
|                     continue | ||||
|                 # credentials.*: zuerst in defaults_applications prüfen, dann im Schema | ||||
|                 if dotted.startswith('credentials.'): | ||||
|                     key = dotted.split('.', 1)[1] | ||||
| @@ -182,4 +196,4 @@ class TestGetAppConfPaths(unittest.TestCase): | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
|     unittest.main() | ||||
| @@ -50,6 +50,61 @@ class TestGenerateDefaultApplications(unittest.TestCase): | ||||
|         self.assertIn("testapp", data["defaults_applications"]) | ||||
|         self.assertEqual(data["defaults_applications"]["testapp"]["foo"], "bar") | ||||
|         self.assertEqual(data["defaults_applications"]["testapp"]["baz"], 123) | ||||
|          | ||||
|     def test_missing_config_adds_empty_defaults(self): | ||||
|         """ | ||||
|         If a role has vars/main.yml but no config/main.yml, | ||||
|         the generator should still create an entry with an empty dict. | ||||
|         """ | ||||
|         # Create a role with vars/main.yml but without config/main.yml | ||||
|         role_no_config = self.roles_dir / "role-no-config" | ||||
|         (role_no_config / "vars").mkdir(parents=True) | ||||
|         (role_no_config / "vars" / "main.yml").write_text("application_id: noconfigapp\n") | ||||
|  | ||||
|         # Run the generator | ||||
|         result = subprocess.run( | ||||
|             ["python3", str(self.script_path), | ||||
|              "--roles-dir", str(self.roles_dir), | ||||
|              "--output-file", str(self.output_file)], | ||||
|             capture_output=True, text=True | ||||
|         ) | ||||
|         self.assertEqual(result.returncode, 0, msg=result.stderr) | ||||
|  | ||||
|         # Verify the output YAML | ||||
|         data = yaml.safe_load(self.output_file.read_text()) | ||||
|         apps = data.get("defaults_applications", {}) | ||||
|  | ||||
|         # The new application_id must exist and be an empty dict | ||||
|         self.assertIn("noconfigapp", apps) | ||||
|         self.assertEqual(apps["noconfigapp"], {}) | ||||
|  | ||||
|     def test_no_config_directory_adds_empty_defaults(self): | ||||
|         """ | ||||
|         If a role has vars/main.yml but no config directory at all, | ||||
|         the generator should still emit an empty-dict entry. | ||||
|         """ | ||||
|         # Create a role with vars/main.yml but do not create config/ at all | ||||
|         role_no_cfg_dir = self.roles_dir / "role-no-cfg-dir" | ||||
|         (role_no_cfg_dir / "vars").mkdir(parents=True) | ||||
|         (role_no_cfg_dir / "vars" / "main.yml").write_text("application_id: nocfgdirapp\n") | ||||
|         # Note: no config/ directory is created here | ||||
|  | ||||
|         # Run the generator again | ||||
|         result = subprocess.run( | ||||
|             ["python3", str(self.script_path), | ||||
|              "--roles-dir", str(self.roles_dir), | ||||
|              "--output-file", str(self.output_file)], | ||||
|             capture_output=True, text=True | ||||
|         ) | ||||
|         self.assertEqual(result.returncode, 0, msg=result.stderr) | ||||
|  | ||||
|         # Load and inspect the output | ||||
|         data = yaml.safe_load(self.output_file.read_text()) | ||||
|         apps = data.get("defaults_applications", {}) | ||||
|  | ||||
|         # Ensure that the application_id appears with an empty mapping | ||||
|         self.assertIn("nocfgdirapp", apps) | ||||
|         self.assertEqual(apps["nocfgdirapp"], {}) | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|   | ||||
							
								
								
									
										71
									
								
								tests/unit/filter_plugins/test_app_conf_extra_schema.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								tests/unit/filter_plugins/test_app_conf_extra_schema.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| import os | ||||
| import tempfile | ||||
| import unittest | ||||
| import yaml | ||||
| from pathlib import Path | ||||
|  | ||||
| from filter_plugins.get_app_conf import get_app_conf, AppConfigKeyError, ConfigEntryNotSetError | ||||
|  | ||||
| class TestGetAppConfFunctionality(unittest.TestCase): | ||||
|     def setUp(self): | ||||
|         # Create a temp directory to simulate roles and schema files | ||||
|         self.tempdir = tempfile.TemporaryDirectory() | ||||
|         self.roles_dir = Path(self.tempdir.name) / "roles" | ||||
|         self.roles_dir.mkdir() | ||||
|  | ||||
|         # application_id used in tests | ||||
|         self.app_id = "testapp" | ||||
|         # application dict missing the 'nested.key' | ||||
|         self.applications = {self.app_id: {}} | ||||
|  | ||||
|         # Create schema directory and file for testapp | ||||
|         schema_dir = self.roles_dir / self.app_id / "schema" | ||||
|         schema_dir.mkdir(parents=True) | ||||
|         schema_file = schema_dir / "main.yml" | ||||
|         # Define a nested key in schema | ||||
|         schema_data = { | ||||
|             'nested': { | ||||
|                 'key': '' | ||||
|             } | ||||
|         } | ||||
|         with schema_file.open('w', encoding='utf-8') as f: | ||||
|             yaml.safe_dump(schema_data, f) | ||||
|  | ||||
|         # Change cwd so get_app_conf finds roles/<app_id>/schema/main.yml | ||||
|         self.orig_cwd = os.getcwd() | ||||
|         os.chdir(self.tempdir.name) | ||||
|  | ||||
|     def tearDown(self): | ||||
|         os.chdir(self.orig_cwd) | ||||
|         self.tempdir.cleanup() | ||||
|  | ||||
|     def test_config_entry_not_set_error_raised(self): | ||||
|         # Attempt to access a key defined in schema but not set in applications | ||||
|         with self.assertRaises(ConfigEntryNotSetError): | ||||
|             get_app_conf(self.applications, self.app_id, 'nested.key', strict=True) | ||||
|  | ||||
|     def test_strict_key_error_when_not_in_schema(self): | ||||
|         # Access a key not defined in schema nor in applications | ||||
|         with self.assertRaises(AppConfigKeyError): | ||||
|             get_app_conf(self.applications, self.app_id, 'undefined.path', strict=True) | ||||
|  | ||||
|     def test_non_strict_returns_default(self): | ||||
|         # Non-strict mode should return default instead of raising | ||||
|         default_val = 'fallback' | ||||
|         result = get_app_conf(self.applications, self.app_id, 'nested.key', strict=False, default=default_val) | ||||
|         self.assertEqual(result, default_val) | ||||
|  | ||||
|     def test_list_indexing_and_missing(self): | ||||
|         # Extend schema to define a list | ||||
|         schema_file = Path('roles') / self.app_id / 'schema' / 'main.yml' | ||||
|         schema_data = {'items': ['']} | ||||
|         with schema_file.open('w', encoding='utf-8') as f: | ||||
|             yaml.safe_dump(schema_data, f) | ||||
|  | ||||
|         # Insert list into applications but leave index out of range | ||||
|         self.applications[self.app_id]['items'] = [] | ||||
|         with self.assertRaises(AppConfigKeyError): | ||||
|             get_app_conf(self.applications, self.app_id, 'items[0]', strict=True) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
		Reference in New Issue
	
	Block a user