ztk 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -35,26 +35,70 @@ module ZTK
35
35
  # and extend functionality as appropriate.
36
36
  class Base
37
37
 
38
+ class << self
39
+
40
+ # @param [Hash] config Configuration options hash.
41
+ # @option config [IO] :stdout Instance of IO to be used for STDOUT.
42
+ # @option config [IO] :stderr Instance of IO to be used for STDERR.
43
+ # @option config [IO] :stdin Instance of IO to be used for STDIN.
44
+ # @option config [Logger] :logger Instance of Logger to be used for logging.
45
+ def build_config(configuration={})
46
+ if configuration.is_a?(OpenStruct)
47
+ configuration = configuration.send(:table)
48
+ end
49
+
50
+ rails_logger = Rails.logger if defined?(Rails)
51
+
52
+ config = OpenStruct.new({
53
+ :stdout => $stdout,
54
+ :stderr => $stderr,
55
+ :stdin => $stdin,
56
+ :logger => ($logger || rails_logger || ZTK::Logger.new("/dev/null"))
57
+ }.merge(configuration))
58
+
59
+ (config.stdout && config.stdout.respond_to?(:sync=)) and config.stdout.sync = true
60
+ (config.stderr && config.stderr.respond_to?(:sync=)) and config.stderr.sync = true
61
+ (config.stdin && config.stdin.respond_to?(:sync=)) and config.stdin.sync = true
62
+ (config.logger && config.logger.respond_to?(:sync=)) and config.logger.sync = true
63
+
64
+ config
65
+ end
66
+
67
+ # Removes all key-value pairs which are not core so values do not bleed
68
+ # into classes they are not meant for.
69
+ #
70
+ # This method will leave :stdout, :stderr, :stdin and :logger key-values
71
+ # intact, while removing all other key-value pairs.
72
+ def sanitize_config(configuration={})
73
+ if configuration.is_a?(OpenStruct)
74
+ configuration = configuration.send(:table)
75
+ end
76
+
77
+ config = configuration.reject do |key,value|
78
+ !(%w(stdout stderr stdin logger).map(&:to_sym).include?(key))
79
+ end
80
+
81
+ config
82
+ end
83
+
84
+ def log_and_raise(logger, exception, message, shift=1)
85
+ if logger.is_a?(ZTK::Logger)
86
+ logger.shift(:fatal, shift) { "EXCEPTION: #{exception.inspect} - #{message.inspect}" }
87
+ else
88
+ logger.fatal { "EXCEPTION: #{exception.inspect} - #{message.inspect}" }
89
+ end
90
+ raise exception, message
91
+ end
92
+
93
+ end
94
+
38
95
  # @param [Hash] config Configuration options hash.
39
96
  # @option config [IO] :stdout Instance of IO to be used for STDOUT.
40
97
  # @option config [IO] :stderr Instance of IO to be used for STDERR.
41
98
  # @option config [IO] :stdin Instance of IO to be used for STDIN.
42
99
  # @option config [Logger] :logger Instance of Logger to be used for logging.
43
100
  def initialize(config={})
44
- # defined?(Rails) and rails_logger = Rails.logger
45
- @config = OpenStruct.new({
46
- :stdout => $stdout,
47
- :stderr => $stderr,
48
- :stdin => $stdin,
49
- :logger => $logger
50
- }.merge(config))
51
-
52
- @config.stdout.respond_to?(:sync=) and @config.stdout.sync = true
53
- @config.stderr.respond_to?(:sync=) and @config.stderr.sync = true
54
- @config.stdin.respond_to?(:sync=) and @config.stdin.sync = true
55
- @config.logger.respond_to?(:sync=) and @config.logger.sync = true
56
-
57
- log(:debug) { "config(#{@config.inspect})" }
101
+ @config = Base.build_config(config)
58
102
  end
59
103
 
60
104
  # Configuration OpenStruct accessor method.
@@ -73,7 +117,15 @@ module ZTK
73
117
  end
74
118
  end
75
119
 
76
- # Base logging method.
120
+ def log_and_raise(exception, message)
121
+ Base.log_and_raise(config.logger, exception, message, 2)
122
+ end
123
+
124
+ # Direct logging method.
125
+ #
126
+ # This method provides direct writing of data to the current log device.
127
+ # This is mainly used for pushing STDOUT and STDERR into the log file in
128
+ # ZTK::SSH and ZTK::Command, but could easily be used by other classes.
77
129
  #
78
130
  # The value returned in the block is passed down to the logger specified in
79
131
  # the classes configuration.
@@ -81,24 +133,18 @@ module ZTK
81
133
  # @param [Symbol] method_name This should be any one of [:debug, :info, :warn, :error, :fatal].
82
134
  # @yield No value is passed to the block.
83
135
  # @yieldreturn [String] The message to log.
84
- def log(method_name, &block)
85
- if block_given?
86
- @config.logger and @config.logger.method(method_name.to_sym).call { yield }
87
- else
88
- raise BaseError, "You must supply a block to the log method!"
89
- end
90
- end
91
-
92
136
  def direct_log(log_level, &blocK)
93
137
  @config.logger.nil? and raise BaseError, "You must supply a logger for direct logging support!"
94
138
 
95
139
  if !block_given?
96
- raise BaseError, "You must supply a block to the log method!"
140
+ log_and_raise(BaseError, "You must supply a block to the log method!")
97
141
  elsif (@config.logger.level <= ZTK::Logger.const_get(log_level.to_s.upcase))
98
142
  if @config.logger.respond_to?(:logdev)
99
143
  @config.logger.logdev.write(yield)
144
+ @config.logger.logdev.respond_to?(:flush) and @config.logger.logdev.flush
100
145
  else
101
146
  @config.logger.instance_variable_get(:@logdev).instance_variable_get(:@dev).write(yield)
147
+ @config.logger.instance_variable_get(:@logdev).instance_variable_get(:@dev).respond_to?(:flush) and @config.logger.instance_variable_get(:@logdev).instance_variable_get(:@dev).flush
102
148
  end
103
149
  end
104
150
  end
@@ -34,30 +34,27 @@ module ZTK
34
34
  class << self
35
35
 
36
36
  def bench(options={}, &block)
37
- !block_given? and raise BenchmarkError, "You must supply a block!"
37
+ options = Base.build_config(options)
38
+ options.logger.debug { "options(#{options.inspect})" }
38
39
 
39
- options = { :stdout => STDOUT, :logger => $logger, :message => nil, :mark => nil }.merge(options)
40
+ !block_given? and Base.log_and_raise(options.logger, BenchmarkError, "You must supply a block!")
40
41
 
41
- stdout = options[:stdout]
42
- logger = options[:logger]
43
- message = options[:message]
44
- mark = options[:mark]
42
+ check = [options.message, options.mark]
43
+ (check.any?{ |z| !z.nil? } && !check.all?{ |z| !z.nil? }) and Base.log_and_raise(options.logger, BenchmarkError, "You must supply both a message and a mark!")
45
44
 
46
- logger and logger.debug { options.inspect }
47
-
48
- (!message.nil? && !mark.nil?) and stdout.print("#{message} ")
45
+ (options.message && options.mark) and options.stdout.print("#{options.message} ")
49
46
  benchmark = ::Benchmark.realtime do
50
- if message.nil?
51
- yield
52
- else
53
- ZTK::Spinner.spin do
47
+ if (options.message && options.mark)
48
+ ZTK::Spinner.spin(Base.sanitize_config(options)) do
54
49
  yield
55
50
  end
51
+ else
52
+ yield
56
53
  end
57
54
  end
58
55
 
59
- (!message.nil? && !mark.nil?) and stdout.print("#{mark}\n" % benchmark)
60
- logger and logger.info { "#{message} #{mark}" }
56
+ (options.message && options.mark) and options.stdout.print("#{options.mark}\n" % benchmark)
57
+ options.logger.info { "#{options.message} #{options.mark}" }
61
58
 
62
59
  benchmark
63
60
  end
@@ -19,6 +19,7 @@
19
19
  ################################################################################
20
20
 
21
21
  require "ostruct"
22
+ require "timeout"
22
23
 
23
24
  module ZTK
24
25
 
@@ -41,8 +42,11 @@ module ZTK
41
42
  # @author Zachary Patten <zachary@jovelabs.net>
42
43
  class Command < ZTK::Base
43
44
 
44
- def initialize(config={})
45
- super(config)
45
+ def initialize(configuration={})
46
+ super({
47
+ :timeout => 600
48
+ }.merge(configuration))
49
+ config.logger.debug { "config(#{config.inspect})" }
46
50
  end
47
51
 
48
52
  def inspect
@@ -56,8 +60,8 @@ module ZTK
56
60
  # @param [Hash] options The options hash for executing the command.
57
61
  #
58
62
  # @return [OpenStruct#output] The output of the command, both STDOUT and
59
- # STDERR.
60
- # @return [OpenStruct#exit] The exit status (i.e. $?).
63
+ # STDERR combined.
64
+ # @return [OpenStruct#exit_code] The exit code of the process.
61
65
  #
62
66
  # @example Execute a command:
63
67
  #
@@ -73,16 +77,21 @@ module ZTK
73
77
  end
74
78
 
75
79
  options = OpenStruct.new({ :exit_code => 0, :silence => false }.merge(options))
76
- log(:debug) { "options(#{options.inspect})" }
77
- log(:debug) { "command(#{command.inspect})" }
80
+
81
+ config.logger.debug { "config(#{config.inspect})" }
82
+ config.logger.debug { "options(#{options.inspect})" }
83
+ config.logger.info { "command(#{command.inspect})" }
78
84
 
79
85
  output = ""
86
+ exit_code = -1
80
87
  stdout_header = false
81
88
  stderr_header = false
82
89
 
83
90
  parent_stdout_reader, child_stdout_writer = IO.pipe
84
91
  parent_stderr_reader, child_stderr_writer = IO.pipe
85
92
 
93
+ start_time = Time.now.utc
94
+
86
95
  pid = Process.fork do
87
96
  parent_stdout_reader.close
88
97
  parent_stderr_reader.close
@@ -105,61 +114,71 @@ module ZTK
105
114
  direct_log(:debug) { log_header("COMMAND") }
106
115
  direct_log(:debug) { "#{command}\n" }
107
116
  direct_log(:debug) { log_header("STARTED") }
108
- loop do
109
- break if reader_writer_map.keys.all?{ |reader| reader.eof? }
110
-
111
- sockets = IO.select(reader_writer_map.keys).first
112
- sockets.each do |socket|
113
- data = socket.read
114
- next if (data.nil? || data.empty?)
115
-
116
- case reader_writer_key[socket]
117
- when :stdout then
118
- if !stdout_header
119
- direct_log(:debug) { log_header("STDOUT") }
120
- stdout_header = true
121
- stderr_header = false
122
- end
123
- reader_writer_map[socket].write(data) unless options.silence
124
- direct_log(:debug) { data }
125
-
126
- when :stderr then
127
- if !stderr_header
128
- direct_log(:warn) { log_header("STDERR") }
129
- stderr_header = true
130
- stdout_header = false
117
+
118
+ begin
119
+ Timeout.timeout(config.timeout) do
120
+ loop do
121
+ pipes = IO.select(reader_writer_map.keys, [], reader_writer_map.keys).first
122
+ pipes.each do |pipe|
123
+ data = pipe.read
124
+ next if (data.nil? || data.empty?)
125
+
126
+ case reader_writer_key[pipe]
127
+ when :stdout then
128
+ if !stdout_header
129
+ direct_log(:debug) { log_header("STDOUT") }
130
+ stdout_header = true
131
+ stderr_header = false
132
+ end
133
+ reader_writer_map[pipe].write(data) unless options.silence
134
+ direct_log(:debug) { data }
135
+
136
+ when :stderr then
137
+ if !stderr_header
138
+ direct_log(:warn) { log_header("STDERR") }
139
+ stderr_header = true
140
+ stdout_header = false
141
+ end
142
+ reader_writer_map[pipe].write(data) unless options.silence
143
+ direct_log(:warn) { data }
144
+ end
145
+
146
+ output += data
131
147
  end
132
- reader_writer_map[socket].write(data) unless options.silence
133
- direct_log(:warn) { data }
148
+ break if reader_writer_map.keys.all?{ |reader| reader.eof? }
134
149
  end
135
-
136
- output += data
137
150
  end
151
+ rescue Timeout::Error => e
152
+ direct_log(:debug) { log_header("TIMEOUT") }
153
+ log_and_raise(CommandError, "Process timed out after #{config.timeout} seconds!")
138
154
  end
139
- direct_log(:debug) { log_header("STOPPED") }
140
155
 
141
156
  Process.waitpid(pid)
157
+ direct_log(:debug) { log_header("STOPPED") }
142
158
 
143
159
  parent_stdout_reader.close
144
160
  parent_stderr_reader.close
145
161
 
146
- log(:debug) { "exit_code(#{$?.inspect})" }
147
-
148
- if ($? != options.exit_code)
149
- message = "exec(#{command.inspect}, #{options.inspect}) failed! [#{$?.inspect}]"
150
- log(:fatal) { message }
151
- raise CommandError, message
162
+ if RUBY_VERSION >= "1.9"
163
+ exit_code = $?.exitstatus
164
+ else
165
+ exit_code = $?
152
166
  end
153
167
 
154
- OpenStruct.new(:output => output, :exit => $?)
168
+ config.logger.debug { "exit_code(#{exit_code})" }
169
+
170
+ if (exit_code != options.exit_code)
171
+ log_and_raise(CommandError, "exec(#{command.inspect}, #{options.inspect}) failed! [#{exit_code}]")
172
+ end
173
+ OpenStruct.new(:output => output, :exit_code => exit_code)
155
174
  end
156
175
 
157
176
  def upload(*args)
158
- raise CommandError, "Not Implemented"
177
+ log_and_raise(CommandError, "Not Supported")
159
178
  end
160
179
 
161
180
  def download(*args)
162
- raise CommandError, "Not Implemented"
181
+ log_and_raise(CommandError, "Not Supported")
163
182
  end
164
183
 
165
184
  end
@@ -56,6 +56,11 @@ module ZTK
56
56
  self.instance_variable_get(:@logdev).instance_variable_get(:@dev)
57
57
  end
58
58
 
59
+ def shift(severity, shift=0, &block)
60
+ severity = ZTK::Logger.const_get(severity.to_s.upcase)
61
+ add(severity, nil, nil, shift, &block)
62
+ end
63
+
59
64
 
60
65
  private
61
66
 
@@ -81,17 +86,18 @@ module ZTK
81
86
  # @param [String] progname Optional name of the program to prefix the log
82
87
  # entry with.
83
88
  # @yieldreturn [String] The block should return the desired log message.
84
- def add(severity, message = nil, progname = nil, &block)
89
+ def add(severity, message=nil, progname=nil, shift=0, &block)
85
90
  return if (@level > severity)
86
91
 
87
92
  msg = (block && block.call)
88
93
  (msg.nil? || msg.strip.empty?) and return
89
94
  @hostname ||= %x(hostname -s).chomp.strip
90
- called_by = "#{@hostname}:#{parse_caller(caller[1])}"
95
+ called_by = "#{@hostname}:#{parse_caller(caller[1+shift])}"
91
96
  message = [message, progname, msg].flatten.compact.join(": ")
92
97
  message = "%19s.%06d|%05d|%5s|%60s%s\n" % [Time.now.utc.strftime("%Y-%m-%d|%H:%M:%S"), Time.now.utc.usec, Process.pid, SEVERITIES[severity], called_by, message]
93
98
 
94
99
  @logdev.write(message)
100
+ @logdev.respond_to?(:flush) and @logdev.flush
95
101
 
96
102
  true
97
103
  end
@@ -76,12 +76,13 @@ module ZTK
76
76
  # @option config [Integer] :max_forks Maximum number of forks to use.
77
77
  # @option config [Proc] :before_fork (nil) Proc to call before forking.
78
78
  # @option config [Proc] :after_fork (nil) Proc to call after forking.
79
- def initialize(config={})
79
+ def initialize(configuration={})
80
80
  super({
81
81
  :max_forks => MAX_FORKS
82
- }.merge(config))
82
+ }.merge(configuration))
83
+ config.logger.debug { "config(#{config.inspect})" }
83
84
 
84
- raise ParallelError, "max_forks must be equal to or greater than one!" if @config.max_forks < 1
85
+ (config.max_forks < 1) and log_and_raise(ParallelError, "max_forks must be equal to or greater than one!")
85
86
 
86
87
  @forks = Array.new
87
88
  @results = Array.new
@@ -95,26 +96,26 @@ module ZTK
95
96
  # the parent processes result set.
96
97
  # @return [Integer] Returns the pid of the child process forked.
97
98
  def process(&block)
98
- raise ParallelError, "You must supply a block to the process method!" if !block_given?
99
+ !block_given? and log_and_raise(ParallelError, "You must supply a block to the process method!")
99
100
 
100
- log(:debug) { "forks(#{@forks.inspect})" }
101
+ config.logger.debug { "forks(#{@forks.inspect})" }
101
102
 
102
- while (@forks.count >= @config.max_forks) do
103
+ while (@forks.count >= config.max_forks) do
103
104
  wait
104
105
  end
105
106
 
106
107
  child_reader, parent_writer = IO.pipe
107
108
  parent_reader, child_writer = IO.pipe
108
109
 
109
- @config.before_fork and @config.before_fork.call(Process.pid)
110
+ config.before_fork and config.before_fork.call(Process.pid)
110
111
  pid = Process.fork do
111
- @config.after_fork and @config.after_fork.call(Process.pid)
112
+ config.after_fork and config.after_fork.call(Process.pid)
112
113
 
113
114
  parent_writer.close
114
115
  parent_reader.close
115
116
 
116
117
  if !(data = block.call).nil?
117
- log(:debug) { "write(#{data.inspect})" }
118
+ config.logger.debug { "write(#{data.inspect})" }
118
119
  child_writer.write(Base64.encode64(Marshal.dump(data)))
119
120
  end
120
121
 
@@ -122,7 +123,7 @@ module ZTK
122
123
  child_writer.close
123
124
  Process.exit!(0)
124
125
  end
125
- @config.after_fork and @config.after_fork.call(Process.pid)
126
+ config.after_fork and config.after_fork.call(Process.pid)
126
127
 
127
128
  child_reader.close
128
129
  child_writer.close
@@ -142,12 +143,12 @@ module ZTK
142
143
  # status and data returned from the process block. If wait2() fails nil
143
144
  # is returned.
144
145
  def wait
145
- log(:debug) { "wait" }
146
- log(:debug) { "forks(#{@forks.inspect})" }
146
+ config.logger.debug { "wait" }
147
+ config.logger.debug { "forks(#{@forks.inspect})" }
147
148
  pid, status = (Process.wait2(-1) rescue nil)
148
149
  if !pid.nil? && !status.nil? && !(fork = @forks.select{ |f| f[:pid] == pid }.first).nil?
149
150
  data = (Marshal.load(Base64.decode64(fork[:reader].read.to_s)) rescue nil)
150
- log(:debug) { "read(#{data.inspect})" }
151
+ config.logger.debug { "read(#{data.inspect})" }
151
152
  !data.nil? and @results.push(data)
152
153
  fork[:reader].close
153
154
  fork[:writer].close
@@ -162,7 +163,7 @@ module ZTK
162
163
  #
163
164
  # @return [Array<Object>] The results from all of the *process* blocks.
164
165
  def waitall
165
- log(:debug) { "waitall" }
166
+ config.logger.debug { "waitall" }
166
167
  while @forks.count > 0
167
168
  self.wait
168
169
  end
@@ -173,7 +174,7 @@ module ZTK
173
174
  #
174
175
  # @return [Integer] Current number of active forks.
175
176
  def count
176
- log(:debug) { "count(#{@forks.count})" }
177
+ config.logger.debug { "count(#{@forks.count})" }
177
178
  @forks.count
178
179
  end
179
180