Compare commits
311 commits
docker-ent
...
main
Author | SHA1 | Date | |
---|---|---|---|
4856fbe7eb | |||
23d56b2185 | |||
![]() |
1da623019f | ||
![]() |
e234ffba20 | ||
![]() |
18030a7896 | ||
![]() |
dfbff1eb9c | ||
![]() |
34a33a2255 | ||
![]() |
a61e485410 | ||
![]() |
9312d5239a | ||
![]() |
9fc7cec15c | ||
![]() |
8447a36046 | ||
![]() |
923f05032f | ||
![]() |
820d4a29ac | ||
![]() |
1859811311 | ||
![]() |
691724c81f | ||
![]() |
aa8145b62d | ||
![]() |
836012cad6 | ||
![]() |
005377c6e5 | ||
![]() |
bfdd72fe95 | ||
![]() |
40309e2441 | ||
![]() |
daa9449176 | ||
![]() |
4ccbc3edec | ||
![]() |
1f732a54c6 | ||
![]() |
ae0c9a8e96 | ||
![]() |
ea99c1a6f9 | ||
![]() |
1e9745f604 | ||
![]() |
7b6baa43cd | ||
![]() |
a2c316a876 | ||
![]() |
fb0fc331c0 | ||
![]() |
eb09876f97 | ||
![]() |
35e2d21527 | ||
![]() |
b5dea1cf50 | ||
![]() |
c60eff5d16 | ||
![]() |
142a159d56 | ||
![]() |
92eb45f0ec | ||
![]() |
2bb0bbe5c9 | ||
![]() |
fbad3ac3fc | ||
![]() |
0c4be4e90f | ||
![]() |
b9657874b9 | ||
![]() |
56a4bb8601 | ||
![]() |
c67739f1cc | ||
![]() |
a25b7ea1ef | ||
![]() |
22e6e59034 | ||
![]() |
0c141e64ba | ||
![]() |
3c5bbcf029 | ||
![]() |
9bd84fc95e | ||
![]() |
b6c8851794 | ||
![]() |
30299e69bc | ||
![]() |
271e31dec4 | ||
![]() |
559ddc7d13 | ||
![]() |
e96a3ddec2 | ||
![]() |
f0034c661a | ||
![]() |
7c003a4aa4 | ||
![]() |
ac4ce5b775 | ||
![]() |
baa2ce5e8b | ||
![]() |
993bcc07c0 | ||
![]() |
07b469d8ac | ||
![]() |
ce2e076d65 | ||
![]() |
d4b1925624 | ||
![]() |
d433b3bee6 | ||
![]() |
7fee758a70 | ||
![]() |
d30b573b2c | ||
![]() |
f600ce3e11 | ||
![]() |
b4c2b0dd5e | ||
![]() |
12b482a7ea | ||
![]() |
c6664335b7 | ||
![]() |
f54f89a5f3 | ||
![]() |
760c891a0c | ||
![]() |
627b6ad75f | ||
![]() |
b0efe80f05 | ||
![]() |
90e9308f87 | ||
![]() |
f325dadd6a | ||
![]() |
4d03645200 | ||
![]() |
57016e43f3 | ||
![]() |
c944877b70 | ||
![]() |
52ffab1ce6 | ||
![]() |
7bd26d624d | ||
![]() |
d7acd931a3 | ||
![]() |
d4d6567bd3 | ||
![]() |
9ca7d26821 | ||
![]() |
fdb7310cda | ||
![]() |
40c153e8b0 | ||
![]() |
a79445feef | ||
![]() |
80474e37e8 | ||
![]() |
523112d205 | ||
![]() |
469765bbb7 | ||
![]() |
023a3d17da | ||
![]() |
57e503c830 | ||
![]() |
fbf3e2f458 | ||
![]() |
7d909782f9 | ||
![]() |
72bb8a66d8 | ||
![]() |
01d14f0942 | ||
![]() |
cab2a2d151 | ||
![]() |
a06cd1ae9a | ||
![]() |
946485e0b8 | ||
![]() |
db0421eb4a | ||
![]() |
30b909de8e | ||
![]() |
dcbb695a4b | ||
![]() |
07aef2bd83 | ||
![]() |
52100c40af | ||
![]() |
031a2b16bb | ||
![]() |
9114b8047d | ||
![]() |
190030745c | ||
![]() |
32ad9b7102 | ||
![]() |
7dafac8b87 | ||
![]() |
dce7663b69 | ||
![]() |
fe52878940 | ||
![]() |
ebed622fd7 | ||
![]() |
8fed348ff7 | ||
![]() |
a3db62bb14 | ||
![]() |
4c67b24552 | ||
![]() |
1cd7b97e8f | ||
![]() |
5baf4a163f | ||
![]() |
73ba28091a | ||
![]() |
5ec120a296 | ||
![]() |
8417046748 | ||
![]() |
ac0a4ca826 | ||
![]() |
3ba0678ce5 | ||
![]() |
4251249761 | ||
![]() |
e95a66eedf | ||
![]() |
69488808b9 | ||
![]() |
712a7ef176 | ||
![]() |
76873746f3 | ||
![]() |
90f6e1b3dc | ||
![]() |
246da3ff85 | ||
![]() |
1baf048a6e | ||
![]() |
f100b4426f | ||
![]() |
1c00b898a3 | ||
![]() |
d4d3f12a41 | ||
![]() |
54a1b0aa25 | ||
![]() |
83110b24ef | ||
![]() |
dee91b4d93 | ||
![]() |
c5f054ed36 | ||
![]() |
290d275a68 | ||
![]() |
5f4e77b6e7 | ||
![]() |
434a089251 | ||
![]() |
5fe52442bb | ||
![]() |
8d47458026 | ||
![]() |
f4ccdbd656 | ||
![]() |
31642a748f | ||
![]() |
261ac6f43b | ||
![]() |
b67b434711 | ||
![]() |
705dfa090b | ||
![]() |
e6c301a4a5 | ||
![]() |
ff4657181a | ||
![]() |
477366d9a4 | ||
![]() |
4606b15309 | ||
![]() |
50608d7826 | ||
![]() |
60b123d249 | ||
![]() |
9506e60f43 | ||
![]() |
cea905ff88 | ||
![]() |
714fa25704 | ||
![]() |
4bd542de99 | ||
![]() |
8de923fd33 | ||
![]() |
7922c925af | ||
![]() |
57e86561eb | ||
![]() |
9cf736f1f8 | ||
![]() |
e904471cd3 | ||
![]() |
5f3ec18b28 | ||
![]() |
0dfc8b7a50 | ||
![]() |
7a761e1073 | ||
![]() |
fb9c3a646a | ||
![]() |
b4c7ea603e | ||
![]() |
2a793b2d5b | ||
![]() |
df464bbc81 | ||
![]() |
1c1f8e8700 | ||
![]() |
ce2a78bebf | ||
![]() |
49f0b89fb0 | ||
![]() |
57501eb13f | ||
![]() |
87ce03e3c3 | ||
![]() |
f22b64df79 | ||
![]() |
8e372c39c2 | ||
![]() |
f0f71f72f7 | ||
![]() |
dcc97b9cff | ||
![]() |
5aea820aaa | ||
![]() |
1b4a3b036b | ||
![]() |
91652aac48 | ||
![]() |
2564a93966 | ||
![]() |
6c1b532ffb | ||
![]() |
db07d90ad8 | ||
![]() |
8e2642909e | ||
![]() |
5b1491f464 | ||
![]() |
fc02a5ae38 | ||
![]() |
59d5293480 | ||
![]() |
ec405f31d7 | ||
![]() |
12c4f0c7c2 | ||
![]() |
be319e6629 | ||
![]() |
99645f104d | ||
![]() |
f964176106 | ||
![]() |
d6c38a0bab | ||
![]() |
1bf7eff04f | ||
![]() |
c4c65a4441 | ||
![]() |
be7c4c8c85 | ||
![]() |
9a76577ebc | ||
![]() |
1b97b11173 | ||
![]() |
8b91f09a9b | ||
![]() |
d80ca7ec36 | ||
![]() |
53e9a03ac9 | ||
![]() |
0baff189bc | ||
![]() |
8359aa2089 | ||
![]() |
47e2838e61 | ||
![]() |
216eae9f70 | ||
![]() |
62212186eb | ||
![]() |
62d9c08474 | ||
![]() |
fc98f4c953 | ||
![]() |
87e5b58b77 | ||
![]() |
6e771fb3be | ||
![]() |
340f6b0c08 | ||
![]() |
a4c694fefc | ||
![]() |
2ac4509a61 | ||
![]() |
b4bceac3fd | ||
![]() |
08f563888d | ||
![]() |
6602769d9a | ||
![]() |
c678cfdddf | ||
![]() |
46724e7cb6 | ||
![]() |
e46e62e660 | ||
![]() |
877f243ce2 | ||
![]() |
43b8ab82a9 | ||
![]() |
12d639dad7 | ||
![]() |
1e55d9883f | ||
![]() |
2ac4540a0d | ||
![]() |
769d58bcfb | ||
![]() |
766db8d942 | ||
![]() |
559c43e75e | ||
![]() |
0d7aac7c53 | ||
![]() |
103cb7d4ca | ||
![]() |
94fb8459cd | ||
![]() |
c7bf75609b | ||
![]() |
3c3b3675c1 | ||
![]() |
16b84ea1b5 | ||
![]() |
6441daf25b | ||
![]() |
45770e04bd | ||
![]() |
26b0a0008b | ||
![]() |
4b38bf259d | ||
![]() |
12e1360fc3 | ||
![]() |
b09575297d | ||
![]() |
418f1a3cd0 | ||
![]() |
60618afe52 | ||
![]() |
2a5521625d | ||
![]() |
b8b53794bf | ||
![]() |
d588c96dff | ||
![]() |
f960061e2d | ||
![]() |
e97be61272 | ||
![]() |
4d9d067630 | ||
![]() |
8c95ed1cc0 | ||
![]() |
9d2eeb7273 | ||
![]() |
24592e39a3 | ||
![]() |
a9f8a9c32a | ||
![]() |
da006557d0 | ||
![]() |
4464269c51 | ||
![]() |
b4902f145d | ||
![]() |
d4438b5a09 | ||
![]() |
50750bea53 | ||
![]() |
0dd4f7a8a8 | ||
![]() |
a2a441e1db | ||
![]() |
5a32bffd6f | ||
![]() |
745a2035cf | ||
![]() |
29168655cf | ||
![]() |
2eaf689172 | ||
![]() |
e75c2dfb79 | ||
![]() |
020b993654 | ||
![]() |
5583b385cb | ||
![]() |
d9b1751f97 | ||
![]() |
1aef017df2 | ||
![]() |
5c0c8c93d8 | ||
![]() |
cb59c24470 | ||
![]() |
887e2e950c | ||
![]() |
3a19382702 | ||
![]() |
f47c874c5a | ||
![]() |
25883ac05f | ||
![]() |
62f55b4428 | ||
![]() |
76778cebb1 | ||
![]() |
8aefce916f | ||
![]() |
de9809a9e5 | ||
![]() |
e50429e87e | ||
![]() |
fa23c11edb | ||
![]() |
df890def7c | ||
![]() |
40a347bfae | ||
![]() |
af56ba2254 | ||
![]() |
5d10467c42 | ||
![]() |
3d61f47b54 | ||
![]() |
af5acae58a | ||
![]() |
e8d95764fb | ||
![]() |
bfe9e0ac1d | ||
![]() |
d46881fb58 | ||
![]() |
6f042854e1 | ||
![]() |
14347a4178 | ||
![]() |
63411b2407 | ||
![]() |
b492cae053 | ||
![]() |
329b69fe76 | ||
![]() |
1433daed4d | ||
![]() |
a3de83c7d1 | ||
![]() |
e2bd0d6365 | ||
![]() |
d12689cd52 | ||
![]() |
2aa35aa637 | ||
![]() |
443ee5202c | ||
![]() |
d56e8f1d8a | ||
![]() |
7ce72d59a9 | ||
![]() |
26db13387e | ||
![]() |
35c6b2afe5 | ||
![]() |
beeb80732a | ||
![]() |
47ee75d780 | ||
![]() |
53656ffe51 | ||
![]() |
795cc1332b | ||
![]() |
051c8a5105 | ||
![]() |
4fbd14f1fd | ||
![]() |
eb9fca85cf | ||
![]() |
758b43a5b2 | ||
![]() |
de4cb3d2b0 | ||
![]() |
5c1db95551 | ||
![]() |
a5566f5e8f |
268 changed files with 4332 additions and 1782 deletions
85
.github/workflows/build-publish-dispatch.yml
vendored
Normal file
85
.github/workflows/build-publish-dispatch.yml
vendored
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
name: Build and publish Docker images on demand
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
image_tag:
|
||||||
|
description: "Image tag"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
multiarch-build:
|
||||||
|
name: Build and publish ${{ matrix.base }} image with tag ${{ inputs.image_tag }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
base: [alpine, debian]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Retrieve nginx-proxy version
|
||||||
|
id: nginx-proxy_version
|
||||||
|
run: echo "VERSION=$(git describe --tags)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Retrieve docker-gen version
|
||||||
|
id: docker-gen_version
|
||||||
|
run: sed -n -e 's;^FROM nginxproxy/docker-gen:\([0-9.]*\).*;VERSION=\1;p' Dockerfile.${{ matrix.base }} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Get Docker tags
|
||||||
|
id: docker_meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
nginxproxy/nginx-proxy
|
||||||
|
tags: |
|
||||||
|
type=raw,value=${{ inputs.image_tag }},enable=${{ matrix.base == 'debian' }}
|
||||||
|
type=raw,value=${{ inputs.image_tag }},suffix=-alpine,enable=${{ 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 }}
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push the image
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile.${{ matrix.base }}
|
||||||
|
build-args: |
|
||||||
|
NGINX_PROXY_VERSION=${{ steps.nginx-proxy_version.outputs.VERSION }}
|
||||||
|
DOCKER_GEN_VERSION=${{ steps.docker-gen_version.outputs.VERSION }}
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/s390x,linux/arm/v7
|
||||||
|
sbom: true
|
||||||
|
push: true
|
||||||
|
provenance: mode=max
|
||||||
|
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
- name: Images digests
|
||||||
|
run: echo ${{ steps.docker_build.outputs.digest }}
|
4
.github/workflows/build-publish.yml
vendored
4
.github/workflows/build-publish.yml
vendored
|
@ -81,14 +81,14 @@ jobs:
|
||||||
|
|
||||||
- name: Build and push the image
|
- name: Build and push the image
|
||||||
id: docker_build
|
id: docker_build
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: Dockerfile.${{ matrix.base }}
|
file: Dockerfile.${{ matrix.base }}
|
||||||
build-args: |
|
build-args: |
|
||||||
NGINX_PROXY_VERSION=${{ steps.nginx-proxy_version.outputs.VERSION }}
|
NGINX_PROXY_VERSION=${{ steps.nginx-proxy_version.outputs.VERSION }}
|
||||||
DOCKER_GEN_VERSION=${{ steps.docker-gen_version.outputs.VERSION }}
|
DOCKER_GEN_VERSION=${{ steps.docker-gen_version.outputs.VERSION }}
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64,linux/s390x,linux/arm/v7
|
||||||
sbom: true
|
sbom: true
|
||||||
push: true
|
push: true
|
||||||
provenance: mode=max
|
provenance: mode=max
|
||||||
|
|
2
.github/workflows/dockerhub-description.yml
vendored
2
.github/workflows/dockerhub-description.yml
vendored
|
@ -18,7 +18,7 @@ jobs:
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Docker Hub Description
|
- name: Docker Hub Description
|
||||||
uses: peter-evans/dockerhub-description@v3
|
uses: peter-evans/dockerhub-description@v4
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN_RWD }}
|
password: ${{ secrets.DOCKERHUB_TOKEN_RWD }}
|
||||||
|
|
7
.github/workflows/test.yml
vendored
7
.github/workflows/test.yml
vendored
|
@ -25,10 +25,10 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python 3.9
|
- name: Set up Python 3.12
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.12
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
@ -36,6 +36,9 @@ jobs:
|
||||||
pip install -r python-requirements.txt
|
pip install -r python-requirements.txt
|
||||||
working-directory: test/requirements
|
working-directory: test/requirements
|
||||||
|
|
||||||
|
- name: Pull nginx:alpine image
|
||||||
|
run: docker pull nginx:alpine
|
||||||
|
|
||||||
- name: Build Docker web server image
|
- name: Build Docker web server image
|
||||||
run: make build-webserver
|
run: make build-webserver
|
||||||
|
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
**/__pycache__/
|
**/__pycache__/
|
||||||
**/.cache/
|
**/.cache/
|
||||||
.idea/
|
.idea/
|
||||||
|
wip
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
FROM nginxproxy/docker-gen:0.11.0 AS docker-gen
|
FROM docker.io/nginxproxy/docker-gen:0.14.5 AS docker-gen
|
||||||
|
|
||||||
FROM nginxproxy/forego:0.17.3 AS forego
|
FROM docker.io/nginxproxy/forego:0.18.2 AS forego
|
||||||
|
|
||||||
# Build the final image
|
# Build the final image
|
||||||
FROM nginx:1.25.3-alpine
|
FROM docker.io/library/nginx:1.27.3-alpine
|
||||||
|
|
||||||
ARG NGINX_PROXY_VERSION
|
ARG NGINX_PROXY_VERSION
|
||||||
# Add DOCKER_GEN_VERSION environment variable because
|
# Add DOCKER_GEN_VERSION environment variable because
|
||||||
|
@ -14,26 +14,25 @@ ENV NGINX_PROXY_VERSION=${NGINX_PROXY_VERSION} \
|
||||||
DOCKER_HOST=unix:///tmp/docker.sock
|
DOCKER_HOST=unix:///tmp/docker.sock
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN apk add --no-cache --virtual .run-deps \
|
RUN apk add --no-cache --virtual .run-deps bash openssl
|
||||||
bash \
|
|
||||||
ca-certificates \
|
|
||||||
openssl \
|
|
||||||
&& update-ca-certificates
|
|
||||||
|
|
||||||
# Configure Nginx
|
# Configure Nginx
|
||||||
RUN sed -i 's/\[ "\$1" = "nginx" \]/\[ "\$1" = "forego" \] || \[ "\$1" = "nginx" \]/' /docker-entrypoint.sh \
|
RUN echo -e "\ninclude /etc/nginx/toplevel.conf.d/*.conf;" >> /etc/nginx/nginx.conf \
|
||||||
&& sed -i 's/worker_processes 1/worker_processes auto/' /etc/nginx/nginx.conf \
|
&& sed -i 's/worker_connections.*;$/worker_connections 10240;/' /etc/nginx/nginx.conf \
|
||||||
&& sed -i 's/worker_connections 1024/worker_connections 10240/' /etc/nginx/nginx.conf \
|
&& sed -i -e '/^\}$/{s//\}\nworker_rlimit_nofile 20480;/;:a' -e '$!N;$!ba' -e '}' /etc/nginx/nginx.conf \
|
||||||
&& mkdir -p '/etc/nginx/dhparam'
|
&& mkdir -p '/etc/nginx/toplevel.conf.d' \
|
||||||
|
&& mkdir -p '/etc/nginx/dhparam' \
|
||||||
|
&& mkdir -p '/etc/nginx/certs' \
|
||||||
|
&& mkdir -p '/usr/share/nginx/html/errors'
|
||||||
|
|
||||||
# Install Forego + docker-gen
|
# Install Forego + docker-gen
|
||||||
COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego
|
COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego
|
||||||
COPY --from=docker-gen /usr/local/bin/docker-gen /usr/local/bin/docker-gen
|
COPY --from=docker-gen /usr/local/bin/docker-gen /usr/local/bin/docker-gen
|
||||||
|
|
||||||
COPY network_internal.conf /etc/nginx/
|
COPY network_internal.conf /etc/nginx/
|
||||||
COPY app/40-nginx-proxy.sh /docker-entrypoint.d/
|
|
||||||
|
|
||||||
COPY app nginx.tmpl LICENSE /app/
|
COPY app nginx.tmpl LICENSE /app/
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||||
CMD ["forego", "start", "-r"]
|
CMD ["forego", "start", "-r"]
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
FROM nginxproxy/docker-gen:0.11.0-debian AS docker-gen
|
FROM docker.io/nginxproxy/docker-gen:0.14.5-debian AS docker-gen
|
||||||
|
|
||||||
FROM nginxproxy/forego:0.17.3-debian AS forego
|
FROM docker.io/nginxproxy/forego:0.18.2-debian AS forego
|
||||||
|
|
||||||
# Build the final image
|
# Build the final image
|
||||||
FROM nginx:1.25.3
|
FROM docker.io/library/nginx:1.27.3
|
||||||
|
|
||||||
ARG NGINX_PROXY_VERSION
|
ARG NGINX_PROXY_VERSION
|
||||||
# Add DOCKER_GEN_VERSION environment variable because
|
# Add DOCKER_GEN_VERSION environment variable because
|
||||||
|
@ -13,26 +13,23 @@ ENV NGINX_PROXY_VERSION=${NGINX_PROXY_VERSION} \
|
||||||
DOCKER_GEN_VERSION=${DOCKER_GEN_VERSION} \
|
DOCKER_GEN_VERSION=${DOCKER_GEN_VERSION} \
|
||||||
DOCKER_HOST=unix:///tmp/docker.sock
|
DOCKER_HOST=unix:///tmp/docker.sock
|
||||||
|
|
||||||
# Install/update certificates
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install -y -q --no-install-recommends ca-certificates \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -r /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Configure Nginx
|
# Configure Nginx
|
||||||
RUN sed -i 's/\[ "\$1" = "nginx" \]/\[ "\$1" = "forego" \] || \[ "\$1" = "nginx" \]/' /docker-entrypoint.sh \
|
RUN echo "\ninclude /etc/nginx/toplevel.conf.d/*.conf;" >> /etc/nginx/nginx.conf \
|
||||||
&& sed -i 's/worker_processes 1/worker_processes auto/' /etc/nginx/nginx.conf \
|
&& sed -i 's/worker_connections.*;$/worker_connections 10240;/' /etc/nginx/nginx.conf \
|
||||||
&& sed -i 's/worker_connections 1024/worker_connections 10240/' /etc/nginx/nginx.conf \
|
&& sed -i -e '/^\}$/{s//\}\nworker_rlimit_nofile 20480;/;:a' -e '$!N;$!ba' -e '}' /etc/nginx/nginx.conf \
|
||||||
&& mkdir -p '/etc/nginx/dhparam'
|
&& mkdir -p '/etc/nginx/toplevel.conf.d' \
|
||||||
|
&& mkdir -p '/etc/nginx/dhparam' \
|
||||||
|
&& mkdir -p '/etc/nginx/certs' \
|
||||||
|
&& mkdir -p '/usr/share/nginx/html/errors'
|
||||||
|
|
||||||
# Install Forego + docker-gen
|
# Install Forego + docker-gen
|
||||||
COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego
|
COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego
|
||||||
COPY --from=docker-gen /usr/local/bin/docker-gen /usr/local/bin/docker-gen
|
COPY --from=docker-gen /usr/local/bin/docker-gen /usr/local/bin/docker-gen
|
||||||
|
|
||||||
COPY network_internal.conf /etc/nginx/
|
COPY network_internal.conf /etc/nginx/
|
||||||
COPY app/40-nginx-proxy.sh /docker-entrypoint.d/
|
|
||||||
|
|
||||||
COPY app nginx.tmpl LICENSE /app/
|
COPY app nginx.tmpl LICENSE /app/
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||||
CMD ["forego", "start", "-r"]
|
CMD ["forego", "start", "-r"]
|
||||||
|
|
50
README.md
50
README.md
|
@ -1,10 +1,9 @@
|
||||||
[](https://github.com/nginx-proxy/nginx-proxy/actions/workflows/test.yml)
|
[](https://github.com/nginx-proxy/nginx-proxy/actions/workflows/test.yml)
|
||||||
[](https://github.com/nginx-proxy/nginx-proxy/releases)
|
[](https://github.com/nginx-proxy/nginx-proxy/releases)
|
||||||

|
[](https://nginx.org/en/CHANGES)
|
||||||
[](https://hub.docker.com/r/nginxproxy/nginx-proxy "Click to view the image on Docker Hub")
|
[](https://hub.docker.com/r/nginxproxy/nginx-proxy "Click to view the image on Docker Hub")
|
||||||
[](https://hub.docker.com/r/nginxproxy/nginx-proxy 'DockerHub')
|
[](https://hub.docker.com/r/nginxproxy/nginx-proxy "DockerHub")
|
||||||
[](https://hub.docker.com/r/nginxproxy/nginx-proxy 'DockerHub')
|
[](https://hub.docker.com/r/nginxproxy/nginx-proxy "DockerHub")
|
||||||
|
|
||||||
|
|
||||||
nginx-proxy sets up a container running nginx and [docker-gen](https://github.com/nginx-proxy/docker-gen). docker-gen generates reverse proxy configs for nginx and reloads nginx when containers are started and stopped.
|
nginx-proxy sets up a container running nginx and [docker-gen](https://github.com/nginx-proxy/docker-gen). docker-gen generates reverse proxy configs for nginx and reloads nginx when containers are started and stopped.
|
||||||
|
|
||||||
|
@ -19,9 +18,19 @@ docker run --detach \
|
||||||
--name nginx-proxy \
|
--name nginx-proxy \
|
||||||
--publish 80:80 \
|
--publish 80:80 \
|
||||||
--volume /var/run/docker.sock:/tmp/docker.sock:ro \
|
--volume /var/run/docker.sock:/tmp/docker.sock:ro \
|
||||||
nginxproxy/nginx-proxy:1.4
|
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`
|
Then start any containers (here an nginx container) you want proxied with an env var `VIRTUAL_HOST=subdomain.yourdomain.com`
|
||||||
|
|
||||||
```console
|
```console
|
||||||
|
@ -30,10 +39,16 @@ docker run --detach \
|
||||||
--env VIRTUAL_HOST=foo.bar.com \
|
--env VIRTUAL_HOST=foo.bar.com \
|
||||||
nginx
|
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).
|
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 :
|
The containers being proxied must :
|
||||||
|
|
||||||
- [expose](https://docs.docker.com/engine/reference/run/#expose-incoming-ports) the port to be proxied, either by using the `EXPOSE` directive in their `Dockerfile` or by using the `--expose` flag to `docker run` or `docker create`.
|
- [expose](https://docs.docker.com/engine/reference/run/#expose-incoming-ports) the port to be proxied, either by using the `EXPOSE` directive in their `Dockerfile` or by using the `--expose` flag to `docker run` or `docker create`.
|
||||||
- share at least one Docker network with the nginx-proxy container: by default, if you don't pass the `--net` flag when your nginx-proxy container is created, it will only be attached to the default bridge network. This means that it will not be able to connect to containers on networks other than bridge.
|
- share at least one Docker network with the nginx-proxy container: by default, if you don't pass the `--net` flag when your nginx-proxy container is created, it will only be attached to the default bridge network. This means that it will not be able to connect to containers on networks other than bridge.
|
||||||
|
|
||||||
|
@ -48,7 +63,7 @@ The nginx-proxy images are available in two flavors.
|
||||||
This image is based on the nginx:mainline image, itself based on the debian slim image.
|
This image is based on the nginx:mainline image, itself based on the debian slim image.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
docker pull nginxproxy/nginx-proxy:1.4
|
docker pull nginxproxy/nginx-proxy:1.6
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Alpine based version (`-alpine` suffix)
|
#### Alpine based version (`-alpine` suffix)
|
||||||
|
@ -56,15 +71,22 @@ docker pull nginxproxy/nginx-proxy:1.4
|
||||||
This image is based on the nginx:alpine image.
|
This image is based on the nginx:alpine image.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
docker pull nginxproxy/nginx-proxy:1.4-alpine
|
docker pull nginxproxy/nginx-proxy:1.6-alpine
|
||||||
```
|
```
|
||||||
|
|
||||||
#### :warning: a note on `latest` and `alpine`:
|
> [!IMPORTANT]
|
||||||
|
>
|
||||||
It is not recommended to use the `latest` (`nginxproxy/nginx-proxy`, `nginxproxy/nginx-proxy:latest`) or `alpine` (`nginxproxy/nginx-proxy:alpine`) tag for production setups.
|
> #### A note on `latest` and `alpine`:
|
||||||
|
>
|
||||||
Those tags points to the latest commit in the `main` branch. They do not carry any promise of stability, and using them will probably put your nginx-proxy setup at risk of experiencing uncontrolled updates to non backward compatible versions (or versions with breaking changes). You should always specify the version you want to use explicitly to ensure your setup doesn't break when the image is updated.
|
> It is not recommended to use the `latest` (`nginxproxy/nginx-proxy`, `nginxproxy/nginx-proxy:latest`) or `alpine` (`nginxproxy/nginx-proxy:alpine`) tag for production setups.
|
||||||
|
>
|
||||||
|
> [Those tags point](https://hub.docker.com/r/nginxproxy/nginx-proxy/tags) to the latest commit in the `main` branch. They do not carry any promise of stability, and using them will probably put your nginx-proxy setup at risk of experiencing uncontrolled updates to non backward compatible versions (or versions with breaking changes). You should always specify the version you want to use explicitly to ensure your setup doesn't break when the image is updated.
|
||||||
|
|
||||||
### Additional documentation
|
### Additional documentation
|
||||||
|
|
||||||
Please check the [docs section](https://github.com/nginx-proxy/nginx-proxy/tree/main/docs).
|
Please check the [docs section](https://github.com/nginx-proxy/nginx-proxy/tree/main/docs).
|
||||||
|
|
||||||
|
### Powered by
|
||||||
|
|
||||||
|
[](https://www.jetbrains.com/go/)
|
||||||
|
[](https://www.jetbrains.com/pycharm/)
|
||||||
|
|
|
@ -117,3 +117,5 @@ if [[ $* == 'forego start -r' ]]; then
|
||||||
EOT
|
EOT
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
exec "$@"
|
|
@ -1,4 +1,5 @@
|
||||||
version: "2"
|
volumes:
|
||||||
|
nginx_conf:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
nginx:
|
nginx:
|
||||||
|
@ -7,17 +8,15 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/nginx/conf.d
|
- nginx_conf:/etc/nginx/conf.d:ro
|
||||||
|
|
||||||
dockergen:
|
dockergen:
|
||||||
image: nginxproxy/docker-gen
|
image: nginxproxy/docker-gen
|
||||||
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl
|
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
|
||||||
/etc/nginx/conf.d/default.conf
|
|
||||||
volumes_from:
|
|
||||||
- nginx
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
- ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl
|
- ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl
|
||||||
|
- nginx_conf:/etc/nginx/conf.d
|
||||||
|
|
||||||
whoami:
|
whoami:
|
||||||
image: jwilder/whoami
|
image: jwilder/whoami
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
nginx-proxy:
|
nginx-proxy:
|
||||||
image: nginxproxy/nginx-proxy
|
image: nginxproxy/nginx-proxy
|
||||||
|
@ -9,6 +7,9 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
|
|
||||||
|
# if you want to proxy based on host ports, you'll want to use the host network
|
||||||
|
# network_mode: "host"
|
||||||
|
|
||||||
whoami:
|
whoami:
|
||||||
image: jwilder/whoami
|
image: jwilder/whoami
|
||||||
environment:
|
environment:
|
||||||
|
|
1157
docs/README.md
1157
docs/README.md
File diff suppressed because it is too large
Load diff
815
nginx.tmpl
815
nginx.tmpl
File diff suppressed because it is too large
Load diff
|
@ -4,12 +4,10 @@ Nginx proxy test suite
|
||||||
Install requirements
|
Install requirements
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
You need [python 3.9](https://www.python.org/) and [pip](https://pip.pypa.io/en/stable/installing/) installed. Then run the commands:
|
You need [Docker Compose v2](https://docs.docker.com/compose/install/linux/), [python 3.9](https://www.python.org/) and [pip](https://pip.pypa.io/en/stable/installation/) installed. Then run the commands:
|
||||||
|
|
||||||
pip install -r requirements/python-requirements.txt
|
pip install -r requirements/python-requirements.txt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Prepare the nginx-proxy test image
|
Prepare the nginx-proxy test image
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
|
@ -37,6 +35,16 @@ Run one single test module
|
||||||
|
|
||||||
pytest test_nominal.py
|
pytest test_nominal.py
|
||||||
|
|
||||||
|
Run the test suite from a Docker container
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
If you cannot (or don't want to) install pytest and its requirements on your computer. You can use the nginx-proxy-tester docker image to run the test suite from a Docker container.
|
||||||
|
|
||||||
|
make test-debian
|
||||||
|
|
||||||
|
or if you want to test the alpine flavor:
|
||||||
|
|
||||||
|
make test-alpine
|
||||||
|
|
||||||
Write a test module
|
Write a test module
|
||||||
-------------------
|
-------------------
|
||||||
|
@ -49,13 +57,39 @@ This test suite uses [pytest](http://doc.pytest.org/en/latest/). The [conftest.p
|
||||||
|
|
||||||
### docker_compose fixture
|
### 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.
|
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.
|
||||||
|
|
||||||
|
@ -63,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:
|
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`
|
||||||
- `nginx-proxy.com`
|
- `nginx-proxy.com`
|
||||||
- `www.nginx-proxy.com`
|
- `www.nginx-proxy.com`
|
||||||
|
@ -72,14 +109,16 @@ Any domain name containing the substring `nginx-proxy` will resolve to the IP ad
|
||||||
- `whatever.nginx-proxyooooooo`
|
- `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
|
- `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
|
- `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
|
- `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.
|
Otherwise, domain names are resoved as usual using your system DNS resolver.
|
||||||
|
|
||||||
|
|
||||||
### nginxproxy fixture
|
### 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.
|
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.
|
||||||
|
|
|
@ -24,7 +24,7 @@ fi
|
||||||
# Create a nginx container (which conveniently provides the `openssl` command)
|
# Create a nginx container (which conveniently provides the `openssl` command)
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
CONTAINER=$(docker run -d -v $DIR:/work -w /work -e SAN="$ALTERNATE_DOMAINS" nginx:1.25.3)
|
CONTAINER=$(docker run -d -v $DIR:/work -w /work -e SAN="$ALTERNATE_DOMAINS" nginx:1.27.3)
|
||||||
# Configure openssl
|
# Configure openssl
|
||||||
docker exec $CONTAINER bash -c '
|
docker exec $CONTAINER bash -c '
|
||||||
mkdir -p /ca/{certs,crl,private,newcerts} 2>/dev/null
|
mkdir -p /ca/{certs,crl,private,newcerts} 2>/dev/null
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
nginxproxy:
|
nginx-proxy:
|
||||||
image: nginxproxy/nginx-proxy:test
|
image: nginxproxy/nginx-proxy:test
|
||||||
|
container_name: nginx-proxy
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
316
test/conftest.py
316
test/conftest.py
|
@ -1,28 +1,35 @@
|
||||||
import contextlib
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
|
import platform
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
from typing import List
|
from io import StringIO
|
||||||
|
from typing import Iterator, List, Optional
|
||||||
|
|
||||||
import backoff
|
import backoff
|
||||||
import docker
|
import docker.errors
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
from _pytest._code.code import ReprExceptionInfo
|
from _pytest.fixtures import FixtureRequest
|
||||||
from distutils.version import LooseVersion
|
from docker import DockerClient
|
||||||
from docker.models.containers import Container
|
from docker.models.containers import Container
|
||||||
from requests.packages.urllib3.util.connection import HAS_IPV6
|
from docker.models.networks import Network
|
||||||
|
from packaging.version import Version
|
||||||
|
from requests import Response
|
||||||
|
from urllib3.util.connection import HAS_IPV6
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logging.getLogger('backoff').setLevel(logging.INFO)
|
logging.getLogger('backoff').setLevel(logging.INFO)
|
||||||
logging.getLogger('DNS').setLevel(logging.DEBUG)
|
logging.getLogger('DNS').setLevel(logging.DEBUG)
|
||||||
logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(logging.WARN)
|
logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(logging.WARN)
|
||||||
|
|
||||||
CA_ROOT_CERTIFICATE = os.path.join(os.path.dirname(__file__), 'certs/ca-root.crt')
|
CA_ROOT_CERTIFICATE = pathlib.Path(__file__).parent.joinpath("certs/ca-root.crt")
|
||||||
PYTEST_RUNNING_IN_CONTAINER = os.environ.get('PYTEST_RUNNING_IN_CONTAINER') == "1"
|
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
|
FORCE_CONTAINER_IPV6 = False # ugly global state to consider containers' IPv6 address instead of IPv4
|
||||||
|
|
||||||
|
@ -40,8 +47,9 @@ test_container = 'nginx-proxy-pytest'
|
||||||
#
|
#
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@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:
|
Meant to be used as a context manager to force IPv6 sockets:
|
||||||
|
|
||||||
|
@ -59,19 +67,19 @@ def ipv6(force_ipv6=True):
|
||||||
FORCE_CONTAINER_IPV6 = False
|
FORCE_CONTAINER_IPV6 = False
|
||||||
|
|
||||||
|
|
||||||
class requests_for_docker(object):
|
class RequestsForDocker:
|
||||||
"""
|
"""
|
||||||
Proxy for calling methods of the requests module.
|
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.
|
Provides method `get_conf` to extract the nginx-proxy configuration content.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
if os.path.isfile(CA_ROOT_CERTIFICATE):
|
if CA_ROOT_CERTIFICATE.is_file():
|
||||||
self.session.verify = CA_ROOT_CERTIFICATE
|
self.session.verify = CA_ROOT_CERTIFICATE.as_posix()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_nginx_proxy_containers() -> List[Container]:
|
def get_nginx_proxy_container() -> Container:
|
||||||
"""
|
"""
|
||||||
Return list of containers
|
Return list of containers
|
||||||
"""
|
"""
|
||||||
|
@ -80,69 +88,69 @@ class requests_for_docker(object):
|
||||||
pytest.fail("Too many running nginxproxy/nginx-proxy:test containers", pytrace=False)
|
pytest.fail("Too many running nginxproxy/nginx-proxy:test containers", pytrace=False)
|
||||||
elif len(nginx_proxy_containers) == 0:
|
elif len(nginx_proxy_containers) == 0:
|
||||||
pytest.fail("No running nginxproxy/nginx-proxy:test container", pytrace=False)
|
pytest.fail("No running nginxproxy/nginx-proxy:test container", pytrace=False)
|
||||||
return nginx_proxy_containers
|
return nginx_proxy_containers.pop()
|
||||||
|
|
||||||
def get_conf(self):
|
def get_conf(self) -> bytes:
|
||||||
"""
|
"""
|
||||||
Return the nginx config file
|
Return the nginx config file
|
||||||
"""
|
"""
|
||||||
nginx_proxy_containers = self.get_nginx_proxy_containers()
|
nginx_proxy_container = self.get_nginx_proxy_container()
|
||||||
return get_nginx_conf_from_container(nginx_proxy_containers[0])
|
return get_nginx_conf_from_container(nginx_proxy_container)
|
||||||
|
|
||||||
def get_ip(self) -> str:
|
def get_ip(self) -> str:
|
||||||
"""
|
"""
|
||||||
Return the nginx container ip address
|
Return the nginx container ip address
|
||||||
"""
|
"""
|
||||||
nginx_proxy_containers = self.get_nginx_proxy_containers()
|
nginx_proxy_container = self.get_nginx_proxy_container()
|
||||||
return container_ip(nginx_proxy_containers[0])
|
return container_ip(nginx_proxy_container)
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
def get(self, *args, **kwargs) -> Response:
|
||||||
with ipv6(kwargs.pop('ipv6', False)):
|
with ipv6(kwargs.pop('ipv6', False)):
|
||||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||||
def _get(*args, **kwargs):
|
def _get(*_args, **_kwargs):
|
||||||
return self.session.get(*args, **kwargs)
|
return self.session.get(*_args, **_kwargs)
|
||||||
return _get(*args, **kwargs)
|
return _get(*args, **kwargs)
|
||||||
|
|
||||||
def post(self, *args, **kwargs):
|
def post(self, *args, **kwargs) -> Response:
|
||||||
with ipv6(kwargs.pop('ipv6', False)):
|
with ipv6(kwargs.pop('ipv6', False)):
|
||||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||||
def _post(*args, **kwargs):
|
def _post(*_args, **_kwargs):
|
||||||
return self.session.post(*args, **kwargs)
|
return self.session.post(*_args, **_kwargs)
|
||||||
return _post(*args, **kwargs)
|
return _post(*args, **kwargs)
|
||||||
|
|
||||||
def put(self, *args, **kwargs):
|
def put(self, *args, **kwargs) -> Response:
|
||||||
with ipv6(kwargs.pop('ipv6', False)):
|
with ipv6(kwargs.pop('ipv6', False)):
|
||||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||||
def _put(*args, **kwargs):
|
def _put(*_args, **_kwargs):
|
||||||
return self.session.put(*args, **kwargs)
|
return self.session.put(*_args, **_kwargs)
|
||||||
return _put(*args, **kwargs)
|
return _put(*args, **kwargs)
|
||||||
|
|
||||||
def head(self, *args, **kwargs):
|
def head(self, *args, **kwargs) -> Response:
|
||||||
with ipv6(kwargs.pop('ipv6', False)):
|
with ipv6(kwargs.pop('ipv6', False)):
|
||||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||||
def _head(*args, **kwargs):
|
def _head(*_args, **_kwargs):
|
||||||
return self.session.head(*args, **kwargs)
|
return self.session.head(*_args, **_kwargs)
|
||||||
return _head(*args, **kwargs)
|
return _head(*args, **kwargs)
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs) -> Response:
|
||||||
with ipv6(kwargs.pop('ipv6', False)):
|
with ipv6(kwargs.pop('ipv6', False)):
|
||||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||||
def _delete(*args, **kwargs):
|
def _delete(*_args, **_kwargs):
|
||||||
return self.session.delete(*args, **kwargs)
|
return self.session.delete(*_args, **_kwargs)
|
||||||
return _delete(*args, **kwargs)
|
return _delete(*args, **kwargs)
|
||||||
|
|
||||||
def options(self, *args, **kwargs):
|
def options(self, *args, **kwargs) -> Response:
|
||||||
with ipv6(kwargs.pop('ipv6', False)):
|
with ipv6(kwargs.pop('ipv6', False)):
|
||||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||||
def _options(*args, **kwargs):
|
def _options(*_args, **_kwargs):
|
||||||
return self.session.options(*args, **kwargs)
|
return self.session.options(*_args, **_kwargs)
|
||||||
return _options(*args, **kwargs)
|
return _options(*args, **kwargs)
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
return getattr(requests, name)
|
return getattr(requests, name)
|
||||||
|
|
||||||
|
|
||||||
def container_ip(container: Container):
|
def container_ip(container: Container) -> str:
|
||||||
"""
|
"""
|
||||||
return the IP address of a container.
|
return the IP address of a container.
|
||||||
|
|
||||||
|
@ -171,7 +179,7 @@ def container_ip(container: Container):
|
||||||
return net_info[network_name]["IPAddress"]
|
return net_info[network_name]["IPAddress"]
|
||||||
|
|
||||||
|
|
||||||
def container_ipv6(container):
|
def container_ipv6(container: Container) -> str:
|
||||||
"""
|
"""
|
||||||
return the IPv6 address of a container.
|
return the IPv6 address of a container.
|
||||||
"""
|
"""
|
||||||
|
@ -188,7 +196,7 @@ def container_ipv6(container):
|
||||||
return net_info[network_name]["GlobalIPv6Address"]
|
return net_info[network_name]["GlobalIPv6Address"]
|
||||||
|
|
||||||
|
|
||||||
def nginx_proxy_dns_resolver(domain_name):
|
def nginx_proxy_dns_resolver(domain_name: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
if "nginx-proxy" if found in host, return the ip address of the docker container
|
if "nginx-proxy" if found in host, return the ip address of the docker container
|
||||||
issued from the docker image nginxproxy/nginx-proxy:test.
|
issued from the docker image nginxproxy/nginx-proxy:test.
|
||||||
|
@ -200,21 +208,21 @@ def nginx_proxy_dns_resolver(domain_name):
|
||||||
if 'nginx-proxy' in domain_name:
|
if 'nginx-proxy' in domain_name:
|
||||||
nginxproxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginxproxy/nginx-proxy:test"})
|
nginxproxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginxproxy/nginx-proxy:test"})
|
||||||
if len(nginxproxy_containers) == 0:
|
if len(nginxproxy_containers) == 0:
|
||||||
log.warn(f"no container found from image nginxproxy/nginx-proxy:test while resolving {domain_name!r}")
|
log.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"})
|
exited_nginxproxy_containers = docker_client.containers.list(filters={"status": "exited", "ancestor": "nginxproxy/nginx-proxy:test"})
|
||||||
if len(exited_nginxproxy_containers) > 0:
|
if len(exited_nginxproxy_containers) > 0:
|
||||||
exited_nginxproxy_container_logs = exited_nginxproxy_containers[0].logs()
|
exited_nginxproxy_container_logs = exited_nginxproxy_containers[0].logs()
|
||||||
log.warn(f"nginxproxy/nginx-proxy:test container might have exited unexpectedly. Container logs: " + "\n" + exited_nginxproxy_container_logs.decode())
|
log.warning(f"nginxproxy/nginx-proxy:test container might have exited unexpectedly. Container logs: " + "\n" + exited_nginxproxy_container_logs.decode())
|
||||||
return
|
return None
|
||||||
nginxproxy_container = nginxproxy_containers[0]
|
nginxproxy_container = nginxproxy_containers[0]
|
||||||
ip = container_ip(nginxproxy_container)
|
ip = container_ip(nginxproxy_container)
|
||||||
log.info(f"resolving domain name {domain_name!r} as IP address {ip} of nginx-proxy container {nginxproxy_container.name}")
|
log.info(f"resolving domain name {domain_name!r} as IP address {ip} of nginx-proxy container {nginxproxy_container.name}")
|
||||||
return ip
|
return ip
|
||||||
|
|
||||||
def docker_container_dns_resolver(domain_name):
|
def docker_container_dns_resolver(domain_name: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
if domain name is of the form "XXX.container.docker" or "anything.XXX.container.docker", return the ip address of the docker container
|
if domain name is of the form "XXX.container.docker" or "anything.XXX.container.docker",
|
||||||
named XXX.
|
return the ip address of the docker container named XXX.
|
||||||
|
|
||||||
:return: IP or None
|
:return: IP or None
|
||||||
"""
|
"""
|
||||||
|
@ -224,15 +232,15 @@ def docker_container_dns_resolver(domain_name):
|
||||||
match = re.search(r'(^|.+\.)(?P<container>[^.]+)\.container\.docker$', domain_name)
|
match = re.search(r'(^|.+\.)(?P<container>[^.]+)\.container\.docker$', domain_name)
|
||||||
if not match:
|
if not match:
|
||||||
log.debug(f"{domain_name!r} does not match")
|
log.debug(f"{domain_name!r} does not match")
|
||||||
return
|
return None
|
||||||
|
|
||||||
container_name = match.group('container')
|
container_name = match.group('container')
|
||||||
log.debug(f"looking for container {container_name!r}")
|
log.debug(f"looking for container {container_name!r}")
|
||||||
try:
|
try:
|
||||||
container = docker_client.containers.get(container_name)
|
container = docker_client.containers.get(container_name)
|
||||||
except docker.errors.NotFound:
|
except docker.errors.NotFound:
|
||||||
log.warn(f"container named {container_name!r} not found while resolving {domain_name!r}")
|
log.warning(f"container named {container_name!r} not found while resolving {domain_name!r}")
|
||||||
return
|
return None
|
||||||
log.debug(f"container {container.name!r} found ({container.short_id})")
|
log.debug(f"container {container.name!r} found ({container.short_id})")
|
||||||
|
|
||||||
ip = container_ip(container)
|
ip = container_ip(container)
|
||||||
|
@ -244,7 +252,10 @@ def monkey_patch_urllib_dns_resolver():
|
||||||
"""
|
"""
|
||||||
Alter the behavior of the urllib DNS resolver so that any domain name
|
Alter the behavior of the urllib DNS resolver so that any domain name
|
||||||
containing substring 'nginx-proxy' will resolve to the IP address
|
containing substring 'nginx-proxy' will resolve to the IP address
|
||||||
of the container created from image 'nginxproxy/nginx-proxy:test'.
|
of the container created from image 'nginxproxy/nginx-proxy:test',
|
||||||
|
or 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
|
prv_getaddrinfo = socket.getaddrinfo
|
||||||
dns_cache = {}
|
dns_cache = {}
|
||||||
|
@ -252,12 +263,17 @@ def monkey_patch_urllib_dns_resolver():
|
||||||
logging.getLogger('DNS').debug(f"resolving domain name {repr(args)}")
|
logging.getLogger('DNS').debug(f"resolving domain name {repr(args)}")
|
||||||
_args = list(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`.
|
# Otherwise a pytest container not using the host network fails to pass `test_raw-ip-vhost`.
|
||||||
if FORCE_CONTAINER_IPV6 and not HAS_IPV6:
|
if FORCE_CONTAINER_IPV6 and not HAS_IPV6:
|
||||||
pytest.skip("This system does not support IPv6")
|
pytest.skip("This system does not support IPv6")
|
||||||
|
|
||||||
# custom DNS resolvers
|
# 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_dns_resolver(args[0])
|
ip = nginx_proxy_dns_resolver(args[0])
|
||||||
if ip is None:
|
if ip is None:
|
||||||
ip = docker_container_dns_resolver(args[0])
|
ip = docker_container_dns_resolver(args[0])
|
||||||
|
@ -274,19 +290,12 @@ def monkey_patch_urllib_dns_resolver():
|
||||||
socket.getaddrinfo = new_getaddrinfo
|
socket.getaddrinfo = new_getaddrinfo
|
||||||
return prv_getaddrinfo
|
return prv_getaddrinfo
|
||||||
|
|
||||||
|
|
||||||
def restore_urllib_dns_resolver(getaddrinfo_func):
|
def restore_urllib_dns_resolver(getaddrinfo_func):
|
||||||
socket.getaddrinfo = getaddrinfo_func
|
socket.getaddrinfo = getaddrinfo_func
|
||||||
|
|
||||||
|
|
||||||
def remove_all_containers():
|
def get_nginx_conf_from_container(container: Container) -> bytes:
|
||||||
for container in docker_client.containers.list(all=True):
|
|
||||||
if PYTEST_RUNNING_IN_CONTAINER and container.name == test_container:
|
|
||||||
continue # pytest is running within a Docker container, so we do not want to remove that particular container
|
|
||||||
logging.info(f"removing container {container.name}")
|
|
||||||
container.remove(v=True, force=True)
|
|
||||||
|
|
||||||
|
|
||||||
def get_nginx_conf_from_container(container):
|
|
||||||
"""
|
"""
|
||||||
return the nginx /etc/nginx/conf.d/default.conf file content from a container
|
return the nginx /etc/nginx/conf.d/default.conf file content from a container
|
||||||
"""
|
"""
|
||||||
|
@ -301,20 +310,40 @@ def get_nginx_conf_from_container(container):
|
||||||
return conffile.read()
|
return conffile.read()
|
||||||
|
|
||||||
|
|
||||||
def docker_compose_up(compose_file='docker-compose.yml'):
|
def __prepare_and_execute_compose_cmd(compose_files: List[str], project_name: str, cmd: str):
|
||||||
logging.info(f'{DOCKER_COMPOSE} -f {compose_file} up -d')
|
"""
|
||||||
|
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:
|
try:
|
||||||
subprocess.check_output(shlex.split(f'{DOCKER_COMPOSE} -f {compose_file} up -d'), stderr=subprocess.STDOUT)
|
subprocess.check_output(shlex.split(compose_cmd.getvalue()), stderr=subprocess.STDOUT)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
pytest.fail(f"Error while runninng '{DOCKER_COMPOSE} -f {compose_file} up -d':\n{e.output}", pytrace=False)
|
pytest.fail(f"Error while running '{compose_cmd.getvalue()}':\n{e.output}", pytrace=False)
|
||||||
|
|
||||||
|
|
||||||
def docker_compose_down(compose_file='docker-compose.yml'):
|
def docker_compose_up(compose_files: List[str], project_name: str):
|
||||||
logging.info(f'{DOCKER_COMPOSE} -f {compose_file} down -v')
|
"""
|
||||||
try:
|
Execute compose up --detach with the provided compose files and project name.
|
||||||
subprocess.check_output(shlex.split(f'{DOCKER_COMPOSE} -f {compose_file} down -v'), stderr=subprocess.STDOUT)
|
"""
|
||||||
except subprocess.CalledProcessError as e:
|
if compose_files is None or len(compose_files) == 0:
|
||||||
pytest.fail(f"Error while runninng '{DOCKER_COMPOSE} -f {compose_file} down -v':\n{e.output}", pytrace=False)
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
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():
|
def wait_for_nginxproxy_to_be_ready():
|
||||||
|
@ -333,35 +362,50 @@ def wait_for_nginxproxy_to_be_ready():
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def docker_compose_file(request):
|
def docker_compose_files(request: FixtureRequest) -> List[str]:
|
||||||
"""Fixture naming the docker compose file to consider.
|
"""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
|
If a YAML file exists with the same name as the test module (with the `.py` extension
|
||||||
with `.yml` or `.yaml`), use that. Otherwise, use `docker-compose.yml` in the same directory
|
replaced with `.base.yml`, ie `test_foo.py`-> `test_foo.base.yml`) and in the same
|
||||||
as the test module.
|
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.
|
Tests can override this fixture to specify a custom location.
|
||||||
"""
|
"""
|
||||||
test_module_dir = os.path.dirname(request.module.__file__)
|
compose_files: List[str] = []
|
||||||
yml_file = os.path.join(test_module_dir, request.module.__name__ + '.yml')
|
test_module_path = pathlib.Path(request.module.__file__).parent
|
||||||
yaml_file = os.path.join(test_module_dir, request.module.__name__ + '.yaml')
|
|
||||||
default_file = os.path.join(test_module_dir, 'docker-compose.yml')
|
|
||||||
|
|
||||||
if os.path.isfile(yml_file):
|
module_base_file = test_module_path.joinpath(f"{request.module.__name__}.base.yml")
|
||||||
docker_compose_file = yml_file
|
if module_base_file.is_file():
|
||||||
elif os.path.isfile(yaml_file):
|
return [module_base_file.as_posix()]
|
||||||
docker_compose_file = yaml_file
|
|
||||||
else:
|
|
||||||
docker_compose_file = default_file
|
|
||||||
|
|
||||||
if not os.path.isfile(docker_compose_file):
|
global_base_file = test_module_path.parent.joinpath("compose.base.yml")
|
||||||
logging.error("Could not find any docker compose file named either '{0}.yml', '{0}.yaml' or 'docker-compose.yml'".format(request.module.__name__))
|
if global_base_file.is_file():
|
||||||
|
compose_files.append(global_base_file.as_posix())
|
||||||
|
|
||||||
logging.debug(f"using docker compose file {docker_compose_file}")
|
module_base_override_file = test_module_path.joinpath("compose.base.override.yml")
|
||||||
return docker_compose_file
|
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
|
If we are running from a container, connect our container to the given network
|
||||||
|
|
||||||
|
@ -371,8 +415,8 @@ def connect_to_network(network):
|
||||||
try:
|
try:
|
||||||
my_container = docker_client.containers.get(test_container)
|
my_container = docker_client.containers.get(test_container)
|
||||||
except docker.errors.NotFound:
|
except docker.errors.NotFound:
|
||||||
logging.warn(f"container {test_container} not found")
|
logging.warning(f"container {test_container} not found")
|
||||||
return
|
return None
|
||||||
|
|
||||||
# figure out our container networks
|
# figure out our container networks
|
||||||
my_networks = list(my_container.attrs["NetworkSettings"]["Networks"].keys())
|
my_networks = list(my_container.attrs["NetworkSettings"]["Networks"].keys())
|
||||||
|
@ -389,7 +433,7 @@ def connect_to_network(network):
|
||||||
return 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.
|
If we are running from a container, disconnect our container from the given network.
|
||||||
|
|
||||||
|
@ -399,7 +443,7 @@ def disconnect_from_network(network=None):
|
||||||
try:
|
try:
|
||||||
my_container = docker_client.containers.get(test_container)
|
my_container = docker_client.containers.get(test_container)
|
||||||
except docker.errors.NotFound:
|
except docker.errors.NotFound:
|
||||||
logging.warn(f"container {test_container} not found")
|
logging.warning(f"container {test_container} not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
# figure out our container networks
|
# figure out our container networks
|
||||||
|
@ -411,7 +455,7 @@ def disconnect_from_network(network=None):
|
||||||
network.disconnect(my_container)
|
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.
|
If we are running from a container, connect our container to all current docker networks.
|
||||||
|
|
||||||
|
@ -427,31 +471,34 @@ def connect_to_all_networks():
|
||||||
|
|
||||||
class DockerComposer(contextlib.AbstractContextManager):
|
class DockerComposer(contextlib.AbstractContextManager):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._docker_compose_file = None
|
self._networks = None
|
||||||
|
self._docker_compose_files = None
|
||||||
|
self._project_name = None
|
||||||
|
|
||||||
def __exit__(self, *exc_info):
|
def __exit__(self, *exc_info):
|
||||||
self._down()
|
self._down()
|
||||||
|
|
||||||
def _down(self):
|
def _down(self):
|
||||||
if self._docker_compose_file is None:
|
if self._docker_compose_files is None:
|
||||||
return
|
return
|
||||||
for network in self._networks:
|
for network in self._networks:
|
||||||
disconnect_from_network(network)
|
disconnect_from_network(network)
|
||||||
docker_compose_down(self._docker_compose_file)
|
docker_compose_down(self._docker_compose_files, self._project_name)
|
||||||
self._docker_compose_file = None
|
self._docker_compose_file = None
|
||||||
|
self._project_name = None
|
||||||
|
|
||||||
def compose(self, docker_compose_file):
|
def compose(self, docker_compose_files: List[str], project_name: str):
|
||||||
if docker_compose_file == self._docker_compose_file:
|
if docker_compose_files == self._docker_compose_files and project_name == self._project_name:
|
||||||
return
|
return
|
||||||
self._down()
|
self._down()
|
||||||
if docker_compose_file is None:
|
if docker_compose_files is None or project_name is None:
|
||||||
return
|
return
|
||||||
remove_all_containers()
|
docker_compose_up(docker_compose_files, project_name)
|
||||||
docker_compose_up(docker_compose_file)
|
|
||||||
self._networks = connect_to_all_networks()
|
self._networks = connect_to_all_networks()
|
||||||
wait_for_nginxproxy_to_be_ready()
|
wait_for_nginxproxy_to_be_ready()
|
||||||
time.sleep(3) # give time to containers to be ready
|
time.sleep(3) # give time to containers to be ready
|
||||||
self._docker_compose_file = docker_compose_file
|
self._docker_compose_files = docker_compose_files
|
||||||
|
self._project_name = project_name
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
@ -462,14 +509,14 @@ class DockerComposer(contextlib.AbstractContextManager):
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def docker_composer():
|
def docker_composer() -> Iterator[DockerComposer]:
|
||||||
with DockerComposer() as d:
|
with DockerComposer() as d:
|
||||||
yield d
|
yield d
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def ca_root_certificate():
|
def ca_root_certificate() -> str:
|
||||||
return CA_ROOT_CERTIFICATE
|
return CA_ROOT_CERTIFICATE.as_posix()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -480,25 +527,38 @@ def monkey_patched_dns():
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def docker_compose(monkey_patched_dns, docker_composer, docker_compose_file):
|
def docker_compose(
|
||||||
"""Ensures containers described in a docker compose file are started.
|
request: FixtureRequest,
|
||||||
|
monkeypatch,
|
||||||
A custom docker compose file name can be specified by overriding the `docker_compose_file`
|
monkey_patched_dns,
|
||||||
fixture.
|
docker_composer,
|
||||||
|
docker_compose_files
|
||||||
Also, in the case where pytest is running from a docker container, this fixture makes sure
|
) -> Iterator[DockerClient]:
|
||||||
our container will be attached to all the docker networks.
|
|
||||||
"""
|
"""
|
||||||
docker_composer.compose(docker_compose_file)
|
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(docker_compose_files, project_name)
|
||||||
|
|
||||||
yield docker_client
|
yield docker_client
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture
|
||||||
def nginxproxy():
|
def nginxproxy() -> Iterator[RequestsForDocker]:
|
||||||
"""
|
"""
|
||||||
Provides the `nginxproxy` object that can be used in the same way the requests module is:
|
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
|
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.
|
indicates that nginx has just reloaded), we retry up to 30 times the query.
|
||||||
|
@ -507,23 +567,29 @@ def nginxproxy():
|
||||||
made against containers to use the containers IPv6 address when set to `True`. If IPv6 is not
|
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.
|
supported by the system or docker, that particular test will be skipped.
|
||||||
"""
|
"""
|
||||||
yield requests_for_docker()
|
yield RequestsForDocker()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def acme_challenge_path() -> str:
|
||||||
|
"""
|
||||||
|
Provides fake Let's Encrypt ACME challenge path used in certain tests
|
||||||
|
"""
|
||||||
|
return ".well-known/acme-challenge/test-filename"
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
#
|
#
|
||||||
# Py.test hooks
|
# Py.test hooks
|
||||||
#
|
#
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
# pytest hook to display additionnal stuff in test report
|
# pytest hook to display additional stuff in test report
|
||||||
def pytest_runtest_logreport(report):
|
def pytest_runtest_logreport(report):
|
||||||
if report.failed:
|
if report.failed:
|
||||||
if isinstance(report.longrepr, ReprExceptionInfo):
|
|
||||||
test_containers = docker_client.containers.list(all=True, filters={"ancestor": "nginxproxy/nginx-proxy:test"})
|
test_containers = docker_client.containers.list(all=True, filters={"ancestor": "nginxproxy/nginx-proxy:test"})
|
||||||
for container in test_containers:
|
for container in test_containers:
|
||||||
report.longrepr.addsection('nginx-proxy logs', container.logs())
|
report.longrepr.addsection('nginx-proxy logs', container.logs().decode())
|
||||||
report.longrepr.addsection('nginx-proxy conf', get_nginx_conf_from_container(container))
|
report.longrepr.addsection('nginx-proxy conf', get_nginx_conf_from_container(container).decode())
|
||||||
|
|
||||||
|
|
||||||
# Py.test `incremental` marker, see http://stackoverflow.com/a/12579625/107049
|
# Py.test `incremental` marker, see http://stackoverflow.com/a/12579625/107049
|
||||||
|
@ -550,5 +616,5 @@ try:
|
||||||
except docker.errors.ImageNotFound:
|
except docker.errors.ImageNotFound:
|
||||||
pytest.exit("The docker image 'nginxproxy/nginx-proxy:test' is missing")
|
pytest.exit("The docker image 'nginxproxy/nginx-proxy:test' is missing")
|
||||||
|
|
||||||
if LooseVersion(docker.__version__) < LooseVersion("5.0.0"):
|
if Version(docker.__version__) < Version("7.0.0"):
|
||||||
pytest.exit("This test suite is meant to work with the python docker module v5.0.0 or later")
|
pytest.exit("This test suite is meant to work with the python docker module v7.0.0 or later")
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# #
|
# #
|
||||||
# This script is meant to run the test suite from a Docker container. #
|
# 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. #
|
# Docker Toolbox. #
|
||||||
# #
|
# #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
|
@ -1,9 +1,35 @@
|
||||||
FROM python:3.9
|
FROM python:3.12
|
||||||
|
|
||||||
ENV PYTEST_RUNNING_IN_CONTAINER=1
|
ENV PYTEST_RUNNING_IN_CONTAINER=1
|
||||||
|
|
||||||
COPY python-requirements.txt /requirements.txt
|
COPY python-requirements.txt /requirements.txt
|
||||||
RUN pip install -r /requirements.txt
|
RUN pip install -r /requirements.txt
|
||||||
|
|
||||||
|
# Add Docker's official GPG key
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
&& install -m 0755 -d /etc/apt/keyrings \
|
||||||
|
&& curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc \
|
||||||
|
&& chmod a+r /etc/apt/keyrings/docker.asc
|
||||||
|
|
||||||
|
# Add the Docker repository to Apt sources
|
||||||
|
RUN echo \
|
||||||
|
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
|
||||||
|
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
|
||||||
|
tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
|
||||||
|
# Install docker-ce-cli and docker-compose-plugin requirements for Pytest docker_compose fixture
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
docker-ce-cli \
|
||||||
|
docker-compose-plugin \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -r /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Check if docker compose is available
|
||||||
|
RUN docker compose version
|
||||||
|
|
||||||
WORKDIR /test
|
WORKDIR /test
|
||||||
ENTRYPOINT ["pytest"]
|
ENTRYPOINT ["pytest"]
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
backoff==2.2.1
|
backoff==2.2.1
|
||||||
docker==7.0.0
|
docker==7.1.0
|
||||||
pytest==7.4.3
|
packaging==24.2
|
||||||
requests==2.31.0
|
pytest==8.3.4
|
||||||
|
requests==2.32.3
|
||||||
|
urllib3==2.3.0
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Docker Image running one (or multiple) webservers listening on all given ports from WEB_PORTS environment variable
|
# Docker Image running one (or multiple) webservers listening on all given ports from WEB_PORTS environment variable
|
||||||
|
|
||||||
FROM python:3
|
FROM python:3-alpine
|
||||||
|
RUN apk add --no-cache bash
|
||||||
COPY ./webserver.py /
|
COPY ./webserver.py /
|
||||||
COPY ./entrypoint.sh /
|
COPY ./entrypoint.sh /
|
||||||
WORKDIR /opt
|
WORKDIR /opt
|
||||||
|
|
|
@ -5,11 +5,11 @@ trap '[ ${#PIDS[@]} -gt 0 ] && kill -TERM ${PIDS[@]}' TERM
|
||||||
declare -a PIDS
|
declare -a PIDS
|
||||||
|
|
||||||
for port in $WEB_PORTS; do
|
for port in $WEB_PORTS; do
|
||||||
echo starting a web server listening on port $port;
|
echo starting a web server listening on port "$port";
|
||||||
/webserver.py $port &
|
/webserver.py "$port" &
|
||||||
PIDS+=($!)
|
PIDS+=($!)
|
||||||
done
|
done
|
||||||
|
|
||||||
wait ${PIDS[@]}
|
wait "${PIDS[@]}"
|
||||||
trap - TERM
|
trap - TERM
|
||||||
wait ${PIDS[@]}
|
wait "${PIDS[@]}"
|
||||||
|
|
|
@ -14,8 +14,8 @@ class Handler(http.server.SimpleHTTPRequestHandler):
|
||||||
response_body += self.headers.as_string()
|
response_body += self.headers.as_string()
|
||||||
elif self.path == "/port":
|
elif self.path == "/port":
|
||||||
response_body += f"answer from port {PORT}\n"
|
response_body += f"answer from port {PORT}\n"
|
||||||
elif re.match("/status/(\d+)", self.path):
|
elif re.match(r"/status/(\d+)", self.path):
|
||||||
result = re.match("/status/(\d+)", self.path)
|
result = re.match(r"/status/(\d+)", self.path)
|
||||||
response_code = int(result.group(1))
|
response_code = int(result.group(1))
|
||||||
response_body += f"answer with response code {response_code}\n"
|
response_body += f"answer with response code {response_code}\n"
|
||||||
elif self.path == "/":
|
elif self.path == "/":
|
||||||
|
@ -28,7 +28,7 @@ class Handler(http.server.SimpleHTTPRequestHandler):
|
||||||
self.send_header("Content-Type", "text/plain")
|
self.send_header("Content-Type", "text/plain")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
if (len(response_body)):
|
if len(response_body):
|
||||||
self.wfile.write(response_body.encode())
|
self.wfile.write(response_body.encode())
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
This directory contains tests that showcase scenarios known to break the expected behavior of nginx-proxy.
|
|
|
@ -1,5 +0,0 @@
|
||||||
Test the behavior of nginx-proxy when restarted after deleting a certificate file is was using.
|
|
||||||
|
|
||||||
1. nginx-proxy is created with a virtual host having a certificate
|
|
||||||
1. while nginx-proxy is running, the certificate file is deleted
|
|
||||||
1. nginx-proxy is then restarted (without removing the container)
|
|
|
@ -1,70 +0,0 @@
|
||||||
Certificate:
|
|
||||||
Data:
|
|
||||||
Version: 3 (0x2)
|
|
||||||
Serial Number: 4096 (0x1000)
|
|
||||||
Signature Algorithm: sha256WithRSAEncryption
|
|
||||||
Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld
|
|
||||||
Validity
|
|
||||||
Not Before: Feb 17 23:20:54 2017 GMT
|
|
||||||
Not After : Jul 5 23:20:54 2044 GMT
|
|
||||||
Subject: CN=web.nginx-proxy
|
|
||||||
Subject Public Key Info:
|
|
||||||
Public Key Algorithm: rsaEncryption
|
|
||||||
Public-Key: (2048 bit)
|
|
||||||
Modulus:
|
|
||||||
00:b6:27:63:a5:c6:e8:f4:7a:94:0e:cc:a2:62:76:
|
|
||||||
6d:5d:33:6f:cf:19:fc:e7:e5:bb:0e:0e:d0:7c:4f:
|
|
||||||
73:4c:48:2b:17:d1:4d:d5:9f:42:08:73:84:54:8c:
|
|
||||||
86:d2:c5:da:59:01:3f:42:22:e0:36:f0:dc:ab:de:
|
|
||||||
0a:bd:26:2b:22:13:87:a6:1f:23:ef:0e:99:27:8b:
|
|
||||||
15:4a:1b:ef:93:c9:6b:91:de:a0:02:0c:62:bb:cc:
|
|
||||||
56:37:e8:25:92:c3:1f:f1:69:d8:7c:a8:33:e0:89:
|
|
||||||
ce:14:67:a0:39:77:88:91:e6:a3:07:97:90:22:88:
|
|
||||||
d0:79:18:63:fb:6f:7e:ee:2b:42:7e:23:f5:e7:da:
|
|
||||||
e9:ee:6a:fa:96:65:9f:e1:2b:15:49:c8:cd:2d:ce:
|
|
||||||
86:4f:2c:2a:67:79:bf:41:30:14:cc:f6:0f:14:74:
|
|
||||||
9e:b6:d3:d0:3b:f0:1b:b8:e8:19:2a:fd:d6:fd:dc:
|
|
||||||
4b:4e:65:7d:9b:bf:37:7e:2d:35:22:2e:74:90:ce:
|
|
||||||
41:35:3d:41:a0:99:db:97:1f:bf:3e:18:3c:48:fb:
|
|
||||||
da:df:c6:4e:4e:b9:67:b8:10:d5:a5:13:03:c4:b7:
|
|
||||||
65:e7:aa:f0:14:4b:d3:4d:ea:fe:8f:69:cf:50:21:
|
|
||||||
63:27:cf:9e:4c:67:15:7b:3f:3b:da:cb:17:80:61:
|
|
||||||
1e:25
|
|
||||||
Exponent: 65537 (0x10001)
|
|
||||||
X509v3 extensions:
|
|
||||||
X509v3 Subject Alternative Name:
|
|
||||||
DNS:web.nginx-proxy
|
|
||||||
Signature Algorithm: sha256WithRSAEncryption
|
|
||||||
09:31:be:db:4e:b0:b6:68:da:ae:5b:16:51:29:fc:9f:61:b6:
|
|
||||||
5a:2f:3c:35:ef:67:76:97:b0:34:4e:3b:b4:d6:88:19:4f:84:
|
|
||||||
2e:73:d3:c0:3a:4c:41:54:6c:bb:67:89:67:ad:25:55:d7:d4:
|
|
||||||
80:fe:a7:3f:3d:9e:f1:34:96:d8:da:5a:78:51:c0:63:f1:52:
|
|
||||||
29:35:55:f4:7d:70:1c:d3:96:62:7f:64:86:81:52:27:c4:c6:
|
|
||||||
10:13:c6:73:56:4d:32:d0:b3:c3:c8:2c:25:83:e4:2b:1d:d4:
|
|
||||||
74:30:e5:85:af:2d:b6:a5:6b:fe:5d:d3:3c:00:58:94:f4:6a:
|
|
||||||
f5:a6:1d:cf:f9:ed:d5:27:ed:13:24:b2:4f:2b:f3:b8:e4:af:
|
|
||||||
0c:1d:fe:e0:6a:01:5e:a2:44:ff:3e:96:fa:6c:39:a3:51:37:
|
|
||||||
f3:72:55:d8:2d:29:6e:de:95:b9:d8:e3:1e:65:a5:9c:0d:79:
|
|
||||||
2d:39:ab:c7:ac:16:b6:a5:71:4b:35:a4:6c:72:47:1b:72:9c:
|
|
||||||
67:58:c1:fc:f6:7f:a7:73:50:7b:d6:27:57:74:a1:31:38:a7:
|
|
||||||
31:e3:b9:d4:c9:45:33:ec:ed:16:cf:c5:bd:d0:03:b1:45:3f:
|
|
||||||
68:0d:91:5c:26:4e:37:05:74:ed:3e:75:5e:ca:5e:ee:e2:51:
|
|
||||||
4b:da:08:99
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIC8zCCAdugAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp
|
|
||||||
bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs
|
|
||||||
ZDAeFw0xNzAyMTcyMzIwNTRaFw00NDA3MDUyMzIwNTRaMBoxGDAWBgNVBAMMD3dl
|
|
||||||
Yi5uZ2lueC1wcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALYn
|
|
||||||
Y6XG6PR6lA7MomJ2bV0zb88Z/Ofluw4O0HxPc0xIKxfRTdWfQghzhFSMhtLF2lkB
|
|
||||||
P0Ii4Dbw3KveCr0mKyITh6YfI+8OmSeLFUob75PJa5HeoAIMYrvMVjfoJZLDH/Fp
|
|
||||||
2HyoM+CJzhRnoDl3iJHmoweXkCKI0HkYY/tvfu4rQn4j9efa6e5q+pZln+ErFUnI
|
|
||||||
zS3Ohk8sKmd5v0EwFMz2DxR0nrbT0DvwG7joGSr91v3cS05lfZu/N34tNSIudJDO
|
|
||||||
QTU9QaCZ25cfvz4YPEj72t/GTk65Z7gQ1aUTA8S3Zeeq8BRL003q/o9pz1AhYyfP
|
|
||||||
nkxnFXs/O9rLF4BhHiUCAwEAAaMeMBwwGgYDVR0RBBMwEYIPd2ViLm5naW54LXBy
|
|
||||||
b3h5MA0GCSqGSIb3DQEBCwUAA4IBAQAJMb7bTrC2aNquWxZRKfyfYbZaLzw172d2
|
|
||||||
l7A0Tju01ogZT4Quc9PAOkxBVGy7Z4lnrSVV19SA/qc/PZ7xNJbY2lp4UcBj8VIp
|
|
||||||
NVX0fXAc05Zif2SGgVInxMYQE8ZzVk0y0LPDyCwlg+QrHdR0MOWFry22pWv+XdM8
|
|
||||||
AFiU9Gr1ph3P+e3VJ+0TJLJPK/O45K8MHf7gagFeokT/Ppb6bDmjUTfzclXYLSlu
|
|
||||||
3pW52OMeZaWcDXktOavHrBa2pXFLNaRsckcbcpxnWMH89n+nc1B71idXdKExOKcx
|
|
||||||
47nUyUUz7O0Wz8W90AOxRT9oDZFcJk43BXTtPnVeyl7u4lFL2giZ
|
|
||||||
-----END CERTIFICATE-----
|
|
|
@ -1,27 +0,0 @@
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEowIBAAKCAQEAtidjpcbo9HqUDsyiYnZtXTNvzxn85+W7Dg7QfE9zTEgrF9FN
|
|
||||||
1Z9CCHOEVIyG0sXaWQE/QiLgNvDcq94KvSYrIhOHph8j7w6ZJ4sVShvvk8lrkd6g
|
|
||||||
Agxiu8xWN+glksMf8WnYfKgz4InOFGegOXeIkeajB5eQIojQeRhj+29+7itCfiP1
|
|
||||||
59rp7mr6lmWf4SsVScjNLc6GTywqZ3m/QTAUzPYPFHSettPQO/AbuOgZKv3W/dxL
|
|
||||||
TmV9m783fi01Ii50kM5BNT1BoJnblx+/Phg8SPva38ZOTrlnuBDVpRMDxLdl56rw
|
|
||||||
FEvTTer+j2nPUCFjJ8+eTGcVez872ssXgGEeJQIDAQABAoIBAGQCMFW+ZfyEqHGP
|
|
||||||
rMA+oUEAkqy0agSwPwky3QjDXlxNa0uCYSeebtTRB6CcHxHuCzm+04puN4gyqhW6
|
|
||||||
rU64fAoTivCMPGBuNWxekmvD9r+/YM4P2u4E+th9EgFT9f0kII+dO30FpKXtQzY0
|
|
||||||
xuWGWXcxl+T9M+eiEkPKPmq4BoqgTDo5ty7qDv0ZqksGotKFmdYbtSvgBAueJdwu
|
|
||||||
VWJvenI9F42ExBRKOW1aldiRiaYBCLiCVPKJtOg9iuOP9RHUL1SE8xy5I5mm78g3
|
|
||||||
a13ji3BNq3yS+VhGjQ7zDy1V1jGupLoJw4I7OThu8hy+B8Vt8EN/iqakufOkjlTN
|
|
||||||
xTJ33CkCgYEA5Iymg0NTjWk6aEkFa9pERjfUWqdVp9sWSpFFZZgi55n7LOx6ohi3
|
|
||||||
vuLim3is/gYfK2kU/kHGZZLPnT0Rdx0MbOB4XK0CAUlqtUd0IyO4jMZ06g4/kn3N
|
|
||||||
e2jLdCCIBoEQuLk4ELxj2mHsLQhEvDrg7nzU2WpTHHhvJbIbDWOAxhsCgYEAzAgv
|
|
||||||
rKpanF+QDf4yeKHxAj2rrwRksTw4Pe7ZK/bog/i+HIVDA70vMapqftHbual/IRrB
|
|
||||||
JL7hxskoJ/h9c1w4xkWDjqkSKz8/Ihr4dyPfWyGINWbx/rarT/m5MU5SarScoK7o
|
|
||||||
Xgb25x+W+61rtI+2JhVRGO86+JiAeT4LkAX88L8CgYAwHHug/jdEeXZWJakCfzwI
|
|
||||||
HBCT1M3vO+uBXvtg25ndb0i0uENIhDOJ93EEkW65Osis9r34mBgPocwaqZRXosHO
|
|
||||||
2aH8wF6/rpjL+HK2QvrCh7Rs4Pr494qeA/1wQLjhxaGjgToQK9hJTHvPLwJpLWvU
|
|
||||||
SGr2Ka+9Oo0LPmb7dorRKQKBgQCLsNcjOodLJMp2KiHYIdfmlt6itzlRd09yZ8Nc
|
|
||||||
rHHJWVagJEUbnD1hnbHIHlp3pSqbObwfMmlWNoc9xo3tm6hrZ1CJLgx4e5b3/Ms8
|
|
||||||
ltznge/F0DPDFsH3wZwfu+YFlJ7gDKCfL9l/qEsxCS0CtJobPOEHV1NivNbJK8ey
|
|
||||||
1ca19QKBgDTdMOUsobAmDEkPQIpxfK1iqYAB7hpRLi79OOhLp23NKeyRNu8FH9fo
|
|
||||||
G3DZ4xUi6hP2bwiYugMXDyLKfvxbsXwQC84kGF8j+bGazKNhHqEC1OpYwmaTB3kg
|
|
||||||
qL9cHbjWySeRdIsRY/eWmiKjUwmiO54eAe1HWUdcsuz8yM3xf636
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
|
@ -1,17 +0,0 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
|
||||||
web:
|
|
||||||
image: web
|
|
||||||
expose:
|
|
||||||
- "81"
|
|
||||||
environment:
|
|
||||||
WEB_PORTS: 81
|
|
||||||
VIRTUAL_HOST: web.nginx-proxy
|
|
||||||
|
|
||||||
reverseproxy:
|
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
container_name: reverseproxy
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
|
||||||
- ./tmp_certs:/etc/nginx/certs:ro
|
|
|
@ -1,72 +0,0 @@
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from os.path import join, isfile
|
|
||||||
from shutil import copy
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from requests import ConnectionError
|
|
||||||
|
|
||||||
script_dir = os.path.dirname(__file__)
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.xfail() # TODO delete this marker once those issues are fixed
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module", autouse=True)
|
|
||||||
def certs():
|
|
||||||
"""
|
|
||||||
pytest fixture that provides cert and key files into the tmp_certs directory
|
|
||||||
"""
|
|
||||||
file_names = ("web.nginx-proxy.crt", "web.nginx-proxy.key")
|
|
||||||
logging.info("copying server cert and key files into tmp_certs")
|
|
||||||
for f_name in file_names:
|
|
||||||
copy(join(script_dir, "certs", f_name), join(script_dir, "tmp_certs"))
|
|
||||||
yield
|
|
||||||
logging.info("cleaning up the tmp_cert directory")
|
|
||||||
for f_name in file_names:
|
|
||||||
if isfile(join(script_dir, "tmp_certs", f_name)):
|
|
||||||
os.remove(join(script_dir, "tmp_certs", f_name))
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
|
|
||||||
def test_unknown_virtual_host_is_503(docker_compose, nginxproxy):
|
|
||||||
r = nginxproxy.get("http://foo.nginx-proxy/")
|
|
||||||
assert r.status_code == 503
|
|
||||||
|
|
||||||
|
|
||||||
def test_http_web_is_301(docker_compose, nginxproxy):
|
|
||||||
r = nginxproxy.get("http://web.nginx-proxy/port", allow_redirects=False)
|
|
||||||
assert r.status_code == 301
|
|
||||||
|
|
||||||
|
|
||||||
def test_https_web_is_200(docker_compose, nginxproxy):
|
|
||||||
r = nginxproxy.get("https://web.nginx-proxy/port")
|
|
||||||
assert r.status_code == 200
|
|
||||||
assert "answer from port 81\n" in r.text
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.incremental
|
|
||||||
def test_delete_cert_and_restart_reverseproxy(docker_compose):
|
|
||||||
os.remove(join(script_dir, "tmp_certs", "web.nginx-proxy.crt"))
|
|
||||||
docker_compose.containers.get("reverseproxy").restart()
|
|
||||||
sleep(3) # give time for the container to initialize
|
|
||||||
assert "running" == docker_compose.containers.get("reverseproxy").status
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.incremental
|
|
||||||
def test_unknown_virtual_host_is_still_503(nginxproxy):
|
|
||||||
r = nginxproxy.get("http://foo.nginx-proxy/")
|
|
||||||
assert r.status_code == 503
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.incremental
|
|
||||||
def test_http_web_is_now_200(nginxproxy):
|
|
||||||
r = nginxproxy.get("http://web.nginx-proxy/port", allow_redirects=False)
|
|
||||||
assert r.status_code == 200
|
|
||||||
assert "answer from port 81\n" == r.text
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.incremental
|
|
||||||
def test_https_web_is_now_broken_since_there_is_no_cert(nginxproxy):
|
|
||||||
with pytest.raises(ConnectionError):
|
|
||||||
nginxproxy.get("https://web.nginx-proxy/port")
|
|
|
@ -1,2 +0,0 @@
|
||||||
*
|
|
||||||
!.gitignore
|
|
|
@ -0,0 +1 @@
|
||||||
|
challenge-teststring
|
|
@ -0,0 +1,70 @@
|
||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number: 4096 (0x1000)
|
||||||
|
Signature Algorithm: sha256WithRSAEncryption
|
||||||
|
Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld
|
||||||
|
Validity
|
||||||
|
Not Before: Jan 10 00:08:52 2017 GMT
|
||||||
|
Not After : May 28 00:08:52 2044 GMT
|
||||||
|
Subject: CN=*.nginx-proxy.tld
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: rsaEncryption
|
||||||
|
Public-Key: (2048 bit)
|
||||||
|
Modulus:
|
||||||
|
00:cb:45:f4:14:9b:fe:64:85:79:4a:36:8d:3d:d1:
|
||||||
|
27:d0:7c:36:28:30:e6:73:80:6f:7c:49:23:d0:6c:
|
||||||
|
17:e4:44:c0:77:4d:9a:c2:bc:24:84:e3:a5:4d:ba:
|
||||||
|
d2:da:51:7b:a1:2a:12:d4:c0:19:55:69:2c:22:27:
|
||||||
|
2d:1a:f6:fc:4b:7f:e9:cb:a8:3c:e8:69:b8:d2:4f:
|
||||||
|
de:4e:50:e2:d0:74:30:7c:42:5a:ae:aa:85:a5:b1:
|
||||||
|
71:4d:c9:7e:86:8b:62:8c:3e:0d:e3:3b:c3:f5:81:
|
||||||
|
0b:8c:68:79:fe:bf:10:fb:ae:ec:11:49:6d:64:5e:
|
||||||
|
1a:7d:b3:92:93:4e:96:19:3a:98:04:a7:66:b2:74:
|
||||||
|
61:2d:41:13:0c:a4:54:0d:2c:78:fd:b4:a3:e8:37:
|
||||||
|
78:9a:de:fa:bc:2e:a8:0f:67:14:58:ce:c3:87:d5:
|
||||||
|
14:0e:8b:29:7d:48:19:b2:a9:f5:b4:e8:af:32:21:
|
||||||
|
67:15:7e:43:52:8b:20:cf:9f:38:43:bf:fd:c8:24:
|
||||||
|
7f:52:a3:88:f2:f1:4a:14:91:2a:6e:91:6f:fb:7d:
|
||||||
|
6a:78:c6:6d:2e:dd:1e:4c:2b:63:bb:3a:43:9c:91:
|
||||||
|
f9:df:d3:08:13:63:86:7d:ce:e8:46:cf:f1:6c:1f:
|
||||||
|
ca:f7:4c:de:d8:4b:e0:da:bc:06:d9:87:0f:ff:96:
|
||||||
|
45:85
|
||||||
|
Exponent: 65537 (0x10001)
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Subject Alternative Name:
|
||||||
|
DNS:*.nginx-proxy.tld
|
||||||
|
Signature Algorithm: sha256WithRSAEncryption
|
||||||
|
6e:a5:0e:e4:d3:cc:d5:b7:fc:34:75:89:4e:98:8c:e7:08:06:
|
||||||
|
a8:5b:ec:13:7d:83:99:a2:61:b8:d5:12:6e:c5:b4:53:4e:9a:
|
||||||
|
22:cd:ad:14:30:6a:7d:58:d7:23:d9:a4:2a:96:a0:40:9e:50:
|
||||||
|
9f:ce:f2:fe:8c:dd:9a:ac:99:39:5b:89:2d:ca:e5:3e:c3:bc:
|
||||||
|
03:04:1c:12:d9:6e:b8:9f:f0:3a:be:12:44:7e:a4:21:86:73:
|
||||||
|
af:d5:00:51:3f:2c:56:70:34:8f:26:b0:7f:b0:cf:cf:7f:f9:
|
||||||
|
40:6f:00:29:c4:cf:c3:b7:c2:49:3d:3f:b0:26:78:87:b9:c7:
|
||||||
|
6c:1b:aa:6a:1a:dd:c5:eb:f2:69:ba:6d:46:0b:92:49:b5:11:
|
||||||
|
3c:eb:48:c7:2f:fb:33:a6:6a:82:a2:ab:f8:1e:5f:7d:e3:b7:
|
||||||
|
f2:fd:f5:88:a5:09:4d:a0:bc:f4:3b:cd:d2:8b:d7:57:1f:86:
|
||||||
|
3b:d2:3e:a4:92:21:b0:02:0b:e9:e0:c4:1c:f1:78:e2:58:a7:
|
||||||
|
26:5f:4c:29:c8:23:f0:6e:12:3f:bd:ad:44:7b:0b:bd:db:ba:
|
||||||
|
63:8d:07:c6:9d:dc:46:cc:63:40:ba:5e:45:82:dd:9a:e5:50:
|
||||||
|
e8:e7:d7:27:88:fc:6f:1d:8a:e7:5c:49:28:aa:10:29:75:28:
|
||||||
|
c7:52:de:f9
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC9zCCAd+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp
|
||||||
|
bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs
|
||||||
|
ZDAeFw0xNzAxMTAwMDA4NTJaFw00NDA1MjgwMDA4NTJaMBwxGjAYBgNVBAMMESou
|
||||||
|
bmdpbngtcHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
|
||||||
|
y0X0FJv+ZIV5SjaNPdEn0Hw2KDDmc4BvfEkj0GwX5ETAd02awrwkhOOlTbrS2lF7
|
||||||
|
oSoS1MAZVWksIictGvb8S3/py6g86Gm40k/eTlDi0HQwfEJarqqFpbFxTcl+hoti
|
||||||
|
jD4N4zvD9YELjGh5/r8Q+67sEUltZF4afbOSk06WGTqYBKdmsnRhLUETDKRUDSx4
|
||||||
|
/bSj6Dd4mt76vC6oD2cUWM7Dh9UUDospfUgZsqn1tOivMiFnFX5DUosgz584Q7/9
|
||||||
|
yCR/UqOI8vFKFJEqbpFv+31qeMZtLt0eTCtjuzpDnJH539MIE2OGfc7oRs/xbB/K
|
||||||
|
90ze2Evg2rwG2YcP/5ZFhQIDAQABoyAwHjAcBgNVHREEFTATghEqLm5naW54LXBy
|
||||||
|
b3h5LnRsZDANBgkqhkiG9w0BAQsFAAOCAQEAbqUO5NPM1bf8NHWJTpiM5wgGqFvs
|
||||||
|
E32DmaJhuNUSbsW0U06aIs2tFDBqfVjXI9mkKpagQJ5Qn87y/ozdmqyZOVuJLcrl
|
||||||
|
PsO8AwQcEtluuJ/wOr4SRH6kIYZzr9UAUT8sVnA0jyawf7DPz3/5QG8AKcTPw7fC
|
||||||
|
ST0/sCZ4h7nHbBuqahrdxevyabptRguSSbURPOtIxy/7M6ZqgqKr+B5ffeO38v31
|
||||||
|
iKUJTaC89DvN0ovXVx+GO9I+pJIhsAIL6eDEHPF44linJl9MKcgj8G4SP72tRHsL
|
||||||
|
vdu6Y40Hxp3cRsxjQLpeRYLdmuVQ6OfXJ4j8bx2K51xJKKoQKXUox1Le+Q==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAy0X0FJv+ZIV5SjaNPdEn0Hw2KDDmc4BvfEkj0GwX5ETAd02a
|
||||||
|
wrwkhOOlTbrS2lF7oSoS1MAZVWksIictGvb8S3/py6g86Gm40k/eTlDi0HQwfEJa
|
||||||
|
rqqFpbFxTcl+hotijD4N4zvD9YELjGh5/r8Q+67sEUltZF4afbOSk06WGTqYBKdm
|
||||||
|
snRhLUETDKRUDSx4/bSj6Dd4mt76vC6oD2cUWM7Dh9UUDospfUgZsqn1tOivMiFn
|
||||||
|
FX5DUosgz584Q7/9yCR/UqOI8vFKFJEqbpFv+31qeMZtLt0eTCtjuzpDnJH539MI
|
||||||
|
E2OGfc7oRs/xbB/K90ze2Evg2rwG2YcP/5ZFhQIDAQABAoIBAQCjAro2PNLJMfCO
|
||||||
|
fyjNRgmzu6iCmpR0U68T8GN0JPsT576g7e8J828l0pkhuIyW33lRSThIvLSUNf9a
|
||||||
|
dChL032H3lBTLduKVh4NKleQXnVFzaeEPoISSFVdButiAhAhPW4OIUVp0OfY3V+x
|
||||||
|
fac3j2nDLAfL5SKAtqZv363Py9m66EBYm5BmGTQqT/frQWeCEBvlErQef5RIaU8p
|
||||||
|
e2zMWgSNNojVai8U3nKNRvYHWeWXM6Ck7lCvkHhMF+RpbmCZuqhbEARVnehU/Jdn
|
||||||
|
QHJ3nxeA2OWpoWKXvAHtSnno49yxq1UIstiQvY+ng5C5i56UlB60UiU2NJ6doZkB
|
||||||
|
uQ7/1MaBAoGBAORdcFtgdgRALjXngFWhpCp0CseyUehn1KhxDCG+D1pJ142/ymcf
|
||||||
|
oJOzKJPMRNDdDUBMnR1GBfy7rmwvYevI/SMNy2Qs7ofcXPbdtwwvTCToZ1V9/54k
|
||||||
|
VfuPBFT+3QzWRvG1tjTV3E4L2VV3nrl2qNPhE5DlfIaU3nQq5Fl0HprJAoGBAOPf
|
||||||
|
MWOTGev61CdODO5KN3pLAoamiPs5lEUlz3kM3L1Q52YLITxNDjRj9hWBUATJZOS2
|
||||||
|
pLOoYRwmhD7vrnimMc41+NuuFX+4T7hWPc8uSuOxX0VijYtULyNRK57mncG1Fq9M
|
||||||
|
RMLbOJ7FD+8jdXNsSMqpQ+pxLJRX/A10O2fOQnbdAoGAL5hV4YWSM0KZHvz332EI
|
||||||
|
ER0MXiCJN7HkPZMKH0I4eu3m8hEmAyYxVndBnsQ1F37q0xrkqAQ/HTSUntGlS/og
|
||||||
|
4Bxw5pkCwegoq/77tpto+ExDtSrEitYx4XMmSPyxX4qNULU5m3tzJgUML+b1etwD
|
||||||
|
Rd2kMU/TC02dq4KBAy/TbRkCgYAl1xN5iJz+XenLGR/2liZ+TWR+/bqzlU006mF4
|
||||||
|
pZUmbv/uJxz+yYD5XDwqOA4UrWjuvhG9r9FoflDprp2XdWnB556KxG7XhcDfSJr9
|
||||||
|
A5/2DadXe1Ur9O/a+oi2228JEsxQkea9QPA3FVxfBtFjOHEiDlez39VaUP4PMeUH
|
||||||
|
iO3qlQKBgFQhdTb7HeYnApYIDHLmd1PvjRvp8XKR1CpEN0nkw8HpHcT1q1MUjQCr
|
||||||
|
iT6FQupULEvGmO3frQsgVeRIQDbEdZK3C5xCtn6qOw70sYATVf361BbTtidmU9yV
|
||||||
|
THFxwDSVLiVZgFryoY/NtAc27sVdJnGsPRjjaeVgALAsLbmZ1K/H
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -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
|
|
@ -0,0 +1,27 @@
|
||||||
|
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
|
||||||
|
)
|
||||||
|
assert r.status_code == 301
|
||||||
|
|
||||||
|
def test_redirect_acme_challenge_location_enabled(docker_compose, nginxproxy, acme_challenge_path):
|
||||||
|
r = nginxproxy.get(
|
||||||
|
f"http://web2.nginx-proxy.tld/{acme_challenge_path}",
|
||||||
|
allow_redirects=False
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
assert r.status_code == 404
|
||||||
|
|
||||||
|
def test_noredirect_acme_challenge_location_enabled(docker_compose, nginxproxy, acme_challenge_path):
|
||||||
|
r = nginxproxy.get(
|
||||||
|
f"http://web4.nginx-proxy.tld/{acme_challenge_path}",
|
||||||
|
allow_redirects=False
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
|
@ -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"
|
|
@ -0,0 +1,27 @@
|
||||||
|
def test_redirect_acme_challenge_location_enabled(docker_compose, nginxproxy, acme_challenge_path):
|
||||||
|
r = nginxproxy.get(
|
||||||
|
f"http://web1.nginx-proxy.tld/{acme_challenge_path}",
|
||||||
|
allow_redirects=False
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
assert r.status_code == 301
|
||||||
|
|
||||||
|
def test_noredirect_acme_challenge_location_enabled(docker_compose, nginxproxy, acme_challenge_path):
|
||||||
|
r = nginxproxy.get(
|
||||||
|
f"http://web3.nginx-proxy.tld/{acme_challenge_path}",
|
||||||
|
allow_redirects=False
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
assert r.status_code == 404
|
|
@ -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"
|
|
@ -0,0 +1,13 @@
|
||||||
|
def test_redirect_acme_challenge_location_legacy(docker_compose, nginxproxy, acme_challenge_path):
|
||||||
|
r = nginxproxy.get(
|
||||||
|
f"http://web1.nginx-proxy.tld/{acme_challenge_path}",
|
||||||
|
allow_redirects=False
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
assert r.status_code == 404
|
|
@ -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
|
66
test/test_build/test_build.py
Normal file
66
test/test_build/test_build.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
"""
|
||||||
|
Test that nginx-proxy-tester can build successfully
|
||||||
|
"""
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
|
||||||
|
import docker
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
client = docker.from_env()
|
||||||
|
|
||||||
|
@pytest.fixture(scope = "session")
|
||||||
|
def docker_build(request):
|
||||||
|
# Define Dockerfile path
|
||||||
|
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.as_posix(),
|
||||||
|
dockerfile = dockerfile_name,
|
||||||
|
rm = True, # Remove intermediate containers
|
||||||
|
tag = "nginx-proxy-tester-ci", # Tag for the built image
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check for build success
|
||||||
|
for log in logs:
|
||||||
|
if "stream" in log:
|
||||||
|
print(log["stream"].strip())
|
||||||
|
if "error" in log:
|
||||||
|
raise Exception(log["error"])
|
||||||
|
|
||||||
|
def teardown():
|
||||||
|
# Clean up after teardown
|
||||||
|
client.images.remove(image.id, force=True)
|
||||||
|
|
||||||
|
request.addfinalizer(teardown)
|
||||||
|
|
||||||
|
# Return the image name
|
||||||
|
return "nginx-proxy-tester-ci"
|
||||||
|
|
||||||
|
def test_build_nginx_proxy_tester(docker_build):
|
||||||
|
assert docker_build == "nginx-proxy-tester-ci"
|
||||||
|
|
||||||
|
def test_run_nginx_proxy_tester(docker_build):
|
||||||
|
# Run the container with 'pytest -v' command to output version info
|
||||||
|
container = client.containers.run("nginx-proxy-tester-ci",
|
||||||
|
command = "pytest -V",
|
||||||
|
detach = True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Wait for the container to finish and get the exit code
|
||||||
|
result = container.wait()
|
||||||
|
exit_code = result.get("StatusCode", 1) # Default to 1 (error) if not found
|
||||||
|
|
||||||
|
# Get the output logs from the container
|
||||||
|
output = container.logs().decode("utf-8").strip()
|
||||||
|
|
||||||
|
# Clean up: Remove the container
|
||||||
|
container.remove()
|
||||||
|
|
||||||
|
# Assertions
|
||||||
|
assert exit_code == 0, "Container exited with a non-zero exit code"
|
||||||
|
assert re.search(r"pytest\s\d+\.\d+\.\d+", output)
|
|
@ -1,10 +0,0 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
def test_unknown_virtual_host(docker_compose, nginxproxy):
|
|
||||||
r = nginxproxy.get("http://nginx-proxy/")
|
|
||||||
assert r.status_code == 503
|
|
||||||
|
|
||||||
def test_forwards_to_whoami(docker_compose, nginxproxy):
|
|
||||||
r = nginxproxy.get("http://web.nginx-proxy.example/port")
|
|
||||||
assert r.status_code == 200
|
|
||||||
assert r.text == "answer from port 81\n"
|
|
|
@ -1,15 +0,0 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
|
||||||
nginx-proxy:
|
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
|
||||||
|
|
||||||
web:
|
|
||||||
image: web
|
|
||||||
expose:
|
|
||||||
- "81"
|
|
||||||
environment:
|
|
||||||
WEB_PORTS: 81
|
|
||||||
VIRTUAL_HOST: web.nginx-proxy.example
|
|
23
test/test_custom-error-page/50x.html
Normal file
23
test/test_custom-error-page/50x.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Maintenance</title>
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
color-scheme: light dark;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
width: 35em;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-family: Tahoma, Verdana, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Damn, there's some maintenance in progress.</h1>
|
||||||
|
<p>
|
||||||
|
Our apologies for this temporary inconvenience. Regular service
|
||||||
|
performance will be re-established shortly.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
7
test/test_custom-error-page/test_custom-error-page.py
Normal file
7
test/test_custom-error-page/test_custom-error-page.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_error_page(docker_compose, nginxproxy):
|
||||||
|
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)
|
5
test/test_custom-error-page/test_custom-error-page.yml
Normal file
5
test/test_custom-error-page/test_custom-error-page.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
services:
|
||||||
|
nginx-proxy:
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
|
- ${PYTEST_MODULE_PATH}/50x.html:/usr/share/nginx/html/errors/50x.html:ro
|
|
@ -1,5 +1,3 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
def test_custom_default_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
|
def test_custom_default_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
|
||||||
r = nginxproxy.get("http://nginx-proxy/")
|
r = nginxproxy.get("http://nginx-proxy/")
|
||||||
assert r.status_code == 503
|
assert r.status_code == 503
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
nginx-proxy:
|
nginx-proxy:
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
- ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/default_location:ro
|
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_f00.conf:/etc/nginx/vhost.d/default_location:ro
|
||||||
- ./my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/web3.nginx-proxy.example_location:ro
|
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/web3.nginx-proxy.example_location:ro
|
||||||
|
|
||||||
web1:
|
web1:
|
||||||
image: web
|
image: web
|
||||||
expose:
|
expose:
|
||||||
- "81"
|
- "81"
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: 81
|
WEB_PORTS: "81"
|
||||||
VIRTUAL_HOST: web1.nginx-proxy.example
|
VIRTUAL_HOST: web1.nginx-proxy.example
|
||||||
|
|
||||||
web2:
|
web2:
|
||||||
|
@ -21,7 +18,7 @@ services:
|
||||||
expose:
|
expose:
|
||||||
- "82"
|
- "82"
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: 82
|
WEB_PORTS: "82"
|
||||||
VIRTUAL_HOST: web2.nginx-proxy.example
|
VIRTUAL_HOST: web2.nginx-proxy.example
|
||||||
|
|
||||||
web3:
|
web3:
|
||||||
|
@ -29,5 +26,5 @@ services:
|
||||||
expose:
|
expose:
|
||||||
- "83"
|
- "83"
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: 83
|
WEB_PORTS: "83"
|
||||||
VIRTUAL_HOST: web3.nginx-proxy.example
|
VIRTUAL_HOST: web3.nginx-proxy.example
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
|
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
|
||||||
r = nginxproxy.get("http://nginx-proxy/")
|
r = nginxproxy.get("http://nginx-proxy/")
|
||||||
assert r.status_code == 503
|
assert r.status_code == 503
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
nginx-proxy:
|
nginx-proxy:
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
- ./my_custom_proxy_settings.conf:/etc/nginx/proxy.conf:ro
|
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_f00.conf:/etc/nginx/proxy.conf:ro
|
||||||
|
|
||||||
web1:
|
web1:
|
||||||
image: web
|
image: web
|
||||||
expose:
|
expose:
|
||||||
- "81"
|
- "81"
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: 81
|
WEB_PORTS: "81"
|
||||||
VIRTUAL_HOST: web1.nginx-proxy.example
|
VIRTUAL_HOST: web1.nginx-proxy.example
|
||||||
|
|
||||||
web2:
|
web2:
|
||||||
|
@ -20,5 +17,5 @@ services:
|
||||||
expose:
|
expose:
|
||||||
- "82"
|
- "82"
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: 82
|
WEB_PORTS: "82"
|
||||||
VIRTUAL_HOST: web2.nginx-proxy.example
|
VIRTUAL_HOST: web2.nginx-proxy.example
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
|
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
|
||||||
r = nginxproxy.get("http://nginx-proxy/")
|
r = nginxproxy.get("http://nginx-proxy/")
|
||||||
assert r.status_code == 503
|
assert r.status_code == 503
|
||||||
|
@ -12,6 +10,13 @@ def test_custom_conf_applies_to_web1(docker_compose, nginxproxy):
|
||||||
assert "X-test" in r.headers
|
assert "X-test" in r.headers
|
||||||
assert "f00" == r.headers["X-test"]
|
assert "f00" == r.headers["X-test"]
|
||||||
|
|
||||||
|
def test_custom_conf_applies_to_regex(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://regex.foo.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_conf_does_not_apply_to_web2(docker_compose, nginxproxy):
|
def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy):
|
||||||
r = nginxproxy.get("http://web2.nginx-proxy.example/port")
|
r = nginxproxy.get("http://web2.nginx-proxy.example/port")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
nginx-proxy:
|
nginx-proxy:
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
- ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example_location: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:
|
web1:
|
||||||
image: web
|
image: web
|
||||||
expose:
|
expose:
|
||||||
- "81"
|
- "81"
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: 81
|
WEB_PORTS: "81"
|
||||||
VIRTUAL_HOST: web1.nginx-proxy.example
|
VIRTUAL_HOST: web1.nginx-proxy.example
|
||||||
|
|
||||||
web2:
|
web2:
|
||||||
|
@ -20,5 +18,13 @@ services:
|
||||||
expose:
|
expose:
|
||||||
- "82"
|
- "82"
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: 82
|
WEB_PORTS: "82"
|
||||||
VIRTUAL_HOST: web2.nginx-proxy.example
|
VIRTUAL_HOST: web2.nginx-proxy.example
|
||||||
|
|
||||||
|
regex:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "83"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "83"
|
||||||
|
VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
|
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
|
||||||
r = nginxproxy.get("http://nginx-proxy/")
|
r = nginxproxy.get("http://nginx-proxy/")
|
||||||
assert r.status_code == 503
|
assert r.status_code == 503
|
||||||
|
@ -12,6 +10,13 @@ def test_custom_conf_applies_to_web1(docker_compose, nginxproxy):
|
||||||
assert "X-test" in r.headers
|
assert "X-test" in r.headers
|
||||||
assert "f00" == r.headers["X-test"]
|
assert "f00" == r.headers["X-test"]
|
||||||
|
|
||||||
|
def test_custom_conf_applies_to_regex(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://regex.foo.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_conf_does_not_apply_to_web2(docker_compose, nginxproxy):
|
def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy):
|
||||||
r = nginxproxy.get("http://web2.nginx-proxy.example/port")
|
r = nginxproxy.get("http://web2.nginx-proxy.example/port")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
nginx-proxy:
|
nginx-proxy:
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
- ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example: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:
|
web1:
|
||||||
image: web
|
image: web
|
||||||
expose:
|
expose:
|
||||||
- "81"
|
- "81"
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: 81
|
WEB_PORTS: "81"
|
||||||
VIRTUAL_HOST: web1.nginx-proxy.example
|
VIRTUAL_HOST: web1.nginx-proxy.example
|
||||||
|
|
||||||
web2:
|
web2:
|
||||||
|
@ -20,5 +18,13 @@ services:
|
||||||
expose:
|
expose:
|
||||||
- "82"
|
- "82"
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: 82
|
WEB_PORTS: "82"
|
||||||
VIRTUAL_HOST: web2.nginx-proxy.example
|
VIRTUAL_HOST: web2.nginx-proxy.example
|
||||||
|
|
||||||
|
regex:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "83"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "83"
|
||||||
|
VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
|
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
|
||||||
r = nginxproxy.get("http://nginx-proxy/")
|
r = nginxproxy.get("http://nginx-proxy/")
|
||||||
assert r.status_code == 503
|
assert r.status_code == 503
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
nginx-proxy:
|
nginx-proxy:
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
- ./my_custom_proxy_settings.conf:/etc/nginx/conf.d/my_custom_proxy_settings.conf:ro
|
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_f00.conf:/etc/nginx/conf.d/my_custom_proxy_settings_f00.conf:ro
|
||||||
|
|
||||||
web1:
|
web1:
|
||||||
image: web
|
image: web
|
||||||
expose:
|
expose:
|
||||||
- "81"
|
- "81"
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: 81
|
WEB_PORTS: "81"
|
||||||
VIRTUAL_HOST: web1.nginx-proxy.example
|
VIRTUAL_HOST: web1.nginx-proxy.example
|
||||||
|
|
||||||
web2:
|
web2:
|
||||||
|
@ -20,5 +17,5 @@ services:
|
||||||
expose:
|
expose:
|
||||||
- "82"
|
- "82"
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: 82
|
WEB_PORTS: "82"
|
||||||
VIRTUAL_HOST: web2.nginx-proxy.example
|
VIRTUAL_HOST: web2.nginx-proxy.example
|
||||||
|
|
48
test/test_debug-endpoint/test_global.py
Normal file
48
test/test_debug-endpoint/test_global.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
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
|
||||||
|
r = nginxproxy.get("http://stripped.debug.nginx-proxy.example/nginx-proxy-debug")
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_debug_endpoint_response_contains_expected_values(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://enabled.debug.nginx-proxy.example/nginx-proxy-debug")
|
||||||
|
assert r.status_code == 200
|
||||||
|
try:
|
||||||
|
jsonResponse = json.loads(r.text)
|
||||||
|
except ValueError as err:
|
||||||
|
pytest.fail("Failed to parse debug endpoint response as JSON: %s" % err, pytrace=False)
|
||||||
|
assert jsonResponse["global"]["enable_debug_endpoint"] == "true"
|
||||||
|
assert jsonResponse["vhost"]["enable_debug_endpoint"] == True
|
||||||
|
|
||||||
|
|
||||||
|
def test_debug_endpoint_paths_stripped_if_response_too_long(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://stripped.debug.nginx-proxy.example/nginx-proxy-debug")
|
||||||
|
assert r.status_code == 200
|
||||||
|
try:
|
||||||
|
jsonResponse = json.loads(r.text)
|
||||||
|
except ValueError as err:
|
||||||
|
pytest.fail("Failed to parse debug endpoint response as JSON: %s" % err, pytrace=False)
|
||||||
|
if "paths" in jsonResponse["vhost"]:
|
||||||
|
pytest.fail("Expected paths to be stripped from debug endpoint response", pytrace=False)
|
||||||
|
assert jsonResponse["warning"] == "Virtual paths configuration for this hostname is too large and has been stripped from response."
|
||||||
|
|
||||||
|
|
||||||
|
def test_debug_endpoint_hostname_replaced_by_warning_if_regexp(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://regexp.foo.debug.nginx-proxy.example/nginx-proxy-debug")
|
||||||
|
assert r.status_code == 200
|
||||||
|
try:
|
||||||
|
jsonResponse = json.loads(r.text)
|
||||||
|
except ValueError as err:
|
||||||
|
pytest.fail("Failed to parse debug endpoint response as JSON: %s" % err, pytrace=False)
|
||||||
|
assert jsonResponse["vhost"]["hostname"] == "Hostname is a regexp and unsafe to include in the debug response."
|
||||||
|
|
||||||
|
|
||||||
|
def test_debug_endpoint_is_disabled_per_container(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://disabled.debug.nginx-proxy.example/nginx-proxy-debug")
|
||||||
|
assert r.status_code == 404
|
59
test/test_debug-endpoint/test_global.yml
Normal file
59
test/test_debug-endpoint/test_global.yml
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
services:
|
||||||
|
nginx-proxy:
|
||||||
|
environment:
|
||||||
|
DEBUG_ENDPOINT: "true"
|
||||||
|
|
||||||
|
debug_enabled:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "81"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "81"
|
||||||
|
VIRTUAL_HOST: enabled.debug.nginx-proxy.example
|
||||||
|
|
||||||
|
debug_stripped:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "82"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "82"
|
||||||
|
VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
stripped.debug.nginx-proxy.example:
|
||||||
|
"/1":
|
||||||
|
"/2":
|
||||||
|
"/3":
|
||||||
|
"/4":
|
||||||
|
"/5":
|
||||||
|
"/6":
|
||||||
|
"/7":
|
||||||
|
"/8":
|
||||||
|
"/9":
|
||||||
|
"/10":
|
||||||
|
"/11":
|
||||||
|
"/12":
|
||||||
|
"/13":
|
||||||
|
"/14":
|
||||||
|
"/15":
|
||||||
|
"/16":
|
||||||
|
"/17":
|
||||||
|
"/18":
|
||||||
|
"/19":
|
||||||
|
"/20":
|
||||||
|
|
||||||
|
debug_regexp:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "84"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "84"
|
||||||
|
VIRTUAL_HOST: ~^regexp.*\.debug.nginx-proxy.example
|
||||||
|
|
||||||
|
debug_disabled:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "83"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "83"
|
||||||
|
VIRTUAL_HOST: disabled.debug.nginx-proxy.example
|
||||||
|
labels:
|
||||||
|
com.github.nginx-proxy.nginx-proxy.debug-endpoint: "false"
|
26
test/test_debug-endpoint/test_per-container.py
Normal file
26
test/test_debug-endpoint/test_per-container.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
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")
|
||||||
|
assert r.status_code == 404
|
||||||
|
r = nginxproxy.get("http://disabled2.debug.nginx-proxy.example/nginx-proxy-debug")
|
||||||
|
assert r.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_debug_endpoint_is_enabled_per_container(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://enabled.debug.nginx-proxy.example/nginx-proxy-debug")
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_debug_endpoint_response_contains_expected_values(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://enabled.debug.nginx-proxy.example/nginx-proxy-debug")
|
||||||
|
assert r.status_code == 200
|
||||||
|
try:
|
||||||
|
jsonResponse = json.loads(r.text)
|
||||||
|
except ValueError as err:
|
||||||
|
pytest.fail("Failed to parse debug endpoint response as JSON:: %s" % err, pytrace=False)
|
||||||
|
assert jsonResponse["global"]["enable_debug_endpoint"] == "false"
|
||||||
|
assert jsonResponse["vhost"]["enable_debug_endpoint"] == True
|
27
test/test_debug-endpoint/test_per-container.yml
Normal file
27
test/test_debug-endpoint/test_per-container.yml
Normal 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"
|
|
@ -1,19 +0,0 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
|
||||||
# GIVEN a webserver with VIRTUAL_HOST set to web1.tld
|
|
||||||
web1:
|
|
||||||
image: web
|
|
||||||
expose:
|
|
||||||
- "81"
|
|
||||||
environment:
|
|
||||||
WEB_PORTS: 81
|
|
||||||
VIRTUAL_HOST: web1.tld
|
|
||||||
|
|
||||||
# WHEN nginx-proxy runs with DEFAULT_HOST set to web1.tld
|
|
||||||
sut:
|
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
|
||||||
environment:
|
|
||||||
DEFAULT_HOST: web1.tld
|
|
|
@ -1,6 +1,3 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
|
|
||||||
def test_fallback_on_default(docker_compose, nginxproxy):
|
def test_fallback_on_default(docker_compose, nginxproxy):
|
||||||
r = nginxproxy.get("http://unknown.nginx-proxy.tld/port")
|
r = nginxproxy.get("http://unknown.nginx-proxy.tld/port")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
12
test/test_default-host/test_default-host.yml
Normal file
12
test/test_default-host/test_default-host.yml
Normal 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
|
|
@ -1,5 +1,3 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
def test_unknown_virtual_host(docker_compose, nginxproxy):
|
def test_unknown_virtual_host(docker_compose, nginxproxy):
|
||||||
r = nginxproxy.get("http://nginx-proxy/port")
|
r = nginxproxy.get("http://nginx-proxy/port")
|
||||||
assert r.status_code == 503
|
assert r.status_code == 503
|
|
@ -1,12 +1,16 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
nginx-proxy:
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/f00.sock:ro
|
||||||
|
environment:
|
||||||
|
DOCKER_HOST: unix:///f00.sock
|
||||||
|
|
||||||
web1:
|
web1:
|
||||||
image: web
|
image: web
|
||||||
expose:
|
expose:
|
||||||
- "81"
|
- "81"
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: 81
|
WEB_PORTS: "81"
|
||||||
VIRTUAL_HOST: web1.nginx-proxy.tld
|
VIRTUAL_HOST: web1.nginx-proxy.tld
|
||||||
|
|
||||||
web2:
|
web2:
|
||||||
|
@ -14,12 +18,5 @@ services:
|
||||||
expose:
|
expose:
|
||||||
- "82"
|
- "82"
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: 82
|
WEB_PORTS: "82"
|
||||||
VIRTUAL_HOST: web2.nginx-proxy.tld
|
VIRTUAL_HOST: web2.nginx-proxy.tld
|
||||||
|
|
||||||
sut:
|
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/f00.sock:ro
|
|
||||||
environment:
|
|
||||||
DOCKER_HOST: unix:///f00.sock
|
|
1
test/test_dockergen/.gitignore
vendored
1
test/test_dockergen/.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
nginx.tmpl
|
|
|
@ -1,13 +1,18 @@
|
||||||
version: "3"
|
volumes:
|
||||||
|
nginx_conf:
|
||||||
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
nginx:
|
nginx-proxy-nginx:
|
||||||
image: nginx
|
image: nginx
|
||||||
container_name: nginx
|
container_name: nginx
|
||||||
volumes:
|
volumes:
|
||||||
- nginx_conf:/etc/nginx/conf.d
|
- nginx_conf:/etc/nginx/conf.d:ro
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
|
||||||
dockergen:
|
nginx-proxy-dockergen:
|
||||||
image: nginxproxy/docker-gen
|
image: nginxproxy/docker-gen
|
||||||
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
|
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
|
||||||
volumes:
|
volumes:
|
||||||
|
@ -21,8 +26,5 @@ services:
|
||||||
expose:
|
expose:
|
||||||
- "80"
|
- "80"
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: 80
|
WEB_PORTS: "80"
|
||||||
VIRTUAL_HOST: whoami.nginx.container.docker
|
VIRTUAL_HOST: whoami.nginx.container.docker
|
||||||
|
|
||||||
volumes:
|
|
||||||
nginx_conf: {}
|
|
|
@ -1,11 +1,11 @@
|
||||||
import docker
|
import docker
|
||||||
import pytest
|
import pytest
|
||||||
from distutils.version import LooseVersion
|
from packaging.version import Version
|
||||||
|
|
||||||
|
|
||||||
raw_version = docker.from_env().version()["Version"]
|
raw_version = docker.from_env().version()["Version"]
|
||||||
pytestmark = pytest.mark.skipif(
|
pytestmark = pytest.mark.skipif(
|
||||||
LooseVersion(raw_version) < LooseVersion("1.13"),
|
Version(raw_version) < Version("1.13"),
|
||||||
reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})"
|
reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
def test_unknown_virtual_host_is_503(docker_compose, nginxproxy):
|
|
||||||
r = nginxproxy.get("http://unknown.nginx.container.docker/")
|
|
||||||
assert r.status_code == 503
|
|
||||||
|
|
||||||
|
|
||||||
def test_forwards_to_whoami(docker_compose, nginxproxy):
|
|
||||||
r = nginxproxy.get("http://whoami.nginx.container.docker/")
|
|
||||||
assert r.status_code == 200
|
|
||||||
whoami_container = docker_compose.containers.get("whoami")
|
|
||||||
assert r.text == f"I'm {whoami_container.id[:12]}\n"
|
|
|
@ -1,26 +0,0 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
|
||||||
nginx:
|
|
||||||
image: nginx
|
|
||||||
container_name: nginx
|
|
||||||
volumes:
|
|
||||||
- /etc/nginx/conf.d
|
|
||||||
|
|
||||||
dockergen:
|
|
||||||
image: nginxproxy/docker-gen
|
|
||||||
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
|
|
||||||
volumes_from:
|
|
||||||
- nginx
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
|
||||||
- ../../nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl
|
|
||||||
|
|
||||||
web:
|
|
||||||
image: web
|
|
||||||
container_name: whoami
|
|
||||||
expose:
|
|
||||||
- "80"
|
|
||||||
environment:
|
|
||||||
WEB_PORTS: 80
|
|
||||||
VIRTUAL_HOST: whoami.nginx.container.docker
|
|
|
@ -0,0 +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_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
|
|
@ -0,0 +1,40 @@
|
||||||
|
services:
|
||||||
|
nginx-proxy:
|
||||||
|
environment:
|
||||||
|
ENABLE_HTTP_ON_MISSING_CERT: "false"
|
||||||
|
|
||||||
|
nohttp-missing-cert-disabled:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "81"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "81"
|
||||||
|
VIRTUAL_HOST: nohttp-missing-cert-disabled.nginx-proxy.tld
|
||||||
|
HTTPS_METHOD: nohttp
|
||||||
|
|
||||||
|
nohttp-missing-cert-enabled:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "82"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "82"
|
||||||
|
VIRTUAL_HOST: nohttp-missing-cert-enabled.nginx-proxy.tld
|
||||||
|
HTTPS_METHOD: nohttp
|
||||||
|
ENABLE_HTTP_ON_MISSING_CERT: "true"
|
||||||
|
|
||||||
|
redirect-missing-cert-disabled:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "83"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "83"
|
||||||
|
VIRTUAL_HOST: redirect-missing-cert-disabled.nginx-proxy.tld
|
||||||
|
|
||||||
|
redirect-missing-cert-enabled:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "84"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "84"
|
||||||
|
VIRTUAL_HOST: redirect-missing-cert-enabled.nginx-proxy.tld
|
||||||
|
ENABLE_HTTP_ON_MISSING_CERT: "true"
|
|
@ -7,7 +7,7 @@ import pytest
|
||||||
from docker.errors import NotFound
|
from docker.errors import NotFound
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture
|
||||||
def web1(docker_compose):
|
def web1(docker_compose):
|
||||||
"""
|
"""
|
||||||
pytest fixture creating a web container with `VIRTUAL_HOST=web1.nginx-proxy` listening on port 81.
|
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}
|
ports={"81/tcp": None}
|
||||||
)
|
)
|
||||||
docker_compose.networks.get("test_default").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
|
sleep(2) # give it some time to initialize and for docker-gen to detect it
|
||||||
yield container
|
yield container
|
||||||
try:
|
try:
|
||||||
|
@ -30,7 +30,7 @@ def web1(docker_compose):
|
||||||
except NotFound:
|
except NotFound:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture
|
||||||
def web2(docker_compose):
|
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.
|
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}
|
ports={"82/tcp": None}
|
||||||
)
|
)
|
||||||
docker_compose.networks.get("test_default").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
|
sleep(2) # give it some time to initialize and for docker-gen to detect it
|
||||||
yield container
|
yield container
|
||||||
try:
|
try:
|
3
test/test_events/test_events.yml
Normal file
3
test/test_events/test_events.yml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
name: test_events-net
|
|
@ -1,17 +0,0 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
|
||||||
sut:
|
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
|
||||||
- ./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
|
|
9
test/test_fallback/test_fallback.data/compose.base.yml
Normal file
9
test/test_fallback/test_fallback.data/compose.base.yml
Normal 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"
|
|
@ -1,12 +1,8 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
sut:
|
nginx-proxy:
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
environment:
|
- ${PYTEST_MODULE_PATH}/test_fallback.data/custom-fallback.conf:/etc/nginx/conf.d/zzz-custom-fallback.conf:ro
|
||||||
HTTPS_METHOD: nohttps
|
|
||||||
|
|
||||||
http-only:
|
http-only:
|
||||||
image: web
|
image: web
|
||||||
|
@ -15,3 +11,4 @@ services:
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: "83"
|
WEB_PORTS: "83"
|
||||||
VIRTUAL_HOST: http-only.nginx-proxy.test
|
VIRTUAL_HOST: http-only.nginx-proxy.test
|
||||||
|
HTTPS_METHOD: nohttps
|
|
@ -1,11 +1,8 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
sut:
|
nginx-proxy:
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
- ./nodefault.certs:/etc/nginx/certs:ro
|
- ${PYTEST_MODULE_PATH}/test_fallback.data/nodefault.certs:/etc/nginx/certs:ro
|
||||||
|
|
||||||
https-and-http:
|
https-and-http:
|
||||||
image: web
|
image: web
|
|
@ -1,11 +1,8 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
sut:
|
nginx-proxy:
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
- ./withdefault.certs:/etc/nginx/certs:ro
|
- ${PYTEST_MODULE_PATH}/test_fallback.data/withdefault.certs:/etc/nginx/certs:ro
|
||||||
environment:
|
environment:
|
||||||
HTTPS_METHOD: redirect
|
HTTPS_METHOD: redirect
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
sut:
|
nginx-proxy:
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
- ./withdefault.certs:/etc/nginx/certs:ro
|
- ${PYTEST_MODULE_PATH}/test_fallback.data/withdefault.certs:/etc/nginx/certs:ro
|
||||||
environment:
|
environment:
|
||||||
HTTPS_METHOD: nohttp
|
HTTPS_METHOD: nohttp
|
||||||
|
|
||||||
|
@ -24,3 +21,13 @@ services:
|
||||||
environment:
|
environment:
|
||||||
WEB_PORTS: "84"
|
WEB_PORTS: "84"
|
||||||
VIRTUAL_HOST: missing-cert.nginx-proxy.test
|
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"
|
|
@ -1,11 +1,8 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
sut:
|
nginx-proxy:
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
- ./withdefault.certs:/etc/nginx/certs:ro
|
- ${PYTEST_MODULE_PATH}/test_fallback.data/withdefault.certs:/etc/nginx/certs:ro
|
||||||
environment:
|
environment:
|
||||||
HTTPS_METHOD: nohttp
|
HTTPS_METHOD: nohttp
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
sut:
|
nginx-proxy:
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
|
||||||
environment:
|
environment:
|
||||||
HTTPS_METHOD: redirect
|
HTTPS_METHOD: redirect
|
||||||
|
|
12
test/test_fallback/test_fallback.data/nohttps.yml
Normal file
12
test/test_fallback/test_fallback.data/nohttps.yml
Normal 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
|
|
@ -1,11 +1,10 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
sut:
|
nginx-proxy:
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
- ./withdefault.certs:/etc/nginx/certs:ro
|
- ${PYTEST_MODULE_PATH}/test_fallback.data/withdefault.certs:/etc/nginx/certs:ro
|
||||||
|
environment:
|
||||||
|
TRUST_DEFAULT_CERT: "false"
|
||||||
|
|
||||||
https-and-http:
|
https-and-http:
|
||||||
image: web
|
image: web
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue