1
0
Fork 0

Compare commits

..

4 commits

Author SHA1 Message Date
1302425d09
Merge branch 'fix/host-network-mode'
Some checks failed
Docker / Test (push) Has been cancelled
Docker / Tests-E2E-Docker (push) Has been cancelled
Docker / Tests-E2E-Kubernetes (push) Has been cancelled
Docker / Tests-E2E-Additional (push) Has been cancelled
Docker / Tests-E2E-Swarm (push) Has been cancelled
Docker / Build (push) Has been cancelled
Docker / Publish-PyPI (push) Has been cancelled
Docker / Helm (push) Has been cancelled
Docker / HelmDeploy (push) Has been cancelled
Docker / Documentation (push) Has been cancelled
2026-09-02 13:22:14 -07:00
53104905b9
Merge branch 'fix/label-scoped-attach' 2026-09-02 13:19:23 -07:00
e7a0f7b930
Inspect only the containers carrying the lookup label 2026-09-01 16:37:19 -07:00
463e2023c8
Support containers sharing a network namespace (issue #47) 2026-09-01 16:37:07 -07:00
8 changed files with 450 additions and 16 deletions

View file

@ -8,9 +8,7 @@ sidebar_label: "Limitations"
## EasyHAProxy will not work with --network=host ## EasyHAProxy will not work with --network=host
:::danger Network Mode Incompatibility :::danger Network Mode Incompatibility
The `--network=host` option **cannot** be used with EasyHAProxy due to its networking requirements. The `--network=host` option **cannot** be used with the EasyHAProxy container itself due to its networking requirements.
EasyHAProxy needs to inspect and interact with Docker containers from within the Docker network where it's running. Using the `--network=host` option bypasses Docker networking, preventing EasyHAProxy from accessing and configuring containers effectively.
::: :::
## Considerations for Multiple Replica Deployments in EasyHAProxy ## Considerations for Multiple Replica Deployments in EasyHAProxy

View file

@ -9,7 +9,7 @@ EasyHAProxy inspects running Docker containers, reads their labels, and configur
:::warning Limitations :::warning Limitations
- You cannot mix Docker containers with Swarm containers. - You cannot mix Docker containers with Swarm containers.
- This method does not work with containers that use the `--network=host` option. See [limitations](../concepts/limitations.md) for details. - EasyHAProxy itself cannot run with the `--network=host` option. See [limitations](../concepts/limitations.md) for details.
::: :::
## Step 1 — Create a shared network ## Step 1 — Create a shared network
@ -49,6 +49,32 @@ docker run -d \
EasyHAProxy detects this container automatically and routes traffic from `example.org:80` to port 8080 in your container. You do not need to expose any container ports. EasyHAProxy detects this container automatically and routes traffic from `example.org:80` to port 8080 in your container. You do not need to expose any container ports.
### Containers using `--network=host`
Containers that share the host network namespace (`--network=host`, or `network_mode: host` in
Compose) cannot join the EasyHAProxy network, so EasyHAProxy reaches them through the gateway of
its own network instead. Label them as usual, but set `localport` to the port the service binds
**on the host**:
```yaml
services:
myapp:
image: my/image:tag
network_mode: host
labels:
easyhaproxy.myapp.host: example.org
easyhaproxy.myapp.port: 80
easyhaproxy.myapp.localport: 8080 # the port on the host
```
The same applies to containers sharing another container's namespace
(`network_mode: "container:xxx"`, as used by VPN sidecars); those are reached at the address of
the container owning the namespace.
The service must bind `0.0.0.0` rather than `127.0.0.1`, otherwise it is unreachable from the
EasyHAProxy container. If your host firewall blocks the Docker bridge, or the detected gateway is
not the address you want, override it with `EASYHAPROXY_HOST_NETWORK_IP`.
## Step 4 — Verify ## Step 4 — Verify
Open `http://example.org` in your browser (or `curl http://example.org`). Traffic should reach your container. Open `http://example.org` in your browser (or `curl http://example.org`). Traffic should reach your container.

View file

@ -9,6 +9,7 @@ sidebar_label: "Environment Variables"
|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------| |---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------|
| EASYHAPROXY_DISCOVER | How the services will be discovered to create `haproxy.cfg`: `static`, `docker`, `swarm` or `kubernetes` | **required** | | EASYHAPROXY_DISCOVER | How the services will be discovered to create `haproxy.cfg`: `static`, `docker`, `swarm` or `kubernetes` | **required** |
| EASYHAPROXY_LABEL_PREFIX | (Optional) The key will search for matching resources. | `easyhaproxy` | | EASYHAPROXY_LABEL_PREFIX | (Optional) The key will search for matching resources. | `easyhaproxy` |
| EASYHAPROXY_HOST_NETWORK_IP | (Optional) Address used to reach containers running with `--network=host`. Defaults to the gateway of the EasyHAProxy network. | *auto* |
| EASYHAPROXY_BASE_PATH | (Optional) Base directory for all EasyHAProxy files. All paths (config, certs, plugins, www) are constructed relative to this base. | `/etc/easyhaproxy` | | EASYHAPROXY_BASE_PATH | (Optional) Base directory for all EasyHAProxy files. All paths (config, certs, plugins, www) are constructed relative to this base. | `/etc/easyhaproxy` |
| EASYHAPROXY_CERTBOT_* | (Optional) Enable Let's Encrypt or any other ACME certificate. See more: [acme](../guides/acme.md) | *empty* | | EASYHAPROXY_CERTBOT_* | (Optional) Enable Let's Encrypt or any other ACME certificate. See more: [acme](../guides/acme.md) | *empty* |
| EASYHAPROXY_SSL_MODE | (Optional) `strict` supports only the most recent TLS version; `default` good SSL integration with recent browsers; `loose` supports all old SSL protocols for old browsers (not recommended). | `default` | | EASYHAPROXY_SSL_MODE | (Optional) `strict` supports only the most recent TLS version; `default` good SSL integration with recent browsers; `loose` supports all old SSL protocols for old browsers (not recommended). | `default` |

View file

@ -38,6 +38,8 @@ class ContainerEnv:
env_vars["lookup_label"] = os.getenv("EASYHAPROXY_LABEL_PREFIX") if os.getenv( env_vars["lookup_label"] = os.getenv("EASYHAPROXY_LABEL_PREFIX") if os.getenv(
"EASYHAPROXY_LABEL_PREFIX") else "easyhaproxy" "EASYHAPROXY_LABEL_PREFIX") else "easyhaproxy"
env_vars["host_network_ip"] = os.getenv("EASYHAPROXY_HOST_NETWORK_IP", "")
env_vars["logLevel"] = { env_vars["logLevel"] = {
"easyhaproxy": os.getenv("EASYHAPROXY_LOG_LEVEL") if os.getenv( "easyhaproxy": os.getenv("EASYHAPROXY_LOG_LEVEL") if os.getenv(
"EASYHAPROXY_LOG_LEVEL") else Functions.DEBUG, "EASYHAPROXY_LOG_LEVEL") else Functions.DEBUG,

View file

@ -1,7 +1,10 @@
import ipaddress
import socket import socket
import docker import docker
from functions import ContainerEnv, logger_easyhaproxy
from .interface import ProcessorInterface from .interface import ProcessorInterface
@ -12,24 +15,145 @@ class Docker(ProcessorInterface):
super().__init__() super().__init__()
def inspect_network(self): def inspect_network(self):
self.parsed_object = {}
own_container = None
try: try:
ha_proxy_network_name = next( own_container = self.client.containers.get(socket.gethostname())
iter(self.client.containers.get(socket.gethostname()).attrs["NetworkSettings"]["Networks"])) ha_proxy_network_name = next(iter(own_container.attrs["NetworkSettings"]["Networks"]))
except Exception: except Exception:
# HAProxy is not running in a container, get first container network # HAProxy is not running in a container, get first container network
if len(self.client.containers.list()) == 0: own_container = None
ha_proxy_network_name = self._first_attachable_network()
if ha_proxy_network_name is None:
return return
ha_proxy_network_name = next(iter(
self.client.containers.get(self.client.containers.list()[0].name).attrs["NetworkSettings"]["Networks"]))
ha_proxy_network = self.client.networks.get(ha_proxy_network_name) ha_proxy_network = self.client.networks.get(ha_proxy_network_name)
host_address = self._host_gateway_address(own_container, ha_proxy_network, ha_proxy_network_name)
self.parsed_object = {}
for container in self.client.containers.list(): for container in self.client.containers.list():
# Issue 32 - Docker container cannot connect to containers in different network. if not any(self.label in key for key in container.labels):
if ha_proxy_network_name not in container.attrs["NetworkSettings"]["Networks"].keys(): continue
ha_proxy_network.connect(container.name)
container = self.client.containers.get(container.name) # refresh object
ip_address = container.attrs["NetworkSettings"]["Networks"][ha_proxy_network_name]["IPAddress"] ip_address = self._container_address(container, ha_proxy_network, ha_proxy_network_name, host_address)
self.parsed_object[ip_address] = container.labels if ip_address is None:
continue
self._merge_labels(ip_address, container)
def _first_attachable_network(self):
# Containers sharing a network namespace have no network of their own to borrow.
for container in self.client.containers.list():
if self._network_mode(container) in ["host", "none"] or self._namespace_owner_id(container) is not None:
continue
networks = container.attrs["NetworkSettings"]["Networks"] or {}
if networks:
return next(iter(networks))
return None
@staticmethod
def _network_mode(container):
return (container.attrs.get("HostConfig") or {}).get("NetworkMode", "")
@staticmethod
def _namespace_owner_id(container):
network_mode = Docker._network_mode(container)
if network_mode.startswith("container:"):
return network_mode.split(":", 1)[1]
return None
def _container_address(self, container, ha_proxy_network, ha_proxy_network_name, host_address, seen=None):
"""
Resolve the address HAProxy must use to reach this container.
Returns None when the container cannot be reached.
"""
# Issue 47 - Containers sharing the host network namespace cannot join any other network.
if self._network_mode(container) == "host":
if host_address is None:
logger_easyhaproxy.warning(
f"Container '{container.name}' uses the host network, but the address of the host could not be "
f"determined. Set EASYHAPROXY_HOST_NETWORK_IP to fix it. Skipping it."
)
return host_address
owner_id = self._namespace_owner_id(container)
if owner_id is not None:
return self._namespace_owner_address(
container, owner_id, ha_proxy_network, ha_proxy_network_name, host_address, seen
)
networks = container.attrs["NetworkSettings"]["Networks"] or {}
# Issue 32 - Docker container cannot connect to containers in different network.
if ha_proxy_network_name not in networks.keys():
try:
ha_proxy_network.connect(container.name)
except docker.errors.APIError as e:
logger_easyhaproxy.warning(
f"Container '{container.name}' could not be connected to the network "
f"'{ha_proxy_network_name}': {e}. Skipping it."
)
return None
container = self.client.containers.get(container.name) # refresh object
networks = container.attrs["NetworkSettings"]["Networks"] or {}
return (networks.get(ha_proxy_network_name) or {}).get("IPAddress") or None
def _namespace_owner_address(self, container, owner_id, ha_proxy_network, ha_proxy_network_name, host_address,
seen=None):
"""
Containers started with `network_mode: container:xxx` are reachable at the address of the
container owning the network namespace.
"""
seen = seen or set()
if container.id in seen:
logger_easyhaproxy.warning(
f"Container '{container.name}' has a circular network namespace reference. Skipping it."
)
return None
seen.add(container.id)
try:
owner = self.client.containers.get(owner_id)
except docker.errors.NotFound:
logger_easyhaproxy.warning(
f"Container '{container.name}' shares the network namespace of '{owner_id}', which was not found. "
f"Skipping it."
)
return None
return self._container_address(owner, ha_proxy_network, ha_proxy_network_name, host_address, seen)
def _host_gateway_address(self, own_container, ha_proxy_network, ha_proxy_network_name):
"""
Address the host is reachable at from inside the HAProxy network. Every container sharing the
host network namespace is served through it.
"""
host_network_ip = ContainerEnv.read()["host_network_ip"]
if host_network_ip:
return host_network_ip
if own_container is not None:
gateway = (own_container.attrs["NetworkSettings"]["Networks"].get(ha_proxy_network_name) or {}).get(
"Gateway")
if gateway:
return gateway
for config in (ha_proxy_network.attrs.get("IPAM") or {}).get("Config") or []:
if config.get("Gateway"):
return config["Gateway"]
try:
return str(next(ipaddress.ip_network(config["Subnet"]).hosts()))
except (KeyError, ValueError, StopIteration):
continue
return None
def _merge_labels(self, ip_address, container):
# Every container sharing the host network namespace resolves to the same address, so the
# labels are merged instead of replaced.
labels = self.parsed_object.setdefault(ip_address, {})
for key, value in container.labels.items():
if key.startswith(self.label) and key in labels and labels[key] != value:
logger_easyhaproxy.warning(
f"Container '{container.name}' redefines '{key}' for '{ip_address}' as '{value}' instead of "
f"'{labels[key]}'. Give each container a unique definition name."
)
labels.update(container.labels)

View file

@ -23,6 +23,7 @@ _CONTAINER_ENV_VARS = [
"HAPROXY_CUSTOMERRORS", "HAPROXY_CUSTOMERRORS",
"EASYHAPROXY_SSL_MODE", "EASYHAPROXY_SSL_MODE",
"EASYHAPROXY_LABEL_PREFIX", "EASYHAPROXY_LABEL_PREFIX",
"EASYHAPROXY_HOST_NETWORK_IP",
"EASYHAPROXY_LOG_LEVEL", "EASYHAPROXY_LOG_LEVEL",
"HAPROXY_LOG_LEVEL", "HAPROXY_LOG_LEVEL",
"CERTBOT_LOG_LEVEL", "CERTBOT_LOG_LEVEL",

View file

@ -8,6 +8,7 @@ def test_container_env_empty():
"customerrors": False, "customerrors": False,
"ssl_mode": "default", "ssl_mode": "default",
"lookup_label": "easyhaproxy", "lookup_label": "easyhaproxy",
"host_network_ip": "",
"logLevel": { "logLevel": {
"easyhaproxy": Functions.DEBUG, "easyhaproxy": Functions.DEBUG,
"haproxy": Functions.INFO, "haproxy": Functions.INFO,
@ -42,6 +43,7 @@ def test_container_env_customerrors():
"customerrors": True, "customerrors": True,
"ssl_mode": "default", "ssl_mode": "default",
"lookup_label": "easyhaproxy", "lookup_label": "easyhaproxy",
"host_network_ip": "",
"logLevel": { "logLevel": {
"easyhaproxy": Functions.DEBUG, "easyhaproxy": Functions.DEBUG,
"haproxy": Functions.INFO, "haproxy": Functions.INFO,
@ -76,6 +78,7 @@ def test_container_env_sslmode():
"customerrors": False, "customerrors": False,
"ssl_mode": "strict", "ssl_mode": "strict",
"lookup_label": "easyhaproxy", "lookup_label": "easyhaproxy",
"host_network_ip": "",
"logLevel": { "logLevel": {
"easyhaproxy": Functions.DEBUG, "easyhaproxy": Functions.DEBUG,
"haproxy": Functions.INFO, "haproxy": Functions.INFO,
@ -111,6 +114,7 @@ def test_container_env_stats():
"customerrors": False, "customerrors": False,
"ssl_mode": "default", "ssl_mode": "default",
"lookup_label": "easyhaproxy", "lookup_label": "easyhaproxy",
"host_network_ip": "",
"logLevel": { "logLevel": {
"easyhaproxy": Functions.DEBUG, "easyhaproxy": Functions.DEBUG,
"haproxy": Functions.INFO, "haproxy": Functions.INFO,
@ -146,6 +150,7 @@ def test_container_env_stats_password():
"customerrors": False, "customerrors": False,
"ssl_mode": "default", "ssl_mode": "default",
"lookup_label": "easyhaproxy", "lookup_label": "easyhaproxy",
"host_network_ip": "",
"stats": { "stats": {
"username": "admin", "username": "admin",
"password": "xyz", "password": "xyz",
@ -188,6 +193,7 @@ def test_container_env_stats_password_2():
"customerrors": False, "customerrors": False,
"ssl_mode": "default", "ssl_mode": "default",
"lookup_label": "easyhaproxy", "lookup_label": "easyhaproxy",
"host_network_ip": "",
"stats": { "stats": {
"username": "abc", "username": "abc",
"password": "xyz", "password": "xyz",
@ -230,6 +236,7 @@ def test_container_env_certbot_email():
"customerrors": False, "customerrors": False,
"ssl_mode": "default", "ssl_mode": "default",
"lookup_label": "easyhaproxy", "lookup_label": "easyhaproxy",
"host_network_ip": "",
"logLevel": { "logLevel": {
"easyhaproxy": Functions.DEBUG, "easyhaproxy": Functions.DEBUG,
"haproxy": Functions.INFO, "haproxy": Functions.INFO,
@ -272,6 +279,7 @@ def test_container_env_certbot_full():
"customerrors": False, "customerrors": False,
"ssl_mode": "default", "ssl_mode": "default",
"lookup_label": "easyhaproxy", "lookup_label": "easyhaproxy",
"host_network_ip": "",
"logLevel": { "logLevel": {
"easyhaproxy": Functions.DEBUG, "easyhaproxy": Functions.DEBUG,
"haproxy": Functions.INFO, "haproxy": Functions.INFO,
@ -316,6 +324,7 @@ def test_container_log_level():
"customerrors": False, "customerrors": False,
"ssl_mode": "default", "ssl_mode": "default",
"lookup_label": "easyhaproxy", "lookup_label": "easyhaproxy",
"host_network_ip": "",
"logLevel": { "logLevel": {
"easyhaproxy": Functions.ERROR, "easyhaproxy": Functions.ERROR,
"haproxy": Functions.FATAL, "haproxy": Functions.FATAL,

View file

@ -105,4 +105,277 @@ def test_processor_docker():
container2.stop() container2.stop()
def _skip_unless_docker_is_idle():
try:
client = docker.from_env()
except docker.errors.DockerException:
pytest.skip("There is no docker environment")
if len(client.containers.list()) > 0:
pytest.skip("I cannot run this test with other containers running.")
return client
def _bridge_gateway(client):
return client.networks.get("bridge").attrs["IPAM"]["Config"][0]["Gateway"]
def test_processor_docker_host_network():
client = _skip_unless_docker_is_idle()
container = client.containers.run("byjg/static-httpserver",
name="test_processor_docker_host",
detach=True,
auto_remove=True,
remove=True,
network_mode="host",
labels={
"easyhaproxy.hostmode.port": "80",
"easyhaproxy.hostmode.localport": "8080",
"easyhaproxy.hostmode.host": "hostmode.local",
})
container2 = client.containers.run("byjg/static-httpserver",
name="test_processor_docker_bridge",
detach=True,
auto_remove=True,
remove=True,
labels={
"easyhaproxy.bridged.port": "80",
"easyhaproxy.bridged.localport": "8080",
"easyhaproxy.bridged.host": "bridged.local",
})
try:
time.sleep(1)
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
assert {
'easyhaproxy.hostmode.host': 'hostmode.local',
'easyhaproxy.hostmode.localport': '8080',
'easyhaproxy.hostmode.port': '80',
} == _get_hydrated_object(static.get_parsed_object(), "easyhaproxy.hostmode")
# The container shares the host network namespace, so it is served through the gateway.
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.hostmode") == _bridge_gateway(client)
# The container on the bridge network keeps being served through its own address.
bridged_ip = client.containers.get(container2.name).attrs["NetworkSettings"]["Networks"]["bridge"]["IPAddress"]
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.bridged") == bridged_ip
static.get_haproxy_conf()
assert static.get_hosts() == [
'bridged.local:80',
'hostmode.local:80'
]
finally:
container.stop()
container2.stop()
def test_processor_docker_host_network_merges_labels():
client = _skip_unless_docker_is_idle()
container = client.containers.run("byjg/static-httpserver",
name="test_processor_docker_host1",
detach=True,
auto_remove=True,
remove=True,
network_mode="host",
labels={
"easyhaproxy.first.port": "80",
"easyhaproxy.first.localport": "8080",
"easyhaproxy.first.host": "first.local",
})
container2 = client.containers.run("byjg/static-httpserver",
name="test_processor_docker_host2",
detach=True,
auto_remove=True,
remove=True,
network_mode="host",
environment={"PORT": "9000", "TLS_PORT": "9443"},
labels={
"easyhaproxy.second.port": "80",
"easyhaproxy.second.localport": "9000",
"easyhaproxy.second.host": "second.local",
})
container3 = client.containers.run("byjg/static-httpserver",
name="test_processor_docker_bridge_merge",
detach=True,
auto_remove=True,
remove=True,
labels={
"easyhaproxy.bridged.port": "80",
"easyhaproxy.bridged.localport": "8080",
"easyhaproxy.bridged.host": "bridged.local",
})
try:
time.sleep(1)
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
gateway = _bridge_gateway(client)
# Both containers resolve to the same address, so their labels are merged, not replaced.
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.first") == gateway
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.second") == gateway
merged = {key: value for key, value in static.get_parsed_object()[gateway].items()
if key.startswith("easyhaproxy.")}
assert {
'easyhaproxy.first.host': 'first.local',
'easyhaproxy.first.localport': '8080',
'easyhaproxy.first.port': '80',
'easyhaproxy.second.host': 'second.local',
'easyhaproxy.second.localport': '9000',
'easyhaproxy.second.port': '80',
} == merged
haproxy_cfg = static.get_haproxy_conf()
assert f"server srv-0 {gateway}:8080" in haproxy_cfg
assert f"server srv-0 {gateway}:9000" in haproxy_cfg
finally:
container.stop()
container2.stop()
container3.stop()
def test_processor_docker_host_network_ip_override():
client = _skip_unless_docker_is_idle()
container = client.containers.run("byjg/static-httpserver",
name="test_processor_docker_host_override",
detach=True,
auto_remove=True,
remove=True,
network_mode="host",
labels={
"easyhaproxy.hostmode.port": "80",
"easyhaproxy.hostmode.localport": "8080",
"easyhaproxy.hostmode.host": "hostmode.local",
})
container2 = client.containers.run("byjg/static-httpserver",
name="test_processor_docker_bridge_override",
detach=True,
auto_remove=True,
remove=True,
labels={
"easyhaproxy.bridged.port": "80",
"easyhaproxy.bridged.localport": "8080",
"easyhaproxy.bridged.host": "bridged.local",
})
try:
time.sleep(1)
os.environ['EASYHAPROXY_HOST_NETWORK_IP'] = '10.20.30.40'
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.hostmode") == '10.20.30.40'
finally:
del os.environ['EASYHAPROXY_HOST_NETWORK_IP']
container.stop()
container2.stop()
def test_processor_docker_shared_namespace():
client = _skip_unless_docker_is_idle()
owner = client.containers.run("byjg/static-httpserver",
name="test_processor_docker_owner",
detach=True,
auto_remove=True,
remove=True)
time.sleep(1)
container = client.containers.run("byjg/static-httpserver",
name="test_processor_docker_sidecar",
detach=True,
auto_remove=True,
remove=True,
environment={"PORT": "9000", "TLS_PORT": "9443"},
network_mode="container:test_processor_docker_owner",
labels={
"easyhaproxy.sidecar.port": "80",
"easyhaproxy.sidecar.localport": "9000",
"easyhaproxy.sidecar.host": "sidecar.local",
})
try:
time.sleep(1)
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
# The sidecar is reachable at the address of the container owning the network namespace.
owner_ip = client.containers.get(owner.name).attrs["NetworkSettings"]["Networks"]["bridge"]["IPAddress"]
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.sidecar") == owner_ip
finally:
container.stop()
owner.stop()
def test_processor_docker_host_network_only():
client = _skip_unless_docker_is_idle()
container = client.containers.run("byjg/static-httpserver",
name="test_processor_docker_host_only",
detach=True,
auto_remove=True,
remove=True,
network_mode="host",
labels={
"easyhaproxy.hostmode.port": "80",
"easyhaproxy.hostmode.localport": "8080",
"easyhaproxy.hostmode.host": "hostmode.local",
})
try:
time.sleep(1)
# There is no network to borrow, but the discovery must not fail.
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
assert static.get_parsed_object() == {}
assert static.get_haproxy_conf() != ""
finally:
container.stop()
def test_processor_docker_ignores_unlabeled():
client = _skip_unless_docker_is_idle()
network = client.networks.create("test_processor_docker_network", driver="bridge")
# The most recent container is inspected first, so the labeled one defines the network to use.
unlabeled = client.containers.run("byjg/static-httpserver",
name="test_processor_docker_unlabeled",
detach=True,
auto_remove=True,
remove=True)
time.sleep(1)
container = client.containers.run("byjg/static-httpserver",
name="test_processor_docker_labeled",
detach=True,
auto_remove=True,
remove=True,
network=network.name,
labels={
"easyhaproxy.labeled.port": "80",
"easyhaproxy.labeled.localport": "8080",
"easyhaproxy.labeled.host": "labeled.local",
})
try:
time.sleep(1)
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
# The labeled container is served through the network it already belongs to.
labeled_ip = client.containers.get(container.name).attrs["NetworkSettings"]["Networks"][network.name][
"IPAddress"]
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.labeled") == labeled_ip
# The container without any label is neither inspected nor connected to the network.
unlabeled_networks = client.containers.get(unlabeled.name).attrs["NetworkSettings"]["Networks"]
assert list(unlabeled_networks.keys()) == ["bridge"]
assert unlabeled_networks["bridge"]["IPAddress"] not in static.get_parsed_object()
finally:
container.stop()
unlabeled.stop()
time.sleep(1)
network.remove()
# test_processor_docker() # test_processor_docker()