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
53 changes: 50 additions & 3 deletions docker/warm-extensions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
set -eu

WEBROOT="$(/usr/local/bin/rustcfml-webroot)"
# Absolute, so the link into the probe's scratch directory (below) points at it
# from anywhere, not relative to the scratch directory.
if [ -d "$WEBROOT" ]; then
WEBROOT="$(cd "$WEBROOT" && pwd)"
fi
STRICT="${RUSTCFML_EXTENSIONS_STRICT:-1}"
BIN_DIR="$(dirname "$(readlink -f "$(command -v rustcfml)")")"

Expand All @@ -37,19 +42,61 @@ if [ "$found" -eq 0 ] && [ -z "${CFCONFIG:-}" ] && [ ! -f "$WEBROOT/.cfconfig.js
exit 0
fi

set -- --verbose -c 'writeOutput("")'
# The probe is an empty template in a scratch directory, not inline code run in
# the webroot: inline code there runs the app's Application.cfc (onApplicationStart,
# then onRequest with no page), so every container start would boot the app once
# against its database and could log an error.
#
# - An empty Application.cfc beside the probe ends the engine's upward search
# for one, so the app's is not found even when the scratch directory is under
# the webroot (TMPDIR inside it, or a webroot of /tmp).
# - The engine's application directory is the template's, so the webroot's
# extensions/ is linked into the scratch directory to keep it in the search.
# - The cwd stays the webroot, so the server .cfconfig.json (and its
# extensions.directory) is found exactly as the server finds it.
#
# With no writable temp directory (a read-only container), fall back to the
# inline probe in the webroot, which runs the app's Application.cfc as before.
probe=""
if probe="$(mktemp -d 2>/dev/null)"; then
trap 'rm -rf "$probe"' EXIT
: > "$probe/probe.cfm"
printf '<cfcomponent></cfcomponent>\n' > "$probe/Application.cfc"
[ -d "$WEBROOT/extensions" ] && ln -s "$WEBROOT/extensions" "$probe/extensions"
set -- --verbose "$probe/probe.cfm"
else
probe=""
echo "rustcfml: no writable temp directory; checking extensions from the webroot, which runs its Application.cfc" >&2
set -- --verbose -c 'writeOutput("")'
fi
[ -n "${RUSTCFML_EXTENSIONS:-}" ] && set -- --extensions "$RUSTCFML_EXTENSIONS" "$@"

# One engine start in CLI mode walks the exact loader the server uses; verbose
# prints "Loaded extension …" per success and "…warning…" per problem.
# prints "Loaded extension …" per success and "…warning…" per problem. Counts
# come from the engine's own output; only the lines shown are edited.
out="$(cd "$WEBROOT" && rustcfml "$@" 2>&1 | grep -v '^\[.*\] DEBUG ' || true)"

loaded="$(printf '%s\n' "$out" | grep -c '^Loaded extension ' || true)"
problems="$(printf '%s\n' "$out" | grep -i -c 'warning' || true)"

# Report the scratch link's paths as the webroot's. A literal replacement (awk
# index), so no character in either path can change what is matched.
show() {
FROM="${probe:+$probe/extensions/}" TO="$WEBROOT/extensions/" awk '
BEGIN { f = ENVIRON["FROM"]; t = ENVIRON["TO"] }
{
rest = $0; done = ""
if (f != "") while ((i = index(rest, f)) > 0) {
done = done substr(rest, 1, i - 1) t
rest = substr(rest, i + length(f))
}
print " " done rest
}'
}

if [ "$found" -gt 0 ] || [ "$loaded" -gt 0 ] || [ "$problems" -gt 0 ]; then
echo "rustcfml: extensions — $found .rcx found, $loaded loaded, $problems problem(s)"
printf '%s\n' "$out" | grep -E '^Loaded extension |[Ww]arning' | sed 's/^/ /' || true
printf '%s\n' "$out" | grep -E '^Loaded extension |[Ww]arning' | show || true
fi

if [ "$problems" -gt 0 ] && [ "$STRICT" = 1 ]; then
Expand Down
53 changes: 53 additions & 0 deletions tests/smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,59 @@ curl -s "http://127.0.0.1:$PORT/index.cfm" | grep -q "Hello from RustCFML" || fa
docker rm -f "$cid" >/dev/null; trap - EXIT
rm -rf "$tmp"

echo "--- the extension check does not start the application"
# The check runs before the server, at every container start. Booting the app's
# Application.cfc there would open its database and fire its handlers with no page.
tmp=$(mktemp -d)
echo '{}' > "$tmp/.cfconfig.json"
cat > "$tmp/Application.cfc" <<'CFC'
<cfcomponent>
<cfset this.name = "smoke_no_boot" />
<cffunction name="onApplicationStart"><cfset fileWrite("/tmp/app-started", "x") /><cfreturn true /></cffunction>
</cfcomponent>
CFC
docker run --rm --entrypoint sh -v "$tmp:/srv/app:ro" "$IMAGE" \
-c 'rustcfml-warm-extensions && test ! -e /tmp/app-started' \
|| fail "the extension check ran the application's onApplicationStart"
rm -rf "$tmp"

echo "--- ... nor when its scratch directory is inside the webroot"
# The probe's own empty Application.cfc stops the engine's upward search there.
tmp=$(mktemp -d); mkdir -m 0777 "$tmp/scratch"
echo '{}' > "$tmp/.cfconfig.json"
cat > "$tmp/Application.cfc" <<'CFC'
<cfcomponent>
<cfset this.name = "smoke_no_boot" />
<cffunction name="onApplicationStart"><cfset fileWrite("/tmp/app-started", "x") /><cfreturn true /></cffunction>
</cfcomponent>
CFC
docker run --rm --entrypoint sh -v "$tmp:/srv/app" -e TMPDIR=/srv/app/scratch "$IMAGE" \
-c 'rustcfml-warm-extensions && test ! -e /tmp/app-started' \
|| fail "with TMPDIR inside the webroot, the extension check ran onApplicationStart"
rm -rf "$tmp"

echo "--- a broken extension is still fatal from a relative webroot, or one with | and & in its path"
tmp=$(mktemp -d); mkdir -p "$tmp/extensions"; echo junk > "$tmp/extensions/bad-0.0.1.rcx"
set +e
docker run --rm --entrypoint sh -v "$tmp:/srv/app:ro" -w /srv/app -e RUSTCFML_WEBROOT=. "$IMAGE" \
-c 'rustcfml-warm-extensions' >/dev/null 2>&1; rc=$?
set -e
[ "$rc" -eq 78 ] || fail "relative webroot: expected exit 78, got $rc"
set +e
out=$(docker run --rm --entrypoint sh -v "$tmp:/srv/a|b&c:ro" -e "RUSTCFML_WEBROOT=/srv/a|b&c" "$IMAGE" \
-c 'rustcfml-warm-extensions' 2>&1); rc=$?
set -e
echo "$out"
[ "$rc" -eq 78 ] || fail "webroot with | and &: expected exit 78, got $rc"
echo "$out" | grep -qF "/srv/a|b&c/extensions/bad-0.0.1.rcx" || fail "webroot with | and &: path not reported as the webroot's"
rm -rf "$tmp"

echo "--- a read-only container still passes the extension check"
tmp=$(mktemp -d); echo '{}' > "$tmp/.cfconfig.json"
docker run --rm --read-only --entrypoint sh -v "$tmp:/srv/app:ro" "$IMAGE" -c 'rustcfml-warm-extensions' \
|| fail "the extension check failed in a read-only container"
rm -rf "$tmp"

if [ -n "${SMOKE_RCX:-}" ]; then
echo "--- real extension: $SMOKE_RCX"
tmp=$(mktemp -d); mkdir -p "$tmp/extensions"; cp "$SMOKE_RCX" "$tmp/extensions/"
Expand Down
Loading