outpost 0.1.0 → 0.2.0
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/.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
|