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)

Returns:
  • (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)

Returns:
  • (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)

Returns:
  • (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)

Returns:
  • (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)

Returns:
  • (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