benotified 0.0.1
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.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
|