Ver Fonte

feat: pre bundled dockergen w/ nginx-proxy template image

Nicolas Duchon há 5 meses atrás
pai
commit
a67aff92e9

+ 12 - 9
.github/workflows/build-publish.yml

@@ -23,7 +23,7 @@ jobs:
     name: Build and publish image
     name: Build and publish image
     strategy:
     strategy:
       matrix:
       matrix:
-        base: [alpine, debian]
+        flavor: [alpine, debian, dockergen]
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
     steps:
       - name: Checkout
       - name: Checkout
@@ -37,7 +37,7 @@ jobs:
 
 
       - name: Retrieve docker-gen version
       - name: Retrieve docker-gen version
         id: docker-gen_version
         id: docker-gen_version
-        run: sed -n -e 's;^FROM nginxproxy/docker-gen:\([0-9.]*\).*;VERSION=\1;p' Dockerfile.${{ matrix.base }} >> "$GITHUB_OUTPUT"
+        run: sed -n -e 's;^FROM docker.io/nginxproxy/docker-gen:\([0-9.]*\).*;VERSION=\1;p' Dockerfile.${{ matrix.flavor }} >> "$GITHUB_OUTPUT"
 
 
       - name: Get Docker tags
       - name: Get Docker tags
         id: docker_meta
         id: docker_meta
@@ -48,12 +48,15 @@ jobs:
             nginxproxy/nginx-proxy
             nginxproxy/nginx-proxy
             jwilder/nginx-proxy
             jwilder/nginx-proxy
           tags: |
           tags: |
-            type=semver,pattern={{version}},enable=${{ matrix.base == 'debian' }}
-            type=semver,pattern={{major}}.{{minor}},enable=${{ matrix.base == 'debian' }}
-            type=semver,suffix=-alpine,pattern={{version}},enable=${{ matrix.base == 'alpine' }}
-            type=semver,suffix=-alpine,pattern={{major}}.{{minor}},enable=${{ matrix.base == 'alpine' }}
-            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' && matrix.base == 'debian' }}
-            type=raw,value=alpine,enable=${{ github.ref == 'refs/heads/main' && matrix.base == 'alpine' }}
+            type=semver,pattern={{version}},enable=${{ matrix.flavor == 'debian' }}
+            type=semver,pattern={{major}}.{{minor}},enable=${{ matrix.flavor == 'debian' }}
+            type=semver,suffix=-alpine,pattern={{version}},enable=${{ matrix.flavor == 'alpine' }}
+            type=semver,suffix=-alpine,pattern={{major}}.{{minor}},enable=${{ matrix.flavor == 'alpine' }}
+            type=semver,suffix=-dockergen,pattern={{version}},enable=${{ matrix.flavor == 'dockergen' }}
+            type=semver,suffix=-dockergen,pattern={{major}}.{{minor}},enable=${{ matrix.flavor == 'dockergen' }}
+            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' && matrix.flavor == 'debian' }}
+            type=raw,value=alpine,enable=${{ github.ref == 'refs/heads/main' && matrix.flavor == 'alpine' }}
+            type=raw,value=dockergen,enable=${{ github.ref == 'refs/heads/main' && matrix.flavor == 'dockergen' }}
           labels: |
           labels: |
             org.opencontainers.image.authors=Nicolas Duchon <nicolas.duchon@gmail.com> (@buchdag), Jason Wilder
             org.opencontainers.image.authors=Nicolas Duchon <nicolas.duchon@gmail.com> (@buchdag), Jason Wilder
             org.opencontainers.image.version=${{ steps.nginx-proxy_version.outputs.VERSION }}
             org.opencontainers.image.version=${{ steps.nginx-proxy_version.outputs.VERSION }}
@@ -84,7 +87,7 @@ jobs:
         uses: docker/build-push-action@v6
         uses: docker/build-push-action@v6
         with:
         with:
           context: .
           context: .
-          file: Dockerfile.${{ matrix.base }}
+          file: Dockerfile.${{ matrix.flavor }}
           build-args: |
           build-args: |
             NGINX_PROXY_VERSION=${{ steps.nginx-proxy_version.outputs.VERSION }}
             NGINX_PROXY_VERSION=${{ steps.nginx-proxy_version.outputs.VERSION }}
             DOCKER_GEN_VERSION=${{ steps.docker-gen_version.outputs.VERSION }}
             DOCKER_GEN_VERSION=${{ steps.docker-gen_version.outputs.VERSION }}

+ 4 - 2
.github/workflows/test.yml

@@ -20,7 +20,7 @@ jobs:
 
 
     strategy:
     strategy:
       matrix:
       matrix:
-        base_docker_image: [alpine, debian]
+        flavor: [alpine, debian, dockergen]
 
 
     steps:
     steps:
       - uses: actions/checkout@v4
       - uses: actions/checkout@v4
@@ -43,8 +43,10 @@ jobs:
         run: make build-webserver
         run: make build-webserver
 
 
       - name: Build Docker nginx proxy test image
       - name: Build Docker nginx proxy test image
-        run: make build-nginx-proxy-test-${{ matrix.base_docker_image }}
+        run: make build-nginx-proxy-test-${{ matrix.flavor }}
 
 
       - name: Run tests
       - name: Run tests
         run: pytest
         run: pytest
         working-directory: test
         working-directory: test
+        env:
+          COMPOSE_PROFILES: ${{ matrix.flavor == 'dockergen' && 'separateContainers' || 'singleContainer' }}

+ 18 - 0
Dockerfile.dockergen

@@ -0,0 +1,18 @@
+FROM docker.io/nginxproxy/docker-gen:0.14.4
+
+ARG NGINX_PROXY_VERSION
+ENV NGINX_PROXY_VERSION=${NGINX_PROXY_VERSION} \
+   DOCKER_HOST=unix:///tmp/docker.sock \
+   DHPARAM_SKIP=true \
+   NGINX_CONTAINER_NAME=nginx-proxy
+
+# Install dependencies
+RUN apk add --no-cache --virtual .run-deps bash
+
+RUN mkdir -p '/etc/nginx/conf.d'
+
+COPY app nginx.tmpl LICENSE /app/
+WORKDIR /app/
+
+ENTRYPOINT ["/app/docker-entrypoint.sh"]
+CMD ["docker-gen", "-notify-sighup", "$NGINX_CONTAINER_NAME", "-watch", "/app/nginx.tmpl", "/etc/nginx/conf.d/default.conf"]

+ 10 - 1
Makefile

@@ -11,10 +11,19 @@ build-nginx-proxy-test-debian:
 build-nginx-proxy-test-alpine:
 build-nginx-proxy-test-alpine:
 	docker build --pull --build-arg NGINX_PROXY_VERSION="test" -f Dockerfile.alpine -t nginxproxy/nginx-proxy:test .
 	docker build --pull --build-arg NGINX_PROXY_VERSION="test" -f Dockerfile.alpine -t nginxproxy/nginx-proxy:test .
 
 
+build-nginx-proxy-test-dockergen:
+	docker build --pull --build-arg NGINX_PROXY_VERSION="test" -f Dockerfile.dockergen -t nginxproxy/nginx-proxy:test-dockergen .
+
+test-debian: export COMPOSE_PROFILES = singleContainer
 test-debian: build-webserver build-nginx-proxy-test-debian
 test-debian: build-webserver build-nginx-proxy-test-debian
 	test/pytest.sh
 	test/pytest.sh
 
 
+test-alpine: export COMPOSE_PROFILES = singleContainer
 test-alpine: build-webserver build-nginx-proxy-test-alpine
 test-alpine: build-webserver build-nginx-proxy-test-alpine
 	test/pytest.sh
 	test/pytest.sh
 
 
-test: test-debian test-alpine
+test-dockergen: export COMPOSE_PROFILES = separateContainers
+test-dockergen: build-webserver build-nginx-proxy-test-docker-gen
+	test/pytest.sh
+
+test: test-debian test-alpine test-dockergen

+ 6 - 1
app/docker-entrypoint.sh

@@ -101,7 +101,7 @@ function _setup_dhparam() {
 }
 }
 
 
 # Run the init logic if the default CMD was provided
 # Run the init logic if the default CMD was provided
-if [[ $* == 'forego start -r' ]]; then
+if [[ $* == "forego start -r" ]] || [[ $* =~ "docker-gen -notify-sighup" ]]; then
 	_print_version
 	_print_version
 	
 	
 	_check_unix_socket
 	_check_unix_socket
@@ -116,6 +116,11 @@ if [[ $* == 'forego start -r' ]]; then
 			Warning: The default value of TRUST_DOWNSTREAM_PROXY might change to "false" in a future version of nginx-proxy. If you require TRUST_DOWNSTREAM_PROXY to be enabled, explicitly set it to "true".
 			Warning: The default value of TRUST_DOWNSTREAM_PROXY might change to "false" in a future version of nginx-proxy. If you require TRUST_DOWNSTREAM_PROXY to be enabled, explicitly set it to "true".
 		EOT
 		EOT
 	fi
 	fi
+
+	if [[ $3 == "\$NGINX_CONTAINER_NAME" && -n "$NGINX_CONTAINER_NAME" ]]; then
+		# change the value of $3 to the expanded $NGINX_CONTAINER_NAME variable
+		set -- "${@:1:2}" "$NGINX_CONTAINER_NAME" "${@:4}"
+	fi
 fi
 fi
 
 
 exec "$@"
 exec "$@"

+ 52 - 18
test/conftest.py

@@ -27,6 +27,9 @@ PYTEST_RUNNING_IN_CONTAINER = os.environ.get('PYTEST_RUNNING_IN_CONTAINER') == "
 FORCE_CONTAINER_IPV6 = False  # ugly global state to consider containers' IPv6 address instead of IPv4
 FORCE_CONTAINER_IPV6 = False  # ugly global state to consider containers' IPv6 address instead of IPv4
 
 
 DOCKER_COMPOSE = os.environ.get('DOCKER_COMPOSE', 'docker compose')
 DOCKER_COMPOSE = os.environ.get('DOCKER_COMPOSE', 'docker compose')
+COMPOSE_PROFILES = os.environ.get('COMPOSE_PROFILES', 'singleContainer')
+IMAGE_TAG = "test-dockergen" if COMPOSE_PROFILES == "separateContainers" else "test"
+
 
 
 docker_client = docker.from_env()
 docker_client = docker.from_env()
 
 
@@ -75,11 +78,11 @@ class requests_for_docker(object):
         """
         """
         Return list of containers
         Return list of containers
         """
         """
-        nginx_proxy_containers = docker_client.containers.list(filters={"ancestor": "nginxproxy/nginx-proxy:test"})
+        nginx_proxy_containers = docker_client.containers.list(filters={"ancestor": f"nginxproxy/nginx-proxy:{IMAGE_TAG}"})
         if len(nginx_proxy_containers) > 1:
         if len(nginx_proxy_containers) > 1:
-            pytest.fail("Too many running nginxproxy/nginx-proxy:test containers", pytrace=False)
+            pytest.fail(f"Too many running nginxproxy/nginx-proxy:{IMAGE_TAG} containers", pytrace=False)
         elif len(nginx_proxy_containers) == 0:
         elif len(nginx_proxy_containers) == 0:
-            pytest.fail("No running nginxproxy/nginx-proxy:test container", pytrace=False)
+            pytest.fail(f"No running nginxproxy/nginx-proxy:{IMAGE_TAG} container", pytrace=False)
         return nginx_proxy_containers
         return nginx_proxy_containers
 
 
     def get_conf(self):
     def get_conf(self):
@@ -188,7 +191,7 @@ def container_ipv6(container):
     return net_info[network_name]["GlobalIPv6Address"]
     return net_info[network_name]["GlobalIPv6Address"]
 
 
 
 
-def nginx_proxy_dns_resolver(domain_name):
+def nginx_proxy_single_container_dns_resolver(domain_name):
     """
     """
     if "nginx-proxy" if found in host, return the ip address of the docker container
     if "nginx-proxy" if found in host, return the ip address of the docker container
     issued from the docker image nginxproxy/nginx-proxy:test.
     issued from the docker image nginxproxy/nginx-proxy:test.
@@ -196,21 +199,44 @@ def nginx_proxy_dns_resolver(domain_name):
     :return: IP or None
     :return: IP or None
     """
     """
     log = logging.getLogger('DNS')
     log = logging.getLogger('DNS')
-    log.debug(f"nginx_proxy_dns_resolver({domain_name!r})")
+    log.debug(f"nginx_proxy_single_container_dns_resolver({domain_name!r})")
     if 'nginx-proxy' in domain_name:
     if 'nginx-proxy' in domain_name:
         nginxproxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginxproxy/nginx-proxy:test"})
         nginxproxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginxproxy/nginx-proxy:test"})
         if len(nginxproxy_containers) == 0:
         if len(nginxproxy_containers) == 0:
-            log.warn(f"no container found from image nginxproxy/nginx-proxy:test while resolving {domain_name!r}")
+            log.info(f"no container found from image nginxproxy/nginx-proxy:test while resolving {domain_name!r}")
             exited_nginxproxy_containers = docker_client.containers.list(filters={"status": "exited", "ancestor": "nginxproxy/nginx-proxy:test"})
             exited_nginxproxy_containers = docker_client.containers.list(filters={"status": "exited", "ancestor": "nginxproxy/nginx-proxy:test"})
             if len(exited_nginxproxy_containers) > 0:
             if len(exited_nginxproxy_containers) > 0:
                 exited_nginxproxy_container_logs = exited_nginxproxy_containers[0].logs()
                 exited_nginxproxy_container_logs = exited_nginxproxy_containers[0].logs()
-                log.warn(f"nginxproxy/nginx-proxy:test container might have exited unexpectedly. Container logs: " + "\n" + exited_nginxproxy_container_logs.decode())
+                log.warning(f"nginxproxy/nginx-proxy:test container might have exited unexpectedly. Container logs: " + "\n" + exited_nginxproxy_container_logs.decode())
             return
             return
         nginxproxy_container = nginxproxy_containers[0]
         nginxproxy_container = nginxproxy_containers[0]
         ip = container_ip(nginxproxy_container)
         ip = container_ip(nginxproxy_container)
         log.info(f"resolving domain name {domain_name!r} as IP address {ip} of nginx-proxy container {nginxproxy_container.name}")
         log.info(f"resolving domain name {domain_name!r} as IP address {ip} of nginx-proxy container {nginxproxy_container.name}")
         return ip
         return ip
 
 
+def nginx_proxy_separate_containers_dns_resolver(domain_name):
+    """
+    if "nginx-proxy" if found in host, return the ip address of the docker container
+    labeled with "com.github.nginx-proxy.nginx-proxy.nginx".
+
+    :return: IP or None
+    """
+    log = logging.getLogger('DNS')
+    log.debug(f"nginx_proxy_separate_containers_dns_resolver({domain_name!r})")
+    if 'nginx-proxy' in domain_name:
+        nginx_containers = docker_client.containers.list(filters={"status": "running", "label": "com.github.nginx-proxy.nginx-proxy.nginx"})
+        if len(nginx_containers) == 0:
+            log.info(f"no container labeled with com.github.nginx-proxy.nginx-proxy.nginx found while resolving {domain_name!r}")
+            exited_nginx_containers = docker_client.containers.list(filters={"status": "exited", "label": "com.github.nginx-proxy.nginx-proxy.nginx"})
+            if len(exited_nginx_containers) > 0:
+                exited_nginx_container_logs = exited_nginx_containers[0].logs()
+                log.warning(f"nginx container might have exited unexpectedly. Container logs: " + "\n" + exited_nginx_container_logs.decode())
+            return
+        nginx_container = nginx_containers[0]
+        ip = container_ip(nginx_container)
+        log.info(f"resolving domain name {domain_name!r} as IP address {ip} of nginx container {nginx_container.name}")
+        return ip
+
 def docker_container_dns_resolver(domain_name):
 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
     if domain name is of the form "XXX.container.docker" or "anything.XXX.container.docker", return the ip address of the docker container
@@ -231,7 +257,7 @@ def docker_container_dns_resolver(domain_name):
     try:
     try:
         container = docker_client.containers.get(container_name)
         container = docker_client.containers.get(container_name)
     except docker.errors.NotFound:
     except docker.errors.NotFound:
-        log.warn(f"container named {container_name!r} not found while resolving {domain_name!r}")
+        log.warning(f"container named {container_name!r} not found while resolving {domain_name!r}")
         return
         return
     log.debug(f"container {container.name!r} found ({container.short_id})")
     log.debug(f"container {container.name!r} found ({container.short_id})")
 
 
@@ -244,7 +270,8 @@ 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 'nginxproxy/nginx-proxy:test'.
+    of the container created from image 'nginxproxy/nginx-proxy:test' or
+    labeled with 'com.github.nginx-proxy.nginx-proxy.nginx'.
     """
     """
     prv_getaddrinfo = socket.getaddrinfo
     prv_getaddrinfo = socket.getaddrinfo
     dns_cache = {}
     dns_cache = {}
@@ -258,7 +285,9 @@ def monkey_patch_urllib_dns_resolver():
             pytest.skip("This system does not support IPv6")
             pytest.skip("This system does not support IPv6")
 
 
         # custom DNS resolvers
         # custom DNS resolvers
-        ip = nginx_proxy_dns_resolver(args[0])
+        ip = nginx_proxy_single_container_dns_resolver(args[0])
+        if ip is None:
+            ip = nginx_proxy_separate_containers_dns_resolver(args[0])
         if ip is None:
         if ip is None:
             ip = docker_container_dns_resolver(args[0])
             ip = docker_container_dns_resolver(args[0])
         if ip is not None:
         if ip is not None:
@@ -319,10 +348,11 @@ def docker_compose_down(compose_file='docker-compose.yml'):
 
 
 def wait_for_nginxproxy_to_be_ready():
 def wait_for_nginxproxy_to_be_ready():
     """
     """
-    If one (and only one) container started from image nginxproxy/nginx-proxy:test is found,
-    wait for its log to contain substring "Watching docker events"
+    If one (and only one) container started from image nginxproxy/nginx-proxy:test
+    or nginxproxy/nginx-proxy:test-dockergen is found, wait for its log to contain
+    substring "Watching docker events"
     """
     """
-    containers = docker_client.containers.list(filters={"ancestor": "nginxproxy/nginx-proxy:test"})
+    containers = docker_client.containers.list(filters={"ancestor": f"nginxproxy/nginx-proxy:{IMAGE_TAG}"})
     if len(containers) != 1:
     if len(containers) != 1:
         return
         return
     container = containers[0]
     container = containers[0]
@@ -371,7 +401,7 @@ def connect_to_network(network):
         try:
         try:
             my_container = docker_client.containers.get(test_container)
             my_container = docker_client.containers.get(test_container)
         except docker.errors.NotFound:
         except docker.errors.NotFound:
-            logging.warn(f"container {test_container} not found")
+            logging.warning(f"container {test_container} not found")
             return
             return
 
 
         # figure out our container networks
         # figure out our container networks
@@ -399,7 +429,7 @@ def disconnect_from_network(network=None):
         try:
         try:
             my_container = docker_client.containers.get(test_container)
             my_container = docker_client.containers.get(test_container)
         except docker.errors.NotFound:
         except docker.errors.NotFound:
-            logging.warn(f"container {test_container} not found")
+            logging.warning(f"container {test_container} not found")
             return
             return
 
 
         # figure out our container networks
         # figure out our container networks
@@ -527,7 +557,11 @@ def acme_challenge_path():
 def pytest_runtest_logreport(report):
 def pytest_runtest_logreport(report):
     if report.failed:
     if report.failed:
         if isinstance(report.longrepr, ReprExceptionInfo):
         if isinstance(report.longrepr, ReprExceptionInfo):
-            test_containers = docker_client.containers.list(all=True, filters={"ancestor": "nginxproxy/nginx-proxy:test"})
+            nginx_containers = docker_client.containers.list(all=True, filters={"label": "com.github.nginx-proxy.nginx-proxy.nginx"})
+            for container in nginx_containers:
+                report.longrepr.addsection('nginx container logs', container.logs())
+
+            test_containers = docker_client.containers.list(all=True, filters={"ancestor": f"nginxproxy/nginx-proxy:{IMAGE_TAG}"})
             for container in test_containers:
             for container in test_containers:
                 report.longrepr.addsection('nginx-proxy logs', container.logs())
                 report.longrepr.addsection('nginx-proxy logs', container.logs())
                 report.longrepr.addsection('nginx-proxy conf', get_nginx_conf_from_container(container))
                 report.longrepr.addsection('nginx-proxy conf', get_nginx_conf_from_container(container))
@@ -553,9 +587,9 @@ def pytest_runtest_setup(item):
 ###############################################################################
 ###############################################################################
 
 
 try:
 try:
-    docker_client.images.get('nginxproxy/nginx-proxy:test')
+    docker_client.images.get(f"nginxproxy/nginx-proxy:{IMAGE_TAG}")
 except docker.errors.ImageNotFound:
 except docker.errors.ImageNotFound:
-    pytest.exit("The docker image 'nginxproxy/nginx-proxy:test' is missing")
+    pytest.exit(f"The docker image 'nginxproxy/nginx-proxy:{IMAGE_TAG}' is missing")
 
 
 if Version(docker.__version__) < Version("7.0.0"):
 if Version(docker.__version__) < Version("7.0.0"):
     pytest.exit("This test suite is meant to work with the python docker module v7.0.0 or later")
     pytest.exit("This test suite is meant to work with the python docker module v7.0.0 or later")

+ 29 - 0
test/stress_tests/test_unreachable_network/docker-compose.yml

@@ -2,14 +2,43 @@ networks:
   netA:
   netA:
   netB:
   netB:
 
 
+volumes:
+  nginx_conf:
+
 services:
 services:
   reverseproxy:
   reverseproxy:
+    profiles:
+      - singleContainer
     container_name: reverseproxy
     container_name: reverseproxy
     networks:
     networks:
       - netA
       - netA
     image: nginxproxy/nginx-proxy:test
     image: nginxproxy/nginx-proxy:test
     volumes:
     volumes:
       - /var/run/docker.sock:/tmp/docker.sock:ro
       - /var/run/docker.sock:/tmp/docker.sock:ro
+  
+  reverseproxynginx:
+    profiles:
+      - separateContainers
+    container_name: reverseproxy
+    networks:
+      - netA
+    image: nginx:alpine
+    volumes:
+      - nginx_conf:/etc/nginx/conf.d:ro
+    labels:
+      - "com.github.nginx-proxy.nginx-proxy.nginx"
+
+  docker-gen:
+    profiles:
+      - separateContainers
+    networks:
+      - netA
+    image: nginxproxy/nginx-proxy:test-dockergen
+    volumes:
+      - /var/run/docker.sock:/tmp/docker.sock:ro
+      - nginx_conf:/etc/nginx/conf.d
+    environment:
+      NGINX_CONTAINER_NAME: reverseproxy
 
 
   webA:
   webA:
     networks:
     networks: