1
0
mirror of https://github.com/thib8956/nginx-proxy synced 2024-11-25 05:16:30 +00:00

TESTS: add support for resolving containers IP address from domain names in the form *.<container name>.container.docker

This commit is contained in:
Thomas LEVEIL 2017-02-11 22:41:09 +01:00
parent 743d9801e9
commit a533ff6aff
2 changed files with 107 additions and 28 deletions

View File

@ -17,6 +17,10 @@ Prepare the nginx-proxy test image
docker build -t jwilder/nginx-proxy:test .. docker build -t jwilder/nginx-proxy:test ..
or if you want to test the alpine flavor:
docker build -t jwilder/nginx-proxy:test -f Dockerfile.alpine ..
make sure to tag that test image exactly `jwilder/nginx-proxy:test` or the test suite won't work. make sure to tag that test image exactly `jwilder/nginx-proxy:test` or the test suite won't work.
@ -44,17 +48,6 @@ This test suite uses [pytest](http://doc.pytest.org/en/latest/). The [conftest.p
- docker_compose - docker_compose
- nginxproxy - nginxproxy
Also _conftest.py_ alters the way the python interpreter resolves domain names to IP addresses in such a way that any domain name containing the substring `nginx-proxy` will resolve to the IP address of the container that was created from the `jwilder/nginx-proxy:test` image.
So all the following domain names will resolve to the nginx-proxy container in tests:
- `nginx-proxy`
- `nginx-proxy.com`
- `www.nginx-proxy.com`
- `www.nginx-proxy.test`
- `www.nginx-proxy`
- `whatever.nginx-proxyooooooo`
- ...
### docker_compose fixture ### docker_compose fixture
@ -68,6 +61,26 @@ The fixture will run the _docker-compose_ command with the `-f` option to load t
In the case you are running pytest from within a docker container, the `docker_compose` fixture will make sure the container running pytest is attached to all docker networks. That way, your test will be able to reach any of them. In the case you are running pytest from within a docker container, the `docker_compose` fixture will make sure the container running pytest is attached to all docker networks. That way, your test will be able to reach any of them.
In your tests, you can use the `docker_compose` variable to query and command the docker daemon as it provides you with a [client from the docker python module](https://docker-py.readthedocs.io/en/2.0.2/client.html#client-reference).
Also this fixture alters the way the python interpreter resolves domain names to IP addresses in the following ways:
Any domain name containing the substring `nginx-proxy` will resolve to the IP address of the container that was created from the `jwilder/nginx-proxy:test` image. So all the following domain names will resolve to the nginx-proxy container in tests:
- `nginx-proxy`
- `nginx-proxy.com`
- `www.nginx-proxy.com`
- `www.nginx-proxy.test`
- `www.nginx-proxy`
- `whatever.nginx-proxyooooooo`
- ...
Any domain name ending with `XXX.container.docker` will resolve to the IP address of the XXX container.
- `web1.container.docker` will resolve to the IP address of the `web1` container
- `f00.web1.container.docker` will resolve to the IP address of the `web1` container
- `anything.whatever.web2.container.docker` will resolve to the IP address of the `web2` container
Otherwise, domain names are resoved as usual using your system DNS resolver.
### nginxproxy fixture ### nginxproxy fixture

View File

@ -6,6 +6,7 @@ import shlex
import socket import socket
import subprocess import subprocess
import time import time
import re
import backoff import backoff
import docker import docker
@ -14,7 +15,7 @@ import requests
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logging.getLogger('backoff').setLevel(logging.INFO) logging.getLogger('backoff').setLevel(logging.INFO)
logging.getLogger('patched DNS').setLevel(logging.INFO) logging.getLogger('DNS').setLevel(logging.INFO)
logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(logging.WARN) logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(logging.WARN)
CA_ROOT_CERTIFICATE = os.path.join(os.path.dirname(__file__), 'certs/ca-root.crt') CA_ROOT_CERTIFICATE = os.path.join(os.path.dirname(__file__), 'certs/ca-root.crt')
@ -92,33 +93,90 @@ class requests_for_docker(object):
return getattr(requests, name) return getattr(requests, name)
def container_ip(container):
"""
return the IP address of a container
"""
net_info = container.attrs["NetworkSettings"]["Networks"]
if "bridge" in net_info:
return net_info["bridge"]["IPAddress"]
# not default bridge network, fallback on first network defined
network_name = net_info.keys()[0]
return net_info[network_name]["IPAddress"]
def nginx_proxy_dns_resolver(domain_name):
"""
if "nginx-proxy" if found in domain_name, return the ip address of the docker container
issued from the docker image jwilder/nginx-proxy:test.
:return: IP or None
"""
log = logging.getLogger('DNS')
log.debug("nginx_proxy_dns_resolver(%r)" % domain_name)
if 'nginx-proxy' in domain_name:
nginxproxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "jwilder/nginx-proxy:test"})
if len(nginxproxy_containers) == 0:
log.warn("no container found from image jwilder/nginx-proxy:test while resolving %r", domain_name)
return
nginxproxy_container = nginxproxy_containers[0]
ip = container_ip(nginxproxy_container)
log.info("resolving domain name %r as IP address %s of nginx-proxy container %s" % (domain_name, ip, nginxproxy_container.name))
return ip
def docker_container_dns_resolver(domain_name):
"""
if domain name is of the form "XXX.container.docker" or "anything.XXX.container.docker", return the ip address of the docker container
named XXX.
:return: IP or None
"""
log = logging.getLogger('DNS')
log.debug("docker_container_dns_resolver(%r)" % domain_name)
match = re.search('(^|.+\.)(?P<container>[^.]+)\.container\.docker$', domain_name)
if not match:
log.debug("%r does not match" % domain_name)
return
container_name = match.group('container')
log.debug("looking for container %r" % container_name)
try:
container = docker_client.containers.get(container_name)
except docker.errors.NotFound:
log.warn("container named %r not found while resolving %r" % (container_name, domain_name))
return
log.debug("container %r found (%s)" % (container.name, container.short_id))
ip = container_ip(container)
log.info("resolving domain name %r as IP address %s of container %s" % (domain_name, ip, container.name))
return ip
def monkey_patch_urllib_dns_resolver(): def monkey_patch_urllib_dns_resolver():
""" """
Alter the behavior of the urllib DNS resolver so that any domain name Alter the behavior of the urllib DNS resolver so that any domain name
containing substring 'nginx-proxy' will resolve to the IP address containing substring 'nginx-proxy' will resolve to the IP address
of the container created from image 'jwilder/nginx-proxy:test'. of the container created from image 'jwilder/nginx-proxy:test'.
""" """
log = logging.getLogger("patched DNS")
prv_getaddrinfo = socket.getaddrinfo prv_getaddrinfo = socket.getaddrinfo
dns_cache = {} dns_cache = {}
def new_getaddrinfo(*args): def new_getaddrinfo(*args):
log.debug("resolving domain name %s" % repr(args)) logging.getLogger('DNS').debug("resolving domain name %s" % repr(args))
if 'nginx-proxy' in args[0]:
nginxproxy_container = docker_client.containers.list(filters={"status": "running", "ancestor": "jwilder/nginx-proxy:test"})[0] # custom DNS resolvers
net_info = nginxproxy_container.attrs["NetworkSettings"]["Networks"] ip = nginx_proxy_dns_resolver(args[0])
if "bridge" in net_info: if ip is None:
ip = net_info["bridge"]["IPAddress"] ip = docker_container_dns_resolver(args[0])
else: if ip is not None:
# not default bridge network, fallback on first network defined
network_name = net_info.keys()[0]
ip = net_info[network_name]["IPAddress"]
log.info("resolving domain name %r as IP address is %s" % (args[0], ip))
return [ return [
(socket.AF_INET, socket.SOCK_STREAM, 6, '', (ip, args[1])), (socket.AF_INET, socket.SOCK_STREAM, 6, '', (ip, args[1])),
(socket.AF_INET, socket.SOCK_DGRAM, 17, '', (ip, args[1])), (socket.AF_INET, socket.SOCK_DGRAM, 17, '', (ip, args[1])),
(socket.AF_INET, socket.SOCK_RAW, 0, '', (ip, args[1])) (socket.AF_INET, socket.SOCK_RAW, 0, '', (ip, args[1]))
] ]
# fallback on original DNS resolver
try: try:
return dns_cache[args] return dns_cache[args]
except KeyError: except KeyError:
@ -179,7 +237,6 @@ def wait_for_nginxproxy_to_be_ready():
container = containers[0] container = containers[0]
for line in container.logs(stream=True): for line in container.logs(stream=True):
if "Watching docker events" in line: if "Watching docker events" in line:
time.sleep(1) # give time to docker-gen to produce the new nginx config and reload nginx
logging.debug("nginx-proxy ready") logging.debug("nginx-proxy ready")
break break
@ -223,7 +280,11 @@ def connect_to_network(network):
:return: the name of the network we were connected to, or None :return: the name of the network we were connected to, or None
""" """
if I_AM_RUNNING_INSIDE_A_DOCKER_CONTAINER: if I_AM_RUNNING_INSIDE_A_DOCKER_CONTAINER:
my_container = docker_client.containers.get(socket.gethostname()) try:
my_container = docker_client.containers.get(socket.gethostname())
except docker.errors.NotFound:
logging.warn("container %r not found" % socket.gethostname())
return
# figure out our container networks # figure out our container networks
my_networks = my_container.attrs["NetworkSettings"]["Networks"].keys() my_networks = my_container.attrs["NetworkSettings"]["Networks"].keys()
@ -242,7 +303,11 @@ def disconnect_from_network(network=None):
:param network: name of a docker network to disconnect from :param network: name of a docker network to disconnect from
""" """
if I_AM_RUNNING_INSIDE_A_DOCKER_CONTAINER and network is not None: if I_AM_RUNNING_INSIDE_A_DOCKER_CONTAINER and network is not None:
my_container = docker_client.containers.get(socket.gethostname()) try:
my_container = docker_client.containers.get(socket.gethostname())
except docker.errors.NotFound:
logging.warn("container %r not found" % socket.gethostname())
return
# figure out our container networks # figure out our container networks
my_networks_names = my_container.attrs["NetworkSettings"]["Networks"].keys() my_networks_names = my_container.attrs["NetworkSettings"]["Networks"].keys()
@ -289,7 +354,8 @@ def docker_compose(request):
docker_compose_up(docker_compose_file) docker_compose_up(docker_compose_file)
networks = connect_to_all_networks() networks = connect_to_all_networks()
wait_for_nginxproxy_to_be_ready() wait_for_nginxproxy_to_be_ready()
yield time.sleep(3) # give time to containers to be ready
yield docker_client
for network in networks: for network in networks:
disconnect_from_network(network) disconnect_from_network(network)
docker_compose_down(docker_compose_file) docker_compose_down(docker_compose_file)