class Rufio::ScriptExecutor
スクリプトを安全に実行するクラス
def build_error_result(error)
def build_error_result(error) { success: false, exit_code: 1, stdout: "", stderr: "", error: error.message, timeout: false } end
def execute(interpreter, script_path, args = [], timeout: nil, chdir: nil, env: nil)
-
(Hash)- 実行結果
Parameters:
-
env(Hash, nil) -- 環境変数 -
chdir(String, nil) -- 作業ディレクトリ -
timeout(Numeric, nil) -- タイムアウト秒数(nilの場合は無制限) -
args(Array) -- スクリプトへの引数 -
script_path(String) -- スクリプトのパス -
interpreter(String) -- インタープリタ(ruby, python3, bashなど)
def execute(interpreter, script_path, args = [], timeout: nil, chdir: nil, env: nil) # 配列ベースのコマンドを構築(シェルインジェクション防止) command = [interpreter, script_path, *args] # オプションを構築 options = {} options[:chdir] = chdir if chdir # 環境変数をマージ(既存の環境変数を保持) spawn_env = env || {} execute_with_options(command, spawn_env, options, timeout) rescue StandardError => e build_error_result(e) end
def execute_command(dsl_command, args = [], timeout: nil, chdir: nil, env: nil)
-
(Hash)- 実行結果
Parameters:
-
env(Hash, nil) -- 環境変数 -
chdir(String, nil) -- 作業ディレクトリ -
timeout(Numeric, nil) -- タイムアウト秒数 -
args(Array) -- 追加の引数 -
dsl_command(DslCommand) -- 実行するDSLコマンド
def execute_command(dsl_command, args = [], timeout: nil, chdir: nil, env: nil) case dsl_command.command_type when :ruby execute_ruby(dsl_command) when :shell execute_shell(dsl_command, timeout: timeout, chdir: chdir, env: env) else exec_args = dsl_command.to_execution_args execute(exec_args[0], exec_args[1], args, timeout: timeout, chdir: chdir, env: env) end end
def execute_ruby(dsl_command)
-
(Hash)- 実行結果
Parameters:
-
dsl_command(DslCommand) -- 実行するDSLコマンド
def execute_ruby(dsl_command) result = dsl_command.ruby_block.call { success: true, exit_code: 0, stdout: result.to_s, stderr: "", timeout: false } rescue StandardError => e { success: false, exit_code: 1, stdout: "", stderr: "", error: e.message, timeout: false } end
def execute_shell(dsl_command, timeout: nil, chdir: nil, env: nil)
-
(Hash)- 実行結果
Parameters:
-
env(Hash, nil) -- 環境変数 -
chdir(String, nil) -- 作業ディレクトリ -
timeout(Numeric, nil) -- タイムアウト秒数 -
dsl_command(DslCommand) -- 実行するDSLコマンド
def execute_shell(dsl_command, timeout: nil, chdir: nil, env: nil) options = {} options[:chdir] = chdir if chdir spawn_env = env || {} if timeout execute_shell_with_timeout(dsl_command.shell_command, spawn_env, options, timeout) else execute_shell_without_timeout(dsl_command.shell_command, spawn_env, options) end rescue StandardError => e build_error_result(e) end
def execute_shell_with_timeout(shell_command, env, options, timeout_sec)
def execute_shell_with_timeout(shell_command, env, options, timeout_sec) stdout = "" stderr = "" status = nil timed_out = false pid = nil begin Timeout.timeout(timeout_sec) do stdin, stdout_io, stderr_io, wait_thread = Open3.popen3(env, shell_command, **options) pid = wait_thread.pid stdin.close stdout = stdout_io.read stderr = stderr_io.read stdout_io.close stderr_io.close status = wait_thread.value end rescue Timeout::Error timed_out = true if pid begin Process.kill("TERM", pid) sleep 0.1 Process.kill("KILL", pid) rescue Errno::ESRCH, Errno::EPERM # プロセスが既に終了している、または権限がない end end end if timed_out { success: false, exit_code: nil, stdout: stdout, stderr: stderr, timeout: true } else { success: status&.success? || false, exit_code: status&.exitstatus || 1, stdout: stdout, stderr: stderr, timeout: false } end end
def execute_shell_without_timeout(shell_command, env, options)
def execute_shell_without_timeout(shell_command, env, options) stdout, stderr, status = Open3.capture3(env, shell_command, **options) { success: status.success?, exit_code: status.exitstatus, stdout: stdout, stderr: stderr, timeout: false } end
def execute_with_options(command, env, options, timeout_sec)
-
(Hash)- 実行結果
Parameters:
-
timeout_sec(Numeric, nil) -- タイムアウト秒数 -
options(Hash) -- Open3オプション -
env(Hash) -- 環境変数 -
command(Array) -- 実行するコマンド
def execute_with_options(command, env, options, timeout_sec) if timeout_sec execute_with_timeout(command, env, options, timeout_sec) else execute_without_timeout(command, env, options) end end
def execute_with_timeout(command, env, options, timeout_sec)
def execute_with_timeout(command, env, options, timeout_sec) stdout = "" stderr = "" status = nil timed_out = false pid = nil begin Timeout.timeout(timeout_sec) do stdin, stdout_io, stderr_io, wait_thread = Open3.popen3(env, *command, **options) pid = wait_thread.pid stdin.close stdout = stdout_io.read stderr = stderr_io.read stdout_io.close stderr_io.close status = wait_thread.value end rescue Timeout::Error timed_out = true # プロセスを終了 if pid begin Process.kill("TERM", pid) sleep 0.1 Process.kill("KILL", pid) rescue Errno::ESRCH, Errno::EPERM # プロセスが既に終了している、または権限がない end end end if timed_out { success: false, exit_code: nil, stdout: stdout, stderr: stderr, timeout: true } else { success: status&.success? || false, exit_code: status&.exitstatus || 1, stdout: stdout, stderr: stderr, timeout: false } end end
def execute_without_timeout(command, env, options)
def execute_without_timeout(command, env, options) stdout, stderr, status = Open3.capture3(env, *command, **options) { success: status.success?, exit_code: status.exitstatus, stdout: stdout, stderr: stderr, timeout: false } end