211 lines
5.4 KiB
Bash
211 lines
5.4 KiB
Bash
#!/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"
|
|
if ! DOCKER_LAST_RESPONSE=$(curl --fail --show-error --silent --unix-socket "$DOCKER_SOCKET" \
|
|
-X POST \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"Detach": false, "Tty": true}' \
|
|
"$docker_exec_start_endpoint"); then
|
|
log "command execution failed. output from container: $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"
|
|
|
|
if ! start_output=$(docker_exec_start "$exec_id" "exec"); then
|
|
# The error is already logged inside docker_exec_start
|
|
exit 1
|
|
fi
|
|
|
|
if ! exit_code=$(docker_exec_exit_code "$exec_id"); then
|
|
error_message=$(printf '%s' "$DOCKER_LAST_RESPONSE" | jq -r '.message // "unknown error"')
|
|
log "could not determine exec exit code: $error_message"
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$exit_code" != "0" ]; then
|
|
log "exec exited with status $exit_code"
|
|
log "docker inspect response: $DOCKER_LAST_RESPONSE"
|
|
exit "$exit_code"
|
|
fi
|
|
|
|
log "command finished successfully" |