lib/ztk/command.rb in ztk-0.2.4 vs lib/ztk/command.rb in ztk-0.2.5

- old
+ new

@@ -17,10 +17,11 @@ # limitations under the License. # ################################################################################ require "ostruct" +require "timeout" module ZTK # ZTK::Command Error Class # @@ -39,12 +40,15 @@ # cmd = ZTK::Command.new(:stdout => std_combo, :stderr => std_combo) # # @author Zachary Patten <zachary@jovelabs.net> class Command < ZTK::Base - def initialize(config={}) - super(config) + def initialize(configuration={}) + super({ + :timeout => 600 + }.merge(configuration)) + config.logger.debug { "config(#{config.inspect})" } end def inspect @hostname ||= %x(hostname -f).chomp "#{ENV['USER']}@#{@hostname}" @@ -54,12 +58,12 @@ # # @param [String] command The command to execute. # @param [Hash] options The options hash for executing the command. # # @return [OpenStruct#output] The output of the command, both STDOUT and - # STDERR. - # @return [OpenStruct#exit] The exit status (i.e. $?). + # STDERR combined. + # @return [OpenStruct#exit_code] The exit code of the process. # # @example Execute a command: # # cmd = ZTK::Command.new # puts cmd.exec("hostname -f").inspect @@ -71,20 +75,25 @@ header = [sep, "[ #{tag} ]", sep, "[ #{self.inspect} ]", sep, "[ #{tag} ]", sep].join "#{header}\n" end options = OpenStruct.new({ :exit_code => 0, :silence => false }.merge(options)) - log(:debug) { "options(#{options.inspect})" } - log(:debug) { "command(#{command.inspect})" } + config.logger.debug { "config(#{config.inspect})" } + config.logger.debug { "options(#{options.inspect})" } + config.logger.info { "command(#{command.inspect})" } + output = "" + exit_code = -1 stdout_header = false stderr_header = false parent_stdout_reader, child_stdout_writer = IO.pipe parent_stderr_reader, child_stderr_writer = IO.pipe + start_time = Time.now.utc + pid = Process.fork do parent_stdout_reader.close parent_stderr_reader.close STDOUT.reopen(child_stdout_writer) @@ -103,64 +112,74 @@ reader_writer_map = {parent_stdout_reader => @config.stdout, parent_stderr_reader => @config.stderr} direct_log(:debug) { log_header("COMMAND") } direct_log(:debug) { "#{command}\n" } direct_log(:debug) { log_header("STARTED") } - loop do - break if reader_writer_map.keys.all?{ |reader| reader.eof? } - sockets = IO.select(reader_writer_map.keys).first - sockets.each do |socket| - data = socket.read - next if (data.nil? || data.empty?) + begin + Timeout.timeout(config.timeout) do + loop do + pipes = IO.select(reader_writer_map.keys, [], reader_writer_map.keys).first + pipes.each do |pipe| + data = pipe.read + next if (data.nil? || data.empty?) - case reader_writer_key[socket] - when :stdout then - if !stdout_header - direct_log(:debug) { log_header("STDOUT") } - stdout_header = true - stderr_header = false - end - reader_writer_map[socket].write(data) unless options.silence - direct_log(:debug) { data } + case reader_writer_key[pipe] + when :stdout then + if !stdout_header + direct_log(:debug) { log_header("STDOUT") } + stdout_header = true + stderr_header = false + end + reader_writer_map[pipe].write(data) unless options.silence + direct_log(:debug) { data } - when :stderr then - if !stderr_header - direct_log(:warn) { log_header("STDERR") } - stderr_header = true - stdout_header = false + when :stderr then + if !stderr_header + direct_log(:warn) { log_header("STDERR") } + stderr_header = true + stdout_header = false + end + reader_writer_map[pipe].write(data) unless options.silence + direct_log(:warn) { data } + end + + output += data end - reader_writer_map[socket].write(data) unless options.silence - direct_log(:warn) { data } + break if reader_writer_map.keys.all?{ |reader| reader.eof? } end - - output += data end + rescue Timeout::Error => e + direct_log(:debug) { log_header("TIMEOUT") } + log_and_raise(CommandError, "Process timed out after #{config.timeout} seconds!") end - direct_log(:debug) { log_header("STOPPED") } Process.waitpid(pid) + direct_log(:debug) { log_header("STOPPED") } parent_stdout_reader.close parent_stderr_reader.close - log(:debug) { "exit_code(#{$?.inspect})" } - - if ($? != options.exit_code) - message = "exec(#{command.inspect}, #{options.inspect}) failed! [#{$?.inspect}]" - log(:fatal) { message } - raise CommandError, message + if RUBY_VERSION >= "1.9" + exit_code = $?.exitstatus + else + exit_code = $? end - OpenStruct.new(:output => output, :exit => $?) + config.logger.debug { "exit_code(#{exit_code})" } + + if (exit_code != options.exit_code) + log_and_raise(CommandError, "exec(#{command.inspect}, #{options.inspect}) failed! [#{exit_code}]") + end + OpenStruct.new(:output => output, :exit_code => exit_code) end def upload(*args) - raise CommandError, "Not Implemented" + log_and_raise(CommandError, "Not Supported") end def download(*args) - raise CommandError, "Not Implemented" + log_and_raise(CommandError, "Not Supported") end end end