cepa-health 0.1.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 +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +99 -0
- data/Rakefile +10 -0
- data/cepa-health.gemspec +23 -0
- data/lib/cepa-health/generator.rb +35 -0
- data/lib/cepa-health/middleware.rb +125 -0
- data/lib/cepa-health/railtie.rb +14 -0
- data/lib/cepa-health/version.rb +4 -0
- data/lib/cepa-health.rb +83 -0
- data/probes/delayed_job.rb +11 -0
- data/probes/mongoid.rb +3 -0
- data/probes/rails.rb +12 -0
- data/probes/sqlite.rb +16 -0
- data/spec/cepa_health_middleware_spec.rb +195 -0
- data/spec/cepa_health_spec.rb +146 -0
- metadata +98 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Jon Williams
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# Cepa Health Check for Rack-based Applications
|
2
|
+
|
3
|
+
Cepa Health is a Rack Middleware that provides "probes". A probe is a block
|
4
|
+
of code executed when the `/healthy` path is accessed. This path will return
|
5
|
+
a list of the probe results, most importantly with the HTTP Status Code of
|
6
|
+
"200 OK" for an overall successful result, or a "500 Internal Server Error" if
|
7
|
+
any probe fails.
|
8
|
+
|
9
|
+
This path is intended for use by services such as
|
10
|
+
[Pingdom](https://www.pingdom.com/) or [New Relic](http://newrelic.com/).
|
11
|
+
This path is also for use with Load Balancer that use a health check, such as
|
12
|
+
Amazon's [Elastic Load Balancer](http://aws.amazon.com/elasticloadbalancing/).
|
13
|
+
|
14
|
+
See Discussion before for more details on the use of Cepa Health.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
gem 'cepa-health'
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
$ bundle
|
25
|
+
|
26
|
+
Equally you can install without a gemfile using:
|
27
|
+
|
28
|
+
$ gem install cepa-health
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
http://rubyonrails.org/
|
33
|
+
If you're running a Rails application, you can generate a default initializer with
|
34
|
+
|
35
|
+
$ rails generate cepa_health:initializer
|
36
|
+
|
37
|
+
This will create a file `config/initializers/cepa_health.rb`. Refer to that file for more instructions.
|
38
|
+
|
39
|
+
Alternatively, if you're running a Rack-based application (e.g. using
|
40
|
+
[Sinatra Framework](http://www.sinatrarb.com/)) add the following to config.ru. Note this assumes you're running with [Bundler](http://bundler.io/sinatra.html) or loading the cepa-health gem yourself.
|
41
|
+
|
42
|
+
use CepaHealth::Middleware
|
43
|
+
|
44
|
+
To define probes, register blocks as:
|
45
|
+
|
46
|
+
# Create a Probe with the default level of "error"
|
47
|
+
CepaHealth.register "Probe Name" do
|
48
|
+
record("Other result", true, "This will add another reporting row")
|
49
|
+
true # Ultimate result
|
50
|
+
end
|
51
|
+
|
52
|
+
# Create a Probe specifically tagged as level "warn"
|
53
|
+
CepaHealth.register "Warning Probe", :warn do
|
54
|
+
record("Other result", true, "This will add another reporting row")
|
55
|
+
true # Ultimate result
|
56
|
+
end
|
57
|
+
|
58
|
+
The result of these Probes is summarized at the `/healthy` path when you run your Rack application. This will render a HTML table, you can similarly use `/healthy.json` or `healthy.txt` for JSON and Text results respectively. Take a look at the
|
59
|
+
[probes directory](https://github.com/cepaorg/cepa-health/tree/master/probes) for
|
60
|
+
some examples of probes.
|
61
|
+
|
62
|
+
By default, `/healthy` will return all probes. You can cut this back using filters. For example, `healthy.txt?filters=warn` will return a Text summary of just the "warn" level Probes. `healthy.txt?filters=error,warn` resturns both "error" and "warn" probes.
|
63
|
+
|
64
|
+
## Privacy
|
65
|
+
|
66
|
+
You may not want your health check available to anyone - either because you want to be private about the results, or you don't want to unnecessarily reveal details of your stack. To provide an extra layer of privacy, you can set a key on your health check. Just add (or comment out in the initalizer):
|
67
|
+
|
68
|
+
CepaHealth.key = "sekret"
|
69
|
+
|
70
|
+
The health check will only be available if `key=sekret` is added to the path. If it doesn't match, a blank 404 is returned.
|
71
|
+
|
72
|
+
This will prevent casual access to your health check.
|
73
|
+
|
74
|
+
## Discussion
|
75
|
+
|
76
|
+
There are already a handful of Rack and Rails-based Health Checks. For example,
|
77
|
+
[rack-health](https://github.com/mirakui/rack-health) or
|
78
|
+
[rack-ping](https://github.com/jondot/rack-ping).
|
79
|
+
|
80
|
+
Cepa Health addresses a handful of specific needs:
|
81
|
+
|
82
|
+
- Different levels of probes. The "error" is appropriate for removing a server from a Load Balancer and raising alarms. The "warn" level may be less serious, such as a failed Delayed Job. You way still wish to raise an alert, but perhaps of different severity.
|
83
|
+
- Simiarly, there may be other needs for uptime reporting. In this case you may only wish to measure probes that directly affect site usage.
|
84
|
+
- Cepa Health can be stubbed out by simply adding a blank `healthy.txt` to the root directory of the given path.
|
85
|
+
|
86
|
+
|
87
|
+
## Contributing
|
88
|
+
|
89
|
+
1. Fork it
|
90
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
91
|
+
3. Make sure you have some tests or way of validating the feature.
|
92
|
+
4. Commit your changes (`git commit -am 'Add some feature'`)
|
93
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
94
|
+
6. Create new Pull Request
|
95
|
+
7. ... and thanks!
|
96
|
+
|
97
|
+
## License
|
98
|
+
|
99
|
+
MIT Licensed. See LICENSE.txt
|
data/Rakefile
ADDED
data/cepa-health.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cepa-health/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "cepa-health"
|
8
|
+
gem.version = CepaHealth::VERSION
|
9
|
+
gem.authors = ["Jon Williams"]
|
10
|
+
gem.email = ["jon@jonathannen.com"]
|
11
|
+
gem.description = %q{Health Check Middleware for Rails and Rack-based Applications}
|
12
|
+
gem.summary = %q{Provides the facility for probes that are evaluated when a health URL is accessed.}
|
13
|
+
gem.license = "MIT"
|
14
|
+
gem.homepage = "https://github.com/cepaorg/cepa-health"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
gem.add_dependency 'rack', '>= 1.2.0'
|
22
|
+
gem.add_development_dependency 'rspec', '>= 2.0.0'
|
23
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
module CepaHealth
|
5
|
+
|
6
|
+
class InitializerGenerator < Rails::Generators::Base
|
7
|
+
|
8
|
+
def create_initializer_file
|
9
|
+
key = SecureRandom.hex(3)
|
10
|
+
create_file "config/initializers/cepa_health.rb", <<-CONTENT
|
11
|
+
# Configure Cepa Health checks.
|
12
|
+
# See: https://github.com/cepaorg/cepa-health
|
13
|
+
|
14
|
+
# # Comment out the following to remove the standard probes.
|
15
|
+
CepaHealth.load_probes
|
16
|
+
|
17
|
+
# # Comment out the below if you'd like your health check protected
|
18
|
+
# # by a key. The new health link will now be whatever the key is. In this
|
19
|
+
# # example, "/healthcheck?key=#{key}"
|
20
|
+
# CepaHealth.key = "#{key}"
|
21
|
+
|
22
|
+
# # Add the following to bring a standard probe back after removing the above.
|
23
|
+
# CepaHealth.load_probe(:rails)
|
24
|
+
|
25
|
+
# # And/Or you can also add your own probes
|
26
|
+
# CepaHealth.register "Probe Name" do
|
27
|
+
# record("Other result", true, "This will add another reporting row")
|
28
|
+
# true # Ultimate result
|
29
|
+
# end
|
30
|
+
CONTENT
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright © 2013 Jon Williams. See LICENSE.txt for details.
|
3
|
+
require 'cgi'
|
4
|
+
|
5
|
+
module CepaHealth
|
6
|
+
|
7
|
+
class Middleware
|
8
|
+
DEFAULT_PATH = /\A\/healthy(\.html|\.json|\.txt)?\z/
|
9
|
+
|
10
|
+
def initialize(app, options={})
|
11
|
+
@app = app
|
12
|
+
@path = options.fetch(:path, DEFAULT_PATH)
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
path = env['PATH_INFO']
|
17
|
+
path_matches?(path) ? process(path, env) : @app.call(env)
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def determine_mime_type(path)
|
23
|
+
case path.split('.').last.downcase
|
24
|
+
when 'json' then 'application/json'
|
25
|
+
when 'txt' then 'text/plain'
|
26
|
+
else 'text/html'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def process(path, env)
|
31
|
+
query = env['QUERY_STRING']
|
32
|
+
params = Rack::Utils.parse_nested_query(query)
|
33
|
+
filters = params['filters']
|
34
|
+
filters = filters.nil? ? [] : filters.split(",").map { |v| v.strip }
|
35
|
+
result = CepaHealth.execute(*filters)
|
36
|
+
|
37
|
+
unless CepaHealth.key.nil?
|
38
|
+
key = params['key']
|
39
|
+
return [404, {}, [""]] unless key == CepaHealth.key
|
40
|
+
end
|
41
|
+
|
42
|
+
mime = determine_mime_type(path)
|
43
|
+
|
44
|
+
body = case mime
|
45
|
+
when 'text/plain' then render_text(result)
|
46
|
+
else render_html(result)
|
47
|
+
end
|
48
|
+
|
49
|
+
[result.success? ? 200: 500, { 'Content-Type' => "#{mime}; charset=utf-8" }, [body]]
|
50
|
+
end
|
51
|
+
|
52
|
+
def path_matches?(path)
|
53
|
+
case @path
|
54
|
+
when Proc then @path.call(path)
|
55
|
+
when Regexp then path =~ @path
|
56
|
+
else @path.to_s == path
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def render_html(result)
|
61
|
+
rows = result.records.map do |name, status, comment|
|
62
|
+
stat = status ? "<td class='status ok'>OK</td>" : "<td class='status fail'>FAIL</td>"
|
63
|
+
"<tr>#{stat}<td class='name'>#{CGI::escapeHTML(name)}</td><td>#{CGI::escapeHTML(comment)}</td></tr>"
|
64
|
+
end
|
65
|
+
<<-HTML
|
66
|
+
<!DOCTYPE html>
|
67
|
+
<html>
|
68
|
+
<head>
|
69
|
+
<title>Health Check</title>
|
70
|
+
<style type='text/css'>
|
71
|
+
body {
|
72
|
+
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
73
|
+
}
|
74
|
+
div.container {
|
75
|
+
margin: 0 auto;
|
76
|
+
width: 960px;
|
77
|
+
}
|
78
|
+
|
79
|
+
h1 { font-size: 20px; font-weight: bold; }
|
80
|
+
h2 { font-size: 22px; font-weight: bold; }
|
81
|
+
h2 span { font-size: 36px; }
|
82
|
+
|
83
|
+
table {
|
84
|
+
border-bottom: 1px solid #999;
|
85
|
+
border-top: 1px solid #999;
|
86
|
+
margin-top: 10px;
|
87
|
+
width: 100%;
|
88
|
+
}
|
89
|
+
|
90
|
+
td {
|
91
|
+
font-size: 14px;
|
92
|
+
padding: 5px;
|
93
|
+
}
|
94
|
+
td.name { background: #f7f7f7; font-weight: bold; width: 200px; }
|
95
|
+
td.status { font-weight: bold; text-align: center; width: 50px; }
|
96
|
+
td.status.fail { background: #fdd; color: #c00; }
|
97
|
+
td.status.ok { background: #dfd; color: #0c0; }
|
98
|
+
|
99
|
+
.fail h2 { color: #600; }
|
100
|
+
.fail span { color: #c00; text-shadow: 2px 2px 0 #600; }
|
101
|
+
.ok h2 { color: #060; }
|
102
|
+
.ok span { color: #0c0; text-shadow: 2px 2px 0 #060; }
|
103
|
+
|
104
|
+
</style>
|
105
|
+
</head>
|
106
|
+
<body class='#{ result.success? ? 'ok' : 'fail'}'>
|
107
|
+
<div class='container'>
|
108
|
+
<h1>Health Check</h1>
|
109
|
+
<h2>#{ result.success? ? "<span>✔</span> Great, the Application is Healthy" : "<span>✘</span> Damn, something is broken"}</h2>
|
110
|
+
<table>#{ rows * "\n" }</table>
|
111
|
+
</div>
|
112
|
+
</body>
|
113
|
+
</html>
|
114
|
+
HTML
|
115
|
+
end
|
116
|
+
|
117
|
+
def render_text(result)
|
118
|
+
body = "#Entry\t#Status\t#Comment\n"
|
119
|
+
body << (result.success? ? "Overall\tSuccess\n" : "Overall\tFailure\n")
|
120
|
+
body + result.records.map { |a,b,c| "#{a}\t#{b ? "Success" : "Failure"}\t#{c}" } * "\n"
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright © 2013 Jon Williams. See LICENSE.txt for details.
|
3
|
+
|
4
|
+
module CepaHealth
|
5
|
+
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
|
8
|
+
initializer "cepa_health.configure_rails_initialization" do |app|
|
9
|
+
app.middleware.use CepaHealth::Middleware
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
data/lib/cepa-health.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright © 2013 Jon Williams. See LICENSE.txt for details.
|
3
|
+
|
4
|
+
module CepaHealth
|
5
|
+
attr_accessor :success
|
6
|
+
|
7
|
+
# Container that holds the result of the probe execution
|
8
|
+
class Result
|
9
|
+
attr_reader :records
|
10
|
+
|
11
|
+
def execute(name, block)
|
12
|
+
@success &&= !!(v = instance_exec(&block))
|
13
|
+
record(name, v, v ? "Success" : "Failed")
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@records = []
|
18
|
+
@success = true
|
19
|
+
end
|
20
|
+
|
21
|
+
def record(name, status, comment)
|
22
|
+
@records << [name, status, comment]
|
23
|
+
end
|
24
|
+
|
25
|
+
def success?
|
26
|
+
@success
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
attr_accessor :key
|
33
|
+
attr_reader :probes
|
34
|
+
|
35
|
+
def clear_probes!
|
36
|
+
@probes = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Executes the probes.
|
40
|
+
# @param [ Array<String> ] filters to the given levels when the probes
|
41
|
+
# were registered. If no filters are specified, all probes are resulted.
|
42
|
+
def execute(*filters)
|
43
|
+
result = CepaHealth::Result.new
|
44
|
+
filters = filters.map { |v| v.to_s }
|
45
|
+
selected = filters.empty? ? probes : probes.select { |k,v| filters.include?(k) }
|
46
|
+
selected.values.flatten(1).each { |v| result.execute(*v) }
|
47
|
+
result
|
48
|
+
end
|
49
|
+
|
50
|
+
# Loads an individual probe
|
51
|
+
def load_probe(name)
|
52
|
+
dir = File.expand_path(File.join(File.dirname(File.expand_path(__FILE__)), '..', 'probes'))
|
53
|
+
Dir[File.join(dir, name)].each { |v| require(v) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Scans the probes directory for Ruby files.
|
57
|
+
def load_probes
|
58
|
+
dir = File.expand_path(File.join(File.dirname(File.expand_path(__FILE__)), '..', 'probes'))
|
59
|
+
Dir[File.join(dir, "**/*.rb")].each { |v| require(v) }
|
60
|
+
end
|
61
|
+
|
62
|
+
# Registers the given block as a probe. An optional level can be supplied,
|
63
|
+
# which can be used as a filter.
|
64
|
+
def register(name, level = :error, &block)
|
65
|
+
list = probes[level.to_s] ||= []
|
66
|
+
list << [name, block]
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
@probes = {}
|
72
|
+
@key = nil
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
require "cepa-health/middleware"
|
77
|
+
require "cepa-health/version"
|
78
|
+
|
79
|
+
# Railtie to add the Middleware Automatically
|
80
|
+
if defined?(Rails)
|
81
|
+
require 'cepa-health/generator'
|
82
|
+
require 'cepa-health/railtie'
|
83
|
+
end
|
data/probes/mongoid.rb
ADDED
data/probes/rails.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright © 2013 Jon Williams. See LICENSE.txt for details.
|
3
|
+
|
4
|
+
if defined?(Rails)
|
5
|
+
|
6
|
+
# A Trivial Rails probe.
|
7
|
+
CepaHealth.register "Rails" do
|
8
|
+
record "Rails Major Version", true, Rails.version.split('.').first
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
data/probes/sqlite.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright © 2013 Jon Williams. See LICENSE.txt for details.
|
3
|
+
|
4
|
+
if defined?(ActiveRecord) && defined?(SQLite3)
|
5
|
+
|
6
|
+
CepaHealth.register "SQLite" do
|
7
|
+
begin
|
8
|
+
ActiveRecord::Base.connection.exec_query("PRAGMA quick_check")
|
9
|
+
true
|
10
|
+
rescue Exception => e
|
11
|
+
record("SQLite Failure", false, e.inspect)
|
12
|
+
false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright © 2013 Jon Williams. See LICENSE.txt for details.
|
3
|
+
|
4
|
+
require 'rack/lint'
|
5
|
+
require 'rack/mock'
|
6
|
+
require 'cepa-health'
|
7
|
+
|
8
|
+
describe CepaHealth::Middleware do
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
CepaHealth.clear_probes!
|
12
|
+
CepaHealth.key = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:rackapp) do
|
16
|
+
app = ->(e) { [200, { 'Content-Type' => 'text/plain' }, ["Boom"]] }
|
17
|
+
Rack::Lint.new CepaHealth::Middleware.new(app)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should not match other URLs" do
|
21
|
+
code, headers, body = get("/someotherpage.html")
|
22
|
+
body.should == "Boom"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should match the healthy URL" do
|
26
|
+
code, headers, body = get("/healthy.html")
|
27
|
+
code.should == 200
|
28
|
+
body.should_not == "Boom"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return a 200 OK for passing probes" do
|
32
|
+
CepaHealth.register("VeryUniqueTestIFear") { true }
|
33
|
+
code, headers, body = get("/healthy.html")
|
34
|
+
code.should == 200
|
35
|
+
body.should =~ /VeryUniqueTestIFear/
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should return a 500 Error for failing probes" do
|
39
|
+
CepaHealth.register("VeryUniqueTestIFear") { true }
|
40
|
+
CepaHealth.register("TotallyUniqueFailure") { false }
|
41
|
+
code, headers, body = get("/healthy.html")
|
42
|
+
code.should == 500
|
43
|
+
body.should =~ /TotallyUniqueFailure/
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should return a 404 if a key is set and not specified" do
|
47
|
+
CepaHealth.register("VeryUniqueTestIFear") { true }
|
48
|
+
CepaHealth.key = 'stone'
|
49
|
+
code, headers, body = get("/healthy.html")
|
50
|
+
code.should == 404
|
51
|
+
body.should == ""
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should return successful if a key is set and specified" do
|
55
|
+
CepaHealth.register("VeryUniqueTestIFear") { true }
|
56
|
+
CepaHealth.key = 'stone'
|
57
|
+
code, headers, body = get("/healthy.html", 'QUERY_STRING' => 'key=stone')
|
58
|
+
code.should == 200
|
59
|
+
body.should =~ /VeryUniqueTestIFear/
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should filter responses if they have different levels" do
|
63
|
+
CepaHealth.register("error1", "error") { false }
|
64
|
+
CepaHealth.register("error2", "error") { false }
|
65
|
+
CepaHealth.register("warn1", "warn") { true }
|
66
|
+
CepaHealth.register("warn2", "warn") { true }
|
67
|
+
CepaHealth.key = 'stone'
|
68
|
+
|
69
|
+
code, headers, body = get("/healthy.txt", 'QUERY_STRING' => 'key=stone')
|
70
|
+
code.should == 500
|
71
|
+
body.should =~ /error1/
|
72
|
+
body.should =~ /warn1/
|
73
|
+
|
74
|
+
code, headers, body = get("/healthy.txt", 'QUERY_STRING' => 'key=stone&filters=error,warn')
|
75
|
+
code.should == 500
|
76
|
+
body.should =~ /error1/
|
77
|
+
body.should =~ /warn1/
|
78
|
+
|
79
|
+
code, headers, body = get("/healthy.txt", 'QUERY_STRING' => 'key=stone&filters=error,other2')
|
80
|
+
code.should == 500
|
81
|
+
body.should =~ /error1/
|
82
|
+
body.should_not =~ /warn1/
|
83
|
+
|
84
|
+
code, headers, body = get("/healthy.txt", 'QUERY_STRING' => 'key=stone&filters=warn')
|
85
|
+
code.should == 200
|
86
|
+
body.should_not =~ /error1/
|
87
|
+
body.should =~ /warn1/
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
def get(url, opts = {})
|
93
|
+
code, headers, lint = rackapp.call(Rack::MockRequest.env_for(url, opts))
|
94
|
+
body = ""; lint.each { |v| body << v.to_s }
|
95
|
+
[code, headers, body]
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
# describe Rack::Health do
|
100
|
+
# def env(url='/', *args)
|
101
|
+
# Rack::MockRequest.env_for(url, *args)
|
102
|
+
# end
|
103
|
+
|
104
|
+
# let(:base_app) do
|
105
|
+
# lambda do |env|
|
106
|
+
# [200, {'Content-Type' => 'text/plain'}, ["I'm base_app"]]
|
107
|
+
# end
|
108
|
+
# end
|
109
|
+
# let(:app) { Rack::Lint.new Rack::Health.new(base_app, rack_health_options) }
|
110
|
+
# let(:rack_health_options) { {} }
|
111
|
+
# let(:status) { subject[0] }
|
112
|
+
# let(:body) { str = ''; subject[2].each {|s| str += s }; str }
|
113
|
+
|
114
|
+
# describe 'with default options' do
|
115
|
+
# let(:rack_health_options) { {} }
|
116
|
+
|
117
|
+
# describe '/' do
|
118
|
+
# subject { app.call env('/') }
|
119
|
+
|
120
|
+
# it { status.should == 200 }
|
121
|
+
# it { body.should == "I'm base_app" }
|
122
|
+
# end
|
123
|
+
|
124
|
+
# describe '/rack_health' do
|
125
|
+
# subject { app.call env('/rack_health') }
|
126
|
+
|
127
|
+
# it { status.should == 200 }
|
128
|
+
# it { body.should == 'Rack::Health says "healthy"' }
|
129
|
+
# end
|
130
|
+
# end
|
131
|
+
|
132
|
+
# describe 'with :sick_if' do
|
133
|
+
# subject { app.call env('/rack_health') }
|
134
|
+
|
135
|
+
# describe '== lambda { true }' do
|
136
|
+
# let(:rack_health_options) { { :sick_if => lambda { true } } }
|
137
|
+
|
138
|
+
# it { status.should == 503 }
|
139
|
+
# it { body.should == 'Rack::Health says "sick"' }
|
140
|
+
# end
|
141
|
+
|
142
|
+
# describe '== lambda { false }' do
|
143
|
+
# let(:rack_health_options) { { :sick_if => lambda { false } } }
|
144
|
+
|
145
|
+
# it { status.should == 200 }
|
146
|
+
# it { body.should == 'Rack::Health says "healthy"' }
|
147
|
+
# end
|
148
|
+
# end
|
149
|
+
|
150
|
+
# describe 'with :status' do
|
151
|
+
# let(:status_proc) { lambda {|healthy| healthy ? 202 : 404 } }
|
152
|
+
# subject { app.call env('/rack_health') }
|
153
|
+
|
154
|
+
# context 'healthy' do
|
155
|
+
# let(:rack_health_options) { { :sick_if => lambda { false }, :status => status_proc } }
|
156
|
+
|
157
|
+
# it { status.should == 202 }
|
158
|
+
# it { body.should == 'Rack::Health says "healthy"' }
|
159
|
+
# end
|
160
|
+
|
161
|
+
# context 'sick' do
|
162
|
+
# let(:rack_health_options) { { :sick_if => lambda { true }, :status => status_proc } }
|
163
|
+
|
164
|
+
# it { status.should == 404 }
|
165
|
+
# it { body.should == 'Rack::Health says "sick"' }
|
166
|
+
# end
|
167
|
+
# end
|
168
|
+
|
169
|
+
# describe 'with :body' do
|
170
|
+
# let(:body_proc) { lambda {|healthy| healthy ? 'fine' : 'bad' } }
|
171
|
+
# subject { app.call env('/rack_health') }
|
172
|
+
|
173
|
+
# context 'healthy' do
|
174
|
+
# let(:rack_health_options) { { :sick_if => lambda { false }, :body => body_proc } }
|
175
|
+
|
176
|
+
# it { status.should == 200 }
|
177
|
+
# it { body.should == 'fine' }
|
178
|
+
# end
|
179
|
+
|
180
|
+
# context 'sick' do
|
181
|
+
# let(:rack_health_options) { { :sick_if => lambda { true }, :body => body_proc } }
|
182
|
+
|
183
|
+
# it { status.should == 503 }
|
184
|
+
# it { body.should == 'bad' }
|
185
|
+
# end
|
186
|
+
# end
|
187
|
+
|
188
|
+
# describe 'with :path' do
|
189
|
+
# subject { app.call env('/how_are_you') }
|
190
|
+
# let(:rack_health_options) { { :path => '/how_are_you' } }
|
191
|
+
|
192
|
+
# it { status.should == 200 }
|
193
|
+
# it { body.should == 'Rack::Health says "healthy"' }
|
194
|
+
# end
|
195
|
+
# end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright © 2013 Jon Williams. See LICENSE.txt for details.
|
3
|
+
|
4
|
+
require 'cepa-health'
|
5
|
+
|
6
|
+
describe CepaHealth do
|
7
|
+
|
8
|
+
before(:each) { CepaHealth.clear_probes! }
|
9
|
+
|
10
|
+
it "should register and execute successful probes" do
|
11
|
+
standard_setup
|
12
|
+
should_be(true, 4)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should register a failure on any unsuccessful probe" do
|
16
|
+
standard_setup
|
17
|
+
CepaHealth.register("Fail") { false }
|
18
|
+
CepaHealth.register("Four") { true }
|
19
|
+
should_be(false, 6)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should allow the registration of probes in levels" do
|
23
|
+
CepaHealth.register("One") { false }
|
24
|
+
CepaHealth.register("Two", "error") { false }
|
25
|
+
CepaHealth.register("Three", "warn") { true }
|
26
|
+
CepaHealth.register("Four", "warn") { true }
|
27
|
+
CepaHealth.register("Five", "warn") { true }
|
28
|
+
should_be(false, 5)
|
29
|
+
should_be(false, 2, "error")
|
30
|
+
should_be(true, 3, "warn")
|
31
|
+
should_be(false, 5, %w{error warn other})
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def should_be(ok, record_length, filters = [])
|
37
|
+
r = CepaHealth.execute(*filters)
|
38
|
+
r.success?.should == ok
|
39
|
+
r.records.length.should == record_length
|
40
|
+
r.success?
|
41
|
+
end
|
42
|
+
|
43
|
+
def standard_setup
|
44
|
+
CepaHealth.register("One") { true }
|
45
|
+
CepaHealth.register("Two") { true }
|
46
|
+
CepaHealth.register("Three") { record("Three-B", true, "") }
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
# describe Rack::Health do
|
51
|
+
# def env(url='/', *args)
|
52
|
+
# Rack::MockRequest.env_for(url, *args)
|
53
|
+
# end
|
54
|
+
|
55
|
+
# let(:base_app) do
|
56
|
+
# lambda do |env|
|
57
|
+
# [200, {'Content-Type' => 'text/plain'}, ["I'm base_app"]]
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
# let(:app) { Rack::Lint.new Rack::Health.new(base_app, rack_health_options) }
|
61
|
+
# let(:rack_health_options) { {} }
|
62
|
+
# let(:status) { subject[0] }
|
63
|
+
# let(:body) { str = ''; subject[2].each {|s| str += s }; str }
|
64
|
+
|
65
|
+
# describe 'with default options' do
|
66
|
+
# let(:rack_health_options) { {} }
|
67
|
+
|
68
|
+
# describe '/' do
|
69
|
+
# subject { app.call env('/') }
|
70
|
+
|
71
|
+
# it { status.should == 200 }
|
72
|
+
# it { body.should == "I'm base_app" }
|
73
|
+
# end
|
74
|
+
|
75
|
+
# describe '/rack_health' do
|
76
|
+
# subject { app.call env('/rack_health') }
|
77
|
+
|
78
|
+
# it { status.should == 200 }
|
79
|
+
# it { body.should == 'Rack::Health says "healthy"' }
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
|
83
|
+
# describe 'with :sick_if' do
|
84
|
+
# subject { app.call env('/rack_health') }
|
85
|
+
|
86
|
+
# describe '== lambda { true }' do
|
87
|
+
# let(:rack_health_options) { { :sick_if => lambda { true } } }
|
88
|
+
|
89
|
+
# it { status.should == 503 }
|
90
|
+
# it { body.should == 'Rack::Health says "sick"' }
|
91
|
+
# end
|
92
|
+
|
93
|
+
# describe '== lambda { false }' do
|
94
|
+
# let(:rack_health_options) { { :sick_if => lambda { false } } }
|
95
|
+
|
96
|
+
# it { status.should == 200 }
|
97
|
+
# it { body.should == 'Rack::Health says "healthy"' }
|
98
|
+
# end
|
99
|
+
# end
|
100
|
+
|
101
|
+
# describe 'with :status' do
|
102
|
+
# let(:status_proc) { lambda {|healthy| healthy ? 202 : 404 } }
|
103
|
+
# subject { app.call env('/rack_health') }
|
104
|
+
|
105
|
+
# context 'healthy' do
|
106
|
+
# let(:rack_health_options) { { :sick_if => lambda { false }, :status => status_proc } }
|
107
|
+
|
108
|
+
# it { status.should == 202 }
|
109
|
+
# it { body.should == 'Rack::Health says "healthy"' }
|
110
|
+
# end
|
111
|
+
|
112
|
+
# context 'sick' do
|
113
|
+
# let(:rack_health_options) { { :sick_if => lambda { true }, :status => status_proc } }
|
114
|
+
|
115
|
+
# it { status.should == 404 }
|
116
|
+
# it { body.should == 'Rack::Health says "sick"' }
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
|
120
|
+
# describe 'with :body' do
|
121
|
+
# let(:body_proc) { lambda {|healthy| healthy ? 'fine' : 'bad' } }
|
122
|
+
# subject { app.call env('/rack_health') }
|
123
|
+
|
124
|
+
# context 'healthy' do
|
125
|
+
# let(:rack_health_options) { { :sick_if => lambda { false }, :body => body_proc } }
|
126
|
+
|
127
|
+
# it { status.should == 200 }
|
128
|
+
# it { body.should == 'fine' }
|
129
|
+
# end
|
130
|
+
|
131
|
+
# context 'sick' do
|
132
|
+
# let(:rack_health_options) { { :sick_if => lambda { true }, :body => body_proc } }
|
133
|
+
|
134
|
+
# it { status.should == 503 }
|
135
|
+
# it { body.should == 'bad' }
|
136
|
+
# end
|
137
|
+
# end
|
138
|
+
|
139
|
+
# describe 'with :path' do
|
140
|
+
# subject { app.call env('/how_are_you') }
|
141
|
+
# let(:rack_health_options) { { :path => '/how_are_you' } }
|
142
|
+
|
143
|
+
# it { status.should == 200 }
|
144
|
+
# it { body.should == 'Rack::Health says "healthy"' }
|
145
|
+
# end
|
146
|
+
# end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cepa-health
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jon Williams
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-01 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rack
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.2.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.2.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 2.0.0
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 2.0.0
|
46
|
+
description: Health Check Middleware for Rails and Rack-based Applications
|
47
|
+
email:
|
48
|
+
- jon@jonathannen.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- Gemfile
|
55
|
+
- LICENSE.txt
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- cepa-health.gemspec
|
59
|
+
- lib/cepa-health.rb
|
60
|
+
- lib/cepa-health/generator.rb
|
61
|
+
- lib/cepa-health/middleware.rb
|
62
|
+
- lib/cepa-health/railtie.rb
|
63
|
+
- lib/cepa-health/version.rb
|
64
|
+
- probes/delayed_job.rb
|
65
|
+
- probes/mongoid.rb
|
66
|
+
- probes/rails.rb
|
67
|
+
- probes/sqlite.rb
|
68
|
+
- spec/cepa_health_middleware_spec.rb
|
69
|
+
- spec/cepa_health_spec.rb
|
70
|
+
homepage: https://github.com/cepaorg/cepa-health
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.8.25
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: Provides the facility for probes that are evaluated when a health URL is
|
95
|
+
accessed.
|
96
|
+
test_files:
|
97
|
+
- spec/cepa_health_middleware_spec.rb
|
98
|
+
- spec/cepa_health_spec.rb
|