fix: drop shell-quoted body grep and add docker label diagnostic

The retry loop in T3 fed the response body through `bash -c "grep <<<\"$body\""`. When the body contains JSON env vars with embedded double quotes (PS1, paths with `$`, etc.), the inner `"$body"` terminates the outer `bash -c "..."` quoting prematurely and bash tries to execute the JSON content as commands. grep itself returns 0 but the subsequent error messages get appended to bats's `$output`, breaking `assert_output "0"`. Each T3 now curls into a file and greps the file directly, so the body never crosses a shell-quote boundary. Each label-based backend file additionally echoes `docker inspect <app>.api.1 --format '{{json .Config.Labels}}'` after `ps:rebuild`, surfacing the actual labels emitted onto the api container so the CI log shows whether the docker-args-process-deploy emission matches what the proxy daemon expects. A 5s sleep after rebuild gives the proxy daemon time to read the new labels before the HTTP retry loop starts.
This commit is contained in:
Jose Diaz-Gonzalez
2026-06-02 19:38:35 -04:00
parent 6dbd9b4d82
commit 5dce07ca7e
4 changed files with 184 additions and 26 deletions

View File

@@ -37,6 +37,14 @@ teardown() {
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
# Without --strip-prefix, /api/v0/Procfile reaches api as /api/v0/Procfile
# which python's http.server returns 404 for. A 404 here proves the route
# reaches api - web's catch-all would have returned 200.
@@ -56,6 +64,14 @@ teardown() {
echo "output: $output"
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
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"
@@ -64,6 +80,14 @@ teardown() {
echo "output: $output"
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
}
@@ -81,6 +105,14 @@ teardown() {
echo "output: $output"
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
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"
@@ -89,6 +121,14 @@ teardown() {
echo "output: $output"
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
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"
@@ -107,17 +147,29 @@ teardown() {
echo "status: $status"
assert_success
local body=""
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
# Poll the response body via file to avoid shell-quoting issues with JSON
# bodies that contain embedded double quotes.
rm -f /tmp/route-removed-body
local attempt=0
for attempt in $(seq 1 30); do
run curl --connect-to "${TEST_APP}.${DOKKU_DOMAIN}:80:localhost:80" -kSs "http://${TEST_APP}.${DOKKU_DOMAIN}:80/api/v0/Procfile"
body="$output"
if ! grep -q 'python3 -m http.server' <<<"$body"; then
curl --connect-to "${TEST_APP}.${DOKKU_DOMAIN}:80:localhost:80" -kSso /tmp/route-removed-body "http://${TEST_APP}.${DOKKU_DOMAIN}:80/api/v0/Procfile" || true
if ! grep -q 'python3 -m http.server' /tmp/route-removed-body 2>/dev/null; then
break
fi
sleep 1
done
echo "final body attempt $attempt: $body"
run /bin/bash -c "grep -c 'python3 -m http.server' <<<\"$body\" || true"
echo "final body attempt $attempt:"
cat /tmp/route-removed-body
echo
run /bin/bash -c "grep -c 'python3 -m http.server' /tmp/route-removed-body || true"
assert_output "0"
}

View File

@@ -99,18 +99,21 @@ teardown() {
# return 200 for /api/v0/Procfile so the polling loop in
# assert_http_localhost_response_contains exits on status alone before
# nginx picks up the new config. Hand-roll a retry that polls the BODY
# until it no longer contains api's marker.
local body=""
# written to a file (avoids shell-quoting issues with JSON bodies that
# contain embedded double quotes).
rm -f /tmp/route-removed-body
local attempt=0
for attempt in $(seq 1 30); do
run curl --connect-to "${TEST_APP}.${DOKKU_DOMAIN}:80:localhost:80" -kSs "http://${TEST_APP}.${DOKKU_DOMAIN}:80/api/v0/Procfile"
body="$output"
if ! grep -q 'python3 -m http.server' <<<"$body"; then
curl --connect-to "${TEST_APP}.${DOKKU_DOMAIN}:80:localhost:80" -kSso /tmp/route-removed-body "http://${TEST_APP}.${DOKKU_DOMAIN}:80/api/v0/Procfile" || true
if ! grep -q 'python3 -m http.server' /tmp/route-removed-body 2>/dev/null; then
break
fi
sleep 1
done
echo "final body attempt $attempt: $body"
run /bin/bash -c "grep -c 'python3 -m http.server' <<<\"$body\" || true"
echo "final body attempt $attempt:"
cat /tmp/route-removed-body
echo
run /bin/bash -c "grep -c 'python3 -m http.server' /tmp/route-removed-body || true"
assert_output "0"
}

View File

@@ -37,6 +37,14 @@ teardown() {
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
# Without --strip-prefix, /api/v0/Procfile reaches api as /api/v0/Procfile
# which python's http.server returns 404 for. A 404 here proves the route
# reaches api - web's catch-all would have returned 200.
@@ -56,6 +64,14 @@ teardown() {
echo "output: $output"
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
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"
@@ -64,6 +80,14 @@ teardown() {
echo "output: $output"
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
}
@@ -81,6 +105,14 @@ teardown() {
echo "output: $output"
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
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"
@@ -89,6 +121,14 @@ teardown() {
echo "output: $output"
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
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"
@@ -107,17 +147,29 @@ teardown() {
echo "status: $status"
assert_success
local body=""
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
# Poll the response body via file to avoid shell-quoting issues with JSON
# bodies that contain embedded double quotes.
rm -f /tmp/route-removed-body
local attempt=0
for attempt in $(seq 1 30); do
run curl --connect-to "${TEST_APP}.${DOKKU_DOMAIN}:80:localhost:80" -kSs "http://${TEST_APP}.${DOKKU_DOMAIN}:80/api/v0/Procfile"
body="$output"
if ! grep -q 'python3 -m http.server' <<<"$body"; then
curl --connect-to "${TEST_APP}.${DOKKU_DOMAIN}:80:localhost:80" -kSso /tmp/route-removed-body "http://${TEST_APP}.${DOKKU_DOMAIN}:80/api/v0/Procfile" || true
if ! grep -q 'python3 -m http.server' /tmp/route-removed-body 2>/dev/null; then
break
fi
sleep 1
done
echo "final body attempt $attempt: $body"
run /bin/bash -c "grep -c 'python3 -m http.server' <<<\"$body\" || true"
echo "final body attempt $attempt:"
cat /tmp/route-removed-body
echo
run /bin/bash -c "grep -c 'python3 -m http.server' /tmp/route-removed-body || true"
assert_output "0"
}

View File

@@ -39,6 +39,14 @@ teardown() {
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
# Without --strip-prefix, /api/v0/Procfile reaches api as /api/v0/Procfile
# which python's http.server returns 404 for. A 404 here proves the route
# reaches api - web's catch-all would have returned 200.
@@ -58,6 +66,14 @@ teardown() {
echo "output: $output"
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
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"
@@ -66,6 +82,14 @@ teardown() {
echo "output: $output"
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "python3 -m http.server"
}
@@ -84,6 +108,14 @@ teardown() {
echo "output: $output"
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
assert_http_localhost_response_contains "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/api/v0/Procfile" "" "404"
# Switch to --strip-prefix and verify api now serves the Procfile.
@@ -93,6 +125,14 @@ teardown() {
echo "output: $output"
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
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"
@@ -112,20 +152,31 @@ teardown() {
echo "status: $status"
assert_success
# Surface the actual labels attached to the api container so CI logs show
# whether docker-args-process-deploy emitted them correctly.
run /bin/bash -c "docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1 | jq . 2>/dev/null || docker inspect ${TEST_APP}.api.1 --format '{{json .Config.Labels}}' 2>&1"
echo "api labels: $output"
# Give the daemon a moment to read the new labels.
sleep 5
# After ps:rebuild, traefik recreates the api container without the route
# labels, but there is still a race with traefik picking up the new state.
# Hand-roll a retry that polls the BODY until api's marker is gone.
local body=""
# Poll the response body via file to avoid shell-quoting issues with JSON
# bodies that contain embedded double quotes.
rm -f /tmp/route-removed-body
local attempt=0
for attempt in $(seq 1 30); do
run curl --connect-to "${TEST_APP}.${DOKKU_DOMAIN}:80:localhost:80" -kSs "http://${TEST_APP}.${DOKKU_DOMAIN}:80/api/v0/Procfile"
body="$output"
if ! grep -q 'python3 -m http.server' <<<"$body"; then
curl --connect-to "${TEST_APP}.${DOKKU_DOMAIN}:80:localhost:80" -kSso /tmp/route-removed-body "http://${TEST_APP}.${DOKKU_DOMAIN}:80/api/v0/Procfile" || true
if ! grep -q 'python3 -m http.server' /tmp/route-removed-body 2>/dev/null; then
break
fi
sleep 1
done
echo "final body attempt $attempt: $body"
run /bin/bash -c "grep -c 'python3 -m http.server' <<<\"$body\" || true"
echo "final body attempt $attempt:"
cat /tmp/route-removed-body
echo
run /bin/bash -c "grep -c 'python3 -m http.server' /tmp/route-removed-body || true"
assert_output "0"
}