a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
547 lines
23 KiB
Bash
Executable File
547 lines
23 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
#####################################################################################
|
|
#### docker-smoke-tests.sh — Verify Docker daemon and containers are healthy ####
|
|
#### Checks daemon, API, lifecycle, networking, volumes, DNS, compose, images. ####
|
|
#### ####
|
|
#### Author: Phil Connor ####
|
|
#### Contact: contact@mylinux.work ####
|
|
#### License: MIT ####
|
|
#### Version: 1.0 ####
|
|
#### ####
|
|
#### Usage: ./docker-smoke-tests.sh ####
|
|
#### COMPOSE_FILE=/path/to/docker-compose.yml ./docker-smoke-tests.sh ####
|
|
#### ####
|
|
#### See --help for all options. ####
|
|
#####################################################################################
|
|
|
|
set -euo pipefail
|
|
|
|
# ── Defaults ──────────────────────────────────────────────────────────
|
|
TEST_IMAGE="${TEST_IMAGE:-alpine:latest}"
|
|
COMPOSE_FILE="${COMPOSE_FILE:-}"
|
|
SKIP_LIFECYCLE="${SKIP_LIFECYCLE:-false}"
|
|
SKIP_NETWORK="${SKIP_NETWORK:-false}"
|
|
SKIP_VOLUME="${SKIP_VOLUME:-false}"
|
|
SKIP_BUILD="${SKIP_BUILD:-false}"
|
|
DNS_TEST_DOMAIN="${DNS_TEST_DOMAIN:-google.com}"
|
|
OUTPUT_FORMAT="${OUTPUT_FORMAT:-text}"
|
|
COLOR="${COLOR:-auto}"
|
|
VERBOSE="${VERBOSE:-false}"
|
|
|
|
# ── State ─────────────────────────────────────────────────────────────
|
|
PASS=0
|
|
FAIL=0
|
|
SKIP=0
|
|
TOTAL=0
|
|
RESULTS=()
|
|
START_TIME=""
|
|
|
|
# ── Test artifact names ──────────────────────────────────────────────
|
|
SMOKE_PREFIX="smoke-test-$$"
|
|
SMOKE_CONTAINER="${SMOKE_PREFIX}-ctr"
|
|
SMOKE_PORT_CONTAINER="${SMOKE_PREFIX}-port"
|
|
SMOKE_DNS_CONTAINER="${SMOKE_PREFIX}-dns"
|
|
SMOKE_VOL_CONTAINER="${SMOKE_PREFIX}-vol"
|
|
SMOKE_NET_CONTAINER="${SMOKE_PREFIX}-net"
|
|
SMOKE_MEM_CONTAINER="${SMOKE_PREFIX}-mem"
|
|
SMOKE_VOLUME="${SMOKE_PREFIX}-vol"
|
|
SMOKE_NETWORK="${SMOKE_PREFIX}-net"
|
|
SMOKE_BUILD_TAG="${SMOKE_PREFIX}-img"
|
|
SMOKE_PORT=$((47000 + (RANDOM % 1000)))
|
|
|
|
# ── Colors ────────────────────────────────────────────────────────────
|
|
setup_colors() {
|
|
if [[ "$COLOR" == "never" ]]; then
|
|
RED="" GREEN="" YELLOW="" BLUE="" BOLD="" RESET=""
|
|
return
|
|
fi
|
|
if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
BLUE='\033[0;34m'
|
|
BOLD='\033[1m'
|
|
RESET='\033[0m'
|
|
else
|
|
RED="" GREEN="" YELLOW="" BLUE="" BOLD="" RESET=""
|
|
fi
|
|
}
|
|
|
|
# ── Logging ───────────────────────────────────────────────────────────
|
|
log() { echo -e "${BLUE}[INFO]${RESET} $*"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
|
|
err() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
|
|
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${BLUE}[DEBUG]${RESET} $*"; fi; }
|
|
|
|
# ── Test Result Recording ─────────────────────────────────────────────
|
|
record_pass() {
|
|
local name="$1" detail="${2:-}"
|
|
((PASS++)) || true; ((TOTAL++)) || true
|
|
RESULTS+=("PASS|${name}|${detail}")
|
|
if [[ "$OUTPUT_FORMAT" == "tap" ]]; then echo "ok ${TOTAL} - ${name}${detail:+ (${detail})}"
|
|
else echo -e " ${GREEN}✓${RESET} ${name}${detail:+ — ${detail}}"; fi
|
|
}
|
|
|
|
record_fail() {
|
|
local name="$1" detail="${2:-}"
|
|
((FAIL++)) || true; ((TOTAL++)) || true
|
|
RESULTS+=("FAIL|${name}|${detail}")
|
|
if [[ "$OUTPUT_FORMAT" == "tap" ]]; then
|
|
echo "not ok ${TOTAL} - ${name}"
|
|
[[ -n "$detail" ]] && echo " # ${detail}"
|
|
else echo -e " ${RED}✗${RESET} ${name}${detail:+ — ${detail}}"; fi
|
|
}
|
|
|
|
record_skip() {
|
|
local name="$1" reason="${2:-}"
|
|
((SKIP++)) || true; ((TOTAL++)) || true
|
|
RESULTS+=("SKIP|${name}|${reason}")
|
|
if [[ "$OUTPUT_FORMAT" == "tap" ]]; then echo "ok ${TOTAL} - ${name} # SKIP ${reason}"
|
|
else echo -e " ${YELLOW}⊘${RESET} ${name}${reason:+ — ${reason}}"; fi
|
|
}
|
|
|
|
# ── Helpers ───────────────────────────────────────────────────────────
|
|
has_cmd() { command -v "$1" >/dev/null 2>&1; }
|
|
|
|
remove_container() {
|
|
local name="$1"
|
|
docker rm -f "$name" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
section() {
|
|
if [[ "$OUTPUT_FORMAT" != "tap" ]]; then echo ""; echo -e "${BOLD}$1${RESET}"; fi
|
|
}
|
|
|
|
# ── Cleanup ───────────────────────────────────────────────────────────
|
|
# shellcheck disable=SC2317
|
|
cleanup() {
|
|
verbose "Cleaning up test artifacts..."
|
|
remove_container "$SMOKE_CONTAINER"
|
|
remove_container "$SMOKE_PORT_CONTAINER"
|
|
remove_container "$SMOKE_DNS_CONTAINER"
|
|
remove_container "$SMOKE_VOL_CONTAINER"
|
|
remove_container "$SMOKE_NET_CONTAINER"
|
|
remove_container "$SMOKE_MEM_CONTAINER"
|
|
docker volume rm -f "$SMOKE_VOLUME" >/dev/null 2>&1 || true
|
|
docker network rm "$SMOKE_NETWORK" >/dev/null 2>&1 || true
|
|
docker rmi -f "$SMOKE_BUILD_TAG" >/dev/null 2>&1 || true
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# TEST FUNCTIONS
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
# ── 1. Docker daemon running ─────────────────────────────────────────
|
|
test_daemon_running() {
|
|
if has_cmd systemctl; then
|
|
if systemctl is-active --quiet docker 2>/dev/null; then record_pass "Docker daemon running" "systemctl active"
|
|
else record_fail "Docker daemon running" "systemctl inactive"; fi
|
|
elif has_cmd service; then
|
|
if service docker status >/dev/null 2>&1; then record_pass "Docker daemon running" "service running"
|
|
else record_fail "Docker daemon running" "service stopped"; fi
|
|
elif docker info >/dev/null 2>&1; then record_pass "Docker daemon running" "docker info ok"
|
|
else record_fail "Docker daemon running" "cannot determine status"; fi
|
|
}
|
|
|
|
# ── 2. Docker API responsive ─────────────────────────────────────────
|
|
test_api_responsive() {
|
|
local output
|
|
if output=$(timeout 10 docker info 2>&1); then
|
|
local ver; ver=$(echo "$output" | grep -i "Server Version" | head -1 | awk '{print $NF}') || true
|
|
record_pass "Docker API responsive" "server ${ver:-unknown}"
|
|
else record_fail "Docker API responsive" "docker info timed out or failed"; fi
|
|
}
|
|
|
|
# ── 3. Docker socket accessible ──────────────────────────────────────
|
|
test_socket_accessible() {
|
|
local socket="/var/run/docker.sock"
|
|
if [[ -S "$socket" ]]; then
|
|
if [[ -r "$socket" && -w "$socket" ]]; then record_pass "Docker socket accessible" "$socket"
|
|
else record_fail "Docker socket accessible" "$socket not readable/writable"; fi
|
|
elif docker info >/dev/null 2>&1; then record_pass "Docker socket accessible" "non-default socket"
|
|
else record_fail "Docker socket accessible" "$socket not found"; fi
|
|
}
|
|
|
|
# ── 4. Container lifecycle ───────────────────────────────────────────
|
|
test_container_lifecycle() {
|
|
if [[ "$SKIP_LIFECYCLE" == "true" ]]; then record_skip "Container lifecycle" "SKIP_LIFECYCLE=true"; return; fi
|
|
remove_container "$SMOKE_CONTAINER"
|
|
|
|
if ! docker create --name "$SMOKE_CONTAINER" "$TEST_IMAGE" sleep 30 >/dev/null 2>&1; then
|
|
record_fail "Container lifecycle" "docker create failed"
|
|
return
|
|
fi
|
|
|
|
if ! docker start "$SMOKE_CONTAINER" >/dev/null 2>&1; then
|
|
record_fail "Container lifecycle" "docker start failed"
|
|
return
|
|
fi
|
|
|
|
local exec_output
|
|
exec_output=$(docker exec "$SMOKE_CONTAINER" echo "smoke-ok" 2>&1) || true
|
|
if [[ "$exec_output" != "smoke-ok" ]]; then
|
|
record_fail "Container lifecycle" "docker exec failed"
|
|
return
|
|
fi
|
|
|
|
if ! docker stop -t 5 "$SMOKE_CONTAINER" >/dev/null 2>&1; then
|
|
record_fail "Container lifecycle" "docker stop failed"
|
|
return
|
|
fi
|
|
|
|
if ! docker rm "$SMOKE_CONTAINER" >/dev/null 2>&1; then
|
|
record_fail "Container lifecycle" "docker rm failed"
|
|
return
|
|
fi
|
|
|
|
record_pass "Container lifecycle" "create/start/exec/stop/rm"
|
|
}
|
|
|
|
# ── 5. Port binding ──────────────────────────────────────────────────
|
|
test_port_binding() {
|
|
if [[ "$SKIP_LIFECYCLE" == "true" ]]; then record_skip "Port binding" "SKIP_LIFECYCLE=true"; return; fi
|
|
if ! has_cmd curl; then record_skip "Port binding" "curl not installed"; return; fi
|
|
remove_container "$SMOKE_PORT_CONTAINER"
|
|
|
|
if ! docker run -d --name "$SMOKE_PORT_CONTAINER" \
|
|
-p "${SMOKE_PORT}:80" \
|
|
"$TEST_IMAGE" sh -c 'mkdir -p /var/www && echo "smoke-ok" > /var/www/index.html && httpd -f -p 80 -h /var/www 2>/dev/null || { while true; do echo -e "HTTP/1.1 200 OK\r\nContent-Length: 9\r\n\r\nsmoke-ok\n" | nc -l -p 80 2>/dev/null || break; done; }' >/dev/null 2>&1; then
|
|
record_fail "Port binding" "failed to start container with port mapping"
|
|
return
|
|
fi
|
|
|
|
sleep 2
|
|
local response
|
|
response=$(curl -sf --max-time 5 "http://localhost:${SMOKE_PORT}/" 2>/dev/null) || true
|
|
remove_container "$SMOKE_PORT_CONTAINER"
|
|
|
|
if [[ "$response" == *"smoke-ok"* ]]; then
|
|
record_pass "Port binding" "curl localhost:${SMOKE_PORT}"
|
|
else
|
|
record_fail "Port binding" "no response on localhost:${SMOKE_PORT}"
|
|
fi
|
|
}
|
|
|
|
# ── 6. Container DNS ─────────────────────────────────────────────────
|
|
test_container_dns() {
|
|
if [[ "$SKIP_LIFECYCLE" == "true" ]]; then record_skip "Container DNS" "SKIP_LIFECYCLE=true"; return; fi
|
|
remove_container "$SMOKE_DNS_CONTAINER"
|
|
|
|
local dns_output
|
|
dns_output=$(docker run --rm --name "$SMOKE_DNS_CONTAINER" "$TEST_IMAGE" \
|
|
sh -c "nslookup ${DNS_TEST_DOMAIN} 2>/dev/null || getent hosts ${DNS_TEST_DOMAIN} 2>/dev/null || ping -c1 -W3 ${DNS_TEST_DOMAIN} 2>/dev/null" 2>&1) || true
|
|
|
|
if [[ -n "$dns_output" ]] && ! echo "$dns_output" | grep -qi "can't resolve\|not found\|failure\|NXDOMAIN"; then
|
|
record_pass "Container DNS" "${DNS_TEST_DOMAIN}"
|
|
else
|
|
record_fail "Container DNS" "failed to resolve ${DNS_TEST_DOMAIN}"
|
|
fi
|
|
}
|
|
|
|
# ── 7. Volume mount ──────────────────────────────────────────────────
|
|
test_volume_mount() {
|
|
if [[ "$SKIP_VOLUME" == "true" ]]; then record_skip "Volume mount" "SKIP_VOLUME=true"; return; fi
|
|
docker volume rm -f "$SMOKE_VOLUME" >/dev/null 2>&1 || true
|
|
remove_container "$SMOKE_VOL_CONTAINER"
|
|
|
|
if ! docker volume create "$SMOKE_VOLUME" >/dev/null 2>&1; then
|
|
record_fail "Volume mount" "docker volume create failed"
|
|
return
|
|
fi
|
|
|
|
local write_result
|
|
write_result=$(docker run --rm --name "$SMOKE_VOL_CONTAINER" \
|
|
-v "${SMOKE_VOLUME}:/data" "$TEST_IMAGE" \
|
|
sh -c 'echo "smoke-vol-ok" > /data/test.txt && cat /data/test.txt' 2>&1) || true
|
|
|
|
docker volume rm -f "$SMOKE_VOLUME" >/dev/null 2>&1 || true
|
|
|
|
if [[ "$write_result" == "smoke-vol-ok" ]]; then
|
|
record_pass "Volume mount" "write/read verified"
|
|
else
|
|
record_fail "Volume mount" "write/read mismatch"
|
|
fi
|
|
}
|
|
|
|
# ── 8. Network create/connect ────────────────────────────────────────
|
|
test_network_create() {
|
|
if [[ "$SKIP_NETWORK" == "true" ]]; then record_skip "Network create/connect" "SKIP_NETWORK=true"; return; fi
|
|
docker network rm "$SMOKE_NETWORK" >/dev/null 2>&1 || true
|
|
remove_container "$SMOKE_NET_CONTAINER"
|
|
|
|
if ! docker network create --driver bridge "$SMOKE_NETWORK" >/dev/null 2>&1; then
|
|
record_fail "Network create/connect" "docker network create failed"
|
|
return
|
|
fi
|
|
|
|
local net_output
|
|
net_output=$(docker run --rm --name "$SMOKE_NET_CONTAINER" \
|
|
--network "$SMOKE_NETWORK" "$TEST_IMAGE" \
|
|
sh -c 'ip addr show 2>/dev/null || ifconfig 2>/dev/null' 2>&1) || true
|
|
|
|
docker network rm "$SMOKE_NETWORK" >/dev/null 2>&1 || true
|
|
|
|
if [[ -n "$net_output" ]]; then
|
|
record_pass "Network create/connect" "bridge network"
|
|
else
|
|
record_fail "Network create/connect" "container failed to attach to network"
|
|
fi
|
|
}
|
|
|
|
# ── 9. Image pull ────────────────────────────────────────────────────
|
|
test_image_pull() {
|
|
if docker pull "$TEST_IMAGE" >/dev/null 2>&1; then
|
|
record_pass "Image pull" "$TEST_IMAGE"
|
|
else
|
|
record_fail "Image pull" "failed to pull $TEST_IMAGE"
|
|
fi
|
|
}
|
|
|
|
# ── 10. Image build ──────────────────────────────────────────────────
|
|
test_image_build() {
|
|
if [[ "$SKIP_BUILD" == "true" ]]; then record_skip "Image build" "SKIP_BUILD=true"; return; fi
|
|
docker rmi -f "$SMOKE_BUILD_TAG" >/dev/null 2>&1 || true
|
|
|
|
if echo "FROM alpine:latest" | docker build -t "$SMOKE_BUILD_TAG" - >/dev/null 2>&1; then
|
|
docker rmi -f "$SMOKE_BUILD_TAG" >/dev/null 2>&1 || true
|
|
record_pass "Image build" "inline Dockerfile"
|
|
else
|
|
record_fail "Image build" "docker build failed"
|
|
fi
|
|
}
|
|
|
|
# ── 11. Docker Compose ───────────────────────────────────────────────
|
|
test_compose_stack() {
|
|
if [[ -z "$COMPOSE_FILE" ]]; then
|
|
record_skip "Compose stack" "COMPOSE_FILE not set"
|
|
return
|
|
fi
|
|
if [[ ! -f "$COMPOSE_FILE" ]]; then
|
|
record_fail "Compose stack" "${COMPOSE_FILE} not found"
|
|
return
|
|
fi
|
|
local compose_cmd=""
|
|
if docker compose version >/dev/null 2>&1; then
|
|
compose_cmd="docker compose"
|
|
elif has_cmd docker-compose; then
|
|
compose_cmd="docker-compose"
|
|
else
|
|
record_skip "Compose stack" "neither 'docker compose' nor 'docker-compose' available"
|
|
return
|
|
fi
|
|
|
|
local ps_output expected_count running_count
|
|
ps_output=$($compose_cmd -f "$COMPOSE_FILE" ps --format json 2>/dev/null) || true
|
|
|
|
if [[ -z "$ps_output" ]]; then
|
|
ps_output=$($compose_cmd -f "$COMPOSE_FILE" ps 2>/dev/null) || true
|
|
if [[ -z "$ps_output" ]]; then
|
|
record_fail "Compose stack" "could not read compose project status"
|
|
return
|
|
fi
|
|
expected_count=$(echo "$ps_output" | tail -n +2 | wc -l)
|
|
running_count=$(echo "$ps_output" | tail -n +2 | grep -ciE "up|running" || true)
|
|
else
|
|
expected_count=$(echo "$ps_output" | grep -c '"Service"' 2>/dev/null || echo "$ps_output" | wc -l)
|
|
running_count=$(echo "$ps_output" | grep -ciE '"running"' 2>/dev/null || true)
|
|
fi
|
|
|
|
if [[ "$expected_count" -eq 0 ]]; then
|
|
record_fail "Compose stack" "no services found"
|
|
elif [[ "$running_count" -ge "$expected_count" ]]; then
|
|
record_pass "Compose stack" "${running_count}/${expected_count} services running"
|
|
else
|
|
record_fail "Compose stack" "${running_count}/${expected_count} services running"
|
|
fi
|
|
}
|
|
|
|
# ── 12. Resource limits ──────────────────────────────────────────────
|
|
test_resource_limits() {
|
|
if [[ "$SKIP_LIFECYCLE" == "true" ]]; then record_skip "Resource limits" "SKIP_LIFECYCLE=true"; return; fi
|
|
remove_container "$SMOKE_MEM_CONTAINER"
|
|
|
|
local mem_limit
|
|
mem_limit=$(docker run --rm --name "$SMOKE_MEM_CONTAINER" \
|
|
--memory=64m "$TEST_IMAGE" \
|
|
sh -c 'cat /sys/fs/cgroup/memory.max 2>/dev/null || cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2>/dev/null' 2>&1) || true
|
|
|
|
if [[ -z "$mem_limit" ]]; then
|
|
record_skip "Resource limits" "cgroup memory info not available"
|
|
return
|
|
fi
|
|
|
|
local limit_bytes=67108864 # 64 MiB
|
|
if [[ "$mem_limit" =~ ^[0-9]+$ ]]; then
|
|
if [[ "$mem_limit" -le $((limit_bytes + 1048576)) ]]; then
|
|
local limit_mb=$((mem_limit / 1048576))
|
|
record_pass "Resource limits" "memory cgroup enforced (${limit_mb}M)"
|
|
else
|
|
record_fail "Resource limits" "memory limit not enforced (got ${mem_limit})"
|
|
fi
|
|
else
|
|
record_skip "Resource limits" "unexpected cgroup value: ${mem_limit}"
|
|
fi
|
|
}
|
|
|
|
# ── 13. Disk space ───────────────────────────────────────────────────
|
|
test_disk_space() {
|
|
local df_output
|
|
df_output=$(docker system df 2>/dev/null) || true
|
|
|
|
if [[ -z "$df_output" ]]; then
|
|
record_fail "Disk space" "docker system df failed"
|
|
return
|
|
fi
|
|
|
|
local docker_root used_pct
|
|
docker_root=$(docker info --format '{{.DockerRootDir}}' 2>/dev/null) || docker_root="/var/lib/docker"
|
|
used_pct=$(df "$docker_root" 2>/dev/null | tail -1 | awk '{print $5}' | tr -d '%') || used_pct=0
|
|
|
|
if [[ "$used_pct" -gt 80 ]]; then
|
|
record_fail "Disk space" "${used_pct}% used (threshold 80%)"
|
|
else
|
|
record_pass "Disk space" "${used_pct}% used"
|
|
fi
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# OUTPUT
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
print_tap_header() {
|
|
echo "TAP version 13"
|
|
}
|
|
|
|
print_tap_footer() {
|
|
echo "1..${TOTAL}"
|
|
echo "# pass ${PASS}"
|
|
echo "# fail ${FAIL}"
|
|
echo "# skip ${SKIP}"
|
|
}
|
|
|
|
print_summary() {
|
|
local end_time; end_time=$(date +%s)
|
|
local duration=$(( end_time - START_TIME ))
|
|
echo ""
|
|
echo -e "${BOLD}────────────────────────────────────────${RESET}"
|
|
echo -e "${BOLD}Summary${RESET} Docker Smoke Tests"
|
|
echo -e " ${GREEN}${PASS} passed${RESET} ${RED}${FAIL} failed${RESET} ${YELLOW}${SKIP} skipped${RESET} (${duration}s)"
|
|
echo -e "${BOLD}────────────────────────────────────────${RESET}"
|
|
if [[ $FAIL -eq 0 ]]; then echo -e "${GREEN}${BOLD}All tests passed.${RESET}"
|
|
else echo -e "${RED}${BOLD}${FAIL} test(s) failed.${RESET}"; fi
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# MAIN
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $(basename "$0") [OPTIONS]
|
|
|
|
Smoke-test Docker daemon, containers, networking, volumes, images, and compose.
|
|
|
|
Environment variables (all optional):
|
|
TEST_IMAGE Image for tests (default: alpine:latest)
|
|
COMPOSE_FILE docker-compose.yml path (default: none)
|
|
SKIP_LIFECYCLE Skip lifecycle tests (default: false)
|
|
SKIP_NETWORK Skip network tests (default: false)
|
|
SKIP_VOLUME Skip volume tests (default: false)
|
|
SKIP_BUILD Skip build test (default: false)
|
|
DNS_TEST_DOMAIN DNS domain to resolve (default: google.com)
|
|
OUTPUT_FORMAT text or tap (default: text)
|
|
COLOR auto, always, never (default: auto)
|
|
VERBOSE Show debug output (default: false)
|
|
|
|
Options:
|
|
--skip-lifecycle Skip container lifecycle tests
|
|
--skip-network Skip network tests
|
|
--skip-volume Skip volume tests
|
|
--skip-build Skip image build test
|
|
--format FORMAT Output format: text (default), tap
|
|
--verbose Show debug output
|
|
--no-color Disable colored output
|
|
--help Show this help
|
|
|
|
Examples:
|
|
./$(basename "$0")
|
|
COMPOSE_FILE=/opt/stacks/web/docker-compose.yml ./$(basename "$0")
|
|
OUTPUT_FORMAT=tap ./$(basename "$0") --skip-build
|
|
EOF
|
|
}
|
|
|
|
main() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--skip-lifecycle) SKIP_LIFECYCLE=true ;;
|
|
--skip-network) SKIP_NETWORK=true ;;
|
|
--skip-volume) SKIP_VOLUME=true ;;
|
|
--skip-build) SKIP_BUILD=true ;;
|
|
--format) OUTPUT_FORMAT="$2"; shift ;;
|
|
--verbose) VERBOSE=true ;;
|
|
--no-color) COLOR=never ;;
|
|
--help|-h) usage; exit 0 ;;
|
|
*) err "Unknown option: $1"; usage; exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
setup_colors
|
|
|
|
if ! has_cmd docker; then
|
|
err "docker CLI not found in PATH"
|
|
exit 1
|
|
fi
|
|
|
|
START_TIME=$(date +%s)
|
|
|
|
if [[ "$OUTPUT_FORMAT" == "tap" ]]; then
|
|
print_tap_header
|
|
else
|
|
echo ""
|
|
echo -e "${BOLD}Docker Smoke Tests${RESET}"
|
|
echo -e "Image: ${TEST_IMAGE} Port: ${SMOKE_PORT}"
|
|
echo -e "Time: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
fi
|
|
|
|
section "Daemon & Infrastructure"
|
|
test_daemon_running
|
|
test_api_responsive
|
|
test_socket_accessible
|
|
|
|
section "Image Operations"
|
|
test_image_pull
|
|
test_image_build
|
|
|
|
section "Container Operations"
|
|
test_container_lifecycle
|
|
test_port_binding
|
|
test_container_dns
|
|
|
|
section "Storage"
|
|
test_volume_mount
|
|
test_disk_space
|
|
|
|
section "Networking"
|
|
test_network_create
|
|
|
|
section "Resource Limits"
|
|
test_resource_limits
|
|
|
|
section "Compose"
|
|
test_compose_stack
|
|
|
|
# ── Results ──
|
|
if [[ "$OUTPUT_FORMAT" == "tap" ]]; then
|
|
print_tap_footer
|
|
else
|
|
print_summary
|
|
fi
|
|
|
|
[[ $FAIL -eq 0 ]] && exit 0 || exit 1
|
|
}
|
|
|
|
main "$@"
|