From 03bb6b3663c308d4ae633e490e29d27598b61546 Mon Sep 17 00:00:00 2001 From: Felipe Santos Date: Thu, 17 Oct 2024 14:46:34 -0300 Subject: [PATCH] Add shim for docker-compose --- Dockerfile | 4 + dond-compose | 356 +++++++++++++++++++++++++++++ tests/fixtures/docker-compose.yaml | 6 + 3 files changed, 366 insertions(+) create mode 100755 dond-compose create mode 100644 tests/fixtures/docker-compose.yaml diff --git a/Dockerfile b/Dockerfile index 522113a..923b484 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,10 @@ ARG DOCKER_PATH="/usr/local/bin/docker" RUN mv -f "${DOCKER_PATH}" "${DOCKER_PATH}.orig" COPY --from=dond-shim-bin /dond "${DOCKER_PATH}" +ARG DOCKER_COMPOSE_PATH="/usr/local/bin/docker-compose" +RUN mv -f "${DOCKER_COMPOSE_PATH}" "${DOCKER_COMPOSE_PATH}.orig" +COPY ./dond-compose "${DOCKER_COMPOSE_PATH}" + FROM dond-shim AS test # Create fixtures diff --git a/dond-compose b/dond-compose new file mode 100755 index 0000000..4e69066 --- /dev/null +++ b/dond-compose @@ -0,0 +1,356 @@ +#!/usr/bin/env bash + +if [[ "${DOND_COMPOSE_SHIM_DEBUG:-false}" == true ]]; then + set -o xtrace +fi + +# Exit on any kind of errors +# https://unix.stackexchange.com/questions/23026 +set -o errexit +set -o nounset +set -o pipefail +set -o errtrace +set -o functrace +shopt -s inherit_errexit + +function echo_error() { + echo "ERROR(dond-shim):" "${@}" >&2 +} + +function error() { + echo_error "${@}" + uncaught_error=false + exit 1 +} + +# Ensure the user knows that the error was originated from the shim +function handle_error_trap() { + if [[ "${uncaught_error:-true}" == true ]]; then + echo_error "Uncaught error at line ${LINENO}" + fi +} + +trap handle_error_trap ERR + +# Runs the docker command with the given arguments. +function run_docker_compose() { + if [[ "${print_command}" == true ]]; then + echo "${docker_compose_path}" "${@}" + exit 0 + else + DOND_COMPOSE_SHIM_SKIP=true exec "${docker_compose_path}" "${@}" + fi +} + +# Gets the current/parent container id on the host. +function set_container_id() { + local result + + local mount_info_lines=() + readarray -t mount_info_lines /dev/null; then + error "docker_compose_path (${docker_compose_path}) points to a non-existing file or command" +fi + +# Save original arguments +original_args=("$@") +readonly original_args + +# Avoid infinite loop if this script calls itself at a different path +if [[ "${DOND_COMPOSE_SHIM_SKIP:-false}" == true ]]; then + exec "${docker_compose_path}" "${original_args[@]}" +fi + +# Exit early if the original command does not have at least 3 args, like +# run --volume=/tmp:/tmp ubuntu +if [[ "${#original_args[@]}" -lt 3 ]]; then + run_docker_compose "${original_args[@]}" +fi + +# We need to identify which arguments are global, which are for the container +# run/create command and which are for the image. That's to avoid transforming +# arguments that are not meant to be transformed (we should only transform +# arguments that are meant to be passed to the container run/create command). + +# Example: --host whatever container run +global_args=() +# Example: --volume /tmp:/tmp ubuntu +command_args=() +# Example: bash -c "echo hello" +image_args=() + +skip_next_arg=false +next_arg_type="global" +docker_command=() +global_options_with_value_fetched=false +command_options_with_value_fetched=false +first_global_positional_arg_found=false + +for arg in "${original_args[@]}"; do + if [[ "${next_arg_type}" == "global" ]]; then + global_args+=("${arg}") + + if [[ "${skip_next_arg}" == true ]]; then + skip_next_arg=false + continue + fi + + if [[ "${arg}" == "-"* ]]; then + # Only parse global options before the first positional command, because + # docker does not allow an option between container and run/create like + # this: docker container --host whatever run + if [[ "${first_global_positional_arg_found}" == false ]]; then + # Only fetch docker global options when needed and only once + if [[ "${global_options_with_value_fetched}" == false ]]; then + set_docker_options_with_value + global_options_with_value_fetched=true + fi + + # Skip next argument if it is a global option that accepts a value + for option in "${docker_options_with_value[@]}"; do + if [[ "${arg}" == "${option}" ]]; then + skip_next_arg=true + break + fi + done + fi + elif [[ "${arg}" == "run" || "${arg}" == "create" ]]; then + docker_command+=("${arg}") + next_arg_type="command" + elif [[ "${arg}" == "container" ]]; then + docker_command+=("${arg}") + first_global_positional_arg_found=true + else + # Skip if command is not run, create, container run, or container create + run_docker_compose "${original_args[@]}" + fi + elif [[ "${next_arg_type}" == "command" ]]; then + command_args+=("${arg}") + + if [[ "${skip_next_arg}" == true ]]; then + skip_next_arg=false + continue + fi + + if [[ "${arg}" == "-"* ]]; then + # Only fetch docker command options when needed and only once + if [[ "${command_options_with_value_fetched}" == false ]]; then + set_docker_options_with_value "${docker_command[@]}" + command_options_with_value_fetched=true + fi + + for option in "${docker_options_with_value[@]}"; do + if [[ "${arg}" == "${option}" ]]; then + skip_next_arg=true + continue + fi + done + else + # First non-option argument is the image + next_arg_type="image" + fi + elif [[ "${next_arg_type}" == "image" ]]; then + image_args+=("${arg}") + fi +done +readonly global_args command_args image_args +unset next_arg_type skip_next_arg docker_command global_options_with_value_fetched \ + command_options_with_value_fetched first_global_positional_arg_found \ + docker_options_with_value + +# Finally it's time to transform the volume|mount arguments +container_data_fetched=false +fix_next_arg=false +fixed_args=() +next_extra_args=() + +for arg in "${command_args[@]}"; do + if [[ "${fix_next_arg}" == true ]]; then + fix_next_arg=false + volume_arg="${arg}" + fix_volume_arg + arg="${volume_arg}" + elif [[ "${arg}" == "-v" || "${arg}" == "--volume" || "${arg}" == "--mount" ]]; then + fix_next_arg=true + elif [[ "${arg}" == "-v="* || "${arg}" == "--volume="* ]]; then + option_name="${arg%%"="*}" + volume_arg="${arg#*"="}" + fix_volume_arg + arg="${option_name}=${volume_arg}" + elif [[ "${arg}" == "--mount="* ]]; then + option_name="${arg%%"="*}" + volume_arg="${arg#*"="}" + fix_volume_arg + arg="${option_name}=${volume_arg}" + fi + + fixed_args+=("${arg}" "${next_extra_args[@]}") + next_extra_args=() +done + +run_docker_compose "${global_args[@]}" "${fixed_args[@]}" "${image_args[@]}" diff --git a/tests/fixtures/docker-compose.yaml b/tests/fixtures/docker-compose.yaml new file mode 100644 index 0000000..82edaf7 --- /dev/null +++ b/tests/fixtures/docker-compose.yaml @@ -0,0 +1,6 @@ +services: + test: + image: busybox + volumes: + - /wd:/wd + command: [grep, -q, ^test$$, /wd/testfile]