ztk 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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