#!/bin/sh set -eu # 通过 Docker Engine unix 套接字在容器中执行命令。 # 示例: ./docker-exec-via-sock.sh --container=my-container --cmd="ls -l /" log() { printf '%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ') $*" >&2 } log_stream() { log_stream_prefix=$1 log_stream_payload=$2 if [ -n "$log_stream_payload" ]; then printf '%s\n' "$log_stream_payload" | while IFS= read -r log_stream_line; do log "$log_stream_prefix: $log_stream_line" done fi } print_usage() { cat <&2 Usage: $0 [--socket=PATH] [--api-version=VERSION] --container=NAME --cmd=COMMAND [--user=NAME] [--shell=SHELL] Options: --socket=PATH Docker Engine unix socket path (default: /var/run/docker.sock) --api-version=VERSION Docker API version (default: v1.51) --container=NAME Container name to execute command in --cmd=COMMAND Command to execute in the container --user=NAME Run command as the specified user (default: container default user) --shell=SHELL Shell to use (default: sh) --help Show this help message EOF } DOCKER_SOCKET="/var/run/docker.sock" DOCKER_API_VERSION="v1.51" CONTAINER_NAME="" CMD_TO_EXEC="" EXEC_USER="" DOCKER_LAST_RESPONSE="" EXEC_SHELL="sh" require_command() { if ! command -v "$1" >/dev/null 2>&1; then printf '%s requires %s in PATH\n' "$0" "$1" >&2 exit 1 fi } missing_value() { printf 'Missing value for %s\n' "$1" >&2 print_usage exit 1 } docker_exec_create() { docker_exec_create_desc=$1 docker_exec_create_cmd=$2 docker_exec_create_payload=$(jq -n --arg cmd "$docker_exec_create_cmd" --arg user "$EXEC_USER" --arg shell "$EXEC_SHELL" '{ AttachStdin: false, AttachStdout: true, AttachStderr: true, Tty: true, Cmd: [$shell, "-c", $cmd] } | if ($user | length) > 0 then .User = $user else . end') DOCKER_LAST_RESPONSE=$(curl --silent --show-error --unix-socket "$DOCKER_SOCKET" \ -X POST \ -H "Content-Type: application/json" \ -d "$docker_exec_create_payload" \ "$exec_create_endpoint") docker_exec_create_id=$(printf '%s' "$DOCKER_LAST_RESPONSE" | jq -r '.Id // empty') if [ -z "$docker_exec_create_id" ]; then error_message=$(printf '%s' "$DOCKER_LAST_RESPONSE" | jq -r '.message // "unknown error"') log "failed to create $docker_exec_create_desc exec for $CONTAINER_NAME: $error_message" return 1 fi printf '%s' "$docker_exec_create_id" } docker_exec_start() { docker_exec_start_id=$1 docker_exec_start_endpoint="${docker_api_base}/exec/${docker_exec_start_id}/start" DOCKER_LAST_RESPONSE=$(curl --silent --show-error --unix-socket "$DOCKER_SOCKET" \ -X POST \ -H "Content-Type: application/json" \ -d '{"Detach": false, "Tty": true}' \ "$docker_exec_start_endpoint") printf '%s' "$DOCKER_LAST_RESPONSE" } docker_exec_exit_code() { docker_exec_exit_id=$1 docker_exec_inspect_endpoint="${docker_api_base}/exec/${docker_exec_exit_id}/json" DOCKER_LAST_RESPONSE=$(curl --silent --show-error --unix-socket "$DOCKER_SOCKET" "$docker_exec_inspect_endpoint") docker_exec_exit_code_value=$(printf '%s' "$DOCKER_LAST_RESPONSE" | jq -r '.ExitCode') if [ "$docker_exec_exit_code_value" = "null" ]; then log "failed to get exit code. response: $DOCKER_LAST_RESPONSE" return 1 fi printf '%s' "$docker_exec_exit_code_value" } while [ "$#" -gt 0 ]; do case $1 in --socket=*) DOCKER_SOCKET="${1#*=}" ;; --socket) if [ "$#" -lt 2 ]; then missing_value '--socket'; fi shift DOCKER_SOCKET="$1" ;; --api-version=*) DOCKER_API_VERSION="${1#*=}" ;; --api-version) if [ "$#" -lt 2 ]; then missing_value '--api-version'; fi shift DOCKER_API_VERSION="$1" ;; --container=*) CONTAINER_NAME="${1#*=}" ;; --container) if [ "$#" -lt 2 ]; then missing_value '--container'; fi shift CONTAINER_NAME="$1" ;; --cmd=*) CMD_TO_EXEC="${1#*=}" ;; --cmd) if [ "$#" -lt 2 ]; then missing_value '--cmd'; fi shift CMD_TO_EXEC="$1" ;; --user=*) EXEC_USER="${1#*=}" ;; --user) if [ "$#" -lt 2 ]; then missing_value '--user'; fi shift EXEC_USER="$1" ;; --shell=*) EXEC_SHELL="${1#*=}" ;; --shell) if [ "$#" -lt 2 ]; then missing_value '--shell'; fi shift EXEC_SHELL="$1" ;; --help) print_usage exit 0 ;; --) shift break ;; *) printf 'Unknown option: %s\n' "$1" >&2 print_usage exit 1 ;; esac shift done if [ "$#" -gt 0 ]; then printf 'Unexpected positional arguments: %s\n' "$*" >&2 print_usage exit 1 fi require_command curl require_command jq if [ -z "$DOCKER_SOCKET" ] || [ -z "$DOCKER_API_VERSION" ] || [ -z "$CONTAINER_NAME" ] || [ -z "$CMD_TO_EXEC" ]; then print_usage exit 1 fi if [ -z "$EXEC_SHELL" ]; then EXEC_SHELL="sh" fi if [ ! -S "$DOCKER_SOCKET" ]; then log "docker socket $DOCKER_SOCKET not found" exit 1 fi docker_api_base="http://localhost/${DOCKER_API_VERSION}" exec_create_endpoint="${docker_api_base}/containers/${CONTAINER_NAME}/exec" if [ -n "$EXEC_USER" ]; then log "executing command in container '$CONTAINER_NAME' as '$EXEC_USER': $CMD_TO_EXEC" else log "executing command in container '$CONTAINER_NAME': $CMD_TO_EXEC" fi exec_id=$(docker_exec_create "command" "$CMD_TO_EXEC") if [ -z "$exec_id" ]; then exit 1 fi log "starting exec $exec_id" output=$(docker_exec_start "$exec_id") exit_code=$(docker_exec_exit_code "$exec_id") if [ "$exit_code" = "0" ]; then log "command finished successfully" # 将纯净的输出打印到 stdout printf '%s' "$output" exit 0 else log "command failed with exit code $exit_code" log "output from container:" # 将错误输出打印到 stderr echo "$output" >&2 exit "$exit_code" fi