mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
Conflicts resolved by keeping both sides' intent: - call-test-deploy.yml: main's re-enabled deploy concurrency group, keyed on the network axis instead of the removed tor axis. - sys-svc-proxy: main's shared headers/forwarded.conf.j2 now carries the branch's per-vhost semantics (X-Forwarded-Host, TLS port and state read from the vhost domain), so html, ws, upload and the Prometheus probes all answer per network sibling. - plugins/lookup/email.py: main's provider-agnostic mail lookup asks the network registry instead of testing the .onion suffix, as the branch does everywhere; container_extra_hosts.py likewise. The provider host's NETWORK_MODE now decides whether it is served onion-first, since a Tor node defaults to multi and keeps clearnet canonical. - workspace common.sh: main's INFINITO_DOMAIN fallback without the duplicated no-op line. - README.md and requirement 035: both sides' rows (Wazuh plus main's Pi-hole and Stalwart entries); adapter_candidate count 39. Adapted to main where the branch's rules now reach main's code: - six tests from main read the primary domain from default.env instead of writing it out, as the hardcoded-domain lint requires; - the chutney worker-check test passes ONION_SUFFIX from the network registry, which the branch's svc-net-tor test script requires; - web-app-wazuh moves its local http ports to 8096/8097 (8094/8095 now belong to web-app-stalwart) and sizes the indexer heap through service_memory_mb, the same fixes #414 carries; - web-app-wazuh's README cosmos follows Stalwart as the mail provider. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
160 lines
5.5 KiB
Python
160 lines
5.5 KiB
Python
"""Unit tests for the smtp_host lookup plugin.
|
|
|
|
Pins the SMTP endpoint matrix msmtp relays through: the configured relay in
|
|
production, loopback on a host that carries the stack, and the swarm manager's
|
|
routing mesh on a bare infra node inside the containerised test rig.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import unittest
|
|
|
|
from ansible.errors import AnsibleError
|
|
|
|
from utils.domains.default_primary import default_domain_primary
|
|
|
|
from . import PROJECT_ROOT
|
|
|
|
DOMAIN = default_domain_primary()
|
|
RELAY = f"mail.{DOMAIN}"
|
|
MANAGER = "swarm-manager-01"
|
|
|
|
|
|
def _load_lookup():
|
|
spec = importlib.util.spec_from_file_location(
|
|
"lookup_smtp_host",
|
|
str(PROJECT_ROOT / "plugins/lookup/smtp_host.py"),
|
|
)
|
|
mod = importlib.util.module_from_spec(spec)
|
|
assert spec and spec.loader
|
|
spec.loader.exec_module(mod)
|
|
return mod.LookupModule
|
|
|
|
|
|
class _DummyTemplar:
|
|
def __init__(self, available_variables=None):
|
|
self.available_variables = available_variables or {}
|
|
|
|
def template(self, value):
|
|
return value
|
|
|
|
|
|
class _RenderingTemplar:
|
|
"""A templar that actually resolves expressions, like the real one.
|
|
|
|
``_DummyTemplar`` returns its input unchanged, so it cannot expose a value
|
|
that only misbehaves while still an unrendered template.
|
|
"""
|
|
|
|
def __init__(self, available_variables=None, rendered=None):
|
|
self.available_variables = available_variables or {}
|
|
self._rendered = rendered or {}
|
|
|
|
def template(self, value):
|
|
return self._rendered.get(value, value)
|
|
|
|
|
|
def _email(external=True, host=RELAY):
|
|
return {"external": external, "host": host}
|
|
|
|
|
|
class TestSmtpHostLookup(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.LookupModule = _load_lookup()
|
|
|
|
def _resolve(self, variables, email=None):
|
|
lm = self.LookupModule()
|
|
lm._templar = _DummyTemplar(variables)
|
|
lm._loader = None
|
|
return lm.run([email if email is not None else _email()], variables=variables)[
|
|
0
|
|
]
|
|
|
|
def test_production_keeps_the_configured_relay(self):
|
|
vars_ = {"DOCKER_IN_CONTAINER": False, "DEPLOYMENT_MODE": "swarm"}
|
|
self.assertEqual(self._resolve(vars_), RELAY)
|
|
|
|
def test_non_external_mail_keeps_the_configured_relay(self):
|
|
vars_ = {"DOCKER_IN_CONTAINER": True, "DEPLOYMENT_MODE": "swarm"}
|
|
email = _email(external=False, host="localhost")
|
|
self.assertEqual(self._resolve(vars_, email), "localhost")
|
|
|
|
def test_compose_rig_uses_loopback(self):
|
|
vars_ = {"DOCKER_IN_CONTAINER": True, "DEPLOYMENT_MODE": "compose"}
|
|
self.assertEqual(self._resolve(vars_), "127.0.0.1")
|
|
|
|
def test_an_unrendered_group_var_still_reaches_the_rig_loopback(self):
|
|
"""DOCKER_IN_CONTAINER is a group_vars expression, and a lookup receives it
|
|
raw. Read unrendered it is neither true nor false, so the rig kept the
|
|
configured relay instead of loopback — and on an onion node that relay is a
|
|
.onion, which msmtp cannot route (EX_UNAVAILABLE, 'proxy failure')."""
|
|
raw = "{{ (ansible_facts['env']['container'] | default('')) != '' }}"
|
|
vars_ = {"DOCKER_IN_CONTAINER": raw, "DEPLOYMENT_MODE": "compose"}
|
|
lm = self.LookupModule()
|
|
lm._templar = _RenderingTemplar(vars_, {raw: True})
|
|
lm._loader = None
|
|
self.assertEqual(lm.run([_email()], variables=vars_)[0], "127.0.0.1")
|
|
|
|
def test_swarm_node_uses_loopback(self):
|
|
vars_ = {
|
|
"DOCKER_IN_CONTAINER": True,
|
|
"DEPLOYMENT_MODE": "swarm",
|
|
"group_names": ["svc-swarm-node"],
|
|
}
|
|
self.assertEqual(self._resolve(vars_), "127.0.0.1")
|
|
|
|
def test_infra_node_enters_through_the_manager(self):
|
|
vars_ = {
|
|
"DOCKER_IN_CONTAINER": True,
|
|
"DEPLOYMENT_MODE": "swarm",
|
|
"group_names": ["svc-nfs-server"],
|
|
"groups": {"svc-swarm-manager": [MANAGER, "swarm-manager-02"]},
|
|
}
|
|
self.assertEqual(self._resolve(vars_), MANAGER)
|
|
|
|
def test_infra_node_without_manager_falls_back_to_the_relay(self):
|
|
vars_ = {
|
|
"DOCKER_IN_CONTAINER": True,
|
|
"DEPLOYMENT_MODE": "swarm",
|
|
"group_names": ["svc-nfs-server"],
|
|
"groups": {"svc-swarm-manager": []},
|
|
}
|
|
self.assertEqual(self._resolve(vars_), RELAY)
|
|
|
|
def test_string_booleans_are_coerced(self):
|
|
vars_ = {"DOCKER_IN_CONTAINER": "True", "DEPLOYMENT_MODE": "compose"}
|
|
self.assertEqual(self._resolve(vars_, _email(external="yes")), "127.0.0.1")
|
|
|
|
def test_swarm_node_wins_over_a_populated_manager_group(self):
|
|
vars_ = {
|
|
"DOCKER_IN_CONTAINER": True,
|
|
"DEPLOYMENT_MODE": "swarm",
|
|
"group_names": ["svc-swarm-node"],
|
|
"groups": {"svc-swarm-manager": [MANAGER]},
|
|
}
|
|
self.assertEqual(self._resolve(vars_), "127.0.0.1")
|
|
|
|
def test_missing_terms_raise(self):
|
|
lm = self.LookupModule()
|
|
lm._templar = _DummyTemplar({})
|
|
with self.assertRaises(AnsibleError):
|
|
lm.run([], variables={})
|
|
|
|
def test_too_many_terms_raise(self):
|
|
lm = self.LookupModule()
|
|
lm._templar = _DummyTemplar({})
|
|
with self.assertRaises(AnsibleError):
|
|
lm.run([_email(), _email()], variables={})
|
|
|
|
def test_non_mapping_term_raises(self):
|
|
lm = self.LookupModule()
|
|
lm._templar = _DummyTemplar({})
|
|
with self.assertRaises(AnsibleError):
|
|
lm.run([RELAY], variables={})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main(verbosity=2)
|