filter/domain_redirect_mappings: add auto_build_alias parameter

- Extend filter signature with auto_build_alias flag to control automatic
  default→canonical alias creation
- group_vars/all: introduce AUTO_BUILD_ALIASES variable for global toggle
- Update unit tests: adjust calls to new signature and add dedicated
  test cases for auto_build_aliases=False

Ref: conversation https://chatgpt.com/share/68cd512c-c878-800f-bdf2-81737adf7e0e
This commit is contained in:
2025-09-19 14:49:02 +02:00
parent c6677ca61b
commit 0f85d27a4d
3 changed files with 87 additions and 16 deletions

View File

@@ -7,7 +7,7 @@ class FilterModule(object):
def filters(self): def filters(self):
return {'domain_mappings': self.domain_mappings} return {'domain_mappings': self.domain_mappings}
def domain_mappings(self, apps, PRIMARY_DOMAIN): def domain_mappings(self, apps, primary_domain, auto_build_alias):
""" """
Build a flat list of redirect mappings for all apps: Build a flat list of redirect mappings for all apps:
- source: each alias domain - source: each alias domain
@@ -43,7 +43,7 @@ class FilterModule(object):
domains_cfg = cfg.get('server',{}).get('domains',{}) domains_cfg = cfg.get('server',{}).get('domains',{})
entry = domains_cfg.get('canonical') entry = domains_cfg.get('canonical')
if entry is None: if entry is None:
canonical_map[app_id] = [default_domain(app_id, PRIMARY_DOMAIN)] canonical_map[app_id] = [default_domain(app_id, primary_domain)]
elif isinstance(entry, dict): elif isinstance(entry, dict):
canonical_map[app_id] = list(entry.values()) canonical_map[app_id] = list(entry.values())
elif isinstance(entry, list): elif isinstance(entry, list):
@@ -61,11 +61,11 @@ class FilterModule(object):
alias_map[app_id] = [] alias_map[app_id] = []
continue continue
if isinstance(domains_cfg, dict) and not domains_cfg: if isinstance(domains_cfg, dict) and not domains_cfg:
alias_map[app_id] = [default_domain(app_id, PRIMARY_DOMAIN)] alias_map[app_id] = [default_domain(app_id, primary_domain)]
continue continue
aliases = parse_entry(domains_cfg, 'aliases', app_id) or [] aliases = parse_entry(domains_cfg, 'aliases', app_id) or []
default = default_domain(app_id, PRIMARY_DOMAIN) default = default_domain(app_id, primary_domain)
has_aliases = 'aliases' in domains_cfg has_aliases = 'aliases' in domains_cfg
has_canonical = 'canonical' in domains_cfg has_canonical = 'canonical' in domains_cfg
@@ -74,7 +74,7 @@ class FilterModule(object):
aliases.append(default) aliases.append(default)
elif has_canonical: elif has_canonical:
canon = canonical_map.get(app_id, []) canon = canonical_map.get(app_id, [])
if default not in canon and default not in aliases: if default not in canon and default not in aliases and auto_build_alias:
aliases.append(default) aliases.append(default)
alias_map[app_id] = aliases alias_map[app_id] = aliases
@@ -84,7 +84,7 @@ class FilterModule(object):
mappings = [] mappings = []
for app_id, sources in alias_map.items(): for app_id, sources in alias_map.items():
canon_list = canonical_map.get(app_id, []) canon_list = canonical_map.get(app_id, [])
target = canon_list[0] if canon_list else default_domain(app_id, PRIMARY_DOMAIN) target = canon_list[0] if canon_list else default_domain(app_id, primary_domain)
for src in sources: for src in sources:
if src == target: if src == target:
# skip self-redirects # skip self-redirects

View File

@@ -32,6 +32,8 @@ WEBSOCKET_PROTOCOL: "{{ 'wss' if WEB_PROTOCOL == 'https' else 'ws' }}"
# WWW-Redirect to None WWW-Domains enabled # WWW-Redirect to None WWW-Domains enabled
WWW_REDIRECT_ENABLED: "{{ ('web-opt-rdr-www' in group_names) | bool }}" WWW_REDIRECT_ENABLED: "{{ ('web-opt-rdr-www' in group_names) | bool }}"
AUTO_BUILD_ALIASES: False # If enabled it creates an alias domain for each web application by the entity name, recommended to set to false to safge domain space
# Domain # Domain
PRIMARY_DOMAIN: "localhost" # Primary Domain of the server PRIMARY_DOMAIN: "localhost" # Primary Domain of the server

View File

@@ -18,20 +18,20 @@ class TestDomainMappings(unittest.TestCase):
def test_empty_apps(self): def test_empty_apps(self):
apps = {} apps = {}
result = self.filter.domain_mappings(apps, self.primary) result = self.filter.domain_mappings(apps, self.primary, True)
self.assertEqual(result, []) self.assertEqual(result, [])
def test_app_without_domains(self): def test_app_without_domains(self):
apps = {'web-app-desktop': {}} apps = {'web-app-desktop': {}}
# no domains key → no mappings # no domains key → no mappings
result = self.filter.domain_mappings(apps, self.primary) result = self.filter.domain_mappings(apps, self.primary, True)
self.assertEqual(result, []) self.assertEqual(result, [])
def test_empty_domains_cfg(self): def test_empty_domains_cfg(self):
apps = {'web-app-desktop': {'domains': {}}} apps = {'web-app-desktop': {'domains': {}}}
default = 'desktop.example.com' default = 'desktop.example.com'
expected = [] expected = []
result = self.filter.domain_mappings(apps, self.primary) result = self.filter.domain_mappings(apps, self.primary, True)
self.assertEqual(result, expected) self.assertEqual(result, expected)
def test_explicit_aliases(self): def test_explicit_aliases(self):
@@ -46,7 +46,7 @@ class TestDomainMappings(unittest.TestCase):
expected = [ expected = [
{'source': 'alias.com', 'target': default}, {'source': 'alias.com', 'target': default},
] ]
result = self.filter.domain_mappings(apps, self.primary) result = self.filter.domain_mappings(apps, self.primary, True)
# order not important # order not important
self.assertCountEqual(result, expected) self.assertCountEqual(result, expected)
@@ -61,7 +61,7 @@ class TestDomainMappings(unittest.TestCase):
expected = [ expected = [
{'source': 'desktop.example.com', 'target': 'foo.com'} {'source': 'desktop.example.com', 'target': 'foo.com'}
] ]
result = self.filter.domain_mappings(apps, self.primary) result = self.filter.domain_mappings(apps, self.primary, True)
self.assertEqual(result, expected) self.assertEqual(result, expected)
def test_canonical_dict(self): def test_canonical_dict(self):
@@ -78,7 +78,7 @@ class TestDomainMappings(unittest.TestCase):
expected = [ expected = [
{'source': 'desktop.example.com', 'target': 'one.com'} {'source': 'desktop.example.com', 'target': 'one.com'}
] ]
result = self.filter.domain_mappings(apps, self.primary) result = self.filter.domain_mappings(apps, self.primary, True)
self.assertEqual(result, expected) self.assertEqual(result, expected)
def test_multiple_apps(self): def test_multiple_apps(self):
@@ -94,7 +94,7 @@ class TestDomainMappings(unittest.TestCase):
{'source': 'a1.com', 'target': 'desktop.example.com'}, {'source': 'a1.com', 'target': 'desktop.example.com'},
{'source': 'mastodon.example.com', 'target': 'c2.com'}, {'source': 'mastodon.example.com', 'target': 'c2.com'},
] ]
result = self.filter.domain_mappings(apps, self.primary) result = self.filter.domain_mappings(apps, self.primary, True)
self.assertCountEqual(result, expected) self.assertCountEqual(result, expected)
def test_multiple_aliases(self): def test_multiple_aliases(self):
@@ -108,7 +108,7 @@ class TestDomainMappings(unittest.TestCase):
{'source': 'a1.com', 'target': 'desktop.example.com'}, {'source': 'a1.com', 'target': 'desktop.example.com'},
{'source': 'a2.com', 'target': 'desktop.example.com'} {'source': 'a2.com', 'target': 'desktop.example.com'}
] ]
result = self.filter.domain_mappings(apps, self.primary) result = self.filter.domain_mappings(apps, self.primary, True)
self.assertCountEqual(result, expected) self.assertCountEqual(result, expected)
def test_invalid_aliases_type(self): def test_invalid_aliases_type(self):
@@ -116,8 +116,77 @@ class TestDomainMappings(unittest.TestCase):
'web-app-desktop': {'server':{'domains': {'aliases': 123}}} 'web-app-desktop': {'server':{'domains': {'aliases': 123}}}
} }
with self.assertRaises(AnsibleFilterError): with self.assertRaises(AnsibleFilterError):
self.filter.domain_mappings(apps, self.primary) self.filter.domain_mappings(apps, self.primary, True)
def test_canonical_not_default_no_autobuild(self):
"""
When only a canonical different from the default exists and auto_build_aliases is False,
we should NOT auto-generate a default alias -> canonical mapping.
"""
apps = {
'web-app-desktop': {
'server': {
'domains': {'canonical': ['foo.com']}
}
}
}
result = self.filter.domain_mappings(apps, self.primary, False)
self.assertEqual(result, []) # no auto-added default alias
def test_aliases_and_canonical_no_autobuild_still_adds_default(self):
"""
If explicit aliases are present, the filter always appends the default domain
to the alias list (to cover 'www'/'root' style defaults), regardless of auto_build_aliases.
With a canonical set, both the explicit alias and the default should point to the canonical.
"""
apps = {
'web-app-desktop': {
'server': {
'domains': {
'aliases': ['alias.com'],
'canonical': ['foo.com']
}
}
}
}
expected = [
{'source': 'alias.com', 'target': 'foo.com'},
{'source': 'desktop.example.com', 'target': 'foo.com'},
]
result = self.filter.domain_mappings(apps, self.primary, False)
self.assertCountEqual(result, expected)
def test_mixed_apps_no_autobuild(self):
"""
One app with only canonical (no aliases) and one app with only aliases:
- The canonical-only app produces no mappings when auto_build_aliases is False.
- The alias-only app maps its aliases to its default domain; default self-mapping is skipped.
"""
apps = {
'web-app-desktop': {
'server': {'domains': {'canonical': ['c1.com']}}
},
'web-app-mastodon': {
'server': {'domains': {'aliases': ['m1.com']}}
},
}
expected = [
{'source': 'm1.com', 'target': 'mastodon.example.com'},
]
result = self.filter.domain_mappings(apps, self.primary, False)
self.assertCountEqual(result, expected)
def test_no_domains_key_no_autobuild(self):
"""
App ohne 'server.domains' erzeugt keine Mappings, unabhängig von auto_build_aliases.
"""
apps = {
'web-app-desktop': {
# no 'server' or 'domains'
}
}
result = self.filter.domain_mappings(apps, self.primary, False)
self.assertEqual(result, [])
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()