From d5dd56899424230de9c15b536c261d6974f05f60 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Tue, 20 May 2025 04:19:10 +0200 Subject: [PATCH] Optimized code and solved bugs --- .../applications_if_group_and_deps.py | 92 ++++++++----- filter_plugins/canonical_domains_map.py | 126 ++++++++++-------- group_vars/all/15_about.yml | 8 +- roles/dns-records-cloudflare/tasks/main.yml | 5 - roles/docker-bigbluebutton/templates/env.j2 | 2 +- .../templates/discourse_application.yml.j2 | 2 +- roles/docker-espocrm/templates/env.j2 | 2 +- roles/docker-listmonk/vars/main.yml | 2 +- roles/docker-mailu/templates/env.j2 | 2 +- roles/docker-mailu/vars/main.yml | 2 +- roles/docker-mastodon/templates/env.j2 | 2 +- roles/docker-nextcloud/vars/configuration.yml | 2 - .../templates/oauth2-proxy-keycloak.cfg.j2 | 8 +- .../docker-openproject/vars/configuration.yml | 2 + roles/docker-pgadmin/vars/main.yml | 2 +- roles/docker-phpmyadmin/vars/main.yml | 2 +- .../health-nginx/templates/health-nginx.py.j2 | 2 +- roles/nginx-serve-html/vars/main.yml | 2 +- tasks/constructor.yml | 36 ++--- tasks/server.yml | 2 +- tests/unit/test_load_configuration.py | 8 +- 21 files changed, 167 insertions(+), 144 deletions(-) diff --git a/filter_plugins/applications_if_group_and_deps.py b/filter_plugins/applications_if_group_and_deps.py index 64723e9e..54572b5b 100644 --- a/filter_plugins/applications_if_group_and_deps.py +++ b/filter_plugins/applications_if_group_and_deps.py @@ -1,8 +1,8 @@ from ansible.errors import AnsibleFilterError import os -import sys import yaml + class FilterModule(object): def filters(self): return { @@ -15,48 +15,67 @@ class FilterModule(object): 1) directly in group_names, or 2) the application_id of any role reachable (recursively) from any group in group_names via meta/dependencies. - Expects: - - applications: dict mapping application_id → config - - group_names: list of active role names """ - # validate inputs + self._validate_inputs(applications, group_names) + + roles_dir = self._get_roles_directory() + + included_roles = self._collect_reachable_roles(group_names, roles_dir) + included_app_ids = self._gather_application_ids(included_roles, roles_dir) + + return self._filter_applications(applications, group_names, included_app_ids) + + def _validate_inputs(self, applications, group_names): + """Validate the inputs for correct types.""" if not isinstance(applications, dict): raise AnsibleFilterError(f"Expected applications as dict, got {type(applications).__name__}") if not isinstance(group_names, (list, tuple)): raise AnsibleFilterError(f"Expected group_names as list/tuple, got {type(group_names).__name__}") - # locate roles directory (assume plugin sits in filter_plugins/) + def _get_roles_directory(self): + """Locate and return the roles directory.""" plugin_dir = os.path.dirname(__file__) project_root = os.path.abspath(os.path.join(plugin_dir, '..')) - roles_dir = os.path.join(project_root, 'roles') - - # recursively collect all roles reachable from the given groups - def collect_roles(role, seen): - if role in seen: - return - seen.add(role) - meta_file = os.path.join(roles_dir, role, 'meta', 'main.yml') - if not os.path.isfile(meta_file): - return - try: - with open(meta_file) as f: - meta = yaml.safe_load(f) or {} - except Exception: - return - for dep in meta.get('dependencies', []): - if isinstance(dep, str): - dep_name = dep - elif isinstance(dep, dict): - dep_name = dep.get('role') or dep.get('name') - else: - continue - collect_roles(dep_name, seen) + return os.path.join(project_root, 'roles') + def _collect_reachable_roles(self, group_names, roles_dir): + """Recursively collect all roles reachable from the given groups via meta/dependencies.""" included_roles = set() - for grp in group_names: - collect_roles(grp, included_roles) + for group in group_names: + self._collect_roles_from_group(group, included_roles, roles_dir) + return included_roles - # gather application_ids from those roles + def _collect_roles_from_group(self, group, seen, roles_dir): + """Recursively collect roles from a specific group.""" + if group in seen: + return + seen.add(group) + + meta_file = os.path.join(roles_dir, group, 'meta', 'main.yml') + if not os.path.isfile(meta_file): + return + + try: + with open(meta_file) as f: + meta = yaml.safe_load(f) or {} + except Exception: + return + + for dep in meta.get('dependencies', []): + dep_name = self._get_dependency_name(dep) + if dep_name: + self._collect_roles_from_group(dep_name, seen, roles_dir) + + def _get_dependency_name(self, dependency): + """Extract the dependency role name from the meta data.""" + if isinstance(dependency, str): + return dependency + elif isinstance(dependency, dict): + return dependency.get('role') or dependency.get('name') + return None + + def _gather_application_ids(self, included_roles, roles_dir): + """Gather application_ids from the roles.""" included_app_ids = set() for role in included_roles: vars_file = os.path.join(roles_dir, role, 'vars', 'main.yml') @@ -67,14 +86,17 @@ class FilterModule(object): vars_data = yaml.safe_load(f) or {} except Exception: continue + app_id = vars_data.get('application_id') if isinstance(app_id, str) and app_id: included_app_ids.add(app_id) - # build filtered result: include any application whose key is in group_names or in included_app_ids + return included_app_ids + + def _filter_applications(self, applications, group_names, included_app_ids): + """Filter and return the applications that match the conditions.""" result = {} for app_key, cfg in applications.items(): if app_key in group_names or app_key in included_app_ids: result[app_key] = cfg - - return result \ No newline at end of file + return result diff --git a/filter_plugins/canonical_domains_map.py b/filter_plugins/canonical_domains_map.py index afbb0c10..0e005279 100644 --- a/filter_plugins/canonical_domains_map.py +++ b/filter_plugins/canonical_domains_map.py @@ -5,71 +5,79 @@ class FilterModule(object): return {'canonical_domains_map': self.canonical_domains_map} def canonical_domains_map(self, apps, primary_domain): - def parse_entry(domains_cfg, key, app_id): - if key not in domains_cfg: - return None - entry = domains_cfg[key] - if isinstance(entry, dict): - values = list(entry.values()) - elif isinstance(entry, list): - values = entry - else: - raise AnsibleFilterError( - f"Unexpected type for 'domains.{key}' in application '{app_id}': {type(entry).__name__}" - ) - for d in values: - if not isinstance(d, str) or not d.strip(): - raise AnsibleFilterError( - f"Invalid domain entry in '{key}' for application '{app_id}': {d!r}" - ) - return values - + """ + Maps applications to their canonical domains, checking for conflicts + and ensuring all domains are valid and unique across applications. + """ result = {} - seen = {} + seen_domains = {} for app_id, cfg in apps.items(): domains_cfg = cfg.get('domains') if not domains_cfg or 'canonical' not in domains_cfg: - default = f"{app_id}.{primary_domain}" - if default in seen: - raise AnsibleFilterError( - f"Domain '{default}' is already configured for '{seen[default]}' and '{app_id}'" - ) - seen[default] = app_id - result[app_id] = [default] + self._add_default_domain(app_id, primary_domain, seen_domains, result) continue - entry = domains_cfg['canonical'] - - if isinstance(entry, dict): - for name, domain in entry.items(): - if not isinstance(domain, str) or not domain.strip(): - raise AnsibleFilterError( - f"Invalid domain entry in 'canonical' for application '{app_id}': {domain!r}" - ) - if domain in seen: - raise AnsibleFilterError( - f"Domain '{domain}' is already configured for '{seen[domain]}' and '{app_id}'" - ) - seen[domain] = app_id - result[app_id] = entry.copy() - - elif isinstance(entry, list): - for domain in entry: - if not isinstance(domain, str) or not domain.strip(): - raise AnsibleFilterError( - f"Invalid domain entry in 'canonical' for application '{app_id}': {domain!r}" - ) - if domain in seen: - raise AnsibleFilterError( - f"Domain '{domain}' is already configured for '{seen[domain]}' and '{app_id}'" - ) - seen[domain] = app_id - result[app_id] = list(entry) - - else: - raise AnsibleFilterError( - f"Unexpected type for 'domains.canonical' in application '{app_id}': {type(entry).__name__}" - ) + canonical_domains = domains_cfg['canonical'] + self._process_canonical_domains(app_id, canonical_domains, seen_domains, result) return result + + def _add_default_domain(self, app_id, primary_domain, seen_domains, result): + """ + Add the default domain for an application if no canonical domains are defined. + Ensures the domain is unique across applications. + """ + default_domain = f"{app_id}.{primary_domain}" + if default_domain in seen_domains: + raise AnsibleFilterError( + f"Domain '{default_domain}' is already configured for " + f"'{seen_domains[default_domain]}' and '{app_id}'" + ) + seen_domains[default_domain] = app_id + result[app_id] = [default_domain] + + def _process_canonical_domains(self, app_id, canonical_domains, seen_domains, result): + """ + Process the canonical domains for an application, handling both lists and dicts, + and ensuring each domain is unique. + """ + if isinstance(canonical_domains, dict): + self._process_canonical_domains_dict(app_id, canonical_domains, seen_domains, result) + elif isinstance(canonical_domains, list): + self._process_canonical_domains_list(app_id, canonical_domains, seen_domains, result) + else: + raise AnsibleFilterError( + f"Unexpected type for 'domains.canonical' in application '{app_id}': " + f"{type(canonical_domains).__name__}" + ) + + def _process_canonical_domains_dict(self, app_id, domains_dict, seen_domains, result): + """ + Process a dictionary of canonical domains for an application. + """ + for name, domain in domains_dict.items(): + self._validate_and_check_domain(app_id, domain, seen_domains) + result[app_id] = domains_dict.copy() + + def _process_canonical_domains_list(self, app_id, domains_list, seen_domains, result): + """ + Process a list of canonical domains for an application. + """ + for domain in domains_list: + self._validate_and_check_domain(app_id, domain, seen_domains) + result[app_id] = list(domains_list) + + def _validate_and_check_domain(self, app_id, domain, seen_domains): + """ + Validate the domain and check if it has already been assigned to another application. + """ + if not isinstance(domain, str) or not domain.strip(): + raise AnsibleFilterError( + f"Invalid domain entry in 'canonical' for application '{app_id}': {domain!r}" + ) + if domain in seen_domains: + raise AnsibleFilterError( + f"Domain '{domain}' is already configured for '{seen_domains[domain]}' and '{app_id}'" + ) + seen_domains[domain] = app_id diff --git a/group_vars/all/15_about.yml b/group_vars/all/15_about.yml index 2c2dc3df..ef7ad12f 100644 --- a/group_vars/all/15_about.yml +++ b/group_vars/all/15_about.yml @@ -30,10 +30,4 @@ defaults_service_provider: legal: editorial_responsible: "Johannes Gutenberg" source_code: "https://github.com/kevinveenbirkenbach/cymais" - imprint: >- - {{ "{protocol}://{domain}/imprint.html" - | safe_placeholders({ - 'protocol': web_protocol, - 'domain': domains.html_server - }) - }} + imprint: "{{web_protocol}}://{{domains['html-server']}}/imprint.html" \ No newline at end of file diff --git a/roles/dns-records-cloudflare/tasks/main.yml b/roles/dns-records-cloudflare/tasks/main.yml index 464b989f..93a9a66a 100644 --- a/roles/dns-records-cloudflare/tasks/main.yml +++ b/roles/dns-records-cloudflare/tasks/main.yml @@ -1,8 +1,3 @@ -- name: "Debug: cloudflare_domains" - debug: - var: cloudflare_domains - when: enable_debug - - name: Create or update Cloudflare A-record for {{ item }} community.general.cloudflare_dns: api_token: "{{ cloudflare_api_token }}" diff --git a/roles/docker-bigbluebutton/templates/env.j2 b/roles/docker-bigbluebutton/templates/env.j2 index f25154fc..91f0345b 100644 --- a/roles/docker-bigbluebutton/templates/env.j2 +++ b/roles/docker-bigbluebutton/templates/env.j2 @@ -283,7 +283,7 @@ HELP_URL=https://docs.bigbluebutton.org/greenlight/gl-overview.html # approval - For approve/decline registration DEFAULT_REGISTRATION=invite -{% if applications[application_id].features.oidc | bool %} +{% if applications | is_feature_enabled('oidc',application_id) %} ### EXTERNAL AUTHENTICATION METHODS # @See https://docs.bigbluebutton.org/greenlight/v3/external-authentication/ # diff --git a/roles/docker-discourse/templates/discourse_application.yml.j2 b/roles/docker-discourse/templates/discourse_application.yml.j2 index aee7fdec..825c99e8 100644 --- a/roles/docker-discourse/templates/discourse_application.yml.j2 +++ b/roles/docker-discourse/templates/discourse_application.yml.j2 @@ -118,7 +118,7 @@ run: ## If you want to set the 'From' email address for your first registration, uncomment and change: ## After getting the first signup email, re-comment the line. It only needs to run once. #- exec: rails r "SiteSetting.notification_email='info@unconfigured.discourse.org'" -{% if applications[application_id].features.oidc | bool %} +{% if applications | is_feature_enabled('oidc',application_id) %} # Deactivate Default Login - exec: rails r "SiteSetting.enable_local_logins = false" - exec: rails r "SiteSetting.enable_passkeys = false" # https://meta.discourse.org/t/passwordless-login-using-passkeys/285589 diff --git a/roles/docker-espocrm/templates/env.j2 b/roles/docker-espocrm/templates/env.j2 index a1662624..a43aa74d 100644 --- a/roles/docker-espocrm/templates/env.j2 +++ b/roles/docker-espocrm/templates/env.j2 @@ -77,7 +77,7 @@ ESPOCRM_CONFIG_LDAP_USER_LOGIN_FILTER=(sAMAccountName=%USERNAME%) # OpenID Connect settings (optional) # Applied only if the feature flag is true # ------------------------------------------------ -{% if applications[application_id].features.oidc | bool %} +{% if applications | is_feature_enabled('oidc',application_id) %} # ------------------------------------------------ # OpenID Connect settings diff --git a/roles/docker-listmonk/vars/main.yml b/roles/docker-listmonk/vars/main.yml index 40893978..a41ba31c 100644 --- a/roles/docker-listmonk/vars/main.yml +++ b/roles/docker-listmonk/vars/main.yml @@ -17,7 +17,7 @@ listmonk_settings: "provider_url": oidc.client.issuer_url, "client_secret": oidc.client.secret } | to_json }} - when: applications[application_id].features.oidc | bool + when: applications | is_feature_enabled('oidc',application_id) # hCaptcha toggles and credentials - key: "security.enable_captcha" diff --git a/roles/docker-mailu/templates/env.j2 b/roles/docker-mailu/templates/env.j2 index ac956f3d..9a7d71b6 100644 --- a/roles/docker-mailu/templates/env.j2 +++ b/roles/docker-mailu/templates/env.j2 @@ -158,7 +158,7 @@ API_TOKEN={{applications.mailu.credentials.api_token}} AUTH_REQUIRE_TOKENS=True -{% if applications[application_id].features.oidc | bool %} +{% if applications | is_feature_enabled('oidc',application_id) %} ################################### # OpenID Connect settings ################################### diff --git a/roles/docker-mailu/vars/main.yml b/roles/docker-mailu/vars/main.yml index 52f8a81b..630377b0 100644 --- a/roles/docker-mailu/vars/main.yml +++ b/roles/docker-mailu/vars/main.yml @@ -8,7 +8,7 @@ cert_mount_directory: "{{docker_compose.directories.volumes}}certs/" # Use dedicated source for oidc if activated # @see https://github.com/heviat/Mailu-OIDC/tree/2024.06 -docker_source: "{{ 'ghcr.io/heviat' if applications[application_id].features.oidc | bool else 'ghcr.io/mailu' }}" +docker_source: "{{ 'ghcr.io/heviat' if applications | is_feature_enabled('oidc',application_id) else 'ghcr.io/mailu' }}" domain: "{{ domains | get_domain(application_id) }}" http_port: "{{ ports.localhost.http[application_id] }}" \ No newline at end of file diff --git a/roles/docker-mastodon/templates/env.j2 b/roles/docker-mastodon/templates/env.j2 index c02ee72d..093773db 100644 --- a/roles/docker-mastodon/templates/env.j2 +++ b/roles/docker-mastodon/templates/env.j2 @@ -52,7 +52,7 @@ SMTP_OPENSSL_VERIFY_MODE=none SMTP_ENABLE_STARTTLS=auto SMTP_FROM_ADDRESS=Mastodon <{{ users['no-reply'].email }}> -{% if applications[application_id].features.oidc | bool %} +{% if applications | is_feature_enabled('oidc',application_id) %} ################################### # OpenID Connect settings ################################### diff --git a/roles/docker-nextcloud/vars/configuration.yml b/roles/docker-nextcloud/vars/configuration.yml index 83dc5f47..f3477c44 100644 --- a/roles/docker-nextcloud/vars/configuration.yml +++ b/roles/docker-nextcloud/vars/configuration.yml @@ -20,8 +20,6 @@ oidc: # @see https://apps.nextcloud.com/apps/sociallogin flavor: "oidc_login" # Keeping on sociallogin because the other option is not implemented yet credentials: -# database_password: Null # Needs to be set in inventory file -# administrator_password: None # Keep in mind to change the password fast after creation and activate 2FA features: matomo: true css: true diff --git a/roles/docker-oauth2-proxy/templates/oauth2-proxy-keycloak.cfg.j2 b/roles/docker-oauth2-proxy/templates/oauth2-proxy-keycloak.cfg.j2 index 5533ca82..1e23d193 100644 --- a/roles/docker-oauth2-proxy/templates/oauth2-proxy-keycloak.cfg.j2 +++ b/roles/docker-oauth2-proxy/templates/oauth2-proxy-keycloak.cfg.j2 @@ -1,15 +1,15 @@ http_address = "0.0.0.0:4180" cookie_secret = "{{ applications[oauth2_proxy_application_id].credentials.oauth2_proxy_cookie_secret }}" email_domains = "{{ primary_domain }}" -cookie_secure = "true" # True is necessary to force the cookie set via https +cookie_secure = "true" # True is necessary to force the cookie set via https upstreams = "http://{{ applications[oauth2_proxy_application_id].oauth2_proxy.application }}:{{ applications[oauth2_proxy_application_id].oauth2_proxy.port }}" -cookie_domains = ["{{ domains[oauth2_proxy_application_id] }}", "{{ domains | get_domain('keycloak') }}"] # Required so cookie can be read on all subdomains. -whitelist_domains = [".{{ primary_domain }}"] # Required to allow redirection back to original requested target. +cookie_domains = ["{{ domains | get_domain(oauth2_proxy_application_id) }}", "{{ domains | get_domain('keycloak') }}"] # Required so cookie can be read on all subdomains. +whitelist_domains = [".{{ primary_domain }}"] # Required to allow redirection back to original requested target. # keycloak provider client_secret = "{{ oidc.client.secret }}" client_id = "{{ oidc.client.id }}" -redirect_url = "{{ web_protocol }}://{{domains[oauth2_proxy_application_id]}}/oauth2/callback" +redirect_url = "{{ web_protocol }}://{{ domains | get_domain(oauth2_proxy_application_id) }}/oauth2/callback" oidc_issuer_url = "{{ oidc.client.issuer_url }}" provider = "oidc" provider_display_name = "Keycloak" diff --git a/roles/docker-openproject/vars/configuration.yml b/roles/docker-openproject/vars/configuration.yml index 06f7d03d..34c63c6a 100644 --- a/roles/docker-openproject/vars/configuration.yml +++ b/roles/docker-openproject/vars/configuration.yml @@ -17,6 +17,8 @@ csp: flags: script-src: unsafe-inline: true + style-src: + unsafe-inline: true domains: canonical: - "project.{{ primary_domain }}" \ No newline at end of file diff --git a/roles/docker-pgadmin/vars/main.yml b/roles/docker-pgadmin/vars/main.yml index 527b9421..b376a11a 100644 --- a/roles/docker-pgadmin/vars/main.yml +++ b/roles/docker-pgadmin/vars/main.yml @@ -1,5 +1,5 @@ application_id: "pgadmin" database_type: "postgres" -database_host: "{{ 'central-' + database_type if applications | is_feature_enabled('central_database',application_id)" +database_host: "{{ 'central-' + database_type if applications | is_feature_enabled('central_database',application_id) }}" pgadmin_user: 5050 pgadmin_group: "{{pgadmin_user}}" \ No newline at end of file diff --git a/roles/docker-phpmyadmin/vars/main.yml b/roles/docker-phpmyadmin/vars/main.yml index e044e087..60836a8e 100644 --- a/roles/docker-phpmyadmin/vars/main.yml +++ b/roles/docker-phpmyadmin/vars/main.yml @@ -1,3 +1,3 @@ application_id: "phpmyadmin" database_type: "mariadb" -database_host: "{{ 'central-' + database_type if applications | is_feature_enabled('central_database',application_id)" \ No newline at end of file +database_host: "{{ 'central-' + database_type if applications | is_feature_enabled('central_database',application_id) }}" \ No newline at end of file diff --git a/roles/health-nginx/templates/health-nginx.py.j2 b/roles/health-nginx/templates/health-nginx.py.j2 index 96fc2a35..3ce85300 100644 --- a/roles/health-nginx/templates/health-nginx.py.j2 +++ b/roles/health-nginx/templates/health-nginx.py.j2 @@ -47,7 +47,7 @@ for filename in os.listdir(config_path): # Prepare the URL and expected status codes url = f"{{ web_protocol }}://{domain}" - redirected_domains = [domain['source'] for domain in {{current_play_redirect_domain_mappings}}] + redirected_domains = [domain['source'] for domain in {{ current_play_domain_mappings_redirect}}] {%- if domains.mailu | safe_var | bool %} redirected_domains.append("{{domains | get_domain('mailu')}}") {%- endif %} diff --git a/roles/nginx-serve-html/vars/main.yml b/roles/nginx-serve-html/vars/main.yml index d039ff0d..891758c8 100644 --- a/roles/nginx-serve-html/vars/main.yml +++ b/roles/nginx-serve-html/vars/main.yml @@ -1,2 +1,2 @@ -application_id: "html_server" +application_id: "html-server" domain: "{{domains | get_domain(application_id)}}" \ No newline at end of file diff --git a/tasks/constructor.yml b/tasks/constructor.yml index 248a9cf5..900675ad 100644 --- a/tasks/constructor.yml +++ b/tasks/constructor.yml @@ -9,14 +9,17 @@ set_fact: system_email: "{{ default_system_email | combine(system_email | default({}, true), recursive=True) }}" + - name: Merge application definitions + set_fact: + applications: "{{ defaults_applications | combine(applications | default({}, true), recursive=True) }}" + - name: Merge current play applications set_fact: current_play_applications: >- {{ - defaults_applications | - combine(applications | default({}, true), recursive=True) | + applications | applications_if_group_and_deps(group_names) - }} + }} - name: Merge current play domain definitions set_fact: @@ -26,28 +29,29 @@ combine(domains | default({}, true), recursive=True) }} - - name: Set current play all domains incl. www redirect if enabled - set_fact: - current_play_domains_all: >- - {{ - current_play_domains | - generate_all_domains( - ('www_redirect' in group_names) - ) - }} - - name: Set current play redirect domain mappings set_fact: - current_play_redirect_domain_mappings: >- + current_play_domain_mappings_redirect: >- {{ current_play_applications | domain_mappings(primary_domain) | merge_mapping(redirect_domain_mappings, 'source') }} - - name: Merge application definitions + - name: Set current play all domains incl. www redirect if enabled set_fact: - applications: "{{ defaults_applications | combine(applications | default({}, true), recursive=True) }}" + current_play_domains_all: >- + {{ + (current_play_domains | + combine( + current_play_domain_mappings_redirect | + items2dict(key_name='target', value_name='source'), + recursive=True + )) | + generate_all_domains( + ('www_redirect' in group_names) + ) + }} - name: Merge domain definitions for all domains set_fact: diff --git a/tasks/server.yml b/tasks/server.yml index f410ec93..a2c042cb 100644 --- a/tasks/server.yml +++ b/tasks/server.yml @@ -32,7 +32,7 @@ include_role: name: nginx-redirect-domains vars: - domain_mappings: "{{current_play_redirect_domain_mappings}}" + domain_mappings: "{{ current_play_domain_mappings_redirect}}" - name: setup www redirect when: ("www_redirect" in group_names) diff --git a/tests/unit/test_load_configuration.py b/tests/unit/test_load_configuration.py index b1515d66..fcb9258c 100644 --- a/tests/unit/test_load_configuration.py +++ b/tests/unit/test_load_configuration.py @@ -15,9 +15,9 @@ class TestLoadConfigurationFilter(unittest.TestCase): def setUp(self): _cfg_cache.clear() self.f = FilterModule().filters()['load_configuration'] - self.app = 'html_server' + self.app = 'html-server' self.nested_cfg = { - 'html_server': { + 'html-server': { 'features': {'matomo': True}, 'domains': {'canonical': ['html.example.com']} } @@ -76,8 +76,8 @@ class TestLoadConfigurationFilter(unittest.TestCase): @patch('load_configuration.os.listdir', return_value=['r1']) @patch('load_configuration.os.path.isdir', return_value=True) @patch('load_configuration.os.path.exists', return_value=True) - @patch('load_configuration.open', mock_open(read_data="html_server: {}")) - @patch('load_configuration.yaml.safe_load', return_value={'html_server': {}}) + @patch('load_configuration.open', mock_open(read_data="html-server: {}")) + @patch('load_configuration.yaml.safe_load', return_value={'html-server': {}}) def test_key_not_found_after_load(self, *_): with self.assertRaises(AnsibleFilterError): self.f(self.app, 'does.not.exist')