#!/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') $*" } 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 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 --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="" DOCKER_LAST_RESPONSE="" 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" '{ AttachStdin: false, AttachStdout: true, AttachStderr: true, Tty: true, Cmd: ["sh", "-c", $cmd] }') DOCKER_LAST_RESPONSE=$(curl --fail --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_desc=$2 docker_exec_start_endpoint="${docker_api_base}/exec/${docker_exec_start_id}/start" DOCKER_LAST_RESPONSE=$(curl --show-error --silent --unix-socket "$DOCKER_SOCKET" \ -X POST \ -H "Content-Type: application/json" \ -d '{"Detach": false, "Tty": true}' \ "$docker_exec_start_endpoint") || true # 检查 exec 实例的退出码 exit_code=$(docker_exec_exit_code "$docker_exec_start_id") # 如果退出码不是 0,说明命令执行失败 if [ "$exit_code" != "0" ]; then log "command execution failed with exit code $exit_code." log "output from container:" # 直接打印从容器收到的原始输出 printf '%s\n' "$DOCKER_LAST_RESPONSE" return 1 fi log_stream "$docker_exec_start_desc output" "$DOCKER_LAST_RESPONSE" 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 --fail --silent --show-error --unix-socket "$DOCKER_SOCKET" "$docker_exec_inspect_endpoint") docker_exec_exit_code_value=$(printf '%s' "$DOCKER_LAST_RESPONSE" | jq -r '.ExitCode // empty') if [ -z "$docker_exec_exit_code_value" ]; then 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" ;; --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 [ ! -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" log "executing command in container $CONTAINER_NAME" if ! exec_id=$(docker_exec_create "command" "$CMD_TO_EXEC"); then exit 1 fi log "starting exec $exec_id" # docker_exec_start 会处理成功和失败的逻辑,并记录日志 if ! docker_exec_start "$exec_id" "exec"; then exit 1 fi log "command finished successfully"