outpost 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/CHANGELOG.rdoc +16 -0
- data/Gemfile +9 -3
- data/Gemfile.lock +39 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +40 -8
- data/Rakefile +14 -0
- data/TODO.md +7 -0
- data/lib/outpost.rb +1 -1
- data/lib/outpost/application.rb +143 -0
- data/lib/outpost/expectations/response_body.rb +23 -9
- data/lib/outpost/expectations/response_code.rb +5 -0
- data/lib/outpost/expectations/response_time.rb +10 -1
- data/lib/outpost/notifiers.rb +2 -0
- data/lib/outpost/notifiers/campfire.rb +50 -0
- data/lib/outpost/notifiers/email.rb +59 -0
- data/lib/outpost/report.rb +7 -1
- data/lib/outpost/scout.rb +82 -0
- data/lib/outpost/scout_config.rb +10 -4
- data/lib/outpost/scouts/http.rb +28 -7
- data/lib/outpost/scouts/ping.rb +26 -6
- data/lib/outpost/version.rb +4 -1
- data/outpost.gemspec +0 -2
- data/test/integration/{basic_dsl_test.rb → basic_application_test.rb} +5 -5
- data/test/integration/more_complex_test.rb +20 -8
- data/test/integration/notifiers_test.rb +51 -0
- data/test/outpost/dsl_test.rb +74 -3
- data/test/outpost/expectations/response_body_test.rb +13 -15
- data/test/outpost/expectations/response_code_test.rb +5 -7
- data/test/outpost/expectations/response_time_test.rb +9 -11
- data/test/outpost/notifiers/campfire_test.rb +72 -0
- data/test/outpost/notifiers/email_test.rb +90 -0
- data/test/outpost/scout_test.rb +5 -6
- data/test/outpost/scouts/http_test.rb +51 -0
- data/test/outpost/scouts/ping_test.rb +41 -0
- data/test/support/nothing_raised.rb +10 -0
- data/test/support/server.rb +0 -1
- data/test/support/stubs.rb +11 -0
- data/test/test_helper.rb +8 -6
- metadata +32 -22
- data/lib/outpost/dsl.rb +0 -54
@@ -1,10 +1,15 @@
|
|
1
1
|
module Outpost
|
2
2
|
module Expectations
|
3
|
+
# Module containing response_code logic. It is the simplest of all,
|
4
|
+
# it is a simple direct equality check. Extend your Scout to win instant
|
5
|
+
# response_code checking.
|
3
6
|
module ResponseCode
|
7
|
+
# Installs the response code expectation
|
4
8
|
def self.extended(base)
|
5
9
|
base.expect :response_code, base.method(:evaluate_response_code)
|
6
10
|
end
|
7
11
|
|
12
|
+
# Method that will be used as an expectation to evaluate response code
|
8
13
|
def evaluate_response_code(scout, response_code)
|
9
14
|
scout.response_code == response_code.to_i
|
10
15
|
end
|
@@ -1,17 +1,26 @@
|
|
1
1
|
module Outpost
|
2
2
|
module Expectations
|
3
|
+
# Module containing response_time matching expectations. Extend your Scout
|
4
|
+
# with ResponseTime and it will evaluate timely expressions, all in
|
5
|
+
# milisseconds.
|
6
|
+
#
|
7
|
+
# It respond to the following rules:
|
8
|
+
# * less_than => If the response time is less than the associated number
|
9
|
+
# * more_than => If the response time is below than the associated number
|
3
10
|
module ResponseTime
|
4
11
|
RESPONSE_TIME_MAPPING = {
|
5
12
|
:less_than => "<",
|
6
13
|
:more_than => ">",
|
7
14
|
}.freeze
|
8
15
|
|
16
|
+
# Installs the response time expectation
|
9
17
|
def self.extended(base)
|
10
18
|
base.expect :response_time, base.method(:evaluate_response_time)
|
11
19
|
end
|
12
20
|
|
21
|
+
# Method that will be used as an expectation to evaluate response time
|
13
22
|
def evaluate_response_time(scout, rules)
|
14
|
-
rules.all? do |rule,comparison|
|
23
|
+
rules.all? do |rule, comparison|
|
15
24
|
scout.response_time.send(RESPONSE_TIME_MAPPING[rule], comparison)
|
16
25
|
end
|
17
26
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
begin
|
2
|
+
require 'tinder'
|
3
|
+
rescue LoadError => e
|
4
|
+
puts "Please install tinder gem: gem install tinder"
|
5
|
+
raise
|
6
|
+
end
|
7
|
+
|
8
|
+
module Outpost
|
9
|
+
module Notifiers
|
10
|
+
# The Campfire notifier issues Outpost notifications to the 37signals'
|
11
|
+
# Campfire web app (http://campfirenow.com). It issues messages about
|
12
|
+
# the system status in the specified subdomain and room.
|
13
|
+
#
|
14
|
+
# This requires the 'tinder' gem to be installed.
|
15
|
+
class Campfire
|
16
|
+
|
17
|
+
# @param [Hash] Options to create a campfire notification.
|
18
|
+
# @option options [String] :subdomain The subdomain of your campfire
|
19
|
+
# rooms
|
20
|
+
# @option options [String] :token An access token, can be found
|
21
|
+
# in your Account info
|
22
|
+
# @option options [String] :room The room notifications will be sent to
|
23
|
+
# @option options [Class] :campfire_notifier Another Campfire
|
24
|
+
# notification class. Defaults to Tinder's gem
|
25
|
+
def initialize(options={})
|
26
|
+
@subdomain = options[:subdomain] || ''
|
27
|
+
@token = options[:token] || ''
|
28
|
+
@room = options[:room] || ''
|
29
|
+
@campfire_notifier = options[:campfire_notifier] || Tinder::Campfire
|
30
|
+
|
31
|
+
if [@subdomain, @token, @room].any?(&:empty?)
|
32
|
+
raise ArgumentError, 'You need to supply :token, :subdomain and :room.'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Issues a notification to a Campfire room. This is a callback, called by
|
37
|
+
# an Outpost instance.
|
38
|
+
# @param [Outpost::Application, #read] outpost an instance of an outpost, containing
|
39
|
+
# latest status, messages and reports that can be queried to build
|
40
|
+
# a notification message.
|
41
|
+
def notify(outpost)
|
42
|
+
campfire = @campfire_notifier.new @subdomain, :token => @token
|
43
|
+
room = campfire.find_room_by_name @room
|
44
|
+
|
45
|
+
status = outpost.last_status.to_s
|
46
|
+
room.speak "System is #{status}: #{outpost.messages.join(',')}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
begin
|
2
|
+
require 'mail'
|
3
|
+
rescue LoadError => e
|
4
|
+
puts "Please install mail gem: gem install mail"
|
5
|
+
raise
|
6
|
+
end
|
7
|
+
|
8
|
+
module Outpost
|
9
|
+
module Notifiers
|
10
|
+
# The Email notifier issues Outpost notifications to through email. It
|
11
|
+
# uses the 'mail' gem send the emails. You can see mail's documentation
|
12
|
+
# in order to change how emails will be delivered:
|
13
|
+
# https://github.com/mikel/mail
|
14
|
+
#
|
15
|
+
# This requires the 'mail' gem to be installed.
|
16
|
+
class Email
|
17
|
+
|
18
|
+
# @param [Hash] Options to create an email notification.
|
19
|
+
# @option options [String] :from The "from" email field
|
20
|
+
# @option options [String] :to Where e-mails will be delivered
|
21
|
+
# @option options [String] :subject The email's subject
|
22
|
+
def initialize(options={})
|
23
|
+
@from = options[:from]
|
24
|
+
@to = options[:to]
|
25
|
+
@subject = options[:subject] || 'Outpost notification'
|
26
|
+
|
27
|
+
unless @from && @to
|
28
|
+
raise ArgumentError, 'You need to set :from and :to to send emails.'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Issues a notification through email. This is a callback, called by
|
33
|
+
# an Outpost instance.
|
34
|
+
# @param [Outpost::Application, #read] outpost an instance of an outpost, containing
|
35
|
+
# latest status, messages and reports that can be queried to build
|
36
|
+
# a notification message.
|
37
|
+
def notify(outpost)
|
38
|
+
mail = Mail.new
|
39
|
+
mail.from = @from
|
40
|
+
mail.to = @to
|
41
|
+
mail.subject = @subject
|
42
|
+
mail.body = build_message(outpost)
|
43
|
+
|
44
|
+
mail.deliver
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def build_message(outpost)
|
49
|
+
status = outpost.last_status.to_s
|
50
|
+
|
51
|
+
message = "This is the report for #{outpost.name}: "
|
52
|
+
message += "System is #{status.upcase}!\n\n"
|
53
|
+
|
54
|
+
message += outpost.messages.join("\n")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
data/lib/outpost/report.rb
CHANGED
@@ -1,13 +1,19 @@
|
|
1
1
|
module Outpost
|
2
|
+
# Contain the status report of an Outpost execution. Holds the name,
|
3
|
+
# description and status of the reported item.
|
2
4
|
class Report
|
3
|
-
#
|
5
|
+
# Summarizes the list of statuses in a single status only.
|
4
6
|
# The logic is rather simple - it will return the lowest status
|
5
7
|
# present in the list.
|
6
8
|
#
|
7
9
|
# Examples:
|
8
10
|
#
|
9
11
|
# if passed [:up, :up, :up], will result on :up
|
12
|
+
#
|
10
13
|
# if passed [:up, :down, :up], will result on :down
|
14
|
+
#
|
15
|
+
# @params [Array] status_list a list of statuses to be analyzed
|
16
|
+
# @return [Symbol] the final status to be considered.
|
11
17
|
def self.summarize(status_list)
|
12
18
|
return :down if status_list.empty? || status_list.include?(:down)
|
13
19
|
return :up
|
data/lib/outpost/scout.rb
CHANGED
@@ -1,11 +1,69 @@
|
|
1
1
|
module Outpost
|
2
|
+
# Scouts are responsible for gathering data about a something specific that is
|
3
|
+
# a part of your system. For example, a HTTP Scout is responsible for getting
|
4
|
+
# the response code (200 for OK, 404 for Page Not Found, etc.), response
|
5
|
+
# body and any other thing you might want to know.
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# Scouts must also contain expectations. They must be registered into the
|
9
|
+
# Scout that wish to support different types of expectations. You can supply
|
10
|
+
# a block or an object that respond to #call and return true if any of the
|
11
|
+
# rules match. It will receive an instance of the scout (so you can query
|
12
|
+
# current system state) as the first parameter and the state defined by the
|
13
|
+
# Outpost as the second.
|
14
|
+
#
|
15
|
+
#
|
16
|
+
# Example:
|
17
|
+
# expect(:response_code) do |scout, code|
|
18
|
+
# scout.response_code == code
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# @example an example of a Scout that parses the HTTP response code
|
22
|
+
# module Outpost
|
23
|
+
# module Scouts
|
24
|
+
# class Http < Outpost::Scout
|
25
|
+
# expect(:response_code) { |scout,code| scout.response_code == code }
|
26
|
+
#
|
27
|
+
# attr_reader :response_code
|
28
|
+
#
|
29
|
+
# def setup(options)
|
30
|
+
# @host = options[:host]
|
31
|
+
# @port = options[:port] || 80
|
32
|
+
# @path = options[:path] || '/'
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# def execute
|
36
|
+
# response = Net::HTTP.get_response(@host, @path, @port)
|
37
|
+
# @response_code = response.code.to_i
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# @abstract Subclasses must override {#setup} and {#execute} to be a valid
|
44
|
+
# Scout
|
2
45
|
class Scout
|
3
46
|
class << self
|
4
47
|
|
48
|
+
# Returns the hash of expectations, where the key is the name of the
|
49
|
+
# expectation and the value is the callable Object (object that responds
|
50
|
+
# to #call)
|
51
|
+
#
|
52
|
+
# @return [Hash<Symbol, Object>]
|
5
53
|
def expectations
|
6
54
|
@expectations ? @expectations.dup : []
|
7
55
|
end
|
8
56
|
|
57
|
+
# Registers a new expectation into the Scout. If the callable does not
|
58
|
+
# respond to #call, an ArgumentError will be raised.
|
59
|
+
#
|
60
|
+
# @param [Symbol, #read] expectation The name of the expectation
|
61
|
+
# (examples: :response_code, :response_time)
|
62
|
+
# @param [Object, #read] callable An object that responds to call and
|
63
|
+
# returns a Boolean. It will be executed whenever a Scout is run.
|
64
|
+
# A block is also accepted.
|
65
|
+
# @raise [ArgumentError] raised when the callable parameter does not
|
66
|
+
# respond to #call method.
|
9
67
|
def expect(expectation, callable=nil, &callable_block)
|
10
68
|
callable ||= callable_block
|
11
69
|
|
@@ -18,6 +76,10 @@ module Outpost
|
|
18
76
|
end
|
19
77
|
end
|
20
78
|
|
79
|
+
# @param [String, #read] description A string containing a description of
|
80
|
+
# the Scout so it may be identified in the construction of status reports.
|
81
|
+
# @param [Hash, #read] config A hash containing any number of
|
82
|
+
# configurations that will be passed to the #setup method
|
21
83
|
def initialize(description, config)
|
22
84
|
@description = description
|
23
85
|
@config = config
|
@@ -25,9 +87,21 @@ module Outpost
|
|
25
87
|
setup(config.options)
|
26
88
|
end
|
27
89
|
|
90
|
+
# Executes the Scout and go through all the registered expectations to find
|
91
|
+
# out all expectations that match and return the associated status.
|
92
|
+
#
|
93
|
+
# @return [Symbol] the current status of the Scout (:up, :down)
|
94
|
+
# @raise [NotImplementedError] raised when a configured expectation was not
|
95
|
+
# registered in the Scout.
|
28
96
|
def run
|
29
97
|
statuses = []
|
30
98
|
execute
|
99
|
+
# Response_pair contains the expectation as key and the expected value as
|
100
|
+
# value.
|
101
|
+
# Example: {:response_time => 200}
|
102
|
+
#
|
103
|
+
# status is the status (:up or :down, for example) that will be returned
|
104
|
+
# in case the expectation match current system status.
|
31
105
|
@config.reports.each do |response_pair, status|
|
32
106
|
response_pair.each do |expectation, value|
|
33
107
|
if self.class.expectations[expectation].nil?
|
@@ -44,10 +118,18 @@ module Outpost
|
|
44
118
|
Report.summarize(statuses)
|
45
119
|
end
|
46
120
|
|
121
|
+
# Called when the scout object is being constructed. Arguments can be
|
122
|
+
# everything the developer set in the creation of Outpost.
|
123
|
+
#
|
124
|
+
# @raise [NotImplementedError] raised when method is not overriden.
|
47
125
|
def setup(*args)
|
48
126
|
raise NotImplementedError, 'You must implement the setup method for Scout to work correctly.'
|
49
127
|
end
|
50
128
|
|
129
|
+
# Called when the Scout must take action and gather all the data needed to
|
130
|
+
# be analyzed.
|
131
|
+
#
|
132
|
+
# @raise [NotImplementedError] raised when method is not overriden.
|
51
133
|
def execute(*args)
|
52
134
|
raise NotImplementedError, 'You must implement the execute method for Scout to work correctly.'
|
53
135
|
end
|
data/lib/outpost/scout_config.rb
CHANGED
@@ -1,9 +1,16 @@
|
|
1
1
|
module Outpost
|
2
|
+
# Provides the DSL used to configure Scouts in Outposts.
|
2
3
|
class ScoutConfig
|
3
4
|
attr_reader :options, :reports
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
def initialize
|
7
|
+
@reports = {}
|
8
|
+
@options = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
# Reads/writes any options. It will passed down to the Scout.
|
12
|
+
# @param [Object] args Any argument that will be passed to the Scout.
|
13
|
+
# Rreturn [Object] The associated option
|
7
14
|
def options(args=nil)
|
8
15
|
if args.nil?
|
9
16
|
@options
|
@@ -21,10 +28,9 @@ module Outpost
|
|
21
28
|
# status = 200
|
22
29
|
# params = {:response_code => 200}
|
23
30
|
#
|
24
|
-
#
|
31
|
+
# gets stored as:
|
25
32
|
# {:response_code => 200} = up
|
26
33
|
def report(status, params)
|
27
|
-
@reports ||= {}
|
28
34
|
@reports[params] = status
|
29
35
|
end
|
30
36
|
end
|
data/lib/outpost/scouts/http.rb
CHANGED
@@ -1,27 +1,48 @@
|
|
1
1
|
require 'net/http'
|
2
|
-
require 'outpost'
|
3
|
-
|
4
2
|
require 'outpost/expectations'
|
5
3
|
|
6
4
|
module Outpost
|
7
5
|
module Scouts
|
6
|
+
# Uses ruby's own Net:HTTP to send HTTP requests and evaluate response
|
7
|
+
# body, response time and response code.
|
8
|
+
#
|
9
|
+
# * Responds to response_time expectation
|
10
|
+
# ({Outpost::Expectations::ResponseTime})
|
11
|
+
# * Responds to response_code expectation
|
12
|
+
# ({Outpost::Expectations::ResponseCode})
|
13
|
+
# * Responds to response_body expectation
|
14
|
+
# ({Outpost::Expectations::ResponseBody})
|
15
|
+
#
|
8
16
|
class Http < Outpost::Scout
|
9
17
|
extend Outpost::Expectations::ResponseCode
|
10
18
|
extend Outpost::Expectations::ResponseBody
|
11
19
|
|
12
20
|
attr_reader :response_code, :response_body
|
13
21
|
|
22
|
+
# Configure the scout with given options.
|
23
|
+
# @param [Hash] Options to setup the scout
|
24
|
+
# @option options [String] :host The host that will be connected to.
|
25
|
+
# @option options [Number] :port The port that will be used to.
|
26
|
+
# @option options [String] :path The path that will be fetched from the
|
27
|
+
# host.
|
28
|
+
# @option options [String] :http_class The class that will be used to
|
29
|
+
# fetch the page, defaults to Net::HTTP
|
14
30
|
def setup(options)
|
15
|
-
@host
|
16
|
-
@port
|
17
|
-
@path
|
31
|
+
@host = options[:host]
|
32
|
+
@port = options[:port] || 80
|
33
|
+
@path = options[:path] || '/'
|
34
|
+
@http_class = options[:http_class] || Net::HTTP
|
18
35
|
end
|
19
36
|
|
37
|
+
# Runs the scout, connecting to the host and getting the response code,
|
38
|
+
# body and time.
|
20
39
|
def execute
|
21
|
-
|
22
|
-
|
40
|
+
response = @http_class.get_response(@host, @path, @port)
|
41
|
+
|
23
42
|
@response_code = response.code.to_i
|
24
43
|
@response_body = response.body
|
44
|
+
rescue SocketError, Errno::ECONNREFUSED
|
45
|
+
@response_code = @response_body = nil
|
25
46
|
end
|
26
47
|
end
|
27
48
|
end
|
data/lib/outpost/scouts/ping.rb
CHANGED
@@ -1,21 +1,41 @@
|
|
1
|
-
|
1
|
+
begin
|
2
|
+
require 'net/ping/external'
|
3
|
+
rescue LoadError => e
|
4
|
+
puts "Please install net-ping gem: gem install net-ping".
|
5
|
+
raise
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'outpost/expectations'
|
2
9
|
|
3
10
|
module Outpost
|
4
11
|
module Scouts
|
12
|
+
# Uses system's "ping" command line tool to check if the server is
|
13
|
+
# responding in a timely manner.
|
14
|
+
#
|
15
|
+
# * Responds to response_time expectation
|
16
|
+
# ({Outpost::Expectations::ResponseTime})
|
17
|
+
#
|
18
|
+
# It needs the 'net-ping' gem.
|
5
19
|
class Ping < Outpost::Scout
|
6
20
|
extend Outpost::Expectations::ResponseTime
|
7
21
|
attr_reader :response_time
|
8
22
|
|
23
|
+
|
24
|
+
# Configure the scout with given options.
|
25
|
+
# @param [Hash] Options to setup the scout
|
26
|
+
# @option options [String] :host The host that will be "pinged".
|
27
|
+
# @option options [Object] :pinger An object that can ping hosts.
|
28
|
+
# Defaults to Net::Ping::External.new
|
9
29
|
def setup(options)
|
10
|
-
@host
|
30
|
+
@host = options[:host]
|
31
|
+
@pinger = options[:pinger] || Net::Ping::External.new
|
11
32
|
end
|
12
33
|
|
34
|
+
# Runs the scout, pinging the host and getting the duration.
|
13
35
|
def execute
|
14
|
-
|
15
|
-
pinger = Net::Ping::External.new
|
16
|
-
if pinger.ping(@host)
|
36
|
+
if @pinger.ping(@host)
|
17
37
|
# Miliseconds
|
18
|
-
@response_time = pinger.duration *
|
38
|
+
@response_time = @pinger.duration * 1000
|
19
39
|
end
|
20
40
|
end
|
21
41
|
end
|