class GenericCommand
Generic command executor that holds the code shared by all the command executors.
Properties:
-
code
: integer holding the exit code. Read-only -
stdout
: string of the standard output. Read-only -
stderr
: string of the standard error. Read-only -
command
: command to execute. Read-only
The protocol for scripts to log is as follows:
-
Log messages will be sent to STDOUT
-
The script will return 0 if it succeded or any other value if there was a failure
-
In case of failure the cause of the error will be written to STDERR wrapped by start and end marks as follows:
ERROR MESSAGE --8<------ error message for the failure ERROR MESSAGE ------>8--
Constants
- ERROR_CLOSE
- ERROR_OPEN
Attributes
Public Class Methods
Creates the new command: command
: string with the command to be executed logger
: proc that takes a message parameter and logs it
# File lib/CommandManager.rb, line 60 def initialize(command, logger=nil, stdin=nil, timeout=nil) @command = command @logger = logger @stdin = stdin @timeout = timeout end
Creates a command and runs it
# File lib/CommandManager.rb, line 51 def self.run(command, logger=nil, stdin=nil, timeout=nil) cmd = self.new(command, logger, stdin, timeout) cmd.run cmd end
Public Instance Methods
Parses error message from stderr
output
# File lib/CommandManager.rb, line 103 def get_error_message tmp=@stderr.scan(/^#{ERROR_OPEN}\n(.*?)#{ERROR_CLOSE}$/m) return "-" if !tmp[0] tmp[0].join(' ').strip end
Sends a log message to the logger proc
# File lib/CommandManager.rb, line 68 def log(message, all=true) @logger.call(message, all) if @logger end
Runs the command
# File lib/CommandManager.rb, line 73 def run begin @stdout, @stderr, status = execute if status && status.exited? @code = status.exitstatus else @code = 255 end if @code != 0 log("Command execution failed (exit code: #{@code}): #{command}") log(@stderr) end rescue Exception => e if e.is_a?(Timeout::Error) error_message = "Timeout executing #{command}" else error_message = "Internal error #{e}" end log(error_message) @stderr = ERROR_OPEN + "\n" + error_message + "\n" + ERROR_CLOSE @code = 255 end return @code end
Private Instance Methods
modified Open3.capture with terminator thread to deal with timeouts
# File lib/CommandManager.rb, line 121 def capture3_timeout(*cmd) if Hash === cmd.last opts = cmd.pop.dup else opts = {} end stdin_data = opts.delete(:stdin_data) || '' binmode = opts.delete(:binmode) Open3.popen3(*cmd, opts) {|i, o, e, t| if binmode i.binmode o.binmode e.binmode end terminator_e = nil mutex = Mutex.new out_reader = Thread.new { o.read } err_reader = Thread.new { e.read } terminator = Thread.new { if @timeout and @timeout>0 begin pid = Process.getpgid(t.pid) * -1 rescue pid = t.pid end if pid begin sleep @timeout mutex.synchronize do terminator_e = Timeout::Error end ensure end Process.kill('TERM', pid) end end } i.write stdin_data i.close out = [out_reader.value, err_reader.value, t.value] mutex.lock terminator.kill raise terminator_e if terminator_e out } end
Low level command execution. This method has to be redefined for each kind of command execution. Returns an array with stdout
, stderr
and status
of the command execution.
# File lib/CommandManager.rb, line 114 def execute puts "About to execute \"#{@command}\"" ['', '', nil] end