#!/usr/bin/env bash # Why a disposable Compose project did not become ready: `docker compose ps` and the last log lines # of every service, so a CI failure says what went wrong (a port already allocated, a container that # exited, a health check that never passed) instead of only that something did. # # source scripts/lib/compose-diagnostics.sh # COMPOSE_DIAGNOSTICS_SECRETS=("$password" "$secret_key") # literal values to mask, optional # compose_report_not_ready "$up_output" "${compose[@]}" # # Everything printed passes through `compose_diagnostics_redact` first. The projects these scripts # start hold only disposable, generated test credentials, but output is redacted anyway: every # literal in COMPOSE_DIAGNOSTICS_SECRETS, the value of any `NAME=value` or `NAME: value` whose name # looks like a credential, the user-information part of any URL, and the rest of any Authorization # header. Terminal control sequences are stripped. Output goes to standard error. COMPOSE_DIAGNOSTICS_SECRETS=() COMPOSE_DIAGNOSTICS_TAIL_LINES=40 # compose_diagnostics_redact: standard input to standard output, with credentials masked. compose_diagnostics_redact() { local secrets="" if ((${#COMPOSE_DIAGNOSTICS_SECRETS[@]} > 0)); then secrets="$(printf '%s\n' "${COMPOSE_DIAGNOSTICS_SECRETS[@]}")" fi # Literal values first, by plain substring rather than as patterns. Very short values are skipped: # masking every occurrence of a two-letter word would only make the output unreadable. COMPOSE_DIAGNOSTICS_LITERALS="$secrets" awk ' BEGIN { count = split(ENVIRON["COMPOSE_DIAGNOSTICS_LITERALS"], secret, "\n") } { line = $0 for (i = 1; i <= count; i++) { if (length(secret[i]) < 6) { continue } masked = "" while ((at = index(line, secret[i])) > 0) { masked = masked substr(line, 1, at - 1) "[redacted]" line = substr(line, at + length(secret[i])) } line = masked line } print line } ' | sed -E \ -e 's/\x1b\[[0-9;?]*[A-Za-z]//g' \ -e 's#([Aa][Uu][Tt][Hh][Oo][Rr][Ii][Zz][Aa][Tt][Ii][Oo][Nn][[:space:]]*[:=]).*#\1 [redacted]#' \ -e 's#([A-Za-z0-9_.-]*([Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd]|[Pp][Aa][Ss][Ss][Ww][Dd]|[Ss][Ee][Cc][Rr][Ee][Tt]|[Tt][Oo][Kk][Ee][Nn]|[_.-][Kk][Ee][Yy])[A-Za-z0-9_.-]*"?[[:space:]]*[=:][[:space:]]*)("[^"]*"|[^[:space:],;"]+)#\1[redacted]#g' \ -e 's#([A-Za-z][A-Za-z0-9+.-]*://)[^/@[:space:]]+@#\1[redacted]@#g' } # compose_report_not_ready : the failed command's own output, then # the project's containers and the tail of each service's logs, all redacted. compose_report_not_ready() { local up_output="$1" shift local service services { if [[ -n "$up_output" ]]; then echo "--- docker compose up (last 20 lines)" printf '%s\n' "$up_output" | tail -n 20 | compose_diagnostics_redact fi echo "--- docker compose ps" "$@" ps --all 2>&1 | compose_diagnostics_redact services="$("$@" config --services 2>/dev/null || true)" for service in $services; do echo "--- docker compose logs ${service} (last ${COMPOSE_DIAGNOSTICS_TAIL_LINES} lines)" "$@" logs --no-color --tail "$COMPOSE_DIAGNOSTICS_TAIL_LINES" "$service" 2>&1 | compose_diagnostics_redact done echo "--- end of diagnostics" } >&2 }