chart/ops/chart-debug-command

111 lines
3.7 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
CONFIG=/etc/chart-debug.conf
MAX_LOG_LINES=4000
if [[ ! -r "$CONFIG" ]]; then
echo "chart-debug is not configured" >&2
exit 1
fi
# Root-owned configuration written by install-chart-debug.
# shellcheck source=/dev/null
source "$CONFIG"
original=${1:-${SSH_ORIGINAL_COMMAND:-}}
if [[ -z "$original" || "$original" == *$'\n'* || "$original" == *$'\r'* ]]; then
echo "usage: logs --since 20m | status | container-state | recent-deploy | capture-read ID | capture-delete ID" >&2
exit 2
fi
read -r -a args <<< "$original"
logger -t chart-debug -- "user=${SUDO_USER:-${USER:-unknown}} from=${SSH_CONNECTION:-local} command=$original"
resolve_container() {
local id name
local -a matches=()
while read -r id name; do
[[ -n "$id" && "$name" =~ $CHART_CONTAINER_PATTERN ]] && matches+=("$id")
done < <(docker ps --format '{{.ID}} {{.Names}}')
if [[ ${#matches[@]} -ne 1 ]]; then
echo "container selector matched ${#matches[@]} running containers" >&2
exit 3
fi
printf '%s' "${matches[0]}"
}
redact() {
sed -E \
-e 's/(Authorization:[[:space:]]*Bearer[[:space:]]+)[A-Za-z0-9._-]+/\1[REDACTED]/Ig' \
-e 's/((token|secret|password|api[_-]?key)["=:[:space:]]+)[^,[:space:]"}]+/\1[REDACTED]/Ig'
}
valid_capture_id() {
[[ "$1" =~ ^c-[A-Za-z0-9_-]{12}$ ]]
}
case "${args[0]}" in
logs)
[[ ${#args[@]} -eq 3 && "${args[1]}" == "--since" ]] || {
echo "usage: logs --since 20m" >&2; exit 2;
}
[[ "${args[2]}" =~ ^[1-9][0-9]*(s|m|h|d)$ ]] || {
echo "invalid duration" >&2; exit 2;
}
container=$(resolve_container)
docker logs --timestamps --tail "$MAX_LOG_LINES" --since "${args[2]}" "$container" 2>&1 | redact
;;
status)
[[ ${#args[@]} -eq 1 ]] || { echo "usage: status" >&2; exit 2; }
container=$(resolve_container)
token=""
while IFS= read -r entry; do
[[ "$entry" == CHART_AUTH_TOKEN=* ]] && token=${entry#CHART_AUTH_TOKEN=}
done < <(docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$container")
if [[ -n "$token" ]]; then
[[ "$token" != *$'\n'* && "$token" != *$'\r'* ]] || {
echo "invalid CHART_AUTH_TOKEN" >&2; exit 4;
}
escaped_token=${token//\\/\\\\}
escaped_token=${escaped_token//\"/\\\"}
printf 'url = "%s/api/status"\nheader = "X-Chart-Token: %s"\nfail\nsilent\nshow-error\n' \
"$CHART_PUBLIC_URL" "$escaped_token" | curl --config - | redact
else
echo "CHART_AUTH_TOKEN is unavailable" >&2
exit 4
fi
;;
container-state)
[[ ${#args[@]} -eq 1 ]] || { echo "usage: container-state" >&2; exit 2; }
container=$(resolve_container)
docker inspect --format \
'name={{.Name}} image={{.Config.Image}} status={{.State.Status}} started={{.State.StartedAt}} restarts={{.RestartCount}} oom={{.State.OOMKilled}} exit={{.State.ExitCode}}' \
"$container"
;;
recent-deploy)
[[ ${#args[@]} -eq 1 ]] || { echo "usage: recent-deploy" >&2; exit 2; }
curl -fsS "$CHART_PUBLIC_URL/api/version"
printf '\n'
container=$(resolve_container)
docker inspect --format 'container_started={{.State.StartedAt}} image={{.Config.Image}}' "$container"
;;
capture-read)
[[ ${#args[@]} -eq 2 ]] && valid_capture_id "${args[1]}" || {
echo "usage: capture-read c-XXXXXXXXXXXX" >&2; exit 2;
}
container=$(resolve_container)
docker exec "$container" base64 "/tmp/chart-captures/${args[1]}.png"
;;
capture-delete)
[[ ${#args[@]} -eq 2 ]] && valid_capture_id "${args[1]}" || {
echo "usage: capture-delete c-XXXXXXXXXXXX" >&2; exit 2;
}
curl -fsS -X DELETE "$CHART_PUBLIC_URL/api/debug/captures/${args[1]}" >/dev/null
echo "deleted ${args[1]}"
;;
*)
echo "command not allowed" >&2
exit 2
;;
esac