shunt_cache 0.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b68ce7d0aa2e76b446f3a797d7b3a77f472212ab
4
+ data.tar.gz: a4648b322d98173fdb49191d0d270dfa053c82a7
5
+ SHA512:
6
+ metadata.gz: 8010ccfb4a3b57e74e8b9a39641f819d499ce1a19bead2b98b1d8aceb2592afb1c794a68cfad6cc8f77b7aa342b973ef8c0c6b6bd0502b09b5d5213d6fe31f7f
7
+ data.tar.gz: 638b92d052e64798182d91e1180e3f356523625b29310a7b42ce5e711d8174fbf09389c82cfaae11b1e1287aa07111b807840cf1c6ee4e15612c32311d8fcc40
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
6
+ env:
7
+ global:
8
+ - CODECLIMATE_REPO_TOKEN=345df6b93f97ec1c36c0cb074ce645c8f92aefdf13e71308c1dc626c1e0fc165
9
+ matrix:
10
+ - "AS_VERSION=3.2.0"
11
+ - "AS_VERSION=4.0.0"
12
+ - "AS_VERSION=4.1.0"
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in shunt_cache.gemspec
4
+ gemspec
5
+
6
+ as_version = ENV["AS_VERSION"] || "default"
7
+
8
+ as_version = case as_version
9
+ when "default"
10
+ ">= 3.2.0"
11
+ else
12
+ "~> #{as_version}"
13
+ end
14
+
15
+ gem 'rack'
16
+ gem "activesupport", as_version
17
+
18
+ gem "codeclimate-test-reporter", group: :test, require: nil
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jeff Ching
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,88 @@
1
+ # ShuntCache [![Build Status](https://travis-ci.org/chingor13/shunt_cache.png)](https://travis-ci.org/chingor13/shunt_cache) [![Code Climate](https://codeclimate.com/github/chingor13/shunt_cache.png)](https://codeclimate.com/github/chingor13/shunt_cache) [![Code Coverage](https://codeclimate.com/github/chingor13/shunt_cache/coverage.png)](https://codeclimate.com/github/chingor13/shunt_cache)
2
+
3
+ Store temporary shunt status in a cache to conditionally show maintenance page.
4
+
5
+ ## Motivation
6
+
7
+ Many load balancer programs (HAProxy, et al), can utilize an HTTP status check to determine if a server is up or down. [dplummer](https://github.com/dplummer) suggested that we could pull machines out of rotation by having their health check endpoint return with the maintenance status response.
8
+
9
+ Rather than having your app know where all the load balancers are and explicitly telling them to take each server on/offline, we can rely on them to do the right thing when we report that we should be out of rotation.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'shunt_cache'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install shunt_cache
26
+
27
+ ## Usage
28
+
29
+ ### Rails
30
+
31
+ If you're using rails, the railtie will automatically be loaded. The railtie will automatically install the middleware and rake tasks.
32
+
33
+ ### Rake tasks
34
+
35
+ * `shunt_cache:shunt` - takes the current machine out of rotation
36
+ * `shunt_cache:unshunt` - return the machine to rotation
37
+ * `shunt_cache:status` - prints the status of the current machine to STDOUT
38
+ * `shunt_cache:wait_for_http` - checks the url at ENV['URL'] to see if it is up
39
+
40
+ ### Capistrano Deploys
41
+
42
+ Assuming capistrano version 3 (nice in-sequence commands):
43
+
44
+ ```
45
+
46
+ namespace :deploy do
47
+
48
+ desc 'Restart application'
49
+ task :restart do
50
+ on roles(:app), in: :sequence, wait: 5 do |host|
51
+ execute :rake, 'shunt_cache:shunt'
52
+ sleep(10)
53
+ sudo 'service myservice restart'
54
+ execute :rake, 'shunt_cache:wait_for_http', "URL=http://#{host}:4004/"
55
+ execute :rake, 'shunt_cache:unshunt'
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ ```
62
+
63
+ * First, we shunt the machine, then wait some amount of time. We assume that HAProxy or whatever load balancer we're using has removed it from rotation.
64
+ * We can safely restart the service.
65
+ * We then check to make sure that our service is back up. Note this is not our health check because it should report that we're shunted.
66
+ * Unshunt the machine because we know the service is running.
67
+
68
+ ## Configuration
69
+
70
+ ```
71
+
72
+ ShuntCache::Status.configure do |config|
73
+ config.cache = MyCacheStore.new
74
+ config.key = "some key that identifies this machine/service"
75
+ config.logger = SomeLogger.new # optional
76
+ end
77
+
78
+ ```
79
+
80
+ If using rails, the configuration is set by default to use `Rails.cache` as the cache store, generates a key based off hostname and the `Rails.application` name, and sets the logger to the `Rails.logger`
81
+
82
+ ## Contributing
83
+
84
+ 1. Fork it ( https://github.com/chingor13/shunt_cache/fork )
85
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
86
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
87
+ 4. Push to the branch (`git push origin my-new-feature`)
88
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << 'lib'
7
+ t.libs << 'test'
8
+ t.pattern = 'test/**/*_test.rb'
9
+ t.verbose = false
10
+ end
11
+
12
+
13
+ task default: :test
@@ -0,0 +1,9 @@
1
+ require "shunt_cache/version"
2
+
3
+ module ShuntCache
4
+ autoload :Checker, 'shunt_cache/checker'
5
+ autoload :Middleware, 'shunt_cache/middleware'
6
+ autoload :Status, 'shunt_cache/status'
7
+ end
8
+
9
+ require 'shunt_cache/railtie' if defined?(Rails)
@@ -0,0 +1,40 @@
1
+ require 'net/http'
2
+
3
+ module ShuntCache
4
+ module Checker
5
+
6
+ class << self
7
+ def wait_for_http(url, options = {})
8
+ retries = options.fetch(:retry, 10)
9
+ wait_time = options.fetch(:wait_time, 6)
10
+
11
+ uri = URI(url)
12
+
13
+ retries.times do
14
+ begin
15
+ response = request(uri, options)
16
+ code = response.code.to_i
17
+ return true if 200 <= code && 399 >= code
18
+ rescue Errno::ECONNREFUSED, Timeout::Error => e
19
+ end
20
+ sleep(wait_time)
21
+ end
22
+
23
+ false
24
+ end
25
+
26
+ def request(uri, options = {})
27
+ timeout = options.fetch(:timeout, 10)
28
+
29
+ response = nil
30
+ Net::HTTP.start(uri.host, uri.port) do |http|
31
+ http.read_timeout = timeout
32
+ request = Net::HTTP::Get.new(uri.path)
33
+ response = http.request(request)
34
+ end
35
+ response
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ module ShuntCache
2
+ class Middleware
3
+ attr_accessor :endpoint_matcher
4
+
5
+ def initialize(app, options = {})
6
+ @app = app
7
+ @endpoint_matcher = options.fetch(:endpoint) do
8
+ "/options/full_stack_status"
9
+ end
10
+ end
11
+
12
+ def call(env)
13
+ path = env.fetch('REQUEST_PATH') do
14
+ env.fetch('PATH_INFO') do
15
+ env.fetch('REQUEST_URI', '')
16
+ end
17
+ end
18
+ if path.match(endpoint_matcher) && ShuntCache::Status.shunted?
19
+ return ['503', {'Content-Type' => 'text/html'}, ['Maintenance']]
20
+ end
21
+ @app.call(env)
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ module ShuntCache
2
+ class Railtie < ::Rails::Railtie
3
+
4
+ initializer 'shunt_cache.set_cache_store' do |app|
5
+ require 'socket'
6
+ ShuntCache::Status.configure do |status|
7
+ status.cache = Rails.cache
8
+ status.key = [
9
+ Rails.application.class.name.deconstantize,
10
+ "shunt_cache",
11
+ Socket.gethostname
12
+ ].join(":")
13
+ status.logger = Rails.logger
14
+ end
15
+ end
16
+
17
+ initializer 'shunt_cache.middleware' do |app|
18
+ app.middleware.use ShuntCache::Middleware
19
+ end
20
+
21
+ rake_tasks do
22
+ load 'shunt_cache/shunt_cache.rake'
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ namespace :shunt_cache do
2
+ desc 'Mark the site as shunted'
3
+ task :shunt => :environment do
4
+ ShuntCache::Status.shunt!
5
+ end
6
+
7
+ desc 'Mark the site as unshunted'
8
+ task :unshunt => :environment do
9
+ ShuntCache::Status.unshunt!
10
+ end
11
+
12
+ desc 'Check the site status'
13
+ task :status => :environment do
14
+ puts ShuntCache::Status.status
15
+ end
16
+
17
+ desc "Wait until we get a 200 or 300 ranged http response code for ENV['URL']"
18
+ task :wait_for_http => :environment do
19
+ url = ENV.fetch('URL')
20
+ success = ShuntCache::Checker.wait_for_http(url)
21
+ unless success
22
+ puts "error checking: #{url} - never returned with status within 200..399"
23
+ exit(1)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,51 @@
1
+ require 'singleton'
2
+ require 'forwardable'
3
+
4
+ module ShuntCache
5
+ class Status
6
+ include Singleton
7
+ class << self
8
+ extend Forwardable
9
+ def_delegators :instance, :shunt!, :unshunt!, :status, :shunted?, :clear!
10
+ end
11
+ attr_accessor :key, :cache, :logger
12
+
13
+ SHUNTED = "shunted"
14
+ UNSHUNTED = "ok"
15
+
16
+ def self.configure
17
+ yield instance
18
+ end
19
+
20
+ def status
21
+ cache.fetch(key) do
22
+ UNSHUNTED
23
+ end
24
+ end
25
+
26
+ def shunted?
27
+ status == SHUNTED
28
+ end
29
+
30
+ # reset to default
31
+ def clear!
32
+ cache.delete(key)
33
+ end
34
+
35
+ def shunt!
36
+ log(:info, "Shunting site with key: #{key}")
37
+ cache.write(key, SHUNTED)
38
+ end
39
+
40
+ def unshunt!
41
+ log(:info, "Unshunting site with key: #{key}")
42
+ cache.write(key, UNSHUNTED)
43
+ end
44
+
45
+ private
46
+
47
+ def log(level, msg)
48
+ logger.send(level, msg) if logger
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module ShuntCache
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'shunt_cache/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "shunt_cache"
8
+ spec.version = ShuntCache::VERSION
9
+ spec.authors = ["Jeff Ching"]
10
+ spec.email = ["ching.jeff@gmail.com"]
11
+ spec.summary = "Cache the site status in a cache"
12
+ spec.description = "Mark the site status in a cache"
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "webmock"
24
+ end
@@ -0,0 +1,45 @@
1
+ require 'test_helper'
2
+
3
+ class CheckerTest < MiniTest::Unit::TestCase
4
+ TEST_URL = 'http://localhost:3000/options/full_stack_status'
5
+
6
+ def test_maintenance
7
+ stub_request(:get, TEST_URL)
8
+ .to_return(body: 'Maintenance', status: 503)
9
+
10
+ res = ShuntCache::Checker.wait_for_http(TEST_URL, {
11
+ wait_time: 0
12
+ })
13
+ assert_equal false, res
14
+ end
15
+
16
+ def test_connection_issues
17
+ stub_request(:get, TEST_URL)
18
+ .to_raise(Errno::ECONNREFUSED)
19
+
20
+ res = ShuntCache::Checker.wait_for_http(TEST_URL, {
21
+ wait_time: 0
22
+ })
23
+ assert_equal false, res
24
+ end
25
+
26
+ def test_should_retry
27
+ stub_request(:get, TEST_URL)
28
+ .to_raise(Errno::ECONNREFUSED).then
29
+ .to_return(body: 'OK')
30
+
31
+ res = ShuntCache::Checker.wait_for_http(TEST_URL, {
32
+ wait_time: 0
33
+ })
34
+ assert_equal true, res
35
+ end
36
+
37
+ def test_success
38
+ stub_request(:get, TEST_URL)
39
+ .to_return(body: 'OK')
40
+
41
+ res = ShuntCache::Checker.wait_for_http(TEST_URL)
42
+ assert_equal true, res
43
+ end
44
+
45
+ end
@@ -0,0 +1,85 @@
1
+ require 'test_helper'
2
+
3
+ class MiddlewareTest < MiniTest::Unit::TestCase
4
+
5
+ def setup
6
+ super
7
+ @app = ShuntCache::Middleware.new(proc{[200,{},['Hello, world.']]})
8
+ @request = Rack::MockRequest.new(@app)
9
+
10
+ # should default to unshunted
11
+ cache = ActiveSupport::Cache::MemoryStore.new
12
+ ShuntCache::Status.configure do |status|
13
+ status.cache = cache
14
+ status.key = "test_key"
15
+ end
16
+ assert_equal false, ShuntCache::Status.shunted?
17
+ end
18
+
19
+ def teardown
20
+ ShuntCache::Status.clear!
21
+ super
22
+ end
23
+
24
+ def test_unshunted_hitting_test_endpoint
25
+ response = @request.get('/options/full_stack_status')
26
+
27
+ assert_unshunted(response)
28
+ end
29
+
30
+ def test_shunted_hitting_test_endpoint
31
+ ShuntCache::Status.shunt!
32
+
33
+ response = @request.get('/options/full_stack_status')
34
+
35
+ assert_shunted(response)
36
+ end
37
+
38
+ def test_unshunted_hitting_non_status_endpoint
39
+ response = @request.get('/')
40
+
41
+ assert_unshunted(response)
42
+ end
43
+
44
+ def test_shunted_hitting_non_status_endpoint
45
+ ShuntCache::Status.shunt!
46
+
47
+ response = @request.get('/')
48
+
49
+ assert_unshunted(response)
50
+ end
51
+
52
+ def test_custom_endpoint
53
+ ShuntCache::Status.shunt!
54
+
55
+ @app.endpoint_matcher = '/foo/bar'
56
+ response = @request.get('/foo/bar')
57
+
58
+ assert_shunted(response)
59
+ end
60
+
61
+ def test_custom_endpoint_regex
62
+ ShuntCache::Status.shunt!
63
+
64
+ @app.endpoint_matcher = /\/foo\/.*/
65
+ response = @request.get('/foo/bar')
66
+
67
+ assert_shunted(response)
68
+
69
+ response = @request.get('/foo/qwerty')
70
+ assert_shunted(response)
71
+ end
72
+
73
+ private
74
+
75
+ def assert_unshunted(response)
76
+ assert_equal 200, response.status
77
+ assert_equal 'Hello, world.', response.body
78
+ end
79
+
80
+ def assert_shunted(response)
81
+ assert_equal 503, response.status
82
+ assert_equal 'Maintenance', response.body
83
+ end
84
+
85
+ end
@@ -0,0 +1,45 @@
1
+ require 'test_helper'
2
+
3
+ class StatusTest < MiniTest::Unit::TestCase
4
+
5
+ def setup
6
+ super
7
+ cache = ActiveSupport::Cache::MemoryStore.new
8
+ ShuntCache::Status.configure do |status|
9
+ status.cache = cache
10
+ status.key = "test_key"
11
+ end
12
+ end
13
+
14
+ def teardown
15
+ ShuntCache::Status.configure do |status|
16
+ status.cache = nil
17
+ status.key = nil
18
+ end
19
+
20
+ super
21
+ end
22
+
23
+ def test_defaults_to_normal
24
+ assert_equal false, ShuntCache::Status.shunted?
25
+ assert_equal ShuntCache::Status::UNSHUNTED, ShuntCache::Status.status
26
+ end
27
+
28
+ def test_can_shunt_and_unshunt
29
+ assert_equal false, ShuntCache::Status.shunted?
30
+
31
+ # shunt!
32
+ assert ShuntCache::Status.shunt!
33
+
34
+ # ensure that we've set the right state
35
+ assert_equal true, ShuntCache::Status.shunted?
36
+ assert_equal ShuntCache::Status::SHUNTED, ShuntCache::Status.status
37
+
38
+ # unshunt!
39
+ assert ShuntCache::Status.unshunt!
40
+
41
+ # ensure that we've set the right state
42
+ assert_equal false, ShuntCache::Status.shunted?
43
+ assert_equal ShuntCache::Status::UNSHUNTED, ShuntCache::Status.status
44
+ end
45
+ end
@@ -0,0 +1,12 @@
1
+ Bundler.require(:default, :test)
2
+
3
+ require "codeclimate-test-reporter"
4
+ CodeClimate::TestReporter.start
5
+
6
+ require 'minitest/autorun'
7
+ require 'webmock/minitest'
8
+
9
+ require 'active_support/cache'
10
+ require 'pp'
11
+
12
+ WebMock.disable_net_connect!(:allow => "codeclimate.com")
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shunt_cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Jeff Ching
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: webmock
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Mark the site status in a cache
56
+ email:
57
+ - ching.jeff@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".travis.yml"
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - lib/shunt_cache.rb
69
+ - lib/shunt_cache/checker.rb
70
+ - lib/shunt_cache/middleware.rb
71
+ - lib/shunt_cache/railtie.rb
72
+ - lib/shunt_cache/shunt_cache.rake
73
+ - lib/shunt_cache/status.rb
74
+ - lib/shunt_cache/version.rb
75
+ - shunt_cache.gemspec
76
+ - test/checker_test.rb
77
+ - test/middleware_test.rb
78
+ - test/status_test.rb
79
+ - test/test_helper.rb
80
+ homepage: ''
81
+ licenses:
82
+ - MIT
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 2.2.2
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Cache the site status in a cache
104
+ test_files:
105
+ - test/checker_test.rb
106
+ - test/middleware_test.rb
107
+ - test/status_test.rb
108
+ - test/test_helper.rb
109
+ has_rdoc: