Packages
A typed Elixir wrapper for the Docker CLI with struct-based commands and pipeline composition
Current section
Files
Jump to
Current section
Files
lib/docker/debug/executor.ex
defmodule Docker.Debug.Executor do
@moduledoc """
Retry-aware command executor with dry-run and verbose logging support.
Wraps `Docker.Command.run/3` with debug capabilities.
"""
require Logger
alias Docker.Config, as: DockerConfig
alias Docker.Debug.{Config, Retry}
@doc """
Executes a command with debug options applied.
When `dry_run: true`, returns the command string that would be executed.
When `verbose: true`, logs the command and result.
When `retry` is set, retries failed commands according to the policy.
"""
@spec run(module(), struct(), DockerConfig.t(), Config.t()) ::
{:ok, term()} | {:error, term()}
def run(mod, command, config, %Config{dry_run: true}) do
args = DockerConfig.base_args(config) ++ mod.args(command)
{:ok, Enum.join([config.binary | args], " ")}
end
def run(mod, command, config, %Config{} = debug) do
log_command(mod, command, config, debug.verbose)
result = execute_with_retry(mod, command, config, debug.retry, debug.verbose)
log_result(result, debug.verbose)
result
end
defp log_command(mod, command, config, true) do
args = DockerConfig.base_args(config) ++ mod.args(command)
Logger.info("[docker_wrapper] executing: #{Enum.join([config.binary | args], " ")}")
end
defp log_command(_, _, _, _), do: :ok
defp log_result({:ok, val}, true) do
Logger.info("[docker_wrapper] success: #{inspect(val, limit: 200)}")
end
defp log_result({:error, reason}, true) do
Logger.warning("[docker_wrapper] failed: #{inspect(reason, limit: 200)}")
end
defp log_result(_, _), do: :ok
defp execute_with_retry(mod, command, config, nil, _verbose) do
Docker.Command.run(mod, command, config)
end
defp execute_with_retry(mod, command, config, %Retry{} = policy, verbose) do
do_retry(mod, command, config, policy, 0, verbose, nil)
end
defp do_retry(_mod, _command, _config, %Retry{max_attempts: max}, attempt, _verbose, last_error)
when attempt >= max do
{:error, last_error}
end
defp do_retry(mod, command, config, %Retry{} = policy, attempt, verbose, _last_error) do
maybe_sleep(policy, attempt, verbose)
case Docker.Command.run(mod, command, config) do
{:ok, _} = success ->
success
{:error, reason} = error ->
if policy.retryable?.(reason) do
do_retry(mod, command, config, policy, attempt + 1, verbose, reason)
else
error
end
end
end
defp maybe_sleep(_policy, 0, _verbose), do: :ok
defp maybe_sleep(policy, attempt, verbose) do
delay = Retry.delay(policy, attempt - 1)
if verbose do
Logger.info("[docker_wrapper] retry #{attempt}/#{policy.max_attempts - 1} after #{delay}ms")
end
Process.sleep(delay)
end
end