Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ test-vcl-compile:
-e VARNISH_BACKEND_HOST=localhost \
-e VARNISH_BACKEND_PORT=9090 \
-e VARNISH_BACKEND_PROBE_PATH=/healthz \
$(IMAGE) /bin/bash -lc '/start.sh --check && grep -q "\\.host = \"localhost\";" /etc/varnish/backend.vcl && grep -q "\\.port = \"9090\";" /etc/varnish/backend.vcl && grep -q "\\.url = \"/healthz\";" /etc/varnish/backend.vcl'; \
$(IMAGE) /start.sh --check; \
echo "OK: Embedded VCL compiles with configured backend output"; \
name="$(CONTAINER_PREFIX)-compose-config-$$(openssl rand -hex 4)"; \
docker run --rm --name $$name --add-host web:127.0.0.1 \
Expand Down Expand Up @@ -140,6 +140,7 @@ test-container-restart:

test-smoke-runtime-interface:
@echo "=== Test: Runtime start interface ==="
@bash ./test/e2e/assert-check-isolation.sh $(IMAGE)
@bash ./test/e2e/assert-config-check.sh $(IMAGE)
@set -euo pipefail; \
name="$(CONTAINER_PREFIX)-runtime-$$(openssl rand -hex 4)"; \
Expand Down Expand Up @@ -290,17 +291,18 @@ test-smoke-runtime-interface:
docker rm -f $$name >/dev/null 2>&1 || true; \
exit 1; \
fi; \
docker exec $$name grep -q '.host = "localhost";' /etc/varnish/backend.vcl || { \
active_vcl=$$(docker exec $$name varnishadm vcl.show -v boot); \
grep -q '.host = "localhost";' <<< "$$active_vcl" || { \
echo "FAIL: expected rendered backend host in backend VCL"; \
docker exec $$name cat /etc/varnish/backend.vcl; \
exit 1; \
}; \
docker exec $$name grep -q '.port = "9090";' /etc/varnish/backend.vcl || { \
grep -q '.port = "9090";' <<< "$$active_vcl" || { \
echo "FAIL: expected rendered backend port in backend VCL"; \
docker exec $$name cat /etc/varnish/backend.vcl; \
exit 1; \
}; \
docker exec $$name grep -q '.url = "/healthz";' /etc/varnish/backend.vcl || { \
grep -q '.url = "/healthz";' <<< "$$active_vcl" || { \
echo "FAIL: expected rendered backend probe path in backend VCL"; \
docker exec $$name cat /etc/varnish/backend.vcl; \
exit 1; \
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ The container starts `varnishd` from named runtime values instead of a single ba

For advanced cases, `VARNISH_START` still works as a full-command override, but it cannot be combined with the narrower `VARNISH_*` settings or checked with `--check`.

Use `/start.sh --check` to validate the runtime settings, render the backend configuration, and compile the selected VCL without starting Varnish. This is the supported preflight for CI and rollout checks:
Use `/start.sh --check` to validate the runtime settings, render candidate backend configuration in scratch space, and compile the selected VCL without starting Varnish or changing live VCL files. This is the supported preflight for CI and rollout checks:

```bash
docker compose run --rm varnish /start.sh --check
Expand All @@ -57,7 +57,7 @@ Backend configuration also has named values:
- `VARNISH_BACKEND_PORT` defaults to `8080`
- `VARNISH_BACKEND_PROBE_PATH` defaults to `/`

These render the embedded `/etc/varnish/backend.vcl`. If you supply a custom `VARNISH_VCL`, keep backend wiring in that VCL instead.
On startup, these render a candidate backend, compile it with the selected VCL, and atomically replace `/etc/varnish/backend.vcl` only after validation succeeds. Checks never publish the candidate. Without overrides, the existing backend file is preserved. If you supply a custom `VARNISH_VCL`, keep backend wiring in that VCL instead. The embedded VCL uses a relative `backend.vcl` include so preflight can resolve the candidate without rewriting VCL.

## Restarting Varnish

Expand Down
2 changes: 1 addition & 1 deletion embedded-default.vcl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
vcl 4.0;

include "/etc/varnish/backend.vcl";
include "backend.vcl";
include "/etc/varnish/cache-policy.vcl";
5 changes: 1 addition & 4 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ apt-get -y install varnish=7.*
install -m 755 render-vcl.sh /usr/local/bin/render-vcl
cp embedded-default.vcl /etc/varnish/default.vcl
cp cache-policy.vcl /etc/varnish/cache-policy.vcl
VARNISH_BACKEND_HOST=127.0.0.1 \
VARNISH_BACKEND_PORT=8080 \
VARNISH_BACKEND_PROBE_PATH=/ \
/usr/local/bin/render-vcl /etc/varnish/backend.vcl
/usr/local/bin/render-vcl /etc/varnish/backend.vcl

touch /etc/varnish/secret
41 changes: 31 additions & 10 deletions start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@ if [ -n "${backend_host}${backend_port}${backend_probe_path}" ]; then
fail "VARNISH_BACKEND_* requires VARNISH_VCL to be /etc/varnish/default.vcl"
fi

VARNISH_BACKEND_HOST="${backend_host:-127.0.0.1}" \
VARNISH_BACKEND_PORT="${backend_port:-8080}" \
VARNISH_BACKEND_PROBE_PATH="${backend_probe_path:-/}" \
/usr/local/bin/render-vcl /etc/varnish/backend.vcl
fi

listen_host="${listen%:*}"
Expand Down Expand Up @@ -108,19 +104,44 @@ varnishd_args() {
fi
}

if [ "${check_mode}" = true ]; then
compile_output="$(mktemp)"
trap 'rm -f "$compile_output"' EXIT
# Candidates live on the destination filesystem so publication is one atomic rename.
# Without overrides, retain the caller's backend (including hand-edited files).
if [ -n "${backend_host}${backend_port}${backend_probe_path}" ]; then
scratch="$(mktemp -d /etc/varnish/.backend-XXXXXX)"
else
scratch="$(mktemp -d)"
fi
trap 'rm -rf "$scratch"' EXIT

if [ -n "${backend_host}${backend_port}${backend_probe_path}" ]; then
/usr/local/bin/render-vcl "${scratch}/backend.vcl"
fi

if [ "${check_mode}" = true ] || [ -f "${scratch}/backend.vcl" ]; then
varnishd_args -C
if "${args[@]}" >"${compile_output}" 2>&1; then
exit 0
# Isolate compiler artifacts from the running instance and concurrent checks.
# Relative backend includes resolve to the candidate first, policy includes to
# the installed configuration. No VCL text rewriting is needed.
args+=(-n "${scratch}/instance")
if [ -f "${scratch}/backend.vcl" ]; then
args+=(-p "vcl_path=${scratch}:/etc/varnish")
fi
if "${args[@]}" >"${scratch}/compile-output" 2>&1; then
:
else
compile_status=$?
cat "${compile_output}" >&2
cat "${scratch}/compile-output" >&2
exit "${compile_status}"
fi
if [ "${check_mode}" = true ]; then
exit 0
fi
mv -f "${scratch}/backend.vcl" /etc/varnish/backend.vcl
fi

rm -rf "${scratch}"
trap - EXIT

varnishd_args -F
exec "${args[@]}"

57 changes: 57 additions & 0 deletions test/e2e/assert-check-isolation.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
set -euo pipefail
image="${1:-jonbaldie/varnish:latest}"
name="varnish-test-check-isolation-$(openssl rand -hex 4)"
trap 'docker logs "$name" 2>&1 || true; docker rm -fv "$name" >/dev/null 2>&1 || true' EXIT
# A real running instance must retain its configuration across checks and restart.
docker run -d --name "$name" -p 127.0.0.1::80 "$image" >/dev/null
port="$(docker port "$name" 80/tcp | cut -d: -f2)"
wait_http() {
for _ in {1..60}; do
if curl -s --fail-with-body --max-time 1 "http://127.0.0.1:$port/" >/dev/null 2>&1; then return; fi
# The standalone backend is absent; 503 still proves Varnish serves HTTP.
if [[ "$(curl -s -o /dev/null -w '%{http_code}' --max-time 1 "http://127.0.0.1:$port/" || true)" == 503 ]]; then return; fi
sleep 0.2
done
echo 'FAIL: container did not serve HTTP'; return 1
}
wait_http
# Use -i to feed assertions through the supported entrypoint and real compiler.
docker exec -i "$name" bash -s <<'SCRIPT'
set -euo pipefail
trap 'rc=$?; if (( rc )); then cat /tmp/check-out 2>/dev/null || true; fi' EXIT
export VARNISH_LISTEN=127.0.0.1:18099
snapshot() { sha256sum /etc/varnish/*.vcl; }
before="$(snapshot)"
assert_unchanged() {
[[ "$(snapshot)" == "$before" ]] || { echo 'FAIL: check/start overwrote live VCL'; exit 1; }
[[ -z "$(find /etc/varnish -maxdepth 1 -name '.backend-*' -print)" ]] || { echo 'FAIL: candidate leaked'; exit 1; }
}
# Success must be quiet and must not publish a candidate.
VARNISH_BACKEND_HOST=localhost VARNISH_BACKEND_PORT=9090 VARNISH_BACKEND_PROBE_PATH=/healthz /start.sh --check >/tmp/check-out 2>&1
[[ ! -s /tmp/check-out ]]
assert_unchanged
# Candidate-specific compiler diagnostics prove it did not compile the live backend.
if VARNISH_BACKEND_HOST=nohost.invalid /start.sh --check >/tmp/check-out 2>&1; then exit 1; fi
grep -q nohost.invalid /tmp/check-out
grep -q 'VCL compilation failed' /tmp/check-out
assert_unchanged
# Failed startup must not publish either (render failure and compile failure).
for host in 'invalid/host' nohost.invalid; do
if VARNISH_BACKEND_HOST="$host" /start.sh >/tmp/check-out 2>&1; then exit 1; fi
assert_unchanged
done
# Concurrent checks must have private candidate and compiler work directories.
pids=()
for port in 9091 9092 9093; do
VARNISH_LISTEN="127.0.0.1:$port" VARNISH_BACKEND_HOST=localhost VARNISH_BACKEND_PORT="$port" /start.sh --check &
pids+=("$!")
done
for pid in "${pids[@]}"; do wait "$pid"; done
assert_unchanged
SCRIPT
wait_http
docker restart "$name" >/dev/null
port="$(docker port "$name" 80/tcp | cut -d: -f2)"
wait_http
echo 'OK: checks preserve live VCL; failed check followed by restart still serves HTTP'
Loading