stoplight 5.0.3 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +162 -27
- data/lib/generators/stoplight/install/USAGE +16 -0
- data/lib/generators/stoplight/install/install_generator.rb +75 -0
- data/lib/generators/stoplight/install/templates/stoplight.rb.erb +18 -0
- data/lib/stoplight/admin.rb +1 -1
- data/lib/stoplight/config/compatibility_result.rb +54 -0
- data/lib/stoplight/config/dsl.rb +97 -0
- data/lib/stoplight/config/library_default_config.rb +13 -21
- data/lib/stoplight/config/system_config.rb +7 -0
- data/lib/stoplight/config/user_default_config.rb +19 -17
- data/lib/stoplight/data_store/fail_safe.rb +10 -3
- data/lib/stoplight/data_store/memory.rb +5 -0
- data/lib/stoplight/data_store/redis.rb +4 -0
- data/lib/stoplight/default.rb +4 -5
- data/lib/stoplight/light/config.rb +87 -94
- data/lib/stoplight/metadata.rb +16 -0
- data/lib/stoplight/notifier/fail_safe.rb +5 -2
- data/lib/stoplight/traffic_control/base.rb +35 -0
- data/lib/stoplight/traffic_control/{consecutive_failures.rb → consecutive_errors.rb} +17 -5
- data/lib/stoplight/traffic_control/error_rate.rb +49 -0
- data/lib/stoplight/traffic_recovery/base.rb +27 -0
- data/lib/stoplight/traffic_recovery/consecutive_successes.rb +66 -0
- data/lib/stoplight/version.rb +1 -1
- data/lib/stoplight.rb +46 -14
- metadata +15 -6
- data/lib/stoplight/config/config_provider.rb +0 -62
- data/lib/stoplight/traffic_recovery/single_success.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b9625eb129c8bbefdab0e3c9552bd6abe6d146c6ffaaab8f25edcb14ddf29db
|
4
|
+
data.tar.gz: db63d134926cceef9e46ed7b0dd72156fa1d55a883f116626a46ee1aaa7af9ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c59ce8f66fdf2871c2998e59adec143ad563e2815f3ebeaf7e8555a5ca39c65062fc0b067067067e1c7541c39db99d3724539eca6e26102abc5fd5f908a2326
|
7
|
+
data.tar.gz: 7c9c7a289d356acfbdbda23f0f28f32d0379999f644e7eae447a9f60b6a6ac05fc73c01af0118f05158802a53eabe449ec03ba5cbdad0de3b2b7148b2b91aa22
|
data/README.md
CHANGED
@@ -13,10 +13,10 @@ Stoplight is traffic control for code. It's an implementation of the circuit bre
|
|
13
13
|
the documentation of the previous version 4.x, you can find it [here](https://github.com/bolshakov/stoplight/tree/v4.1.1).
|
14
14
|
|
15
15
|
Stoplight helps your application gracefully handle failures in external dependencies
|
16
|
-
(like flaky databases, unreliable APIs, or spotty web services). By wrapping these unreliable
|
16
|
+
(like flaky databases, unreliable APIs, or spotty web services). By wrapping these unreliable
|
17
17
|
calls, Stoplight prevents cascading failures from affecting your entire application.
|
18
18
|
|
19
|
-
**The best part?** Stoplight works with zero configuration out of the box, while offering deep customization when you
|
19
|
+
**The best part?** Stoplight works with zero configuration out of the box, while offering deep customization when you
|
20
20
|
need it.
|
21
21
|
|
22
22
|
## Installation
|
@@ -41,10 +41,10 @@ Stoplight operates like a traffic light with three states:
|
|
41
41
|
|
42
42
|
```mermaid
|
43
43
|
stateDiagram
|
44
|
-
Green --> Red:
|
44
|
+
Green --> Red: Errors reach threshold
|
45
45
|
Red --> Yellow: After cool_off_time
|
46
|
-
Yellow --> Green: Successful
|
47
|
-
Yellow --> Red: Failed
|
46
|
+
Yellow --> Green: Successful recovery
|
47
|
+
Yellow --> Red: Failed recovery
|
48
48
|
Green --> Green: Success
|
49
49
|
|
50
50
|
classDef greenState fill:#28a745,stroke:#1e7e34,stroke-width:2px,color:#fff
|
@@ -60,11 +60,15 @@ stateDiagram
|
|
60
60
|
- **Red**: Failure state. Fast-fails without running the code. (Circuit open)
|
61
61
|
- **Yellow**: Recovery state. Allows a test execution to see if the problem is resolved. (Circuit half-open)
|
62
62
|
|
63
|
-
Stoplight's behavior is controlled by
|
63
|
+
Stoplight's behavior is controlled by two main parameters:
|
64
64
|
|
65
|
-
1. **
|
66
|
-
2. **
|
67
|
-
|
65
|
+
1. **Window Size** (default: `nil`): Time window in which errors are counted toward the threshold. By default, all errors are counted.
|
66
|
+
2. **Threshold** (default: `3`): Number of errors required to transition from green to red.
|
67
|
+
|
68
|
+
Additionally, two other parameters control how Stoplight behaves after it turns red:
|
69
|
+
|
70
|
+
1. **Cool Off Time** (default: `60` seconds): Time to wait in the red state before transitioning to yellow.
|
71
|
+
2. **Recovery Threshold** (default: `1`): Number of successful attempts required to transition from yellow back to green.
|
68
72
|
|
69
73
|
## Basic Usage
|
70
74
|
|
@@ -78,7 +82,7 @@ light = Stoplight("Payment Service")
|
|
78
82
|
result = light.run { payment_gateway.process(order) }
|
79
83
|
```
|
80
84
|
|
81
|
-
When everything works, the light stays green and your code runs normally. If the code fails repeatedly, the
|
85
|
+
When everything works, the light stays green and your code runs normally. If the code fails repeatedly, the
|
82
86
|
light turns red and raises a `Stoplight::Error::RedLight` exception to prevent further calls.
|
83
87
|
|
84
88
|
```ruby
|
@@ -112,7 +116,7 @@ light.color #=> "green"
|
|
112
116
|
|
113
117
|
### Using Fallbacks
|
114
118
|
|
115
|
-
Provide fallbacks to gracefully handle
|
119
|
+
Provide fallbacks to gracefully handle errors:
|
116
120
|
|
117
121
|
```ruby
|
118
122
|
fallback = ->(error) { error ? "Failed: #{error.message}" : "Service unavailable" }
|
@@ -121,25 +125,30 @@ light = Stoplight('example-fallback')
|
|
121
125
|
result = light.run(fallback) { external_service.call }
|
122
126
|
```
|
123
127
|
|
124
|
-
If the light is green but the call fails, the fallback receives the `error`. If the light is red, the fallback
|
128
|
+
If the light is green but the call fails, the fallback receives the `error`. If the light is red, the fallback
|
125
129
|
receives `nil`. In both cases, the return value of the fallback becomes the return value of the `run` method.
|
126
130
|
|
127
131
|
## Admin Panel
|
128
132
|
|
129
133
|
Stoplight goes with a built-in Admin Panel that can track all active Lights and manually lock them in the desired state (`Green` or `Red`). Locking lights in certain states might be helpful in scenarios like E2E testing.
|
130
134
|
|
131
|
-
To add Admin Panel to your Rails project, add this configuration to your `config/routes.rb` file.
|
135
|
+
To add Admin Panel protected by basic authentication to your Rails project, add this configuration to your `config/routes.rb` file.
|
132
136
|
|
133
137
|
```ruby
|
134
138
|
Rails.application.routes.draw do
|
135
139
|
# ...
|
136
140
|
|
141
|
+
Stoplight::Admin.use(Rack::Auth::Basic) do |username, password|
|
142
|
+
username == ENV["STOPLIGHT_ADMIN_USERNAME"] && password == ENV["STOPLIGHT_ADMIN_PASSWORD"]
|
143
|
+
end
|
137
144
|
mount Stoplight::Admin => '/stoplights'
|
138
145
|
|
139
146
|
# ...
|
140
147
|
end
|
141
148
|
```
|
142
149
|
|
150
|
+
Then set up `STOPLIGHT_ADMIN_USERNAME` and `STOPLIGHT_ADMIN_PASSWORD` env variables to access your Admin panel.
|
151
|
+
|
143
152
|
**IMPORTANT:** Stoplight Admin Panel requires you to have `sinatra` and `sinatra-contrib` gems installed. You can either add them to your Gemfile:
|
144
153
|
|
145
154
|
```ruby
|
@@ -158,17 +167,16 @@ gem install sinatra-contrib
|
|
158
167
|
It is possible to run the Admin Panel separately from your application using the `stoplight-admin:<release-version>` docker image.
|
159
168
|
|
160
169
|
```shell
|
161
|
-
docker run --net=host stoplight-admin
|
170
|
+
docker run --net=host bolshakov/stoplight-admin
|
162
171
|
```
|
163
172
|
|
164
173
|
**IMPORTANT:** Standalone Admin Panel should use the same Redis your application uses. To achieve this, set the `REDIS_URL` ENV variable via `-e REDIS_URL=<url-to-your-redis-servier>.` E.g.:
|
165
174
|
|
166
175
|
```shell
|
167
|
-
docker run -e REDIS_URL=redis://localhost:6378 --net=host stoplight-admin
|
176
|
+
docker run -e REDIS_URL=redis://localhost:6378 --net=host bolshakov/stoplight-admin
|
168
177
|
```
|
169
178
|
|
170
|
-
|
171
|
-
## Configuration
|
179
|
+
## Configuration
|
172
180
|
|
173
181
|
### Global Configuration
|
174
182
|
|
@@ -177,9 +185,11 @@ Stoplight allows you to set default values for all lights in your application:
|
|
177
185
|
```ruby
|
178
186
|
Stoplight.configure do |config|
|
179
187
|
# Set default behavior for all stoplights
|
180
|
-
config.
|
188
|
+
config.traffic_control = :error_rate
|
189
|
+
config.window_size = 300
|
190
|
+
config.threshold = 0.5
|
181
191
|
config.cool_off_time = 30
|
182
|
-
config.
|
192
|
+
config.recovery_threshold = 5
|
183
193
|
|
184
194
|
# Set up default data store and notifiers
|
185
195
|
config.data_store = Stoplight::DataStore::Redis.new(redis)
|
@@ -204,10 +214,11 @@ You can also provide settings during creation:
|
|
204
214
|
```ruby
|
205
215
|
data_store = Stoplight::DataStore::Redis.new(Redis.new)
|
206
216
|
|
207
|
-
light = Stoplight("Payment Service",
|
208
|
-
|
217
|
+
light = Stoplight("Payment Service",
|
218
|
+
window_size: 300, # Only count errors in the last five minutes
|
219
|
+
threshold: 5, # 5 errors before turning red
|
209
220
|
cool_off_time: 60, # Wait 60 seconds before attempting recovery
|
210
|
-
|
221
|
+
recovery_threshold: 1, # 1 successful attempt to turn green again
|
211
222
|
data_store: data_store, # Use Redis for persistence
|
212
223
|
tracked_errors: [TimeoutError], # Only count TimeoutError
|
213
224
|
skipped_errors: [ValidationError] # Ignore ValidationError
|
@@ -228,7 +239,7 @@ users_api = base_api.with(
|
|
228
239
|
)
|
229
240
|
```
|
230
241
|
|
231
|
-
The `#with` method creates a new stoplight instance without modifying the original, making it ideal for creating
|
242
|
+
The `#with` method creates a new stoplight instance without modifying the original, making it ideal for creating
|
232
243
|
specialized stoplights from a common configuration.
|
233
244
|
|
234
245
|
## Error Handling
|
@@ -254,6 +265,117 @@ When both methods are used, `skipped_errors` takes precedence over `tracked_erro
|
|
254
265
|
|
255
266
|
## Advanced Configuration
|
256
267
|
|
268
|
+
### Traffic Control Strategies
|
269
|
+
|
270
|
+
You've seen how Stoplight transitions from green to red when errors reach the threshold. But **how exactly does it
|
271
|
+
decide when that threshold is reached?** That's where traffic control strategies come in.
|
272
|
+
|
273
|
+
Stoplight offers two built-in strategies for counting errors:
|
274
|
+
|
275
|
+
#### Consecutive Errors (Default)
|
276
|
+
|
277
|
+
Stops traffic when a specified number of consecutive errors occur. Works with or without time sliding windows.
|
278
|
+
|
279
|
+
```ruby
|
280
|
+
light = Stoplight(
|
281
|
+
"Payment API",
|
282
|
+
traffic_control: :consecutive_errors,
|
283
|
+
threshold: 5,
|
284
|
+
)
|
285
|
+
```
|
286
|
+
|
287
|
+
Counts consecutive errors regardless of when they occurred. Once 5 consecutive errors happen, the stoplight
|
288
|
+
turns red and stops traffic.
|
289
|
+
|
290
|
+
```ruby
|
291
|
+
light = Stoplight(
|
292
|
+
"Payment API",
|
293
|
+
traffic_control: :consecutive_errors,
|
294
|
+
threshold: 5,
|
295
|
+
window_size: 300,
|
296
|
+
)
|
297
|
+
```
|
298
|
+
|
299
|
+
Counts consecutive errors within a 5-minute sliding window. Both conditions must be met: 5 consecutive errors
|
300
|
+
AND at least 5 total errors within the window.
|
301
|
+
|
302
|
+
_This is Stoplight's default strategy when no `traffic_control` is specified._ You can omit `traffic_control` parameter
|
303
|
+
in the above examples:
|
304
|
+
|
305
|
+
```ruby
|
306
|
+
light = Stoplight(
|
307
|
+
"Payment API",
|
308
|
+
threshold: 5,
|
309
|
+
)
|
310
|
+
```
|
311
|
+
|
312
|
+
#### Error Rate
|
313
|
+
|
314
|
+
Stops traffic when the error rate exceeds a percentage within a sliding time window. Requires `window_size` to be
|
315
|
+
configured:
|
316
|
+
|
317
|
+
|
318
|
+
```ruby
|
319
|
+
light = Stoplight(
|
320
|
+
"Payment API",
|
321
|
+
traffic_control: :error_rate,
|
322
|
+
window_size: 300,
|
323
|
+
threshold: 0.5,
|
324
|
+
)
|
325
|
+
```
|
326
|
+
|
327
|
+
Monitors error rate over a 5-minute sliding window. The stoplight turns red when error rate exceeds 50%.
|
328
|
+
|
329
|
+
```ruby
|
330
|
+
light = Stoplight(
|
331
|
+
"Payment API",
|
332
|
+
traffic_control: {
|
333
|
+
error_rate: { min_requests: 20 },
|
334
|
+
},
|
335
|
+
window_size: 300,
|
336
|
+
threshold: 0.5,
|
337
|
+
)
|
338
|
+
```
|
339
|
+
|
340
|
+
Only evaluates error rate after at least 20 requests within the window. Default `min_requests` is 10.
|
341
|
+
|
342
|
+
|
343
|
+
#### When to use:
|
344
|
+
|
345
|
+
* **Consecutive Errors**: Low-medium traffic, simple behavior, occasional spikes expected
|
346
|
+
* **Error Rate**: High traffic, percentage-based SLAs, variable traffic patterns
|
347
|
+
|
348
|
+
### Traffic Recovery Strategies
|
349
|
+
|
350
|
+
In the yellow state, Stoplight behaves differently from normal (green) operation. Instead of
|
351
|
+
blocking all traffic, it allows a limited number of real requests to pass through to
|
352
|
+
the underlying service to determine if it has recovered. These aren't synthetic probes -
|
353
|
+
they're actual user requests that will execute normally if the service is healthy.
|
354
|
+
|
355
|
+
After collecting the necessary data from these requests, Stoplight decides whether to
|
356
|
+
return to green or red state.
|
357
|
+
|
358
|
+
Traffic Recovery strategies control how Stoplight evaluates these requests during
|
359
|
+
the recovery phase.
|
360
|
+
|
361
|
+
#### Consecutive Successes (Default)
|
362
|
+
|
363
|
+
Returns to green after a specified number of consecutive successful recovery attempts. This is the default behavior.
|
364
|
+
|
365
|
+
```ruby
|
366
|
+
light = Stoplight(
|
367
|
+
"Payment API",
|
368
|
+
traffic_recovery: :consecutive_successes,
|
369
|
+
recovery_threshold: 3,
|
370
|
+
)
|
371
|
+
```
|
372
|
+
|
373
|
+
This configuration requires 3 consecutive successful recovery probes before resuming normal traffic. If any probe
|
374
|
+
fails during recovery, the stoplight immediately returns to red and waits for another cool-off period before trying again.
|
375
|
+
|
376
|
+
**Default behavior**: If no `recovery_threshold` is specified, Stoplight uses a conservative default of 1, meaning a
|
377
|
+
single successful recovery probe will resume traffic flow.
|
378
|
+
|
257
379
|
### Data Store
|
258
380
|
|
259
381
|
Stoplight uses an in-memory data store out of the box:
|
@@ -279,7 +401,7 @@ end
|
|
279
401
|
|
280
402
|
#### Connection Pooling with Redis
|
281
403
|
|
282
|
-
For high-traffic applications or when you want to control a number of opened connections to Redis:
|
404
|
+
For high-traffic applications or when you want to control a number of opened connections to Redis:
|
283
405
|
|
284
406
|
```ruby
|
285
407
|
require "connection_pool"
|
@@ -324,7 +446,7 @@ the [notifier interface documentation] for detailed instructions. Pull requests
|
|
324
446
|
|
325
447
|
### Error Notifiers
|
326
448
|
|
327
|
-
Stoplight is built for resilience. If the Redis data store fails, Stoplight automatically falls back to the in-memory
|
449
|
+
Stoplight is built for resilience. If the Redis data store fails, Stoplight automatically falls back to the in-memory
|
328
450
|
data store. To get notified about such errors, you can configure an error notifier:
|
329
451
|
|
330
452
|
```ruby
|
@@ -335,7 +457,7 @@ end
|
|
335
457
|
|
336
458
|
### Locking
|
337
459
|
|
338
|
-
Sometimes you need to override Stoplight's automatic behavior. Locking allows you to manually control the state of
|
460
|
+
Sometimes you need to override Stoplight's automatic behavior. Locking allows you to manually control the state of
|
339
461
|
a stoplight, which is useful for:
|
340
462
|
|
341
463
|
* **Maintenance periods**: Lock to red when a service is known to be unavailable
|
@@ -384,6 +506,12 @@ Stoplight.configure do |config|
|
|
384
506
|
end
|
385
507
|
```
|
386
508
|
|
509
|
+
You can generate initializer with Redis, Admin Panel route and add needed gems to your Gemfile:
|
510
|
+
|
511
|
+
```sh
|
512
|
+
rails generate stoplight:install --with-admin-panel
|
513
|
+
```
|
514
|
+
|
387
515
|
## Testing
|
388
516
|
|
389
517
|
Tips for working with Stoplight in test environments:
|
@@ -413,9 +541,15 @@ stoplight = Stoplight("test-#{rand}")
|
|
413
541
|
## Maintenance Policy
|
414
542
|
|
415
543
|
Stoplight supports the latest three minor versions of Ruby, which currently are: `3.2.x`, `3.3.x`, and `3.4.x`. Changing
|
416
|
-
the minimum supported Ruby version is not considered a breaking change. We support the current stable Redis
|
544
|
+
the minimum supported Ruby version is not considered a breaking change. We support the current stable Redis
|
417
545
|
version (`7.4.x`) and the latest release of the previous major version (`6.2.x`)
|
418
546
|
|
547
|
+
## Development
|
548
|
+
|
549
|
+
After checking out the repo, run `bundle install` to install dependencies. Run tests with `bundle exec rspec` and check
|
550
|
+
code style with `bundle exec standardrb`. We follow a git flow branching strategy - see our [Git Flow wiki page] for
|
551
|
+
details on branch naming, releases, and contribution workflow.
|
552
|
+
|
419
553
|
## Credits
|
420
554
|
|
421
555
|
Stoplight was originally created by [camdez][] and [tfausak][]. It is currently maintained by [bolshakov][] and
|
@@ -444,3 +578,4 @@ Fowler’s [CircuitBreaker][] article.
|
|
444
578
|
[complete list of contributors]: https://github.com/bolshakov/stoplight/graphs/contributors
|
445
579
|
[CircuitBreaker]: http://martinfowler.com/bliki/CircuitBreaker.html
|
446
580
|
[Redis]: https://redis.io/
|
581
|
+
[Git Flow wiki page]: https://github.com/bolshakov/stoplight/wiki/Git-Flow
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Description:
|
2
|
+
Generates stoplight initializer to setup Stoplight Redis configuration
|
3
|
+
and optionally sets up Admin panel
|
4
|
+
|
5
|
+
Examples:
|
6
|
+
rails generate stoplight:install
|
7
|
+
|
8
|
+
This will generate "config/initializers/stoplight.rb" initializer with basic config
|
9
|
+
Then you should adjust Redis config there.
|
10
|
+
|
11
|
+
rails generate stoplight:install --with-admin-panel
|
12
|
+
|
13
|
+
This generated all needed requirements to set up Stoplight Admin Panel:
|
14
|
+
* It generates initializer "config/initializers/stoplight.rb" with Redis configuration
|
15
|
+
* It injects your "config/routes.rb" with route with basic authentication to Stoplight Admin panel
|
16
|
+
* It injects your Gemfile with needed dependencies ('redis', 'sinatra' and 'sinatra-contrib')
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "rails/generators"
|
5
|
+
rescue LoadError
|
6
|
+
raise <<~WARN
|
7
|
+
Currently generators are only available for Rails applications
|
8
|
+
WARN
|
9
|
+
end
|
10
|
+
|
11
|
+
module Stoplight
|
12
|
+
module Generators
|
13
|
+
class InstallGenerator < ::Rails::Generators::Base # :nodoc:
|
14
|
+
source_root File.expand_path("templates", __dir__)
|
15
|
+
|
16
|
+
class_option :with_admin_panel, type: :boolean, optional: true,
|
17
|
+
desc: "Define whether to set up admin panel"
|
18
|
+
|
19
|
+
ROUTES_PATH = "config/routes.rb"
|
20
|
+
STOPLIGHT_CONFIG_TEMPLATE = "stoplight.rb.erb"
|
21
|
+
INITIALIZERS_PATH = "config/initializers"
|
22
|
+
AFTER_INSTALL_NOTIFICATION = <<~TEXT
|
23
|
+
\nThank you for using stoplight!
|
24
|
+
Now to finish configuration:
|
25
|
+
* Run `bundle` from the project root to install new gems
|
26
|
+
* Go to 'config/initializers/stoplight.rb' to set up connection to Redis.\n
|
27
|
+
TEXT
|
28
|
+
|
29
|
+
STOPLIGHT_ADMIN_ROUTE = <<-RUBY
|
30
|
+
mount Stoplight::Admin => '/stoplights'
|
31
|
+
RUBY
|
32
|
+
|
33
|
+
STOPLIGHT_AUTH = <<-RUBY
|
34
|
+
Stoplight::Admin.use(Rack::Auth::Basic) do |username, password|
|
35
|
+
username == ENV["STOPLIGHT_ADMIN_USERNAME"] && password == ENV["STOPLIGHT_ADMIN_PASSWORD"]
|
36
|
+
end
|
37
|
+
RUBY
|
38
|
+
|
39
|
+
def generate_redis_gem
|
40
|
+
if options[:with_admin_panel]
|
41
|
+
conf = "\ngem 'redis'"
|
42
|
+
inject_into_file "Gemfile", conf
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def generate_sinatra_deps
|
47
|
+
if options[:with_admin_panel]
|
48
|
+
conf = <<~RUBY
|
49
|
+
gem 'sinatra', require: false
|
50
|
+
gem 'sinatra-contrib', require: false
|
51
|
+
RUBY
|
52
|
+
|
53
|
+
inject_into_file "Gemfile", "\n#{conf}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def generate_initializer
|
58
|
+
initializer_template = STOPLIGHT_CONFIG_TEMPLATE
|
59
|
+
copy_file initializer_template, "#{INITIALIZERS_PATH}/stoplight.rb"
|
60
|
+
end
|
61
|
+
|
62
|
+
def generate_admin_panel
|
63
|
+
if options[:with_admin_panel]
|
64
|
+
route_config = "#{STOPLIGHT_AUTH}#{STOPLIGHT_ADMIN_ROUTE}\n"
|
65
|
+
|
66
|
+
inject_into_file ROUTES_PATH, route_config, after: ".application.routes.draw do\n"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def redis_configuration_notification
|
71
|
+
print AFTER_INSTALL_NOTIFICATION
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redis"
|
4
|
+
|
5
|
+
# Set up your project-specific Redis setup here
|
6
|
+
# One of the simplest configurations would be configuring Redis via url
|
7
|
+
# Example:
|
8
|
+
# redis = Redis.new(url: ENV["REDIS_URL"])
|
9
|
+
# REDIS_URL usually looks like this
|
10
|
+
# "redis://<username>:<password>@<server ip address>:<port>/<redis db>"
|
11
|
+
# Example:
|
12
|
+
# "redis://admin:p4ssw0rd@10.0.1.1:6380/15"
|
13
|
+
redis = Redis.new
|
14
|
+
data_store = Stoplight::DataStore::Redis.new(redis)
|
15
|
+
|
16
|
+
Stoplight.configure do |config|
|
17
|
+
config.data_store = data_store
|
18
|
+
end
|
data/lib/stoplight/admin.rb
CHANGED
@@ -25,7 +25,7 @@ module Stoplight
|
|
25
25
|
helpers Helpers
|
26
26
|
|
27
27
|
set :protection, except: %i[json_csrf]
|
28
|
-
set :data_store, proc { Stoplight.
|
28
|
+
set :data_store, proc { Stoplight.default_config.data_store }
|
29
29
|
set :views, File.join(__dir__, "admin", "views")
|
30
30
|
|
31
31
|
get "/" do
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Stoplight
|
4
|
+
module Config
|
5
|
+
# The +CompatibilityResult+ class represents the result of a compatibility check
|
6
|
+
# for a strategy. It provides methods to determine if the strategy is compatible
|
7
|
+
# and to retrieve error messages when it is not.
|
8
|
+
class CompatibilityResult
|
9
|
+
class << self
|
10
|
+
# Creates a new +CompatibilityResult+ instance representing a compatible strategy.
|
11
|
+
#
|
12
|
+
# @return [CompatibilityResult] An instance with no errors.
|
13
|
+
def compatible
|
14
|
+
new(errors: [])
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates a new +CompatibilityResult+ instance representing an incompatible strategy.
|
18
|
+
#
|
19
|
+
# @param errors [Array<String>] List of error messages indicating incompatibility.
|
20
|
+
# @return [CompatibilityResult] An instance with the provided errors.
|
21
|
+
def incompatible(*errors)
|
22
|
+
new(errors:)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Initializes a new `CompatibilityResult` instance.
|
27
|
+
# @param errors [Array<String>] List of error messages if the strategy is not compatible.
|
28
|
+
def initialize(errors: [])
|
29
|
+
@errors = errors.freeze
|
30
|
+
end
|
31
|
+
|
32
|
+
# Checks if the strategy is compatible.
|
33
|
+
# @return [Boolean] `true` if there are no errors, `false` otherwise.
|
34
|
+
def compatible?
|
35
|
+
@errors.empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
def incompatible? = !compatible?
|
39
|
+
|
40
|
+
# Retrieves the list of error messages.
|
41
|
+
# @return [Array<String>] The list of error messages.
|
42
|
+
attr_reader :errors
|
43
|
+
|
44
|
+
# Retrieves a concatenated error message string.
|
45
|
+
# @return [String, nil] A string containing all error messages joined by "; ",
|
46
|
+
# or `nil` if the strategy is compatible.
|
47
|
+
def error_messages
|
48
|
+
unless compatible?
|
49
|
+
@errors.join("; ")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Stoplight
|
4
|
+
module Config
|
5
|
+
# This is a DSL for configuring Stoplight settings. It is responsible for
|
6
|
+
# transforming the provided settings into a format that can be used by Stoplight.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class DSL
|
10
|
+
def transform(settings)
|
11
|
+
if settings.has_key?(:data_store)
|
12
|
+
settings[:data_store] = build_data_store(settings[:data_store])
|
13
|
+
end
|
14
|
+
|
15
|
+
if settings.has_key?(:notifiers)
|
16
|
+
settings[:notifiers] = build_notifiers(settings[:notifiers])
|
17
|
+
end
|
18
|
+
|
19
|
+
if settings.has_key?(:tracked_errors)
|
20
|
+
settings[:tracked_errors] = build_tracked_errors(settings[:tracked_errors])
|
21
|
+
end
|
22
|
+
|
23
|
+
if settings.has_key?(:skipped_errors)
|
24
|
+
settings[:skipped_errors] = build_skipped_errors(settings[:skipped_errors])
|
25
|
+
end
|
26
|
+
|
27
|
+
if settings.has_key?(:cool_off_time)
|
28
|
+
settings[:cool_off_time] = build_cool_off_time(settings[:cool_off_time])
|
29
|
+
end
|
30
|
+
|
31
|
+
if settings.has_key?(:traffic_control)
|
32
|
+
settings[:traffic_control] = build_traffic_control(settings[:traffic_control])
|
33
|
+
end
|
34
|
+
|
35
|
+
if settings.has_key?(:traffic_recovery)
|
36
|
+
settings[:traffic_recovery] = build_traffic_recovery(settings[:traffic_recovery])
|
37
|
+
end
|
38
|
+
settings
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def build_data_store(data_store)
|
44
|
+
DataStore::FailSafe.wrap(data_store)
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_notifiers(notifiers)
|
48
|
+
notifiers.map { |notifier| Notifier::FailSafe.wrap(notifier) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def build_tracked_errors(tracked_error)
|
52
|
+
Array(tracked_error)
|
53
|
+
end
|
54
|
+
|
55
|
+
def build_skipped_errors(skipped_errors)
|
56
|
+
Array(skipped_errors)
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_cool_off_time(cool_off_time)
|
60
|
+
cool_off_time.to_i
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_traffic_control(traffic_control)
|
64
|
+
case traffic_control
|
65
|
+
in Stoplight::TrafficControl::Base
|
66
|
+
traffic_control
|
67
|
+
in :consecutive_errors
|
68
|
+
Stoplight::TrafficControl::ConsecutiveErrors.new
|
69
|
+
in :error_rate
|
70
|
+
Stoplight::TrafficControl::ErrorRate.new
|
71
|
+
in {error_rate: error_rate_settings}
|
72
|
+
Stoplight::TrafficControl::ErrorRate.new(**error_rate_settings)
|
73
|
+
else
|
74
|
+
raise Error::ConfigurationError, <<~ERROR
|
75
|
+
unsupported traffic_control strategy provided (`#{traffic_control}`). Supported options:
|
76
|
+
* Stoplight::TrafficControl::ConsecutiveErrors
|
77
|
+
* Stoplight::TrafficControl::ErrorRate
|
78
|
+
ERROR
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def build_traffic_recovery(traffic_recovery)
|
83
|
+
case traffic_recovery
|
84
|
+
in Stoplight::TrafficRecovery::Base
|
85
|
+
traffic_recovery
|
86
|
+
in :consecutive_successes
|
87
|
+
Stoplight::TrafficRecovery::ConsecutiveSuccesses.new
|
88
|
+
else
|
89
|
+
raise Error::ConfigurationError, <<~ERROR
|
90
|
+
unsupported traffic_recovery strategy provided (`#{traffic_recovery}`). Supported options:
|
91
|
+
* Stoplight::TrafficRecovery::ConsecutiveSuccesses
|
92
|
+
ERROR
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -4,26 +4,18 @@ module Stoplight
|
|
4
4
|
module Config
|
5
5
|
# Provides default settings for the Stoplight library.
|
6
6
|
# @api private
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
private_constant :DEFAULT_SETTINGS
|
21
|
-
|
22
|
-
# Returns library default settings.
|
23
|
-
# @return [Hash]
|
24
|
-
def to_h
|
25
|
-
DEFAULT_SETTINGS
|
26
|
-
end
|
27
|
-
end
|
7
|
+
LibraryDefaultConfig = Light::Config.empty.with(
|
8
|
+
cool_off_time: Stoplight::Default::COOL_OFF_TIME,
|
9
|
+
data_store: Stoplight::Default::DATA_STORE,
|
10
|
+
error_notifier: Stoplight::Default::ERROR_NOTIFIER,
|
11
|
+
notifiers: Stoplight::Default::NOTIFIERS,
|
12
|
+
threshold: Stoplight::Default::THRESHOLD,
|
13
|
+
recovery_threshold: Stoplight::Default::RECOVERY_THRESHOLD,
|
14
|
+
window_size: Stoplight::Default::WINDOW_SIZE,
|
15
|
+
tracked_errors: Stoplight::Default::TRACKED_ERRORS,
|
16
|
+
skipped_errors: Stoplight::Default::SKIPPED_ERRORS,
|
17
|
+
traffic_control: Stoplight::Default::TRAFFIC_CONTROL,
|
18
|
+
traffic_recovery: Stoplight::Default::TRAFFIC_RECOVERY
|
19
|
+
)
|
28
20
|
end
|
29
21
|
end
|