stoplight 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/LICENSE.md +1 -1
- data/README.md +216 -206
- data/lib/stoplight.rb +8 -1
- data/lib/stoplight/data_store/base.rb +18 -0
- data/lib/stoplight/data_store/memory.rb +1 -0
- data/lib/stoplight/data_store/redis.rb +2 -0
- data/lib/stoplight/failure.rb +15 -0
- data/lib/stoplight/light.rb +28 -0
- data/lib/stoplight/light/runnable.rb +2 -0
- data/lib/stoplight/notifier/base.rb +6 -0
- data/lib/stoplight/notifier/hip_chat.rb +12 -0
- data/lib/stoplight/notifier/io.rb +5 -0
- data/lib/stoplight/version.rb +5 -0
- data/spec/stoplight/version_spec.rb +9 -0
- data/spec/stoplight_spec.rb +12 -0
- metadata +14 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bcc53c7687a3a7fc307685f81463b7a97aec96bd
|
4
|
+
data.tar.gz: 7fe4bfe9357b1a52ffad84dbba77decbb8875979
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2461d125e6b721bb12d38eddfa0047e823899febb818f7276006cea9a8da70cef5215efe77b6017b0fe3a3b92c389e0927306b385cdf347a3903d048fed989b
|
7
|
+
data.tar.gz: bd7939dd6723a2a405865aa756b9cc4d903b00931eff7796d3276ea98aa3d4b139b23809cd726ee3a17b446ca59a976a0095645283386d10ac2e65f1390c5290
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
This project uses [Semantic Versioning][1].
|
4
4
|
|
5
|
+
## v0.5.2 (2014-11-19)
|
6
|
+
|
7
|
+
- Created a convenience function for creating stoplights.
|
8
|
+
- Created a `VERSION` constant.
|
9
|
+
- Added YARD type documentation for public methods.
|
10
|
+
|
5
11
|
## v0.5.1 (2014-11-19)
|
6
12
|
|
7
13
|
- Fixed a logic bug that incorrectly determined red lights to be yellow.
|
data/LICENSE.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c)
|
1
|
+
Copyright (c) 2015 Cameron Desautels, Taylor Fausak & Justin Steffy
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
4
|
this software and associated documentation files (the "Software"), to deal in
|
data/README.md
CHANGED
@@ -1,17 +1,39 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
1
|
+
<p align="center">
|
2
|
+
<img alt="Stoplight" src="stoplight.png">
|
3
|
+
</p>
|
4
|
+
|
5
|
+
<h1 align="center">
|
6
|
+
<a href="https://github.com/orgsync/stoplight">
|
7
|
+
Stoplight
|
8
|
+
</a>
|
9
|
+
</h1>
|
10
|
+
|
11
|
+
<p align="center">
|
12
|
+
Stoplight is traffic control for code. It's an implementation of
|
13
|
+
the circuit breaker pattern in Ruby.
|
14
|
+
</p>
|
15
|
+
|
16
|
+
<p align="center">
|
17
|
+
<a href="https://rubygems.org/gems/stoplight">
|
18
|
+
<img alt="" src="https://img.shields.io/gem/v/stoplight.svg">
|
19
|
+
</a>
|
20
|
+
<a href="https://travis-ci.org/orgsync/stoplight">
|
21
|
+
<img alt="" src="https://img.shields.io/travis/orgsync/stoplight/master.svg">
|
22
|
+
</a>
|
23
|
+
<a href="https://coveralls.io/r/orgsync/stoplight">
|
24
|
+
<img alt="" src="https://img.shields.io/coveralls/orgsync/stoplight/master.svg">
|
25
|
+
</a>
|
26
|
+
<a href="https://codeclimate.com/github/orgsync/stoplight">
|
27
|
+
<img alt="" src="https://img.shields.io/codeclimate/github/orgsync/stoplight.svg">
|
28
|
+
</a>
|
29
|
+
<a href="https://gemnasium.com/orgsync/stoplight">
|
30
|
+
<img alt="" src="https://img.shields.io/gemnasium/orgsync/stoplight.svg">
|
31
|
+
</a>
|
32
|
+
</p>
|
33
|
+
|
34
|
+
<hr>
|
35
|
+
|
36
|
+
Check out [stoplight-admin][] for controlling your stoplights.
|
15
37
|
|
16
38
|
- [Installation](#installation)
|
17
39
|
- [Setup](#setup)
|
@@ -33,13 +55,13 @@ Check out [stoplight-admin][12] for controlling your stoplights.
|
|
33
55
|
Add it to your Gemfile:
|
34
56
|
|
35
57
|
``` rb
|
36
|
-
gem 'stoplight', '~> 0.5.
|
58
|
+
gem 'stoplight', '~> 0.5.2'
|
37
59
|
```
|
38
60
|
|
39
61
|
Or install it manually:
|
40
62
|
|
41
63
|
``` sh
|
42
|
-
$ gem install stoplight
|
64
|
+
$ gem install stoplight --version '~> 0.5.2'
|
43
65
|
```
|
44
66
|
|
45
67
|
## Setup
|
@@ -48,58 +70,60 @@ $ gem install stoplight
|
|
48
70
|
|
49
71
|
Stoplight uses an in-memory data store out of the box.
|
50
72
|
|
51
|
-
```
|
52
|
-
|
53
|
-
=> true
|
54
|
-
|
55
|
-
=> #<Stoplight::DataStore::Memory:...>
|
73
|
+
``` rb
|
74
|
+
require 'stoplight'
|
75
|
+
# => true
|
76
|
+
Stoplight::Light.default_data_store
|
77
|
+
# => #<Stoplight::DataStore::Memory:...>
|
56
78
|
```
|
57
79
|
|
58
|
-
If you want to use a persistent data store, you'll have to set it
|
59
|
-
the only supported persistent data store is Redis.
|
60
|
-
gem][
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
80
|
+
If you want to use a persistent data store, you'll have to set it
|
81
|
+
up. Currently the only supported persistent data store is Redis.
|
82
|
+
Make sure you have [the Redis gem][] installed before configuring
|
83
|
+
Stoplight.
|
84
|
+
|
85
|
+
``` rb
|
86
|
+
require 'redis'
|
87
|
+
# => true
|
88
|
+
redis = Redis.new
|
89
|
+
# => #<Redis client ...>
|
90
|
+
data_store = Stoplight::DataStore::Redis.new(redis)
|
91
|
+
# => #<Stoplight::DataStore::Redis:...>
|
92
|
+
Stoplight::Light.default_data_store = data_store
|
93
|
+
# => #<Stoplight::DataStore::Redis:...>
|
71
94
|
```
|
72
95
|
|
73
96
|
### Notifiers
|
74
97
|
|
75
98
|
Stoplight sends notifications to standard error by default.
|
76
99
|
|
77
|
-
```
|
78
|
-
|
79
|
-
=> [#<Stoplight::Notifier::IO:...>]
|
100
|
+
``` rb
|
101
|
+
Stoplight::Light.default_notifiers
|
102
|
+
# => [#<Stoplight::Notifier::IO:...>]
|
80
103
|
```
|
81
104
|
|
82
|
-
If you want to send notifications elsewhere, you'll have to set
|
83
|
-
Currently the only other supported notifier is HipChat.
|
84
|
-
HipChat gem][
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
105
|
+
If you want to send notifications elsewhere, you'll have to set
|
106
|
+
them up. Currently the only other supported notifier is HipChat.
|
107
|
+
Make sure you have [the HipChat gem][] installed before configuring
|
108
|
+
Stoplight.
|
109
|
+
|
110
|
+
``` rb
|
111
|
+
require 'hipchat'
|
112
|
+
# => true
|
113
|
+
hip_chat = HipChat::Client.new('token')
|
114
|
+
# => #<HipChat::Client:...>
|
115
|
+
notifier = Stoplight::Notifier::HipChat.new(hip_chat, 'room')
|
116
|
+
# => #<Stoplight::Notifier::HipChat:...>
|
117
|
+
Stoplight::Light.default_notifiers += [notifier]
|
118
|
+
# => [#<Stoplight::Notifier::IO:...>, #<Stoplight::Notifier::HipChat:...>]
|
95
119
|
```
|
96
120
|
|
97
121
|
### Rails
|
98
122
|
|
99
|
-
Stoplight is designed to work seamlessly with Rails. If you want
|
100
|
-
in-memory data store, you don't need to do anything
|
101
|
-
a persistent data store, you'll need
|
102
|
-
Stoplight:
|
123
|
+
Stoplight is designed to work seamlessly with Rails. If you want
|
124
|
+
to use the in-memory data store, you don't need to do anything
|
125
|
+
special. If you want to use a persistent data store, you'll need
|
126
|
+
to configure it. Create an initializer for Stoplight:
|
103
127
|
|
104
128
|
``` rb
|
105
129
|
# config/initializers/stoplight.rb
|
@@ -112,150 +136,152 @@ Stoplight::Light.default_notifiers += [Stoplight::Notifier::HipChat.new(...)]
|
|
112
136
|
|
113
137
|
To get started, create a stoplight:
|
114
138
|
|
115
|
-
```
|
116
|
-
|
117
|
-
=> #<Stoplight::Light:...>
|
139
|
+
``` rb
|
140
|
+
light = Stoplight('example-1') { 22.0 / 7 }
|
141
|
+
# => #<Stoplight::Light:...>
|
118
142
|
```
|
119
143
|
|
120
|
-
Then you can run it and it will return the result of calling the
|
121
|
-
the green state.
|
144
|
+
Then you can run it and it will return the result of calling the
|
145
|
+
block. This is the green state.
|
122
146
|
|
123
|
-
```
|
124
|
-
|
125
|
-
=> 3.142857142857143
|
126
|
-
|
127
|
-
=> "green"
|
147
|
+
``` rb
|
148
|
+
light.run
|
149
|
+
# => 3.142857142857143
|
150
|
+
light.color
|
151
|
+
# => "green"
|
128
152
|
```
|
129
153
|
|
130
|
-
If everything goes well, you shouldn't even be able to tell that
|
131
|
-
stoplight. That's not very interesting though. Let's
|
154
|
+
If everything goes well, you shouldn't even be able to tell that
|
155
|
+
you're using a stoplight. That's not very interesting though. Let's
|
156
|
+
create a failing stoplight:
|
132
157
|
|
133
|
-
```
|
134
|
-
|
135
|
-
=> #<Stoplight::Light:...>
|
158
|
+
``` rb
|
159
|
+
light = Stoplight('example-2') { 1 / 0 }
|
160
|
+
# => #<Stoplight::Light:...>
|
136
161
|
```
|
137
162
|
|
138
|
-
Now when you run it, the error will be recorded and passed through.
|
139
|
-
running it a few times, the stoplight will stop trying and
|
140
|
-
the red state.
|
141
|
-
|
142
|
-
```
|
143
|
-
|
144
|
-
ZeroDivisionError: divided by 0
|
145
|
-
|
146
|
-
ZeroDivisionError: divided by 0
|
147
|
-
|
148
|
-
Switching example-2 from green to red because ZeroDivisionError divided by 0
|
149
|
-
ZeroDivisionError: divided by 0
|
150
|
-
|
151
|
-
Stoplight::Error::RedLight: example-2
|
152
|
-
|
153
|
-
=> "red"
|
163
|
+
Now when you run it, the error will be recorded and passed through.
|
164
|
+
After running it a few times, the stoplight will stop trying and
|
165
|
+
fail fast. This is the red state.
|
166
|
+
|
167
|
+
``` rb
|
168
|
+
light.run
|
169
|
+
# ZeroDivisionError: divided by 0
|
170
|
+
light.run
|
171
|
+
# ZeroDivisionError: divided by 0
|
172
|
+
light.run
|
173
|
+
# Switching example-2 from green to red because ZeroDivisionError divided by 0
|
174
|
+
# ZeroDivisionError: divided by 0
|
175
|
+
light.run
|
176
|
+
# Stoplight::Error::RedLight: example-2
|
177
|
+
light.color
|
178
|
+
# => "red"
|
154
179
|
```
|
155
180
|
|
156
|
-
When the stoplight changes from green to red, it will notify every
|
157
|
-
notifier.
|
181
|
+
When the stoplight changes from green to red, it will notify every
|
182
|
+
configured notifier.
|
158
183
|
|
159
184
|
### Custom errors
|
160
185
|
|
161
|
-
Some errors shouldn't cause your stoplight to move into the red
|
162
|
-
these are handled elsewhere in your stack and don't
|
163
|
-
good example is `ActiveRecord::RecordNotFound`.
|
164
|
-
|
165
|
-
```
|
166
|
-
|
167
|
-
|
168
|
-
=> #<Stoplight::Light:...>
|
169
|
-
|
170
|
-
ActiveRecord::RecordNotFound: Couldn't find User with ID=123
|
171
|
-
|
172
|
-
ActiveRecord::RecordNotFound: Couldn't find User with ID=123
|
173
|
-
|
174
|
-
ActiveRecord::RecordNotFound: Couldn't find User with ID=123
|
175
|
-
|
176
|
-
=> "green"
|
186
|
+
Some errors shouldn't cause your stoplight to move into the red
|
187
|
+
state. Usually these are handled elsewhere in your stack and don't
|
188
|
+
represent real failures. A good example is `ActiveRecord::RecordNotFound`.
|
189
|
+
|
190
|
+
``` rb
|
191
|
+
light = Stoplight('example-3') { User.find(123) }
|
192
|
+
.with_allowed_errors([ActiveRecord::RecordNotFound])
|
193
|
+
# => #<Stoplight::Light:...>
|
194
|
+
light.run
|
195
|
+
# ActiveRecord::RecordNotFound: Couldn't find User with ID=123
|
196
|
+
light.run
|
197
|
+
# ActiveRecord::RecordNotFound: Couldn't find User with ID=123
|
198
|
+
light.run
|
199
|
+
# ActiveRecord::RecordNotFound: Couldn't find User with ID=123
|
200
|
+
light.color
|
201
|
+
# => "green"
|
177
202
|
```
|
178
203
|
|
179
204
|
### Custom fallback
|
180
205
|
|
181
|
-
By default, stoplights will re-raise errors when they're green.
|
182
|
-
red, they'll raise a `Stoplight::Error::RedLight`
|
183
|
-
fallback that will be called in both of
|
184
|
-
if the light was green.
|
185
|
-
|
186
|
-
```
|
187
|
-
|
188
|
-
|
189
|
-
=> #<Stoplight::Light:..>
|
190
|
-
|
191
|
-
#<ZeroDivisionError: divided by 0>
|
192
|
-
=> "default"
|
193
|
-
|
194
|
-
#<ZeroDivisionError: divided by 0>
|
195
|
-
=> "default"
|
196
|
-
|
197
|
-
Switching example-4 from green to red because ZeroDivisionError divided by 0
|
198
|
-
#<ZeroDivisionError: divided by 0>
|
199
|
-
=> "default"
|
200
|
-
|
201
|
-
nil
|
202
|
-
=> "default"
|
206
|
+
By default, stoplights will re-raise errors when they're green.
|
207
|
+
When they're red, they'll raise a `Stoplight::Error::RedLight`
|
208
|
+
error. You can provide a fallback that will be called in both of
|
209
|
+
these cases. It will be passed the error if the light was green.
|
210
|
+
|
211
|
+
``` rb
|
212
|
+
light = Stoplight('example-4') { 1 / 0 }
|
213
|
+
.with_fallback { |e| p e; 'default' }
|
214
|
+
# => #<Stoplight::Light:..>
|
215
|
+
light.run
|
216
|
+
# #<ZeroDivisionError: divided by 0>
|
217
|
+
# => "default"
|
218
|
+
light.run
|
219
|
+
# #<ZeroDivisionError: divided by 0>
|
220
|
+
# => "default"
|
221
|
+
light.run
|
222
|
+
# Switching example-4 from green to red because ZeroDivisionError divided by 0
|
223
|
+
# #<ZeroDivisionError: divided by 0>
|
224
|
+
# => "default"
|
225
|
+
light.run
|
226
|
+
# nil
|
227
|
+
# => "default"
|
203
228
|
```
|
204
229
|
|
205
230
|
### Custom threshold
|
206
231
|
|
207
|
-
Some bits of code might be allowed to fail more or less frequently
|
208
|
-
You can configure this by setting a custom threshold
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
RuntimeError
|
217
|
-
|
218
|
-
|
232
|
+
Some bits of code might be allowed to fail more or less frequently
|
233
|
+
than others. You can configure this by setting a custom threshold
|
234
|
+
in seconds.
|
235
|
+
|
236
|
+
``` rb
|
237
|
+
light = Stoplight('example-5') { fail }
|
238
|
+
.with_threshold(1)
|
239
|
+
# => #<Stoplight::Light:...>
|
240
|
+
light.run
|
241
|
+
# Switching example-5 from green to red because RuntimeError
|
242
|
+
# RuntimeError:
|
243
|
+
light.run
|
244
|
+
# Stoplight::Error::RedLight: example-5
|
219
245
|
```
|
220
246
|
|
221
247
|
### Custom timeout
|
222
248
|
|
223
|
-
Stoplights will automatically attempt to recover after a certain
|
224
|
-
A light in the red state for longer than the timeout
|
225
|
-
yellow state. This timeout is customizable.
|
226
|
-
|
227
|
-
```
|
228
|
-
|
229
|
-
|
230
|
-
=> #<Stoplight::Light:...>
|
231
|
-
|
232
|
-
RuntimeError:
|
233
|
-
|
234
|
-
RuntimeError:
|
235
|
-
|
236
|
-
Switching example-6 from green to red because RuntimeError
|
237
|
-
RuntimeError:
|
238
|
-
|
239
|
-
=> 1
|
240
|
-
|
241
|
-
=> "yellow"
|
242
|
-
|
243
|
-
RuntimeError:
|
249
|
+
Stoplights will automatically attempt to recover after a certain
|
250
|
+
amount of time. A light in the red state for longer than the timeout
|
251
|
+
will transition to the yellow state. This timeout is customizable.
|
252
|
+
|
253
|
+
``` rb
|
254
|
+
light = Stoplight('example-6') { fail }
|
255
|
+
.with_timeout(1)
|
256
|
+
# => #<Stoplight::Light:...>
|
257
|
+
light.run
|
258
|
+
# RuntimeError:
|
259
|
+
light.run
|
260
|
+
# RuntimeError:
|
261
|
+
light.run
|
262
|
+
# Switching example-6 from green to red because RuntimeError
|
263
|
+
# RuntimeError:
|
264
|
+
sleep(1)
|
265
|
+
# => 1
|
266
|
+
light.color
|
267
|
+
# => "yellow"
|
268
|
+
light.run
|
269
|
+
# RuntimeError:
|
244
270
|
```
|
245
271
|
|
246
272
|
Set the timeout to `-1` to disable automatic recovery.
|
247
273
|
|
248
274
|
### Rails
|
249
275
|
|
250
|
-
Stoplight was designed to wrap Rails actions with minimal effort.
|
251
|
-
example configuration:
|
276
|
+
Stoplight was designed to wrap Rails actions with minimal effort.
|
277
|
+
Here's an example configuration:
|
252
278
|
|
253
279
|
``` rb
|
254
280
|
class ApplicationController < ActionController::Base
|
255
281
|
around_action :stoplight
|
256
282
|
private
|
257
283
|
def stoplight(&block)
|
258
|
-
Stoplight
|
284
|
+
Stoplight("#{params[:controller]}##{params[:action]}", &block)
|
259
285
|
.with_allowed_errors([ActiveRecord::RecordNotFound])
|
260
286
|
.with_fallback do |error|
|
261
287
|
Rails.logger.error(error)
|
@@ -270,55 +296,39 @@ end
|
|
270
296
|
|
271
297
|
### Locking
|
272
298
|
|
273
|
-
Although stoplights can operate on their own, occasionally you may
|
274
|
-
override the default behavior. You can lock a light in
|
275
|
-
state using `set_state`.
|
276
|
-
|
277
|
-
```
|
278
|
-
|
279
|
-
=> #<Stoplight::Light:..>
|
280
|
-
|
281
|
-
=> true
|
282
|
-
|
283
|
-
=> "locked_red"
|
284
|
-
|
285
|
-
Stoplight::Error::RedLight: example-7
|
299
|
+
Although stoplights can operate on their own, occasionally you may
|
300
|
+
want to override the default behavior. You can lock a light in
|
301
|
+
either the green or red state using `set_state`.
|
302
|
+
|
303
|
+
``` rb
|
304
|
+
light = Stoplight('example-7') { true }
|
305
|
+
# => #<Stoplight::Light:..>
|
306
|
+
light.run
|
307
|
+
# => true
|
308
|
+
light.data_store.set_state(light, Stoplight::State::LOCKED_RED)
|
309
|
+
# => "locked_red"
|
310
|
+
light.run
|
311
|
+
# Stoplight::Error::RedLight: example-7
|
286
312
|
```
|
287
313
|
|
288
|
-
**Code in locked red lights may still run under certain conditions!**
|
289
|
-
have configured a custom data store and that data store
|
290
|
-
switch over to using a blank in-memory data
|
291
|
-
locked state of any stoplights.
|
314
|
+
**Code in locked red lights may still run under certain conditions!**
|
315
|
+
If you have configured a custom data store and that data store
|
316
|
+
fails, Stoplight will switch over to using a blank in-memory data
|
317
|
+
store. That means you will lose the locked state of any stoplights.
|
292
318
|
|
293
319
|
## Credits
|
294
320
|
|
295
|
-
Stoplight is brought to you by [@camdez][
|
296
|
-
[@OrgSync][
|
321
|
+
Stoplight is brought to you by [@camdez][] and [@tfausak][] from
|
322
|
+
[@OrgSync][]. We were inspired by Martin Fowler's [CircuitBreaker][]
|
297
323
|
article.
|
298
324
|
|
299
|
-
|
300
|
-
|
301
|
-
[
|
302
|
-
|
303
|
-
[
|
304
|
-
[
|
305
|
-
[
|
306
|
-
[
|
307
|
-
[
|
308
|
-
[
|
309
|
-
[7]: https://coveralls.io/r/orgsync/stoplight
|
310
|
-
[8]: https://codeclimate.com/github/orgsync/stoplight/badges/gpa.svg
|
311
|
-
[9]: https://codeclimate.com/github/orgsync/stoplight
|
312
|
-
[10]: https://gemnasium.com/orgsync/stoplight.svg
|
313
|
-
[11]: https://gemnasium.com/orgsync/stoplight
|
314
|
-
[12]: https://github.com/orgsync/stoplight-admin
|
315
|
-
[13]: https://rubygems.org/gems/redis
|
316
|
-
[14]: https://rubygems.org/gems/hipchat
|
317
|
-
[15]: https://github.com/camdez
|
318
|
-
[16]: https://github.com/tfausak
|
319
|
-
[17]: https://github.com/OrgSync
|
320
|
-
[18]: http://martinfowler.com/bliki/CircuitBreaker.html
|
321
|
-
[19]: https://github.com/alg/circuit_b
|
322
|
-
[20]: https://github.com/wsargent/circuit_breaker
|
323
|
-
[21]: https://github.com/soundcloud/simple_circuit_breaker
|
324
|
-
[22]: https://github.com/wooga/circuit_breaker
|
325
|
+
Emoji provided free by [Emoji One][].
|
326
|
+
|
327
|
+
[stoplight-admin]: https://github.com/orgsync/stoplight-admin
|
328
|
+
[the redis gem]: https://rubygems.org/gems/redis
|
329
|
+
[the hipchat gem]: https://rubygems.org/gems/hipchat
|
330
|
+
[@camdez]: https://github.com/camdez
|
331
|
+
[@tfausak]: https://github.com/tfausak
|
332
|
+
[@orgsync]: https://github.com/OrgSync
|
333
|
+
[circuitbreaker]: http://martinfowler.com/bliki/CircuitBreaker.html
|
334
|
+
[emoji one]: http://www.emojione.com
|
data/lib/stoplight.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
+
module Stoplight
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'stoplight/version'
|
7
|
+
|
3
8
|
require 'stoplight/color'
|
4
9
|
require 'stoplight/error'
|
5
10
|
require 'stoplight/failure'
|
@@ -20,5 +25,7 @@ require 'stoplight/default'
|
|
20
25
|
require 'stoplight/light/runnable'
|
21
26
|
require 'stoplight/light'
|
22
27
|
|
23
|
-
|
28
|
+
# @see Stoplight::Light#initialize
|
29
|
+
def Stoplight(name, &code) # rubocop:disable Style/MethodName
|
30
|
+
Stoplight::Light.new(name, &code)
|
24
31
|
end
|
@@ -2,35 +2,53 @@
|
|
2
2
|
|
3
3
|
module Stoplight
|
4
4
|
module DataStore
|
5
|
+
# @abstract
|
5
6
|
class Base
|
7
|
+
# @return [Array<String>]
|
6
8
|
def names
|
7
9
|
fail NotImplementedError
|
8
10
|
end
|
9
11
|
|
12
|
+
# @param _light [Light]
|
13
|
+
# @return [Array(Array<Failure>, String)]
|
10
14
|
def get_all(_light)
|
11
15
|
fail NotImplementedError
|
12
16
|
end
|
13
17
|
|
18
|
+
# @param _light [Light]
|
19
|
+
# @return [Array<Failure>]
|
14
20
|
def get_failures(_light)
|
15
21
|
fail NotImplementedError
|
16
22
|
end
|
17
23
|
|
24
|
+
# @param _light [Light]
|
25
|
+
# @param _failure [Failure]
|
26
|
+
# @return [Fixnum]
|
18
27
|
def record_failure(_light, _failure)
|
19
28
|
fail NotImplementedError
|
20
29
|
end
|
21
30
|
|
31
|
+
# @param _light [Light]
|
32
|
+
# @return [Array<Failure>]
|
22
33
|
def clear_failures(_light)
|
23
34
|
fail NotImplementedError
|
24
35
|
end
|
25
36
|
|
37
|
+
# @param _light [Light]
|
38
|
+
# @return [String]
|
26
39
|
def get_state(_light)
|
27
40
|
fail NotImplementedError
|
28
41
|
end
|
29
42
|
|
43
|
+
# @param _light [Light]
|
44
|
+
# @param _state [String]
|
45
|
+
# @return [String]
|
30
46
|
def set_state(_light, _state)
|
31
47
|
fail NotImplementedError
|
32
48
|
end
|
33
49
|
|
50
|
+
# @param _light [Light]
|
51
|
+
# @return [String]
|
34
52
|
def clear_state(_light)
|
35
53
|
fail NotImplementedError
|
36
54
|
end
|
data/lib/stoplight/failure.rb
CHANGED
@@ -5,14 +5,23 @@ require 'time'
|
|
5
5
|
|
6
6
|
module Stoplight
|
7
7
|
class Failure
|
8
|
+
# @return [String]
|
8
9
|
attr_reader :error_class
|
10
|
+
# @return [String]
|
9
11
|
attr_reader :error_message
|
12
|
+
# @return [Time]
|
10
13
|
attr_reader :time
|
11
14
|
|
15
|
+
# @param error [Exception]
|
16
|
+
# @return (see #initialize)
|
12
17
|
def self.from_error(error)
|
13
18
|
new(error.class.name, error.message, Time.new)
|
14
19
|
end
|
15
20
|
|
21
|
+
# @param json [String]
|
22
|
+
# @return (see #initialize)
|
23
|
+
# @raise [JSON::ParserError]
|
24
|
+
# @raise [ArgumentError]
|
16
25
|
def self.from_json(json)
|
17
26
|
object = JSON.parse(json)
|
18
27
|
|
@@ -23,18 +32,24 @@ module Stoplight
|
|
23
32
|
new(error_class, error_message, time)
|
24
33
|
end
|
25
34
|
|
35
|
+
# @param error_class [String]
|
36
|
+
# @param error_message [String]
|
37
|
+
# @param time [Time]
|
26
38
|
def initialize(error_class, error_message, time)
|
27
39
|
@error_class = error_class
|
28
40
|
@error_message = error_message
|
29
41
|
@time = time
|
30
42
|
end
|
31
43
|
|
44
|
+
# @param other [Failure]
|
45
|
+
# @return [Boolean]
|
32
46
|
def ==(other)
|
33
47
|
error_class == other.error_class &&
|
34
48
|
error_message == other.error_message &&
|
35
49
|
time == other.time
|
36
50
|
end
|
37
51
|
|
52
|
+
# @return [String]
|
38
53
|
def to_json
|
39
54
|
JSON.generate(
|
40
55
|
error: {
|
data/lib/stoplight/light.rb
CHANGED
@@ -4,19 +4,31 @@ module Stoplight
|
|
4
4
|
class Light
|
5
5
|
include Runnable
|
6
6
|
|
7
|
+
# @return [Array<Exception>]
|
7
8
|
attr_reader :allowed_errors
|
9
|
+
# @return [Proc]
|
8
10
|
attr_reader :code
|
11
|
+
# @return [DataStore::Base]
|
9
12
|
attr_reader :data_store
|
13
|
+
# @return [Proc]
|
10
14
|
attr_reader :error_notifier
|
15
|
+
# @return [Proc, nil]
|
11
16
|
attr_reader :fallback
|
17
|
+
# @return [String]
|
12
18
|
attr_reader :name
|
19
|
+
# @return [Array<Notifier::Base>]
|
13
20
|
attr_reader :notifiers
|
21
|
+
# @return [Fixnum]
|
14
22
|
attr_reader :threshold
|
23
|
+
# @return [Float]
|
15
24
|
attr_reader :timeout
|
16
25
|
|
17
26
|
class << self
|
27
|
+
# @return [DataStore::Base]
|
18
28
|
attr_accessor :default_data_store
|
29
|
+
# @return [Proc]
|
19
30
|
attr_accessor :default_error_notifier
|
31
|
+
# @return [Array<Notifier::Base>]
|
20
32
|
attr_accessor :default_notifiers
|
21
33
|
end
|
22
34
|
|
@@ -24,6 +36,8 @@ module Stoplight
|
|
24
36
|
@default_error_notifier = Default::ERROR_NOTIFIER
|
25
37
|
@default_notifiers = Default::NOTIFIERS
|
26
38
|
|
39
|
+
# @param name [String]
|
40
|
+
# @yield []
|
27
41
|
def initialize(name, &code)
|
28
42
|
@name = name
|
29
43
|
@code = code
|
@@ -37,36 +51,50 @@ module Stoplight
|
|
37
51
|
@timeout = Default::TIMEOUT
|
38
52
|
end
|
39
53
|
|
54
|
+
# @param allowed_errors [Array<Exception>]
|
55
|
+
# @return [self]
|
40
56
|
def with_allowed_errors(allowed_errors)
|
41
57
|
@allowed_errors = Default::ALLOWED_ERRORS + allowed_errors
|
42
58
|
self
|
43
59
|
end
|
44
60
|
|
61
|
+
# @param data_store [DataStore::Base]
|
62
|
+
# @return [self]
|
45
63
|
def with_data_store(data_store)
|
46
64
|
@data_store = data_store
|
47
65
|
self
|
48
66
|
end
|
49
67
|
|
68
|
+
# @yieldparam error [Exception]
|
69
|
+
# @return [self]
|
50
70
|
def with_error_notifier(&error_notifier)
|
51
71
|
@error_notifier = error_notifier
|
52
72
|
self
|
53
73
|
end
|
54
74
|
|
75
|
+
# @yieldparam error [Exception, nil]
|
76
|
+
# @return [self]
|
55
77
|
def with_fallback(&fallback)
|
56
78
|
@fallback = fallback
|
57
79
|
self
|
58
80
|
end
|
59
81
|
|
82
|
+
# @param notifiers [Array<Notifier::Base>]
|
83
|
+
# @return [self]
|
60
84
|
def with_notifiers(notifiers)
|
61
85
|
@notifiers = notifiers
|
62
86
|
self
|
63
87
|
end
|
64
88
|
|
89
|
+
# @param threshold [Fixnum]
|
90
|
+
# @return [self]
|
65
91
|
def with_threshold(threshold)
|
66
92
|
@threshold = threshold
|
67
93
|
self
|
68
94
|
end
|
69
95
|
|
96
|
+
# @param timeout [Float]
|
97
|
+
# @return [self]
|
70
98
|
def with_timeout(timeout)
|
71
99
|
@timeout = timeout
|
72
100
|
self
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module Stoplight
|
4
4
|
class Light
|
5
5
|
module Runnable
|
6
|
+
# @return [String]
|
6
7
|
def color
|
7
8
|
failures, state = failures_and_state
|
8
9
|
failure = failures.first
|
@@ -16,6 +17,7 @@ module Stoplight
|
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
20
|
+
# @raise [Error::RedLight]
|
19
21
|
def run
|
20
22
|
case color
|
21
23
|
when Color::GREEN then run_green
|
@@ -2,7 +2,13 @@
|
|
2
2
|
|
3
3
|
module Stoplight
|
4
4
|
module Notifier
|
5
|
+
# @abstract
|
5
6
|
class Base
|
7
|
+
# @param _light [Light]
|
8
|
+
# @param _from_color [String]
|
9
|
+
# @param _to_color [String]
|
10
|
+
# @param _error [Exception, nil]
|
11
|
+
# @return [String]
|
6
12
|
def notify(_light, _from_color, _to_color, _error)
|
7
13
|
fail NotImplementedError
|
8
14
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module Stoplight
|
4
4
|
module Notifier
|
5
|
+
# @see Base
|
5
6
|
class HipChat < Base
|
6
7
|
DEFAULT_OPTIONS = {
|
7
8
|
color: 'purple',
|
@@ -9,11 +10,22 @@ module Stoplight
|
|
9
10
|
notify: true
|
10
11
|
}.freeze
|
11
12
|
|
13
|
+
# @return [Proc]
|
12
14
|
attr_reader :formatter
|
15
|
+
# @return [::HipChat::Client]
|
13
16
|
attr_reader :hip_chat
|
17
|
+
# @return [Hash{Symbol => Object}]
|
14
18
|
attr_reader :options
|
19
|
+
# @return [String]
|
15
20
|
attr_reader :room
|
16
21
|
|
22
|
+
# @param hip_chat [::HipChat::Client]
|
23
|
+
# @param room [String]
|
24
|
+
# @param formatter [Proc, nil]
|
25
|
+
# @param options [Hash{Symbol => Object}]
|
26
|
+
# @option options [String] :color
|
27
|
+
# @option options [String] :message_format
|
28
|
+
# @option options [Boolean] :notify
|
17
29
|
def initialize(hip_chat, room, formatter = nil, options = {})
|
18
30
|
@hip_chat = hip_chat
|
19
31
|
@room = room
|
@@ -4,10 +4,15 @@ require 'stringio'
|
|
4
4
|
|
5
5
|
module Stoplight
|
6
6
|
module Notifier
|
7
|
+
# @see Base
|
7
8
|
class IO < Base
|
9
|
+
# @return [Proc]
|
8
10
|
attr_reader :formatter
|
11
|
+
# @return [::IO]
|
9
12
|
attr_reader :io
|
10
13
|
|
14
|
+
# @param io [::IO]
|
15
|
+
# @param formatter [Proc, nil]
|
11
16
|
def initialize(io, formatter = nil)
|
12
17
|
@io = io
|
13
18
|
@formatter = formatter || Default::FORMATTER
|
data/spec/stoplight_spec.rb
CHANGED
@@ -7,3 +7,15 @@ describe Stoplight do
|
|
7
7
|
expect(described_class).to be_a(Module)
|
8
8
|
end
|
9
9
|
end
|
10
|
+
|
11
|
+
describe 'Stoplight' do
|
12
|
+
subject(:light) { Stoplight(name, &code) }
|
13
|
+
let(:name) { ('a'..'z').to_a.shuffle.join }
|
14
|
+
let(:code) { -> {} }
|
15
|
+
|
16
|
+
it 'creates a stoplight' do
|
17
|
+
expect(light).to be_a(Stoplight::Light)
|
18
|
+
expect(light.name).to eql(name)
|
19
|
+
expect(light.code).to eql(code)
|
20
|
+
end
|
21
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stoplight
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cameron Desautels
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2015-02-13 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: benchmark-ips
|
@@ -74,56 +74,56 @@ dependencies:
|
|
74
74
|
requirements:
|
75
75
|
- - ~>
|
76
76
|
- !ruby/object:Gem::Version
|
77
|
-
version: '10.
|
77
|
+
version: '10.4'
|
78
78
|
type: :development
|
79
79
|
prerelease: false
|
80
80
|
version_requirements: !ruby/object:Gem::Requirement
|
81
81
|
requirements:
|
82
82
|
- - ~>
|
83
83
|
- !ruby/object:Gem::Version
|
84
|
-
version: '10.
|
84
|
+
version: '10.4'
|
85
85
|
- !ruby/object:Gem::Dependency
|
86
86
|
name: redis
|
87
87
|
requirement: !ruby/object:Gem::Requirement
|
88
88
|
requirements:
|
89
89
|
- - ~>
|
90
90
|
- !ruby/object:Gem::Version
|
91
|
-
version: '3.
|
91
|
+
version: '3.2'
|
92
92
|
type: :development
|
93
93
|
prerelease: false
|
94
94
|
version_requirements: !ruby/object:Gem::Requirement
|
95
95
|
requirements:
|
96
96
|
- - ~>
|
97
97
|
- !ruby/object:Gem::Version
|
98
|
-
version: '3.
|
98
|
+
version: '3.2'
|
99
99
|
- !ruby/object:Gem::Dependency
|
100
100
|
name: rspec
|
101
101
|
requirement: !ruby/object:Gem::Requirement
|
102
102
|
requirements:
|
103
103
|
- - ~>
|
104
104
|
- !ruby/object:Gem::Version
|
105
|
-
version: '3.
|
105
|
+
version: '3.2'
|
106
106
|
type: :development
|
107
107
|
prerelease: false
|
108
108
|
version_requirements: !ruby/object:Gem::Requirement
|
109
109
|
requirements:
|
110
110
|
- - ~>
|
111
111
|
- !ruby/object:Gem::Version
|
112
|
-
version: '3.
|
112
|
+
version: '3.2'
|
113
113
|
- !ruby/object:Gem::Dependency
|
114
114
|
name: rubocop
|
115
115
|
requirement: !ruby/object:Gem::Requirement
|
116
116
|
requirements:
|
117
117
|
- - ~>
|
118
118
|
- !ruby/object:Gem::Version
|
119
|
-
version: '0.
|
119
|
+
version: '0.29'
|
120
120
|
type: :development
|
121
121
|
prerelease: false
|
122
122
|
version_requirements: !ruby/object:Gem::Requirement
|
123
123
|
requirements:
|
124
124
|
- - ~>
|
125
125
|
- !ruby/object:Gem::Version
|
126
|
-
version: '0.
|
126
|
+
version: '0.29'
|
127
127
|
- !ruby/object:Gem::Dependency
|
128
128
|
name: timecop
|
129
129
|
requirement: !ruby/object:Gem::Requirement
|
@@ -181,6 +181,7 @@ files:
|
|
181
181
|
- lib/stoplight/notifier/hip_chat.rb
|
182
182
|
- lib/stoplight/notifier/io.rb
|
183
183
|
- lib/stoplight/state.rb
|
184
|
+
- lib/stoplight/version.rb
|
184
185
|
- spec/spec_helper.rb
|
185
186
|
- spec/stoplight/color_spec.rb
|
186
187
|
- spec/stoplight/data_store/base_spec.rb
|
@@ -197,6 +198,7 @@ files:
|
|
197
198
|
- spec/stoplight/notifier/io_spec.rb
|
198
199
|
- spec/stoplight/notifier_spec.rb
|
199
200
|
- spec/stoplight/state_spec.rb
|
201
|
+
- spec/stoplight/version_spec.rb
|
200
202
|
- spec/stoplight_spec.rb
|
201
203
|
homepage: https://github.com/orgsync/stoplight
|
202
204
|
licenses:
|
@@ -218,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
218
220
|
version: '0'
|
219
221
|
requirements: []
|
220
222
|
rubyforge_project:
|
221
|
-
rubygems_version: 2.4.
|
223
|
+
rubygems_version: 2.4.5
|
222
224
|
signing_key:
|
223
225
|
specification_version: 4
|
224
226
|
summary: Traffic control for code.
|
@@ -239,5 +241,6 @@ test_files:
|
|
239
241
|
- spec/stoplight/notifier/io_spec.rb
|
240
242
|
- spec/stoplight/notifier_spec.rb
|
241
243
|
- spec/stoplight/state_spec.rb
|
244
|
+
- spec/stoplight/version_spec.rb
|
242
245
|
- spec/stoplight_spec.rb
|
243
246
|
has_rdoc:
|