diff --git a/Makefile b/Makefile index 7751d7f1..b0f63dc5 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ install: build partial-test: @echo "🧪 Running Python tests…" - python -m unittest discover -s tests + PYTHONPATH=. python -m unittest discover -s tests @echo "📑 Checking Ansible syntax…" ansible-playbook playbook.yml --syntax-check diff --git a/filter_plugins/get_docker_paths.py b/filter_plugins/get_docker_paths.py index 724ba3f4..3e9e48bc 100644 --- a/filter_plugins/get_docker_paths.py +++ b/filter_plugins/get_docker_paths.py @@ -1,9 +1,15 @@ -def get_docker_paths(path_docker_compose_instances: str, application_id: str) -> dict: +import sys, os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +from module_utils.entity_name_utils import get_entity_name + +def get_docker_paths(application_id: str, path_docker_compose_instances: str) -> dict: """ Build the docker_compose dict based on path_docker_compose_instances and application_id. + Uses get_entity_name to extract the entity name from application_id. """ - base = f"{path_docker_compose_instances}{application_id}/" + entity = get_entity_name(application_id) + base = f"{path_docker_compose_instances}{entity}/" return { 'directories': { @@ -23,5 +29,5 @@ def get_docker_paths(path_docker_compose_instances: str, application_id: str) -> class FilterModule(object): def filters(self): return { - 'get_docker_paths': get_docker_paths, + 'get_docker_paths': get_docker_paths, } diff --git a/filter_plugins/get_entity_name.py b/filter_plugins/get_entity_name.py index 4973f16e..088d3560 100644 --- a/filter_plugins/get_entity_name.py +++ b/filter_plugins/get_entity_name.py @@ -1,52 +1,6 @@ -import os -import yaml - -def _load_categories_tree(categories_file): - with open(categories_file, 'r', encoding='utf-8') as f: - categories = yaml.safe_load(f)['roles'] - return categories - -def _flatten_categories(tree, prefix=''): - """Flattens nested category tree to all possible category paths.""" - result = [] - for k, v in tree.items(): - current = f"{prefix}-{k}" if prefix else k - result.append(current) - if isinstance(v, dict): - for sk, sv in v.items(): - if isinstance(sv, dict): - result.extend(_flatten_categories({sk: sv}, current)) - return result - -def get_entity_name(role_name): - """ - Automatically get the entity name from a role name by removing the - longest matching category path from categories.yml. - """ - possible_locations = [ - os.path.join(os.getcwd(), 'roles', 'categories.yml'), - os.path.join(os.path.dirname(__file__), '..', 'roles', 'categories.yml'), - 'roles/categories.yml', - ] - categories_file = None - for loc in possible_locations: - if os.path.exists(loc): - categories_file = loc - break - if not categories_file: - return role_name - - categories_tree = _load_categories_tree(categories_file) - all_category_paths = _flatten_categories(categories_tree) - - role_name_lc = role_name.lower() - all_category_paths = [cat.lower() for cat in all_category_paths] - for cat in sorted(all_category_paths, key=len, reverse=True): - if role_name_lc.startswith(cat + "-"): - return role_name[len(cat) + 1:] - if role_name_lc == cat: - return "" - return role_name +import sys, os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +from module_utils.entity_name_utils import get_entity_name class FilterModule(object): def filters(self): diff --git a/module_utils/entity_name_utils.py b/module_utils/entity_name_utils.py new file mode 100644 index 00000000..57169903 --- /dev/null +++ b/module_utils/entity_name_utils.py @@ -0,0 +1,49 @@ +import os +import yaml + +def load_categories_tree(categories_file): + with open(categories_file, 'r', encoding='utf-8') as f: + categories = yaml.safe_load(f)['roles'] + return categories + +def flatten_categories(tree, prefix=''): + """Flattens nested category tree to all possible category paths.""" + result = [] + for k, v in tree.items(): + current = f"{prefix}-{k}" if prefix else k + result.append(current) + if isinstance(v, dict): + for sk, sv in v.items(): + if isinstance(sv, dict): + result.extend(flatten_categories({sk: sv}, current)) + return result + +def get_entity_name(role_name): + """ + Get the entity name from a role name by removing the + longest matching category path from categories.yml. + """ + possible_locations = [ + os.path.join(os.getcwd(), 'roles', 'categories.yml'), + os.path.join(os.path.dirname(__file__), '..', 'roles', 'categories.yml'), + 'roles/categories.yml', + ] + categories_file = None + for loc in possible_locations: + if os.path.exists(loc): + categories_file = loc + break + if not categories_file: + return role_name + + categories_tree = load_categories_tree(categories_file) + all_category_paths = flatten_categories(categories_tree) + + role_name_lc = role_name.lower() + all_category_paths = [cat.lower() for cat in all_category_paths] + for cat in sorted(all_category_paths, key=len, reverse=True): + if role_name_lc.startswith(cat + "-"): + return role_name[len(cat) + 1:] + if role_name_lc == cat: + return "" + return role_name diff --git a/roles/cmp-rdbms/vars/database.yml b/roles/cmp-rdbms/vars/database.yml index 16ebfa8b..bf715529 100644 --- a/roles/cmp-rdbms/vars/database.yml +++ b/roles/cmp-rdbms/vars/database.yml @@ -6,8 +6,8 @@ _database_central_enabled: "{{ applications | get_app_conf(database_appli # Definition database_name: "{{ applications | get_app_conf( database_application_id, 'database.name', false, _database_consumer_entity_name ) }}" # The overwritte configuration is needed by bigbluebutton -database_instance: "{{ _database_central_name if _database_central_enabled else database_name }}" # This could lead to bugs at dedicated database @todo cleanup -database_host: "{{ _database_central_name if _database_central_enabled else 'database' }}" # This could lead to bugs at dedicated database @todo cleanup +database_instance: "{{ _database_central_name if _database_central_enabled else database_name }}" # This could lead to bugs at dedicated database @todo cleanup +database_host: "{{ _database_central_name if _database_central_enabled else 'database' }}" # This could lead to bugs at dedicated database @todo cleanup database_username: "{{ applications | get_app_conf(database_application_id, 'database.username', false, _database_consumer_entity_name)}}" # The overwritte configuration is needed by bigbluebutton database_password: "{{ applications | get_app_conf(database_application_id, 'credentials.database_password', true) }}" database_port: "{{ ports.localhost.database[ _database_id ] }}" diff --git a/roles/docker-compose/templates/volumes.yml.j2 b/roles/docker-compose/templates/volumes.yml.j2 index 9dfae1fe..8efc4408 100644 --- a/roles/docker-compose/templates/volumes.yml.j2 +++ b/roles/docker-compose/templates/volumes.yml.j2 @@ -1,6 +1,6 @@ {# This template needs to be included in docker-compose.yml which contain a database and additional volumes #} volumes: -{% if not applications | get_app_conf(application_id, 'features.central_database', False)%} +{% if not applications | get_app_conf(application_id, 'features.central_database', False) and applications | get_app_conf(application_id, 'docker.services.database.enabled', False) %} database: name: {{ database_volume }} {% endif %} diff --git a/roles/docker-compose/vars/docker-compose.yml b/roles/docker-compose/vars/docker-compose.yml index 514ec74a..e08088e8 100644 --- a/roles/docker-compose/vars/docker-compose.yml +++ b/roles/docker-compose/vars/docker-compose.yml @@ -1,2 +1,2 @@ # @See https://chatgpt.com/share/67a23d18-fb54-800f-983c-d6d00752b0b4 -docker_compose: "{{ path_docker_compose_instances | get_docker_paths(application_id) }}" \ No newline at end of file +docker_compose: "{{ application_id | get_docker_paths(path_docker_compose_instances) }}" \ No newline at end of file diff --git a/tests/integration/application_id/__init__.py b/tests/integration/application_id/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/application_id/test_application_id_deprecation.py b/tests/integration/application_id/test_application_id_deprecation.py new file mode 100644 index 00000000..bfda93ee --- /dev/null +++ b/tests/integration/application_id/test_application_id_deprecation.py @@ -0,0 +1,36 @@ +import os +import unittest +import yaml +import warnings + +# Dynamically determine the path to the roles directory +ROLES_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'roles')) + +class TestApplicationIdDeprecation(unittest.TestCase): + def test_application_id_matches_role_name(self): + """ + Deprecation: application_id in vars/main.yml must match the role name. + """ + for role in os.listdir(ROLES_DIR): + role_path = os.path.join(ROLES_DIR, role) + vars_main_yml = os.path.join(role_path, 'vars', 'main.yml') + if not os.path.isfile(vars_main_yml): + continue + with open(vars_main_yml, 'r', encoding='utf-8') as f: + try: + data = yaml.safe_load(f) + except Exception as e: + self.fail(f"Could not parse {vars_main_yml}: {e}") + if not isinstance(data, dict): + continue + app_id = data.get('application_id') + if app_id is not None and app_id != role: + warnings.warn( + f"[DEPRECATION WARNING] application_id '{app_id}' in {vars_main_yml} " + f"does not match its role directory '{role}'.\n" + f"Please update 'application_id' to match the role name for future compatibility.", + DeprecationWarning + ) + +if __name__ == "__main__": + unittest.main()