turnout 0.3.0 → 1.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.
- checksums.yaml +4 -4
- data/README.markdown +65 -29
- data/lib/rack/turnout.rb +12 -95
- data/lib/tasks/maintenance.rake +34 -31
- data/lib/turnout/configuration.rb +36 -0
- data/lib/turnout/maintenance_file.rb +114 -0
- data/lib/turnout/maintenance_page/base.rb +73 -0
- data/lib/turnout/maintenance_page/html.rb +20 -0
- data/lib/turnout/maintenance_page/json.rb +26 -0
- data/lib/turnout/maintenance_page.rb +24 -0
- data/lib/turnout/request.rb +35 -0
- data/lib/turnout/version.rb +1 -1
- data/lib/turnout.rb +12 -0
- data/public/maintenance.html +2 -3
- data/public/maintenance.json +1 -0
- metadata +21 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 41d171143907e5993b28b8c17d1e45605ec22fbb
|
4
|
+
data.tar.gz: fc4f2847372f888e391d8afb282c4ad125d7534f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 668f8abb73f83a5581f621e212783561a3334ced28a4348e4780de4c8025bcb190224bf6eca58c9aa3001f64e0264ad71c5e4945452e0c90fd7ea8eda76389a9
|
7
|
+
data.tar.gz: 4d962edd21460ec6814a46ff9d59bea226a54d146d8084fbb04d26f87145ec0354255d7675305bbd5a6c70fdf373497534769abbd477c0e73855f25c39f47572
|
data/README.markdown
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
Turnout [](https://travis-ci.org/biola/turnout)
|
1
|
+
Turnout [](https://travis-ci.org/biola/turnout) [](https://codeclimate.com/github/biola/turnout)
|
2
2
|
=======
|
3
|
-
Turnout is
|
3
|
+
Turnout is [Rack](http://rack.rubyforge.org/) middleware with a [Ruby on Rails](http://rubyonrails.org) engine that allows you to easily put your app in maintenance mode.
|
4
4
|
|
5
5
|
Features
|
6
6
|
========
|
@@ -11,11 +11,12 @@ Features
|
|
11
11
|
* Allow certain paths to be accessible during maintenance
|
12
12
|
* Easily override the default maintenance.html file with your own
|
13
13
|
* Simple [YAML](http://yaml.org) based config file for easy activation, deactivation and configuration without the rake commands
|
14
|
-
*
|
14
|
+
* SUpport for multiple maintenance page formats. Current [HTML](http://en.wikipedia.org/wiki/HTML) and [JSON](http://en.wikipedia.org/wiki/JSON)
|
15
|
+
* Supports Rails, [Sinatra](http://sinatrarb.com) and any other Rack application
|
15
16
|
|
16
17
|
Installation
|
17
18
|
============
|
18
|
-
Rails 3
|
19
|
+
Rails 3+
|
19
20
|
-------
|
20
21
|
In your `Gemfile` add:
|
21
22
|
|
@@ -25,20 +26,6 @@ then run
|
|
25
26
|
|
26
27
|
bundle install
|
27
28
|
|
28
|
-
Rails 2.3
|
29
|
-
---------
|
30
|
-
In your `config/environment.rb` file add:
|
31
|
-
|
32
|
-
config.gem 'turnout'
|
33
|
-
|
34
|
-
then run
|
35
|
-
|
36
|
-
rake gems:install
|
37
|
-
|
38
|
-
then in your `Rakefile` add:
|
39
|
-
|
40
|
-
require 'turnout/rake_tasks'
|
41
|
-
|
42
29
|
|
43
30
|
Activation
|
44
31
|
==========
|
@@ -74,10 +61,54 @@ Deactivation
|
|
74
61
|
|
75
62
|
rake maintenance:end
|
76
63
|
|
64
|
+
Configuration
|
65
|
+
=============
|
66
|
+
|
67
|
+
Turnout can be configured in two different ways:
|
68
|
+
|
69
|
+
1. __Pass a config hash to the middleware__
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
use Rack::Turnout,
|
73
|
+
app_root: '/some/path',
|
74
|
+
named_maintenance_file_paths: {app: 'tmp/app.yml', server: '/tmp/server.yml'},
|
75
|
+
default_mainteance_page: Turnout::MaintenancePage::JSON,
|
76
|
+
default_reason: 'Somebody googled Google!',
|
77
|
+
default_response_code: 418
|
78
|
+
```
|
79
|
+
|
80
|
+
2. __Using a config block__
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
Turonut.configure do |config|
|
84
|
+
config.app_root = '/some/path'
|
85
|
+
config.named_maintenance_file_paths = {app: 'tmp/app.yml', server: '/tmp/server.yml'},
|
86
|
+
config.default_maintenance_page = Turnout::MaintenancePage::JSON
|
87
|
+
config.default_reason = 'Somebody googled Google!'
|
88
|
+
config.default_response_code = 418
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
__NOTICE:__ Any custom configuration should be loaded not only in the app but in the rake task. This should happen automatically in Rails as the `environment` task is run if it exists. But you may want to create your own `environment` task in non-Rails apps.
|
93
|
+
|
94
|
+
Default Configuration
|
95
|
+
---------------------
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
Turnout.configure do |config|
|
99
|
+
config.app_root = '.',
|
100
|
+
config.named_maintenance_file_paths = {default: app_root.join('tmp', 'maintenance.yml').to_s},
|
101
|
+
config.default_maintenance_page = Turnout::MaintenancePage::HTML,
|
102
|
+
config.default_reason = "The site is temporarily down for maintenance.\nPlease check back soon.",
|
103
|
+
config.default_response_code = 503
|
104
|
+
end
|
105
|
+
}
|
106
|
+
```
|
107
|
+
|
77
108
|
Customization
|
78
109
|
=============
|
79
110
|
|
80
|
-
|
111
|
+
[Default maintenance pages](https://github.com/biola/turnout/blob/master/public/) are provided, but you can create your own `public/maintenance.[html|json]` files instead. If you provide a `reason` to the rake task, Turnout will parse the maintenance page file and attempt to replace a [Liquid](http://liquidmarkup.org/)-style `{{ reason }}` tag with the provided reason. So be sure to include a `{{ reason }}` tag in your `maintenance.html` file.
|
81
112
|
|
82
113
|
Tips
|
83
114
|
====
|
@@ -88,22 +119,27 @@ However you can achieve the same sort of functionality by using
|
|
88
119
|
|
89
120
|
rake maintenance:start allowed_paths="^(?!/your/under/maintenance/path)"
|
90
121
|
|
122
|
+
A central `named_maintenance_file_path` can be configured in all your apps such as `/tmp/turnout.yml` so that all apps on a server can be put into mainteance mode at once. You could even configure service based paths such as `/tmp/mongodb_maintenance.yml` so that all apps using MongoDB could be put into maintenance mode.
|
123
|
+
|
91
124
|
Behind the Scenes
|
92
125
|
=================
|
93
126
|
On every request the Rack app will check to see if `tmp/maintenance.yml` exists. If the file exists the maintenance page will be shown (unless allowed IPs are given and the requester is in the allowed range).
|
94
127
|
|
95
|
-
So if you want to get the maintenance page up or down in a
|
128
|
+
So if you want to get the maintenance page up or down in a hurry `touch tmp/maintenance.yml` and `rm tmp/maintenance.yml` will work.
|
96
129
|
|
97
|
-
Turnout will attempt to parse the `maintenance.yml` file looking for `reason
|
130
|
+
Turnout will attempt to parse the `maintenance.yml` file looking for `reason`, `allowed_ip` and other settings. The file is checked on every request so you can change these values manually or just rerun the `rake maintenance:start` command.
|
98
131
|
|
99
132
|
Example maintenance.yml File
|
100
133
|
----------------------------
|
101
134
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
135
|
+
```yaml
|
136
|
+
---
|
137
|
+
reason: Someone told me I should type <code>sudo rm -rf /</code>
|
138
|
+
allowed_paths:
|
139
|
+
- ^/help
|
140
|
+
- ^/contact_us
|
141
|
+
allowed_ips:
|
142
|
+
- 127.0.0.1
|
143
|
+
- 192.168.0.0/24
|
144
|
+
response_code: 503
|
145
|
+
```
|
data/lib/rack/turnout.rb
CHANGED
@@ -1,111 +1,28 @@
|
|
1
1
|
require 'rack'
|
2
|
-
require '
|
3
|
-
require 'ipaddr'
|
4
|
-
require 'nokogiri'
|
2
|
+
require 'turnout'
|
5
3
|
|
6
4
|
class Rack::Turnout
|
7
5
|
def initialize(app, config={})
|
8
6
|
@app = app
|
9
|
-
@config = config
|
10
|
-
end
|
11
7
|
|
12
|
-
|
13
|
-
self.request = Rack::Request.new(env)
|
14
|
-
reload_settings
|
8
|
+
Turnout.config.update config
|
15
9
|
|
16
|
-
if
|
17
|
-
|
18
|
-
else
|
19
|
-
@app.call(env)
|
10
|
+
if config[:app_root].nil? && app.respond_to?(:app_root)
|
11
|
+
Turnout.config.app_root = app.app_root
|
20
12
|
end
|
21
13
|
end
|
22
14
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def on?
|
28
|
-
maintenance_file_exists? && !request_allowed?
|
29
|
-
end
|
30
|
-
|
31
|
-
def request_allowed?
|
32
|
-
path_allowed? || ip_allowed?
|
33
|
-
end
|
34
|
-
|
35
|
-
def path_allowed?
|
36
|
-
(settings['allowed_paths'] || []).any? do |allowed_path|
|
37
|
-
request.path =~ Regexp.new(allowed_path)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def ip_allowed?
|
42
|
-
begin
|
43
|
-
ip = IPAddr.new(request.ip.to_s)
|
44
|
-
rescue ArgumentError
|
45
|
-
return false
|
46
|
-
end
|
47
|
-
|
48
|
-
(settings['allowed_ips'] || []).any? do |allowed_ip|
|
49
|
-
IPAddr.new(allowed_ip).include? ip
|
50
|
-
end
|
51
|
-
end
|
15
|
+
def call(env)
|
16
|
+
request = Turnout::Request.new(env)
|
17
|
+
settings = Turnout::MaintenanceFile.find
|
52
18
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
19
|
+
if settings && !request.allowed?(settings)
|
20
|
+
page_class = Turnout::MaintenancePage.best_for(env)
|
21
|
+
page = page_class.new(settings.reason)
|
57
22
|
|
58
|
-
|
59
|
-
@settings ||= if File.exists? settings_file
|
60
|
-
YAML::load(File.open(settings_file)) || {}
|
23
|
+
page.rack_response(settings.response_code)
|
61
24
|
else
|
62
|
-
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def app_root
|
67
|
-
@app_root ||= Pathname.new(
|
68
|
-
@config[:app_root] || @app.respond_to?(:root)? @app.root.to_s : '.'
|
69
|
-
)
|
70
|
-
end
|
71
|
-
|
72
|
-
def settings_file
|
73
|
-
app_root.join('tmp', 'maintenance.yml')
|
74
|
-
end
|
75
|
-
|
76
|
-
def maintenance_file_exists?
|
77
|
-
File.exists? settings_file
|
78
|
-
end
|
79
|
-
|
80
|
-
def maintenance_page
|
81
|
-
File.exists?(app_maintenance_page) ? app_maintenance_page : default_maintenance_page
|
82
|
-
end
|
83
|
-
|
84
|
-
def app_maintenance_page
|
85
|
-
@app_maintenance_page ||= app_root.join('public', 'maintenance.html')
|
86
|
-
end
|
87
|
-
|
88
|
-
def default_maintenance_page
|
89
|
-
@default_maintenance_page ||= File.expand_path('../../../public/maintenance.html', __FILE__)
|
90
|
-
end
|
91
|
-
|
92
|
-
def content_length
|
93
|
-
content.size.to_s
|
94
|
-
end
|
95
|
-
|
96
|
-
def content
|
97
|
-
content = File.open(maintenance_page, 'rb').read
|
98
|
-
|
99
|
-
if settings['reason']
|
100
|
-
html = Nokogiri::HTML(content)
|
101
|
-
html.at_css('#reason').inner_html = Nokogiri::HTML.fragment(settings['reason'])
|
102
|
-
content = html.to_s
|
25
|
+
@app.call(env)
|
103
26
|
end
|
104
|
-
|
105
|
-
content
|
106
|
-
end
|
107
|
-
|
108
|
-
def response_code
|
109
|
-
settings['response_code'] || 503
|
110
27
|
end
|
111
28
|
end
|
data/lib/tasks/maintenance.rake
CHANGED
@@ -1,45 +1,48 @@
|
|
1
|
+
require 'turnout'
|
2
|
+
|
1
3
|
namespace :maintenance do
|
2
|
-
desc 'Enable the maintenance mode page ("reason", "allowed_paths" and "
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
file.write settings.to_yaml
|
13
|
-
file.close
|
14
|
-
|
15
|
-
puts "Created #{settings_file}"
|
16
|
-
puts "Run `rake maintenance:end` to stop maintenance mode"
|
4
|
+
desc 'Enable the maintenance mode page ("reason", "allowed_paths", "allowed_ips" and "response_code" can be passed as environment variables)'
|
5
|
+
rule /\Amaintenance:(.*:|)start\Z/ do |task|
|
6
|
+
invoke_environment
|
7
|
+
|
8
|
+
maint_file = maintenance_file_for(task)
|
9
|
+
maint_file.import_env_vars(ENV)
|
10
|
+
maint_file.write
|
11
|
+
|
12
|
+
puts "Created #{maint_file.path}"
|
13
|
+
puts "Run `rake #{task.name.gsub(/\:start/, ':end')}` to stop maintenance mode"
|
17
14
|
end
|
18
15
|
|
19
16
|
desc 'Disable the maintenance mode page'
|
20
|
-
|
21
|
-
|
17
|
+
rule /\Amaintenance:(.*:|)end\Z/ do |task|
|
18
|
+
invoke_environment
|
19
|
+
|
20
|
+
maint_file = maintenance_file_for(task)
|
22
21
|
|
23
|
-
|
22
|
+
if maint_file.delete
|
23
|
+
puts "Deleted #{maint_file.path}"
|
24
|
+
else
|
25
|
+
fail 'Could not find a maintenance file to delete'
|
26
|
+
end
|
24
27
|
end
|
25
28
|
|
26
|
-
def
|
27
|
-
|
29
|
+
def invoke_environment
|
30
|
+
if Rake::Task.task_defined? 'environment'
|
31
|
+
Rake::Task['environment'].invoke
|
32
|
+
end
|
28
33
|
end
|
29
34
|
|
30
|
-
def
|
31
|
-
|
32
|
-
#paths = paths_string.to_s.split(/(?<!\\),\ ?/)
|
35
|
+
def maintenance_file_for(task)
|
36
|
+
path_name = (task.name.split(':') - ['maintenance', 'start', 'end']).join(':')
|
33
37
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
+
maint_file = if path_name == ''
|
39
|
+
Turnout::MaintenanceFile.default
|
40
|
+
else
|
41
|
+
Turnout::MaintenanceFile.named(path_name)
|
38
42
|
end
|
39
|
-
paths
|
40
|
-
end
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
+
fail %{Unknown path name: "#{path_name}"} if maint_file.nil?
|
45
|
+
|
46
|
+
maint_file
|
44
47
|
end
|
45
48
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Turnout
|
2
|
+
class Configuration
|
3
|
+
SETTINGS = [:app_root, :named_maintenance_file_paths, :default_maintenance_page, :default_reason, :default_response_code]
|
4
|
+
|
5
|
+
SETTINGS.each do |setting|
|
6
|
+
attr_accessor setting
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@app_root = '.'
|
11
|
+
@named_maintenance_file_paths = {default: app_root.join('tmp', 'maintenance.yml').to_s}
|
12
|
+
@default_maintenance_page = Turnout::MaintenancePage::HTML
|
13
|
+
@default_reason = "The site is temporarily down for maintenance.\nPlease check back soon."
|
14
|
+
@default_response_code = 503
|
15
|
+
end
|
16
|
+
|
17
|
+
def app_root
|
18
|
+
Pathname.new(@app_root.to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
def named_maintenance_file_paths=(named_paths)
|
22
|
+
# Force keys to symbols
|
23
|
+
@named_maintenance_file_paths = Hash[named_paths.map { |k, v| [k.to_sym, v] }]
|
24
|
+
end
|
25
|
+
|
26
|
+
def update(settings_hash)
|
27
|
+
settings_hash.each do |setting, value|
|
28
|
+
unless SETTINGS.include? setting.to_sym
|
29
|
+
raise ArgumentError, "invalid setting: #{setting}"
|
30
|
+
end
|
31
|
+
|
32
|
+
self.public_send "#{setting}=", value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Turnout
|
5
|
+
class MaintenanceFile
|
6
|
+
attr_reader :path
|
7
|
+
|
8
|
+
SETTINGS = [:reason, :allowed_paths, :allowed_ips, :response_code]
|
9
|
+
attr_reader *SETTINGS
|
10
|
+
|
11
|
+
def initialize(path)
|
12
|
+
@path = path
|
13
|
+
@reason = Turnout.config.default_reason
|
14
|
+
@allowed_paths = []
|
15
|
+
@allowed_ips = []
|
16
|
+
@response_code = Turnout.config.default_response_code
|
17
|
+
|
18
|
+
import_yaml if exists?
|
19
|
+
end
|
20
|
+
|
21
|
+
def exists?
|
22
|
+
File.exists? path
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_h
|
26
|
+
SETTINGS.each_with_object({}) do |att, hash|
|
27
|
+
hash[att] = send(att)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_yaml(key_mapper = :to_s)
|
32
|
+
to_h.each_with_object({}) { |(key, val), hash|
|
33
|
+
hash[key.send(key_mapper)] = val
|
34
|
+
}.to_yaml
|
35
|
+
end
|
36
|
+
|
37
|
+
def write
|
38
|
+
FileUtils.mkdir_p(dir_path) unless Dir.exists? dir_path
|
39
|
+
|
40
|
+
File.open(path, 'w') do |file|
|
41
|
+
file.write to_yaml
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete
|
46
|
+
File.delete(path) if exists?
|
47
|
+
end
|
48
|
+
|
49
|
+
def import(hash)
|
50
|
+
SETTINGS.map(&:to_s).each do |att|
|
51
|
+
self.send(:"#{att}=", hash[att]) unless hash[att].nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
true
|
55
|
+
end
|
56
|
+
alias :import_env_vars :import
|
57
|
+
|
58
|
+
# Find the first MaintenanceFile that exists
|
59
|
+
def self.find
|
60
|
+
path = named_paths.values.find { |path| File.exists? path }
|
61
|
+
self.new(path) if path
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.named(name)
|
65
|
+
path = named_paths[name.to_sym]
|
66
|
+
self.new(path) unless path.nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.default
|
70
|
+
self.new(named_paths.values.first)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def reason=(reason)
|
76
|
+
@reason = reason.to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
# Splits strings on commas for easier importing of environment variables
|
80
|
+
def allowed_paths=(paths)
|
81
|
+
if paths.is_a? String
|
82
|
+
# Grab everything between commas that aren't escaped with a backslash
|
83
|
+
paths = paths.to_s.split(/(?<!\\),\ ?/).map do |path|
|
84
|
+
path.strip.gsub('\,', ',') # remove the escape characters
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
@allowed_paths = paths
|
89
|
+
end
|
90
|
+
|
91
|
+
# Splits strings on commas for easier importing of environment variables
|
92
|
+
def allowed_ips=(ips)
|
93
|
+
ips = ips.to_s.split(',') if ips.is_a? String
|
94
|
+
|
95
|
+
@allowed_ips = ips
|
96
|
+
end
|
97
|
+
|
98
|
+
def response_code=(code)
|
99
|
+
@response_code = code.to_i
|
100
|
+
end
|
101
|
+
|
102
|
+
def dir_path
|
103
|
+
File.dirname(path)
|
104
|
+
end
|
105
|
+
|
106
|
+
def import_yaml
|
107
|
+
import YAML::load(File.open(path)) || {}
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.named_paths
|
111
|
+
Turnout.config.named_maintenance_file_paths
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Turnout
|
2
|
+
module MaintenancePage
|
3
|
+
class Base
|
4
|
+
attr_reader :reason
|
5
|
+
|
6
|
+
def initialize(reason = nil)
|
7
|
+
@reason = reason
|
8
|
+
end
|
9
|
+
|
10
|
+
def rack_response(code = Turnout.config.default_response_code)
|
11
|
+
[code, headers, body]
|
12
|
+
end
|
13
|
+
|
14
|
+
# Override with an array of media type strings. i.e. text/html
|
15
|
+
def self.media_types
|
16
|
+
raise NotImplementedError, '.media_types must be overridden in subclasses'
|
17
|
+
end
|
18
|
+
def media_types() self.class.media_types end
|
19
|
+
|
20
|
+
# Override with a file extension value like 'html' or 'json'
|
21
|
+
def self.extension
|
22
|
+
raise NotImplementedError, '.extension must be overridden in subclasses'
|
23
|
+
end
|
24
|
+
def extension() self.class.extension end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def self.inherited(subclass)
|
29
|
+
MaintenancePage.all << subclass
|
30
|
+
end
|
31
|
+
|
32
|
+
def headers
|
33
|
+
{'Content-Type' => media_types.first, 'Content-Length' => length}
|
34
|
+
end
|
35
|
+
|
36
|
+
def length
|
37
|
+
content.size.to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
def body
|
41
|
+
[content]
|
42
|
+
end
|
43
|
+
|
44
|
+
def content
|
45
|
+
file_content.gsub /{{\s?reason\s?}}/, reason
|
46
|
+
end
|
47
|
+
|
48
|
+
def file_content
|
49
|
+
File.read(path)
|
50
|
+
end
|
51
|
+
|
52
|
+
def path
|
53
|
+
if File.exists? custom_path
|
54
|
+
custom_path
|
55
|
+
else
|
56
|
+
default_path
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def default_path
|
61
|
+
File.expand_path("../../../../public/#{filename}", __FILE__)
|
62
|
+
end
|
63
|
+
|
64
|
+
def custom_path
|
65
|
+
Turnout.config.app_root.join('public', filename)
|
66
|
+
end
|
67
|
+
|
68
|
+
def filename
|
69
|
+
"maintenance.#{extension}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Turnout
|
2
|
+
module MaintenancePage
|
3
|
+
class HTML < Base
|
4
|
+
def reason
|
5
|
+
super.to_s.split("\n").map{|txt| "<p>#{txt}</p>" }.join("\n")
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.media_types
|
9
|
+
%w{
|
10
|
+
text/html
|
11
|
+
application/xhtml+xml
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.extension
|
16
|
+
'html'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Turnout
|
4
|
+
module MaintenancePage
|
5
|
+
class JSON < Base
|
6
|
+
def reason
|
7
|
+
super.to_s.to_json
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.media_types
|
11
|
+
%w{
|
12
|
+
application/json
|
13
|
+
text/json
|
14
|
+
application/x-javascript
|
15
|
+
text/javascript
|
16
|
+
text/x-javascript
|
17
|
+
text/x-json
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.extension
|
22
|
+
'json'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Turnout
|
2
|
+
module MaintenancePage
|
3
|
+
require 'rack/accept'
|
4
|
+
|
5
|
+
def self.all
|
6
|
+
@all ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.best_for(env)
|
10
|
+
request = Rack::Accept::Request.new(env)
|
11
|
+
|
12
|
+
all_types = all.map(&:media_types).flatten
|
13
|
+
best_type = request.best_media_type(all_types)
|
14
|
+
|
15
|
+
best = all.find { |page| page.media_types.include? best_type }
|
16
|
+
|
17
|
+
best || Turnout.default_maintenance_page
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'turnout/maintenance_page/base'
|
21
|
+
require 'turnout/maintenance_page/html'
|
22
|
+
require 'turnout/maintenance_page/json'
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
3
|
+
module Turnout
|
4
|
+
class Request
|
5
|
+
def initialize(env)
|
6
|
+
@rack_request = Rack::Request.new(env)
|
7
|
+
end
|
8
|
+
|
9
|
+
def allowed?(settings)
|
10
|
+
path_allowed?(settings.allowed_paths) || ip_allowed?(settings.allowed_ips)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader :rack_request
|
16
|
+
|
17
|
+
def path_allowed?(allowed_paths)
|
18
|
+
allowed_paths.any? do |allowed_path|
|
19
|
+
rack_request.path =~ Regexp.new(allowed_path)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def ip_allowed?(allowed_ips)
|
24
|
+
begin
|
25
|
+
ip = IPAddr.new(rack_request.ip.to_s)
|
26
|
+
rescue ArgumentError
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
|
30
|
+
allowed_ips.any? do |allowed_ip|
|
31
|
+
IPAddr.new(allowed_ip).include? ip
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/turnout/version.rb
CHANGED
data/lib/turnout.rb
CHANGED
@@ -1,3 +1,15 @@
|
|
1
1
|
module Turnout
|
2
|
+
require 'turnout/configuration'
|
3
|
+
require 'turnout/maintenance_file'
|
4
|
+
require 'turnout/maintenance_page'
|
5
|
+
require 'turnout/request'
|
2
6
|
require 'turnout/engine' if defined? Rails
|
7
|
+
|
8
|
+
def self.configure
|
9
|
+
yield config
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.config
|
13
|
+
@config ||= Configuration.new
|
14
|
+
end
|
3
15
|
end
|
data/public/maintenance.html
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
{"error":"Service Unavailable","message":{{ reason }}}
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: turnout
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Crownoble
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-06-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: rack
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name: rack
|
28
|
+
name: rack-accept
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
33
|
+
version: '0.4'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
40
|
+
version: '0.4'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rack-test
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -78,10 +78,18 @@ files:
|
|
78
78
|
- lib/rack/turnout.rb
|
79
79
|
- lib/tasks/maintenance.rake
|
80
80
|
- lib/turnout.rb
|
81
|
+
- lib/turnout/configuration.rb
|
81
82
|
- lib/turnout/engine.rb
|
83
|
+
- lib/turnout/maintenance_file.rb
|
84
|
+
- lib/turnout/maintenance_page.rb
|
85
|
+
- lib/turnout/maintenance_page/base.rb
|
86
|
+
- lib/turnout/maintenance_page/html.rb
|
87
|
+
- lib/turnout/maintenance_page/json.rb
|
82
88
|
- lib/turnout/rake_tasks.rb
|
89
|
+
- lib/turnout/request.rb
|
83
90
|
- lib/turnout/version.rb
|
84
91
|
- public/maintenance.html
|
92
|
+
- public/maintenance.json
|
85
93
|
- rails/init.rb
|
86
94
|
homepage: https://github.com/biola/turnout
|
87
95
|
licenses:
|
@@ -108,3 +116,4 @@ signing_key:
|
|
108
116
|
specification_version: 4
|
109
117
|
summary: A Rack based maintenance mode plugin for Rails
|
110
118
|
test_files: []
|
119
|
+
has_rdoc:
|