2019-09-10 10:58:45 +01:00
|
|
|
require 'open3'
|
|
|
|
|
|
2019-09-03 14:45:25 +01:00
|
|
|
module Pipeline::Util
|
|
|
|
|
class ExternalCommand
|
|
|
|
|
|
2019-11-27 22:59:33 +00:00
|
|
|
BLOCK_SIZE = 1024
|
|
|
|
|
|
|
|
|
|
attr_accessor :cmd_string, :status, :stdout, :stderr, :suppress_output,
|
|
|
|
|
:stderr_limit, :stdout_limit
|
2019-09-03 14:45:25 +01:00
|
|
|
|
|
|
|
|
def initialize(cmd_string)
|
|
|
|
|
@cmd_string = cmd_string
|
2019-11-27 22:59:33 +00:00
|
|
|
@stdout_limit = -1
|
|
|
|
|
@stderr_limit = -1
|
2019-09-03 14:45:25 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def call!
|
|
|
|
|
call
|
|
|
|
|
raise "Failed #{cmd_string}" unless status.success?
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def call
|
2019-11-27 22:59:33 +00:00
|
|
|
invoke_process
|
|
|
|
|
|
|
|
|
|
|
2019-09-03 14:45:25 +01:00
|
|
|
puts "status: #{status}" unless suppress_output
|
|
|
|
|
puts "stdout: #{stdout}" unless suppress_output
|
|
|
|
|
puts "stderr: #{stderr}" unless suppress_output
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def cmd
|
|
|
|
|
if @timeout
|
2019-11-27 20:22:46 +00:00
|
|
|
"/usr/bin/timeout -s 9 -k #{@timeout + 1} #{@timeout} #{cmd_string}"
|
2019-09-03 14:45:25 +01:00
|
|
|
else
|
|
|
|
|
cmd_string
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def success?
|
2019-11-27 22:59:33 +00:00
|
|
|
status && status.success?
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def killed?
|
|
|
|
|
!! @killed
|
2019-09-03 14:45:25 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def exit_status
|
|
|
|
|
status.exitstatus
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def timeout=(timeout)
|
|
|
|
|
@timeout = timeout
|
|
|
|
|
end
|
2019-09-10 14:29:00 +01:00
|
|
|
|
|
|
|
|
def report
|
|
|
|
|
{
|
|
|
|
|
cmd: cmd_string,
|
|
|
|
|
success: success?,
|
2019-12-05 15:42:41 +00:00
|
|
|
stdout: fix_encoding(stdout),
|
|
|
|
|
stderr: fix_encoding(stderr)
|
2019-09-10 14:29:00 +01:00
|
|
|
}
|
|
|
|
|
end
|
2019-11-27 22:59:33 +00:00
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
2019-12-05 15:42:41 +00:00
|
|
|
def fix_encoding(text)
|
|
|
|
|
return nil if text.nil
|
|
|
|
|
text.force_encoding("ISO-8859-1").encode("UTF-8")
|
|
|
|
|
rescue => e
|
|
|
|
|
puts e.message
|
|
|
|
|
puts e.backtrace
|
|
|
|
|
"--- failed to encode as UTF-8: #{e.message} ---"
|
|
|
|
|
end
|
|
|
|
|
|
2019-11-27 22:59:33 +00:00
|
|
|
def invoke_process
|
|
|
|
|
c = cmd
|
|
|
|
|
captured_stdout = []
|
|
|
|
|
captured_stderr = []
|
|
|
|
|
stdout_size = 0
|
|
|
|
|
stderr_size = 0
|
|
|
|
|
puts "> #{c}" unless suppress_output
|
|
|
|
|
Open3.popen3(c) do |_stdin, _stdout, _stderr, wait_thr|
|
|
|
|
|
pid = wait_thr.pid
|
|
|
|
|
_stdin.close_write
|
|
|
|
|
|
|
|
|
|
begin
|
|
|
|
|
files = [_stdout, _stderr]
|
|
|
|
|
|
|
|
|
|
until files.find { |f| !f.eof }.nil? || @killed do
|
|
|
|
|
ready = IO.select(files)
|
|
|
|
|
|
|
|
|
|
if ready
|
|
|
|
|
readable = ready[0]
|
|
|
|
|
readable.each do |f|
|
|
|
|
|
begin
|
|
|
|
|
data = f.read_nonblock(BLOCK_SIZE)
|
|
|
|
|
if f == _stdout
|
|
|
|
|
unless @killed
|
|
|
|
|
captured_stdout << data
|
|
|
|
|
stdout_size += data.size
|
|
|
|
|
if stdout_limit > 0 && stdout_size > stdout_limit
|
|
|
|
|
Process.kill("KILL", wait_thr.pid)
|
|
|
|
|
@killed = true
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
if f == _stderr
|
|
|
|
|
unless @killed
|
|
|
|
|
captured_stderr << data
|
|
|
|
|
stderr_size += data.size
|
|
|
|
|
if stderr_limit > 0 && stderr_size > stderr_limit
|
|
|
|
|
Process.kill("KILL", wait_thr.pid)
|
|
|
|
|
@killed = true
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
rescue EOFError => e
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
rescue IOError => e
|
|
|
|
|
puts "IOError: #{e}"
|
|
|
|
|
ensure
|
|
|
|
|
@stdout = captured_stdout.join
|
|
|
|
|
@stderr = captured_stderr.join
|
|
|
|
|
@status = wait_thr.value
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2019-09-03 14:45:25 +01:00
|
|
|
end
|
|
|
|
|
end
|