diff --git a/docs/README.md b/docs/README.md index 23e91be..949c0e8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -54,6 +54,105 @@ For each host defined into `VIRTUAL_HOST`, the associated virtual port is retrie 1. From the container's exposed port if there is only one 1. From the default port 80 when none of the above methods apply +### Multiple ports + +If your container expose more than one service on different ports and those services need to be proxied, you'll need to use the `VIRTUAL_HOST_MULTIPORTS` environment variable. This variable takes virtual host, path, port and dest definition in YAML (or JSON) form, and completely override the `VIRTUAL_HOST`, `VIRTUAL_PORT`, `VIRTUAL_PATH` and `VIRTUAL_DEST` environment variables on this container. + +The YAML syntax should be easier to write on Docker compose files, while the JSON syntax can be used for CLI invocation. + +The expected format is the following: + +```yaml +hostname: + path: + port: int + dest: string +``` + +For each hostname entry, `path`, `port` and `dest` are optional and are assigned default values when missing: + +- `path` = "/" +- `port` = default port +- `dest` = "" + +The following examples use an hypothetical container running services on port 80, 8000 and 9000: + +#### Multiple ports routed to different hostnames + +```yaml +services: + multiport-container: + image: somerepo/somecontainer + container_name: multiport-container + environment: + VIRTUAL_HOST_MULTIPORTS: |- + www.example.org: + service1.example.org: + "/": + port: 8000 + service2.example.org: + "/": + port: 9000 + +# There is no path dict specified for www.example.org, so it get the default values: +# www.example.org: +# "/": +# port: 80 (default port) +# dest: "" + +# JSON equivalent: +# VIRTUAL_HOST_MULTIPORTS: |- +# { +# "www.example.org": {}, +# "service1.example.org": { "/": { "port": 8000, "dest": "" } }, +# "service2.example.org": { "/": { "port": 9000, "dest": "" } } +# } +``` + +This would result in the following proxy config: + +- `www.example.org` -> `multiport-container:80` +- `service1.example.org` -> `multiport-container:8000` +- `service2.example.org` -> `multiport-container:9000` + +#### Multiple ports routed to same hostname and different paths + +```yaml +services: + multiport-container: + image: somerepo/somecontainer + container_name: multiport-container + environment: + VIRTUAL_HOST_MULTIPORTS: |- + www.example.org: + "/": + "/service1": + port: 8000 + dest: "/" + "/service2": + port: 9000 + dest: "/" + +# port and dest are not specified on the / path, so this path is routed +# to the default port with the default dest value (empty string) + +# JSON equivalent: +# VIRTUAL_HOST_MULTIPORTS: |- +# { +# "www.example.org": { +# "/": {}, +# "/service1": { "port": 8000, "dest": "/" }, +# "/service2": { "port": 9000, "dest": "/" } +# } +# } +``` + +This would result in the following proxy config: + +- `www.example.org` -> `multiport-container:80` +- `www.example.org/service1` -> `multiport-container:8000` +- `www.example.org/service2` -> `multiport-container:9000` + ⬆️ [back to table of contents](#table-of-contents) ## Path-based Routing diff --git a/nginx.tmpl b/nginx.tmpl index fefb07f..2b73158 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -128,7 +128,7 @@ # exposed ports:{{ range sortObjectsByKeysAsc $.container.Addresses "Port" }} {{ .Port }}/{{ .Proto }}{{ else }} (none){{ end }} {{- $default_port := when (eq (len $.container.Addresses) 1) (first $.container.Addresses).Port "80" }} # default port: {{ $default_port }} - {{- $port := when (eq $.port "legacy") (or $.container.Env.VIRTUAL_PORT $default_port) $.port }} + {{- $port := when (eq $.port "default") $default_port (when (eq $.port "legacy") (or $.container.Env.VIRTUAL_PORT $default_port) $.port) }} # using port: {{ $port }} {{- $addr_obj := where $.container.Addresses "Port" $port | first }} {{- if and $addr_obj $addr_obj.HostPort }} @@ -338,49 +338,6 @@ upstream {{ $vpath.upstream }} { } {{- end }} -{{- /* - * Template used as a function to collect virtual path properties from - * the given containers. These properties are "returned" by storing their - * values into the provided dot dict. - * - * The provided dot dict is expected to have the following entries: - * - "Containers": List of container's RuntimeContainer struct. - * - "Upstream_name" - * - "Has_virtual_paths": boolean - * - "Path" - * - * The return values will be added to the dot dict with keys: - * - "dest" - * - "proto" - * - "network_tag" - * - "upstream" - * - "loadbalance" - * - "keepalive" - */}} -{{- define "get_path_info" }} - {{- /* Get the VIRTUAL_PROTO defined by containers w/ the same vhost-vpath, falling back to "http". */}} - {{- $proto := trim (or (first (groupByKeys $.Containers "Env.VIRTUAL_PROTO")) "http") }} - {{- /* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external". */}} - {{- $network_tag := or (first (groupByKeys $.Containers "Env.NETWORK_ACCESS")) "external" }} - - {{- $loadbalance := first (keys (groupByLabel $.Containers "com.github.nginx-proxy.nginx-proxy.loadbalance")) }} - {{- $keepalive := coalesce (first (keys (groupByLabel $.Containers "com.github.nginx-proxy.nginx-proxy.keepalive"))) "disabled" }} - - {{- $upstream := $.Upstream_name }} - {{- $dest := "" }} - {{- if $.Has_virtual_paths }} - {{- $sum := sha1 $.Path }} - {{- $upstream = printf "%s-%s" $upstream $sum }} - {{- $dest = or (first (groupByKeys $.Containers "Env.VIRTUAL_DEST")) "" }} - {{- end }} - {{- $_ := set $ "proto" $proto }} - {{- $_ := set $ "network_tag" $network_tag }} - {{- $_ := set $ "upstream" $upstream }} - {{- $_ := set $ "dest" $dest }} - {{- $_ := set $ "loadbalance" $loadbalance }} - {{- $_ := set $ "keepalive" $keepalive }} -{{- end }} - # If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the # scheme used to connect to this server map $http_x_forwarded_proto $proxy_x_forwarded_proto { @@ -500,15 +457,131 @@ proxy_set_header X-Original-URI $request_uri; proxy_set_header Proxy ""; {{- end }} -{{- /* Precompute some information about each vhost. */}} -{{- range $hostname, $containers := groupByMulti $globals.containers "Env.VIRTUAL_HOST" "," }} - {{- $hostname = trim $hostname }} - {{- if not $hostname }} - {{- /* Ignore containers with VIRTUAL_HOST set to the empty string. */}} +{{- /* Precompute and store some information about vhost that use VIRTUAL_HOST_MULTIPORTS. */}} +{{- range $vhosts_yaml, $containers := groupBy $globals.containers "Env.VIRTUAL_HOST_MULTIPORTS" }} + {{- /* Print a warning in the config if VIRTUAL_HOST_MULTIPORTS can't be parsed. */}} + {{- $parsedVhosts := fromYaml $vhosts_yaml }} + {{- if (empty $parsedVhosts) }} + {{- $containerNames := list }} + {{- range $container := $containers }} + {{- $containerNames = append $containerNames $container.Name }} + {{- end }} +# /!\ WARNING: the VIRTUAL_HOST_MULTIPORTS environment variable used for {{ len $containerNames | plural "this container" "those containers" }} is not a valid YAML string: +# {{ $containerNames | join ", " }} {{- continue }} {{- end }} - {{- $certName := first (groupByKeys $containers "Env.CERT_NAME") }} + {{- range $hostname, $vhost := $parsedVhosts }} + {{- $vhost_data := when (hasKey $globals.vhosts $hostname) (get $globals.vhosts $hostname) (dict) }} + {{- $paths := coalesce $vhost_data.paths (dict) }} + + {{- if (empty $vhost) }} + {{ $vhost = dict "/" (dict) }} + {{- end }} + + {{- range $path, $vpath := $vhost }} + {{- if (empty $vpath) }} + {{- $vpath = dict "dest" "" "port" "default" }} + {{- end }} + {{- $dest := coalesce $vpath.dest "" }} + {{- $port := when (hasKey $vpath "port") (toString $vpath.port) "default" }} + {{- $path_data := when (hasKey $paths $path) (get $paths $path) (dict) }} + {{- $path_ports := when (hasKey $path_data "ports") (get $path_data "ports") (dict) }} + {{- $path_port_containers := when (hasKey $path_ports $port) (get $path_ports $port) (list) }} + {{- $path_port_containers = concat $path_port_containers $containers }} + {{- $_ := set $path_ports $port $path_port_containers }} + {{- $_ := set $path_data "ports" $path_ports }} + {{- if (not (hasKey $path_data "dest")) }} + {{- $_ := set $path_data "dest" $dest }} + {{- end }} + {{- $_ := set $paths $path $path_data }} + {{- end }} + {{- $_ := set $vhost_data "paths" $paths }} + {{- $is_regexp := hasPrefix "~" $hostname }} + {{- $_ := set $vhost_data "upstream_name" (when (or $is_regexp $globals.sha1_upstream_name) (sha1 $hostname) $hostname) }} + {{- $_ := set $globals.vhosts $hostname $vhost_data }} + {{- end }} +{{- end }} + +{{- /* Precompute and store some information about vhost that use VIRTUAL_HOST. */}} +{{- range $hostname, $containers := groupByMulti $globals.containers "Env.VIRTUAL_HOST" "," }} + {{- /* Ignore containers with VIRTUAL_HOST set to the empty string. */}} + {{- $hostname = trim $hostname }} + {{- if not $hostname }} + {{- continue }} + {{- end }} + + {{- /* Drop containers with both VIRTUAL_HOST and VIRTUAL_HOST_MULTIPORTS set + * (VIRTUAL_HOST_MULTIPORTS takes precedence thanks to the previous loop). + */}} + {{- range $_, $containers_to_drop := groupBy $containers "Env.VIRTUAL_HOST_MULTIPORTS" }} + {{- range $container := $containers_to_drop }} + {{- $containers = without $containers $container }} + {{- end }} + {{- end }} + {{- if (eq (len $containers) 0) }} + {{- continue }} + {{- end }} + + {{- $vhost_data := when (hasKey $globals.vhosts $hostname) (get $globals.vhosts $hostname) (dict) }} + {{- $paths := coalesce $vhost_data.paths (dict) }} + + {{- $tmp_paths := groupByWithDefault $containers "Env.VIRTUAL_PATH" "/" }} + + {{- range $path, $containers := $tmp_paths }} + {{- $dest := or (first (groupByKeys $containers "Env.VIRTUAL_DEST")) "" }} + {{- $port := "legacy" }} + {{- $path_data := when (hasKey $paths $path) (get $paths $path) (dict) }} + {{- $path_ports := when (hasKey $path_data "ports") (get $path_data "ports") (dict) }} + {{- $path_port_containers := when (hasKey $path_ports $port) (get $path_ports $port) (list) }} + {{- $path_port_containers = concat $path_port_containers $containers }} + {{- $_ := set $path_ports $port $path_port_containers }} + {{- $_ := set $path_data "ports" $path_ports }} + {{- if (not (hasKey $path_data "dest")) }} + {{- $_ := set $path_data "dest" $dest }} + {{- end }} + {{- $_ := set $paths $path $path_data }} + {{- end }} + {{- $_ := set $vhost_data "paths" $paths }} + {{- $is_regexp := hasPrefix "~" $hostname }} + {{- $_ := set $vhost_data "upstream_name" (when (or $is_regexp $globals.sha1_upstream_name) (sha1 $hostname) $hostname) }} + {{- $_ := set $globals.vhosts $hostname $vhost_data }} +{{- end }} + +{{- /* Loop over $globals.vhosts and update it with the remaining informations about each vhost. */}} +{{- range $hostname, $vhost_data := $globals.vhosts }} + {{- $vhost_containers := list }} + {{- range $path, $vpath_data := $vhost_data.paths }} + {{- $vpath_containers := list }} + {{- range $port, $vport_containers := $vpath_data.ports }} + {{ $vpath_containers = concat $vpath_containers $vport_containers }} + {{- end }} + + {{- /* Get the VIRTUAL_PROTO defined by containers w/ the same vhost-vpath, falling back to "http". */}} + {{- $proto := trim (or (first (groupByKeys $vpath_containers "Env.VIRTUAL_PROTO")) "http") }} + {{- /* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external". */}} + {{- $network_tag := or (first (groupByKeys $vpath_containers "Env.NETWORK_ACCESS")) "external" }} + + {{- $loadbalance := first (keys (groupByLabel $vpath_containers "com.github.nginx-proxy.nginx-proxy.loadbalance")) }} + {{- $keepalive := coalesce (first (keys (groupByLabel $vpath_containers "com.github.nginx-proxy.nginx-proxy.keepalive"))) "disabled" }} + + {{- $upstream := $vhost_data.upstream_name }} + {{- if (not (eq $path "/")) }} + {{- $sum := sha1 $path }} + {{- $upstream = printf "%s-%s" $upstream $sum }} + {{- end }} + + {{- $_ := set $vpath_data "proto" $proto }} + {{- $_ := set $vpath_data "network_tag" $network_tag }} + {{- $_ := set $vpath_data "upstream" $upstream }} + {{- $_ := set $vpath_data "loadbalance" $loadbalance }} + {{- $_ := set $vpath_data "keepalive" $keepalive }} + {{- $_ := set $vhost_data.paths $path $vpath_data }} + + {{ $vhost_containers = concat $vhost_containers $vpath_containers }} + {{- end }} + + {{- $certName := first (groupByKeys $vhost_containers "Env.CERT_NAME") }} {{- $vhostCert := closest (dir "/etc/nginx/certs") (printf "%s.crt" $hostname) }} {{- $vhostCert = trimSuffix ".crt" $vhostCert }} {{- $vhostCert = trimSuffix ".key" $vhostCert }} @@ -516,49 +589,23 @@ proxy_set_header Proxy ""; {{- $cert_ok := and (ne $cert "") (exists (printf "/etc/nginx/certs/%s.crt" $cert)) (exists (printf "/etc/nginx/certs/%s.key" $cert)) }} {{- $default := eq $globals.Env.DEFAULT_HOST $hostname }} - {{- $https_method := or (first (groupByKeys $containers "Env.HTTPS_METHOD")) $globals.Env.HTTPS_METHOD "redirect" }} - {{- $http2_enabled := parseBool (or (first (keys (groupByLabel $containers "com.github.nginx-proxy.nginx-proxy.http2.enable"))) $globals.Env.ENABLE_HTTP2 "true")}} - {{- $http3_enabled := parseBool (or (first (keys (groupByLabel $containers "com.github.nginx-proxy.nginx-proxy.http3.enable"))) $globals.Env.ENABLE_HTTP3 "false")}} - - {{- $is_regexp := hasPrefix "~" $hostname }} - {{- $upstream_name := when (or $is_regexp $globals.sha1_upstream_name) (sha1 $hostname) $hostname }} + {{- $https_method := or (first (groupByKeys $vhost_containers "Env.HTTPS_METHOD")) $globals.Env.HTTPS_METHOD "redirect" }} + {{- $http2_enabled := parseBool (or (first (keys (groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http2.enable"))) $globals.Env.ENABLE_HTTP2 "true")}} + {{- $http3_enabled := parseBool (or (first (keys (groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http3.enable"))) $globals.Env.ENABLE_HTTP3 "false")}} {{- /* Get the SERVER_TOKENS defined by containers w/ the same vhost, falling back to "". */}} - {{- $server_tokens := trim (or (first (groupByKeys $containers "Env.SERVER_TOKENS")) "") }} + {{- $server_tokens := trim (or (first (groupByKeys $vhost_containers "Env.SERVER_TOKENS")) "") }} {{- /* Get the SSL_POLICY defined by containers w/ the same vhost, falling back to empty string (use default). */}} - {{- $ssl_policy := or (first (groupByKeys $containers "Env.SSL_POLICY")) "" }} + {{- $ssl_policy := or (first (groupByKeys $vhost_containers "Env.SSL_POLICY")) "" }} {{- /* Get the HSTS defined by containers w/ the same vhost, falling back to "max-age=31536000". */}} - {{- $hsts := or (first (groupByKeys $containers "Env.HSTS")) (or $globals.Env.HSTS "max-age=31536000") }} + {{- $hsts := or (first (groupByKeys $vhost_containers "Env.HSTS")) (or $globals.Env.HSTS "max-age=31536000") }} {{- /* Get the VIRTUAL_ROOT By containers w/ use fastcgi root */}} - {{- $vhost_root := or (first (groupByKeys $containers "Env.VIRTUAL_ROOT")) "/var/www/public" }} + {{- $vhost_root := or (first (groupByKeys $vhost_containers "Env.VIRTUAL_ROOT")) "/var/www/public" }} - - {{- $tmp_paths := groupBy $containers "Env.VIRTUAL_PATH" }} - {{- $has_virtual_paths := gt (len $tmp_paths) 0}} - {{- if not $has_virtual_paths }} - {{- $tmp_paths = dict "/" $containers }} - {{- end }} - - {{- $paths := dict }} - - {{- range $path, $containers := $tmp_paths }} - {{- $args := dict "Containers" $containers "Path" $path "Upstream_name" $upstream_name "Has_virtual_paths" $has_virtual_paths }} - {{- template "get_path_info" $args }} - {{- $_ := set $paths $path (dict - "ports" (dict "legacy" $containers) - "dest" $args.dest - "proto" $args.proto - "network_tag" $args.network_tag - "upstream" $args.upstream - "loadbalance" $args.loadbalance - "keepalive" $args.keepalive - ) }} - {{- end }} - - {{- $_ := set $globals.vhosts $hostname (dict + {{- $vhost_data = merge $vhost_data (dict "cert" $cert "cert_ok" $cert_ok "default" $default @@ -566,13 +613,14 @@ proxy_set_header Proxy ""; "https_method" $https_method "http2_enabled" $http2_enabled "http3_enabled" $http3_enabled - "paths" $paths "server_tokens" $server_tokens "ssl_policy" $ssl_policy "vhost_root" $vhost_root ) }} + {{- $_ := set $globals.vhosts $hostname $vhost_data }} {{- end }} + {{- /* * If needed, create a catch-all fallback server to send an error code to * clients that request something from an unknown vhost. @@ -808,4 +856,4 @@ server { } {{- end }} } -{{- end }} +{{- end }} \ No newline at end of file diff --git a/test/test_composev2.py b/test/test_composev2.py deleted file mode 100644 index 695857e..0000000 --- a/test/test_composev2.py +++ /dev/null @@ -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" diff --git a/test/test_composev2.yml b/test/test_composev2.yml deleted file mode 100644 index 3c36022..0000000 --- a/test/test_composev2.yml +++ /dev/null @@ -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 diff --git a/test/test_log_format.py b/test/test_logs/test_log_format.py similarity index 100% rename from test/test_log_format.py rename to test/test_logs/test_log_format.py diff --git a/test/test_log_format.yml b/test/test_logs/test_log_format.yml similarity index 100% rename from test/test_log_format.yml rename to test/test_logs/test_log_format.yml diff --git a/test/test_log_json.py b/test/test_logs/test_log_json.py similarity index 100% rename from test/test_log_json.py rename to test/test_logs/test_log_json.py diff --git a/test/test_log_json.yml b/test/test_logs/test_log_json.yml similarity index 100% rename from test/test_log_json.yml rename to test/test_logs/test_log_json.yml diff --git a/test/test_log_json_format.py b/test/test_logs/test_log_json_format.py similarity index 100% rename from test/test_log_json_format.py rename to test/test_logs/test_log_json_format.py diff --git a/test/test_log_json_format.yml b/test/test_logs/test_log_json_format.yml similarity index 100% rename from test/test_log_json_format.yml rename to test/test_logs/test_log_json_format.yml diff --git a/test/test_multiports/test_multiports-base-json.py b/test/test_multiports/test_multiports-base-json.py new file mode 100644 index 0000000..7f1ef0f --- /dev/null +++ b/test/test_multiports/test_multiports-base-json.py @@ -0,0 +1,39 @@ +import pytest + + +def test_virtual_host_is_dropped_when_using_multiports(docker_compose, nginxproxy): + r = nginxproxy.get("http://notskipped.nginx-proxy.tld/port") + assert r.status_code == 200 + assert "answer from port 81\n" in r.text + r = nginxproxy.get("http://skipped.nginx-proxy.tld/") + assert r.status_code == 503 + + +def test_answer_is_served_from_port_80_by_default(docker_compose, nginxproxy): + r = nginxproxy.get("http://port80.a.nginx-proxy.tld/port") + assert r.status_code == 200 + assert "answer from port 80\n" in r.text + r = nginxproxy.get("http://port80.b.nginx-proxy.tld/port") + assert r.status_code == 200 + assert "answer from port 80\n" in r.text + r = nginxproxy.get("http://port80.c.nginx-proxy.tld/port") + assert r.status_code == 200 + assert "answer from port 80\n" in r.text + + +def test_answer_is_served_from_chosen_ports(docker_compose, nginxproxy): + r = nginxproxy.get("http://port8080.nginx-proxy.tld/port") + assert r.status_code == 200 + assert "answer from port 8080\n" in r.text + r = nginxproxy.get("http://port9000.nginx-proxy.tld/port") + assert r.status_code == 200 + assert "answer from port 9000\n" in r.text + + +def test_answer_is_served_from_chosen_ports_and_dest(docker_compose, nginxproxy): + r = nginxproxy.get("http://virtualpaths.nginx-proxy.tld/rootdest/port") + assert r.status_code == 200 + assert "answer from port 10001\n" in r.text + r = nginxproxy.get("http://virtualpaths.nginx-proxy.tld/customdest") + assert r.status_code == 200 + assert "answer from port 10002\n" in r.text diff --git a/test/test_multiports/test_multiports-base-json.yml b/test/test_multiports/test_multiports-base-json.yml new file mode 100644 index 0000000..fc0d0fa --- /dev/null +++ b/test/test_multiports/test_multiports-base-json.yml @@ -0,0 +1,77 @@ +version: "2" + +services: + skipvirtualhost: + image: web + expose: + - "81" + environment: + WEB_PORTS: "81" + VIRTUAL_HOST: skipped.nginx-proxy.tld + VIRTUAL_HOST_MULTIPORTS: |- + { + "notskipped.nginx-proxy.tld": {} + } + + defaultport: + image: web + expose: + - "80" + - "8080" + environment: + WEB_PORTS: "80 8080" + VIRTUAL_HOST_MULTIPORTS: |- + { + "port80.a.nginx-proxy.tld": {}, + "port80.b.nginx-proxy.tld": {}, + "port80.c.nginx-proxy.tld": { + "/": {} + } + } + + multiports: + image: web + expose: + - "8080" + - "9000" + environment: + WEB_PORTS: "8080 9000" + VIRTUAL_HOST_MULTIPORTS: |- + { + "port8080.nginx-proxy.tld": { + "/": { + "port": 8080 + } + }, + "port9000.nginx-proxy.tld": { + "/": { + "port": 9000 + } + } + } + + virtualpath: + image: web + expose: + - "10001" + - "10002" + environment: + WEB_PORTS: "10001 10002" + VIRTUAL_HOST_MULTIPORTS: |- + { + "virtualpaths.nginx-proxy.tld": { + "/rootdest": { + "port": 10001, + "dest": "/" + }, + "/customdest": { + "port": 10002, + "dest": "/port" + } + } + } + + sut: + image: nginxproxy/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro diff --git a/test/test_multiports/test_multiports-base-yaml.py b/test/test_multiports/test_multiports-base-yaml.py new file mode 100644 index 0000000..7f1ef0f --- /dev/null +++ b/test/test_multiports/test_multiports-base-yaml.py @@ -0,0 +1,39 @@ +import pytest + + +def test_virtual_host_is_dropped_when_using_multiports(docker_compose, nginxproxy): + r = nginxproxy.get("http://notskipped.nginx-proxy.tld/port") + assert r.status_code == 200 + assert "answer from port 81\n" in r.text + r = nginxproxy.get("http://skipped.nginx-proxy.tld/") + assert r.status_code == 503 + + +def test_answer_is_served_from_port_80_by_default(docker_compose, nginxproxy): + r = nginxproxy.get("http://port80.a.nginx-proxy.tld/port") + assert r.status_code == 200 + assert "answer from port 80\n" in r.text + r = nginxproxy.get("http://port80.b.nginx-proxy.tld/port") + assert r.status_code == 200 + assert "answer from port 80\n" in r.text + r = nginxproxy.get("http://port80.c.nginx-proxy.tld/port") + assert r.status_code == 200 + assert "answer from port 80\n" in r.text + + +def test_answer_is_served_from_chosen_ports(docker_compose, nginxproxy): + r = nginxproxy.get("http://port8080.nginx-proxy.tld/port") + assert r.status_code == 200 + assert "answer from port 8080\n" in r.text + r = nginxproxy.get("http://port9000.nginx-proxy.tld/port") + assert r.status_code == 200 + assert "answer from port 9000\n" in r.text + + +def test_answer_is_served_from_chosen_ports_and_dest(docker_compose, nginxproxy): + r = nginxproxy.get("http://virtualpaths.nginx-proxy.tld/rootdest/port") + assert r.status_code == 200 + assert "answer from port 10001\n" in r.text + r = nginxproxy.get("http://virtualpaths.nginx-proxy.tld/customdest") + assert r.status_code == 200 + assert "answer from port 10002\n" in r.text diff --git a/test/test_multiports/test_multiports-base-yaml.yml b/test/test_multiports/test_multiports-base-yaml.yml new file mode 100644 index 0000000..8bd5803 --- /dev/null +++ b/test/test_multiports/test_multiports-base-yaml.yml @@ -0,0 +1,61 @@ +version: "2" + +services: + skipvirtualhost: + image: web + expose: + - "81" + environment: + WEB_PORTS: "81" + VIRTUAL_HOST: skipped.nginx-proxy.tld + VIRTUAL_HOST_MULTIPORTS: |- + notskipped.nginx-proxy.tld: + + defaultport: + image: web + expose: + - "80" + - "8080" + environment: + WEB_PORTS: "80 8080" + VIRTUAL_HOST_MULTIPORTS: |- + port80.a.nginx-proxy.tld: + port80.b.nginx-proxy.tld: + port80.c.nginx-proxy.tld: + "/": + + multiports: + image: web + expose: + - "8080" + - "9000" + environment: + WEB_PORTS: "8080 9000" + VIRTUAL_HOST_MULTIPORTS: |- + port8080.nginx-proxy.tld: + "/": + port: 8080 + port9000.nginx-proxy.tld: + "/": + port: 9000 + + virtualpath: + image: web + expose: + - "10001" + - "10002" + environment: + WEB_PORTS: "10001 10002" + VIRTUAL_HOST_MULTIPORTS: |- + virtualpaths.nginx-proxy.tld: + "/rootdest": + port: 10001 + dest: "/" + "/customdest": + port: 10002 + dest: "/port" + + sut: + image: nginxproxy/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro diff --git a/test/test_multiports/test_multiports-invalid-syntax.py b/test/test_multiports/test_multiports-invalid-syntax.py new file mode 100644 index 0000000..ed1c773 --- /dev/null +++ b/test/test_multiports/test_multiports-invalid-syntax.py @@ -0,0 +1,18 @@ +import pytest +import re + + +def test_virtual_hosts_with_syntax_error_should_not_be_reachable(docker_compose, nginxproxy): + r = nginxproxy.get("http://test1.nginx-proxy.tld") + assert r.status_code == 503 + r = nginxproxy.get("http://test2.nginx-proxy.tld") + assert r.status_code == 503 + + +def test_config_should_have_multiports_warning_comments(docker_compose, nginxproxy): + conf = nginxproxy.get_conf().decode('ASCII') + matches = re.findall(r"the VIRTUAL_HOST_MULTIPORTS environment variable used for this container is not a valid YAML string", conf) + assert len(matches) == 3 + assert "# invalidsyntax" in conf + assert "# hostnamerepeat" in conf + assert "# pathrepeat" in conf diff --git a/test/test_multiports/test_multiports-invalid-syntax.yml b/test/test_multiports/test_multiports-invalid-syntax.yml new file mode 100644 index 0000000..9f40220 --- /dev/null +++ b/test/test_multiports/test_multiports-invalid-syntax.yml @@ -0,0 +1,44 @@ +version: "2" + +services: + invalidsyntax: + image: web + container_name: invalidsyntax + expose: + - "80" + environment: + WEB_PORTS: "80" + VIRTUAL_HOST_MULTIPORTS: |- + test1.nginx-proxy.tld + test2.nginx-proxy.tld: + + hostnamerepeat: + image: web + container_name: hostnamerepeat + expose: + - "80" + environment: + WEB_PORTS: "80" + VIRTUAL_HOST_MULTIPORTS: |- + test1.nginx-proxy.tld: + test1.nginx-proxy.tld: + + pathrepeat: + image: web + container_name: pathrepeat + expose: + - "8080" + - "9000" + environment: + WEB_PORTS: "8080 9000" + VIRTUAL_HOST_MULTIPORTS: |- + test1.nginx-proxy.tld: + "/": + port: 8080 + "/": + port: 9000 + + sut: + image: nginxproxy/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro diff --git a/test/test_multiports/test_multiports-merge.py b/test/test_multiports/test_multiports-merge.py new file mode 100644 index 0000000..f5aa697 --- /dev/null +++ b/test/test_multiports/test_multiports-merge.py @@ -0,0 +1,14 @@ +import backoff +import pytest + + +def test_multiports_and_legacy_configs_should_be_merged(docker_compose, nginxproxy): + @backoff.on_predicate(backoff.constant, lambda r: r == False, interval=.5, max_tries=20, jitter=None) + def answer_contains(answer, url): + return answer in nginxproxy.get(url).text + + assert answer_contains("80", "http://merged.nginx-proxy.tld/port") + assert answer_contains("81", "http://merged.nginx-proxy.tld/port") + + assert answer_contains("9090", "http://merged.nginx-proxy.tld/foo/port") + assert answer_contains("9191", "http://merged.nginx-proxy.tld/foo/port") diff --git a/test/test_multiports/test_multiports-merge.yml b/test/test_multiports/test_multiports-merge.yml new file mode 100644 index 0000000..5c5cd8b --- /dev/null +++ b/test/test_multiports/test_multiports-merge.yml @@ -0,0 +1,41 @@ +version: "2" + +services: + merged-singleport: + image: web + expose: + - "80" + environment: + WEB_PORTS: "80" + VIRTUAL_HOST: merged.nginx-proxy.tld + + merged-singleport-virtual-path: + image: web + expose: + - "9090" + environment: + WEB_PORTS: "9090" + VIRTUAL_HOST: merged.nginx-proxy.tld + VIRTUAL_PORT: "9090" + VIRTUAL_PATH: "/foo" + VIRTUAL_DEST: "/" + + merged-multiports: + image: web + expose: + - "81" + - "9191" + environment: + WEB_PORTS: "81 9191" + VIRTUAL_HOST_MULTIPORTS: |- + merged.nginx-proxy.tld: + "/": + port: 81 + "/foo": + port: 9191 + dest: "/" + + sut: + image: nginxproxy/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro diff --git a/test/test_multiple-ports/test_VIRTUAL_PORT-single-different-from-single-port.py b/test/test_ports/test_VIRTUAL_PORT-single-different-from-single-port.py similarity index 100% rename from test/test_multiple-ports/test_VIRTUAL_PORT-single-different-from-single-port.py rename to test/test_ports/test_VIRTUAL_PORT-single-different-from-single-port.py diff --git a/test/test_multiple-ports/test_VIRTUAL_PORT-single-different-from-single-port.yml b/test/test_ports/test_VIRTUAL_PORT-single-different-from-single-port.yml similarity index 100% rename from test/test_multiple-ports/test_VIRTUAL_PORT-single-different-from-single-port.yml rename to test/test_ports/test_VIRTUAL_PORT-single-different-from-single-port.yml diff --git a/test/test_multiple-ports/test_VIRTUAL_PORT.py b/test/test_ports/test_VIRTUAL_PORT.py similarity index 100% rename from test/test_multiple-ports/test_VIRTUAL_PORT.py rename to test/test_ports/test_VIRTUAL_PORT.py diff --git a/test/test_multiple-ports/test_VIRTUAL_PORT.yml b/test/test_ports/test_VIRTUAL_PORT.yml similarity index 100% rename from test/test_multiple-ports/test_VIRTUAL_PORT.yml rename to test/test_ports/test_VIRTUAL_PORT.yml diff --git a/test/test_multiple-ports/test_default-80.py b/test/test_ports/test_default-80.py similarity index 100% rename from test/test_multiple-ports/test_default-80.py rename to test/test_ports/test_default-80.py diff --git a/test/test_multiple-ports/test_default-80.yml b/test/test_ports/test_default-80.yml similarity index 100% rename from test/test_multiple-ports/test_default-80.yml rename to test/test_ports/test_default-80.yml diff --git a/test/test_multiple-ports/test_single-port-not-80.py b/test/test_ports/test_single-port-not-80.py similarity index 100% rename from test/test_multiple-ports/test_single-port-not-80.py rename to test/test_ports/test_single-port-not-80.py diff --git a/test/test_multiple-ports/test_single-port-not-80.yml b/test/test_ports/test_single-port-not-80.yml similarity index 100% rename from test/test_multiple-ports/test_single-port-not-80.yml rename to test/test_ports/test_single-port-not-80.yml diff --git a/test/test_default-root-none.py b/test/test_virtual-path/test_default-root-none.py similarity index 100% rename from test/test_default-root-none.py rename to test/test_virtual-path/test_default-root-none.py diff --git a/test/test_default-root-none.yml b/test/test_virtual-path/test_default-root-none.yml similarity index 100% rename from test/test_default-root-none.yml rename to test/test_virtual-path/test_default-root-none.yml