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.
- data/lib/ztk/base.rb +70 -24
- data/lib/ztk/benchmark.rb +12 -15
- data/lib/ztk/command.rb +62 -43
- data/lib/ztk/logger.rb +8 -2
- data/lib/ztk/parallel.rb +16 -15
- data/lib/ztk/rescue_retry.rb +10 -12
- data/lib/ztk/spinner.rb +12 -5
- data/lib/ztk/ssh.rb +139 -109
- data/lib/ztk/tcp_socket_check.rb +16 -24
- data/lib/ztk/version.rb +1 -1
- data/spec/spec_helper.rb +8 -0
- data/spec/ztk/base_spec.rb +41 -0
- data/spec/ztk/benchmark_spec.rb +10 -12
- data/spec/ztk/command_spec.rb +142 -9
- data/spec/ztk/config_spec.rb +16 -16
- data/spec/ztk/logger_spec.rb +1 -1
- data/spec/ztk/rescue_retry_spec.rb +3 -3
- data/spec/ztk/spinner_spec.rb +51 -0
- data/spec/ztk/ssh_spec.rb +385 -119
- data/spec/ztk/tcp_socket_check_spec.rb +6 -6
- metadata +8 -4
data/lib/ztk/base.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/ztk/benchmark.rb
CHANGED
@@ -34,30 +34,27 @@ module ZTK
|
|
34
34
|
class << self
|
35
35
|
|
36
36
|
def bench(options={}, &block)
|
37
|
-
|
37
|
+
options = Base.build_config(options)
|
38
|
+
options.logger.debug { "options(#{options.inspect})" }
|
38
39
|
|
39
|
-
|
40
|
+
!block_given? and Base.log_and_raise(options.logger, BenchmarkError, "You must supply a block!")
|
40
41
|
|
41
|
-
|
42
|
-
|
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
|
-
|
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.
|
51
|
-
|
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
|
-
(
|
60
|
-
logger
|
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
|
data/lib/ztk/command.rb
CHANGED
@@ -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(
|
45
|
-
super(
|
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#
|
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
|
-
|
77
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
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
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
|
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
|
-
|
177
|
+
log_and_raise(CommandError, "Not Supported")
|
159
178
|
end
|
160
179
|
|
161
180
|
def download(*args)
|
162
|
-
|
181
|
+
log_and_raise(CommandError, "Not Supported")
|
163
182
|
end
|
164
183
|
|
165
184
|
end
|
data/lib/ztk/logger.rb
CHANGED
@@ -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
|
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
|
data/lib/ztk/parallel.rb
CHANGED
@@ -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(
|
79
|
+
def initialize(configuration={})
|
80
80
|
super({
|
81
81
|
:max_forks => MAX_FORKS
|
82
|
-
}.merge(
|
82
|
+
}.merge(configuration))
|
83
|
+
config.logger.debug { "config(#{config.inspect})" }
|
83
84
|
|
84
|
-
|
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
|
-
|
99
|
+
!block_given? and log_and_raise(ParallelError, "You must supply a block to the process method!")
|
99
100
|
|
100
|
-
|
101
|
+
config.logger.debug { "forks(#{@forks.inspect})" }
|
101
102
|
|
102
|
-
while (@forks.count >=
|
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
|
-
|
110
|
+
config.before_fork and config.before_fork.call(Process.pid)
|
110
111
|
pid = Process.fork do
|
111
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
146
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
177
|
+
config.logger.debug { "count(#{@forks.count})" }
|
177
178
|
@forks.count
|
178
179
|
end
|
179
180
|
|