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.
@@ -0,0 +1,2 @@
1
+ /*.gem
2
+ /.bundle
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
@@ -0,0 +1,18 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ pinglish (0.0.0)
5
+ rack
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ minitest (4.5.0)
11
+ rack (1.5.0)
12
+
13
+ PLATFORMS
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ minitest (~> 4.5)
18
+ pinglish!
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.
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,9 @@
1
+ #!/bin/sh
2
+ # Make sure local development dependencies are satisfied.
3
+
4
+ set -e
5
+
6
+ cd $(dirname "$0")/..
7
+ rm -rf .bundle/config
8
+
9
+ bundle install --path .bundle --binstubs .bundle/binstubs --quiet
@@ -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"
@@ -0,0 +1,9 @@
1
+ #!/bin/sh
2
+ # Run the tests.
3
+
4
+ set -e
5
+
6
+ cd $(dirname "$0")/..
7
+
8
+ script/bootstrap && ruby -I lib -rubygems \
9
+ -e 'require "bundler/setup"; Dir["test/**/*_test.rb"].each { |f| load f }' "$@"
@@ -0,0 +1,7 @@
1
+ require "minitest/autorun"
2
+
3
+ class PinglishTest < MiniTest::Unit::TestCase
4
+ def test_sanity
5
+ assert true
6
+ end
7
+ end
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