feat: render proxy routes under openresty and add per-backend tests
Bumps the openresty sidecar to 0.12.0, which introduces the openresty.route.<n>.* label family for path-based routing. The openresty-vhosts plugin now emits route labels for non-web containers targeted by a proxy:route:set, mirroring the traefik and caddy patterns. Adds integration tests for every rendering backend (proxy-route-{nginx,traefik,caddy,openresty}.bats covering basic routing, --strip-prefix, and removal) plus per-ingress-class scheduler-k3s tests (proxy-route-k3s-{nginx,traefik}.bats covering the same three cases against a real cluster). The nginx file additionally asserts the rendered nginx config carries the WebSocket upgrade headers in each route's location block.
This commit is contained in:
@@ -196,7 +196,7 @@ See the [port management documentation](/docs/networking/port-management.md) for
|
||||
By default, every HTTP/HTTPS request is routed to the `web` process declared in the app's `Procfile`. Use `proxy:route:*` to route specific path prefixes to a different process within the same app - for example, sending `/api/v0/*` to an `api` process or `/ws` to a `websocket` process. The `web` process always handles every other path as an implicit catch-all.
|
||||
|
||||
> [!NOTE]
|
||||
> Path-based routing is rendered by the `nginx`, `traefik`, `caddy`, and `k3s` proxy backends. The `openresty` and `haproxy` backends accept and store route declarations, but the routes are inert (no labels are emitted) because the sidecar images do not yet support path-prefix vocabulary. Routes stored ahead of a backend switch will activate automatically once the proxy is changed to a rendering backend. Tracked at [dokku/openresty-docker-proxy#137](https://github.com/dokku/openresty-docker-proxy/issues/137) and the `haproxy-vhosts` sidecar.
|
||||
> Path-based routing is rendered by the `nginx`, `traefik`, `caddy`, `openresty`, and `k3s` proxy backends. The `haproxy` backend accepts and stores route declarations but the routes are inert (no labels are emitted) because the `byjg/easy-haproxy` sidecar image only supports host-based routing. Routes stored ahead of a backend switch will activate automatically once the proxy is changed to a rendering backend.
|
||||
|
||||
#### Adding a route
|
||||
|
||||
@@ -271,9 +271,9 @@ dokku proxy:route:set node-js-app api /api/v0/admin
|
||||
#### Caveats per backend
|
||||
|
||||
- **nginx**: route changes take effect immediately on the next `proxy-build-config` reload (which `proxy:route:*` invokes automatically).
|
||||
- **traefik** and **caddy**: routes are applied via Docker labels on the target containers. Containers must be recreated for new or removed labels to take effect. The command prints a notice instructing you to run `dokku ps:rebuild <app>` to recreate containers.
|
||||
- **traefik**, **caddy**, and **openresty**: routes are applied via Docker labels on the target containers. Containers must be recreated for new or removed labels to take effect. The command prints a notice instructing you to run `dokku ps:rebuild <app>` to recreate containers.
|
||||
- **k3s**: route changes take effect on the next deploy when the chart is re-rendered.
|
||||
- **openresty** and **haproxy**: routes are stored but inert under these backends. They activate automatically if you switch to a rendering backend (`nginx`, `traefik`, `caddy`, or `k3s`).
|
||||
- **haproxy**: routes are stored but inert under this backend. They activate automatically if you switch to a rendering backend (`nginx`, `traefik`, `caddy`, `openresty`, or `k3s`).
|
||||
- **WebSocket** traffic is forwarded transparently on `nginx`, `traefik`, `caddy`, and `k3s` backends - no additional configuration is required.
|
||||
- A route to a process that is scaled to `0` will resolve to no upstream until the process is scaled up (`dokku ps:scale <app> <process>=1`). `proxy:route:set` warns when this is the case.
|
||||
|
||||
|
||||
@@ -12,11 +12,26 @@ trigger-openresty-vhosts-docker-args-process-deploy() {
|
||||
local include_dir output value DATA
|
||||
local STDIN=$(cat)
|
||||
|
||||
if [[ "$PROC_TYPE" != "web" ]]; then
|
||||
if [[ "$(plugn trigger proxy-type "$APP")" != "openresty" ]] && [[ "$(plugn trigger proxy-type "$APP")" != "nginx" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$(plugn trigger proxy-type "$APP")" != "openresty" ]] && [[ "$(plugn trigger proxy-type "$APP")" != "nginx" ]]; then
|
||||
if [[ "$PROC_TYPE" != "web" ]]; then
|
||||
# Non-web processes are only labeled when at least one path-based route
|
||||
# targets the process. Otherwise openresty should ignore the container.
|
||||
local route_proc
|
||||
local has_route=false
|
||||
while IFS='|' read -r route_proc _ _ _; do
|
||||
if [[ "$route_proc" == "$PROC_TYPE" ]]; then
|
||||
has_route=true
|
||||
break
|
||||
fi
|
||||
done < <(plugn trigger proxy-routes-list "$APP")
|
||||
if [[ "$has_route" != "true" ]]; then
|
||||
echo -n "$STDIN"
|
||||
return
|
||||
fi
|
||||
fn-openresty-route-labels "$APP" "$PROC_TYPE" "$STDIN"
|
||||
return
|
||||
fi
|
||||
|
||||
|
||||
@@ -11,6 +11,35 @@ fn-get-pagesize() {
|
||||
"$PLUGIN_CORE_AVAILABLE_PATH/nginx-vhosts/pagesize"
|
||||
}
|
||||
|
||||
fn-openresty-route-labels() {
|
||||
declare desc="emits per-route openresty labels for a non-web container"
|
||||
declare APP="$1" PROC_TYPE="$2" STDIN="$3"
|
||||
local output route_proc route_port route_path route_strip route_index=0
|
||||
|
||||
output=""
|
||||
while IFS='|' read -r route_proc route_port route_path route_strip; do
|
||||
[[ -z "$route_proc" ]] && continue
|
||||
[[ "$route_proc" != "$PROC_TYPE" ]] && continue
|
||||
|
||||
output="$output --label 'openresty.route.${route_index}.path-prefix=${route_path}'"
|
||||
output="$output --label 'openresty.route.${route_index}.port=${route_port}'"
|
||||
if [[ "$route_strip" == "1" ]]; then
|
||||
output="$output --label 'openresty.route.${route_index}.strip-prefix=true'"
|
||||
fi
|
||||
route_index=$((route_index + 1))
|
||||
done < <(plugn trigger proxy-routes-list "$APP")
|
||||
|
||||
local proxy_labels_file_path=$(fn-proxy-get-labels-file-path "openresty" "$APP")
|
||||
if [[ -f "$proxy_labels_file_path" ]]; then
|
||||
while read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
output="$output --label '$line'"
|
||||
done <"$proxy_labels_file_path"
|
||||
fi
|
||||
|
||||
echo -n "$STDIN $output"
|
||||
}
|
||||
|
||||
fn-openresty-access-log-format() {
|
||||
declare desc="get the raw per-app access log format"
|
||||
declare APP="$1"
|
||||
|
||||
@@ -105,7 +105,7 @@ func CommandRouteClear(appName string) error {
|
||||
// labels are baked in at create-time).
|
||||
func warnIfRebuildRequired(appName string) {
|
||||
switch getComputedProxyType(appName) {
|
||||
case "traefik", "caddy":
|
||||
case "traefik", "caddy", "openresty":
|
||||
common.LogWarn(fmt.Sprintf(
|
||||
"Routes are applied via container labels under the %s proxy backend; run `dokku ps:rebuild %s` to recreate containers and pick up the change",
|
||||
getComputedProxyType(appName), appName,
|
||||
|
||||
88
tests/unit/proxy-route-caddy.bats
Normal file
88
tests/unit/proxy-route-caddy.bats
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
setup() {
|
||||
global_setup
|
||||
dokku nginx:stop
|
||||
dokku caddy:start
|
||||
create_app
|
||||
dokku proxy:set "$TEST_APP" caddy
|
||||
}
|
||||
|
||||
teardown() {
|
||||
dokku proxy:route:clear "$TEST_APP" >/dev/null 2>&1 || true
|
||||
destroy_app
|
||||
dokku caddy:stop
|
||||
dokku nginx:start
|
||||
global_teardown
|
||||
}
|
||||
|
||||
@test "(proxy-route:caddy) basic routing - /api/v0 hits api, / hits web" {
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:rebuild $TEST_APP"
|
||||
assert_success
|
||||
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
}
|
||||
|
||||
@test "(proxy-route:caddy) --strip-prefix toggles upstream path visibility" {
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001"
|
||||
assert_success
|
||||
run /bin/bash -c "dokku ps:rebuild $TEST_APP"
|
||||
assert_success
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "" "404"
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001 --strip-prefix"
|
||||
assert_success
|
||||
run /bin/bash -c "dokku ps:rebuild $TEST_APP"
|
||||
assert_success
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
}
|
||||
|
||||
@test "(proxy-route:caddy) removing a route falls back to web" {
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001 --strip-prefix"
|
||||
assert_success
|
||||
run /bin/bash -c "dokku ps:rebuild $TEST_APP"
|
||||
assert_success
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:remove $TEST_APP /api/v0"
|
||||
assert_success
|
||||
run /bin/bash -c "dokku ps:rebuild $TEST_APP"
|
||||
assert_success
|
||||
|
||||
run curl --connect-to "${TEST_APP}.${DOKKU_DOMAIN}:80:localhost:80" -kSso /tmp/route-removed-body "http://${TEST_APP}.${DOKKU_DOMAIN}:80/api/v0/Procfile"
|
||||
run /bin/bash -c "grep -c 'python3 -m http.server' /tmp/route-removed-body || true"
|
||||
assert_output "0"
|
||||
}
|
||||
|
||||
add_api_process_callback() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
echo "api: python3 -m http.server 5001" >>"$APP_REPO_DIR/Procfile"
|
||||
}
|
||||
111
tests/unit/proxy-route-k3s-nginx.bats
Normal file
111
tests/unit/proxy-route-k3s-nginx.bats
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
TEST_APP="rdmtestapp"
|
||||
|
||||
setup() {
|
||||
uninstall_k3s || true
|
||||
global_setup
|
||||
dokku nginx:stop
|
||||
export KUBECONFIG="/etc/rancher/k3s/k3s.yaml"
|
||||
}
|
||||
|
||||
teardown() {
|
||||
global_teardown
|
||||
dokku nginx:start
|
||||
uninstall_k3s || true
|
||||
}
|
||||
|
||||
@test "(proxy-route:k3s-nginx) basic routing - /api/v0 hits api, / hits web" {
|
||||
if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then
|
||||
skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN"
|
||||
fi
|
||||
|
||||
INGRESS_CLASS=nginx install_k3s
|
||||
|
||||
run /bin/bash -c "dokku apps:create $TEST_APP"
|
||||
assert_success
|
||||
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
}
|
||||
|
||||
@test "(proxy-route:k3s-nginx) --strip-prefix renders rewrite-target Ingress and strips upstream path" {
|
||||
if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then
|
||||
skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN"
|
||||
fi
|
||||
|
||||
INGRESS_CLASS=nginx install_k3s
|
||||
|
||||
run /bin/bash -c "dokku apps:create $TEST_APP"
|
||||
assert_success
|
||||
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001 --strip-prefix"
|
||||
assert_success
|
||||
|
||||
# The chart should emit an additional Ingress carrying the rewrite-target
|
||||
# annotation when --strip-prefix is set.
|
||||
run /bin/bash -c "kubectl get ingress -o json | jq -r '.items[].metadata.annotations | select(.[\"nginx.ingress.kubernetes.io/rewrite-target\"] != null) | .[\"nginx.ingress.kubernetes.io/rewrite-target\"]'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "/\$2"
|
||||
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
}
|
||||
|
||||
@test "(proxy-route:k3s-nginx) removing a route drops the extra Ingress paths" {
|
||||
if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then
|
||||
skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN"
|
||||
fi
|
||||
|
||||
INGRESS_CLASS=nginx install_k3s
|
||||
|
||||
run /bin/bash -c "dokku apps:create $TEST_APP"
|
||||
assert_success
|
||||
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001 --strip-prefix"
|
||||
assert_success
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:remove $TEST_APP /api/v0"
|
||||
assert_success
|
||||
|
||||
# The rewrite-target Ingress should be gone after removal.
|
||||
run /bin/bash -c "kubectl get ingress -o json | jq -r '[.items[] | select(.metadata.annotations[\"nginx.ingress.kubernetes.io/rewrite-target\"] != null)] | length'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "0"
|
||||
}
|
||||
|
||||
add_api_process_callback() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
echo "api: python3 -m http.server 5001" >>"$APP_REPO_DIR/Procfile"
|
||||
}
|
||||
110
tests/unit/proxy-route-k3s-traefik.bats
Normal file
110
tests/unit/proxy-route-k3s-traefik.bats
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
TEST_APP="rdmtestapp"
|
||||
|
||||
setup() {
|
||||
uninstall_k3s || true
|
||||
global_setup
|
||||
dokku nginx:stop
|
||||
export KUBECONFIG="/etc/rancher/k3s/k3s.yaml"
|
||||
}
|
||||
|
||||
teardown() {
|
||||
global_teardown
|
||||
dokku nginx:start
|
||||
uninstall_k3s || true
|
||||
}
|
||||
|
||||
@test "(proxy-route:k3s-traefik) basic routing - /api/v0 hits api, / hits web" {
|
||||
if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then
|
||||
skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN"
|
||||
fi
|
||||
|
||||
INGRESS_CLASS=traefik install_k3s
|
||||
|
||||
run /bin/bash -c "dokku apps:create $TEST_APP"
|
||||
assert_success
|
||||
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
}
|
||||
|
||||
@test "(proxy-route:k3s-traefik) --strip-prefix renders stripPrefix Middleware and strips upstream path" {
|
||||
if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then
|
||||
skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN"
|
||||
fi
|
||||
|
||||
INGRESS_CLASS=traefik install_k3s
|
||||
|
||||
run /bin/bash -c "dokku apps:create $TEST_APP"
|
||||
assert_success
|
||||
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001 --strip-prefix"
|
||||
assert_success
|
||||
|
||||
# The chart should emit a StripPrefix Middleware CRD when --strip-prefix
|
||||
# is set.
|
||||
run /bin/bash -c "kubectl get middleware.traefik.io -o json | jq -r '.items[].spec.stripPrefix.prefixes[]?' | head -1"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "/api/v0"
|
||||
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
}
|
||||
|
||||
@test "(proxy-route:k3s-traefik) removing a route drops the stripPrefix Middleware" {
|
||||
if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then
|
||||
skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN"
|
||||
fi
|
||||
|
||||
INGRESS_CLASS=traefik install_k3s
|
||||
|
||||
run /bin/bash -c "dokku apps:create $TEST_APP"
|
||||
assert_success
|
||||
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001 --strip-prefix"
|
||||
assert_success
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:remove $TEST_APP /api/v0"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "kubectl get middleware.traefik.io -o json | jq -r '[.items[] | select(.spec.stripPrefix != null)] | length'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "0"
|
||||
}
|
||||
|
||||
add_api_process_callback() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
echo "api: python3 -m http.server 5001" >>"$APP_REPO_DIR/Procfile"
|
||||
}
|
||||
107
tests/unit/proxy-route-nginx.bats
Normal file
107
tests/unit/proxy-route-nginx.bats
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
setup() {
|
||||
global_setup
|
||||
create_app
|
||||
}
|
||||
|
||||
teardown() {
|
||||
dokku proxy:route:clear "$TEST_APP" >/dev/null 2>&1 || true
|
||||
destroy_app
|
||||
global_teardown
|
||||
}
|
||||
|
||||
@test "(proxy-route:nginx) basic routing - /api/v0 hits api, / hits web" {
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
}
|
||||
|
||||
@test "(proxy-route:nginx) --strip-prefix toggles upstream path visibility" {
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
# Without --strip-prefix, /api/v0/Procfile is forwarded as-is to api,
|
||||
# which does not have an /api/v0/Procfile file on disk.
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001"
|
||||
assert_success
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "" "404"
|
||||
|
||||
# With --strip-prefix, /api/v0/Procfile becomes /Procfile upstream and api
|
||||
# returns the Procfile content.
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001 --strip-prefix"
|
||||
assert_success
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
}
|
||||
|
||||
@test "(proxy-route:nginx) removing a route falls back to web" {
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001 --strip-prefix"
|
||||
assert_success
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:remove $TEST_APP /api/v0"
|
||||
assert_success
|
||||
|
||||
# Web does not serve /api/v0/Procfile; we just assert it is no longer the
|
||||
# api-served Procfile content.
|
||||
run curl --connect-to "${TEST_APP}.${DOKKU_DOMAIN}:80:localhost:80" -kSso /tmp/route-removed-body "http://${TEST_APP}.${DOKKU_DOMAIN}:80/api/v0/Procfile"
|
||||
run /bin/bash -c "grep -c 'python3 -m http.server' /tmp/route-removed-body || true"
|
||||
assert_output "0"
|
||||
}
|
||||
|
||||
@test "(proxy-route:nginx) websocket upgrade headers are present in rendered config" {
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
assert_success
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku nginx:show-config $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "location /api/v0"
|
||||
assert_output_contains "proxy_http_version 1.1"
|
||||
assert_output_contains "proxy_set_header Upgrade \$http_upgrade"
|
||||
assert_output_contains "proxy_set_header Connection \$http_connection"
|
||||
}
|
||||
|
||||
add_api_process_callback() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
# api serves /app's working directory on port 5001. GET /Procfile returns
|
||||
# the literal Procfile bytes, which web.py does not serve - this gives the
|
||||
# HTTP assertions a clear marker for which process handled the request.
|
||||
echo "api: python3 -m http.server 5001" >>"$APP_REPO_DIR/Procfile"
|
||||
}
|
||||
88
tests/unit/proxy-route-openresty.bats
Normal file
88
tests/unit/proxy-route-openresty.bats
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
setup() {
|
||||
global_setup
|
||||
dokku nginx:stop
|
||||
dokku openresty:start
|
||||
create_app
|
||||
dokku proxy:set "$TEST_APP" openresty
|
||||
}
|
||||
|
||||
teardown() {
|
||||
dokku proxy:route:clear "$TEST_APP" >/dev/null 2>&1 || true
|
||||
destroy_app
|
||||
dokku openresty:stop
|
||||
dokku nginx:start
|
||||
global_teardown
|
||||
}
|
||||
|
||||
@test "(proxy-route:openresty) basic routing - /api/v0 hits api, / hits web" {
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:rebuild $TEST_APP"
|
||||
assert_success
|
||||
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
}
|
||||
|
||||
@test "(proxy-route:openresty) --strip-prefix toggles upstream path visibility" {
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001"
|
||||
assert_success
|
||||
run /bin/bash -c "dokku ps:rebuild $TEST_APP"
|
||||
assert_success
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "" "404"
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001 --strip-prefix"
|
||||
assert_success
|
||||
run /bin/bash -c "dokku ps:rebuild $TEST_APP"
|
||||
assert_success
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
}
|
||||
|
||||
@test "(proxy-route:openresty) removing a route falls back to web" {
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001 --strip-prefix"
|
||||
assert_success
|
||||
run /bin/bash -c "dokku ps:rebuild $TEST_APP"
|
||||
assert_success
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:remove $TEST_APP /api/v0"
|
||||
assert_success
|
||||
run /bin/bash -c "dokku ps:rebuild $TEST_APP"
|
||||
assert_success
|
||||
|
||||
run curl --connect-to "${TEST_APP}.${DOKKU_DOMAIN}:80:localhost:80" -kSso /tmp/route-removed-body "http://${TEST_APP}.${DOKKU_DOMAIN}:80/api/v0/Procfile"
|
||||
run /bin/bash -c "grep -c 'python3 -m http.server' /tmp/route-removed-body || true"
|
||||
assert_output "0"
|
||||
}
|
||||
|
||||
add_api_process_callback() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
echo "api: python3 -m http.server 5001" >>"$APP_REPO_DIR/Procfile"
|
||||
}
|
||||
90
tests/unit/proxy-route-traefik.bats
Normal file
90
tests/unit/proxy-route-traefik.bats
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
setup() {
|
||||
global_setup
|
||||
dokku nginx:stop
|
||||
dokku traefik:start
|
||||
create_app
|
||||
dokku proxy:set "$TEST_APP" traefik
|
||||
}
|
||||
|
||||
teardown() {
|
||||
dokku proxy:route:clear "$TEST_APP" >/dev/null 2>&1 || true
|
||||
destroy_app
|
||||
dokku traefik:stop
|
||||
dokku nginx:start
|
||||
global_teardown
|
||||
}
|
||||
|
||||
@test "(proxy-route:traefik) basic routing - /api/v0 hits api, / hits web" {
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
# Routes are applied via container labels - rebuild so the api container
|
||||
# restarts with the new labels attached.
|
||||
run /bin/bash -c "dokku ps:rebuild $TEST_APP"
|
||||
assert_success
|
||||
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
}
|
||||
|
||||
@test "(proxy-route:traefik) --strip-prefix toggles upstream path visibility" {
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001"
|
||||
assert_success
|
||||
run /bin/bash -c "dokku ps:rebuild $TEST_APP"
|
||||
assert_success
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "" "404"
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001 --strip-prefix"
|
||||
assert_success
|
||||
run /bin/bash -c "dokku ps:rebuild $TEST_APP"
|
||||
assert_success
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
}
|
||||
|
||||
@test "(proxy-route:traefik) removing a route falls back to web" {
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" add_api_process_callback
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:scale $TEST_APP web=1 api=1"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0 --port 5001 --strip-prefix"
|
||||
assert_success
|
||||
run /bin/bash -c "dokku ps:rebuild $TEST_APP"
|
||||
assert_success
|
||||
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:remove $TEST_APP /api/v0"
|
||||
assert_success
|
||||
run /bin/bash -c "dokku ps:rebuild $TEST_APP"
|
||||
assert_success
|
||||
|
||||
run curl --connect-to "${TEST_APP}.${DOKKU_DOMAIN}:80:localhost:80" -kSso /tmp/route-removed-body "http://${TEST_APP}.${DOKKU_DOMAIN}:80/api/v0/Procfile"
|
||||
run /bin/bash -c "grep -c 'python3 -m http.server' /tmp/route-removed-body || true"
|
||||
assert_output "0"
|
||||
}
|
||||
|
||||
add_api_process_callback() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
echo "api: python3 -m http.server 5001" >>"$APP_REPO_DIR/Procfile"
|
||||
}
|
||||
@@ -218,20 +218,6 @@ teardown() {
|
||||
assert_output "true"
|
||||
}
|
||||
|
||||
@test "(proxy:route:set) stores routes under openresty backend even though they are inert" {
|
||||
dokku proxy:set "$TEST_APP" openresty >/dev/null 2>&1 || skip "openresty proxy not available"
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:set $TEST_APP api /api/v0"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:route:report $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_contains "/api/v0 -> api:5000"
|
||||
}
|
||||
|
||||
@test "(proxy:route:set) stores routes under haproxy backend even though they are inert" {
|
||||
dokku proxy:set "$TEST_APP" haproxy >/dev/null 2>&1 || skip "haproxy proxy not available"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user