benotified 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +2 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +51 -0
- data/Manifest +28 -0
- data/README.md +132 -0
- data/Rakefile +24 -0
- data/benotified.gemspec +33 -0
- data/examples/monitor.rb +27 -0
- data/lib/be_notified.rb +9 -0
- data/lib/be_notified/commands.rb +13 -0
- data/lib/be_notified/commands/host_not_alive.rb +29 -0
- data/lib/be_notified/commands/number_of_files.rb +33 -0
- data/lib/be_notified/commands/program_running.rb +46 -0
- data/lib/be_notified/commands/size_of_file.rb +34 -0
- data/lib/be_notified/configuration.rb +91 -0
- data/lib/be_notified/monitor.rb +77 -0
- data/lib/be_notified/notifier.rb +104 -0
- data/lib/be_notified/version.rb +3 -0
- data/lib/benotified.rb +3 -0
- data/test/commands/test_host_not_alive.rb +28 -0
- data/test/commands/test_number_of_files.rb +46 -0
- data/test/commands/test_program_running.rb +27 -0
- data/test/commands/test_size_of_file.rb +32 -0
- data/test/notifiers/test_email_notifier.rb +52 -0
- data/test/notifiers/test_log_notifier.rb +43 -0
- data/test/test_configuration.rb +126 -0
- data/test/test_helper.rb +6 -0
- data/test/test_monitor.rb +36 -0
- data/test/test_notifier.rb +20 -0
- metadata +138 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module BeNotified
|
2
|
+
module Commands
|
3
|
+
# Extend Fixnum class and add some handy methods
|
4
|
+
#
|
5
|
+
# For example:
|
6
|
+
# size_of_file('file.log') > 4.MB
|
7
|
+
class ::Fixnum
|
8
|
+
def KB
|
9
|
+
self * 1000
|
10
|
+
end
|
11
|
+
|
12
|
+
def MB
|
13
|
+
self * 1000 * 1000
|
14
|
+
end
|
15
|
+
|
16
|
+
def GB
|
17
|
+
self * 1000 * 1000 * 1000
|
18
|
+
end
|
19
|
+
end
|
20
|
+
# Get size of the file
|
21
|
+
#
|
22
|
+
# file - The String with file name
|
23
|
+
#
|
24
|
+
# For example:
|
25
|
+
#
|
26
|
+
# size_of_file('abc.txt')
|
27
|
+
#
|
28
|
+
# Returns Integer value, size of the file
|
29
|
+
def size_of_file(file)
|
30
|
+
raise ArgumentError if ! File.exists?(file)
|
31
|
+
File.size(file)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module BeNotified
|
4
|
+
module Configuration
|
5
|
+
|
6
|
+
CONFIG_FILE = "#{ENV['HOME']}/.be_notified"
|
7
|
+
|
8
|
+
# Singleton storing configuration for this library
|
9
|
+
class << self
|
10
|
+
# Main configuration method. It merges default configuration and properties
|
11
|
+
# read from the configuration file. Then it does basic validation
|
12
|
+
#
|
13
|
+
# Returns Hash with options
|
14
|
+
def options
|
15
|
+
@options ||= begin
|
16
|
+
opts = default_options.merge!(config_file_options)
|
17
|
+
raise ArgumentError, "Invalid configuration options for emails #{opts}" if ! email_options_valid?(opts)
|
18
|
+
opts
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Set of default options
|
23
|
+
#
|
24
|
+
# Returns Hash with default options
|
25
|
+
def default_options
|
26
|
+
{
|
27
|
+
:notifier_type => BeNotified::Notifiers::Log,
|
28
|
+
:email => Hash.new { |k,h| k[h] = {}}
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
# Read configuration from the file.
|
33
|
+
#
|
34
|
+
# Returns options in JSON format if everything goes fine
|
35
|
+
# Returns empty string in case of error
|
36
|
+
def load_config_file
|
37
|
+
File.open(CONFIG_FILE).read
|
38
|
+
rescue Errno::ENOENT
|
39
|
+
""
|
40
|
+
end
|
41
|
+
|
42
|
+
# Parse options from configuration file
|
43
|
+
#
|
44
|
+
# Returns hash with options
|
45
|
+
# Returns empty hash in case of parse exception
|
46
|
+
# Returns empty hash if there were problems with reading the file
|
47
|
+
def config_file_options
|
48
|
+
# Make sure that keys are symbols
|
49
|
+
opts = load_config_file
|
50
|
+
opts == "" ? {} : JSON.parse(opts).symbolize_keys!
|
51
|
+
|
52
|
+
rescue JSON::ParserError => e
|
53
|
+
puts "Error while parsing the configuration file: #{e.message}"
|
54
|
+
{}
|
55
|
+
end
|
56
|
+
|
57
|
+
# Validate email properties
|
58
|
+
#
|
59
|
+
# opts - Hash with options
|
60
|
+
#
|
61
|
+
# Returns true if all options exists and are not empty
|
62
|
+
# Returns true if notifier_type is pointing to use emails
|
63
|
+
def email_options_valid?(opts)
|
64
|
+
# We don't really care about email validation if not using emails for notifications
|
65
|
+
return true if ! email_notifier?(opts)
|
66
|
+
|
67
|
+
opts[:email].symbolize_keys!
|
68
|
+
opts[:email].key?(:smtp_address) && opts[:email][:smtp_address] != "" &&
|
69
|
+
opts[:email].key?(:smtp_port) && opts[:email][:smtp_port] != "" &&
|
70
|
+
opts[:email].key?(:domain) && opts[:email][:domain] != "" &&
|
71
|
+
opts[:email].key?(:username) && opts[:email][:username] != "" &&
|
72
|
+
opts[:email].key?(:password) && opts[:email][:password] != "" &&
|
73
|
+
opts[:email].key?(:to) && opts[:email][:to] != "" &&
|
74
|
+
opts[:email].key?(:from) && opts[:email][:from] != "" &&
|
75
|
+
opts[:email].key?(:subject) && opts[:email][:subject] != ""
|
76
|
+
end
|
77
|
+
|
78
|
+
# When we parse configuration file, notifier_type is a String
|
79
|
+
# However, when we define them in the code, usually we use a class type
|
80
|
+
def email_notifier?(opts)
|
81
|
+
opts[:notifier_type] == BeNotified::Notifiers::Email || opts[:notifier_type] == "BeNotified::Notifiers::Email"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Easier and nicer? way for classes that include this module
|
86
|
+
# to access the configuration options
|
87
|
+
def options
|
88
|
+
Configuration.options
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'be_notified/notifier'
|
2
|
+
require 'be_notified/commands'
|
3
|
+
|
4
|
+
module BeNotified
|
5
|
+
class Monitor
|
6
|
+
# Include all available commands and configuration
|
7
|
+
include Commands
|
8
|
+
include Configuration
|
9
|
+
|
10
|
+
def initialize(&block)
|
11
|
+
# Provide a nice way of accessing methods in this class
|
12
|
+
instance_eval(&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Main method provided by the library
|
16
|
+
# Will send the message to the user when block returns true
|
17
|
+
#
|
18
|
+
# message - The String with the message send back to user
|
19
|
+
# block - Block with a THING to monitor
|
20
|
+
#
|
21
|
+
# For example:
|
22
|
+
#
|
23
|
+
# alert_when("block returns true") { true }
|
24
|
+
#
|
25
|
+
# Returns nothing.
|
26
|
+
def alert_when(message, &block)
|
27
|
+
if block.call == true
|
28
|
+
notify(message)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get notifier type form configuration
|
33
|
+
def notifier_type
|
34
|
+
options[:notifier_type]
|
35
|
+
end
|
36
|
+
|
37
|
+
# It is possible to define some configuraiton options
|
38
|
+
# directly in the code (besides configuration file)
|
39
|
+
#
|
40
|
+
# opts - The Hash with options. Currently available:
|
41
|
+
# {
|
42
|
+
# :logger_file => Location of the file where application sends log
|
43
|
+
# :notifier_type => Notifier type. Currenty available: BeNotified::Notifiers::Log, BeNotified::Notifiers::Email
|
44
|
+
# :email => {
|
45
|
+
# :smtp_address => SMTP address
|
46
|
+
# :smtp_port => SMTP port
|
47
|
+
# :domain => Domain name, eg: example.com
|
48
|
+
# :username => Login
|
49
|
+
# :password => Password
|
50
|
+
# :to => Recipient
|
51
|
+
# :from => Sender
|
52
|
+
# :subject => Subject of the message
|
53
|
+
# }
|
54
|
+
#
|
55
|
+
# For example:
|
56
|
+
# configuration({ :notifier_type => BeNotified::Notifiers::Email })
|
57
|
+
#
|
58
|
+
# Returns Hash, merged options from configuration file and defined in the class
|
59
|
+
def configuration(opts)
|
60
|
+
options.merge!(opts)
|
61
|
+
end
|
62
|
+
# Method responsible for notifing the user. Shouldn't be called directly
|
63
|
+
# It gets notifier_type, creates a proper notifier and notifies the user
|
64
|
+
#
|
65
|
+
# message - The String with the message send back to user
|
66
|
+
#
|
67
|
+
# For example:
|
68
|
+
#
|
69
|
+
# notify("too many files in this directory")
|
70
|
+
#
|
71
|
+
# Returns nothing.
|
72
|
+
def notify(message)
|
73
|
+
notifier = BeNotified::Notifier.new(notifier_type, message)
|
74
|
+
notifier.notify
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'log4r'
|
2
|
+
require 'action_mailer'
|
3
|
+
|
4
|
+
module BeNotified
|
5
|
+
# Delegate notification to other classes
|
6
|
+
class Notifier
|
7
|
+
# Initializer requires two arguments:
|
8
|
+
#
|
9
|
+
# notifier_type - String or a Class represending notifier, can be:
|
10
|
+
# BeNotified::Notifiers::Log (default)
|
11
|
+
# BeNotified::Notifiers::Email
|
12
|
+
# message - The String, message send to the user
|
13
|
+
def initialize(notifier_type, message)
|
14
|
+
@notifier = notifier_type.class === "String" ? eval("#{notifier_type}.new") : notifier_type.new
|
15
|
+
@message = message
|
16
|
+
end
|
17
|
+
|
18
|
+
# This method will actually inform the user about the problem
|
19
|
+
#
|
20
|
+
# message - The String with the message that should be passed to user
|
21
|
+
#
|
22
|
+
# For example:
|
23
|
+
# notify("Server is down")
|
24
|
+
#
|
25
|
+
# Returns nothing
|
26
|
+
def notify
|
27
|
+
@notifier.notify(@message)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
module Notifiers
|
33
|
+
|
34
|
+
# This class will send notifications by email.
|
35
|
+
# It is using ActionMailer >= 3.0.0
|
36
|
+
class Email
|
37
|
+
include BeNotified::Configuration
|
38
|
+
|
39
|
+
# Delegated method from BeNotified::Notifier class
|
40
|
+
def notify(message)
|
41
|
+
raise ArgumentError, "You can't use Email notification when Log is set in your configuration" if options[:notifier_type] == BeNotified::Notifiers::Log
|
42
|
+
Mailer.logger = Logger.new(STDOUT)
|
43
|
+
|
44
|
+
Mailer.smtp_settings = {
|
45
|
+
:address => options[:email][:smtp_address],
|
46
|
+
:port => options[:email][:smtp_port].to_i,
|
47
|
+
:domain => options[:email][:domain],
|
48
|
+
:user_name => options[:email][:username],
|
49
|
+
:password => options[:email][:password],
|
50
|
+
:authentication => 'plain',
|
51
|
+
:enable_starttls_auto => true }
|
52
|
+
|
53
|
+
Mailer.email(message, options).deliver
|
54
|
+
end
|
55
|
+
|
56
|
+
# Inner class responsible for sending emails
|
57
|
+
class Mailer < ActionMailer::Base
|
58
|
+
|
59
|
+
def email(message, options)
|
60
|
+
mail_options = {
|
61
|
+
:to => options[:email][:to],
|
62
|
+
:from => options[:email][:from],
|
63
|
+
:subject => options[:email][:subject]
|
64
|
+
}
|
65
|
+
|
66
|
+
mail(mail_options) do |format|
|
67
|
+
# Create the body of the message
|
68
|
+
format.text { render :text => "#{message}"}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# This class will notify the user by writing the message to log
|
75
|
+
# Requires Log4r in any version
|
76
|
+
class Log
|
77
|
+
include Log4r
|
78
|
+
include BeNotified::Configuration
|
79
|
+
|
80
|
+
# Delegated method from BeNotified::Notifier class
|
81
|
+
def notify(message)
|
82
|
+
logger.warn "#{message}"
|
83
|
+
end
|
84
|
+
|
85
|
+
# This method creates logger instance. Depending on configuration it will
|
86
|
+
# either log to the STDOUT or to the log file.
|
87
|
+
#
|
88
|
+
# Returns instance of logger
|
89
|
+
def logger
|
90
|
+
@logger ||= begin
|
91
|
+
|
92
|
+
logger = Logger.new 'notify_logger'
|
93
|
+
outputter = options[:logger_file].nil? ? Outputter.stdout
|
94
|
+
: FileOutputter.new('notify_logger', :filename => options[:logger_file])
|
95
|
+
|
96
|
+
logger.outputters << outputter
|
97
|
+
logger.level = Log4r::WARN
|
98
|
+
|
99
|
+
logger
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/benotified.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class TestHostNotAlive < Test::Unit::TestCase
|
5
|
+
include BeNotified::Commands
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@packets = 1
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_host_is_up
|
12
|
+
expects(:run_system).with('ping -c 1 example.com').returns(PING_SUCCESS)
|
13
|
+
assert ! host_not_alive?('example.com', @packets )
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_host_is_down_timeout
|
17
|
+
expects(:run_system).with('ping -c 1 example.com').returns(PING_FAIL)
|
18
|
+
assert host_not_alive?('example.com', @packets)
|
19
|
+
end
|
20
|
+
|
21
|
+
PING_SUCCESS =<<-END
|
22
|
+
64 bytes from example.com: icmp_seq=0 ttl=64 time=0.033 ms
|
23
|
+
END
|
24
|
+
|
25
|
+
PING_FAIL =<<-END
|
26
|
+
Request timeout for icmp_seq 0
|
27
|
+
END
|
28
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestCommands < Test::Unit::TestCase
|
4
|
+
include BeNotified::Commands
|
5
|
+
|
6
|
+
def setup
|
7
|
+
files = mock
|
8
|
+
files.stubs(:each).multiple_yields('test1.rb', 'test2.rb', 'test.txt')
|
9
|
+
|
10
|
+
Dir.stubs(:glob).returns(files)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_number_of_files_in_directory
|
14
|
+
File.stubs(:file?).returns(true)
|
15
|
+
|
16
|
+
assert_equal 3, number_of_files('.')
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_number_of_files_in_directory_with_given_mask
|
20
|
+
File.stubs(:file?).returns(true)
|
21
|
+
|
22
|
+
assert_equal(2, number_of_files('.', /\.rb$/))
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_number_of_files_in_directory_that_doesnt_exist
|
26
|
+
File.stubs(:exists?).returns(false)
|
27
|
+
|
28
|
+
assert_raise ArgumentError do
|
29
|
+
number_of_files('.')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_number_of_files_in_directory_with_mask
|
34
|
+
File.stubs(:exists?).returns(true)
|
35
|
+
|
36
|
+
assert_raise ArgumentError do
|
37
|
+
number_of_files('.', "")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_number_of_files_returns_zero_if_there_is_no_files_in_directory
|
42
|
+
File.stubs(:file?).returns(false)
|
43
|
+
|
44
|
+
assert_equal 0, number_of_files(".")
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestProgramRunning < Test::Unit::TestCase
|
4
|
+
include BeNotified::Commands
|
5
|
+
|
6
|
+
def test_program_not_running
|
7
|
+
expects(:run_system).with("ps -ef | grep -i Foobar | grep -v grep").returns("")
|
8
|
+
assert_equal true, program_not_running?('Foobar')
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_program_running
|
12
|
+
expects(:run_system).with("ps -ef | grep -i Dock | grep -v grep").returns(PS_OUT)
|
13
|
+
assert_equal true, program_running?("Dock")
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_raising_error_on_windows
|
17
|
+
expects(:running_on_windows?).returns(true)
|
18
|
+
|
19
|
+
assert_raise RuntimeError do
|
20
|
+
program_running?("Dock")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
PS_OUT =<<-END
|
25
|
+
502 101 97 0 0:08.43 ?? 0:44.19 /System/Library/CoreServices/Dock.app/Contents/MacOS/Dock
|
26
|
+
END
|
27
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestSizeOfFile < Test::Unit::TestCase
|
4
|
+
include BeNotified::Commands
|
5
|
+
|
6
|
+
def test_file_doesnt_exist
|
7
|
+
File.expects(:exists?).returns(false)
|
8
|
+
|
9
|
+
assert_raise ArgumentError do
|
10
|
+
size_of_file("test.txt")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_file_size
|
15
|
+
File.expects(:exists?).returns(true)
|
16
|
+
File.expects(:size).returns(200)
|
17
|
+
|
18
|
+
assert_equal(200, size_of_file("test.txt"))
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_file_size_in_KB
|
22
|
+
assert_equal(4_000, 4.KB)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_file_size_in_MB
|
26
|
+
assert_equal(4_000_000, 4.MB)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_file_size_in_GB
|
30
|
+
assert_equal(4_000_000_000, 4.GB)
|
31
|
+
end
|
32
|
+
end
|