mirror of
https://github.com/boxpositron/absolute-dotfiles.git
synced 2026-10-24 08:23:19 +00:00
feat(docker-nuke): add self-excluding Docker process discovery
This commit is contained in:
+47
-1
@@ -33,7 +33,6 @@ DUMP_RETENTION=10 # how many dump directories to keep
|
|||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
LOG_TAIL_LINES=2000 # lines of Docker log to copy (keeps dumps small)
|
LOG_TAIL_LINES=2000 # lines of Docker log to copy (keeps dumps small)
|
||||||
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
DOCKER_APP="/Applications/Docker.app"
|
DOCKER_APP="/Applications/Docker.app"
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
DOCKER_LOG_DIR="$HOME/Library/Containers/com.docker.docker/Data/log"
|
DOCKER_LOG_DIR="$HOME/Library/Containers/com.docker.docker/Data/log"
|
||||||
@@ -87,6 +86,53 @@ run_timeout() {
|
|||||||
return "$rc"
|
return "$rc"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Exact process names to target, in addition to anything under Docker.app.
|
||||||
|
# NOTE: never use `pgrep -f docker` here — it matches this script's own
|
||||||
|
# command line and the tool would kill itself.
|
||||||
|
DOCKER_PROC_NAMES="com.docker.backend com.docker.build com.docker.krun \
|
||||||
|
com.docker.vpnkit com.docker.virtualization vpnkit docker"
|
||||||
|
|
||||||
|
docker_pids() {
|
||||||
|
local self="$$"
|
||||||
|
local parent="${PPID:-0}"
|
||||||
|
local name pid comm
|
||||||
|
{
|
||||||
|
# `pgrep -f "$DOCKER_APP"` matches any process whose *command line*
|
||||||
|
# mentions the bundle path — e.g. `tail -f /Applications/Docker.app/...`
|
||||||
|
# matches even though that process is plainly not Docker. Post-filter
|
||||||
|
# each candidate by its actual executable so only processes truly
|
||||||
|
# launched from the bundle survive. Wrapped in `|| true`: under
|
||||||
|
# `set -o pipefail`, an empty `pgrep -f` match (exit 1) would otherwise
|
||||||
|
# make this whole statement fail and abort the function under `set -e`.
|
||||||
|
pgrep -f "$DOCKER_APP" 2>/dev/null | while read -r pid; do
|
||||||
|
if [ -z "$pid" ]; then continue; fi
|
||||||
|
# A dead PID (process exited between pgrep and ps) or a `ps` failure
|
||||||
|
# must yield an empty comm, which matches no case branch below —
|
||||||
|
# never a crash, never a false positive.
|
||||||
|
# NOTE: `ps -o comm=` reports the executable path as invoked, not
|
||||||
|
# canonicalized — a Docker helper ever exec'd by relative path or bare
|
||||||
|
# name would report a non-bundle comm here and be silently dropped.
|
||||||
|
comm="$(ps -o comm= -p "$pid" 2>/dev/null)" || true
|
||||||
|
case "$comm" in
|
||||||
|
"$DOCKER_APP"/*) printf '%s\n' "$pid" ;;
|
||||||
|
esac
|
||||||
|
done || true
|
||||||
|
|
||||||
|
# Exact-name matches are already precise — do NOT apply the comm
|
||||||
|
# filter here. One job of this branch is catching a hung
|
||||||
|
# /usr/local/bin/docker CLI, which legitimately lives outside the
|
||||||
|
# bundle; filtering by $DOCKER_APP prefix would break that.
|
||||||
|
for name in $DOCKER_PROC_NAMES; do
|
||||||
|
pgrep -x "$name" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
} | sort -un | while read -r pid; do
|
||||||
|
if [ -z "$pid" ]; then continue; fi
|
||||||
|
if [ "$pid" = "$self" ]; then continue; fi
|
||||||
|
if [ "$pid" = "$parent" ]; then continue; fi
|
||||||
|
printf '%s\n' "$pid"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
log() { printf '%s\n' "$*"; }
|
log() { printf '%s\n' "$*"; }
|
||||||
warn() { printf '%s\n' "$*" >&2; }
|
warn() { printf '%s\n' "$*" >&2; }
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ test_run_timeout() {
|
|||||||
# The override is scoped to a subshell so later tests still see the real one.
|
# The override is scoped to a subshell so later tests still see the real one.
|
||||||
rc=0
|
rc=0
|
||||||
(
|
(
|
||||||
# shellcheck disable=SC2329 # invoked indirectly by run_timeout's name lookup
|
# shellcheck disable=SC2329 # invoked indirectly by run_timeout name lookup
|
||||||
bin_exists() { return 1; }
|
bin_exists() { return 1; }
|
||||||
run_timeout 1 sleep 5
|
run_timeout 1 sleep 5
|
||||||
) || rc=$?
|
) || rc=$?
|
||||||
@@ -123,7 +123,7 @@ test_run_timeout() {
|
|||||||
|
|
||||||
rc=0
|
rc=0
|
||||||
out="$(
|
out="$(
|
||||||
# shellcheck disable=SC2329 # invoked indirectly by run_timeout's name lookup
|
# shellcheck disable=SC2329 # invoked indirectly by run_timeout name lookup
|
||||||
bin_exists() { return 1; }
|
bin_exists() { return 1; }
|
||||||
run_timeout 5 printf 'fallback-ok'
|
run_timeout 5 printf 'fallback-ok'
|
||||||
)" || rc=$?
|
)" || rc=$?
|
||||||
@@ -131,6 +131,161 @@ test_run_timeout() {
|
|||||||
}
|
}
|
||||||
test_run_timeout
|
test_run_timeout
|
||||||
|
|
||||||
|
printf '\n=== docker_pids ===\n'
|
||||||
|
|
||||||
|
test_docker_pids() {
|
||||||
|
# Keep both forms: newline for counting, space-padded for substring checks.
|
||||||
|
# A space-padded haystack is required — searching newline-separated output
|
||||||
|
# for " <pid> " can never match, which would make these assertions vacuous.
|
||||||
|
local pids_nl pids_sp
|
||||||
|
pids_nl="$(docker_pids)"
|
||||||
|
pids_sp=" $(printf '%s' "$pids_nl" | tr '\n' ' ') "
|
||||||
|
|
||||||
|
# $$ and $PPID are never real candidates in live output (the harness's own
|
||||||
|
# command line does not contain $DOCKER_APP, and its comm is "bash", which
|
||||||
|
# matches no exact name), so asserting they are absent from live output
|
||||||
|
# proves nothing — it would pass even if the self/parent exclusion were
|
||||||
|
# deleted entirely. Kept below only as an additional sanity check; the
|
||||||
|
# real safety test stubs discovery so $$ and $PPID genuinely ARE
|
||||||
|
# candidates, which exercises the exclusion filter itself.
|
||||||
|
assert_not_contains "sanity: live output has no own PID" "$pids_sp" " $$ "
|
||||||
|
assert_not_contains "sanity: live output has no parent PID" "$pids_sp" " $PPID "
|
||||||
|
|
||||||
|
local filtered
|
||||||
|
filtered="$(
|
||||||
|
# shellcheck disable=SC2329 # invoked indirectly by docker_pids name lookup
|
||||||
|
pgrep() { printf '%s\n%s\n%s\n' "$$" "$PPID" 99999; }
|
||||||
|
# shellcheck disable=SC2329 # invoked indirectly by docker_pids name lookup
|
||||||
|
ps() { printf '%s\n' "$DOCKER_APP/Contents/MacOS/com.docker.backend"; }
|
||||||
|
docker_pids | tr '\n' ' '
|
||||||
|
)"
|
||||||
|
assert_eq "excludes own and parent PID, keeps others" "99999 " "$filtered"
|
||||||
|
|
||||||
|
# Guard against a vacuous suite: if Docker is running there must be matches.
|
||||||
|
local count
|
||||||
|
count="$(printf '%s\n' "$pids_nl" | grep -c . || true)"
|
||||||
|
if pgrep -x com.docker.backend >/dev/null 2>&1; then
|
||||||
|
if [ "$count" -gt 0 ]; then
|
||||||
|
pass "finds Docker processes while Docker is running"
|
||||||
|
else
|
||||||
|
fail "finds Docker processes while Docker is running" "got 0 pids"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Every returned PID must be a live process.
|
||||||
|
local pid bad=""
|
||||||
|
for pid in $pids_nl; do
|
||||||
|
if ! kill -0 "$pid" 2>/dev/null; then
|
||||||
|
bad="$bad $pid"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
assert_eq "all returned PIDs are live" "" "$bad"
|
||||||
|
|
||||||
|
# Output must be numeric and deduped.
|
||||||
|
local nonnum=""
|
||||||
|
for pid in $pids_nl; do
|
||||||
|
case "$pid" in
|
||||||
|
''|*[!0-9]*) nonnum="$nonnum [$pid]" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
assert_eq "all output is numeric" "" "$nonnum"
|
||||||
|
|
||||||
|
local uniq
|
||||||
|
uniq="$(printf '%s\n' "$pids_nl" | sort -u | grep -c . || true)"
|
||||||
|
assert_eq "output is deduped" "$count" "$uniq"
|
||||||
|
|
||||||
|
# A decoy process whose name contains "docker" but is not Docker Desktop
|
||||||
|
# must not be matched.
|
||||||
|
local decoy="$TMPROOT/docker-nuke-decoy"
|
||||||
|
cat >"$decoy" <<'STUB'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
sleep 30
|
||||||
|
STUB
|
||||||
|
chmod +x "$decoy"
|
||||||
|
# The decoy's command line must mention $DOCKER_APP so pgrep -f "$DOCKER_APP"
|
||||||
|
# actually matches it (the script ignores its args and just sleeps); its
|
||||||
|
# comm stays "bash", which the comm filter must then reject. Without this,
|
||||||
|
# pgrep -f never matches the decoy at all and the comm filter is untested.
|
||||||
|
"$decoy" "$DOCKER_APP/Contents/MacOS/not-really-docker" &
|
||||||
|
local decoy_pid=$!
|
||||||
|
sleep 1
|
||||||
|
pids_sp=" $(docker_pids | tr '\n' ' ') "
|
||||||
|
assert_not_contains "ignores decoy named docker-nuke-*" "$pids_sp" " $decoy_pid "
|
||||||
|
|
||||||
|
# Guard against a vacuous pass: with Docker stopped, docker_pids returns
|
||||||
|
# empty output and the assertion above passes for the wrong reason.
|
||||||
|
# Reuses the anti-vacuous guard pattern used above.
|
||||||
|
if pgrep -x com.docker.backend >/dev/null 2>&1; then
|
||||||
|
if [ -n "$(printf '%s' "$pids_sp" | tr -d '[:space:]')" ]; then
|
||||||
|
pass "decoy check ran against non-empty output while Docker is running"
|
||||||
|
else
|
||||||
|
fail "decoy check ran against non-empty output while Docker is running" "got empty pids"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
kill -9 "$decoy_pid" 2>/dev/null || true
|
||||||
|
wait "$decoy_pid" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
test_docker_pids
|
||||||
|
|
||||||
|
printf '\n=== docker_pids errexit contract ===\n'
|
||||||
|
|
||||||
|
test_docker_pids_errexit() {
|
||||||
|
# Every call site in docker-nuke invokes docker_pids inside $( ), where bash
|
||||||
|
# never applies errexit to the failing command that triggered it — so a
|
||||||
|
# regression that lets docker_pids abort under `set -e` would go completely
|
||||||
|
# undetected by every other test in this file. Prove the contract with a
|
||||||
|
# real script, invoked directly (not inside $( )), that calls docker_pids
|
||||||
|
# as a plain statement under `set -euo pipefail`.
|
||||||
|
#
|
||||||
|
# The specific failure mode is `pgrep -f "$DOCKER_APP"` matching zero
|
||||||
|
# processes (pipefail + no trailing `|| true` -> the pipeline itself exits
|
||||||
|
# non-zero -> set -e aborts before the exact-name branch ever runs). On a
|
||||||
|
# machine where Docker Desktop happens to be live, the real $DOCKER_APP
|
||||||
|
# always has matches, so calling docker_pids unmodified would never
|
||||||
|
# exercise that zero-match branch and the mutation would go undetected
|
||||||
|
# regardless of host state. Override DOCKER_APP after sourcing to a path
|
||||||
|
# guaranteed to match nothing, so the zero-match code path is exercised
|
||||||
|
# deterministically no matter whether Docker is running on this host.
|
||||||
|
local probe="$TMPROOT/errexit-probe.sh"
|
||||||
|
cat >"$probe" <<PROBE
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
source "$SCRIPT_DIR/docker-nuke"
|
||||||
|
DOCKER_APP="/nonexistent/docker-nuke-test-probe-guard"
|
||||||
|
docker_pids >/dev/null
|
||||||
|
printf 'survived\n'
|
||||||
|
PROBE
|
||||||
|
chmod +x "$probe"
|
||||||
|
local probe_out probe_rc=0
|
||||||
|
probe_out="$("$probe" 2>&1)" || probe_rc=$?
|
||||||
|
assert_eq "docker_pids does not abort a set -euo pipefail caller" "0" "$probe_rc"
|
||||||
|
assert_contains "set -e probe ran to completion" "$probe_out" "survived"
|
||||||
|
}
|
||||||
|
test_docker_pids_errexit
|
||||||
|
|
||||||
|
printf '\n=== bash 3.2 syntax compatibility ===\n'
|
||||||
|
|
||||||
|
test_bash32_syntax() {
|
||||||
|
# bash 3.2 (stock macOS /bin/bash) has a $( ) scanner that does not skip
|
||||||
|
# comments, so an apostrophe inside a comment inside a command substitution
|
||||||
|
# opens an unterminated quote and the whole file fails to parse. Guard
|
||||||
|
# against that regressing silently by parse-checking both files with the
|
||||||
|
# real system bash on every run.
|
||||||
|
local out rc
|
||||||
|
|
||||||
|
rc=0
|
||||||
|
out="$(/bin/bash -n "$SCRIPT_DIR/docker-nuke" 2>&1)" || rc=$?
|
||||||
|
assert_eq "docker-nuke parses under /bin/bash -n" "0" "$rc"
|
||||||
|
assert_eq "docker-nuke -n produces no output" "" "$out"
|
||||||
|
|
||||||
|
rc=0
|
||||||
|
out="$(/bin/bash -n "$SCRIPT_DIR/test-docker-nuke.sh" 2>&1)" || rc=$?
|
||||||
|
assert_eq "test-docker-nuke.sh parses under /bin/bash -n" "0" "$rc"
|
||||||
|
assert_eq "test-docker-nuke.sh -n produces no output" "" "$out"
|
||||||
|
}
|
||||||
|
test_bash32_syntax
|
||||||
|
|
||||||
printf '\n=== summary ===\n'
|
printf '\n=== summary ===\n'
|
||||||
teardown_tmp
|
teardown_tmp
|
||||||
printf 'ran %d, failed %d\n' "$TESTS_RUN" "$TESTS_FAILED"
|
printf 'ran %d, failed %d\n' "$TESTS_RUN" "$TESTS_FAILED"
|
||||||
|
|||||||
Reference in New Issue
Block a user