fix(container-dns): advertise the bridge resolver only where a listener exists

TOR_CONTAINER_DNS_HOST is empty in swarm mode by design, so no dnsmasq
listener appears on the docker bridge there. The lookup gated on the tor
group alone and wrote 172.17.0.1 into daemon.json anyway, handing every
nested container a resolver nothing answers on — nested image builds then
died resolving dl-cdn.alpinelinux.org.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-07-30 06:47:14 +02:00
1 parent 5dbbaca4a1
commit e308e18923
2 files changed
+23 -1

No files matched your search

+12 -1
View File
@@ -5,6 +5,7 @@ from typing import Any
from ansible.plugins.lookup import LookupBase
_TOR_ROLE = "svc-net-tor"
_BRIDGE_MODE = "compose"
def resolve_container_dns(variables: dict[str, Any]) -> list[str]:
@@ -16,18 +17,28 @@ def resolve_container_dns(variables: dict[str, Any]) -> list[str]:
bridge address because docker discards loopback resolvers, and it is
absent before docker itself is installed, hence the guard.
Only compose mode gets the bridge: TOR_CONTAINER_DNS_HOST creates the
matching dnsmasq listener there and nowhere else.
The clearnet resolver stays as a second entry so that losing dnsmasq costs
onion names rather than all name resolution.
"""
facts = variables.get("ansible_facts") or {}
bridge = ((facts.get("docker0") or {}).get("ipv4") or {}).get("address") or ""
on_tor_node = _TOR_ROLE in (variables.get("group_names") or [])
bridge_reachable = (
str(variables.get("DEPLOYMENT_MODE") or "").strip() == _BRIDGE_MODE
)
clearnet = ((variables.get("networks") or {}).get("internet") or {}).get(
"dns"
) or ""
return [str(r) for r in (bridge if on_tor_node else "", clearnet) if r]
return [
str(r)
for r in (bridge if (on_tor_node and bridge_reachable) else "", clearnet)
if r
]
class LookupModule(LookupBase):
@@ -22,6 +22,7 @@ def _vars(**overrides):
"ansible_facts": _BRIDGE,
"group_names": ["svc-net-tor"],
"networks": _CLEARNET,
"DEPLOYMENT_MODE": "compose",
}
variables.update(overrides)
return variables
@@ -37,6 +38,16 @@ class TestResolveContainerDns(unittest.TestCase):
def test_without_tor_only_clearnet(self):
self.assertEqual(resolve_container_dns(_vars(group_names=[])), ["172.30.0.53"])
def test_swarm_node_skips_the_bridge_resolver(self):
self.assertEqual(
resolve_container_dns(_vars(DEPLOYMENT_MODE="swarm")), ["172.30.0.53"]
)
def test_swarm_node_without_clearnet_yields_empty_list(self):
self.assertEqual(
resolve_container_dns(_vars(DEPLOYMENT_MODE="swarm", networks={})), []
)
def test_without_bridge_only_clearnet(self):
self.assertEqual(
resolve_container_dns(_vars(ansible_facts={})), ["172.30.0.53"]