rack-ecg 0.0.4 → 0.0.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b3d3e3118a76ef6c60141ec590ea0feb00781a65
4
- data.tar.gz: eb8a0b817ab97ce981828fa8c465b60806fcf978
3
+ metadata.gz: 7bd8eb96d04ad56746e1b5a29c1bf994706a4fe1
4
+ data.tar.gz: c64c43e74caa9020f24fab71f72a328cb3a194c6
5
5
  SHA512:
6
- metadata.gz: 76a8c2d9eaf627ee8c4277fe564a0712ae05347237652fb26b8950ecfe51e5aeecbb084a72516d45a9e347ef66bc6e8db657e12eb9444376581fafdfbbb1c8a0
7
- data.tar.gz: 40baa754d837d5ddf996a43604d6e2ed5693a74bf488f01f1b45f7234e60bd53f2e79b073583d66f3f9031d7d1edf729dbffc6126489affc6fd5c1fee7ad03df
6
+ metadata.gz: fc8fc08610d5e269889dc0d5f6717be7b468298f7469f2fa01754be67988aa242bbd0dd5b78ae9f5003d4c096800c2444a6e1d9eadd325a05d9fe71450f9af75
7
+ data.tar.gz: e173e7a8cf827fe3dd3d747afd8cb9d2440e64ec67d44fada6aaf9bf330e08953f78b69eb945fb4d30dba2fffedf98ad675d621423f0ae89f0f98ac7463b6d4e
data/README.md CHANGED
@@ -126,6 +126,56 @@ $ curl http://localhost:9292/_ecg
126
126
  }
127
127
  ```
128
128
 
129
+ #### Checks with parameters
130
+ Some checks, such as the `sequel` check, require a parameter hash. In this case, you must provide the check as a tuple consisting of both the check name, and a hash of parameters:
131
+
132
+ ```ruby
133
+ use Rack::ECG, checks: [:http, [:sequel, {connection: "sqlite://my-sqlite.db"}]]
134
+ ```
135
+
136
+ ```
137
+ $ curl http://localhost:9292/_ecg
138
+ {
139
+ "http": {
140
+ "status": "ok",
141
+ "value": "online"
142
+ },
143
+ "sequel": {
144
+ "status": "ok",
145
+ "value": "true"
146
+ }
147
+ }
148
+ ```
149
+
150
+ Because the `sequel` check operates on a per-connection basis, you can specify multiple Sequel databases to independently check, and provide a friendly name for disambiguation purposes:
151
+
152
+ ```ruby
153
+ use Rack::ECG, checks: [
154
+ :http,
155
+ [:sequel, {connection: 'sqlite://events.db', name: 'events'}],
156
+ [:sequel, {connection: 'sqlite://projections.db', name: 'projections'}]
157
+ ]
158
+ ```
159
+
160
+ ```
161
+ $ curl http://localhost:9292/_ecg
162
+
163
+ {
164
+ "http": {
165
+ "status": "ok",
166
+ "value": "online"
167
+ },
168
+ "sequel_events": {
169
+ "status": "ok",
170
+ "value": "true"
171
+ },
172
+ "sequel_projections": {
173
+ "status": "ok",
174
+ "value": "true"
175
+ }
176
+ }
177
+ ```
178
+
129
179
  ### `at`
130
180
 
131
181
  By default `Rack::ECG` is mapped to a URL of `/_ecg`, you can set this to
@@ -188,7 +238,7 @@ For larger new features: Do everything as above, but first also make contact wit
188
238
 
189
239
  ## About
190
240
 
191
- This project is maintained by the [Envato engineering team][webuild] and funded by [Envato][envato].
241
+ This project is maintained by the [Envato engineering team][webuild] and funded by [Envato][envato].
192
242
 
193
243
  [<img src="http://opensource.envato.com/images/envato-oss-readme-logo.png" alt="Envato logo">][envato]
194
244
 
@@ -0,0 +1,11 @@
1
+ require 'rack/ecg'
2
+ require 'sequel'
3
+ require 'sqlite3'
4
+
5
+ use Rack::ECG, checks: [
6
+ :http,
7
+ [:sequel, {connection: 'sqlite://events.db', name: 'events'}],
8
+ [:sequel, {connection: 'sqlite://projections.db', name: 'projections'}]
9
+ ]
10
+
11
+ run -> (env) { [200, {}, ["Hello, World"]] }
@@ -1,7 +1,7 @@
1
1
  require "rack/ecg/version"
2
2
  require "json"
3
3
  require "open3"
4
- require "rack/ecg/check"
4
+ require "rack/ecg/check_factory"
5
5
 
6
6
  module Rack
7
7
  class ECG
@@ -11,9 +11,8 @@ module Rack
11
11
  def initialize(app=nil, options={})
12
12
  @app = app
13
13
 
14
- check_names = options.delete(:checks) || []
15
- @check_classes = build_check_classes(check_names)
16
-
14
+ check_configuration = options.delete(:checks) || []
15
+ @check_factory = CheckFactory.new(check_configuration, DEFAULT_CHECKS)
17
16
  @at = options.delete(:at) || DEFAULT_MOUNT_AT
18
17
 
19
18
  @hook = options.delete(:hook)
@@ -21,13 +20,11 @@ module Rack
21
20
 
22
21
  def call(env)
23
22
  if env["PATH_INFO"] == @at
24
-
25
- check_results = @check_classes.inject({}){|results, check_class|
26
- check = check_class.new
23
+ check_results = @check_factory.build_all.inject({}) do |results, check|
27
24
  results.merge(check.result.to_json)
28
- }
25
+ end
29
26
 
30
- success = check_results.none? { |check| check[1][:status] == "error" }
27
+ success = check_results.none? { |check| check[1][:status] == Check::Status::ERROR }
31
28
 
32
29
  response_status = success ? 200 : 500
33
30
 
@@ -47,16 +44,5 @@ module Rack
47
44
  [404, {},[]]
48
45
  end
49
46
  end
50
-
51
- private
52
- def build_check_classes(check_names)
53
- check_names = Array(check_names) # handle nil, or not a list
54
- check_names = check_names | DEFAULT_CHECKS # add the :http check if it's not there
55
- check_names.map{|check_name|
56
- check_class = CheckRegistry.instance[check_name]
57
- raise "Don't know about check #{check_name}" unless check_class
58
- check_class
59
- }
60
- end
61
47
  end
62
48
  end
@@ -5,10 +5,16 @@ require "rack/ecg/check/http"
5
5
  require "rack/ecg/check/migration_version"
6
6
  require "rack/ecg/check/active_record_connection"
7
7
  require "rack/ecg/check/redis_connection"
8
+ require "rack/ecg/check/sequel_connection"
8
9
 
9
10
  module Rack
10
11
  class ECG
11
12
  module Check
13
+ module Status
14
+ OK = "ok".freeze
15
+ ERROR = "error".freeze
16
+ end
17
+
12
18
  class Result < Struct.new(:name, :status, :value)
13
19
  def to_json
14
20
  {name => {:status => status, :value => value}}
@@ -4,17 +4,17 @@ module Rack
4
4
  class ActiveRecordConnection
5
5
  def result
6
6
  value = ""
7
- status = "ok"
7
+ status = Status::OK
8
8
  begin
9
9
  if defined?(ActiveRecord)
10
10
  value = ::ActiveRecord::Base.connection.active?
11
- status = value ? "ok" : "error"
11
+ status = value ? Status::OK : Status::ERROR
12
12
  else
13
- status = "error"
13
+ status = Status::ERROR
14
14
  value = "ActiveRecord not found"
15
15
  end
16
16
  rescue => e
17
- status = "error"
17
+ status = Status::ERROR
18
18
  value = e.message
19
19
  end
20
20
 
@@ -5,7 +5,7 @@ module Rack
5
5
  # this is basically a "hello-world"
6
6
  class Error
7
7
  def result
8
- Result.new(:error, "error", "PC LOAD LETTER")
8
+ Result.new(:error, Status::ERROR, "PC LOAD LETTER")
9
9
  end
10
10
  end
11
11
 
@@ -7,7 +7,7 @@ module Rack
7
7
 
8
8
  success = wait_thread.value.success?
9
9
 
10
- status = success ? "ok" : "error"
10
+ status = success ? Status::OK : Status::ERROR
11
11
 
12
12
  value = success ? stdout.read : stderr.read
13
13
  value = value.strip
@@ -5,7 +5,7 @@ module Rack
5
5
  # this is basically a "hello-world"
6
6
  class Http
7
7
  def result
8
- Result.new(:http, "ok", "online")
8
+ Result.new(:http, Status::OK, "online")
9
9
  end
10
10
  end
11
11
 
@@ -4,17 +4,17 @@ module Rack
4
4
  class MigrationVersion
5
5
  def result
6
6
  value = ""
7
- status = "ok"
7
+ status = Status::OK
8
8
  begin
9
9
  if defined?(ActiveRecord)
10
10
  connection = ActiveRecord::Base.connection
11
11
  value = connection.select_value("select max(version) from schema_migrations")
12
12
  else
13
- status = "error"
13
+ status = Status::ERROR
14
14
  value = "ActiveRecord not found"
15
15
  end
16
16
  rescue => e
17
- status = "error"
17
+ status = Status::ERROR
18
18
  value = e.message
19
19
  end
20
20
 
@@ -4,17 +4,17 @@ module Rack
4
4
  class RedisConnection
5
5
  def result
6
6
  value = ""
7
- status = "ok"
7
+ status = Status::OK
8
8
  begin
9
9
  if defined?(::Redis)
10
10
  value = ::Redis.current.connected?
11
- status = value ? "ok" : "error"
11
+ status = value ? Status::OK : Status::ERROR
12
12
  else
13
- status = "error"
13
+ status = Status::ERROR
14
14
  value = "Redis not found"
15
15
  end
16
16
  rescue => e
17
- status = "error"
17
+ status = Status::ERROR
18
18
  value = e.message
19
19
  end
20
20
 
@@ -0,0 +1,47 @@
1
+ module Rack
2
+ class ECG
3
+ module Check
4
+ class SequelConnection
5
+ attr_reader :connection_parameters, :name
6
+ def initialize(parameters = {})
7
+ @connection_parameters = parameters[:connection]
8
+ @name = parameters[:name]
9
+ end
10
+
11
+ def result
12
+ value = ""
13
+ status = Status::OK
14
+ begin
15
+ if connection_parameters.nil?
16
+ status = Status::ERROR
17
+ value = "Sequel Connection parameters not found"
18
+ elsif defined?(::Sequel)
19
+ ::Sequel.connect(connection_parameters) { |db|
20
+ value = db.test_connection
21
+ status = Status::OK
22
+ }
23
+ else
24
+ status = Status::ERROR
25
+ value = "Sequel not found"
26
+ end
27
+ rescue => e
28
+ status = Status::ERROR
29
+ value = e.message
30
+ end
31
+
32
+ Result.new(result_key.to_sym, status, value.to_s)
33
+ end
34
+
35
+ def result_key
36
+ if name
37
+ "sequel #{name.downcase}".gsub(/\W+/, '_')
38
+ else
39
+ "sequel"
40
+ end
41
+ end
42
+
43
+ CheckRegistry.instance.register(:sequel, SequelConnection)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,27 @@
1
+ require "rack/ecg/check"
2
+
3
+ module Rack
4
+ class ECG
5
+ class CheckFactory
6
+ CheckDefinition = Struct.new(:check_class, :parameters)
7
+
8
+ def initialize(definitions, default_checks = [])
9
+ definitions = Array(definitions) | default_checks
10
+
11
+ @checks = definitions.map do |check_name, check_parameters|
12
+ CheckDefinition.new(CheckRegistry.lookup(check_name), check_parameters)
13
+ end
14
+ end
15
+
16
+ def build_all
17
+ @checks.map do |check_definition|
18
+ build(check_definition.check_class, check_definition.parameters)
19
+ end
20
+ end
21
+
22
+ def build(check_class, parameters = nil)
23
+ parameters.nil? ? check_class.new : check_class.new(parameters)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -3,6 +3,7 @@ require "singleton"
3
3
  module Rack
4
4
  class ECG
5
5
  class CheckRegistry
6
+ CheckNotRegistered = Class.new(StandardError)
6
7
  include Singleton
7
8
 
8
9
  def initialize()
@@ -13,8 +14,16 @@ module Rack
13
14
  @registry[name] = check_class
14
15
  end
15
16
 
16
- def [](name)
17
- @registry[name]
17
+ def lookup(name)
18
+ @registry.fetch(name) { raise CheckNotRegistered.new("Check '#{name}' is not registered") }
19
+ end
20
+
21
+ def self.lookup(name)
22
+ instance.lookup(name)
23
+ end
24
+
25
+ def self.register(name, check_class)
26
+ instance.register(name, check_class)
18
27
  end
19
28
  end
20
29
  end
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class ECG
3
- VERSION = "0.0.4"
3
+ VERSION = "0.0.5"
4
4
  end
5
5
  end
@@ -0,0 +1,59 @@
1
+ RSpec.describe Rack::ECG::CheckFactory do
2
+ class MyCheckClass; end
3
+ class MyOtherCheckClass; def initialize(params); end; end
4
+
5
+ let(:definitions) { [] }
6
+ let(:default_checks) { [] }
7
+ subject(:check_factory) { Rack::ECG::CheckFactory.new(definitions, default_checks) }
8
+
9
+ describe "#build" do
10
+ context "with a class that does not take params" do
11
+ let(:check_class) { spy(MyCheckClass) }
12
+
13
+ it "builds the specified class" do
14
+ expect { check_factory.build(check_class) }.not_to raise_error
15
+ expect(check_class).to have_received(:new).with(no_args)
16
+ end
17
+ end
18
+
19
+ context "with a class that does not take params" do
20
+ let(:check_class) { spy(MyOtherCheckClass) }
21
+ let(:check_parameters) { double }
22
+ it "builds the specified class" do
23
+ expect { check_factory.build(check_class, check_parameters) }.not_to raise_error
24
+ expect(check_class).to have_received(:new).with(check_parameters)
25
+ end
26
+ end
27
+ end
28
+
29
+ describe "#build_all" do
30
+ context "with defined checks" do
31
+ let(:definitions) { [:my_check, [:my_other_check, {foo: 'bar'}]] }
32
+ let(:check_class) { spy(MyCheckClass) }
33
+ let(:other_check_class) { spy(MyOtherCheckClass) }
34
+ before do
35
+ allow(Rack::ECG::CheckRegistry).to receive(:lookup).with(:my_check).and_return(check_class)
36
+ allow(Rack::ECG::CheckRegistry).to receive(:lookup).with(:my_other_check).and_return(other_check_class)
37
+ end
38
+
39
+ it "builds all registered checks" do
40
+ check_factory.build_all
41
+ expect(check_class).to have_received(:new).with(no_args)
42
+ expect(other_check_class).to have_received(:new).with(foo: 'bar')
43
+ end
44
+ end
45
+
46
+ context "with defined default checks" do
47
+ let(:default_checks) { [:http] }
48
+ let(:http_class) { spy(Rack::ECG::Check::Http) }
49
+ before do
50
+ allow(Rack::ECG::CheckRegistry).to receive(:lookup).with(:http).and_return(http_class)
51
+ end
52
+
53
+ it "builds registered default checks" do
54
+ check_factory.build_all
55
+ expect(http_class).to have_received(:new).with(no_args)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,23 @@
1
+
2
+ RSpec.describe Rack::ECG::CheckRegistry do
3
+ class MyCheckClass; end
4
+ subject(:check_registry) { described_class }
5
+
6
+ before do
7
+ check_registry.register(:my_check, MyCheckClass)
8
+ end
9
+
10
+ describe ".lookup" do
11
+ context "with a registered class" do
12
+ it "returns the registered class" do
13
+ expect(check_registry.lookup(:my_check)).to eq(MyCheckClass)
14
+ end
15
+ end
16
+
17
+ context "when the class is not registered" do
18
+ it "raises an error" do
19
+ expect { check_registry.lookup(:my_other_check) }.to raise_error(Rack::ECG::CheckRegistry::CheckNotRegistered)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -129,6 +129,9 @@ RSpec.describe "when used as middleware" do
129
129
  let(:options) {
130
130
  { checks: [:migration_version] }
131
131
  }
132
+ let(:connection) { double("connection") }
133
+ let(:version) { "123456" }
134
+
132
135
  context "when available" do
133
136
  it "is reported" do
134
137
  class ActiveRecord
@@ -137,8 +140,6 @@ RSpec.describe "when used as middleware" do
137
140
  end
138
141
  end
139
142
  end
140
- version = "123456"
141
- connection = double("connection")
142
143
  expect(ActiveRecord::Base).to receive(:connection).and_return(connection)
143
144
  expect(connection).to receive(:select_value).
144
145
  with("select max(version) from schema_migrations").
@@ -164,6 +165,8 @@ RSpec.describe "when used as middleware" do
164
165
  { checks: [:active_record] }
165
166
  }
166
167
  context "when available" do
168
+ let(:active) { true }
169
+ let(:connection) { double("connection") }
167
170
  it "is reported" do
168
171
  class ActiveRecord
169
172
  class Base
@@ -171,8 +174,6 @@ RSpec.describe "when used as middleware" do
171
174
  end
172
175
  end
173
176
  end
174
- active = true
175
- connection = double("connection")
176
177
  expect(ActiveRecord::Base).to receive(:connection).and_return(connection)
177
178
  expect(connection).to receive(:active?).and_return(active)
178
179
  get "/_ecg"
@@ -196,13 +197,13 @@ RSpec.describe "when used as middleware" do
196
197
  { checks: [:redis] }
197
198
  }
198
199
  context "when available" do
200
+ let(:instance) { double("current") }
201
+ let(:connected) { true }
199
202
  it "is reported" do
200
203
  class Redis
201
204
  def self.current
202
205
  end
203
206
  end
204
- connected = true
205
- instance = double("current")
206
207
  expect(Redis).to receive(:current).and_return(instance)
207
208
  expect(instance).to receive(:connected?).and_return(connected)
208
209
  get "/_ecg"
@@ -220,5 +221,26 @@ RSpec.describe "when used as middleware" do
220
221
  end
221
222
  end
222
223
  end
224
+
225
+ context "sequel" do
226
+ let(:options) {
227
+ { checks: [[:sequel, {name: 'My Awesome DB', connection: 'sqlite://'}]] }
228
+ }
229
+ let(:instance) { double("sequel_db") }
230
+
231
+ context "when available" do
232
+ it "is reported" do
233
+ class Sequel
234
+ def self.connect(_)
235
+ end
236
+ end
237
+ expect(Sequel).to receive(:connect).with('sqlite://').and_yield(instance)
238
+ expect(instance).to receive(:test_connection).and_return(true)
239
+ get "/_ecg"
240
+ expect(json_body["sequel_my_awesome_db"]["status"]).to eq("ok")
241
+ expect(json_body["sequel_my_awesome_db"]["value"]).to eq("true")
242
+ end
243
+ end
244
+ end
223
245
  end
224
246
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-ecg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Envato
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-05-04 00:00:00.000000000 Z
12
+ date: 2017-05-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -115,6 +115,7 @@ files:
115
115
  - examples/basic.ru
116
116
  - examples/checks.ru
117
117
  - examples/mounted_path.ru
118
+ - examples/parameters.ru
118
119
  - examples/stand_alone.ru
119
120
  - gemfiles/rack_v1.gemfile
120
121
  - lib/rack-ecg.rb
@@ -126,9 +127,13 @@ files:
126
127
  - lib/rack/ecg/check/http.rb
127
128
  - lib/rack/ecg/check/migration_version.rb
128
129
  - lib/rack/ecg/check/redis_connection.rb
130
+ - lib/rack/ecg/check/sequel_connection.rb
131
+ - lib/rack/ecg/check_factory.rb
129
132
  - lib/rack/ecg/check_registry.rb
130
133
  - lib/rack/ecg/version.rb
131
134
  - rack-ecg.gemspec
135
+ - spec/check_factory_spec.rb
136
+ - spec/check_registry_spec.rb
132
137
  - spec/rack_middleware_spec.rb
133
138
  - spec/spec_helper.rb
134
139
  homepage: https://github.com/envato/rack-ecg
@@ -151,10 +156,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
156
  version: '0'
152
157
  requirements: []
153
158
  rubyforge_project:
154
- rubygems_version: 2.5.2
159
+ rubygems_version: 2.6.10
155
160
  signing_key:
156
161
  specification_version: 4
157
162
  summary: Rack middleware serving a health check page
158
163
  test_files:
164
+ - spec/check_factory_spec.rb
165
+ - spec/check_registry_spec.rb
159
166
  - spec/rack_middleware_spec.rb
160
167
  - spec/spec_helper.rb