Skip to content

Testcontainers fails to get container_host_ip when running in docker network #141

Closed

Description

@andredasilvapinto

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)

Activity

  1. dcc-sapienza commented on May 7, 2021

    @dcc-sapienza

    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.

  2. hungphamvn commented on May 25, 2023

    @hungphamvn

    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.

  3. added a commit that references this issue on Jul 5, 2023
    7bc6ec5
  4. added a commit that references this issue on Jul 6, 2023
    672b024
  5. dhutchison commented on Sep 21, 2023

    @dhutchison

    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.

  6. alexanderankin commented on Dec 12, 2023

    @alexanderankin
    Member

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

    from

    def get_container(self, container_id):
    containers = self.client.api.containers(filters={'id': container_id})
    if not containers:
    raise RuntimeError(f'could not get container with id {container_id}')
    return containers[0]

    and

    return port_mappings[0]["HostPort"]

    from

    def port(self, container_id, port):
    port_mappings = self.client.api.port(container_id, port)
    if not port_mappings:
    raise RuntimeError(f'port mapping for container {container_id} and port {port} is not '
    'available')
    return port_mappings[0]["HostPort"]

  7. ivanychev commented on Jan 22, 2024

    @ivanychev

    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()
  8. added a commit that references this issue on Mar 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions