Files
Linux/docker-exec-via-sock.sh

208 lines
5.3 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 <<EOF >&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"