pinglish 0.0.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 +2 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +18 -0
- data/LICENSE +20 -0
- data/README.md +124 -0
- data/lib/pinglish.rb +161 -0
- data/pinglish.gemspec +18 -0
- data/script/bootstrap +9 -0
- data/script/release +38 -0
- data/script/tests +9 -0
- data/test/pinglish_test.rb +7 -0
- metadata +91 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 John Barnette, Will Farrington, and contributors.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# Pinglish
|
2
|
+
|
3
|
+
A simple Rack middleware for checking application health. Pinglish
|
4
|
+
exposes a `/_ping` resource via HTTP `GET`, returning JSON that
|
5
|
+
conforms to the spec below.
|
6
|
+
|
7
|
+
## The Spec
|
8
|
+
|
9
|
+
0. The application __must__ respond to `GET /_ping` as an HTTP request.
|
10
|
+
|
11
|
+
0. The request handler __should__ check the health of all services the
|
12
|
+
application depends on, answering questions like, "Can I query
|
13
|
+
agains my MySQL database," "Can I create/read keys in Reds," or "How
|
14
|
+
many docs are in my ElasticSearch index?"
|
15
|
+
|
16
|
+
0. The response __must__ return within 29 seconds. This is one second
|
17
|
+
less than the default timeout for many monitoring services.
|
18
|
+
|
19
|
+
0. The response __must__ return an `HTTP 200 OK` status code if all
|
20
|
+
health checks pass.
|
21
|
+
|
22
|
+
0. The response __must__ return an `HTTP 503 SERVICE UNAVAILABLE`
|
23
|
+
status code if any health checks fail.
|
24
|
+
|
25
|
+
0. The response __must__ return an `HTTP 418 I'M A TEAPOT` status code
|
26
|
+
if the request asks for any content-type but `application/json`.
|
27
|
+
|
28
|
+
0. The response __must__ be of Content-Type `application/json;
|
29
|
+
charset=UTF-8`.
|
30
|
+
|
31
|
+
0. The response __must__ be valid JSON no matter what, even if JSON
|
32
|
+
serialization or other fundamental code fails.
|
33
|
+
|
34
|
+
0. The response __must__ contain a `"status"` key set either to `"ok"`
|
35
|
+
or `"fail"`.
|
36
|
+
|
37
|
+
0. The response __must__ contain a `"now"` key set to the current
|
38
|
+
server's time in seconds since epoch as a string.
|
39
|
+
|
40
|
+
0. If the `"status"` key is set to `"fail"`, the response __may__
|
41
|
+
contain a `"failures"` key set to an Array of string names
|
42
|
+
representing failed checks.
|
43
|
+
|
44
|
+
0. If the `"status"` key is set to `"fail"`, the response __may__
|
45
|
+
contain a `"timeouts"` key set to an Array of string names
|
46
|
+
representing checks that exceeded an implementation-specific
|
47
|
+
individual timeout.
|
48
|
+
|
49
|
+
0. The response body __may__ contain any other top-level keys to
|
50
|
+
supply additional data about services the application consumes, but
|
51
|
+
all values must be strings, arrays of strings, or hashes where both
|
52
|
+
keys and values are strings.
|
53
|
+
|
54
|
+
### An Example Response
|
55
|
+
|
56
|
+
```javascript
|
57
|
+
{
|
58
|
+
|
59
|
+
// These two keys will always exist.
|
60
|
+
|
61
|
+
"now": "1359055102",
|
62
|
+
"status": "fail",
|
63
|
+
|
64
|
+
// This key may only exist when a named check has failed.
|
65
|
+
|
66
|
+
"failures": ["db"],
|
67
|
+
|
68
|
+
// This key may only exist when a named check exceeds its timeout.
|
69
|
+
|
70
|
+
"timeouts": ["really-long-check"],
|
71
|
+
|
72
|
+
// Keys like this may exist to provide extra information about
|
73
|
+
// healthy services, like the number of objects in an S3 bucket.
|
74
|
+
|
75
|
+
"s3": "127"
|
76
|
+
}
|
77
|
+
```
|
78
|
+
|
79
|
+
## The Middleware
|
80
|
+
|
81
|
+
FIX: exegesis
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
require "pinglish"
|
85
|
+
|
86
|
+
use Pinglish do |ping|
|
87
|
+
|
88
|
+
# A single unnamed check is the simplest possible way to use
|
89
|
+
# Pinglish, and you'll probably never want combine it with other
|
90
|
+
# named checks. An unnamed check contributes to overall success or
|
91
|
+
# failure, but never adds additional data to the response.
|
92
|
+
|
93
|
+
ping.check do
|
94
|
+
App.healthy?
|
95
|
+
end
|
96
|
+
|
97
|
+
# A named check like this can provide useful summary information
|
98
|
+
# when it succeeds. In this case, a top-level "db" key will appear
|
99
|
+
# in the response containing the number of items in the database. If
|
100
|
+
# a check returns nil, no key will be added to the response.
|
101
|
+
|
102
|
+
ping.check :db do
|
103
|
+
App.db.items.size
|
104
|
+
end
|
105
|
+
|
106
|
+
# By default, checks time out after one second. You can override
|
107
|
+
# this with the :timeout option, but be aware that no combination of
|
108
|
+
# checks is ever allowed to exceed the overall 29 second limit.
|
109
|
+
|
110
|
+
ping.check :long, :timeout => 5 do
|
111
|
+
App.dawdle
|
112
|
+
end
|
113
|
+
|
114
|
+
# Signal check failure by raising an exception.
|
115
|
+
|
116
|
+
ping.check :fails do
|
117
|
+
false or raise "Everything's ruined."
|
118
|
+
end
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
## Contributing
|
123
|
+
|
124
|
+
FIX
|
data/lib/pinglish.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
require "json"
|
2
|
+
require "timeout"
|
3
|
+
|
4
|
+
# This Rack middleware provides a "/_ping" endpoint for configurable
|
5
|
+
# system health checks. It's intended to be consumed by machines.
|
6
|
+
|
7
|
+
class Pinglish
|
8
|
+
|
9
|
+
# The HTTP headers sent for every response.
|
10
|
+
|
11
|
+
HEADERS = {
|
12
|
+
"Content-Type" => "application/json; charset=UTF-8"
|
13
|
+
}
|
14
|
+
|
15
|
+
# The maximum amount of time for all checks. 29 seconds, to come in
|
16
|
+
# just under the 30 second limit of many monitoring services.
|
17
|
+
|
18
|
+
MAX_TOTAL_TIME = 29
|
19
|
+
|
20
|
+
# This triple is returned if the request media type isn't
|
21
|
+
# "application/json".
|
22
|
+
|
23
|
+
TEAPOT = [418, HEADERS, ['{"teapot":"true"}']]
|
24
|
+
|
25
|
+
# Represents a check, which is a behavior block with a name and
|
26
|
+
# timeout in seconds.
|
27
|
+
|
28
|
+
class Check
|
29
|
+
attr_reader :name
|
30
|
+
attr_reader :timeout
|
31
|
+
|
32
|
+
def initialize(name, options = nil, &block)
|
33
|
+
@name = name
|
34
|
+
@timeout = options && options[:timeout] || 1
|
35
|
+
@block = block
|
36
|
+
end
|
37
|
+
|
38
|
+
# Call this check's behavior, returning the result of the block.
|
39
|
+
|
40
|
+
def call(*args, &block)
|
41
|
+
@block.call *args, &block
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Raised when a check exceeds its timeout.
|
46
|
+
|
47
|
+
class TooLong < RuntimeError; end
|
48
|
+
|
49
|
+
# Create a new instance of the middleware wrapping `app`, with an
|
50
|
+
# optional `path` (the default is `"/_ping"`) and behavior `block`.
|
51
|
+
|
52
|
+
def initialize(app, path = nil, &block)
|
53
|
+
@app = app
|
54
|
+
@checks = {}
|
55
|
+
@path = path || "/_ping"
|
56
|
+
|
57
|
+
yield self if block_given?
|
58
|
+
end
|
59
|
+
|
60
|
+
# Exercise the middleware, immediately delegating to the wrapped app
|
61
|
+
# unless the request path matches `path`.
|
62
|
+
|
63
|
+
def call(env)
|
64
|
+
request = Rack::Request.new env
|
65
|
+
|
66
|
+
return @app.call env unless request.path == @path
|
67
|
+
return TEAPOT unless request.media_type == "application/json"
|
68
|
+
|
69
|
+
timeout MAX_TOTAL_TIME do
|
70
|
+
results = {}
|
71
|
+
|
72
|
+
@checks.values.each do |check|
|
73
|
+
begin
|
74
|
+
timeout check.timeout do
|
75
|
+
results[check.name] = check.call
|
76
|
+
end
|
77
|
+
rescue StandardError => e
|
78
|
+
results[check.name] = e
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
failed = results.values.any? { |v| failure? v }
|
83
|
+
http_status = failed ? 503 : 200
|
84
|
+
text_status = failed ? "fail" : "ok"
|
85
|
+
|
86
|
+
data = {
|
87
|
+
:now => Time.now.to_i.to_s,
|
88
|
+
:status => text_status
|
89
|
+
}
|
90
|
+
|
91
|
+
results.each do |name, value|
|
92
|
+
|
93
|
+
# The unnnamed/default check doesn't contribute data.
|
94
|
+
next if name.nil?
|
95
|
+
|
96
|
+
if failure? value
|
97
|
+
|
98
|
+
# If a check fails its name is added to a `failures` array.
|
99
|
+
# If the check failed because it timed out, its name is
|
100
|
+
# added to a `timeouts` array instead.
|
101
|
+
|
102
|
+
key = timeout?(value) ? :timeouts : :failures
|
103
|
+
(data[key] ||= []) << name
|
104
|
+
|
105
|
+
elsif value
|
106
|
+
|
107
|
+
# If the check passed and returned a value, the stringified
|
108
|
+
# version of the value is returned under the `name` key.
|
109
|
+
|
110
|
+
data[name] = value.to_s
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
[http_status, HEADERS, [JSON.generate(data)]]
|
115
|
+
end
|
116
|
+
|
117
|
+
rescue Exception => ex
|
118
|
+
|
119
|
+
p :fuck => ex
|
120
|
+
# Something catastrophic happened. We can't even run the checks
|
121
|
+
# and render a JSON response. Fall back on a pre-rendered string
|
122
|
+
# and interpolate the current epoch time.
|
123
|
+
|
124
|
+
now = Time.now.to_i.to_s
|
125
|
+
[500, HEADERS, ['{"status":"fail","now":"' + now + '"}']]
|
126
|
+
end
|
127
|
+
|
128
|
+
# Add a new check with optional `name`. A `:timeout` option can be
|
129
|
+
# specified in seconds for checks that might take longer than the
|
130
|
+
# one second default. A previously added check with the same name
|
131
|
+
# will be replaced.
|
132
|
+
|
133
|
+
def check(name = nil, options = nil, &block)
|
134
|
+
@checks[name] = Check.new(name, options, &block)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Does `value` represent a check failure? This default
|
138
|
+
# implementation returns `true` for any value that is an Exception.
|
139
|
+
# Subclasses can override this method for different behavior.
|
140
|
+
|
141
|
+
def failure?(value)
|
142
|
+
value.is_a? Exception
|
143
|
+
end
|
144
|
+
|
145
|
+
# Raise Pinglish::TooLong after `seconds` has elapsed. This default
|
146
|
+
# implementation uses Ruby's built-in Timeout class. Subclasses can
|
147
|
+
# override this method for different behavior, but any new
|
148
|
+
# implementation must raise Pinglish::TooLong when the timeout is
|
149
|
+
# exceeded or override `timeout?` appropriately.
|
150
|
+
|
151
|
+
def timeout(seconds, &block)
|
152
|
+
Timeout.timeout seconds, Pinglish::TooLong, &block
|
153
|
+
end
|
154
|
+
|
155
|
+
# Does `value` represent a check timeout? Returns `true` for any
|
156
|
+
# value that is an instance of Pinglish::TooLong.
|
157
|
+
|
158
|
+
def timeout?(value)
|
159
|
+
value.is_a? Pinglish::TooLong
|
160
|
+
end
|
161
|
+
end
|
data/pinglish.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = "pinglish"
|
5
|
+
gem.version = "0.0.0"
|
6
|
+
gem.authors = ["John Barnette", "Will Farrington"]
|
7
|
+
gem.email = ["jbarnette@github.com", "wfarr@github.com"]
|
8
|
+
gem.description = "A simple Rack middleware for checking app health."
|
9
|
+
gem.summary = "/_ping your way to freedom."
|
10
|
+
gem.homepage = "https://github.com/jbarnette/pinglish"
|
11
|
+
|
12
|
+
gem.files = `git ls-files`.split $/
|
13
|
+
gem.test_files = gem.files.grep /^test/
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
|
16
|
+
gem.add_dependency "rack"
|
17
|
+
gem.add_development_dependency "minitest", "~> 4.5"
|
18
|
+
end
|
data/script/bootstrap
ADDED
data/script/release
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
# Tag and push a release.
|
3
|
+
|
4
|
+
set -e
|
5
|
+
|
6
|
+
# Make sure we're in the project root.
|
7
|
+
|
8
|
+
cd $(dirname "$0")/..
|
9
|
+
|
10
|
+
# Build a new gem archive.
|
11
|
+
|
12
|
+
rm -rf pinglish-*.gem
|
13
|
+
gem build -q pinglish.gemspec
|
14
|
+
|
15
|
+
# Make sure we're on the master branch.
|
16
|
+
|
17
|
+
(git branch | grep -q '* master') || {
|
18
|
+
echo "Only release from the master branch."
|
19
|
+
exit 1
|
20
|
+
}
|
21
|
+
|
22
|
+
# Figure out what version we're releasing.
|
23
|
+
|
24
|
+
tag=v`ls pinglish-*.gem | sed 's/^pinglish-\(.*\)\.gem$/\1/'`
|
25
|
+
|
26
|
+
# Make sure we haven't released this version before.
|
27
|
+
|
28
|
+
git fetch -t origin
|
29
|
+
|
30
|
+
(git tag -l | grep -q "$tag") && {
|
31
|
+
echo "Whoops, there's already a '${tag}' tag."
|
32
|
+
exit 1
|
33
|
+
}
|
34
|
+
|
35
|
+
# Tag it and bag it.
|
36
|
+
|
37
|
+
gem push pinglish-*.gem && git tag "$tag" &&
|
38
|
+
git push origin master && git push origin "$tag"
|
data/script/tests
ADDED
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pinglish
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- John Barnette
|
9
|
+
- Will Farrington
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-01-24 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rack
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: minitest
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ~>
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '4.5'
|
39
|
+
type: :development
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '4.5'
|
47
|
+
description: A simple Rack middleware for checking app health.
|
48
|
+
email:
|
49
|
+
- jbarnette@github.com
|
50
|
+
- wfarr@github.com
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- .gitignore
|
56
|
+
- Gemfile
|
57
|
+
- Gemfile.lock
|
58
|
+
- LICENSE
|
59
|
+
- README.md
|
60
|
+
- lib/pinglish.rb
|
61
|
+
- pinglish.gemspec
|
62
|
+
- script/bootstrap
|
63
|
+
- script/release
|
64
|
+
- script/tests
|
65
|
+
- test/pinglish_test.rb
|
66
|
+
homepage: https://github.com/jbarnette/pinglish
|
67
|
+
licenses: []
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.8.23
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: /_ping your way to freedom.
|
90
|
+
test_files:
|
91
|
+
- test/pinglish_test.rb
|