Compare commits

..

34 commits

Author SHA1 Message Date
4856fbe7eb Aggiorna README.md
Some checks failed
Update Docker Hub Description / Update Docker Hub Description (push) Has been cancelled
2025-02-05 21:22:14 +08:00
23d56b2185 Aggiorna README.md
Some checks are pending
Update Docker Hub Description / Update Docker Hub Description (push) Waiting to run
2025-02-05 21:20:42 +08:00
Nicolas Duchon
1da623019f
Merge pull request #2576 from nginx-proxy/dependabot/docker/nginxproxy/docker-gen-0.14.5-debian
build: bump nginxproxy/docker-gen from 0.14.4 to 0.14.5
2025-01-19 23:03:10 +01:00
dependabot[bot]
e234ffba20
build: bump nginxproxy/docker-gen from 0.14.4-debian to 0.14.5-debian
Bumps nginxproxy/docker-gen from 0.14.4-debian to 0.14.5-debian.

---
updated-dependencies:
- dependency-name: nginxproxy/docker-gen
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-19 20:34:13 +00:00
Nicolas Duchon
18030a7896
Merge pull request #1737 from junderw/fix-redirect
feat: redirect non-GET methods using 308 instead of 301
2025-01-18 22:03:27 +01:00
Nicolas Duchon
dfbff1eb9c
Merge pull request #2561 from nginx-proxy/fix/proto-with-multiports
fix: add proto to VIRTUAL_HOST_MULTIPORTS
2025-01-18 22:01:09 +01:00
Nicolas Duchon
34a33a2255 tests: virtual proto 2025-01-18 21:50:41 +01:00
Nicolas Duchon
a61e485410 tests: refactor due to rebase 2025-01-18 20:41:17 +01:00
Nicolas Duchon
9312d5239a docs: typo 2025-01-18 20:25:01 +01:00
Nicolas Duchon
9fc7cec15c feat: customizable non get redirect code 2025-01-18 20:25:01 +01:00
Nicolas Duchon
8447a36046 tests: parameterize test 2025-01-18 20:25:01 +01:00
Nicolas Duchon
923f05032f tests: fix tests & test compose file 2025-01-18 20:25:01 +01:00
junderw
820d4a29ac tests: redirects 2025-01-18 20:25:01 +01:00
junderw
1859811311 feat: redirect using 308 for non-GET requests 2025-01-18 20:25:01 +01:00
Nicolas Duchon
691724c81f
Merge pull request #2570 from nginx-proxy/test/refactor-darwin
tests: factor out base nginx-proxy config and enable local testing on macOS / Darwin
2025-01-05 11:37:45 +01:00
Nicolas Duchon
aa8145b62d tests: review changes
Co-authored-by: Niek <100143256+SchoNie@users.noreply.github.com>
2025-01-05 00:05:30 +01:00
Nicolas Duchon
836012cad6 docs: update test README 2025-01-03 16:00:36 +01:00
Nicolas Duchon
005377c6e5 tests: remove remaining unneeded container config 2024-12-30 20:45:08 +01:00
Nicolas Duchon
bfdd72fe95 tests: type hints and linting 2024-12-30 14:17:03 +01:00
Nicolas Duchon
40309e2441 tests: enable local testing on macOS / Darwin 2024-12-30 13:41:47 +01:00
Nicolas Duchon
daa9449176 tests: factor out base nginx-proxy config 2024-12-30 12:07:30 +01:00
Nicolas Duchon
4ccbc3edec
Merge pull request #2569 from nginx-proxy/test/cleanup
tests: fix, cleanup and restructure test code
2024-12-27 21:47:56 +01:00
Nicolas Duchon
1f732a54c6 tests: missing doubles quotes on WEB_PORTS 2024-12-27 21:36:39 +01:00
Nicolas Duchon
ae0c9a8e96 tests: fixture type hints and style standardization 2024-12-27 21:36:07 +01:00
Nicolas Duchon
ea99c1a6f9 tests: review comments 2024-12-27 16:16:55 +01:00
Nicolas Duchon
1e9745f604 tests: complete typing, minor fixes 2024-12-26 16:21:30 +01:00
Nicolas Duchon
7b6baa43cd tests: remove custom system_has_ipv6() method 2024-12-26 01:13:29 +01:00
Nicolas Duchon
a2c316a876
docs: add powered by section with relevant JetBains IDEs
JetBrains is providing a license for GoLang and PyCharm to the
maintainer of this project as part of their open source program.
2024-12-25 15:39:00 +01:00
Nicolas Duchon
fb0fc331c0 tests: minor typos and code style 2024-12-24 16:26:23 +01:00
Nicolas Duchon
eb09876f97 tests: standardize file structure & naming 2024-12-24 16:22:20 +01:00
Nicolas Duchon
35e2d21527 tests: do not remove containers on host 2024-12-24 14:05:42 +01:00
Nicolas Duchon
b5dea1cf50 tests: cleanup test code
- remove unused imports in test cases
- fix code smells and code style in conftest.py
2024-12-24 13:53:09 +01:00
Nicolas Duchon
a25b7ea1ef docs: add proto to VIRTUAL_HOST_MULTIPORTS 2024-12-08 14:06:38 +01:00
Nicolas Duchon
9bd84fc95e fix: add proto to VIRTUAL_HOST_MULTIPORTS 2024-12-08 11:59:48 +01:00
277 changed files with 2022 additions and 4222 deletions

View file

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

View file

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

View file

@ -1,4 +1,4 @@
FROM docker.io/nginxproxy/docker-gen:0.14.4 AS docker-gen
FROM docker.io/nginxproxy/docker-gen:0.14.5 AS docker-gen
FROM docker.io/nginxproxy/forego:0.18.2 AS forego

View file

@ -1,4 +1,4 @@
FROM docker.io/nginxproxy/docker-gen:0.14.4-debian AS docker-gen
FROM docker.io/nginxproxy/docker-gen:0.14.5-debian AS docker-gen
FROM docker.io/nginxproxy/forego:0.18.2-debian AS forego

View file

@ -1,18 +0,0 @@
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"]

View file

@ -11,19 +11,10 @@ build-nginx-proxy-test-debian:
build-nginx-proxy-test-alpine:
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/pytest.sh
test-alpine: export COMPOSE_PROFILES = singleContainer
test-alpine: build-webserver build-nginx-proxy-test-alpine
test/pytest.sh
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
test: test-debian test-alpine

View file

@ -20,7 +20,17 @@ docker run --detach \
--volume /var/run/docker.sock:/tmp/docker.sock:ro \
nginxproxy/nginx-proxy:1.6
```
docker-compose
```docker-compose
services:
nginx-proxy:
image: nginxproxy/nginx-proxy
restart: always
ports:
- "80:80"
volumes:
- "/var/run/docker.sock:/tmp/docker.sock"
```
Then start any containers (here an nginx container) you want proxied with an env var `VIRTUAL_HOST=subdomain.yourdomain.com`
```console
@ -29,7 +39,12 @@ docker run --detach \
--env VIRTUAL_HOST=foo.bar.com \
nginx
```
docker-compose
```docker-compose
environment:
- VIRTUAL_HOST=git.patachina.casacam.net
- VIRTUAL_PORT=3000
```
Provided your DNS is setup to resolve `foo.bar.com` to the host running nginx-proxy, a request to `http://foo.bar.com` will then be routed to a container with the `VIRTUAL_HOST` env var set to `foo.bar.com` (in this case, the **your-proxied-app** container).
The containers being proxied must :
@ -70,3 +85,8 @@ docker pull nginxproxy/nginx-proxy:1.6-alpine
### Additional documentation
Please check the [docs section](https://github.com/nginx-proxy/nginx-proxy/tree/main/docs).
### Powered by
[![GoLand logo](https://resources.jetbrains.com/storage/products/company/brand/logos/GoLand_icon.svg)](https://www.jetbrains.com/go/)
[![PyCharm logo](https://resources.jetbrains.com/storage/products/company/brand/logos/PyCharm_icon.svg)](https://www.jetbrains.com/pycharm/)

View file

@ -101,7 +101,7 @@ function _setup_dhparam() {
}
# Run the init logic if the default CMD was provided
if [[ $* == "forego start -r" ]] || [[ $* =~ "docker-gen -notify-sighup" ]]; then
if [[ $* == 'forego start -r' ]]; then
_print_version
_check_unix_socket
@ -116,11 +116,6 @@ if [[ $* == "forego start -r" ]] || [[ $* =~ "docker-gen -notify-sighup" ]]; the
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
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
exec "$@"

View file

@ -56,7 +56,7 @@ For each host defined into `VIRTUAL_HOST`, the associated virtual port is retrie
### Multiple ports
If your container expose more than one service on different ports and those services need to be proxied, you'll need to use the `VIRTUAL_HOST_MULTIPORTS` environment variable. This variable takes virtual host, path, port and dest definition in YAML (or JSON) form, and completely override the `VIRTUAL_HOST`, `VIRTUAL_PORT`, `VIRTUAL_PATH` and `VIRTUAL_DEST` environment variables on this container.
If your container expose more than one service on different ports and those services need to be proxied, you'll need to use the `VIRTUAL_HOST_MULTIPORTS` environment variable. This variable takes virtual host, path, port and dest definition in YAML (or JSON) form, and completely override the `VIRTUAL_HOST`, `VIRTUAL_PORT`, `VIRTUAL_PROTO`, `VIRTUAL_PATH` and `VIRTUAL_DEST` environment variables on this container.
The YAML syntax should be easier to write on Docker compose files, while the JSON syntax can be used for CLI invocation.
@ -66,19 +66,21 @@ The expected format is the following:
hostname:
path:
port: int
proto: string
dest: string
```
For each hostname entry, `path`, `port` and `dest` are optional and are assigned default values when missing:
For each hostname entry, `path`, `port`, `proto` and `dest` are optional and are assigned default values when missing:
- `path` = "/"
- `port` = default port
- `proto` = "http"
- `dest` = ""
The following examples use an hypothetical container running services on port 80, 8000 and 9000:
#### Multiple ports routed to different hostnames
The following example use an hypothetical container running services over HTTP on port 80, 8000 and 9000:
```yaml
services:
multiport-container:
@ -111,12 +113,14 @@ services:
This would result in the following proxy config:
- `www.example.org` -> `multiport-container:80`
- `service1.example.org` -> `multiport-container:8000`
- `service2.example.org` -> `multiport-container:9000`
- `www.example.org` -> `multiport-container:80` over `HTTP`
- `service1.example.org` -> `multiport-container:8000` over `HTTP`
- `service2.example.org` -> `multiport-container:9000` over `HTTP`
#### Multiple ports routed to same hostname and different paths
The following example use an hypothetical container running services over HTTP on port 80 and 8000 and over HTTPS on port 9443:
```yaml
services:
multiport-container:
@ -130,11 +134,12 @@ services:
port: 8000
dest: "/"
"/service2":
port: 9000
port: 9443
proto: "https"
dest: "/"
# port and dest are not specified on the / path, so this path is routed
# to the default port with the default dest value (empty string)
# port and dest are not specified on the / path, so this path is routed to the
# default port with the default dest value (empty string) and default proto (http)
# JSON equivalent:
# VIRTUAL_HOST_MULTIPORTS: |-
@ -142,16 +147,16 @@ services:
# "www.example.org": {
# "/": {},
# "/service1": { "port": 8000, "dest": "/" },
# "/service2": { "port": 9000, "dest": "/" }
# "/service2": { "port": 9443, "proto": "https", "dest": "/" }
# }
# }
```
This would result in the following proxy config:
- `www.example.org` -> `multiport-container:80`
- `www.example.org/service1` -> `multiport-container:8000`
- `www.example.org/service2` -> `multiport-container:9000`
- `www.example.org` -> `multiport-container:80` over `HTTP`
- `www.example.org/service1` -> `multiport-container:8000` over `HTTP`
- `www.example.org/service2` -> `multiport-container:9443` over `HTTPS`
⬆️ [back to table of contents](#table-of-contents)
@ -575,6 +580,8 @@ The default behavior for the proxy when port 80 and 443 are exposed is as follow
- If the virtual host does not have a usable cert, but `default.crt` and `default.key` exist, those will be used as the virtual host's certificate.
- If the virtual host does not have a usable cert, and `default.crt` and `default.key` do not exist, or if the virtual host is configured not to trust the default certificate, SSL handshake will be rejected (see [Default and Missing Certificate](#default-and-missing-certificate) below).
The redirection from HTTP to HTTPS use by default a [`301`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301) response for every HTTP methods (except `CONNECT` and `TRACE` which are disabled on nginx). If you wish to use a custom redirection response for the `OPTIONS`, `POST`, `PUT`, `PATCH` and `DELETE` HTTP methods, you can either do it globally with the environment variable `NON_GET_REDIRECT` on the proxy container or per virtual host with the `com.github.nginx-proxy.nginx-proxy.non-get-redirect` label on proxied containers. Valid values are [`307`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307) and [`308`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308).
To serve traffic in both SSL and non-SSL modes without redirecting to SSL, you can include the environment variable `HTTPS_METHOD=noredirect` (the default is `HTTPS_METHOD=redirect`). You can also disable the non-SSL site entirely with `HTTPS_METHOD=nohttp`, or disable the HTTPS site with `HTTPS_METHOD=nohttps`. `HTTPS_METHOD` can be specified on each container for which you want to override the default behavior or on the proxy container to set it globally. If `HTTPS_METHOD=noredirect` is used, Strict Transport Security (HSTS) is disabled to prevent HTTPS users from being redirected by the client. If you cannot get to the HTTP site after changing this setting, your browser has probably cached the HSTS policy and is automatically redirecting you back to HTTPS. You will need to clear your browser's HSTS cache or use an incognito window / different browser.
By default, [HTTP Strict Transport Security (HSTS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) is enabled with `max-age=31536000` for HTTPS sites. You can disable HSTS with the environment variable `HSTS=off` or use a custom HSTS configuration like `HSTS=max-age=31536000; includeSubDomains; preload`.

View file

@ -32,6 +32,7 @@
{{- $_ := set $config "enable_http3" ($globals.Env.ENABLE_HTTP3 | default "false") }}
{{- $_ := set $config "enable_http_on_missing_cert" ($globals.Env.ENABLE_HTTP_ON_MISSING_CERT | default "true") }}
{{- $_ := set $config "https_method" ($globals.Env.HTTPS_METHOD | default "redirect") }}
{{- $_ := set $config "non_get_redirect" ($globals.Env.NON_GET_REDIRECT | default "301") }}
{{- $_ := set $config "default_host" $globals.Env.DEFAULT_HOST }}
{{- $_ := set $config "resolvers" $globals.Env.RESOLVERS }}
{{- /* LOG_JSON is a shorthand that sets logging defaults to JSON format */}}
@ -589,18 +590,31 @@ proxy_set_header Proxy "";
{{- range $path, $vpath := $vhost }}
{{- if (empty $vpath) }}
{{- $vpath = dict "dest" "" "port" "default" }}
{{- $vpath = dict
"dest" ""
"port" "default"
"proto" "http"
}}
{{- end }}
{{- $dest := $vpath.dest | default "" }}
{{- $port := $vpath.port | default "default" | toString }}
{{- $proto := $vpath.proto | default "http" }}
{{- $path_data := get $paths $path | default (dict) }}
{{- $path_ports := $path_data.ports | default (dict) }}
{{- $path_port_containers := get $path_ports $port | default (list) | concat $containers }}
{{- $_ := set $path_ports $port $path_port_containers }}
{{- $_ := set $path_data "ports" $path_ports }}
{{- if (not (hasKey $path_data "dest")) }}
{{- $_ := set $path_data "dest" $dest }}
{{- end }}
{{- if (not (hasKey $path_data "proto")) }}
{{- $_ := set $path_data "proto" $proto }}
{{- end }}
{{- $_ := set $paths $path $path_data }}
{{- end }}
{{- $_ := set $vhost_data "paths" $paths }}
@ -635,6 +649,8 @@ proxy_set_header Proxy "";
{{- range $path, $containers := $tmp_paths }}
{{- $dest := groupByKeys $containers "Env.VIRTUAL_DEST" | first | default "" }}
{{- $proto := groupByKeys $containers "Env.VIRTUAL_PROTO" | first | default "http" | trim }}
{{- $path_data := get $paths $path | default (dict) }}
{{- $path_ports := $path_data.ports | default (dict) }}
{{- range $port, $containers := groupByWithDefault $containers "Env.VIRTUAL_PORT" "default" }}
@ -642,9 +658,15 @@ proxy_set_header Proxy "";
{{- $_ := set $path_ports $port $path_port_containers }}
{{- end }}
{{- $_ := set $path_data "ports" $path_ports }}
{{- if (not (hasKey $path_data "dest")) }}
{{- $_ := set $path_data "dest" $dest }}
{{- end }}
{{- if (not (hasKey $path_data "proto")) }}
{{- $_ := set $path_data "proto" $proto }}
{{- end }}
{{- $_ := set $paths $path $path_data }}
{{- end }}
{{- $_ := set $vhost_data "paths" $paths }}
@ -664,8 +686,6 @@ proxy_set_header Proxy "";
{{ $vpath_containers = concat $vpath_containers $vport_containers }}
{{- end }}
{{- /* Get the VIRTUAL_PROTO defined by containers w/ the same vhost-vpath, falling back to "http". */}}
{{- $proto := groupByKeys $vpath_containers "Env.VIRTUAL_PROTO" | first | default "http" | trim }}
{{- /* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external". */}}
{{- $network_tag := groupByKeys $vpath_containers "Env.NETWORK_ACCESS" | first | default "external" }}
@ -678,7 +698,6 @@ proxy_set_header Proxy "";
{{- $upstream = printf "%s-%s" $upstream $sum }}
{{- end }}
{{- $_ := set $vpath_data "proto" $proto }}
{{- $_ := set $vpath_data "network_tag" $network_tag }}
{{- $_ := set $vpath_data "upstream" $upstream }}
{{- $_ := set $vpath_data "loadbalance" $loadbalance }}
@ -718,8 +737,11 @@ proxy_set_header Proxy "";
{{- if and $https_method_disable_http (not $cert_ok) $enable_http_on_missing_cert }}
{{- $https_method = "noredirect" }}
{{- end }}
{{- $non_get_redirect := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.non-get-redirect" | keys | first | default $globals.config.non_get_redirect }}
{{- $http2_enabled := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http2.enable" | keys | first | default $globals.config.enable_http2 | parseBool }}
{{- $http3_enabled := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http3.enable" | keys | first | default $globals.config.enable_http3 | parseBool }}
{{- $acme_http_challenge := groupByKeys $vhost_containers "Env.ACME_HTTP_CHALLENGE_LOCATION" | first | default $globals.config.acme_http_challenge }}
{{- $acme_http_challenge_legacy := eq $acme_http_challenge "legacy" }}
{{- $acme_http_challenge_enabled := false }}
@ -746,6 +768,7 @@ proxy_set_header Proxy "";
"default" $default
"hsts" $hsts
"https_method" $https_method
"non_get_redirect" $non_get_redirect
"http2_enabled" $http2_enabled
"http3_enabled" $http3_enabled
"is_regexp" $is_regexp
@ -886,11 +909,14 @@ server {
{{- end }}
location / {
{{- if eq $globals.config.external_https_port "443" }}
return 301 https://$host$request_uri;
{{- else }}
return 301 https://$host:{{ $globals.config.external_https_port }}$request_uri;
{{- end }}
{{- $redirect_uri := "https://$host$request_uri" }}
{{- if ne $globals.config.external_https_port "443" }}
{{- $redirect_uri = printf "https://$host:%s$request_uri" $globals.config.external_https_port }}
{{- end}}
if ($request_method ~ (OPTIONS|POST|PUT|PATCH|DELETE)) {
return {{ $vhost.non_get_redirect }} {{ $redirect_uri }};
}
return 301 {{ $redirect_uri }};
}
}
{{- end }}

View file

@ -57,13 +57,39 @@ This test suite uses [pytest](http://doc.pytest.org/en/latest/). The [conftest.p
### docker_compose fixture
When using the `docker_compose` fixture in a test, pytest will try to find a yml file named after your test module filename. For instance, if your test module is `test_example.py`, then the `docker_compose` fixture will try to load a `test_example.yml` [docker compose file](https://docs.docker.com/compose/compose-file/).
When using the `docker_compose` fixture in a test, pytest will try to start the [Docker Compose](https://docs.docker.com/compose/) services corresponding to the current test module, based on the test module filename.
Once the docker compose file found, the fixture will remove all containers, run `docker compose up`, and finally your test will be executed.
By default, if your test module file is `test/test_subdir/test_example.py`, then the `docker_compose` fixture will try to load the following files, [merging them](https://docs.docker.com/reference/compose-file/merge/) in this order:
The fixture will run the _docker compose_ command with the `-f` option to load the given compose file. So you can test your docker compose file syntax by running it yourself with:
1. `test/compose.base.yml`
2. `test/test_subdir/compose.base.override.yml` (if it exists)
3. `test/test_subdir/test_example.yml`
docker compose -f test_example.yml up -d
The fixture will run the _docker compose_ command with the `-f` option to load the given compose files. So you can test your docker compose file syntax by running it yourself with:
docker compose -f test/compose.base.yml -f test/test_subdir/test_example.yml up -d
The first file contains the base configuration of the nginx-proxy container common to most tests:
```yaml
services:
nginx-proxy:
image: nginxproxy/nginx-proxy:test
container_name: nginx-proxy
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
ports:
- "80:80"
- "443:443"
```
The second optional file allow you to override this base configuration for all test modules in a subfolder.
The third file contains the services and overrides specific to a given test module.
This automatic merge can be bypassed by using a file named `test_example.base.yml` (instead of `test_example.yml`). When this file exist, it will be the only one used by the test and no merge with other compose files will automatically occur.
The `docker_compose` fixture also set the `PYTEST_MODULE_PATH` environment variable to the absolute path of the current test module directory, so it can be used to mount files or directory relatives to the current test.
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.
@ -71,7 +97,10 @@ In your tests, you can use the `docker_compose` variable to query and command th
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 `nginxproxy/nginx-proxy:test` image. So all the following domain names will resolve to the nginx-proxy container in tests:
Any domain name containing the substring `nginx-proxy` will resolve to `127.0.0.1` if the tests are executed on a Darwin (macOS) system, otherwise the IP address of the container that was created from the `nginxproxy/nginx-proxy:test` image.
So, in tests, all the following domain names will resolve to either localhost or the nginx-proxy container's IP:
- `nginx-proxy`
- `nginx-proxy.com`
- `www.nginx-proxy.com`
@ -80,14 +109,16 @@ Any domain name containing the substring `nginx-proxy` will resolve to the IP ad
- `whatever.nginx-proxyooooooo`
- ...
Any domain name ending with `XXX.container.docker` will resolve to the IP address of the XXX container.
Any domain name ending with `XXX.container.docker` will resolve to `127.0.0.1` if the tests are executed on a Darwin (macOS) system, otherwise the IP address of the container named `XXX`.
So, on a non-Darwin system:
- `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
The `nginxproxy` fixture will provide you with a replacement for the python [requests](https://pypi.python.org/pypi/requests/) module. This replacement will just repeat up to 30 times a requests if it receives the HTTP error 404 or 502. This error occurs when you try to send queries to nginx-proxy too early after the container creation.

9
test/compose.base.yml Normal file
View file

@ -0,0 +1,9 @@
services:
nginx-proxy:
image: nginxproxy/nginx-proxy:test
container_name: nginx-proxy
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
ports:
- "80:80"
- "443:443"

View file

@ -1,35 +1,39 @@
import contextlib
import logging
import os
import pathlib
import platform
import re
import shlex
import socket
import subprocess
import time
from typing import List
from io import StringIO
from typing import Iterator, List, Optional
import backoff
import docker.errors
import pytest
import requests
from _pytest.fixtures import FixtureRequest
from docker import DockerClient
from docker.models.containers import Container
from docker.models.networks import Network
from packaging.version import Version
from requests.packages.urllib3.util.connection import HAS_IPV6
from requests import Response
from urllib3.util.connection import HAS_IPV6
logging.basicConfig(level=logging.INFO)
logging.getLogger('backoff').setLevel(logging.INFO)
logging.getLogger('DNS').setLevel(logging.DEBUG)
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 = pathlib.Path(__file__).parent.joinpath("certs/ca-root.crt")
PYTEST_RUNNING_IN_CONTAINER = os.environ.get('PYTEST_RUNNING_IN_CONTAINER') == "1"
FORCE_CONTAINER_IPV6 = False # ugly global state to consider containers' IPv6 address instead of IPv4
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()
@ -43,8 +47,9 @@ test_container = 'nginx-proxy-pytest'
#
###############################################################################
@contextlib.contextmanager
def ipv6(force_ipv6=True):
def ipv6(force_ipv6: bool = True):
"""
Meant to be used as a context manager to force IPv6 sockets:
@ -62,119 +67,90 @@ def ipv6(force_ipv6=True):
FORCE_CONTAINER_IPV6 = False
class requests_for_docker(object):
class RequestsForDocker:
"""
Proxy for calling methods of the requests module.
When a HTTP response failed due to HTTP Error 404 or 502, retry a few times.
When an HTTP response failed due to HTTP Error 404 or 502, retry a few times.
Provides method `get_conf` to extract the nginx-proxy configuration content.
"""
def __init__(self):
self.session = requests.Session()
if os.path.isfile(CA_ROOT_CERTIFICATE):
self.session.verify = CA_ROOT_CERTIFICATE
if CA_ROOT_CERTIFICATE.is_file():
self.session.verify = CA_ROOT_CERTIFICATE.as_posix()
@staticmethod
def __backoff_predicate(expected_status_codes=None):
if expected_status_codes is not None:
if isinstance(expected_status_codes, int):
expected_status_codes = [expected_status_codes]
return lambda r: r.status_code not in expected_status_codes
else:
return lambda r: r.status_code not in (200, 301)
__backed_off_exceptions = (requests.exceptions.SSLError, requests.exceptions.ConnectionError)
@staticmethod
def get_nginx_proxy_containers() -> List[Container]:
def get_nginx_proxy_container() -> Container:
"""
Return list of containers
"""
nginx_proxy_containers = docker_client.containers.list(filters={"ancestor": f"nginxproxy/nginx-proxy:{IMAGE_TAG}"})
nginx_proxy_containers = docker_client.containers.list(filters={"ancestor": "nginxproxy/nginx-proxy:test"})
if len(nginx_proxy_containers) > 1:
pytest.fail(f"Too many running nginxproxy/nginx-proxy:{IMAGE_TAG} containers", pytrace=False)
pytest.fail("Too many running nginxproxy/nginx-proxy:test containers", pytrace=False)
elif len(nginx_proxy_containers) == 0:
pytest.fail(f"No running nginxproxy/nginx-proxy:{IMAGE_TAG} container", pytrace=False)
return nginx_proxy_containers
pytest.fail("No running nginxproxy/nginx-proxy:test container", pytrace=False)
return nginx_proxy_containers.pop()
def get_conf(self):
def get_conf(self) -> bytes:
"""
Return the nginx config file
"""
nginx_proxy_containers = self.get_nginx_proxy_containers()
return get_nginx_conf_from_container(nginx_proxy_containers[0])
nginx_proxy_container = self.get_nginx_proxy_container()
return get_nginx_conf_from_container(nginx_proxy_container)
def get_ip(self) -> str:
"""
Return the nginx container ip address
"""
nginx_proxy_containers = self.get_nginx_proxy_containers()
return container_ip(nginx_proxy_containers[0])
nginx_proxy_container = self.get_nginx_proxy_container()
return container_ip(nginx_proxy_container)
def get(self, *args, **kwargs):
_expected_status_code = kwargs.pop('expected_status_code', None)
def get(self, *args, **kwargs) -> Response:
with ipv6(kwargs.pop('ipv6', False)):
@backoff.on_exception(backoff.constant, self.__backed_off_exceptions, interval=.3, max_tries=30, jitter=None)
@backoff.on_predicate(backoff.constant, self.__backoff_predicate(_expected_status_code), interval=.3, max_tries=30, jitter=None)
def _get(*args, **kwargs):
return self.session.get(*args, **kwargs)
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
def _get(*_args, **_kwargs):
return self.session.get(*_args, **_kwargs)
return _get(*args, **kwargs)
def get_without_backoff(self, *args, **kwargs):
def post(self, *args, **kwargs) -> Response:
with ipv6(kwargs.pop('ipv6', False)):
def _get(*args, **kwargs):
return self.session.get(*args, **kwargs)
return _get(*args, **kwargs)
def post(self, *args, **kwargs):
_expected_status_code = kwargs.pop('expected_status_code', None)
with ipv6(kwargs.pop('ipv6', False)):
@backoff.on_exception(backoff.constant, requests.exceptions.SSLError, interval=.3, max_tries=30, jitter=None)
@backoff.on_predicate(backoff.constant, self.__backoff_predicate(_expected_status_code), interval=.3, max_tries=30, jitter=None)
def _post(*args, **kwargs):
return self.session.post(*args, **kwargs)
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
def _post(*_args, **_kwargs):
return self.session.post(*_args, **_kwargs)
return _post(*args, **kwargs)
def put(self, *args, **kwargs):
_expected_status_code = kwargs.pop('expected_status_code', None)
def put(self, *args, **kwargs) -> Response:
with ipv6(kwargs.pop('ipv6', False)):
@backoff.on_exception(backoff.constant, requests.exceptions.SSLError, interval=.3, max_tries=30, jitter=None)
@backoff.on_predicate(backoff.constant, self.__backoff_predicate(_expected_status_code), interval=.3, max_tries=30, jitter=None)
def _put(*args, **kwargs):
return self.session.put(*args, **kwargs)
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
def _put(*_args, **_kwargs):
return self.session.put(*_args, **_kwargs)
return _put(*args, **kwargs)
def head(self, *args, **kwargs):
_expected_status_code = kwargs.pop('expected_status_code', None)
def head(self, *args, **kwargs) -> Response:
with ipv6(kwargs.pop('ipv6', False)):
@backoff.on_exception(backoff.constant, requests.exceptions.SSLError, interval=.3, max_tries=30, jitter=None)
@backoff.on_predicate(backoff.constant, self.__backoff_predicate(_expected_status_code), interval=.3, max_tries=30, jitter=None)
def _head(*args, **kwargs):
return self.session.head(*args, **kwargs)
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
def _head(*_args, **_kwargs):
return self.session.head(*_args, **_kwargs)
return _head(*args, **kwargs)
def delete(self, *args, **kwargs):
_expected_status_code = kwargs.pop('expected_status_code', None)
def delete(self, *args, **kwargs) -> Response:
with ipv6(kwargs.pop('ipv6', False)):
@backoff.on_exception(backoff.constant, requests.exceptions.SSLError, interval=.3, max_tries=30, jitter=None)
@backoff.on_predicate(backoff.constant, self.__backoff_predicate(_expected_status_code), interval=.3, max_tries=30, jitter=None)
def _delete(*args, **kwargs):
return self.session.delete(*args, **kwargs)
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
def _delete(*_args, **_kwargs):
return self.session.delete(*_args, **_kwargs)
return _delete(*args, **kwargs)
def options(self, *args, **kwargs):
_expected_status_code = kwargs.pop('expected_status_code', None)
def options(self, *args, **kwargs) -> Response:
with ipv6(kwargs.pop('ipv6', False)):
@backoff.on_exception(backoff.constant, requests.exceptions.SSLError, interval=.3, max_tries=30, jitter=None)
@backoff.on_predicate(backoff.constant, self.__backoff_predicate(_expected_status_code), interval=.3, max_tries=30, jitter=None)
def _options(*args, **kwargs):
return self.session.options(*args, **kwargs)
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
def _options(*_args, **_kwargs):
return self.session.options(*_args, **_kwargs)
return _options(*args, **kwargs)
def __getattr__(self, name):
return getattr(requests, name)
def container_ip(container: Container):
def container_ip(container: Container) -> str:
"""
return the IP address of a container.
@ -203,7 +179,7 @@ def container_ip(container: Container):
return net_info[network_name]["IPAddress"]
def container_ipv6(container):
def container_ipv6(container: Container) -> str:
"""
return the IPv6 address of a container.
"""
@ -220,7 +196,7 @@ def container_ipv6(container):
return net_info[network_name]["GlobalIPv6Address"]
def nginx_proxy_single_container_dns_resolver(domain_name):
def nginx_proxy_dns_resolver(domain_name: str) -> Optional[str]:
"""
if "nginx-proxy" if found in host, return the ip address of the docker container
issued from the docker image nginxproxy/nginx-proxy:test.
@ -228,48 +204,25 @@ def nginx_proxy_single_container_dns_resolver(domain_name):
:return: IP or None
"""
log = logging.getLogger('DNS')
log.debug(f"nginx_proxy_single_container_dns_resolver({domain_name!r})")
log.debug(f"nginx_proxy_dns_resolver({domain_name!r})")
if 'nginx-proxy' in domain_name:
nginxproxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginxproxy/nginx-proxy:test"})
if len(nginxproxy_containers) == 0:
log.info(f"no container found from image nginxproxy/nginx-proxy:test while resolving {domain_name!r}")
log.warning(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"})
if len(exited_nginxproxy_containers) > 0:
exited_nginxproxy_container_logs = exited_nginxproxy_containers[0].logs()
log.warning(f"nginxproxy/nginx-proxy:test container might have exited unexpectedly. Container logs: " + "\n" + exited_nginxproxy_container_logs.decode())
return
return None
nginxproxy_container = nginxproxy_containers[0]
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}")
return ip
def nginx_proxy_separate_containers_dns_resolver(domain_name):
def docker_container_dns_resolver(domain_name: str) -> Optional[str]:
"""
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):
"""
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.
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
"""
@ -279,7 +232,7 @@ def docker_container_dns_resolver(domain_name):
match = re.search(r'(^|.+\.)(?P<container>[^.]+)\.container\.docker$', domain_name)
if not match:
log.debug(f"{domain_name!r} does not match")
return
return None
container_name = match.group('container')
log.debug(f"looking for container {container_name!r}")
@ -287,7 +240,7 @@ def docker_container_dns_resolver(domain_name):
container = docker_client.containers.get(container_name)
except docker.errors.NotFound:
log.warning(f"container named {container_name!r} not found while resolving {domain_name!r}")
return
return None
log.debug(f"container {container.name!r} found ({container.short_id})")
ip = container_ip(container)
@ -299,8 +252,10 @@ def monkey_patch_urllib_dns_resolver():
"""
Alter the behavior of the urllib DNS resolver so that any domain name
containing substring 'nginx-proxy' will resolve to the IP address
of the container created from image 'nginxproxy/nginx-proxy:test' or
labeled with 'com.github.nginx-proxy.nginx-proxy.nginx'.
of the container created from image 'nginxproxy/nginx-proxy:test',
or to 127.0.0.1 on Darwin.
see https://docs.docker.com/desktop/features/networking/#i-want-to-connect-to-a-container-from-the-host
"""
prv_getaddrinfo = socket.getaddrinfo
dns_cache = {}
@ -308,19 +263,18 @@ def monkey_patch_urllib_dns_resolver():
logging.getLogger('DNS').debug(f"resolving domain name {repr(args)}")
_args = list(args)
# Fail early when querying IP directly and it is forced ipv6 when not supported,
# Fail early when querying IP directly, and it is forced ipv6 when not supported,
# Otherwise a pytest container not using the host network fails to pass `test_raw-ip-vhost`.
if FORCE_CONTAINER_IPV6 and not HAS_IPV6:
pytest.skip("This system does not support IPv6")
# custom DNS resolvers
ip = None
# Docker Desktop can't route traffic directly to Linux containers.
if platform.system() == "Darwin":
ip = "127.0.0.1"
if ip is None:
ip = nginx_proxy_single_container_dns_resolver(args[0])
if ip is None:
ip = nginx_proxy_separate_containers_dns_resolver(args[0])
ip = nginx_proxy_dns_resolver(args[0])
if ip is None:
ip = docker_container_dns_resolver(args[0])
if ip is not None:
@ -336,11 +290,12 @@ def monkey_patch_urllib_dns_resolver():
socket.getaddrinfo = new_getaddrinfo
return prv_getaddrinfo
def restore_urllib_dns_resolver(getaddrinfo_func):
socket.getaddrinfo = getaddrinfo_func
def get_nginx_conf_from_container(container):
def get_nginx_conf_from_container(container: Container) -> bytes:
"""
return the nginx /etc/nginx/conf.d/default.conf file content from a container
"""
@ -355,84 +310,102 @@ def get_nginx_conf_from_container(container):
return conffile.read()
def docker_compose_up(project_name, compose_file='docker-compose.yml'):
composeCmd = f'{DOCKER_COMPOSE} --project-name {project_name} --file {compose_file} up --remove-orphans --force-recreate --detach'
logging.info(composeCmd)
def __prepare_and_execute_compose_cmd(compose_files: List[str], project_name: str, cmd: str):
"""
Prepare and execute the Docker Compose command with the provided compose files and project name.
"""
compose_cmd = StringIO()
compose_cmd.write(DOCKER_COMPOSE)
compose_cmd.write(f" --project-name {project_name}")
for compose_file in compose_files:
compose_cmd.write(f" --file {compose_file}")
compose_cmd.write(f" {cmd}")
logging.info(compose_cmd.getvalue())
try:
subprocess.check_output(shlex.split(composeCmd), stderr=subprocess.STDOUT)
subprocess.check_output(shlex.split(compose_cmd.getvalue()), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
pytest.fail(f"Error while runninng '{composeCmd}:\n{e.output}", pytrace=False)
pytest.fail(f"Error while running '{compose_cmd.getvalue()}':\n{e.output}", pytrace=False)
def docker_compose_down(project_name, compose_file='docker-compose.yml'):
composeCmd = f'{DOCKER_COMPOSE} --project-name {project_name} --file {compose_file} down --remove-orphans --volumes'
logging.info(composeCmd)
def docker_compose_up(compose_files: List[str], project_name: str):
"""
Execute compose up --detach with the provided compose files and project name.
"""
if compose_files is None or len(compose_files) == 0:
pytest.fail(f"No compose file passed to docker_compose_up", pytrace=False)
__prepare_and_execute_compose_cmd(compose_files, project_name, cmd="up --detach")
try:
subprocess.check_output(shlex.split(composeCmd), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
pytest.fail(f"Error while runninng '{composeCmd}':\n{e.output}", pytrace=False)
def docker_compose_down(compose_files: List[str], project_name: str):
"""
Execute compose down --volumes with the provided compose files and project name.
"""
if compose_files is None or len(compose_files) == 0:
pytest.fail(f"No compose file passed to docker_compose_up", pytrace=False)
__prepare_and_execute_compose_cmd(compose_files, project_name, cmd="down --volumes")
def wait_for_nginxproxy_to_be_ready():
"""
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"
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"
"""
timeout = time.time() + 10
while True:
containers = docker_client.containers.list(
filters={"status": "running", "ancestor": f"nginxproxy/nginx-proxy:{IMAGE_TAG}"}
)
if len(containers) == 1:
break
if time.time() > timeout:
pytest.fail(f"Got {len(containers)} nginxproxy/nginx-proxy:{IMAGE_TAG} containers after 10s", pytrace=False)
time.sleep(1)
container = containers
conf_generated = False
while True:
for line in container[0].logs(stream=True, follow=True):
if b"Generated '/etc/nginx/conf.d/default.conf'" in line:
containers = docker_client.containers.list(filters={"ancestor": "nginxproxy/nginx-proxy:test"})
if len(containers) != 1:
return
if time.time() > timeout:
pytest.fail(f"nginxproxy/nginx-proxy:{IMAGE_TAG} container not ready after 10s", pytrace=False)
time.sleep(1)
container = containers[0]
for line in container.logs(stream=True):
if b"Watching docker events" in line:
logging.debug("nginx-proxy ready")
break
@pytest.fixture
def docker_compose_file(request):
"""Fixture naming the docker compose file to consider.
def docker_compose_files(request: FixtureRequest) -> List[str]:
"""Fixture returning the docker compose files to consider:
If a YAML file exists with the same name as the test module (with the `.py` extension replaced
with `.yml` or `.yaml`), use that. Otherwise, use `docker-compose.yml` in the same directory
as the test module.
If a YAML file exists with the same name as the test module (with the `.py` extension
replaced with `.base.yml`, ie `test_foo.py`-> `test_foo.base.yml`) and in the same
directory as the test module, use only that file.
Otherwise, merge the following files in this order:
- the `compose.base.yml` file in the parent `test` directory.
- if present in the same directory as the test module, the `compose.base.override.yml` file.
- the YAML file named after the current test module (ie `test_foo.py`-> `test_foo.yml`)
Tests can override this fixture to specify a custom location.
"""
test_module_dir = os.path.dirname(request.module.__file__)
yml_file = os.path.join(test_module_dir, request.module.__name__ + '.yml')
yaml_file = os.path.join(test_module_dir, request.module.__name__ + '.yaml')
default_file = os.path.join(test_module_dir, 'docker-compose.yml')
compose_files: List[str] = []
test_module_path = pathlib.Path(request.module.__file__).parent
if os.path.isfile(yml_file):
docker_compose_file = yml_file
elif os.path.isfile(yaml_file):
docker_compose_file = yaml_file
else:
docker_compose_file = default_file
module_base_file = test_module_path.joinpath(f"{request.module.__name__}.base.yml")
if module_base_file.is_file():
return [module_base_file.as_posix()]
if not os.path.isfile(docker_compose_file):
logging.error("Could not find any docker compose file named either '{0}.yml', '{0}.yaml' or 'docker-compose.yml'".format(request.module.__name__))
global_base_file = test_module_path.parent.joinpath("compose.base.yml")
if global_base_file.is_file():
compose_files.append(global_base_file.as_posix())
logging.info(f"using docker compose file {docker_compose_file}")
yield docker_compose_file
module_base_override_file = test_module_path.joinpath("compose.base.override.yml")
if module_base_override_file.is_file():
compose_files.append(module_base_override_file.as_posix())
module_compose_file = test_module_path.joinpath(f"{request.module.__name__}.yml")
if module_compose_file.is_file():
compose_files.append(module_compose_file.as_posix())
if not module_base_file.is_file() and not module_compose_file.is_file():
logging.error(
f"Could not find any docker compose file named '{module_base_file.name}' or '{module_compose_file.name}'"
)
logging.debug(f"using docker compose files {compose_files}")
return compose_files
def connect_to_network(network):
def connect_to_network(network: Network) -> Optional[Network]:
"""
If we are running from a container, connect our container to the given network
@ -443,7 +416,7 @@ def connect_to_network(network):
my_container = docker_client.containers.get(test_container)
except docker.errors.NotFound:
logging.warning(f"container {test_container} not found")
return
return None
# figure out our container networks
my_networks = list(my_container.attrs["NetworkSettings"]["Networks"].keys())
@ -460,7 +433,7 @@ def connect_to_network(network):
return network
def disconnect_from_network(network=None):
def disconnect_from_network(network: Network = None):
"""
If we are running from a container, disconnect our container from the given network.
@ -482,7 +455,7 @@ def disconnect_from_network(network=None):
network.disconnect(my_container)
def connect_to_all_networks():
def connect_to_all_networks() -> List[Network]:
"""
If we are running from a container, connect our container to all current docker networks.
@ -499,30 +472,32 @@ def connect_to_all_networks():
class DockerComposer(contextlib.AbstractContextManager):
def __init__(self):
self._networks = None
self._docker_compose_file = None
self._docker_compose_files = None
self._project_name = None
def __exit__(self, *exc_info):
self._down()
def _down(self):
if self._docker_compose_file is None:
if self._docker_compose_files is None:
return
for network in self._networks:
disconnect_from_network(network)
docker_compose_down(self._project_name, self._docker_compose_file)
docker_compose_down(self._docker_compose_files, self._project_name)
self._docker_compose_file = None
self._project_name = None
def compose(self, project_name, docker_compose_file):
if docker_compose_file == self._docker_compose_file and project_name == self._project_name:
def compose(self, docker_compose_files: List[str], project_name: str):
if docker_compose_files == self._docker_compose_files and project_name == self._project_name:
return
self._down()
if docker_compose_file is None:
if docker_compose_files is None or project_name is None:
return
docker_compose_up(project_name, docker_compose_file)
docker_compose_up(docker_compose_files, project_name)
self._networks = connect_to_all_networks()
wait_for_nginxproxy_to_be_ready()
self._docker_compose_file = docker_compose_file
time.sleep(3) # give time to containers to be ready
self._docker_compose_files = docker_compose_files
self._project_name = project_name
@ -534,14 +509,14 @@ class DockerComposer(contextlib.AbstractContextManager):
@pytest.fixture(scope="module")
def docker_composer():
def docker_composer() -> Iterator[DockerComposer]:
with DockerComposer() as d:
yield d
@pytest.fixture
def ca_root_certificate():
yield CA_ROOT_CERTIFICATE
def ca_root_certificate() -> str:
return CA_ROOT_CERTIFICATE.as_posix()
@pytest.fixture
@ -552,26 +527,38 @@ def monkey_patched_dns():
@pytest.fixture
def docker_compose(request, monkey_patched_dns, docker_composer, docker_compose_file):
"""Ensures containers described in a docker compose file are started.
A custom docker compose file name can be specified by overriding the `docker_compose_file`
fixture.
Also, in the case where pytest is running from a docker container, this fixture makes sure
our container will be attached to all the docker networks.
def docker_compose(
request: FixtureRequest,
monkeypatch,
monkey_patched_dns,
docker_composer,
docker_compose_files
) -> Iterator[DockerClient]:
"""
Ensures containers necessary for the test module are started in a compose project,
and set the environment variable `PYTEST_MODULE_PATH` to the test module's parent folder.
A list of custom docker compose files path can be specified by overriding
the `docker_compose_file` fixture.
Also, in the case where pytest is running from a docker container, this fixture
makes sure our container will be attached to all the docker networks.
"""
pytest_module_path = pathlib.Path(request.module.__file__).parent
monkeypatch.setenv("PYTEST_MODULE_PATH", pytest_module_path.as_posix())
project_name = request.module.__name__
docker_composer.compose(project_name, docker_compose_file)
docker_composer.compose(docker_compose_files, project_name)
yield docker_client
@pytest.fixture()
def nginxproxy():
@pytest.fixture
def nginxproxy() -> Iterator[RequestsForDocker]:
"""
Provides the `nginxproxy` object that can be used in the same way the requests module is:
r = nginxproxy.get("http://foo.com")
r = nginxproxy.get("https://foo.com")
The difference is that in case an HTTP requests has status code 404 or 502 (which mostly
indicates that nginx has just reloaded), we retry up to 30 times the query.
@ -580,15 +567,15 @@ def nginxproxy():
made against containers to use the containers IPv6 address when set to `True`. If IPv6 is not
supported by the system or docker, that particular test will be skipped.
"""
yield requests_for_docker()
yield RequestsForDocker()
@pytest.fixture()
def acme_challenge_path():
@pytest.fixture
def acme_challenge_path() -> str:
"""
Provides fake Let's Encrypt ACME challenge path used in certain tests
"""
yield ".well-known/acme-challenge/test-filename"
return ".well-known/acme-challenge/test-filename"
###############################################################################
#
@ -596,14 +583,10 @@ def acme_challenge_path():
#
###############################################################################
# pytest hook to display additionnal stuff in test report
# pytest hook to display additional stuff in test report
def pytest_runtest_logreport(report):
if report.failed:
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().decode())
test_containers = docker_client.containers.list(all=True, filters={"ancestor": f"nginxproxy/nginx-proxy:{IMAGE_TAG}"})
test_containers = docker_client.containers.list(all=True, filters={"ancestor": "nginxproxy/nginx-proxy:test"})
for container in test_containers:
report.longrepr.addsection('nginx-proxy logs', container.logs().decode())
report.longrepr.addsection('nginx-proxy conf', get_nginx_conf_from_container(container).decode())
@ -629,9 +612,9 @@ def pytest_runtest_setup(item):
###############################################################################
try:
docker_client.images.get(f"nginxproxy/nginx-proxy:{IMAGE_TAG}")
docker_client.images.get('nginxproxy/nginx-proxy:test')
except docker.errors.ImageNotFound:
pytest.exit(f"The docker image 'nginxproxy/nginx-proxy:{IMAGE_TAG}' is missing")
pytest.exit("The docker image 'nginxproxy/nginx-proxy:test' is missing")
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")

View file

@ -3,7 +3,7 @@
# #
# This script is meant to run the test suite from a Docker container. #
# #
# This is usefull when you want to run the test suite from Mac or #
# This is useful when you want to run the test suite from Mac or #
# Docker Toolbox. #
# #
###############################################################################

View file

@ -1,5 +1,6 @@
backoff==2.2.1
docker==7.1.0
packaging==24.2
pytest==8.3.4
requests==2.32.3
packaging==24.2
urllib3==2.3.0

View file

@ -28,7 +28,7 @@ class Handler(http.server.SimpleHTTPRequestHandler):
self.send_header("Content-Type", "text/plain")
self.end_headers()
if (len(response_body)):
if len(response_body):
self.wfile.write(response_body.encode())
if __name__ == '__main__':

View file

@ -1 +0,0 @@
This directory contains tests that showcase scenarios known to break the expected behavior of nginx-proxy.

View file

@ -1,63 +0,0 @@
networks:
netA:
netB:
volumes:
nginx_conf:
services:
reverseproxy:
profiles:
- singleContainer
container_name: reverseproxy
networks:
- netA
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
docker-gen:
profiles:
- separateContainers
networks:
- netA
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
environment:
NGINX_CONTAINER_NAME: reverseproxy
reverseproxynginx:
profiles:
- separateContainers
container_name: reverseproxy
networks:
- netA
image: nginx:alpine
volumes:
- *confVolume
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
webA:
networks:
- netA
image: web
expose:
- 81
environment:
WEB_PORTS: 81
VIRTUAL_HOST: webA.nginx-proxy
webB:
networks:
- netB
image: web
expose:
- 82
environment:
WEB_PORTS: 82
VIRTUAL_HOST: webB.nginx-proxy

View file

@ -0,0 +1,6 @@
services:
nginx-proxy:
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/certs:/etc/nginx/certs:ro
- ${PYTEST_MODULE_PATH}/acme_root:/usr/share/nginx/html:ro

View file

@ -1,8 +1,7 @@
def test_redirect_acme_challenge_location_disabled(docker_compose, nginxproxy, acme_challenge_path):
r = nginxproxy.get(
f"http://web1.nginx-proxy.tld/{acme_challenge_path}",
allow_redirects=False,
expected_status_code=301
allow_redirects=False
)
assert r.status_code == 301
@ -16,8 +15,7 @@ def test_redirect_acme_challenge_location_enabled(docker_compose, nginxproxy, ac
def test_noredirect_acme_challenge_location_disabled(docker_compose, nginxproxy, acme_challenge_path):
r = nginxproxy.get(
f"http://web3.nginx-proxy.tld/{acme_challenge_path}",
allow_redirects=False,
expected_status_code=404
allow_redirects=False
)
assert r.status_code == 404

View file

@ -0,0 +1,40 @@
services:
nginx-proxy:
environment:
ACME_HTTP_CHALLENGE_LOCATION: "false"
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: "web1.nginx-proxy.tld"
web2:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: "web2.nginx-proxy.tld"
ACME_HTTP_CHALLENGE_LOCATION: "true"
web3:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: "web3.nginx-proxy.tld"
HTTPS_METHOD: noredirect
web4:
image: web
expose:
- "84"
environment:
WEB_PORTS: "84"
VIRTUAL_HOST: "web4.nginx-proxy.tld"
HTTPS_METHOD: noredirect
ACME_HTTP_CHALLENGE_LOCATION: "true"

View file

@ -8,8 +8,7 @@ def test_redirect_acme_challenge_location_enabled(docker_compose, nginxproxy, ac
def test_redirect_acme_challenge_location_disabled(docker_compose, nginxproxy, acme_challenge_path):
r = nginxproxy.get(
f"http://web2.nginx-proxy.tld/{acme_challenge_path}",
allow_redirects=False,
expected_status_code=301
allow_redirects=False
)
assert r.status_code == 301
@ -23,7 +22,6 @@ def test_noredirect_acme_challenge_location_enabled(docker_compose, nginxproxy,
def test_noredirect_acme_challenge_location_disabled(docker_compose, nginxproxy, acme_challenge_path):
r = nginxproxy.get(
f"http://web4.nginx-proxy.tld/{acme_challenge_path}",
allow_redirects=False,
expected_status_code=404
allow_redirects=False
)
assert r.status_code == 404

View file

@ -0,0 +1,36 @@
services:
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: "web1.nginx-proxy.tld"
web2:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: "web2.nginx-proxy.tld"
ACME_HTTP_CHALLENGE_LOCATION: "false"
web3:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: "web3.nginx-proxy.tld"
HTTPS_METHOD: noredirect
web4:
image: web
expose:
- "84"
environment:
WEB_PORTS: "84"
VIRTUAL_HOST: "web4.nginx-proxy.tld"
HTTPS_METHOD: noredirect
ACME_HTTP_CHALLENGE_LOCATION: "false"

View file

@ -8,7 +8,6 @@ def test_redirect_acme_challenge_location_legacy(docker_compose, nginxproxy, acm
def test_noredirect_acme_challenge_location_legacy(docker_compose, nginxproxy, acme_challenge_path):
r = nginxproxy.get(
f"http://web2.nginx-proxy.tld/{acme_challenge_path}",
allow_redirects=False,
expected_status_code=404
allow_redirects=False
)
assert r.status_code == 404

View file

@ -0,0 +1,21 @@
services:
nginx-proxy:
environment:
ACME_HTTP_CHALLENGE_LOCATION: "legacy"
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: "web1.nginx-proxy.tld"
web2:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: "web2.nginx-proxy.tld"
HTTPS_METHOD: noredirect

View file

@ -1,73 +0,0 @@
volumes:
nginx_conf:
services:
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: "web1.nginx-proxy.tld"
web2:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: "web2.nginx-proxy.tld"
ACME_HTTP_CHALLENGE_LOCATION: "true"
web3:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: "web3.nginx-proxy.tld"
HTTPS_METHOD: noredirect
web4:
image: web
expose:
- "84"
environment:
WEB_PORTS: "84"
VIRTUAL_HOST: "web4.nginx-proxy.tld"
HTTPS_METHOD: noredirect
ACME_HTTP_CHALLENGE_LOCATION: "true"
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
environment: &nginxProxyEnv
ACME_HTTP_CHALLENGE_LOCATION: "false"
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &certs ./certs:/etc/nginx/certs:ro
- &acmeRoot ./acme_root:/usr/share/nginx/html:ro
sutdockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
environment: *nginxProxyEnv
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *certs
sutnginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *certs
- *acmeRoot
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"

View file

@ -1,70 +0,0 @@
volumes:
nginx_conf:
services:
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: "web1.nginx-proxy.tld"
web2:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: "web2.nginx-proxy.tld"
ACME_HTTP_CHALLENGE_LOCATION: "false"
web3:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: "web3.nginx-proxy.tld"
HTTPS_METHOD: noredirect
web4:
image: web
expose:
- "84"
environment:
WEB_PORTS: "84"
VIRTUAL_HOST: "web4.nginx-proxy.tld"
HTTPS_METHOD: noredirect
ACME_HTTP_CHALLENGE_LOCATION: "false"
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &certs ./certs:/etc/nginx/certs:ro
- &acmeRoot ./acme_root:/usr/share/nginx/html:ro
sutdockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *certs
sutnginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *certs
- *acmeRoot
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"

View file

@ -1,54 +0,0 @@
volumes:
nginx_conf:
services:
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: "web1.nginx-proxy.tld"
web2:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: "web2.nginx-proxy.tld"
HTTPS_METHOD: noredirect
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
environment: &nginxProxyEnv
ACME_HTTP_CHALLENGE_LOCATION: "legacy"
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &certs ./certs:/etc/nginx/certs:ro
- &acmeRoot ./acme_root:/usr/share/nginx/html:ro
sutdockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
environment: *nginxProxyEnv
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *certs
sutnginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *certs
- *acmeRoot
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"

View file

@ -1,22 +1,25 @@
"""
Test that nginx-proxy-tester can build successfully
"""
import pytest
import docker
import pathlib
import re
import os
import docker
import pytest
client = docker.from_env()
@pytest.fixture(scope = "session")
def docker_build(request):
# Define Dockerfile path
dockerfile_path = os.path.join(os.path.dirname(__file__), "requirements/")
current_file_path = pathlib.Path(__file__)
dockerfile_path = current_file_path.parent.parent.joinpath("requirements")
dockerfile_name = "Dockerfile-nginx-proxy-tester"
# Build the Docker image
image, logs = client.images.build(
path = dockerfile_path,
path = dockerfile_path.as_posix(),
dockerfile = dockerfile_name,
rm = True, # Remove intermediate containers
tag = "nginx-proxy-tester-ci", # Tag for the built image

View file

@ -2,6 +2,6 @@ import re
def test_custom_error_page(docker_compose, nginxproxy):
r = nginxproxy.get("http://unknown.nginx-proxy.tld", expected_status_code=503)
r = nginxproxy.get("http://unknown.nginx-proxy.tld")
assert r.status_code == 503
assert re.search(r"Damn, there's some maintenance in progress.", r.text)

View file

@ -1,32 +1,5 @@
volumes:
nginx_conf:
services:
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
nginx-proxy:
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &customErrorPage ./50x.html:/usr/share/nginx/html/errors/50x.html:ro
sut-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *customErrorPage
sut-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *customErrorPage
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/50x.html:/usr/share/nginx/html/errors/50x.html:ro

View file

@ -1,3 +1,8 @@
def test_custom_default_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/")
assert r.status_code == 503
assert "X-test" not in r.headers
def test_custom_default_conf_applies_to_web1(docker_compose, nginxproxy):
r = nginxproxy.get("http://web1.nginx-proxy.example/port")
assert r.status_code == 200
@ -12,14 +17,10 @@ def test_custom_default_conf_applies_to_web2(docker_compose, nginxproxy):
assert "X-test" in r.headers
assert "f00" == r.headers["X-test"]
def test_custom_default_conf_is_overriden_for_web3(docker_compose, nginxproxy):
r = nginxproxy.get("http://web3.nginx-proxy.example/port")
assert r.status_code == 200
assert r.text == "answer from port 83\n"
assert "X-test" in r.headers
assert "bar" == r.headers["X-test"]
def test_custom_default_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/", expected_status_code=503)
assert r.status_code == 503
assert "X-test" not in r.headers

View file

@ -1,45 +1,16 @@
volumes:
nginx_conf:
services:
nginx-proxy:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &defaultLocation ./my_custom_proxy_settings_f00.conf:/etc/nginx/vhost.d/default_location:ro
- &vhostLocation ./my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/web3.nginx-proxy.example_location:ro
nginx-proxy-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *defaultLocation
- *vhostLocation
nginx-proxy-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *defaultLocation
- *vhostLocation
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_f00.conf:/etc/nginx/vhost.d/default_location:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/web3.nginx-proxy.example_location:ro
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: 81
WEB_PORTS: "81"
VIRTUAL_HOST: web1.nginx-proxy.example
web2:
@ -47,7 +18,7 @@ services:
expose:
- "82"
environment:
WEB_PORTS: 82
WEB_PORTS: "82"
VIRTUAL_HOST: web2.nginx-proxy.example
web3:
@ -55,5 +26,5 @@ services:
expose:
- "83"
environment:
WEB_PORTS: 83
WEB_PORTS: "83"
VIRTUAL_HOST: web3.nginx-proxy.example

View file

@ -1,3 +1,8 @@
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/")
assert r.status_code == 503
assert "X-test" not in r.headers
def test_custom_conf_applies_to_web1(docker_compose, nginxproxy):
r = nginxproxy.get("http://web1.nginx-proxy.example/port")
assert r.status_code == 200
@ -11,8 +16,3 @@ def test_custom_conf_applies_to_web2(docker_compose, nginxproxy):
assert r.text == "answer from port 82\n"
assert "X-test" in r.headers
assert "f00" == r.headers["X-test"]
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/", expected_status_code=503)
assert r.status_code == 503
assert "X-test" not in r.headers

View file

@ -1,42 +1,15 @@
volumes:
nginx_conf:
services:
nginx-proxy:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &defaultConf ./my_custom_proxy_settings_f00.conf:/etc/nginx/proxy.conf:ro
nginx-proxy-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *defaultConf
nginx-proxy-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *defaultConf
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_f00.conf:/etc/nginx/proxy.conf:ro
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: 81
WEB_PORTS: "81"
VIRTUAL_HOST: web1.nginx-proxy.example
web2:
@ -44,5 +17,5 @@ services:
expose:
- "82"
environment:
WEB_PORTS: 82
WEB_PORTS: "82"
VIRTUAL_HOST: web2.nginx-proxy.example

View file

@ -1,3 +1,8 @@
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/")
assert r.status_code == 503
assert "X-test" not in r.headers
def test_custom_conf_applies_to_web1(docker_compose, nginxproxy):
r = nginxproxy.get("http://web1.nginx-proxy.example/port")
assert r.status_code == 200
@ -18,10 +23,5 @@ def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy):
assert r.text == "answer from port 82\n"
assert "X-test" not in r.headers
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/", expected_status_code=503)
assert r.status_code == 503
assert "X-test" not in r.headers
def test_custom_block_is_present_in_nginx_generated_conf(docker_compose, nginxproxy):
assert b"include /etc/nginx/vhost.d/web1.nginx-proxy.example_location;" in nginxproxy.get_conf()

View file

@ -1,45 +1,16 @@
volumes:
nginx_conf:
services:
nginx-proxy:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &vhostLocationConf ./my_custom_proxy_settings_f00.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example_location:ro
- &regexLocationConf ./my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/561032515ede3ab3a015edfb244608b72409c430_location:ro
nginx-proxy-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *vhostLocationConf
- *regexLocationConf
nginx-proxy-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *vhostLocationConf
- *regexLocationConf
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_f00.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example_location:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/561032515ede3ab3a015edfb244608b72409c430_location:ro
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: 81
WEB_PORTS: "81"
VIRTUAL_HOST: web1.nginx-proxy.example
web2:
@ -47,7 +18,7 @@ services:
expose:
- "82"
environment:
WEB_PORTS: 82
WEB_PORTS: "82"
VIRTUAL_HOST: web2.nginx-proxy.example
regex:
@ -55,5 +26,5 @@ services:
expose:
- "83"
environment:
WEB_PORTS: 83
WEB_PORTS: "83"
VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$

View file

@ -1,3 +1,8 @@
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/")
assert r.status_code == 503
assert "X-test" not in r.headers
def test_custom_conf_applies_to_web1(docker_compose, nginxproxy):
r = nginxproxy.get("http://web1.nginx-proxy.example/port")
assert r.status_code == 200
@ -17,8 +22,3 @@ def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy):
assert r.status_code == 200
assert r.text == "answer from port 82\n"
assert "X-test" not in r.headers
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/", expected_status_code=503)
assert r.status_code == 503
assert "X-test" not in r.headers

View file

@ -1,45 +1,16 @@
volumes:
nginx_conf:
services:
nginx-proxy:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &vhostConf ./my_custom_proxy_settings_f00.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example:ro
- &regexConf ./my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/561032515ede3ab3a015edfb244608b72409c430:ro
nginx-proxy-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *vhostConf
- *regexConf
nginx-proxy-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *vhostConf
- *regexConf
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_f00.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/561032515ede3ab3a015edfb244608b72409c430:ro
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: 81
WEB_PORTS: "81"
VIRTUAL_HOST: web1.nginx-proxy.example
web2:
@ -47,7 +18,7 @@ services:
expose:
- "82"
environment:
WEB_PORTS: 82
WEB_PORTS: "82"
VIRTUAL_HOST: web2.nginx-proxy.example
regex:
@ -55,5 +26,5 @@ services:
expose:
- "83"
environment:
WEB_PORTS: 83
WEB_PORTS: "83"
VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$

View file

@ -1,3 +1,8 @@
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/")
assert r.status_code == 503
assert "X-test" not in r.headers
def test_custom_conf_applies_to_web1(docker_compose, nginxproxy):
r = nginxproxy.get("http://web1.nginx-proxy.example/port")
assert r.status_code == 200
@ -11,8 +16,3 @@ def test_custom_conf_applies_to_web2(docker_compose, nginxproxy):
assert r.text == "answer from port 82\n"
assert "X-test" in r.headers
assert "f00" == r.headers["X-test"]
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/", expected_status_code=503)
assert r.status_code == 503
assert "X-test" not in r.headers

View file

@ -1,42 +1,15 @@
volumes:
nginx_conf:
services:
nginx-proxy:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &proxyConf ./my_custom_proxy_settings_f00.conf:/etc/nginx/conf.d/my_custom_proxy_settings_f00.conf:ro
nginx-proxy-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *proxyConf
nginx-proxy-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *proxyConf
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_f00.conf:/etc/nginx/conf.d/my_custom_proxy_settings_f00.conf:ro
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: 81
WEB_PORTS: "81"
VIRTUAL_HOST: web1.nginx-proxy.example
web2:
@ -44,5 +17,5 @@ services:
expose:
- "82"
environment:
WEB_PORTS: 82
WEB_PORTS: "82"
VIRTUAL_HOST: web2.nginx-proxy.example

View file

@ -1,6 +1,8 @@
import json
import pytest
def test_debug_endpoint_is_enabled_globally(docker_compose, nginxproxy):
r = nginxproxy.get("http://enabled.debug.nginx-proxy.example/nginx-proxy-debug")
assert r.status_code == 200
@ -42,5 +44,5 @@ def test_debug_endpoint_hostname_replaced_by_warning_if_regexp(docker_compose, n
def test_debug_endpoint_is_disabled_per_container(docker_compose, nginxproxy):
r = nginxproxy.get("http://disabled.debug.nginx-proxy.example/nginx-proxy-debug", expected_status_code=404)
r = nginxproxy.get("http://disabled.debug.nginx-proxy.example/nginx-proxy-debug")
assert r.status_code == 404

View file

@ -1,42 +1,14 @@
volumes:
nginx_conf:
services:
nginx-proxy:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
environment: &nginxProxyEnv
environment:
DEBUG_ENDPOINT: "true"
nginx-proxy-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
environment: *nginxProxyEnv
nginx-proxy-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
debug_enabled:
image: web
expose:
- "81"
environment:
WEB_PORTS: 81
WEB_PORTS: "81"
VIRTUAL_HOST: enabled.debug.nginx-proxy.example
debug_stripped:
@ -44,7 +16,7 @@ services:
expose:
- "82"
environment:
WEB_PORTS: 82
WEB_PORTS: "82"
VIRTUAL_HOST_MULTIPORTS: |-
stripped.debug.nginx-proxy.example:
"/1":
@ -73,7 +45,7 @@ services:
expose:
- "84"
environment:
WEB_PORTS: 84
WEB_PORTS: "84"
VIRTUAL_HOST: ~^regexp.*\.debug.nginx-proxy.example
debug_disabled:
@ -81,7 +53,7 @@ services:
expose:
- "83"
environment:
WEB_PORTS: 83
WEB_PORTS: "83"
VIRTUAL_HOST: disabled.debug.nginx-proxy.example
labels:
com.github.nginx-proxy.nginx-proxy.debug-endpoint: "false"

View file

@ -1,10 +1,12 @@
import json
import pytest
def test_debug_endpoint_is_disabled_globally(docker_compose, nginxproxy):
r = nginxproxy.get("http://disabled1.debug.nginx-proxy.example/nginx-proxy-debug", expected_status_code=404)
r = nginxproxy.get("http://disabled1.debug.nginx-proxy.example/nginx-proxy-debug")
assert r.status_code == 404
r = nginxproxy.get("http://disabled2.debug.nginx-proxy.example/nginx-proxy-debug", expected_status_code=404)
r = nginxproxy.get("http://disabled2.debug.nginx-proxy.example/nginx-proxy-debug")
assert r.status_code == 404

View file

@ -0,0 +1,27 @@
services:
debug_disabled1:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: disabled1.debug.nginx-proxy.example
debug_disabled2:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: disabled2.debug.nginx-proxy.example
debug_enabled:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: enabled.debug.nginx-proxy.example
labels:
com.github.nginx-proxy.nginx-proxy.debug-endpoint: "true"

View file

@ -1,55 +0,0 @@
volumes:
nginx_conf:
services:
nginx-proxy:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
nginx-proxy-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
nginx-proxy-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
debug_disabled1:
image: web
expose:
- "81"
environment:
WEB_PORTS: 81
VIRTUAL_HOST: disabled1.debug.nginx-proxy.example
debug_disabled2:
image: web
expose:
- "82"
environment:
WEB_PORTS: 82
VIRTUAL_HOST: disabled2.debug.nginx-proxy.example
debug_enabled:
image: web
expose:
- "83"
environment:
WEB_PORTS: 83
VIRTUAL_HOST: enabled.debug.nginx-proxy.example
labels:
com.github.nginx-proxy.nginx-proxy.debug-endpoint: "true"

View file

@ -1,40 +0,0 @@
volumes:
nginx_conf:
services:
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: 81
VIRTUAL_HOST: web1.tld
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
environment: &nginxProxyEnv
DEFAULT_HOST: web1.tld
sutdockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
environment: *nginxProxyEnv
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
sutnginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"

View file

@ -0,0 +1,12 @@
services:
nginx-proxy:
environment:
DEFAULT_HOST: web1.tld
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: web1.tld

View file

@ -1,3 +1,7 @@
def test_unknown_virtual_host(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/port")
assert r.status_code == 503
def test_forwards_to_web1(docker_compose, nginxproxy):
r = nginxproxy.get("http://web1.nginx-proxy.tld/port")
assert r.status_code == 200
@ -7,7 +11,3 @@ def test_forwards_to_web2(docker_compose, nginxproxy):
r = nginxproxy.get("http://web2.nginx-proxy.tld/port")
assert r.status_code == 200
assert r.text == "answer from port 82\n"
def test_unknown_virtual_host(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/port", expected_status_code=503)
assert r.status_code == 503

View file

@ -0,0 +1,22 @@
services:
nginx-proxy:
volumes:
- /var/run/docker.sock:/f00.sock:ro
environment:
DOCKER_HOST: unix:///f00.sock
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: web1.nginx-proxy.tld
web2:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: web2.nginx-proxy.tld

View file

@ -1,48 +0,0 @@
volumes:
nginx_conf:
services:
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: 81
VIRTUAL_HOST: web1.nginx-proxy.tld
web2:
image: web
expose:
- "82"
environment:
WEB_PORTS: 82
VIRTUAL_HOST: web2.nginx-proxy.tld
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/f00.sock:ro
environment: &nginxProxyEnv
DOCKER_HOST: unix:///f00.sock
sutdockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
environment: *nginxProxyEnv
sutnginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"

View file

@ -1 +0,0 @@
nginx.tmpl

View file

@ -1,21 +1,24 @@
volumes:
nginx_conf_dockergen:
nginx_conf:
services:
nginx:
nginx-proxy-nginx:
image: nginx
container_name: nginx
volumes:
- nginx_conf_dockergen:/etc/nginx/conf.d:ro
- nginx_conf:/etc/nginx/conf.d:ro
ports:
- "80:80"
- "443:443"
dockergen:
nginx-proxy-dockergen:
image: nginxproxy/docker-gen
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ../../nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl
- nginx_conf_dockergen:/etc/nginx/conf.d
- nginx_conf:/etc/nginx/conf.d
web:
image: web
@ -23,5 +26,5 @@ services:
expose:
- "80"
environment:
WEB_PORTS: 80
WEB_PORTS: "80"
VIRTUAL_HOST: whoami.nginx.container.docker

View file

@ -1,10 +1,12 @@
import os
import docker
import pytest
from packaging.version import Version
raw_version = docker.from_env().version()["Version"]
pytestmark = pytest.mark.skipif(
True,
reason="This test intefers with the other tests, and might no longer be needed."
Version(raw_version) < Version("1.13"),
reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})"
)

View file

@ -1,23 +1,15 @@
def test_nohttp_missing_cert_disabled(docker_compose, nginxproxy):
r = nginxproxy.get("http://nohttp-missing-cert-disabled.nginx-proxy.tld/", allow_redirects=False)
assert r.status_code == 503
def test_nohttp_missing_cert_enabled(docker_compose, nginxproxy):
r = nginxproxy.get("http://nohttp-missing-cert-enabled.nginx-proxy.tld/", allow_redirects=False)
assert r.status_code == 200
def test_nohttp_missing_cert_disabled(docker_compose, nginxproxy):
r = nginxproxy.get(
"http://nohttp-missing-cert-disabled.nginx-proxy.tld/",
allow_redirects=False,
expected_status_code=503
)
assert r.status_code == 503
def test_redirect_missing_cert_disabled(docker_compose, nginxproxy):
r = nginxproxy.get("http://redirect-missing-cert-disabled.nginx-proxy.tld/", allow_redirects=False)
assert r.status_code == 301
def test_redirect_missing_cert_enabled(docker_compose, nginxproxy):
r = nginxproxy.get("http://redirect-missing-cert-enabled.nginx-proxy.tld/", allow_redirects=False)
assert r.status_code == 200
def test_redirect_missing_cert_disabled(docker_compose, nginxproxy):
r = nginxproxy.get(
"http://redirect-missing-cert-disabled.nginx-proxy.tld/",
allow_redirects=False,
expected_status_code=301
)
assert r.status_code == 301

View file

@ -1,39 +1,8 @@
volumes:
nginx_conf:
services:
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &certs ./withdefault.certs:/etc/nginx/certs:ro
environment: &nginxProxyEnv
nginx-proxy:
environment:
ENABLE_HTTP_ON_MISSING_CERT: "false"
sutdockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
environment: *nginxProxyEnv
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *certs
sutnginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *certs
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
nohttp-missing-cert-disabled:
image: web
expose:

View file

@ -1,33 +0,0 @@
networks:
default:
name: nginx-proxy-test-events
volumes:
nginx_conf:
services:
nginxproxy:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
nginxproxy-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
nginxproxy-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"

View file

@ -7,7 +7,7 @@ import pytest
from docker.errors import NotFound
@pytest.fixture()
@pytest.fixture
def web1(docker_compose):
"""
pytest fixture creating a web container with `VIRTUAL_HOST=web1.nginx-proxy` listening on port 81.
@ -22,7 +22,7 @@ def web1(docker_compose):
},
ports={"81/tcp": None}
)
docker_compose.networks.get("nginx-proxy-test-events").connect(container)
docker_compose.networks.get("test_events-net").connect(container)
sleep(2) # give it some time to initialize and for docker-gen to detect it
yield container
try:
@ -30,7 +30,7 @@ def web1(docker_compose):
except NotFound:
pass
@pytest.fixture()
@pytest.fixture
def web2(docker_compose):
"""
pytest fixture creating a web container with `VIRTUAL_HOST=nginx-proxy`, `VIRTUAL_PATH=/web2/` and `VIRTUAL_DEST=/` listening on port 82.
@ -47,7 +47,7 @@ def web2(docker_compose):
},
ports={"82/tcp": None}
)
docker_compose.networks.get("nginx-proxy-test-events").connect(container)
docker_compose.networks.get("test_events-net").connect(container)
sleep(2) # give it some time to initialize and for docker-gen to detect it
yield container
try:
@ -56,7 +56,7 @@ def web2(docker_compose):
pass
def test_nginx_proxy_behavior_when_alone(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/", expected_status_code=503)
r = nginxproxy.get("http://nginx-proxy/")
assert r.status_code == 503
@ -67,18 +67,18 @@ def test_new_container_is_detected_vhost(web1, nginxproxy):
web1.remove(force=True)
sleep(2)
r = nginxproxy.get("http://web1.nginx-proxy/port", expected_status_code=503)
r = nginxproxy.get("http://web1.nginx-proxy/port")
assert r.status_code == 503
def test_new_container_is_detected_vpath(web2, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/web2/port")
assert r.status_code == 200
assert "answer from port 82\n" == r.text
r = nginxproxy.get("http://nginx-proxy/port", expected_status_code=[404, 503])
r = nginxproxy.get("http://nginx-proxy/port")
assert r.status_code in [404, 503]
web2.remove(force=True)
sleep(2)
r = nginxproxy.get("http://nginx-proxy/web2/port", expected_status_code=503)
r = nginxproxy.get("http://nginx-proxy/web2/port")
assert r.status_code == 503

View file

@ -0,0 +1,3 @@
networks:
default:
name: test_events-net

View file

@ -1,41 +0,0 @@
volumes:
nginx_conf:
services:
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &customFallback ./custom-fallback.conf:/etc/nginx/conf.d/zzz-custom-fallback.conf:ro
sut-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *customFallback
sut-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *customFallback
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
http-only:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: http-only.nginx-proxy.test
HTTPS_METHOD: nohttps

View file

@ -1,44 +0,0 @@
volumes:
nginx_conf:
services:
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &certs ./withdefault.certs:/etc/nginx/certs:ro
environment: &nginxProxyEnv
HTTPS_METHOD: redirect
sut-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *certs
environment: *nginxProxyEnv
sut-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *certs
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
https-only:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
HTTPS_METHOD: nohttp
VIRTUAL_HOST: https-only.nginx-proxy.test

View file

@ -1,61 +0,0 @@
volumes:
nginx_conf:
services:
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &certs ./withdefault.certs:/etc/nginx/certs:ro
environment: &nginxProxyEnv
HTTPS_METHOD: nohttp
sut-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *certs
environment: *nginxProxyEnv
sut-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *certs
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
https-only:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: https-only.nginx-proxy.test
missing-cert:
image: web
expose:
- "84"
environment:
WEB_PORTS: "84"
VIRTUAL_HOST: missing-cert.nginx-proxy.test
missing-cert-default-untrusted:
image: web
expose:
- "85"
environment:
WEB_PORTS: "85"
VIRTUAL_HOST: missing-cert.default-untrusted.nginx-proxy.test
labels:
com.github.nginx-proxy.nginx-proxy.trust-default-cert: "false"

View file

@ -1,43 +0,0 @@
volumes:
nginx_conf:
services:
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &certs ./withdefault.certs:/etc/nginx/certs:ro
environment: &nginxProxyEnv
HTTPS_METHOD: nohttp
sut-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *certs
environment: *nginxProxyEnv
sut-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *certs
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
https-only:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: https-only.nginx-proxy.test

View file

@ -1,41 +0,0 @@
volumes:
nginx_conf:
services:
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
environment: &nginxProxyEnv
HTTPS_METHOD: redirect
sut-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
environment: *nginxProxyEnv
sut-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
http-only:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
HTTPS_METHOD: nohttps
VIRTUAL_HOST: http-only.nginx-proxy.test

View file

@ -1,40 +0,0 @@
volumes:
nginx_conf:
services:
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
environment: &nginxProxyEnv
HTTPS_METHOD: nohttps
sut-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
environment: *nginxProxyEnv
sut-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
http-only:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: http-only.nginx-proxy.test

View file

@ -1,69 +0,0 @@
volumes:
nginx_conf:
services:
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &certs ./withdefault.certs:/etc/nginx/certs:ro
environment: &nginxProxyEnv
TRUST_DEFAULT_CERT: "false"
sut-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *certs
environment: *nginxProxyEnv
sut-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *certs
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
https-and-http:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: https-and-http.nginx-proxy.test
https-only:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: https-only.nginx-proxy.test
HTTPS_METHOD: nohttp
http-only:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: http-only.nginx-proxy.test
HTTPS_METHOD: nohttps
missing-cert:
image: web
expose:
- "84"
environment:
WEB_PORTS: "84"
VIRTUAL_HOST: missing-cert.nginx-proxy.test

View file

@ -0,0 +1,9 @@
services:
nginx-proxy:
image: nginxproxy/nginx-proxy:test
container_name: nginx-proxy
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
ports:
- "80:80"
- "443:443"

View file

@ -0,0 +1,14 @@
services:
nginx-proxy:
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/test_fallback.data/custom-fallback.conf:/etc/nginx/conf.d/zzz-custom-fallback.conf:ro
http-only:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: http-only.nginx-proxy.test
HTTPS_METHOD: nohttps

View file

@ -1,35 +1,8 @@
volumes:
nginx_conf:
services:
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
nginx-proxy:
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &certs ./nodefault.certs:/etc/nginx/certs:ro
sut-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *certs
sut-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *certs
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/test_fallback.data/nodefault.certs:/etc/nginx/certs:ro
https-and-http:
image: web

View file

@ -0,0 +1,16 @@
services:
nginx-proxy:
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/test_fallback.data/withdefault.certs:/etc/nginx/certs:ro
environment:
HTTPS_METHOD: redirect
https-only:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
HTTPS_METHOD: nohttp
VIRTUAL_HOST: https-only.nginx-proxy.test

View file

@ -0,0 +1,33 @@
services:
nginx-proxy:
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/test_fallback.data/withdefault.certs:/etc/nginx/certs:ro
environment:
HTTPS_METHOD: nohttp
https-only:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: https-only.nginx-proxy.test
missing-cert:
image: web
expose:
- "84"
environment:
WEB_PORTS: "84"
VIRTUAL_HOST: missing-cert.nginx-proxy.test
missing-cert-default-untrusted:
image: web
expose:
- "85"
environment:
WEB_PORTS: "85"
VIRTUAL_HOST: missing-cert.default-untrusted.nginx-proxy.test
labels:
com.github.nginx-proxy.nginx-proxy.trust-default-cert: "false"

View file

@ -0,0 +1,15 @@
services:
nginx-proxy:
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/test_fallback.data/withdefault.certs:/etc/nginx/certs:ro
environment:
HTTPS_METHOD: nohttp
https-only:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: https-only.nginx-proxy.test

View file

@ -0,0 +1,13 @@
services:
nginx-proxy:
environment:
HTTPS_METHOD: redirect
http-only:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
HTTPS_METHOD: nohttps
VIRTUAL_HOST: http-only.nginx-proxy.test

View file

@ -0,0 +1,12 @@
services:
nginx-proxy:
environment:
HTTPS_METHOD: nohttps
http-only:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: http-only.nginx-proxy.test

View file

@ -0,0 +1,41 @@
services:
nginx-proxy:
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/test_fallback.data/withdefault.certs:/etc/nginx/certs:ro
environment:
TRUST_DEFAULT_CERT: "false"
https-and-http:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: https-and-http.nginx-proxy.test
https-only:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: https-only.nginx-proxy.test
HTTPS_METHOD: nohttp
http-only:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: http-only.nginx-proxy.test
HTTPS_METHOD: nohttps
missing-cert:
image: web
expose:
- "84"
environment:
WEB_PORTS: "84"
VIRTUAL_HOST: missing-cert.nginx-proxy.test

View file

@ -1,35 +1,8 @@
volumes:
nginx_conf:
services:
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
nginx-proxy:
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &certs ./withdefault.certs:/etc/nginx/certs:ro
sut-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *certs
sut-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *certs
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/test_fallback.data/withdefault.certs:/etc/nginx/certs:ro
https-and-http:
image: web

View file

@ -1,36 +1,33 @@
import os.path
import pathlib
import re
from typing import List, Callable
import backoff
import pytest
import requests
from requests import Response
@pytest.fixture
def data_dir():
return f"{os.path.splitext(__file__)[0]}.data"
def docker_compose_files(compose_file) -> List[str]:
data_dir = pathlib.Path(__file__).parent.joinpath("test_fallback.data")
return [
data_dir.joinpath("compose.base.yml"),
data_dir.joinpath(compose_file).as_posix()
]
@pytest.fixture
def docker_compose_file(data_dir, compose_file):
return os.path.join(data_dir, compose_file)
@pytest.fixture
def get(docker_compose, nginxproxy, want_err_re):
def get(docker_compose, nginxproxy, want_err_re: re.Pattern[str]) -> Callable[[str], Response]:
@backoff.on_exception(
backoff.constant,
requests.exceptions.SSLError,
giveup=lambda e: want_err_re and want_err_re.search(str(e)),
giveup=lambda e: want_err_re and bool(want_err_re.search(str(e))),
interval=.3,
max_tries=30,
jitter=None)
def _get(url, expected_status_code=None):
if expected_status_code is None:
return nginxproxy.get_without_backoff(url, allow_redirects=False)
else:
return nginxproxy.get(url, allow_redirects=False, expected_status_code=expected_status_code)
def _get(url) -> Response:
return nginxproxy.get(url, allow_redirects=False)
return _get
@ -111,9 +108,9 @@ INTERNAL_ERR_RE = re.compile("TLSV1_UNRECOGNIZED_NAME")
# should prefer that server for handling requests for unknown vhosts.
("custom-fallback.yml", "http://unknown.nginx-proxy.test/", 418, None),
])
def test_fallback(get, url, want_code, want_err_re):
def test_fallback(get, compose_file, url, want_code, want_err_re):
if want_err_re is None:
r = get(url, want_code)
r = get(url)
assert r.status_code == want_code
else:
with pytest.raises(requests.exceptions.SSLError, match=want_err_re):

View file

@ -1,7 +1,3 @@
import os
import pytest
def test_arbitrary_headers_are_passed_on(docker_compose, nginxproxy):
r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'Foo': 'Bar'})
assert r.status_code == 200
@ -95,13 +91,9 @@ def test_httpoxy_safe(docker_compose, nginxproxy):
assert "Proxy:" not in r.text
@pytest.mark.xfail(
condition = os.environ.get("COMPOSE_PROFILES") == "separateContainers",
reason = "This test is expected to fail when using separate containers",
)
def test_no_host_server_tokens_off(docker_compose, nginxproxy):
ip = nginxproxy.get_ip()
r = nginxproxy.get(f"http://{ip}/headers", expected_status_code=503)
r = nginxproxy.get(f"http://{ip}/headers")
assert r.status_code == 503
assert r.headers["Server"] == "nginx"

View file

@ -1,14 +1,10 @@
volumes:
nginx_conf:
services:
web:
image: web
expose:
- "80"
environment:
WEB_PORTS: 80
WEB_PORTS: "80"
VIRTUAL_HOST: web.nginx-proxy.tld
web-server-tokens-off:
@ -16,31 +12,6 @@ services:
expose:
- "80"
environment:
WEB_PORTS: 80
WEB_PORTS: "80"
VIRTUAL_HOST: web-server-tokens-off.nginx-proxy.tld
SERVER_TOKENS: "off"
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
sut-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
sut-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"

View file

@ -1,4 +1,3 @@
import os
import pytest
@ -95,14 +94,10 @@ def test_httpoxy_safe(docker_compose, nginxproxy):
assert "Proxy:" not in r.text
@pytest.mark.xfail(
condition = os.environ.get("COMPOSE_PROFILES") == "separateContainers",
reason = "This test is expected to fail when using separate containers",
)
@pytest.mark.filterwarnings('ignore::urllib3.exceptions.InsecureRequestWarning')
def test_no_host_server_tokens_off(docker_compose, nginxproxy):
ip = nginxproxy.get_ip()
r = nginxproxy.get(f"https://{ip}/headers", verify=False, expected_status_code=503)
r = nginxproxy.get(f"https://{ip}/headers", verify=False)
assert r.status_code == 503
assert r.headers["Server"] == "nginx"

View file

@ -1,14 +1,15 @@
volumes:
nginx_conf:
services:
nginx-proxy:
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/certs:/etc/nginx/certs:ro
web:
image: web
expose:
- "80"
environment:
WEB_PORTS: 80
WEB_PORTS: "80"
VIRTUAL_HOST: web.nginx-proxy.tld
web-server-tokens-off:
@ -16,34 +17,6 @@ services:
expose:
- "80"
environment:
WEB_PORTS: 80
WEB_PORTS: "80"
VIRTUAL_HOST: web-server-tokens-off.nginx-proxy.tld
SERVER_TOKENS: "off"
sut:
profiles:
- singleContainer
image: nginxproxy/nginx-proxy:test
volumes:
- &dockerSocket /var/run/docker.sock:/tmp/docker.sock:ro
- &certs ./certs:/etc/nginx/certs:ro
sut-dockergen:
profiles:
- separateContainers
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- &confVolume nginx_conf:/etc/nginx/conf.d
- *dockerSocket
- *certs
sut-nginx:
profiles:
- separateContainers
container_name: nginx-proxy
image: nginx:alpine
volumes:
- *confVolume
- *certs
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"

Some files were not shown because too many files have changed in this diff Show more