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
46 changes: 39 additions & 7 deletions scripts/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -219,19 +219,51 @@ if [ -n "${OSC_ACCESS_TOKEN:-}" ] && [ -n "${CONFIG_SVC:-}" ]; then
# failing command substitution used as an assignment's RHS is a "simple
# command" and is NOT exempt from -e, so a bare non-zero exit here would
# kill the whole script immediately, before config_exit is ever read below
# — silently skipping the tolerant log-and-continue path this config_exit
# check implements, for a timeout exit (124) same as any other non-zero
# — silently skipping the explicit config_exit handling below (so no
# [CONFIG] message), for a timeout exit (124) same as any other non-zero
# exit from this call.
config_env_output=$(timeout 60s npx -y @osaas/cli@latest web config-to-env ${OSC_ENV:+--env "$OSC_ENV"} "$CONFIG_SVC" 2>&1) && config_exit=0 || config_exit=$?
if [ $config_exit -eq 0 ]; then
valid_exports=$(echo "$config_env_output" | grep "^export [A-Za-z_][A-Za-z0-9_]*=")
# The same set -e rule applies to the grep below: `x=$(... | grep ...)`
# exits the script silently when grep matches nothing, so it is guarded
# with an explicit `|| valid_exports=""`.
# A failure to load the config is fatal: continuing would start the app
# without its environment variables. This runner has no loading server, so
# (like the setup.sh timeout below) failure is a non-zero exit.
config_err_file=$(mktemp)
config_env_output=$(timeout 60s npx -y @osaas/cli@latest web config-to-env ${OSC_ENV:+--env "$OSC_ENV"} "$CONFIG_SVC" 2>"$config_err_file") && config_exit=0 || config_exit=$?
config_err_output=$(cat "$config_err_file" 2>/dev/null || true)
rm -f "$config_err_file"
config_failed=0
if [ $config_exit -ne 0 ]; then
config_failed=1
if [ $config_exit -eq 124 ]; then
echo "[CONFIG] ERROR: Timed out loading config from config service '$CONFIG_SVC'" >&2
else
echo "[CONFIG] ERROR: Failed to load config (exit $config_exit): $config_env_output $config_err_output" >&2
fi
if echo "$config_env_output $config_err_output" | grep -qiE '401|403|unauthori[sz]ed|forbidden|expired'; then
echo "[CONFIG] Hint: the access token may have expired. Run refresh-app-config to renew it." >&2
fi
elif [ -z "$(echo "$config_env_output" | tr -d '[:space:]')" ]; then
# Empty store is not a failure
echo "[CONFIG] Config service '$CONFIG_SVC' has no parameters"
else
valid_exports=$(echo "$config_env_output" | grep "^export [A-Za-z_][A-Za-z0-9_]*=") || valid_exports=""
if [ -n "$valid_exports" ]; then
eval "$valid_exports"
var_count=$(echo "$valid_exports" | wc -l | tr -d ' ')
echo "[CONFIG] Loaded $var_count environment variable(s)"
else
config_failed=1
echo "[CONFIG] ERROR: Config service '$CONFIG_SVC' returned output with no valid export lines: $config_env_output" >&2
fi
else
echo "[CONFIG] ERROR: Failed to load config (exit $config_exit): $config_env_output" >&2
fi
if [ $config_failed -ne 0 ]; then
if [ $config_exit -eq 124 ]; then
exit 124
elif [ $config_exit -ne 0 ]; then
exit "$config_exit"
fi
exit 1
fi
fi

Expand Down
80 changes: 80 additions & 0 deletions tests/test-entrypoint-config-load-failure.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# Behavioural tests for the CONFIG_SVC load block in scripts/docker-entrypoint.sh.
# The real block is extracted from the entrypoint and run under `set -e` with
# stub `npx`, `curl` and `timeout` on PATH.

ENTRYPOINT="scripts/docker-entrypoint.sh"
PASS=0
FAIL=0
pass() { echo "PASS: $1"; PASS=$((PASS + 1)); }
fail() { echo "FAIL: $1"; FAIL=$((FAIL + 1)); }

sandbox=$(mktemp -d)
trap 'rm -rf "$sandbox"' EXIT
mkdir "$sandbox/bin"

awk '/^# Load environment variables from config service if configured/{f=1} /^# Install Python dependencies/{f=0} f' "$ENTRYPOINT" > "$sandbox/block.sh"
if [ ! -s "$sandbox/block.sh" ]; then
fail "could not extract config block from entrypoint"
exit 1
fi

cat > "$sandbox/bin/curl" <<'STUB'
#!/bin/bash
exit 22
STUB
# timeout stub: STUB_MODE=timeout simulates exit 124, otherwise run the command
cat > "$sandbox/bin/timeout" <<'STUB'
#!/bin/bash
if [ "$STUB_MODE" = "timeout" ]; then exit 124; fi
shift
exec "$@"
STUB
cat > "$sandbox/bin/npx" <<'STUB'
#!/bin/bash
case "$STUB_MODE" in
fail) echo "boom 401 unauthorized" >&2; exit 2 ;;
empty) exit 0 ;;
garbled) echo "npm notice something"; echo "export my-key=1"; exit 0 ;;
stderr_only) echo "npm warn noise" >&2; exit 0 ;;
ok) echo "export FOO=bar"; echo "export BAZ=qux"; exit 0 ;;
esac
STUB
chmod +x "$sandbox/bin/"*

run_block() {
# $1 = mode; remaining args are env assignments
local mode=$1; shift
env -i PATH="$sandbox/bin:/usr/bin:/bin" STUB_MODE="$mode" "$@" \
bash -c 'set -e; '"$(cat "$sandbox/block.sh")"'; echo "REACHED_PIP FOO=${FOO:-}"' 2>"$sandbox/stderr" >"$sandbox/stdout"
echo $?
}

ENVV=(OSC_ACCESS_TOKEN=tok CONFIG_SVC=store)

# Failure paths: non-zero exit and never reach the next phase
rc=$(run_block timeout "${ENVV[@]}")
if [ "$rc" = "124" ] && ! grep -q REACHED_PIP "$sandbox/stdout" && grep -q '\[CONFIG\] ERROR' "$sandbox/stderr"; then pass "timeout exits 124 before pip install"; else fail "timeout (rc=$rc)"; fi

rc=$(run_block fail "${ENVV[@]}")
if [ "$rc" = "2" ] && ! grep -q REACHED_PIP "$sandbox/stdout" && grep -q '\[CONFIG\] ERROR' "$sandbox/stderr" && grep -q refresh-app-config "$sandbox/stderr"; then pass "non-zero exit propagates and prints refresh hint"; else fail "non-zero exit (rc=$rc)"; fi

rc=$(run_block garbled "${ENVV[@]}")
if [ "$rc" != "0" ] && ! grep -q REACHED_PIP "$sandbox/stdout" && grep -q '\[CONFIG\] ERROR' "$sandbox/stderr"; then pass "non-empty output with no valid exports exits non-zero"; else fail "garbled output (rc=$rc)"; fi

# Passing paths
rc=$(run_block empty "${ENVV[@]}")
if [ "$rc" = "0" ] && grep -q "REACHED_PIP" "$sandbox/stdout" && grep -q "has no parameters" "$sandbox/stdout"; then pass "empty store logs and continues"; else fail "empty store (rc=$rc)"; fi

rc=$(run_block stderr_only "${ENVV[@]}")
if [ "$rc" = "0" ] && grep -q "REACHED_PIP" "$sandbox/stdout"; then pass "stderr noise with empty stdout still continues"; else fail "stderr only (rc=$rc)"; fi

rc=$(run_block ok "${ENVV[@]}")
if [ "$rc" = "0" ] && grep -q "REACHED_PIP FOO=bar" "$sandbox/stdout" && grep -q "Loaded 2 environment variable" "$sandbox/stdout"; then pass "valid exports are evaluated"; else fail "success path (rc=$rc)"; fi

rc=$(run_block fail OSC_ACCESS_TOKEN=tok)
if [ "$rc" = "0" ] && grep -q "REACHED_PIP" "$sandbox/stdout"; then pass "no CONFIG_SVC: block skipped"; else fail "no CONFIG_SVC (rc=$rc)"; fi

echo ""
echo "Results: $PASS passed, $FAIL failed"
[ $FAIL -eq 0 ]
Loading