stoplight 0.5.1 → 0.5.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 +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:
|