rack-downtime 0.0.1
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 +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +190 -0
- data/Rakefile +7 -0
- data/lib/rack/downtime.rb +148 -0
- data/lib/rack/downtime/strategy.rb +111 -0
- data/lib/rack/downtime/utils.rb +14 -0
- data/lib/rack/downtime/version.rb +5 -0
- data/rack-downtime.gemspec +31 -0
- data/spec/downtime_spec.rb +244 -0
- data/spec/spec_helper.rb +34 -0
- metadata +146 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cdd5cb910c23cd91f064883334d4e56c25124deb
|
4
|
+
data.tar.gz: 106113c2a731759876a1aadc4c5aa9b031149862
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2bd6d11b99494837a1b4f857a9894a4f38a9c839182ca78f41c3eae7ac1c62cad46275a97418a4a5f7d82b43a282b23915f7dba5da3f3ec8560dbad674c45045
|
7
|
+
data.tar.gz: cc874fd53780e82e4ec467f364aa2fa08b964a6af6370a9882301bfcc9aa123a7992a0ba0ec6003f705e05f7464d84c1d475596b336059518bec0787e7d0dcf4
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 sshaw
|
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,190 @@
|
|
1
|
+
# Rack::Downtime
|
2
|
+
|
3
|
+
Planned downtime management for Rack applications
|
4
|
+
|
5
|
+
[](https://travis-ci.org/sshaw/rack-dowmtime)
|
6
|
+
[](https://codeclimate.com/github/sshaw/rack-downtime)
|
7
|
+
|
8
|
+
## Overview
|
9
|
+
|
10
|
+
`Rack::Dowtime` **does not** add a maintenance page -there are *plenty* of ways to do this already. Instead,
|
11
|
+
it provides one with a variety of simple ways to trigger and takedown planned maintenance notifications while a site
|
12
|
+
**is still up**.
|
13
|
+
|
14
|
+
### Examples
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
require "rack/downtime"
|
18
|
+
|
19
|
+
use Rack::Downtime
|
20
|
+
```
|
21
|
+
|
22
|
+
In your layout:
|
23
|
+
|
24
|
+
```erb
|
25
|
+
<% if ENV.include?("rack.downtime") %>
|
26
|
+
<div>
|
27
|
+
<p>
|
28
|
+
We will be down for maintenance on
|
29
|
+
<%= ENV["rack.downtime"][0].strftime("%b %e") %> from
|
30
|
+
<%= ENV["rack.downtime"][0].strftime("%l:%M %p") %> to
|
31
|
+
<%= ENV["rack.downtime"][1].strftime("%l:%M %p") %> EST.
|
32
|
+
</p>
|
33
|
+
</div>
|
34
|
+
<% end %>
|
35
|
+
```
|
36
|
+
|
37
|
+
Now set the downtime using the `:file` strategy:
|
38
|
+
|
39
|
+
```
|
40
|
+
# 1 to 4 AM
|
41
|
+
> echo '2014-11-15T01:00/2014-11-15T04:00' > downtime.txt
|
42
|
+
```
|
43
|
+
|
44
|
+
If you prefer, `Rack::Downtime` can insert the message for you:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
# Inserts a downtime message
|
48
|
+
use Rack::Downtime, :insert => "my_template.erb"
|
49
|
+
|
50
|
+
# Specify where to insert message
|
51
|
+
use Rack::Downtime, :insert => "my_template.erb", :insert_at => "body #container"
|
52
|
+
```
|
53
|
+
|
54
|
+
The downtime can be set various ways:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
# From the HTTP header X-Downtime
|
58
|
+
use Rack::Downtime, :strategy => :header
|
59
|
+
use Rack::Downtime, :strategy => { :header => "X-MyHeader" }
|
60
|
+
|
61
|
+
# Or from the query string
|
62
|
+
use Rack::Downtime, :strategy => :query
|
63
|
+
use Rack::Downtime, :strategy => { :query => "dwn__" }
|
64
|
+
```
|
65
|
+
|
66
|
+
Alternate configuration:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
Rack::Downtime.strategy = :file
|
70
|
+
Rack::Downtime::Strategy::File.path = Rails.root.join("downtime.txt")
|
71
|
+
```
|
72
|
+
|
73
|
+
Control its behavior via environment variables:
|
74
|
+
|
75
|
+
```
|
76
|
+
SetEnv RACK_DOWNTIME 2014-11-15T01:00:00-05/2014-11-15T04:00:00-05
|
77
|
+
|
78
|
+
# Disable
|
79
|
+
SetEnv RACK_DOWNTIME_DISABLE 1
|
80
|
+
|
81
|
+
# Or, just turn of insertion
|
82
|
+
SetEnv RACK_DOWNTIME_INSERT 0
|
83
|
+
```
|
84
|
+
|
85
|
+
## Usage
|
86
|
+
|
87
|
+
Downtime can be retrieved from various locations via a [downtime strategy](#downtime-strategies). When downtime is detected,
|
88
|
+
it's turned into 2 instances of `DateTime` and added to the Rack environment at `rack.downtime`. The 0th
|
89
|
+
element is the start time and the 1st element is the end time.
|
90
|
+
|
91
|
+
The dates must be given as an [ISO 8601 time interval](https://en.wikipedia.org/wiki/ISO_8601#Time_intervals)
|
92
|
+
in `start/end` format. If no dates are found `rack.downtime` will not be set.
|
93
|
+
|
94
|
+
Downtime messages can also be added to the response's body. See *[Inserting a Downtime Message](#inserting-a-downtime-message)*.
|
95
|
+
|
96
|
+
### Downtime Strategies
|
97
|
+
|
98
|
+
Strategies are given via the `:strategy` option. If none is provided then `Rack::Downtime.strategy`
|
99
|
+
is used, which defaults to `:file`.
|
100
|
+
|
101
|
+
#### `:file`
|
102
|
+
|
103
|
+
Looks in the current directory for a file named `downtime.txt`.
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
use Rack::Downtime :strategy => :file
|
107
|
+
```
|
108
|
+
|
109
|
+
To use a file named `my_file.txt`:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
use Rack::Downtime :strategy => { :file => "my_file.txt" }
|
113
|
+
```
|
114
|
+
|
115
|
+
#### `:query`
|
116
|
+
|
117
|
+
Looks for a query string parameter named `__dt__`.
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
use Rack::Downtime :strategy => :query
|
121
|
+
```
|
122
|
+
|
123
|
+
To use a query string named `q`:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
use Rack::Downtime :strategy => { :query => "q" }
|
127
|
+
```
|
128
|
+
|
129
|
+
#### `:header`
|
130
|
+
|
131
|
+
Looks for an HTTP header named `X-Downtime`.
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
use Rack::Downtime :strategy => :header
|
135
|
+
```
|
136
|
+
To look for a header named `X-DT-4SHO`:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
use Rack::Downtime :strategy => { :header => "X-DT-4SHO" }
|
140
|
+
```
|
141
|
+
|
142
|
+
Internally the header will be converted to match
|
143
|
+
[what rack generates](http://www.rubydoc.info/github/rack/rack/master/file/SPEC#The_Environment), e.g., `HTTP_*`.
|
144
|
+
|
145
|
+
#### `:env`
|
146
|
+
|
147
|
+
Looks for an environment variable named `RACK_DOWNTIME`.
|
148
|
+
|
149
|
+
#### `:cookie`
|
150
|
+
|
151
|
+
Looks for cookie named `__dt__`.
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
use Rack::Downtime :strategy => :cookie
|
155
|
+
```
|
156
|
+
To use a cookie named `oreo`:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
use Rack::Downtime :strategy => { :cookie => "oreo" }
|
160
|
+
```
|
161
|
+
|
162
|
+
#### Custom
|
163
|
+
|
164
|
+
Just pass in something that responds to `:call`, accepts a rack environment, and returns a [downtime array](#usage).
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
use Rack::Downtime :strategy => ->(env) { YourDownTimeConfig.find_dowmtime }
|
168
|
+
```
|
169
|
+
|
170
|
+
### Inserting a Downtime Message
|
171
|
+
|
172
|
+
A message can be inserted by `Rack::Downtime` into your response's body whenever downtime is scheduled.
|
173
|
+
Just provide a path to an ERB template to the `:insert` option. The downtime will be passed to the template
|
174
|
+
as `start_time` and `end_time`.
|
175
|
+
|
176
|
+
By default the template will be inserted after the `body` tag. This can be changed by providing the
|
177
|
+
desired location to the `:insert_at` option. The location can be given as a CSS selector or an XPath location.
|
178
|
+
|
179
|
+
Messages are only inserted into HTML responses with a `200` status code.
|
180
|
+
|
181
|
+
**Note that when `Rack::Downtime` inserts a message it will turn a streaming response into a buffered one**.
|
182
|
+
If this is a problem you can always check for and insert the downtime yourself in your application.
|
183
|
+
|
184
|
+
## TODO
|
185
|
+
|
186
|
+
Don't invalidate XHTML responses when inserting a downtime message.
|
187
|
+
|
188
|
+
## Author
|
189
|
+
|
190
|
+
Skye Shaw [sshaw AT gmail.com]
|
data/Rakefile
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
require "rack/utils"
|
2
|
+
require "erb"
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
require "rack/downtime/strategy"
|
6
|
+
require "rack/downtime/version"
|
7
|
+
|
8
|
+
module Rack
|
9
|
+
##
|
10
|
+
# For the full documentation see README.md
|
11
|
+
|
12
|
+
class Downtime
|
13
|
+
|
14
|
+
DOWNTIME_DISABLE = "RACK_DOWNTIME_DISABLE".freeze
|
15
|
+
DOWNTIME_INSERT = "RACK_DOWNTIME_INSERT".freeze
|
16
|
+
|
17
|
+
ENV_KEY = "rack.downtime".freeze
|
18
|
+
DEFAULT_INSERT_AT = "html body".freeze
|
19
|
+
|
20
|
+
# Newer versions of Rack should have these
|
21
|
+
CONTENT_TYPE = "Content-Type".freeze
|
22
|
+
CONTENT_LENGTH = "Content-Length".freeze
|
23
|
+
|
24
|
+
class << self
|
25
|
+
attr_writer :strategy
|
26
|
+
|
27
|
+
##
|
28
|
+
# Set the default downtime strategy
|
29
|
+
def strategy
|
30
|
+
@strategy ||= :file
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Create an instance of the Rack middleware to manage downtime notifications
|
36
|
+
#
|
37
|
+
# === Arguments
|
38
|
+
#
|
39
|
+
# [options (Hash)] downtime detection and insertion options; optional
|
40
|
+
#
|
41
|
+
# === Options
|
42
|
+
#
|
43
|
+
# [:strategy (Symbol|Hash)] Set the downtime detection strategy; defaults to Rack::Downtime::strategy. If a +Hash+ its first value is passed as an argument to the strategy class.
|
44
|
+
# [:insert (String)] Path to an ERB template to insert when downtime is planned. Downtimes are given to the template as +start_time+ and +end_time+.
|
45
|
+
# [:insert_at (String)] Where to insert the ERB template given by +:insert+. Can be given as a CSS selector or an XPath location.
|
46
|
+
#
|
47
|
+
# === Errors
|
48
|
+
#
|
49
|
+
# [ArgumentError] If the strategy is unknown
|
50
|
+
|
51
|
+
def initialize(app, options = {})
|
52
|
+
@app = app
|
53
|
+
|
54
|
+
@strategy = options[:strategy] || self.class.strategy
|
55
|
+
@strategy = load_strategy(@strategy) unless @strategy.respond_to?(:call)
|
56
|
+
|
57
|
+
@insert = options[:insert]
|
58
|
+
@insert = load_template(@insert) if @insert
|
59
|
+
|
60
|
+
@insert_at = options[:insert_at] || DEFAULT_INSERT_AT
|
61
|
+
end
|
62
|
+
|
63
|
+
def call(env)
|
64
|
+
return @app.call(env) if env[DOWNTIME_DISABLE] == "1"
|
65
|
+
|
66
|
+
downtime = get_downtime(env)
|
67
|
+
env[ENV_KEY] = downtime if downtime
|
68
|
+
|
69
|
+
response = @app.call(env)
|
70
|
+
return response unless downtime && insert_downtime?(env, response)
|
71
|
+
|
72
|
+
old_body = response[2]
|
73
|
+
new_body = insert_downtime(old_body, downtime)
|
74
|
+
|
75
|
+
old_body.close if old_body.respond_to?(:close)
|
76
|
+
response[1][CONTENT_LENGTH] = Rack::Utils.bytesize(new_body).to_s
|
77
|
+
response[2] = [new_body]
|
78
|
+
|
79
|
+
response
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def load_strategy(options)
|
85
|
+
config = nil
|
86
|
+
strategy = options
|
87
|
+
strategy, config = strategy.first if strategy.is_a?(Hash)
|
88
|
+
|
89
|
+
case strategy
|
90
|
+
when :cookie
|
91
|
+
Strategy::Cookie.new(config)
|
92
|
+
when :env, :environment
|
93
|
+
Strategy::Env.new(config)
|
94
|
+
when :file
|
95
|
+
Strategy::File.new(config)
|
96
|
+
when :header
|
97
|
+
Strategy::Header.new(config)
|
98
|
+
when :query
|
99
|
+
Strategy::Query.new(config)
|
100
|
+
else
|
101
|
+
raise ArgumentError, "unknown strategy: #{strategy}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def load_template(template)
|
106
|
+
Class.new do
|
107
|
+
include ERB.new(::File.read(template), nil, "<>%-").def_module("render(start_time, end_time)")
|
108
|
+
end.new
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_downtime(env)
|
112
|
+
@strategy.call(env)
|
113
|
+
rescue => e
|
114
|
+
message = "Rack::Downtime failed: #{e}"
|
115
|
+
|
116
|
+
if env["rack.logger"].respond_to?(:error)
|
117
|
+
env["rack.logger"].error(message)
|
118
|
+
else
|
119
|
+
env["rack.errors"].puts(message)
|
120
|
+
end
|
121
|
+
|
122
|
+
nil
|
123
|
+
end
|
124
|
+
|
125
|
+
def insert_downtime?(env, response)
|
126
|
+
@insert && env[DOWNTIME_INSERT] != "0" && response[0] == 200 && response[1][CONTENT_TYPE] =~ /html/
|
127
|
+
end
|
128
|
+
|
129
|
+
def insert_downtime(old_body, times)
|
130
|
+
new_body = ""
|
131
|
+
old_body.each { |line| new_body << line }
|
132
|
+
|
133
|
+
doc = Nokogiri::HTML(new_body)
|
134
|
+
e = doc.at(@insert_at)
|
135
|
+
return new_body unless e
|
136
|
+
|
137
|
+
message = @insert.render(*times)
|
138
|
+
|
139
|
+
if e.child
|
140
|
+
e.child.before(message)
|
141
|
+
else
|
142
|
+
e.add_child(message)
|
143
|
+
end
|
144
|
+
|
145
|
+
doc.to_html
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require "rack/request"
|
2
|
+
require "rack/utils"
|
3
|
+
require "rack/downtime/utils"
|
4
|
+
|
5
|
+
class Rack::Downtime
|
6
|
+
module Strategy
|
7
|
+
class Cookie
|
8
|
+
include Rack::Utils
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_writer :named
|
12
|
+
|
13
|
+
def named
|
14
|
+
@named ||= "__dt__"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(named = nil)
|
19
|
+
@named = named || self.class.named
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
req = Rack::Request.new(env)
|
24
|
+
Rack::Downtime::Utils.parse_downtime(req.cookies[@named])
|
25
|
+
#delete_cookie_header!(env, @named) if downtime
|
26
|
+
#downtime
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Env
|
31
|
+
class << self
|
32
|
+
attr_writer :named
|
33
|
+
|
34
|
+
def named
|
35
|
+
@named ||= "RACK_DOWNTIME"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(named = nil)
|
40
|
+
@named = named || self.class.named
|
41
|
+
end
|
42
|
+
|
43
|
+
def call(env)
|
44
|
+
Rack::Downtime::Utils.parse_downtime(env[@named])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Header < Env
|
49
|
+
def self.named
|
50
|
+
@named ||= "X-Downtime"
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize(named = nil)
|
54
|
+
@named = (named || self.class.named).upcase.tr!("-", "_")
|
55
|
+
@named.prepend "HTTP_" unless @named.start_with?("HTTP_")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Query
|
60
|
+
class << self
|
61
|
+
attr_writer :param
|
62
|
+
|
63
|
+
def param
|
64
|
+
@param ||= "__dt__"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def initialize(param = nil)
|
69
|
+
@param = param || self.class.param
|
70
|
+
end
|
71
|
+
|
72
|
+
def call(env)
|
73
|
+
req = Rack::Request.new(env)
|
74
|
+
Rack::Downtime::Utils.parse_downtime(req[@param])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class File
|
79
|
+
class << self
|
80
|
+
attr_writer :path
|
81
|
+
|
82
|
+
def path
|
83
|
+
@path ||= "downtime.txt"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def initialize(path = nil)
|
88
|
+
@path = path || self.class.path
|
89
|
+
@mtime = 0
|
90
|
+
end
|
91
|
+
|
92
|
+
def call(env)
|
93
|
+
return unless ::File.exists?(@path)
|
94
|
+
|
95
|
+
new_mtime = ::File.mtime(@path).to_i
|
96
|
+
if new_mtime > @mtime
|
97
|
+
@downtime = parse_downtime(@path)
|
98
|
+
@mtime = new_mtime
|
99
|
+
end
|
100
|
+
|
101
|
+
@downtime
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def parse_downtime(path)
|
107
|
+
Rack::Downtime::Utils.parse_downtime(::File.read(path))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "date"
|
2
|
+
|
3
|
+
class Rack::Downtime
|
4
|
+
module Utils
|
5
|
+
def parse_downtime(data)
|
6
|
+
return unless data
|
7
|
+
|
8
|
+
downtime = data.split("/", 2).map { |date| DateTime.iso8601(date) }
|
9
|
+
downtime.empty? ? nil : downtime
|
10
|
+
end
|
11
|
+
|
12
|
+
module_function :parse_downtime
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rack/downtime/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rack-downtime"
|
8
|
+
spec.version = Rack::Downtime::VERSION
|
9
|
+
spec.authors = ["Skye Shaw"]
|
10
|
+
spec.email = ["skye.shaw@gmail.com"]
|
11
|
+
spec.summary = %q{Planned downtime management for Rack applications}
|
12
|
+
spec.description =<<DOC
|
13
|
+
Rack::Downtime provides a variety of ways to easily trigger and display planned maintenance notifications to users
|
14
|
+
while a site is still up. Various strategies are provided that will work with sites of all sizes.
|
15
|
+
DOC
|
16
|
+
|
17
|
+
spec.homepage = "https://github.com/sshaw/rack-downtime"
|
18
|
+
spec.license = "MIT"
|
19
|
+
|
20
|
+
spec.files = `git ls-files -z`.split("\x0")
|
21
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
22
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.add_dependency "nokogiri"
|
26
|
+
spec.add_dependency "rack", "~> 1"
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
28
|
+
spec.add_development_dependency "rack-test"
|
29
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
30
|
+
spec.add_development_dependency "rspec"
|
31
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "fileutils"
|
3
|
+
|
4
|
+
describe Rack::Downtime do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@tmp = Dir.mktmpdir
|
8
|
+
Dir.chdir(@tmp)
|
9
|
+
|
10
|
+
@dates = [ DateTime.new(2014,11,11,0,0), DateTime.new(2014,11,11,2,0) ]
|
11
|
+
@downtime = time_interval(@dates)
|
12
|
+
File.write("downtime.txt", @downtime)
|
13
|
+
end
|
14
|
+
|
15
|
+
after { FileUtils.rm_rf(@tmp) }
|
16
|
+
|
17
|
+
it "sets the environment's rack.downtime to the downtime" do
|
18
|
+
set_dates = nil
|
19
|
+
app = new_app { |env| set_dates = env["rack.downtime"] }
|
20
|
+
req = Rack::Test::Session.new(described_class.new(app))
|
21
|
+
req.get "/"
|
22
|
+
|
23
|
+
expect(set_dates).to eq @dates
|
24
|
+
end
|
25
|
+
|
26
|
+
# context "when no downtime has been specified" do
|
27
|
+
# it "does not insert the alert"
|
28
|
+
# it "does not assign any dates to rack.downtime"
|
29
|
+
# end
|
30
|
+
|
31
|
+
context "given a downtime message template" do
|
32
|
+
before do
|
33
|
+
@template = File.join(@tmp, "alert.erb")
|
34
|
+
File.write(@template, "__HERE__")
|
35
|
+
end
|
36
|
+
|
37
|
+
#it "sets the environment's rack.downtime to the downtime"
|
38
|
+
|
39
|
+
context "with a non-200 response" do
|
40
|
+
it "does not insert the template" do
|
41
|
+
app = new_app(:code => 302)
|
42
|
+
|
43
|
+
req = Rack::Test::Session.new(described_class.new(app, :insert => @template))
|
44
|
+
req.get "/"
|
45
|
+
|
46
|
+
expect(req.last_response.body).to_not match("__HERE__")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "with a non-HTML content type" do
|
51
|
+
it "does not insert the template" do
|
52
|
+
app = new_app(:headers => {"Content-Type" => "text/plain"})
|
53
|
+
|
54
|
+
req = Rack::Test::Session.new(described_class.new(app, :insert => @template))
|
55
|
+
req.get "/"
|
56
|
+
|
57
|
+
expect(req.last_response.body).to_not match("__HERE__")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "when RACK_DOWNTIME_INSERT = 0" do
|
62
|
+
it "does not insert the template" do
|
63
|
+
req = Rack::Test::Session.new(described_class.new(new_app, :insert => @template))
|
64
|
+
req.get "/", nil, "RACK_DOWNTIME_INSERT" => "0"
|
65
|
+
|
66
|
+
expect(req.last_response.body).to_not match("__HERE__")
|
67
|
+
end
|
68
|
+
|
69
|
+
#it "sets the environment's rack.downtime to the downtime"
|
70
|
+
end
|
71
|
+
|
72
|
+
context "when RACK_DOWNTIME_DISABLE = 1" do
|
73
|
+
it "does not insert the template" do
|
74
|
+
req = Rack::Test::Session.new(described_class.new(new_app, :insert => @template))
|
75
|
+
req.get "/", nil, "RACK_DOWNTIME_DISABLE" => "1"
|
76
|
+
|
77
|
+
expect(req.last_response.body).to_not match("__HERE__")
|
78
|
+
end
|
79
|
+
|
80
|
+
it "does not set rack.downtime" do
|
81
|
+
set_dates = nil
|
82
|
+
app = new_app { |env| set_dates = env["rack.downtime"] }
|
83
|
+
|
84
|
+
req = Rack::Test::Session.new(described_class.new(new_app))
|
85
|
+
req.get "/", nil, "RACK_DOWNTIME_DISABLE" => "1"
|
86
|
+
|
87
|
+
expect(set_dates).to be_nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it "inserts the template into the response" do
|
92
|
+
req = Rack::Test::Session.new(described_class.new(new_app, :insert => @template))
|
93
|
+
req.get "/"
|
94
|
+
|
95
|
+
expect(req.last_response.body).to eq_html "<!doctype html><html><body>__HERE__<p>Content!</p></body></html>"
|
96
|
+
end
|
97
|
+
|
98
|
+
it "passes downtime times to the template" do
|
99
|
+
File.write(@template, "<%= start_time.hour %>/<%= end_time.hour %>")
|
100
|
+
|
101
|
+
req = Rack::Test::Session.new(described_class.new(new_app, :insert => @template))
|
102
|
+
req.get "/"
|
103
|
+
|
104
|
+
expect(req.last_response.body).to eq_html "<!doctype html><html><body>0/2<p>Content!</p></body></html>"
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "the :insert_at option" do
|
108
|
+
[["CSS", "body p"], ["xpath", "//body/p"]].each do |format, location|
|
109
|
+
it "inserts the template at the given #{format} location" do
|
110
|
+
req = Rack::Test::Session.new(described_class.new(new_app, :insert => @template, :insert_at => location))
|
111
|
+
req.get "/"
|
112
|
+
|
113
|
+
expect(req.last_response.body).to match("<body><p>__HERE__Content!</p>")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "given an invalid location" do
|
118
|
+
it "does not insert the template into response" do
|
119
|
+
req = Rack::Test::Session.new(described_class.new(new_app, :insert => @template, :insert_at => "div span a"))
|
120
|
+
req.get "/"
|
121
|
+
|
122
|
+
expect(req.last_response.body).to_not match("__HERE__")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "strategies" do
|
129
|
+
before do
|
130
|
+
@app = lambda { |env|
|
131
|
+
body = env["rack.downtime"].map { |t| t.strftime("%s") }.join("/")
|
132
|
+
headers = {"Content-Type" => "text/plain"}
|
133
|
+
headers["Content-Length"] = body.size
|
134
|
+
|
135
|
+
[ 200, headers, [body] ]
|
136
|
+
}
|
137
|
+
@body = @dates.map { |d| d.strftime("%s") }.join("/")
|
138
|
+
end
|
139
|
+
|
140
|
+
it "raises an ArgumentError when the strategy is unknown" do
|
141
|
+
expect { described_class.new(@app, :strategy => :g_code) }.to raise_error(ArgumentError, /unknown/)
|
142
|
+
end
|
143
|
+
|
144
|
+
describe ":cookie" do
|
145
|
+
before { @downtime = Rack::Utils.escape(@downtime) }
|
146
|
+
|
147
|
+
it "sets the downtime from the default cookie name" do
|
148
|
+
req = Rack::Test::Session.new(described_class.new(@app, :strategy => :cookie))
|
149
|
+
req.set_cookie "__dt__=#@downtime"
|
150
|
+
req.get "/"
|
151
|
+
|
152
|
+
expect(req.last_response.body).to eq(@body)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "sets the downtime from the given cookie name" do
|
156
|
+
req = Rack::Test::Session.new(described_class.new(@app, :strategy => { :cookie => "QWERTY" }))
|
157
|
+
req.set_cookie "QWERTY=#@downtime"
|
158
|
+
req.get "/"
|
159
|
+
|
160
|
+
expect(req.last_response.body).to eq(@body)
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
# context "with a new default name" do
|
165
|
+
# before { Rack::Downtime::Strategy::Cookie.named = "xxx" }
|
166
|
+
# after { Rack::Downtime::Strategy::Cookie.named = nil }
|
167
|
+
|
168
|
+
|
169
|
+
# req = Rack::Test::Session.new(described_class.new(@app, :strategy => :cookie))
|
170
|
+
# req.set_cookie "__dt__=#@downtime" # esc?
|
171
|
+
# req.get "/"
|
172
|
+
|
173
|
+
# expect(req.last_response.body).to eq(@body)
|
174
|
+
# end
|
175
|
+
# end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe ":env" do
|
179
|
+
it "sets the downtime from the RACK_DOWNTIME environment variable" do
|
180
|
+
req = Rack::Test::Session.new(described_class.new(@app, :strategy => :env))
|
181
|
+
req.get "/", nil, "RACK_DOWNTIME" => @downtime
|
182
|
+
|
183
|
+
expect(req.last_response.body).to eq(@body)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe ":header" do
|
188
|
+
it "sets the downtime from the X-Downtime HTTP header" do
|
189
|
+
req = Rack::Test::Session.new(described_class.new(@app, :strategy => :header))
|
190
|
+
req.header "X-Downtime", @downtime
|
191
|
+
req.get "/"
|
192
|
+
|
193
|
+
expect(req.last_response.body).to eq(@body)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe ":query" do
|
198
|
+
it "sets the downtime from the default query string param" do
|
199
|
+
req = Rack::Test::Session.new(described_class.new(@app, :strategy => :query))
|
200
|
+
req.get "/", :__dt__ => @downtime
|
201
|
+
|
202
|
+
expect(req.last_response.body).to eq(@body)
|
203
|
+
end
|
204
|
+
|
205
|
+
it "sets the downtime from the given query string param" do
|
206
|
+
req = Rack::Test::Session.new(described_class.new(@app, :strategy => { :query => "param" }))
|
207
|
+
req.get "/", :param => @downtime
|
208
|
+
|
209
|
+
expect(req.last_response.body).to eq(@body)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
describe ":file" do
|
214
|
+
it "sets the downtime from the given downtime file" do
|
215
|
+
path = File.join(@tmp, "im_goin_dooooown.txt")
|
216
|
+
File.write(path, @downtime)
|
217
|
+
|
218
|
+
req = Rack::Test::Session.new(described_class.new(@app, :strategy => { :file => path }))
|
219
|
+
req.get "/"
|
220
|
+
|
221
|
+
expect(req.last_response.body).to eq(@body)
|
222
|
+
end
|
223
|
+
|
224
|
+
describe "when the downtime is updated" do
|
225
|
+
it "detects the new downtime" do
|
226
|
+
req = Rack::Test::Session.new(described_class.new(@app, :strategy => { :file => "downtime.txt" }))
|
227
|
+
req.get "/"
|
228
|
+
|
229
|
+
@dates[1] = DateTime.new(2014,12,31)
|
230
|
+
new_body = @dates.map { |d| d.strftime("%s") }.join("/")
|
231
|
+
new_downtime = time_interval(@dates)
|
232
|
+
|
233
|
+
# So we have a different mtime
|
234
|
+
sleep 1
|
235
|
+
File.write("downtime.txt", new_downtime)
|
236
|
+
|
237
|
+
req.get "/"
|
238
|
+
|
239
|
+
expect(req.last_response.body).to eq(new_body)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require "rspec"
|
2
|
+
require "nokogiri"
|
3
|
+
require "rack/test"
|
4
|
+
require "rack/downtime"
|
5
|
+
|
6
|
+
RSpec.configure do |c|
|
7
|
+
c.include Module.new {
|
8
|
+
def time_interval(times)
|
9
|
+
times.map { |d| d.strftime("%FT%X%Z") }.join("/")
|
10
|
+
end
|
11
|
+
|
12
|
+
def new_app(response = {})
|
13
|
+
code = response[:code] || 200
|
14
|
+
headers = response[:headers] || {}
|
15
|
+
body = response[:body] || "<!DOCTYPE html><html><body><p>Content!</p></body></html>"
|
16
|
+
|
17
|
+
headers["Content-Length"]||= body.size
|
18
|
+
headers["Content-Type"] ||= "text/html"
|
19
|
+
|
20
|
+
->(env) do
|
21
|
+
yield env if block_given?
|
22
|
+
|
23
|
+
[code, headers, [body]]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
# Poor man's TestXml
|
30
|
+
RSpec::Matchers.define :eq_html do |expect|
|
31
|
+
match do |actual|
|
32
|
+
Nokogiri::HTML(expect).to_html == Nokogiri::HTML(actual).to_html
|
33
|
+
end
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-downtime
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Skye Shaw
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nokogiri
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.7'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.7'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rack-test
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: |
|
98
|
+
Rack::Downtime provides a variety of ways to easily trigger and display planned maintenance notifications to users
|
99
|
+
while a site is still up. Various strategies are provided that will work with sites of all sizes.
|
100
|
+
email:
|
101
|
+
- skye.shaw@gmail.com
|
102
|
+
executables: []
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files: []
|
105
|
+
files:
|
106
|
+
- ".gitignore"
|
107
|
+
- ".rspec"
|
108
|
+
- ".travis.yml"
|
109
|
+
- Gemfile
|
110
|
+
- LICENSE.txt
|
111
|
+
- README.md
|
112
|
+
- Rakefile
|
113
|
+
- lib/rack/downtime.rb
|
114
|
+
- lib/rack/downtime/strategy.rb
|
115
|
+
- lib/rack/downtime/utils.rb
|
116
|
+
- lib/rack/downtime/version.rb
|
117
|
+
- rack-downtime.gemspec
|
118
|
+
- spec/downtime_spec.rb
|
119
|
+
- spec/spec_helper.rb
|
120
|
+
homepage: https://github.com/sshaw/rack-downtime
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata: {}
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
requirements: []
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 2.2.2
|
141
|
+
signing_key:
|
142
|
+
specification_version: 4
|
143
|
+
summary: Planned downtime management for Rack applications
|
144
|
+
test_files:
|
145
|
+
- spec/downtime_spec.rb
|
146
|
+
- spec/spec_helper.rb
|