testcontainers-python icon indicating copy to clipboard operation
testcontainers-python copied to clipboard

Testcontainers fails to get container_host_ip when running in docker network

Open andredasilvapinto opened this issue 3 years ago • 1 comments

I believe this is because this code assumes the presence of "bridge"

https://github.com/testcontainers/testcontainers-python/blob/master/testcontainers/core/docker_client.py#L52

https://github.com/testcontainers/testcontainers-python/blob/master/testcontainers/core/docker_client.py#L48

which is not the case when the container runs in a specific docker network (possible to do via DockerContainer.with_kwargs)

andredasilvapinto avatar Apr 29 '21 20:04 andredasilvapinto

Hi, I have the same issue when trying to run an integration test inside a docker container. As described in the README, when testcontainers-py is running inside a docker container a docker client is required and the sock must be mounted as volume (with -v /var/run/docker.sock:/var/run/docker.sock) Anyway, when the tests are running they fails because of:

def gateway_ip(self, container_id):
        container = self.client.api.containers(filters={'id': container_id})[0]
>       return container['NetworkSettings']['Networks']['bridge']['Gateway']
E       KeyError: 'bridge'

/root/.cache/pypoetry/virtualenvs/deros-FCD_2FXt-py3.8/lib/python3.8/site-packages/testcontainers/core/docker_client.py:52: KeyError

PS: the testcontainers java implementation work within docker container with the same configuration.

dcc-sapienza avatar May 07 '21 20:05 dcc-sapienza

Hello, I'm trying to run integration test inside .devcontainer (with docker-compose) and got the same error with @dlmiddlecote Any workaround or solution so far? Thanks.

hungphamvn avatar May 25 '23 02:05 hungphamvn

Sharing this test as an as the workaround I've taken to getting testcontainers working from within our devcontainer (noting we originally had DOCKER_HOST set too, which needed changed as it causes a code path to be ignored). Maybe it will help someone else, or point to what I'm doing wrong.

from testcontainers.postgres import PostgresContainer
import logging
import sqlalchemy
import os

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
handler.setLevel(logging.INFO)
logger.addHandler(handler)

def test_container_runs():

    postgres_container = PostgresContainer("postgres:14.6")

    # If we are running in a container, we need to set an environment variable to allow test
    # containers to work, this might only be required in the devcontainer - might need to 
    # validate that, for now we'll assume it is only in the dev container by looking for
    # our marker file
    # The first check is from how testcontainers determines if we are in a container, 
    # the second is our own check to a file that we know is contained in our devcontainer

    # Reference links:
    # https://github.com/testcontainers/testcontainers-python/blob/main/core/testcontainers/core/utils.py#L47
    # https://github.com/docker/docker/blob/a9fa38b1edf30b23cae3eade0be48b3d4b1de14b/daemon/initlayer/setup_unix.go#L25
    if os.path.exists('/.dockerenv') and os.path.exists('/installation-scripts/versions.properties'):
        logger.info("In DevContainer workaround...")
        gateway_ip = postgres_container.get_docker_client().gateway_ip(os.environ["HOSTNAME"])
        logger.info("Setting TC_HOST to gateway ip: " + gateway_ip)
        os.environ["TC_HOST"] = gateway_ip

    with postgres_container as postgres:
        engine = sqlalchemy.create_engine(postgres.get_connection_url())
        with engine.begin() as connection:
            result = connection.execute(sqlalchemy.text("select version()"))
            version, = result.fetchone()

            logger.info("Version is " + version)

I'm not quite sure if there is a bug or if I'm doing something wrong (our devcontainer should be setup as DIND, but is actually docker-from-docker which causes all manner of fun issues).

The key point in this is we have some code that detects if it is running in our devcontainer, and if so sets the TC_HOST environment variable to the gateway IP for the docker network that the devcontainer is running in. With this set testcontainers will correctly determine that the postgres container has started.

dhutchison avatar Sep 21 '23 14:09 dhutchison

the original links in the issue were from these two methods respectively

https://github.com/testcontainers/testcontainers-python/blob/8119ccc1ceb240314bdd6c4ad896271abcbd66c9/testcontainers/core/docker_client.py#L52

from

https://github.com/testcontainers/testcontainers-python/blob/8119ccc1ceb240314bdd6c4ad896271abcbd66c9/testcontainers/core/docker_client.py#L50-L54

and

https://github.com/testcontainers/testcontainers-python/blob/8119ccc1ceb240314bdd6c4ad896271abcbd66c9/testcontainers/core/docker_client.py#L48

from

https://github.com/testcontainers/testcontainers-python/blob/8119ccc1ceb240314bdd6c4ad896271abcbd66c9/testcontainers/core/docker_client.py#L43-L48

alexanderankin avatar Dec 12 '23 04:12 alexanderankin

Faced the same issue in the tests. The working workaround for me is to patch the current implementation of this method (pytest example)

def gateway_ip(self, container_id):
    """Default DockerClient.gateway_ip doesn't know how to work with Docker networks, so
    this is the workaround.
    
    Fix as soon as the https://github.com/testcontainers/testcontainers-python/issues/141 is fixed.
    """
    container = self.get_container(container_id)
    networks = container['NetworkSettings']['Networks']
    if 'bridge' in networks:
        return container['NetworkSettings']['Networks']['bridge']['Gateway']
    if len(networks) == 1:
        return list(networks.values())[0]['Gateway']
    raise ValueError(container['NetworkSettings']['Networks'])


@pytest.fixture()
def container_network(monkeypatch) -> Iterable[Network]:
    monkeypatch.setattr(
        DockerClient,
        'gateway_ip',
        gateway_ip,
    )

    client = docker.from_env()
    network_name = f'test_{uuid.uuid4().hex}'
    network = client.networks.create(network_name)
    yield network
    network.remove()

ivanychev avatar Jan 22 '24 14:01 ivanychev