Class: AgentHarness::DockerCommandExecutor
- Inherits:
-
CommandExecutor
- Object
- CommandExecutor
- AgentHarness::DockerCommandExecutor
- Defined in:
- lib/agent_harness/docker_command_executor.rb
Overview
Executes commands inside a Docker container
Wraps commands with ‘docker exec` so they run inside the specified container rather than on the host.
Constant Summary
Constants inherited from CommandExecutor
CommandExecutor::PREPARATION_CLEANUP_GRACE_PERIOD, CommandExecutor::PREPARATION_LOCK_POLL_INTERVAL, CommandExecutor::PREPARATION_LOCK_ROOT
Instance Attribute Summary collapse
-
#container_id ⇒ Object
readonly
Returns the value of attribute container_id.
Attributes inherited from CommandExecutor
Instance Method Summary collapse
-
#execute(command, timeout: nil, idle_timeout: nil, env: {}, stdin_data: nil, preparation: nil, **execution_options) ⇒ Result
Execute a command inside the Docker container.
-
#initialize(container_id:, logger: nil) ⇒ DockerCommandExecutor
constructor
Initialize the Docker command executor.
-
#which(binary) ⇒ String?
Check if a binary exists inside the container.
Methods inherited from CommandExecutor
Constructor Details
#initialize(container_id:, logger: nil) ⇒ DockerCommandExecutor
Initialize the Docker command executor
25 26 27 28 29 30 31 32 33 |
# File 'lib/agent_harness/docker_command_executor.rb', line 25 def initialize(container_id:, logger: nil) unless container_id.is_a?(String) && !container_id.strip.empty? raise ArgumentError, "container_id cannot be nil or blank" end super(logger: logger) @container_id = container_id validate_docker! end |
Instance Attribute Details
#container_id ⇒ Object (readonly)
Returns the value of attribute container_id.
18 19 20 |
# File 'lib/agent_harness/docker_command_executor.rb', line 18 def container_id @container_id end |
Instance Method Details
#execute(command, timeout: nil, idle_timeout: nil, env: {}, stdin_data: nil, preparation: nil, **execution_options) ⇒ Result
Execute a command inside the Docker container
Wraps the given command with ‘docker exec` and delegates to the parent class for actual process execution.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/agent_harness/docker_command_executor.rb', line 48 def execute(command, timeout: nil, idle_timeout: nil, env: {}, stdin_data: nil, preparation: nil, **) start_time = current_time normalized_command = normalize_command(command) command_name = normalized_command.first deadline = timeout_deadline(timeout) cleanup_steps = [] execution_tracking = nil held_preparation_locks = acquire_preparation_locks( preparation, env: env, timeout: timeout, deadline: deadline, command_name: command_name ) background_cleanup_scheduled = false apply_container_preparation(preparation, timeout: timeout, deadline: deadline, env: env, cleanup_steps: cleanup_steps) execution_tracking = if timeout && !preparation.nil? && !preparation.empty? build_container_execution_tracking(normalized_command, env: env) end docker_cmd = build_docker_command_for_execution( normalized_command, env: env, stdin_data: stdin_data, execution_tracking: execution_tracking ) begin result = super( docker_cmd, timeout: remaining_timeout(deadline, timeout:, command_name: command_name), idle_timeout: idle_timeout, env: {}, stdin_data: stdin_data, ** ) rescue IdleTimeoutError raise rescue TimeoutError schedule_container_cleanup_preparation( cleanup_steps, held_preparation_locks, command_name: command_name, termination_command: execution_tracking && execution_tracking[:terminate_command], finalizer_command: execution_tracking && execution_tracking[:cleanup_command] ) background_cleanup_scheduled = true held_preparation_locks = [] raise TimeoutError, "Command timed out after #{timeout} seconds: #{command_name}" end begin cleanup_container_preparation( cleanup_steps, timeout:, deadline: cleanup_deadline(deadline, timeout:), command_name: command_name ) cleanup_container_execution_tracking( execution_tracking, timeout:, deadline: cleanup_deadline(deadline, timeout:), command_name: command_name ) execution_tracking = nil rescue TimeoutError # The main command already finished; omit termination_command so # background cleanup does not TERM/KILL based on a stale PID file # that may have been reused by an unrelated process. schedule_container_cleanup_preparation( cleanup_steps, held_preparation_locks, command_name: command_name, termination_command: nil, finalizer_command: execution_tracking && execution_tracking[:cleanup_command] ) background_cleanup_scheduled = true held_preparation_locks = [] end Result.new( stdout: result.stdout, stderr: result.stderr, exit_code: result.exit_code, duration: current_time - start_time ) ensure pending_exception = $! cleanup_pending = !cleanup_steps.nil? && !cleanup_steps.empty? tracking_cleanup_pending = !execution_tracking.nil? if !background_cleanup_scheduled && (cleanup_pending || tracking_cleanup_pending) begin cleanup_container_preparation( cleanup_steps, timeout:, deadline: cleanup_deadline(deadline, timeout:), command_name: command_name ) cleanup_container_execution_tracking( execution_tracking, timeout:, deadline: cleanup_deadline(deadline, timeout:), command_name: command_name ) rescue TimeoutError => e raise e if pending_exception.nil? if pending_exception.is_a?(TimeoutError) schedule_container_cleanup_preparation( cleanup_steps, held_preparation_locks, command_name: command_name, termination_command: execution_tracking && execution_tracking[:terminate_command], finalizer_command: execution_tracking && execution_tracking[:cleanup_command] ) background_cleanup_scheduled = true held_preparation_locks = [] else # Preserve the original non-timeout exception; surface that # cleanup also timed out so callers know bootstrap state may # have leaked. raise pending_exception.class, "#{pending_exception.} (cleanup also failed: #{e.})" end rescue => e raise e if pending_exception.nil? # Surface cleanup failures even when unwinding from another exception, # so callers know request-scoped bootstrap state may have leaked. raise pending_exception.class, "#{pending_exception.} (cleanup also failed: #{e.})" end end unless background_cleanup_scheduled || held_preparation_locks.nil? || held_preparation_locks.empty? release_preparation_locks(held_preparation_locks) end end |
#which(binary) ⇒ String?
Check if a binary exists inside the container
187 188 189 190 |
# File 'lib/agent_harness/docker_command_executor.rb', line 187 def which(binary) result = execute(["which", binary], timeout: 5) result.success? ? result.stdout.strip : nil end |