import os import time import docker import pytest from functions import Functions from processor import ProcessorInterface def _get_hydrated_object(parsed_objects, lookup_key): hydrated_object = {} for key in parsed_objects: for keys in parsed_objects[key]: if lookup_key in keys: hydrated_object[keys] = parsed_objects[key][keys] return hydrated_object def _get_ip_host(parsed_objects, lookup_key): for key in parsed_objects: for keys in parsed_objects[key]: if lookup_key in keys: return key def test_processor_docker(): 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.") container = client.containers.run("byjg/static-httpserver", name="test_processor_docker", detach=True, auto_remove=True, remove=True, labels={ "easyhaproxy.http.port": "80", "easyhaproxy.http.localport": "8080", "easyhaproxy.http.host": "host1.local", "easyhaproxy.http2.port": "90", "easyhaproxy.http2.localport": "9000", "easyhaproxy.http2.host": "host2.local", "easyhaproxy.http2.certbot": "true", }) container2 = client.containers.run("byjg/static-httpserver", name="test2_processor_docker", detach=True, auto_remove=True, remove=True, labels={ "easyhaproxy.ssl.port": "443", "easyhaproxy.ssl.localport": "8080", "easyhaproxy.ssl.host": "hostssl.local", "easyhaproxy.ssl.sslcert": "U29tZSBQRU0gQ2VydGlmaWNhdGU=" }) try: time.sleep(1) os.environ['EASYHAPROXY_CERTBOT_EMAIL'] = 'docker@example.org' static = ProcessorInterface.factory(ProcessorInterface.DOCKER) assert static.get_certbot_hosts() is None assert { 'easyhaproxy.http.host': 'host1.local', 'easyhaproxy.http.localport': '8080', 'easyhaproxy.http.port': '80', 'easyhaproxy.http2.host': 'host2.local', 'easyhaproxy.http2.localport': '9000', 'easyhaproxy.http2.port': '90', 'easyhaproxy.http2.certbot': 'true', } == _get_hydrated_object(static.get_parsed_object(), "easyhaproxy.http") assert { 'easyhaproxy.ssl.host': 'hostssl.local', 'easyhaproxy.ssl.localport': '8080', 'easyhaproxy.ssl.port': '443', 'easyhaproxy.ssl.sslcert': 'U29tZSBQRU0gQ2VydGlmaWNhdGU=' } == _get_hydrated_object(static.get_parsed_object(), "easyhaproxy.ssl.") assert static.get_hosts() is None assert static.get_certs() == {} haproxy_cfg = static.get_haproxy_conf() assert haproxy_cfg == Functions.load(os.path.join(os.path.dirname(os.path.realpath(__file__)), "./expected/docker.txt")).replace("test_processor_docker", _get_ip_host( static.get_parsed_object(), "easyhaproxy.http")).replace("test2_processor_docker", _get_ip_host(static.get_parsed_object(), "easyhaproxy.ssl")) assert static.get_certbot_hosts() == ['host2.local'] assert static.get_hosts() == [ 'hostssl.local:443', 'host1.local:80', 'host2.local:90' ] assert static.get_certs() == { 'hostssl.local.pem': 'Some PEM Certificate' } finally: del os.environ['EASYHAPROXY_CERTBOT_EMAIL'] container.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()