From d86ca6cc0e9555afbc582734660997de513f48ff Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Sun, 20 Jul 2025 09:29:56 +0200 Subject: [PATCH] Adapted discourse version to new code after the big refactoring --- filter_plugins/canonical_domains_map.py | 6 ++++ filter_plugins/get_app_conf.py | 6 +++- roles/web-app-discourse/Todo.md | 3 +- roles/web-app-discourse/config/main.yml | 13 +++++++ roles/web-app-discourse/handlers/main.yml | 2 +- roles/web-app-discourse/tasks/main.yml | 9 +++-- .../web-app-discourse/templates/config.yml.j2 | 19 ++++------ .../templates/docker-compose.yml.j2 | 2 +- roles/web-app-discourse/vars/main.yml | 4 ++- tasks/stages/01_constructor.yml | 2 +- .../test_handlers_no_vars_in_name.py | 36 +++++++++++++++++++ 11 files changed, 81 insertions(+), 21 deletions(-) create mode 100644 tests/integration/test_handlers_no_vars_in_name.py diff --git a/filter_plugins/canonical_domains_map.py b/filter_plugins/canonical_domains_map.py index 0e005279..98a48cda 100644 --- a/filter_plugins/canonical_domains_map.py +++ b/filter_plugins/canonical_domains_map.py @@ -13,6 +13,12 @@ class FilterModule(object): seen_domains = {} for app_id, cfg in apps.items(): + if not isinstance(cfg, dict): + raise AnsibleFilterError( + f"Invalid configuration for application '{app_id}': " + f"expected a dict, got {cfg!r}" + ) + domains_cfg = cfg.get('domains') if not domains_cfg or 'canonical' not in domains_cfg: self._add_default_domain(app_id, primary_domain, seen_domains, result) diff --git a/filter_plugins/get_app_conf.py b/filter_plugins/get_app_conf.py index 6c4486e7..cd55d5e9 100644 --- a/filter_plugins/get_app_conf.py +++ b/filter_plugins/get_app_conf.py @@ -2,6 +2,7 @@ import os import re import yaml from ansible.errors import AnsibleFilterError +from collections.abc import Mapping from ansible.errors import AnsibleUndefinedVariable try: @@ -62,12 +63,15 @@ def get_app_conf(applications, application_id, config_path, strict=True, default return default if default is not None else False raise AppConfigKeyError( f"Key '{k}' is undefined at '{'.'.join(path_trace)}'\n" + f" actual type: {type(obj).__name__}\n" + f" repr(obj): {obj!r}\n" + f" repr(applications): {applications!r}\n" f"application_id: {application_id}\n" f"config_path: {config_path}" ) # Access dict key - if isinstance(obj, dict): + if isinstance(obj, Mapping): if k not in obj: # Non-strict mode: always return default on missing key if not strict: diff --git a/roles/web-app-discourse/Todo.md b/roles/web-app-discourse/Todo.md index 26940718..33711e91 100644 --- a/roles/web-app-discourse/Todo.md +++ b/roles/web-app-discourse/Todo.md @@ -1,2 +1,3 @@ # Todo -- Finish LDAP implementation \ No newline at end of file +- Finish LDAP implementation +- Check if this current network setting makes sense. Seems a bit unneccessary complicated. Could be that a more straight foreword approach makes more sense. \ No newline at end of file diff --git a/roles/web-app-discourse/config/main.yml b/roles/web-app-discourse/config/main.yml index ccbaf1ab..715facb0 100644 --- a/roles/web-app-discourse/config/main.yml +++ b/roles/web-app-discourse/config/main.yml @@ -32,3 +32,16 @@ docker: volumes: data: discourse_data network: discourse +plugins: + docker_manager: + enabled: true + discourse-activity-pub: + enabled: true + discourse-akismet: + enabled: true + discourse-cakeday: + enabled: true + discourse-solved: + enabled: true + discourse-voting: + enabled: true \ No newline at end of file diff --git a/roles/web-app-discourse/handlers/main.yml b/roles/web-app-discourse/handlers/main.yml index e0466b02..6a821d23 100644 --- a/roles/web-app-discourse/handlers/main.yml +++ b/roles/web-app-discourse/handlers/main.yml @@ -7,7 +7,7 @@ failed_when: container_action.failed and 'No such container' not in container_action.msg listen: recreate discourse -- name: "add central database temporary to {{ discourse_network }}" +- name: "add central database temporary to discourse network" command: "docker network connect {{ discourse_network }} {{ database_host }}" failed_when: > result.rc != 0 and diff --git a/roles/web-app-discourse/tasks/main.yml b/roles/web-app-discourse/tasks/main.yml index 0ed10bf8..4e9fc12d 100644 --- a/roles/web-app-discourse/tasks/main.yml +++ b/roles/web-app-discourse/tasks/main.yml @@ -35,9 +35,10 @@ template: src: config.yml.j2 dest: "{{ discourse_application_yml_destination }}" + mode: '0640' notify: recreate discourse - - name: "Verify that {{ discourse_container }} is running" + - name: "Verify that '{{ discourse_container }}' is running" command: docker compose ps --filter status=running --format '{{"{{"}}.Name{{"}}"}}' | grep -x {{ discourse_container }} register: docker_ps changed_when: docker_ps.rc == 1 @@ -62,13 +63,17 @@ when: - applications | get_app_conf(application_id, 'features.central_database', False) + - name: Set error string for network not connected + set_fact: + docker_discourse_not_connected: 'is not connected to network {{ discourse_network }}' + - name: "Remove {{ discourse_network }} from {{ database_host }}" command: > docker network disconnect {{ discourse_network }} {{ database_host }} register: network_disconnect failed_when: > network_disconnect.rc != 0 and - 'is not connected to network {{ discourse_network }}' not in network_disconnect.stderr + docker_discourse_not_connected not in network_disconnect.stderr changed_when: network_disconnect.rc == 0 when: - applications | get_app_conf(application_id, 'features.central_database', False) diff --git a/roles/web-app-discourse/templates/config.yml.j2 b/roles/web-app-discourse/templates/config.yml.j2 index ae8ed622..ff7c7e26 100644 --- a/roles/web-app-discourse/templates/config.yml.j2 +++ b/roles/web-app-discourse/templates/config.yml.j2 @@ -74,7 +74,7 @@ env: DISCOURSE_DB_NAME: {{ database_name }} # Redis Configuration - DISCOURSE_REDIS_HOST: {{application_id}}-redis + DISCOURSE_REDIS_HOST: {{ discourse_redis_host }} ## If you added the Lets Encrypt template, uncomment below to get a free SSL certificate #LETSENCRYPT_ACCOUNT_EMAIL: administrator@veen.world @@ -103,18 +103,11 @@ hooks: - exec: cd: $home/plugins cmd: - - git clone --depth=1 https://github.com/discourse/docker_manager.git - - git clone --depth=1 https://github.com/discourse/discourse-activity-pub.git - - git clone --depth=1 https://github.com/discourse/discourse-calendar.git - - git clone --depth=1 https://github.com/discourse/discourse-akismet.git - - git clone --depth=1 https://github.com/discourse/discourse-cakeday.git - - git clone --depth=1 https://github.com/discourse/discourse-solved.git - - git clone --depth=1 https://github.com/discourse/discourse-voting.git - - git clone --depth=1 https://github.com/discourse/discourse-oauth2-basic.git - -{% if applications | get_app_conf(application_id, 'features.oidc', False) %} - - git clone --depth=1 https://github.com/discourse/discourse-openid-connect.git +{% for plugin_name, plugin_config in discourse_plugins.items() %} +{% if plugin_config.enabled %} + - git clone --depth=1 https://github.com/discourse/{{ plugin_name }}.git {% endif %} +{% endfor %} {% if applications | get_app_conf(application_id, 'features.ldap', False) %} - git clone --depth=1 https://github.com/jonmbake/discourse-ldap-auth.git @@ -177,5 +170,5 @@ run: - exec: echo "End of custom commands" docker_args: - - --network={{application_id}}_default + - --network={{ discourse_network }} - --name={{ discourse_container }} diff --git a/roles/web-app-discourse/templates/docker-compose.yml.j2 b/roles/web-app-discourse/templates/docker-compose.yml.j2 index f3ed8012..552af217 100644 --- a/roles/web-app-discourse/templates/docker-compose.yml.j2 +++ b/roles/web-app-discourse/templates/docker-compose.yml.j2 @@ -4,5 +4,5 @@ redis: {% include 'roles/docker-compose/templates/networks.yml.j2' %} - discourse_default: + {{ discourse_network }}: external: true \ No newline at end of file diff --git a/roles/web-app-discourse/vars/main.yml b/roles/web-app-discourse/vars/main.yml index c48e8102..58925dac 100644 --- a/roles/web-app-discourse/vars/main.yml +++ b/roles/web-app-discourse/vars/main.yml @@ -6,10 +6,12 @@ database_type: "postgres" # Discourse discourse_container: "{{ applications | get_app_conf(application_id, 'docker.services.discourse.name') }}" -discourse_application_yml_destination: "{{ docker_repository_directory }}containers/{{ discourse_container }}.yml" discourse_network: "{{ applications | get_app_conf(application_id, 'docker.network') }}" discourse_volume: "{{ applications | get_app_conf(application_id, 'docker.volumes.data') }}" +discourse_plugins: "{{ applications | get_app_conf(application_id, 'plugins') }}" discourse_pg_network: "{{ applications | get_app_conf('svc-db-postgres', 'docker.network' ) }}" +discourse_application_yml_destination: "{{ docker_repository_directory }}containers/{{ discourse_container }}.yml" +discourse_redis_host: "{{ application_id |get_entity_name }}-redis" # General Docker Configuration docker_repository_directory : "{{ docker_compose.directories.services}}{{applications | get_app_conf( application_id, 'repository') }}/" diff --git a/tasks/stages/01_constructor.yml b/tasks/stages/01_constructor.yml index fe968e04..b35e5ec1 100644 --- a/tasks/stages/01_constructor.yml +++ b/tasks/stages/01_constructor.yml @@ -16,7 +16,7 @@ - name: Merge application definitions set_fact: - applications: "{{ defaults_applications | combine(applications | default({}, true), recursive=True) }}" + applications: "{{ defaults_applications | merge_with_defaults(applications | default({}, true)) }}" - name: Merge current play applications set_fact: diff --git a/tests/integration/test_handlers_no_vars_in_name.py b/tests/integration/test_handlers_no_vars_in_name.py new file mode 100644 index 00000000..9c1324c0 --- /dev/null +++ b/tests/integration/test_handlers_no_vars_in_name.py @@ -0,0 +1,36 @@ +import unittest +import yaml +from pathlib import Path + +class HandlerNameIntegrationTest(unittest.TestCase): + """ + Integration test to ensure that handler definitions in Ansible roles + do not include Jinja variable interpolations in their 'name' attribute. + """ + + def test_handlers_have_no_variables_in_name(self): + # Locate all handler YAML files under roles/*/handlers/ + handler_files = Path('roles').glob('*/handlers/*.yml') + for handler_file in handler_files: + with self.subTest(handler_file=str(handler_file)): + content = handler_file.read_text(encoding='utf-8') + # Load all documents in the YAML file + documents = list(yaml.safe_load_all(content)) + for index, doc in enumerate(documents): + if not isinstance(doc, dict): + continue + # Only consider entries that are handlers (they have a 'listen' key) + if 'listen' in doc: + name = doc.get('name', '') + # Assert that no Jinja interpolation is present in the name + self.assertNotRegex( + name, + r"{{.*}}", + msg=( + f"Handler 'name' in file {handler_file} document #{index} " + f"contains a Jinja variable: {name}" + ) + ) + +if __name__ == '__main__': + unittest.main()