watchmonkey_cli 1.4 → 1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 553a66c3a760e64c80ac8af012b7119721aaf521
4
- data.tar.gz: fec50a69b3321cfd2c5d38b9e78d65218471e5bd
3
+ metadata.gz: 856c3354f0039a351a73dfd0175af79e387078fb
4
+ data.tar.gz: 07e20ff5db1bc068d50784b826a05d412b5a7836
5
5
  SHA512:
6
- metadata.gz: ca30f9f9e6702f400c3d05bdf81fca702adc04b52126ad15d71c21472a30d5076d761f7a9877120a54768fd0091934856882901b13891758f9d2156e44e80e88
7
- data.tar.gz: 95df3dba388da9fddc6fd44bf7c9ff6a38480c42db424de3a75307a0949537da4d60d7f3400aac3970eeb43e9fd8f4049ae94be267394d926f0c8c3d51c20449
6
+ metadata.gz: e19791d169aa0d08700b7f85a2b38932271f640b676737f50ecd0beab86c87486068967f5a3ed896925d4fc43548a09932bed2cec201d8fc9ba58d62ed4d3538
7
+ data.tar.gz: 1fa78746dafc66551783806f3ea963badf8aae01a61fdc23fdf349655449721c6203837607013506279d513c8f6cc0f7b44c00de008b22f91b58a7614d658077
data/README.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # Watchmonkey CLI
2
2
 
3
- Coming soon.
3
+ Watchmonkey is a very simple tool to monitor resources with Ruby without the need of installing agents on the systems you want to monitor. To accomplish this the application polls information via SSH or other endpoints (e.g. websites, FTP access). It's suitable for small to medium amounts of services.
4
+
5
+ Before looking any further you might want to know:
6
+
7
+ * There is no escalation or notification system but you may add it yourself
8
+ * I created this for being used with [Platypus](http://sveinbjorn.org/platypus) hence the [Platypus Hook](https://github.com/2called-chaos/watchmonkey_cli/blob/master/lib/watchmonkey_cli/hooks/platypus.rb)
9
+ * This is how the text output looks like: [Screenshot](http://imgur.com/8yLYnKb)
10
+ * This is how the Platypus support looks like: [ProgressBar](http://imgur.com/Vd8ZD7A) [HTML/WebView](http://imgur.com/5FwmWFZ)
4
11
 
5
12
  ---
6
13
 
@@ -9,24 +16,73 @@ If you need help or have problems [open an issue](https://github.com/2called-cha
9
16
 
10
17
 
11
18
  ## Features
12
- * Colors!
13
-
19
+ * Monitor external resources (Web, FTP, Server health via SSH)
20
+ * Run once or loop forever with ReQueue (define intervals globally, per checker or per single test)
21
+ * Includes a selection of buildin checkers (basic *nix health, WWW availability & SSL expiration, FTP)
14
22
 
15
23
 
16
24
  ## Requirements
17
25
  * Ruby >= 2.0
18
- * Unixoid OS (such as Ubuntu/Debian, OS X, maybe others) or Windows 7/8 (not recommended)
26
+ * Unixoid OS (such as Ubuntu/Debian, OS X, maybe others) or Windows (not recommended)
19
27
  * something you want to monitor
20
28
 
21
29
 
22
-
23
30
  ## Installation
24
31
  * `gem install watchmonkey_cli`
25
32
  * `watchmonkey --generate-config [name=default]`
33
+ * Edit the created file to fit your needs
34
+ * Run `watchmonkey`
35
+ * Check out the additional features below (e.g. ReQueue)
36
+
37
+
38
+ ## Usage
39
+ To get a list of available options invoke Watchmonkey with the `--help` or `-h` option:
40
+
41
+ Usage: watchmonkey [options]
42
+ # Application options
43
+ --generate-config [myconfig] Generates a example config in ~/.watchmonkey
44
+ -l, --log [file] Log to file, defaults to ~/.watchmonkey/logs/watchmonkey.log
45
+ -t, --threads [NUM] Amount of threads to be used for checking (default: 10)
46
+ -s, --silent Only print errors and infos
47
+ -q, --quiet Only print errors
48
+
49
+ # General options
50
+ -d, --debug [lvl=1] Enable debug output
51
+ -m, --monochrome Don't colorize output
52
+ -h, --help Shows this help
53
+ -v, --version Shows version and other info
54
+ -z Do not check for updates on GitHub (with -v/--version)
55
+ --dump-core for developers
56
+
57
+
58
+ ## Documentation
59
+ Writing a documentation takes time and I'm not sure if anyone is even interested in this tool. If there is interest I will write a documentation but for now this Readme must suffice. If you need help just [open an issue](https://github.com/2called-chaos/watchmonkey_cli/issues/new) :)
60
+
61
+
62
+ ## Create configs
63
+ Use the `--generate-config` option or refer to the [configuration template](https://github.com/2called-chaos/watchmonkey_cli/blob/master/lib/watchmonkey_cli/application/configuration.tpl) for examples and documentation.
26
64
 
27
65
 
28
66
  ## Deactivate configs
29
- If you want to deactivate configs entirely just rename the file to start with two underscores (e.g.: `__example.rb`).
67
+ If you want to deactivate single configs just rename the file to start with two underscores (e.g.: `__example.rb`).
68
+
69
+
70
+ ## Application configuration
71
+ If you want to add custom checkers, hooks or change default settings you can create `~/.watchmonkey/config.rb`. The file will be eval'd in the application object's context. Take a look at the [example configuration file](https://github.com/2called-chaos/watchmonkey_cli/blob/master/lib/watchmonkey_cli/application.rb) and [application.rb](https://github.com/2called-chaos/watchmonkey_cli/blob/master/lib/watchmonkey_cli/doc/config_example.rb).
72
+
73
+
74
+ ## Custom checkers
75
+ If you want to monitor something that is not covered by the buildin handlers you can create your own, it's not that hard and should be a breeze if you are used to Ruby. All descendants of the `WatchmonkeyCli::Checker` class will be initialized and are usable in the application. Documentation is thin but you can take a look at the [example checker](https://github.com/2called-chaos/watchmonkey_cli/blob/master/lib/watchmonkey_cli/doc/checker_example.rb), the [buildin checkers](https://github.com/2called-chaos/watchmonkey_cli/tree/master/lib/watchmonkey_cli/checkers) or just [open an issue](https://github.com/2called-chaos/watchmonkey_cli/issues/new) and I might just implement it real quick.
76
+
77
+
78
+ ## Additional Features
79
+
80
+ ### ReQueue
81
+ By default Watchmonkey will run all tests once and then exit. This addon will enable Watchmonkey to run in a loop and run tests on a periodic interval.
82
+ Since this seems like a core feature it might get included directly into Watchmonkey but for now take a look at the documentation in the [ReQueue source code](https://github.com/2called-chaos/watchmonkey_cli/blob/master/lib/watchmonkey_cli/hooks/requeue.rb) for integration instructions.
83
+
84
+ ### Platypus support
85
+ [Platypus](http://sveinbjorn.org/platypus) is a MacOS software to create dead simple GUI wrappers for scripts. There is buildin support for the interface types ProgressBar and WebView. For information look at the documentation in the [Platypus hook source code](https://github.com/2called-chaos/watchmonkey_cli/blob/master/lib/watchmonkey_cli/hooks/platypus.rb).
30
86
 
31
87
 
32
88
  ## Contributing
@@ -39,7 +95,6 @@ If you want to deactivate configs entirely just rename the file to start with tw
39
95
  5. Create new Pull Request
40
96
 
41
97
 
42
-
43
98
  ## Legal
44
99
  * © 2016, Sven Pachnit (www.bmonkeys.net)
45
100
  * watchmonkey_cli is licensed under the MIT license.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.4
1
+ 1.5
data/bin/watchmonkey CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  require "watchmonkey_cli"
3
3
  begin
4
+ Process.setproctitle("watchmonkey_cli")
4
5
  WatchmonkeyCli::Application.dispatch(ENV, ARGV)
5
6
  rescue Interrupt
6
7
  puts("\n\nInterrupted")
@@ -2,22 +2,53 @@ module WatchmonkeyCli
2
2
  class Application
3
3
  class Configuration
4
4
  module AppHelper
5
+ def wm_cfg_path
6
+ ENV["WM_CFGDIR"].presence || File.expand_path("~/.watchmonkey")
7
+ end
8
+
5
9
  def config_directory
6
10
  "#{wm_cfg_path}/configs"
7
11
  end
8
12
 
13
+ def checker_directory
14
+ "#{wm_cfg_path}/checkers"
15
+ end
16
+
17
+ def wm_cfg_configfile
18
+ "#{wm_cfg_path}/config.rb"
19
+ end
20
+
21
+ def config_filename name = "default"
22
+ "#{config_directory}/#{name}.rb"
23
+ end
24
+
9
25
  def config_files
10
26
  Dir["#{config_directory}/**/*.rb"].reject do |file|
11
27
  file.gsub(config_directory, "").split("/").any?{|fp| fp.start_with?("__") }
12
28
  end
13
29
  end
14
30
 
15
- def config_filename name = "default"
16
- "#{config_directory}/#{name}.rb"
31
+ def checker_files
32
+ Dir["#{checker_directory}/**/*.rb"].reject do |file|
33
+ file.gsub(config_directory, "").split("/").any?{|fp| fp.start_with?("__") }
34
+ end
17
35
  end
18
36
 
19
37
  def load_configs!
20
- config_files.each {|f| Configuration.new(self, f) }
38
+ configs = config_files
39
+ debug "Loading #{configs.length} config files from `#{config_directory}'"
40
+ configs.each {|f| Configuration.new(self, f) }
41
+ end
42
+
43
+ def load_checkers!
44
+ checkers = checker_files
45
+ debug "Loading #{checkers.length} checker files from `#{checker_directory}'"
46
+ checkers.each {|f| require f }
47
+ end
48
+
49
+ def load_appconfig
50
+ return unless File.exist?(wm_cfg_configfile)
51
+ eval File.read(wm_cfg_configfile, encoding: "utf-8"), binding, wm_cfg_configfile
21
52
  end
22
53
 
23
54
  def generate_config name = "default"
@@ -0,0 +1,155 @@
1
+ module WatchmonkeyCli
2
+ class Application
3
+ module Core
4
+ # ===================
5
+ # = Signal trapping =
6
+ # ===================
7
+ def trap_signals
8
+ debug "Trapping INT signal..."
9
+ Signal.trap("INT") do
10
+ $wm_runtime_exiting = true
11
+ Kernel.puts "Interrupting..."
12
+ end
13
+ end
14
+
15
+ def release_signals
16
+ debug "Releasing INT signal..."
17
+ Signal.trap("INT", "DEFAULT")
18
+ end
19
+
20
+ def haltpoint
21
+ raise Interrupt if $wm_runtime_exiting
22
+ end
23
+
24
+
25
+ # ==========
26
+ # = Events =
27
+ # ==========
28
+ def hook *which, &hook_block
29
+ which.each do |w|
30
+ @hooks[w.to_sym] ||= []
31
+ @hooks[w.to_sym] << hook_block
32
+ end
33
+ end
34
+
35
+ def fire which, *args
36
+ return if @disable_event_firing
37
+ sync { debug "[Event] Firing #{which} (#{@hooks[which].try(:length) || 0} handlers) #{args.map(&:class)}", 99 }
38
+ @hooks[which] && @hooks[which].each{|h| h.call(*args) }
39
+ end
40
+
41
+
42
+ # ==========
43
+ # = Logger =
44
+ # ==========
45
+ def logger_filename
46
+ "#{wm_cfg_path}/logs/watchmonkey.log"
47
+ end
48
+
49
+ def logger
50
+ sync do
51
+ @logger ||= begin
52
+ FileUtils.mkdir_p(File.dirname(@opts[:logfile]))
53
+ Logger.new(@opts[:logfile], 10, 1024000)
54
+ end
55
+ end
56
+ end
57
+
58
+
59
+ # =======================
60
+ # = Connection handling =
61
+ # =======================
62
+ def fetch_connection type, id, opts = {}, &initializer
63
+ if !@connections[type] || !@connections[type][id]
64
+ @connections[type] ||= {}
65
+ case type
66
+ when :loopback
67
+ @connections[type][id] = LoopbackConnection.new(id, opts, &initializer)
68
+ when :ssh
69
+ @connections[type][id] = SshConnection.new(id, opts, &initializer)
70
+ else
71
+ raise NotImplementedError, "unknown connection type `#{type}'!"
72
+ end
73
+ end
74
+ @connections[type][id]
75
+ end
76
+
77
+ def close_connections!
78
+ @connections.each do |type, clist|
79
+ clist.each{|id, con| con.close! }
80
+ end
81
+ end
82
+
83
+
84
+ # =========================
85
+ # = Queue tasks & methods =
86
+ # =========================
87
+ def enqueue checker, *a, &block
88
+ sync do
89
+ cb = block || checker.method(:check!)
90
+ evreg = @disable_event_registration
91
+ fire(:enqueue, checker, a, cb) unless evreg
92
+ @queue << [checker, a, ->(*a) {
93
+ begin
94
+ result = Checker::Result.new(checker, *a)
95
+ checker.debug(result.str_running)
96
+ checker.safe(result.str_safe) { cb.call(result, *a) }
97
+ fire(:result_dump, result, a, checker)
98
+ result.dump!
99
+ ensure
100
+ fire(:dequeue, checker, a) unless evreg
101
+ end
102
+ }]
103
+ end
104
+ end
105
+
106
+ def enqueue_sub checker, which, *args
107
+ sync do
108
+ if sec = @checkers[which.to_s]
109
+ begin
110
+ # ef_was = @disable_event_firing
111
+ er_was = @disable_event_registration
112
+ # @disable_event_firing = true
113
+ @disable_event_registration = true
114
+ sec.enqueue(*args)
115
+ ensure
116
+ # @disable_event_firing = ef_was
117
+ @disable_event_registration = er_was
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ def spawn_threads_and_run!
124
+ if @opts[:threads] > 1
125
+ debug "Spawning #{@opts[:threads]} consumer threads..."
126
+ @opts[:threads].times do
127
+ @threads << Thread.new do
128
+ Thread.current.abort_on_exception = true
129
+ _queueoff
130
+ end
131
+ end
132
+ else
133
+ debug "Running threadless..."
134
+ _queueoff
135
+ end
136
+ end
137
+
138
+ def _queueoff
139
+ while !@queue.empty? || @opts[:loop_forever]
140
+ break if $wm_runtime_exiting
141
+ item = queue.pop(true) rescue false
142
+ if item
143
+ Thread.current[:working] = true
144
+ fire(:wm_work_start, Thread.current)
145
+ sync { @processed += 1 }
146
+ item[2].call(*item[1])
147
+ Thread.current[:working] = false
148
+ fire(:wm_work_end, Thread.current)
149
+ end
150
+ sleep @opts[:loop_wait_empty] if @opts[:loop_forever] && @opts[:loop_wait_empty] && @queue.empty?
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -28,6 +28,7 @@ module WatchmonkeyCli
28
28
  def dispatch_index
29
29
  Thread.abort_on_exception = true
30
30
  trap_signals
31
+ load_checkers!
31
32
  init_checkers!
32
33
  load_configs!
33
34
  dump_and_exit! if @opts[:dump]
@@ -0,0 +1,30 @@
1
+ module WatchmonkeyCli
2
+ class Application
3
+ module OutputHelper
4
+ def puts *a
5
+ sync { @opts[:stdout].send(:puts, *a) }
6
+ end
7
+
8
+ def print *a
9
+ sync { @opts[:stdout].send(:print, *a) }
10
+ end
11
+
12
+ def warn *a
13
+ sync { @opts[:stdout].send(:warn, *a) }
14
+ end
15
+
16
+ def debug msg, lvl = 1
17
+ puts c("[DEBUG] #{msg}", :black) if @opts[:debug] && @opts[:debug] >= lvl
18
+ end
19
+
20
+ def abort msg, exit_code = 1
21
+ puts c("[ABORT] #{msg}", :red)
22
+ exit(exit_code)
23
+ end
24
+
25
+ def error msg
26
+ warn c(msg, :red)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,8 +1,10 @@
1
1
  module WatchmonkeyCli
2
2
  class Application
3
3
  attr_reader :opts, :checkers, :connections, :threads, :queue, :hooks, :processed
4
- include Helpers
4
+ include Helper
5
+ include OutputHelper
5
6
  include Colorize
7
+ include Core
6
8
  include Dispatch
7
9
  include Configuration::AppHelper
8
10
  include Checker::AppHelper
@@ -12,7 +14,7 @@ module WatchmonkeyCli
12
14
  # =========
13
15
  def self.dispatch *a
14
16
  new(*a) do |app|
15
- app.load_config
17
+ app.load_appconfig
16
18
  app.parse_params
17
19
  begin
18
20
  app.dispatch
@@ -27,6 +29,7 @@ module WatchmonkeyCli
27
29
  end
28
30
 
29
31
  def initialize env, argv
32
+ @boot = Time.current
30
33
  @env, @argv = env, argv
31
34
  @connections = {}
32
35
  @hooks = {}
@@ -46,6 +49,7 @@ module WatchmonkeyCli
46
49
  loop_wait_empty: 1, # (internal) time to wait in thread if queue is empty
47
50
  silent: false, # -s flag
48
51
  quiet: false, # -q flag
52
+ stdout: STDOUT, # (internal) STDOUT redirect
49
53
  }
50
54
  init_params
51
55
  yield(self)
@@ -63,7 +67,7 @@ module WatchmonkeyCli
63
67
  opts.on("-q", "--quiet", "Only print errors") { @opts[:quiet] = true }
64
68
 
65
69
  opts.separator("\n" << c("# General options", :blue))
66
- opts.on("-d", "--debug", "Enable debug output") { @opts[:debug] = true }
70
+ opts.on("-d", "--debug [lvl=1]", Integer, "Enable debug output") {|l| @opts[:debug] = l || 1 }
67
71
  opts.on("-m", "--monochrome", "Don't colorize output") { @opts[:colorize] = false }
68
72
  opts.on("-h", "--help", "Shows this help") { @opts[:dispatch] = :help }
69
73
  opts.on("-v", "--version", "Shows version and other info") { @opts[:dispatch] = :info }
@@ -84,167 +88,10 @@ module WatchmonkeyCli
84
88
  @running
85
89
  end
86
90
 
87
- def load_config
88
- return unless File.exist?(wm_cfg_configfile)
89
- eval File.read(wm_cfg_configfile, encoding: "utf-8"), binding, wm_cfg_configfile
90
- end
91
-
92
- def debug msg
93
- puts c("[DEBUG] #{msg}", :black) if @opts[:debug]
94
- end
95
-
96
- def abort msg, exit_code = 1
97
- puts c("[ABORT] #{msg}", :red)
98
- exit(exit_code)
99
- end
100
-
101
- def error msg
102
- warn c(msg, :red)
103
- end
104
-
105
- def hook *which, &hook_block
106
- which.each do |w|
107
- @hooks[w.to_sym] ||= []
108
- @hooks[w.to_sym] << hook_block
109
- end
110
- end
111
-
112
- def fire which, *args
113
- return if @disable_event_firing
114
- sync { debug "[Event] Firing #{which} (#{@hooks[which].try(:length) || 0} handlers) #{args.map(&:class)}" }
115
- @hooks[which] && @hooks[which].each{|h| h.call(*args) }
116
- end
117
-
118
- def fetch_connection type, id, opts = {}, &initializer
119
- if !@connections[type] || !@connections[type][id]
120
- @connections[type] ||= {}
121
- case type
122
- when :loopback
123
- @connections[type][id] = LoopbackConnection.new(id, opts, &initializer)
124
- when :ssh
125
- @connections[type][id] = SshConnection.new(id, opts, &initializer)
126
- else
127
- raise NotImplementedError, "unknown connection type `#{type}'!"
128
- end
129
- end
130
- @connections[type][id]
131
- end
132
-
133
- def close_connections!
134
- @connections.each do |type, clist|
135
- clist.each{|id, con| con.close! }
136
- end
137
- end
138
-
139
91
  def sync &block
140
92
  @monitor.synchronize(&block)
141
93
  end
142
94
 
143
- def spawn_threads_and_run!
144
- if @opts[:threads] > 1
145
- debug "Spawning #{@opts[:threads]} consumer threads..."
146
- @opts[:threads].times do
147
- @threads << Thread.new do
148
- Thread.current.abort_on_exception = true
149
- _queueoff
150
- end
151
- end
152
- else
153
- debug "Running threadless..."
154
- _queueoff
155
- end
156
- end
157
-
158
- def enqueue checker, *a, &block
159
- sync do
160
- cb = block || checker.method(:check!)
161
- evreg = @disable_event_registration
162
- fire(:enqueue, checker, a, cb) unless evreg
163
- @queue << [checker, a, ->(*a) {
164
- begin
165
- result = Checker::Result.new(checker, *a)
166
- checker.debug(result.str_running)
167
- checker.safe(result.str_safe) { cb.call(result, *a) }
168
- fire(:result_dump, result, a, checker)
169
- result.dump!
170
- ensure
171
- fire(:dequeue, checker, a) unless evreg
172
- end
173
- }]
174
- end
175
- end
176
-
177
- def enqueue_sub checker, which, *args
178
- sync do
179
- if sec = @checkers[which.to_s]
180
- begin
181
- # ef_was = @disable_event_firing
182
- er_was = @disable_event_registration
183
- # @disable_event_firing = true
184
- @disable_event_registration = true
185
- sec.enqueue(*args)
186
- ensure
187
- # @disable_event_firing = ef_was
188
- @disable_event_registration = er_was
189
- end
190
- end
191
- end
192
- end
193
-
194
- def _queueoff
195
- while !@queue.empty? || @opts[:loop_forever]
196
- break if $wm_runtime_exiting
197
- item = queue.pop(true) rescue false
198
- if item
199
- Thread.current[:working] = true
200
- fire(:wm_work_start, Thread.current)
201
- sync { @processed += 1 }
202
- item[2].call(*item[1])
203
- Thread.current[:working] = false
204
- fire(:wm_work_end, Thread.current)
205
- end
206
- sleep @opts[:loop_wait_empty] if @opts[:loop_forever] && @opts[:loop_wait_empty] && @queue.empty?
207
- end
208
- end
209
-
210
- def wm_cfg_path
211
- ENV["WM_CFGDIR"].presence || File.expand_path("~/.watchmonkey")
212
- end
213
-
214
- def wm_cfg_configfile
215
- "#{wm_cfg_path}/config.rb"
216
- end
217
-
218
- def logger_filename
219
- "#{wm_cfg_path}/logs/watchmonkey.log"
220
- end
221
-
222
- def logger
223
- sync do
224
- @logger ||= begin
225
- FileUtils.mkdir_p(File.dirname(@opts[:logfile]))
226
- Logger.new(@opts[:logfile], 10, 1024000)
227
- end
228
- end
229
- end
230
-
231
- def trap_signals
232
- debug "Trapping INT signal..."
233
- Signal.trap("INT") do
234
- $wm_runtime_exiting = true
235
- puts "Interrupting..."
236
- end
237
- end
238
-
239
- def release_signals
240
- debug "Releasing INT signal..."
241
- Signal.trap("INT", "DEFAULT")
242
- end
243
-
244
- def haltpoint
245
- raise Interrupt if $wm_runtime_exiting
246
- end
247
-
248
95
  def dump_and_exit!
249
96
  puts " Queue: #{@queue.length}"
250
97
  puts " AppOpts: #{@opts}"
@@ -34,6 +34,7 @@ module WatchmonkeyCli
34
34
  end
35
35
 
36
36
  def stop_checkers!
37
+ return unless @checkers
37
38
  @checkers.each do |key, instance|
38
39
  debug "[SETUP] Stopping checker `#{key}' (#{instance.class.name})"
39
40
  instance.stop
@@ -116,9 +117,7 @@ module WatchmonkeyCli
116
117
  app.fire(:on_info, msg, robj)
117
118
  return if app.opts[:quiet]
118
119
  _tolog(msg, :info)
119
- app.sync do
120
- puts app.c(msg, :blue)
121
- end
120
+ app.puts app.c(msg, :blue)
122
121
  end
123
122
 
124
123
  def debug msg, robj = nil
@@ -126,9 +125,7 @@ module WatchmonkeyCli
126
125
  app.fire(:on_message, msg, robj)
127
126
  return if app.opts[:quiet] || app.opts[:silent]
128
127
  _tolog(msg, :debug)
129
- app.sync do
130
- puts app.c(msg, :black)
131
- end
128
+ app.puts app.c(msg, :black)
132
129
  end
133
130
 
134
131
  def error msg, robj = nil
@@ -144,8 +141,7 @@ module WatchmonkeyCli
144
141
  end
145
142
 
146
143
  def spawn_sub which, *args
147
- return false if app.running?
148
- if sec = app.checkers["ssl_expiration"]
144
+ if sec = app.checkers[which.to_s]
149
145
  sec.enqueue(*args)
150
146
  end
151
147
  end
@@ -221,7 +217,15 @@ module WatchmonkeyCli
221
217
  # and therefore shared resources might still be in use
222
218
  end
223
219
 
220
+ def enqueue *args
221
+ # Called by configuration defining a check with all the arguments.
222
+ # e.g. www_availability :my_host, foo: "bar" => args = [:my_host, {foo: "bar"}]
223
+ # Should invoke `app.enqueue` which will by default call `#check!` method with given arguments.
224
+ raise NotImplementedError, "a checker (#{self.class.name}) must implement `#enqueue' method!"
225
+ end
226
+
224
227
  def check! *a
228
+ # required, see #enqueue
225
229
  raise NotImplementedError, "a checker (#{self.class.name}) must implement `#check!' method!"
226
230
  end
227
231
  end
@@ -13,9 +13,9 @@ module WatchmonkeyCli
13
13
  ftp.login(opts[:user], opts[:password])
14
14
  end
15
15
  rescue Net::FTPPermError
16
- result.error "Invalid credentials!"
16
+ result.error! "Invalid credentials!"
17
17
  rescue SocketError => e
18
- result.error "#{e.class}: #{e.message}"
18
+ result.error! "#{e.class}: #{e.message}"
19
19
  end
20
20
  end
21
21
  end
@@ -7,7 +7,7 @@ module WatchmonkeyCli
7
7
  app.enqueue(self, page, opts.except(:ssl_expiration))
8
8
 
9
9
  # if available enable ssl_expiration support
10
- if page.start_with?("https://") && opts[:ssl_expiration] != false
10
+ if page.start_with?("https://") && opts[:ssl_expiration] != false && !app.running?
11
11
  spawn_sub("ssl_expiration", page, opts[:ssl_expiration].is_a?(Hash) ? opts[:ssl_expiration] : {})
12
12
  end
13
13
  end
@@ -33,9 +33,9 @@ module WatchmonkeyCli
33
33
  # body
34
34
  if rx = opts[:body]
35
35
  if rx.is_a?(String)
36
- result.error! "body does not include #{rx}!" if !result.result.body.include?(rx)
36
+ result.error! "body does not include `#{rx}'!" if !result.result.body.include?(rx)
37
37
  elsif rx.is_a?(Regexp)
38
- result.error! "body does not match #{rx}!" if !result.result.body.match(rx)
38
+ result.error! "body does not match `#{rx}'!" if !result.result.body.match(rx)
39
39
  end
40
40
  end
41
41
 
@@ -1,5 +1,5 @@
1
1
  module WatchmonkeyCli
2
- module Helpers
2
+ module Helper
3
3
  BYTE_UNITS = %W(TiB GiB MiB KiB B).freeze
4
4
 
5
5
  def human_filesize(s)
@@ -1,13 +1,31 @@
1
1
  module WatchmonkeyCli
2
+ JS_ESCAPE_MAP = { '\\' => '\\\\', '</' => '<\/', "\r\n" => '\n', "\n" => '\n', "\r" => '\n', '"' => '\\"', "'" => "\\'" }
2
3
  class Platypus
4
+ class SilentOutput
5
+ def print *a
6
+ end
7
+ def puts *a
8
+ end
9
+ def warn *a
10
+ end
11
+ end
12
+
3
13
  def self.hook!(app, opts = {})
4
- opts = opts.reverse_merge(notifications: true)
14
+ opts = opts.reverse_merge(notifications: 1, progress: true, html: false, draw_delay: 1)
15
+ opts[:progress] = false if opts[:html]
5
16
  app.instance_eval do
17
+ @opts[:stdout] = SilentOutput.new
18
+ @platypus_status_cache = {
19
+ errors: [],
20
+ }
21
+
6
22
  # send errors via notification center
7
23
  hook :result_dump do |robj, args, checker|
8
24
  if robj.error?
9
25
  robj.messages.each do |m|
10
26
  msg = "#{robj.args[0].try(:name) || robj.args[0].presence || "?"}: #{m}"
27
+ @platypus_status_cache[:errors].unshift([Time.current, msg])
28
+ @platypus_status_cache[:errors].pop if @platypus_status_cache[:errors].length > 20
11
29
 
12
30
  case opts[:notifications]
13
31
  when 1
@@ -16,7 +34,7 @@ module WatchmonkeyCli
16
34
  `osascript -e 'display notification "#{fmsg}" with title "WatchMonkey"'`
17
35
  when 2
18
36
  # makes a sound
19
- sync { puts "NOTIFICATION:#{msg}" }
37
+ puts "NOTIFICATION:#{msg}"
20
38
  end
21
39
  end
22
40
  end
@@ -24,13 +42,76 @@ module WatchmonkeyCli
24
42
 
25
43
  hook :wm_work_start, :wm_work_end do
26
44
  # mastermind calculation I swear :D (<-- no idea what I did here)
27
- # sync { puts "PROGRESS:#{((@threads.length-@threads.select{|t| t[:working] }.length.to_d) / @threads.length * 100).to_i}" }
45
+ # puts "PROGRESS:#{((@threads.length-@threads.select{|t| t[:working] }.length.to_d) / @threads.length * 100).to_i}"
28
46
  sync do
29
47
  active = @threads.select{|t| t[:working] }.length
30
48
  total = @threads.select{|t| t[:working] }.length + @queue.length
31
49
  perc = total.zero? ? 100 : (active.to_d / total * 100).to_i
32
50
  puts "PROGRESS:#{perc}"
33
51
  end
52
+ end if opts[:progress]
53
+
54
+ # HTML output (fancy as fuck!)
55
+ if opts[:html]
56
+ def escape_javascript str
57
+ str.gsub(/(\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
58
+ end
59
+
60
+ def platypus_init_html
61
+ output = %{
62
+ <html>
63
+ <head>
64
+ <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
65
+ <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
66
+ <title>Moep</title>
67
+ </head>
68
+ <body style="padding: 8px">
69
+ <dl class="dl-horizontal">
70
+ <dt>Items in Queue</dt><dd class="qlength">#{@queue.length}</dd>
71
+ <dt>Items in ReQ</dt><dd class="rqlength">#{@requeue.length}</dd>
72
+ <dt>Workers</dt><dd class="workers">#{@threads.select{|t| t[:working] }.length}/#{@threads.length} working (#{@threads.select(&:alive?).length} alive)</dd>
73
+ <dt>Threads</dt><dd class="tlength">#{Thread.list.length}</dd>
74
+ <dt>Processed entries</dt><dd class="processed">#{@processed}</dd>
75
+ <dt>Watching since</dt><dd>#{@boot}</dd>
76
+ <dt>Last draw</dt><dd class="lastdraw">#{Time.current}</dd>
77
+ </dl>
78
+ <h3>Latest errors</h3>
79
+ <pre class="lasterrors" style="display: block; white-space: pre; word-break: normal; word-wrap: normal;"></pre>
80
+ </body>
81
+ </html>
82
+ }
83
+ sync { Kernel.puts output }
84
+ end
85
+
86
+ def platypus_update_html
87
+ dead = @threads.reject(&:alive?).length
88
+ ti = " (#{dead} DEAD)" if dead > 0
89
+ output = %{
90
+ <script>
91
+ $("script").remove();
92
+ $("dd.qlength").html("#{@queue.length}");
93
+ $("dd.rqlength").html("#{@requeue.length}");
94
+ $("dd.workers").html("#{@threads.select{|t| t[:working] }.length}/#{@threads.length} working#{ti}");
95
+ $("dd.tlength").html("#{Thread.list.length}");
96
+ $("dd.processed").html("#{@processed}");
97
+ $("dd.lastdraw").html("#{Time.current}");
98
+ $("pre.lasterrors").html("#{escape_javascript @platypus_status_cache[:errors].map{|t,e| "#{t}: #{e}" }.join("\n")}");
99
+ </script>
100
+ }
101
+ sync { Kernel.puts output }
102
+ end
103
+
104
+ platypus_init_html
105
+ @platypus_status_thread = Thread.new do
106
+ Thread.current.abort_on_exception = true
107
+ loop do
108
+ platypus_update_html
109
+ sleep opts[:draw_delay]
110
+ end
111
+ end
112
+ hook :wm_shutdown do
113
+ @platypus_status_thread.try(:kill).try(:join)
114
+ end
34
115
  end
35
116
  end
36
117
  end
@@ -26,7 +26,7 @@ module WatchmonkeyCli
26
26
  # =================
27
27
  # = Status thread =
28
28
  # =================
29
- @platypus_status_thread = Thread.new do
29
+ @requeue_status_thread = Thread.new do
30
30
  Thread.current.abort_on_exception = true
31
31
  while STDIN.gets
32
32
  sync do
@@ -1,4 +1,4 @@
1
1
  module WatchmonkeyCli
2
- VERSION = "1.4"
2
+ VERSION = "1.5"
3
3
  UPDATE_URL = "https://raw.githubusercontent.com/2called-chaos/watchmonkey_cli/master/VERSION"
4
4
  end
@@ -21,8 +21,10 @@ require 'net/ssh'
21
21
  require "watchmonkey_cli/version"
22
22
  require "watchmonkey_cli/loopback_connection"
23
23
  require "watchmonkey_cli/ssh_connection"
24
- require "watchmonkey_cli/helpers"
24
+ require "watchmonkey_cli/helper"
25
25
  require "watchmonkey_cli/checker"
26
+ require "watchmonkey_cli/application/core"
27
+ require "watchmonkey_cli/application/output_helper"
26
28
  require "watchmonkey_cli/application/colorize"
27
29
  require "watchmonkey_cli/application/configuration"
28
30
  require "watchmonkey_cli/application/dispatch"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: watchmonkey_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.4'
4
+ version: '1.5'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Pachnit
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-18 00:00:00.000000000 Z
11
+ date: 2016-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -117,7 +117,9 @@ files:
117
117
  - lib/watchmonkey_cli/application/colorize.rb
118
118
  - lib/watchmonkey_cli/application/configuration.rb
119
119
  - lib/watchmonkey_cli/application/configuration.tpl
120
+ - lib/watchmonkey_cli/application/core.rb
120
121
  - lib/watchmonkey_cli/application/dispatch.rb
122
+ - lib/watchmonkey_cli/application/output_helper.rb
121
123
  - lib/watchmonkey_cli/checker.rb
122
124
  - lib/watchmonkey_cli/checkers/ftp_availability.rb
123
125
  - lib/watchmonkey_cli/checkers/mysql_replication.rb
@@ -129,7 +131,7 @@ files:
129
131
  - lib/watchmonkey_cli/checkers/unix_mdadm.rb
130
132
  - lib/watchmonkey_cli/checkers/unix_memory.rb
131
133
  - lib/watchmonkey_cli/checkers/www_availability.rb
132
- - lib/watchmonkey_cli/helpers.rb
134
+ - lib/watchmonkey_cli/helper.rb
133
135
  - lib/watchmonkey_cli/hooks/platypus.rb
134
136
  - lib/watchmonkey_cli/hooks/requeue.rb
135
137
  - lib/watchmonkey_cli/loopback_connection.rb