opticon 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/CHANGELOG.txt +0 -0
- data/History.txt +0 -0
- data/Manifest.txt +24 -0
- data/README.txt +15 -0
- data/Rakefile +55 -0
- data/examples/sample.rb +58 -0
- data/lib/opticon.rb +19 -0
- data/lib/opticon/failure.rb +40 -0
- data/lib/opticon/http.rb +39 -0
- data/lib/opticon/mailer.rb +20 -0
- data/lib/opticon/mailer/failure_notification.rhtml +5 -0
- data/lib/opticon/notifier.rb +18 -0
- data/lib/opticon/runner.rb +4 -0
- data/lib/opticon/service.rb +67 -0
- data/lib/opticon/tester.rb +118 -0
- data/lib/opticon/version.rb +9 -0
- data/setup.rb +1585 -0
- data/test/opticon/http_test.rb +45 -0
- data/test/opticon/mailer_test.rb +26 -0
- data/test/opticon/tester_test.rb +77 -0
- data/test/opticon_test.rb +11 -0
- data/test/test_helper.rb +2 -0
- metadata +78 -0
data/CHANGELOG.txt
ADDED
File without changes
|
data/History.txt
ADDED
File without changes
|
data/Manifest.txt
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Rakefile
|
2
|
+
README.txt
|
3
|
+
CHANGELOG.txt
|
4
|
+
History.txt
|
5
|
+
Manifest.txt
|
6
|
+
README.txt
|
7
|
+
Rakefile
|
8
|
+
examples/sample.rb
|
9
|
+
lib/opticon.rb
|
10
|
+
lib/opticon/failure.rb
|
11
|
+
lib/opticon/http.rb
|
12
|
+
lib/opticon/mailer.rb
|
13
|
+
lib/opticon/mailer/failure_notification.rhtml
|
14
|
+
lib/opticon/notifier.rb
|
15
|
+
lib/opticon/runner.rb
|
16
|
+
lib/opticon/service.rb
|
17
|
+
lib/opticon/tester.rb
|
18
|
+
lib/opticon/version.rb
|
19
|
+
setup.rb
|
20
|
+
test/opticon/http_test.rb
|
21
|
+
test/opticon/mailer_test.rb
|
22
|
+
test/opticon/tester_test.rb
|
23
|
+
test/opticon_test.rb
|
24
|
+
test/test_helper.rb
|
data/README.txt
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Opticon is a no-nonsense utility for monitoring HTTP-based services (websites).
|
2
|
+
|
3
|
+
When triggered, opticon connects to a list of URIs and checks to make sure that
|
4
|
+
they pass some arbitrary test, such as responding with a non-error HTTP response
|
5
|
+
code, or returning content that matches a regular expression. In case of failure,
|
6
|
+
opticon generates emails that can be forwarded to your mailbox, blackberry,
|
7
|
+
cellphone, etc.
|
8
|
+
|
9
|
+
|
10
|
+
For installation instructions and other info, visit opticon's Google Code
|
11
|
+
page at:
|
12
|
+
|
13
|
+
http://code.google.com/p/opticon/
|
14
|
+
|
15
|
+
(Have a look at the Wiki in particular)
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/contrib/rubyforgepublisher'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'hoe'
|
11
|
+
include FileUtils
|
12
|
+
require File.join(File.dirname(__FILE__), 'lib', 'opticon', 'version')
|
13
|
+
|
14
|
+
AUTHOR = "Matt Zukowski" # can also be an array of Authors
|
15
|
+
EMAIL = "matt [at] roughest.net"
|
16
|
+
DESCRIPTION = "Peace of mind through automated monitoring of your HTTP services."
|
17
|
+
GEM_NAME = "opticon" # what ppl will type to install your gem
|
18
|
+
RUBYFORGE_PROJECT = "opticon" # The unix name for your project
|
19
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
20
|
+
DEPS = ['actionmailer']
|
21
|
+
|
22
|
+
NAME = "opticon"
|
23
|
+
REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
|
24
|
+
VERS = ENV['VERSION'] || (Opticon::VERSION::STRING + (REV ? ".#{REV}" : ""))
|
25
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
26
|
+
RDOC_OPTS = ['--quiet', '--title', "opticon documentation",
|
27
|
+
"--opname", "index.html",
|
28
|
+
"--line-numbers",
|
29
|
+
"--main", "README",
|
30
|
+
"--inline-source"]
|
31
|
+
|
32
|
+
class Hoe
|
33
|
+
def extra_deps
|
34
|
+
@extra_deps.reject { |x| Array(x).first == 'hoe' }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Generate all the Rake tasks
|
39
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
40
|
+
hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
41
|
+
p.author = AUTHOR
|
42
|
+
p.description = DESCRIPTION
|
43
|
+
p.email = EMAIL
|
44
|
+
p.summary = DESCRIPTION
|
45
|
+
p.url = HOMEPATH
|
46
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
47
|
+
p.test_globs = ["test/**/*_test.rb"]
|
48
|
+
p.clean_globs = CLEAN #An array of file patterns to delete on clean.
|
49
|
+
p.extra_deps = DEPS
|
50
|
+
|
51
|
+
# == Optional
|
52
|
+
#p.changes - A description of the release's latest changes.
|
53
|
+
#p.extra_deps - An array of rubygem dependencies.
|
54
|
+
#p.spec_extras - A hash of extra values to set in the gemspec.
|
55
|
+
end
|
data/examples/sample.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'; gem 'opticon'; require 'opticon'
|
3
|
+
|
4
|
+
# Where notifications will be emailed
|
5
|
+
SEND_TO = "sample@nowhere.foo"
|
6
|
+
|
7
|
+
# If you want to send to multiple mailboxes, you can specify an array of
|
8
|
+
# addresses like so:
|
9
|
+
#
|
10
|
+
# SEND_TO = ['robert@nowhere.foo', 'sally@nowhere.foo']
|
11
|
+
|
12
|
+
# Email notifications will show up as coming from this address.
|
13
|
+
FROM = "opticon@nowhere.foo"
|
14
|
+
|
15
|
+
# You may have to set the FROM value to be a real email address (e.g. your
|
16
|
+
# email address). Otherwise your mail server may decide to drop Opticon's
|
17
|
+
# notification messages.
|
18
|
+
|
19
|
+
# You don't have to change anything on this next line. This configures the
|
20
|
+
# default notification behaviour for Opticon, which is to email failures to the
|
21
|
+
# addresses you configured above.
|
22
|
+
Opticon.default_notifiers = Opticon::Notifier::Email.new(SEND_TO, :from => FROM)
|
23
|
+
|
24
|
+
# You may need to configure the Mailer with your mail server info. Here's how to
|
25
|
+
# use an SMTP mail server (unless you do this, local sendmail will be used used
|
26
|
+
# by default):
|
27
|
+
#
|
28
|
+
# Opticon::Mailer.smtp_settings = {
|
29
|
+
# :address => 'mail.nowhere.foo',
|
30
|
+
# :username => 'johnny',
|
31
|
+
# :password => 'topsecret'
|
32
|
+
# }
|
33
|
+
#
|
34
|
+
# See http://api.rubyonrails.com/classes/ActionMailer/Base.html for more details
|
35
|
+
# on setting up the mailer.
|
36
|
+
|
37
|
+
# Now you can set up some tests. Note that this is just plain Ruby code, formatted
|
38
|
+
# a bit strangely (note the dot at the end of each line -- we are calling the test()
|
39
|
+
# method on the URI string, and then on the result of each successive test).
|
40
|
+
# Here are some examples:
|
41
|
+
|
42
|
+
"http://www.yahoo.com/".
|
43
|
+
# check that the HTTP response code is in the 200 range
|
44
|
+
test(:responds_with_code, :success).
|
45
|
+
# check that the page includes the string "Privacy Policy"
|
46
|
+
test(:response_body_contains, "Privacy Policy")
|
47
|
+
|
48
|
+
"http://google.com".
|
49
|
+
# Google will try to redirect to http://www.google.com/
|
50
|
+
test(:responds_with_code, 302)
|
51
|
+
# Note that currently there is no test for checking where you are being
|
52
|
+
# redirected to. This functionality will be added in the future.
|
53
|
+
|
54
|
+
|
55
|
+
# For a comprehensive guide on configuring and using Opticon, please see:
|
56
|
+
#
|
57
|
+
# http://code.google.com/p/opticon/wiki/ConfiguringOpticon
|
58
|
+
#
|
data/lib/opticon.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
module Opticon
|
5
|
+
|
6
|
+
@@default_notifiers = nil
|
7
|
+
def default_notifiers
|
8
|
+
@@default_notifiers
|
9
|
+
end
|
10
|
+
def default_notifiers=(notifiers)
|
11
|
+
notifiers = [notifiers] unless notifiers.kind_of? Array
|
12
|
+
@@default_notifiers = notifiers
|
13
|
+
end
|
14
|
+
module_function :default_notifiers, 'default_notifiers='
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'opticon/service'
|
19
|
+
require 'opticon/notifier'
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Opticon
|
2
|
+
module Failure
|
3
|
+
class Base
|
4
|
+
attr_reader :uri, :condition, :response
|
5
|
+
attr_accessor :exception
|
6
|
+
|
7
|
+
def initialize(uri, condition, response)
|
8
|
+
@uri = uri
|
9
|
+
@condition = condition
|
10
|
+
@response = response
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"#<#{self.class} [#{uri}] #{failure_message}>"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class ConnectionFailure < Base
|
19
|
+
def failure_message
|
20
|
+
"#{exception.message} (#{exception.class})"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class ResponseCodeTestFailure < Base
|
25
|
+
def failure_message
|
26
|
+
"Expected response code #{condition.inspect} but got '#{response.message}' (#{response.code})"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class ContentTestFailure < Base
|
31
|
+
def failure_message
|
32
|
+
if exception
|
33
|
+
return exception.message
|
34
|
+
else
|
35
|
+
"Content did not include the expected #{condition.class} #{condition.inspect}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/opticon/http.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Opticon
|
6
|
+
module HTTP
|
7
|
+
def response
|
8
|
+
raise "URI cannot be empty" if uri.kind_of? String and (uri.nil? or uri.empty?)
|
9
|
+
|
10
|
+
self.uri = URI.parse(self.uri) unless self.uri.kind_of? URI
|
11
|
+
if @response and self.uri == @last_fetched_uri
|
12
|
+
@response
|
13
|
+
else
|
14
|
+
@response = get(self.uri)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def get(get_uri)
|
19
|
+
if self.uri.kind_of? URI
|
20
|
+
get_uri = self.uri
|
21
|
+
else
|
22
|
+
get_uri = URI.parse(self.uri) unless self.uri.kind_of? URI
|
23
|
+
end
|
24
|
+
|
25
|
+
@last_fetched_uri = get_uri
|
26
|
+
|
27
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
28
|
+
http.use_ssl = uri.scheme == "https"
|
29
|
+
|
30
|
+
response = nil
|
31
|
+
http.start do
|
32
|
+
path = uri.path.empty? ? '/' : uri.path
|
33
|
+
response = http.request_get(path)
|
34
|
+
end
|
35
|
+
|
36
|
+
response
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
gem 'actionmailer'
|
4
|
+
require 'action_mailer'
|
5
|
+
|
6
|
+
module Opticon
|
7
|
+
class Mailer < ::ActionMailer::Base
|
8
|
+
# we use sendmail by default since it might just work out of the box
|
9
|
+
# (:smtp, which is the normal default, definetly won't work without manual configuration)
|
10
|
+
self.delivery_method = :sendmail
|
11
|
+
self.template_root = File.dirname(File.expand_path(__FILE__))+'/..'
|
12
|
+
|
13
|
+
def failure_notification(failure, recipients, from)
|
14
|
+
self.recipients recipients
|
15
|
+
self.from from
|
16
|
+
subject "HTTP Service Failure: #{failure.uri}"
|
17
|
+
body :failure => failure
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'opticon/mailer'
|
2
|
+
|
3
|
+
module Opticon
|
4
|
+
module Notifier
|
5
|
+
class Email
|
6
|
+
attr_accessor :recipients, :from
|
7
|
+
|
8
|
+
def initialize(recipients, options = {:from => "opticon@#{ENV['HOSTNAME']}"})
|
9
|
+
@recipients = recipients
|
10
|
+
@from = options[:from]
|
11
|
+
end
|
12
|
+
|
13
|
+
def notify(failure)
|
14
|
+
Opticon::Mailer.deliver_failure_notification(failure, recipients, from)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'opticon/tester'
|
3
|
+
|
4
|
+
module Opticon
|
5
|
+
class Service
|
6
|
+
attr_accessor :notifiers
|
7
|
+
|
8
|
+
def initialize(uri, notifiers = Opticon.default_notifiers)
|
9
|
+
raise ArgumentError, "You must either provide a valid Notifier or set Opticon.default_notifiers." unless
|
10
|
+
notifiers and (notifiers.respond_to? :notify or notifiers.first.respond_to? :notify)
|
11
|
+
@uri = URI.parse(uri)
|
12
|
+
|
13
|
+
if notifiers.kind_of? Array
|
14
|
+
@notifiers = notifiers
|
15
|
+
else
|
16
|
+
@notifiers = [notifiers]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
@uri.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def test(tester, *args)
|
25
|
+
case tester
|
26
|
+
when :responds_with_code, 'responds_with_code'
|
27
|
+
tester = Opticon::Tester::ResponseCodeTester.new
|
28
|
+
when :response_body_contains, 'response_body_contains'
|
29
|
+
tester = Opticon::Tester::ContentTester.new
|
30
|
+
when Class
|
31
|
+
tester = tester.new
|
32
|
+
when Tester::Base
|
33
|
+
tester = tester
|
34
|
+
else
|
35
|
+
begin
|
36
|
+
tester_class = "Opitcon::Tester::#{tester.to_s}".constantize
|
37
|
+
tester = tester_class.new
|
38
|
+
rescue NameError
|
39
|
+
bad_tester = true
|
40
|
+
end
|
41
|
+
|
42
|
+
if bad_tester or !tester.respond_to? :test
|
43
|
+
raise ArgumentError, "'#{tester}' is not a valid tester. The parameter must be: 'responds_with_code', 'response_body_contains', or"+
|
44
|
+
" an Object or Class that responds to a 'test' method."
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
tester.uri = @uri
|
49
|
+
|
50
|
+
@failures = []
|
51
|
+
unless tester.test(*args)
|
52
|
+
notifiers.each {|n| n.notify(tester.failure)}
|
53
|
+
@failures << tester.failure
|
54
|
+
end
|
55
|
+
|
56
|
+
# return self to allow for test chaining
|
57
|
+
return self
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# this makes it possible to treat strings as Service objects
|
63
|
+
class String
|
64
|
+
def test(tester, *args)
|
65
|
+
Opticon::Service.new(to_s).test(tester, *args)
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'opticon/http'
|
2
|
+
require 'opticon/failure'
|
3
|
+
|
4
|
+
module Opticon
|
5
|
+
module Tester
|
6
|
+
class Base
|
7
|
+
attr_accessor :uri
|
8
|
+
attr_reader :failure
|
9
|
+
|
10
|
+
include Opticon::HTTP
|
11
|
+
|
12
|
+
def test(*args)
|
13
|
+
begin
|
14
|
+
test_without_exception_handling(*args)
|
15
|
+
rescue SocketError, TimeoutError, Net::HTTPError, Errno::ECONNREFUSED
|
16
|
+
@failure = Opticon::Failure::ConnectionFailure.new(uri, args, nil)
|
17
|
+
@failure.exception = $!
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class ResponseCodeTester < Base
|
24
|
+
def test_without_exception_handling(condition)
|
25
|
+
case condition
|
26
|
+
when :ok
|
27
|
+
responds_with_code(200)
|
28
|
+
when :success
|
29
|
+
responds_with_success
|
30
|
+
when :failure, :error
|
31
|
+
responds_with_error
|
32
|
+
when :redirect
|
33
|
+
responds_with_redirect
|
34
|
+
when :client_error, :bad_request
|
35
|
+
responds_with_client_error
|
36
|
+
when :server_error
|
37
|
+
responds_with_server_error
|
38
|
+
when Integer, Array, Range
|
39
|
+
# TODO: if array, assert that each element is an integer (an HTTP response code)
|
40
|
+
responds_with_code(condition)
|
41
|
+
when String, Regexp
|
42
|
+
response_contains(condition)
|
43
|
+
else
|
44
|
+
raise ArgumentError, "'#{condition.inspect}' is not a valid argument for responds_with"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def responds_with_code(codes)
|
50
|
+
|
51
|
+
if codes.kind_of? Integer
|
52
|
+
result = codes == response.code.to_i
|
53
|
+
else
|
54
|
+
result = codes.include? response.code.to_i
|
55
|
+
end
|
56
|
+
|
57
|
+
@failure = Opticon::Failure::ResponseCodeTestFailure.new(uri, codes, response) unless result
|
58
|
+
return result
|
59
|
+
end
|
60
|
+
|
61
|
+
def responds_with_success
|
62
|
+
responds_with_code(200..206)
|
63
|
+
end
|
64
|
+
|
65
|
+
def responds_with_redirect
|
66
|
+
responds_with_code(300..207)
|
67
|
+
end
|
68
|
+
|
69
|
+
def responds_with_client_error
|
70
|
+
responds_with_code(400..417)
|
71
|
+
end
|
72
|
+
|
73
|
+
def responds_with_server_error
|
74
|
+
responds_with_code(500..505)
|
75
|
+
end
|
76
|
+
|
77
|
+
def responds_with_error
|
78
|
+
responds_with_client_error or responds_with_client_error
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class ContentTester < Base
|
83
|
+
def test_without_exception_handling(condition)
|
84
|
+
unless (200..206).include?(response.code.to_i)
|
85
|
+
@failure = Opticon::Failure::ContentTestFailure.new(uri, condition, response)
|
86
|
+
if (300..307).include?(response.code.to_i)
|
87
|
+
@failure.exception = RuntimeError.new("Request was redirected to #{response['location']}.")
|
88
|
+
else
|
89
|
+
@failure.exception = RuntimeError.new("Request returned code '#{response.message}'"+
|
90
|
+
" (#{response.code}) but response must be a 2xx HTTP status code.")
|
91
|
+
end
|
92
|
+
return false
|
93
|
+
end
|
94
|
+
|
95
|
+
unless response.class.body_permitted?
|
96
|
+
@failure = Opticon::Failure::ContentTestFailure.new(uri, condition, response)
|
97
|
+
@failure.exception = RuntimeError.new("Response did not include a body (HTTP response: #{response.inspect})")
|
98
|
+
return false
|
99
|
+
end
|
100
|
+
|
101
|
+
case condition
|
102
|
+
when Regexp
|
103
|
+
result = condition =~ response.body
|
104
|
+
when String
|
105
|
+
result = response.body.include? condition
|
106
|
+
end
|
107
|
+
|
108
|
+
if result
|
109
|
+
@failure = nil
|
110
|
+
else
|
111
|
+
@failure = Opticon::Failure::ContentTestFailure.new(uri, condition, response)
|
112
|
+
end
|
113
|
+
|
114
|
+
result
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|