watchmonkey_cli 1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +45 -0
- data/Rakefile +1 -0
- data/VERSION +1 -0
- data/bin/watchmonkey +8 -0
- data/bin/watchmonkey.sh +14 -0
- data/lib/watchmonkey_cli/application/colorize.rb +22 -0
- data/lib/watchmonkey_cli/application/configuration.rb +55 -0
- data/lib/watchmonkey_cli/application/configuration.tpl +142 -0
- data/lib/watchmonkey_cli/application/dispatch.rb +86 -0
- data/lib/watchmonkey_cli/application.rb +255 -0
- data/lib/watchmonkey_cli/checker.rb +228 -0
- data/lib/watchmonkey_cli/checkers/ftp_availability.rb +22 -0
- data/lib/watchmonkey_cli/checkers/mysql_replication.rb +57 -0
- data/lib/watchmonkey_cli/checkers/ssl_expiration.rb +83 -0
- data/lib/watchmonkey_cli/checkers/unix_defaults.rb +40 -0
- data/lib/watchmonkey_cli/checkers/unix_df.rb +41 -0
- data/lib/watchmonkey_cli/checkers/unix_file_exists.rb +25 -0
- data/lib/watchmonkey_cli/checkers/unix_load.rb +30 -0
- data/lib/watchmonkey_cli/checkers/unix_mdadm.rb +60 -0
- data/lib/watchmonkey_cli/checkers/unix_memory.rb +37 -0
- data/lib/watchmonkey_cli/checkers/www_availability.rb +54 -0
- data/lib/watchmonkey_cli/helpers.rb +19 -0
- data/lib/watchmonkey_cli/hooks/platypus.rb +38 -0
- data/lib/watchmonkey_cli/hooks/requeue.rb +106 -0
- data/lib/watchmonkey_cli/loopback_connection.rb +36 -0
- data/lib/watchmonkey_cli/ssh_connection.rb +50 -0
- data/lib/watchmonkey_cli/version.rb +4 -0
- data/lib/watchmonkey_cli.rb +41 -0
- data/watchmonkey_cli.gemspec +27 -0
- metadata +163 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 553a66c3a760e64c80ac8af012b7119721aaf521
|
4
|
+
data.tar.gz: fec50a69b3321cfd2c5d38b9e78d65218471e5bd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ca30f9f9e6702f400c3d05bdf81fca702adc04b52126ad15d71c21472a30d5076d761f7a9877120a54768fd0091934856882901b13891758f9d2156e44e80e88
|
7
|
+
data.tar.gz: 95df3dba388da9fddc6fd44bf7c9ff6a38480c42db424de3a75307a0949537da4d60d7f3400aac3970eeb43e9fd8f4049ae94be267394d926f0c8c3d51c20449
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016, Sven Pachnit aka. 2called-chaos
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Watchmonkey CLI
|
2
|
+
|
3
|
+
Coming soon.
|
4
|
+
|
5
|
+
---
|
6
|
+
|
7
|
+
## Help
|
8
|
+
If you need help or have problems [open an issue](https://github.com/2called-chaos/watchmonkey_cli/issues/new).
|
9
|
+
|
10
|
+
|
11
|
+
## Features
|
12
|
+
* Colors!
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
## Requirements
|
17
|
+
* Ruby >= 2.0
|
18
|
+
* Unixoid OS (such as Ubuntu/Debian, OS X, maybe others) or Windows 7/8 (not recommended)
|
19
|
+
* something you want to monitor
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
* `gem install watchmonkey_cli`
|
25
|
+
* `watchmonkey --generate-config [name=default]`
|
26
|
+
|
27
|
+
|
28
|
+
## Deactivate configs
|
29
|
+
If you want to deactivate configs entirely just rename the file to start with two underscores (e.g.: `__example.rb`).
|
30
|
+
|
31
|
+
|
32
|
+
## Contributing
|
33
|
+
Contributions are very welcome! Either report errors, bugs and propose features or directly submit code:
|
34
|
+
|
35
|
+
1. Fork it
|
36
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
37
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
38
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
39
|
+
5. Create new Pull Request
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
## Legal
|
44
|
+
* © 2016, Sven Pachnit (www.bmonkeys.net)
|
45
|
+
* watchmonkey_cli is licensed under the MIT license.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.4
|
data/bin/watchmonkey
ADDED
data/bin/watchmonkey.sh
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# That's __FILE__ in BASH :)
|
4
|
+
# From: http://stackoverflow.com/questions/59895/can-a-bash-script-tell-what-directory-its-stored-in
|
5
|
+
SOURCE="${BASH_SOURCE[0]}"
|
6
|
+
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
|
7
|
+
MCLDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
8
|
+
SOURCE="$(readlink "$SOURCE")"
|
9
|
+
[[ $SOURCE != /* ]] && SOURCE="$MCLDIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
10
|
+
done
|
11
|
+
PROJECT_ROOT="$( cd -P "$( dirname "$SOURCE" )"/.. && pwd )"
|
12
|
+
|
13
|
+
# Actually run script
|
14
|
+
cd $PROJECT_ROOT && bundle exec ruby bin/watchmonkey "$@"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module WatchmonkeyCli
|
2
|
+
class Application
|
3
|
+
module Colorize
|
4
|
+
COLORMAP = {
|
5
|
+
black: 30,
|
6
|
+
red: 31,
|
7
|
+
green: 32,
|
8
|
+
yellow: 33,
|
9
|
+
blue: 34,
|
10
|
+
magenta: 35,
|
11
|
+
cyan: 36,
|
12
|
+
white: 37,
|
13
|
+
}
|
14
|
+
|
15
|
+
def colorize str, color = :yellow
|
16
|
+
ccode = COLORMAP[color.to_sym] || raise(ArgumentError, "Unknown color #{color}!")
|
17
|
+
@opts[:colorize] ? "\e[#{ccode}m#{str}\e[0m" : "#{str}"
|
18
|
+
end
|
19
|
+
alias_method :c, :colorize
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module WatchmonkeyCli
|
2
|
+
class Application
|
3
|
+
class Configuration
|
4
|
+
module AppHelper
|
5
|
+
def config_directory
|
6
|
+
"#{wm_cfg_path}/configs"
|
7
|
+
end
|
8
|
+
|
9
|
+
def config_files
|
10
|
+
Dir["#{config_directory}/**/*.rb"].reject do |file|
|
11
|
+
file.gsub(config_directory, "").split("/").any?{|fp| fp.start_with?("__") }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def config_filename name = "default"
|
16
|
+
"#{config_directory}/#{name}.rb"
|
17
|
+
end
|
18
|
+
|
19
|
+
def load_configs!
|
20
|
+
config_files.each {|f| Configuration.new(self, f) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def generate_config name = "default"
|
24
|
+
FileUtils.mkdir_p(config_directory)
|
25
|
+
File.open(config_filename(name), "w", encoding: "utf-8") do |f|
|
26
|
+
f << File.read("#{File.dirname(__FILE__)}/configuration.tpl", encoding: "utf-8")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize app, file
|
32
|
+
@app = app
|
33
|
+
@file = file
|
34
|
+
begin
|
35
|
+
eval File.read(file, encoding: "utf-8"), binding, file
|
36
|
+
rescue
|
37
|
+
app.error "Invalid config file #{file}"
|
38
|
+
raise
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def ssh_connection name, opts = {}, &b
|
43
|
+
@app.fetch_connection(:ssh, name, opts, &b)
|
44
|
+
end
|
45
|
+
|
46
|
+
def method_missing meth, *args, &block
|
47
|
+
if c = @app.checkers[meth.to_s]
|
48
|
+
c.enqueue(*args)
|
49
|
+
else
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# This is a Ruby file!
|
2
|
+
|
3
|
+
# =================================
|
4
|
+
# = Step 1: Setup SSH connections =
|
5
|
+
# =================================
|
6
|
+
|
7
|
+
# Synopsis: ssh_connection <name> <net-ssh options>
|
8
|
+
# For a list of options see:
|
9
|
+
# http://net-ssh.github.io/net-ssh/Net/SSH.html#method-c-start
|
10
|
+
# The default options are { config: false }
|
11
|
+
ssh_connection :my_server, host_name: "example.com", user: "wheel", password: "secur3"
|
12
|
+
|
13
|
+
# Key authentication (if you don't specify any keys the default locations might be used)
|
14
|
+
ssh_connection :my_server, host_name: "example.com", user: "wheel", keys_only: true, keys: ["/home/itsme/.ssh/id_rsa"]
|
15
|
+
|
16
|
+
# Therefore you might get away with just
|
17
|
+
ssh_connection :my_server, host_name: "example.com", user: "wheel"
|
18
|
+
|
19
|
+
# There are also two shortcuts you can use...
|
20
|
+
ssh_connection :my_server, "wheel@example.com" # no additional options possible
|
21
|
+
ssh_connection :my_server, host: "wheel@example.com", port: 23 # additional options possible
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
# ==========================
|
27
|
+
# = Step 2: Monitor stuff! =
|
28
|
+
# ==========================
|
29
|
+
|
30
|
+
|
31
|
+
# -----
|
32
|
+
# SSL expiration
|
33
|
+
# -----
|
34
|
+
# Check if a SSL certificate is about to expire.
|
35
|
+
# Default threshold is 1.month
|
36
|
+
ssl_expiration "https://example.com", threshold: 3.months
|
37
|
+
|
38
|
+
|
39
|
+
# -----
|
40
|
+
# WWW availability
|
41
|
+
# -----
|
42
|
+
# Check if a website is reachable and responses properly.
|
43
|
+
# Available options:
|
44
|
+
#
|
45
|
+
# status HTTP status code or array of status codes
|
46
|
+
# body String (include check) or regular expression
|
47
|
+
# headers { header => value }
|
48
|
+
# * keys are lowercased!
|
49
|
+
# * value might be string (equal check) or regular expression
|
50
|
+
#
|
51
|
+
# Note: If page is https and ssl_expiration is not false
|
52
|
+
# SSL expiration will automatically be checked.
|
53
|
+
# You can pass options by setting ssl_expiration to a Hash.
|
54
|
+
|
55
|
+
www_availability "http://example.com", status: 200, body: "<title>Example.com</title>", headers: { "content-type" => "text/html; charset=utf-8" }
|
56
|
+
|
57
|
+
# SSL expiration
|
58
|
+
www_availability "https://example.com", ssl_expiration: false
|
59
|
+
www_availability "https://example.com", ssl_expiration: { threshold: 4.weeks }
|
60
|
+
|
61
|
+
|
62
|
+
# -----
|
63
|
+
# FTP availability
|
64
|
+
# -----
|
65
|
+
# Login to an FTP account via net/ftp to check it's functionality.
|
66
|
+
# Just a port check is not enough!
|
67
|
+
ftp_availability "ftp.example.com", user: "somebody", password: "thatiusedtoknow"
|
68
|
+
|
69
|
+
|
70
|
+
# -----
|
71
|
+
# MySQL replication
|
72
|
+
# -----
|
73
|
+
# Check the health of a MySQL replication.
|
74
|
+
# Host might be :local/false/nil which will test locally (without SSH)
|
75
|
+
# Available options: user(root), password, host(127.0.0.1), port(3306), sbm_threshold(60)
|
76
|
+
# SBM refers to "Seconds Behind Master"
|
77
|
+
mysql_replication :my_server, user: "replication_user", password: "pushit"
|
78
|
+
|
79
|
+
|
80
|
+
# -----
|
81
|
+
# *nix file_exist
|
82
|
+
# -----
|
83
|
+
# Check if a file exists or not.
|
84
|
+
# Host might be :local/false/nil which will test locally (without SSH)
|
85
|
+
# You can change the default message (The file ... does not exist) with the message option.
|
86
|
+
unix_file_exists :my_server, "/etc/passwd", message: "There is no passwd, spooky!"
|
87
|
+
|
88
|
+
|
89
|
+
# -----
|
90
|
+
# *nix df
|
91
|
+
# -----
|
92
|
+
# Checks if disks are running low on free space.
|
93
|
+
# Host might be :local/false/nil which will test locally (without SSH)
|
94
|
+
# Available options: min_percent(25)
|
95
|
+
unix_df :my_server
|
96
|
+
unix_df :my_server, min_percent: 50
|
97
|
+
|
98
|
+
|
99
|
+
# -----
|
100
|
+
# *nix memory
|
101
|
+
# -----
|
102
|
+
# Checks if memory is running low.
|
103
|
+
# Host might be :local/false/nil which will test locally (without SSH)
|
104
|
+
# Available options: min_percent(25)
|
105
|
+
unix_memory :my_server
|
106
|
+
unix_memory :my_server, min_percent: 50
|
107
|
+
|
108
|
+
|
109
|
+
# -----
|
110
|
+
# *nix load
|
111
|
+
# -----
|
112
|
+
# Checks if system load is to high.
|
113
|
+
# Host might be :local/false/nil which will test locally (without SSH)
|
114
|
+
# Available options: limits([4, 2, 1.5])
|
115
|
+
unix_load :my_server
|
116
|
+
unix_load :my_server, limits: [3, 2, 1]
|
117
|
+
|
118
|
+
|
119
|
+
# -----
|
120
|
+
# *nix mdadm
|
121
|
+
# -----
|
122
|
+
# Checks if mdadm raids are intact or checking.
|
123
|
+
# Host might be :local/false/nil which will test locally (without SSH)
|
124
|
+
# Available options: log_checking(true)
|
125
|
+
unix_mdadm :my_server
|
126
|
+
unix_mdadm :my_server, log_checking: false
|
127
|
+
|
128
|
+
|
129
|
+
# -----
|
130
|
+
# *nix defaults
|
131
|
+
# -----
|
132
|
+
# Combines unix_load, unix_memory, unix_df and unix_mdadm.
|
133
|
+
# You can pass options or disable individual checkers by passing
|
134
|
+
# a hash whose keys are named after checkers.
|
135
|
+
unix_defaults :my_server
|
136
|
+
unix_defaults :my_server, unix_mdadm: false, unix_df: { min_percent: 10 }
|
137
|
+
|
138
|
+
# There are also the following shortcuts:
|
139
|
+
unix_defaults :my_server, load: [1, 2, 3] # Array(3) or false
|
140
|
+
unix_defaults :my_server, memory_min: 10 # Fixnum or false
|
141
|
+
unix_defaults :my_server, df_min: 10 # Fixnum or false
|
142
|
+
unix_defaults :my_server, mdadm: false # true or false
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module WatchmonkeyCli
|
2
|
+
class Application
|
3
|
+
module Dispatch
|
4
|
+
def dispatch action = (@opts[:dispatch] || :help)
|
5
|
+
if respond_to?("dispatch_#{action}")
|
6
|
+
send("dispatch_#{action}")
|
7
|
+
else
|
8
|
+
abort("unknown action #{action}", 1)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def dispatch_help
|
13
|
+
puts @optparse.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def dispatch_generate_config
|
17
|
+
cfg_name = @opts[:config_name] || "default"
|
18
|
+
cfg_file = config_filename(cfg_name)
|
19
|
+
puts c("Generating example config `#{cfg_name}'")
|
20
|
+
if File.exist?(cfg_file)
|
21
|
+
abort "Conflict, file already exists: #{cfg_file}", 1
|
22
|
+
else
|
23
|
+
generate_config(cfg_name)
|
24
|
+
puts c("Writing #{cfg_file}...", :green)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def dispatch_index
|
29
|
+
Thread.abort_on_exception = true
|
30
|
+
trap_signals
|
31
|
+
init_checkers!
|
32
|
+
load_configs!
|
33
|
+
dump_and_exit! if @opts[:dump]
|
34
|
+
start_checkers!
|
35
|
+
@running = true
|
36
|
+
spawn_threads_and_run!
|
37
|
+
@threads.each(&:join)
|
38
|
+
# puts config_directory
|
39
|
+
# puts config_files.inspect
|
40
|
+
ensure
|
41
|
+
@running = false
|
42
|
+
stop_checkers!
|
43
|
+
close_connections!
|
44
|
+
release_signals
|
45
|
+
end
|
46
|
+
|
47
|
+
def dispatch_info
|
48
|
+
your_version = Gem::Version.new(WatchmonkeyCli::VERSION)
|
49
|
+
puts c ""
|
50
|
+
puts c(" Your version: ", :yellow) << c("#{your_version}", :magenta)
|
51
|
+
|
52
|
+
print c(" Current version: ", :yellow)
|
53
|
+
if @opts[:check_for_updates]
|
54
|
+
require "net/http"
|
55
|
+
print c("checking...", :blue)
|
56
|
+
|
57
|
+
begin
|
58
|
+
current_version = Gem::Version.new Net::HTTP.get_response(URI.parse(WatchmonkeyCli::UPDATE_URL)).body.strip
|
59
|
+
|
60
|
+
if current_version > your_version
|
61
|
+
status = c("#{current_version} (consider update)", :red)
|
62
|
+
elsif current_version < your_version
|
63
|
+
status = c("#{current_version} (ahead, beta)", :green)
|
64
|
+
else
|
65
|
+
status = c("#{current_version} (up2date)", :green)
|
66
|
+
end
|
67
|
+
rescue
|
68
|
+
status = c("failed (#{$!.message})", :red)
|
69
|
+
end
|
70
|
+
|
71
|
+
print "#{"\b" * 11}#{" " * 11}#{"\b" * 11}" # reset line
|
72
|
+
puts status
|
73
|
+
else
|
74
|
+
puts c("check disabled", :red)
|
75
|
+
end
|
76
|
+
|
77
|
+
# more info
|
78
|
+
puts c ""
|
79
|
+
puts c " Watchmonkey CLI is brought to you by #{c "bmonkeys.net", :green}"
|
80
|
+
puts c " Contribute @ #{c "github.com/2called-chaos/watchmonkey_cli", :cyan}"
|
81
|
+
puts c " Eat bananas every day!"
|
82
|
+
puts c ""
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
module WatchmonkeyCli
|
2
|
+
class Application
|
3
|
+
attr_reader :opts, :checkers, :connections, :threads, :queue, :hooks, :processed
|
4
|
+
include Helpers
|
5
|
+
include Colorize
|
6
|
+
include Dispatch
|
7
|
+
include Configuration::AppHelper
|
8
|
+
include Checker::AppHelper
|
9
|
+
|
10
|
+
# =========
|
11
|
+
# = Setup =
|
12
|
+
# =========
|
13
|
+
def self.dispatch *a
|
14
|
+
new(*a) do |app|
|
15
|
+
app.load_config
|
16
|
+
app.parse_params
|
17
|
+
begin
|
18
|
+
app.dispatch
|
19
|
+
app.haltpoint
|
20
|
+
rescue Interrupt
|
21
|
+
app.abort("Interrupted", 1)
|
22
|
+
ensure
|
23
|
+
app.fire(:wm_shutdown)
|
24
|
+
app.debug "#{Thread.list.length} threads remain..."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize env, argv
|
30
|
+
@env, @argv = env, argv
|
31
|
+
@connections = {}
|
32
|
+
@hooks = {}
|
33
|
+
@monitor = Monitor.new
|
34
|
+
@threads = []
|
35
|
+
@queue = Queue.new
|
36
|
+
@processed = 0
|
37
|
+
@running = false
|
38
|
+
@opts = {
|
39
|
+
dump: false, # (internal) if true app will dump itself and exit before running any checks
|
40
|
+
dispatch: :index, # (internal) action to dispatch
|
41
|
+
check_for_updates: true, # -z flag
|
42
|
+
colorize: true, # -m flag
|
43
|
+
debug: false, # -d flag
|
44
|
+
threads: 10, # -t flag
|
45
|
+
loop_forever: false, # (internal) loop forever (app mode)
|
46
|
+
loop_wait_empty: 1, # (internal) time to wait in thread if queue is empty
|
47
|
+
silent: false, # -s flag
|
48
|
+
quiet: false, # -q flag
|
49
|
+
}
|
50
|
+
init_params
|
51
|
+
yield(self)
|
52
|
+
end
|
53
|
+
|
54
|
+
def init_params
|
55
|
+
@optparse = OptionParser.new do |opts|
|
56
|
+
opts.banner = "Usage: watchmonkey [options]"
|
57
|
+
|
58
|
+
opts.separator(c "# Application options", :blue)
|
59
|
+
opts.on("--generate-config [myconfig]", "Generates a example config in ~/.watchmonkey") {|s| @opts[:dispatch] = :generate_config; @opts[:config_name] = s }
|
60
|
+
opts.on("-l", "--log [file]", "Log to file, defaults to ~/.watchmonkey/logs/watchmonkey.log") {|s| @opts[:logfile] = s || logger_filename }
|
61
|
+
opts.on("-t", "--threads [NUM]", Integer, "Amount of threads to be used for checking (default: 10)") {|s| @opts[:threads] = s }
|
62
|
+
opts.on("-s", "--silent", "Only print errors and infos") { @opts[:silent] = true }
|
63
|
+
opts.on("-q", "--quiet", "Only print errors") { @opts[:quiet] = true }
|
64
|
+
|
65
|
+
opts.separator("\n" << c("# General options", :blue))
|
66
|
+
opts.on("-d", "--debug", "Enable debug output") { @opts[:debug] = true }
|
67
|
+
opts.on("-m", "--monochrome", "Don't colorize output") { @opts[:colorize] = false }
|
68
|
+
opts.on("-h", "--help", "Shows this help") { @opts[:dispatch] = :help }
|
69
|
+
opts.on("-v", "--version", "Shows version and other info") { @opts[:dispatch] = :info }
|
70
|
+
opts.on("-z", "Do not check for updates on GitHub (with -v/--version)") { @opts[:check_for_updates] = false }
|
71
|
+
opts.on("--dump-core", "for developers") { @opts[:dump] = true }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse_params
|
76
|
+
@optparse.parse!(@argv)
|
77
|
+
rescue OptionParser::ParseError => e
|
78
|
+
abort(e.message)
|
79
|
+
dispatch(:help)
|
80
|
+
exit 1
|
81
|
+
end
|
82
|
+
|
83
|
+
def running?
|
84
|
+
@running
|
85
|
+
end
|
86
|
+
|
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
|
+
def sync &block
|
140
|
+
@monitor.synchronize(&block)
|
141
|
+
end
|
142
|
+
|
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
|
+
def dump_and_exit!
|
249
|
+
puts " Queue: #{@queue.length}"
|
250
|
+
puts " AppOpts: #{@opts}"
|
251
|
+
puts "Checkers: #{@checkers.keys.join(",")}"
|
252
|
+
exit 9
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|