stoplight 5.3.8 → 5.7.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 +17 -2
- data/lib/generators/stoplight/install/templates/stoplight.rb.erb +2 -0
- data/lib/stoplight/admin/actions/remove.rb +23 -0
- data/lib/stoplight/admin/dependencies.rb +6 -1
- data/lib/stoplight/admin/helpers.rb +10 -5
- data/lib/stoplight/admin/lights_repository.rb +26 -14
- data/lib/stoplight/admin/views/_card.erb +13 -1
- data/lib/stoplight/admin/views/layout.erb +3 -3
- data/lib/stoplight/admin.rb +13 -4
- data/lib/stoplight/common/deprecations.rb +11 -0
- data/lib/stoplight/domain/color.rb +11 -0
- data/lib/stoplight/{config → domain}/compatibility_result.rb +1 -1
- data/lib/stoplight/domain/config.rb +59 -0
- data/lib/stoplight/{data_store/base.rb → domain/data_store.rb} +71 -17
- data/lib/stoplight/domain/error.rb +42 -0
- data/lib/stoplight/domain/failure.rb +44 -0
- data/lib/stoplight/domain/light/configuration_builder_interface.rb +234 -0
- data/lib/stoplight/domain/light.rb +208 -0
- data/lib/stoplight/domain/light_factory.rb +75 -0
- data/lib/stoplight/domain/metrics.rb +64 -0
- data/lib/stoplight/domain/recovery_lock_token.rb +15 -0
- data/lib/stoplight/domain/state.rb +11 -0
- data/lib/stoplight/domain/state_snapshot.rb +57 -0
- data/lib/stoplight/{notifier/base.rb → domain/state_transition_notifier.rb} +5 -4
- data/lib/stoplight/domain/storage/metrics.rb +42 -0
- data/lib/stoplight/domain/storage/recovery_lock.rb +56 -0
- data/lib/stoplight/domain/storage/state.rb +87 -0
- data/lib/stoplight/domain/strategies/green_run_strategy.rb +69 -0
- data/lib/stoplight/domain/strategies/red_run_strategy.rb +41 -0
- data/lib/stoplight/domain/strategies/run_strategy.rb +22 -0
- data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +125 -0
- data/lib/stoplight/domain/tracker/base.rb +12 -0
- data/lib/stoplight/domain/tracker/recovery_probe.rb +76 -0
- data/lib/stoplight/domain/tracker/request.rb +72 -0
- data/lib/stoplight/domain/traffic_control/base.rb +74 -0
- data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +53 -0
- data/lib/stoplight/domain/traffic_control/error_rate.rb +51 -0
- data/lib/stoplight/domain/traffic_recovery/base.rb +79 -0
- data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +66 -0
- data/lib/stoplight/domain/traffic_recovery.rb +12 -0
- data/lib/stoplight/infrastructure/data_store/fail_safe.rb +164 -0
- data/lib/stoplight/infrastructure/data_store/memory/metrics.rb +27 -0
- data/lib/stoplight/infrastructure/data_store/memory/recovery_lock_store.rb +54 -0
- data/lib/stoplight/infrastructure/data_store/memory/recovery_lock_token.rb +20 -0
- data/lib/stoplight/infrastructure/data_store/memory/sliding_window.rb +79 -0
- data/lib/stoplight/infrastructure/data_store/memory/state.rb +21 -0
- data/lib/stoplight/infrastructure/data_store/memory.rb +338 -0
- data/lib/stoplight/infrastructure/data_store/redis/lua_scripts/get_metrics.lua +26 -0
- data/lib/stoplight/infrastructure/data_store/redis/lua_scripts/record_recovery_probe_failure.lua +27 -0
- data/lib/stoplight/infrastructure/data_store/redis/lua_scripts/record_recovery_probe_success.lua +23 -0
- data/lib/stoplight/infrastructure/data_store/redis/lua_scripts/release_lock.lua +6 -0
- data/lib/stoplight/infrastructure/data_store/redis/recovery_lock_store.rb +73 -0
- data/lib/stoplight/infrastructure/data_store/redis/recovery_lock_token.rb +35 -0
- data/lib/stoplight/infrastructure/data_store/redis/scripting.rb +71 -0
- data/lib/stoplight/infrastructure/data_store/redis.rb +524 -0
- data/lib/stoplight/infrastructure/notifier/fail_safe.rb +62 -0
- data/lib/stoplight/infrastructure/notifier/generic.rb +90 -0
- data/lib/stoplight/infrastructure/notifier/io.rb +23 -0
- data/lib/stoplight/infrastructure/notifier/logger.rb +21 -0
- data/lib/stoplight/infrastructure/storage/compatibility_metrics.rb +48 -0
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_lock.rb +36 -0
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_metrics.rb +55 -0
- data/lib/stoplight/infrastructure/storage/compatibility_state.rb +55 -0
- data/lib/stoplight/rspec/generic_notifier.rb +1 -1
- data/lib/stoplight/version.rb +1 -1
- data/lib/stoplight/wiring/data_store/base.rb +11 -0
- data/lib/stoplight/wiring/data_store/memory.rb +10 -0
- data/lib/stoplight/wiring/data_store/redis.rb +25 -0
- data/lib/stoplight/wiring/default.rb +28 -0
- data/lib/stoplight/{config/user_default_config.rb → wiring/default_configuration.rb} +24 -31
- data/lib/stoplight/wiring/default_factory_builder.rb +25 -0
- data/lib/stoplight/wiring/light/default_config.rb +18 -0
- data/lib/stoplight/wiring/light/system_config.rb +11 -0
- data/lib/stoplight/wiring/light_builder.rb +185 -0
- data/lib/stoplight/wiring/light_factory/compatibility_validator.rb +55 -0
- data/lib/stoplight/wiring/light_factory/config_normalizer.rb +71 -0
- data/lib/stoplight/wiring/light_factory/configuration_pipeline.rb +72 -0
- data/lib/stoplight/wiring/light_factory/traffic_control_dsl.rb +26 -0
- data/lib/stoplight/wiring/light_factory/traffic_recovery_dsl.rb +21 -0
- data/lib/stoplight/wiring/light_factory.rb +101 -0
- data/lib/stoplight/wiring/notifier_factory.rb +26 -0
- data/lib/stoplight/wiring/public_api.rb +29 -0
- data/lib/stoplight.rb +55 -30
- metadata +92 -42
- data/lib/stoplight/color.rb +0 -9
- data/lib/stoplight/config/dsl.rb +0 -97
- data/lib/stoplight/config/library_default_config.rb +0 -21
- data/lib/stoplight/config/system_config.rb +0 -7
- data/lib/stoplight/data_store/fail_safe.rb +0 -113
- data/lib/stoplight/data_store/memory.rb +0 -311
- data/lib/stoplight/data_store/redis/get_metadata.lua +0 -38
- data/lib/stoplight/data_store/redis/lua.rb +0 -23
- data/lib/stoplight/data_store/redis.rb +0 -449
- data/lib/stoplight/data_store.rb +0 -6
- data/lib/stoplight/default.rb +0 -30
- data/lib/stoplight/error.rb +0 -10
- data/lib/stoplight/failure.rb +0 -71
- data/lib/stoplight/light/config.rb +0 -111
- data/lib/stoplight/light/configuration_builder_interface.rb +0 -128
- data/lib/stoplight/light/green_run_strategy.rb +0 -54
- data/lib/stoplight/light/red_run_strategy.rb +0 -27
- data/lib/stoplight/light/run_strategy.rb +0 -32
- data/lib/stoplight/light/yellow_run_strategy.rb +0 -94
- data/lib/stoplight/light.rb +0 -191
- data/lib/stoplight/metadata.rb +0 -99
- data/lib/stoplight/notifier/fail_safe.rb +0 -70
- data/lib/stoplight/notifier/generic.rb +0 -79
- data/lib/stoplight/notifier/io.rb +0 -21
- data/lib/stoplight/notifier/logger.rb +0 -19
- data/lib/stoplight/state.rb +0 -9
- data/lib/stoplight/traffic_control/base.rb +0 -70
- data/lib/stoplight/traffic_control/consecutive_errors.rb +0 -55
- data/lib/stoplight/traffic_control/error_rate.rb +0 -49
- data/lib/stoplight/traffic_recovery/base.rb +0 -75
- data/lib/stoplight/traffic_recovery/consecutive_successes.rb +0 -68
- data/lib/stoplight/traffic_recovery.rb +0 -11
- /data/lib/stoplight/{data_store/redis → infrastructure/data_store/redis/lua_scripts}/record_failure.lua +0 -0
- /data/lib/stoplight/{data_store/redis → infrastructure/data_store/redis/lua_scripts}/record_success.lua +0 -0
- /data/lib/stoplight/{data_store/redis → infrastructure/data_store/redis/lua_scripts}/transition_to_green.lua +0 -0
- /data/lib/stoplight/{data_store/redis → infrastructure/data_store/redis/lua_scripts}/transition_to_red.lua +0 -0
- /data/lib/stoplight/{data_store/redis → infrastructure/data_store/redis/lua_scripts}/transition_to_yellow.lua +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d96e1b5dcce81c642d059b07b2e274b8c7dbd7f246b86ee25ce40567eaba8418
|
|
4
|
+
data.tar.gz: e6b04f0ff592f89f345ff3f9ec9e26b86463ff0baeb56ef752cbd00b7f8952b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 01c5c2356f84eaeade94edba7113e700682911558d165b8b60186abbe0cda3d0f453427b8ab6e96f4ab51948e3ec5a109e53e1b51cc15c46c3fd79aad4865768
|
|
7
|
+
data.tar.gz: 94633b744ef88219f66289d9aae91bbb35b013bbea82c84f0f253abd15d4e5bd7ea5a123c39dffc36911479b1663cee71f786110be0ee15c3c8f56d035032e76
|
data/README.md
CHANGED
|
@@ -99,6 +99,19 @@ light.run { 1 / 0 } #=> raises Stoplight::Error::RedLight: example-zero
|
|
|
99
99
|
light.color # => "red"
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
The `Stoplight::Error::RedLight` provides metadata about the error:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
def run_request
|
|
106
|
+
light = Stoplight("Example", cool_off_time: 10)
|
|
107
|
+
light.run { 1 / 0 } #=> raises Stoplight::Error::RedLight
|
|
108
|
+
rescue Stoplight::Error::RedLight => error
|
|
109
|
+
puts error.light_name #=> "Example"
|
|
110
|
+
puts error.cool_off_time #=> 10
|
|
111
|
+
puts error.retry_after #=> Absolute Time after which a recovery attempt can occur (e.g., "2025-10-21 15:39:50.672414 +0600")
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
102
115
|
After one minute, the light transitions to yellow, allowing a test execution:
|
|
103
116
|
|
|
104
117
|
```ruby
|
|
@@ -131,6 +144,8 @@ receives `nil`. In both cases, the return value of the fallback becomes the retu
|
|
|
131
144
|
|
|
132
145
|
Stoplight comes 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.
|
|
133
146
|
|
|
147
|
+

|
|
148
|
+
|
|
134
149
|
To add Admin Panel protected by basic authentication to your Rails project, add this configuration to your `config/routes.rb` file.
|
|
135
150
|
|
|
136
151
|
```ruby
|
|
@@ -519,7 +534,7 @@ class ApplicationController < ActionController::Base
|
|
|
519
534
|
|
|
520
535
|
def stoplight(&block)
|
|
521
536
|
Stoplight("#{params[:controller]}##{params[:action]}")
|
|
522
|
-
.run(-> { render(nothing: true, status: :service_unavailable) }, &block)
|
|
537
|
+
.run(-> (*) { render(nothing: true, status: :service_unavailable) }, &block)
|
|
523
538
|
end
|
|
524
539
|
end
|
|
525
540
|
```
|
|
@@ -613,7 +628,7 @@ Example: "Ruby 3.2 reaches end-of-life in March 2026, so Stoplight 6.0 will requ
|
|
|
613
628
|
|
|
614
629
|
After checking out the repo, run `bundle install` to install dependencies. Run tests with `bundle exec rspec` and check
|
|
615
630
|
code style with `bundle exec standardrb`. We follow a git flow branching strategy - see our [Git Flow wiki page] for
|
|
616
|
-
details on branch naming, releases, and contribution workflow.
|
|
631
|
+
details on branch naming, releases, and contribution workflow. Also check our CONTRIBUTING.md guide for contributors.
|
|
617
632
|
|
|
618
633
|
## Credits
|
|
619
634
|
|
|
@@ -12,7 +12,9 @@ require "redis"
|
|
|
12
12
|
# "redis://admin:p4ssw0rd@10.0.1.1:6380/15"
|
|
13
13
|
redis = Redis.new
|
|
14
14
|
data_store = Stoplight::DataStore::Redis.new(redis)
|
|
15
|
+
error_notifier = Rails.error.method(:report)
|
|
15
16
|
|
|
16
17
|
Stoplight.configure do |config|
|
|
17
18
|
config.data_store = data_store
|
|
19
|
+
config.error_notifier = error_notifier
|
|
18
20
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
class Admin
|
|
5
|
+
module Actions
|
|
6
|
+
# This action removes a light's metadata from Redis
|
|
7
|
+
class Remove < Action
|
|
8
|
+
# @param params [Hash] query parameters
|
|
9
|
+
# @return [void]
|
|
10
|
+
def call(params)
|
|
11
|
+
light_names(params).each do |name|
|
|
12
|
+
lights_repository.remove(name)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private def light_names(params)
|
|
17
|
+
Array(params[:names])
|
|
18
|
+
.map { |name| CGI.unescape(name) }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -8,7 +8,7 @@ module Stoplight
|
|
|
8
8
|
attr_reader :data_store
|
|
9
9
|
private :data_store
|
|
10
10
|
|
|
11
|
-
# @param data_store [Stoplight::DataStore
|
|
11
|
+
# @param data_store [Stoplight::Domain::DataStore]
|
|
12
12
|
def initialize(data_store:)
|
|
13
13
|
@data_store = data_store
|
|
14
14
|
end
|
|
@@ -45,6 +45,11 @@ module Stoplight
|
|
|
45
45
|
def green_all_action
|
|
46
46
|
Stoplight::Admin::Actions::LockAllGreen.new(lights_repository: lights_repository)
|
|
47
47
|
end
|
|
48
|
+
|
|
49
|
+
# @return [Stoplight::Admin::Actions::Remove]
|
|
50
|
+
def remove_action
|
|
51
|
+
Stoplight::Admin::Actions::Remove.new(lights_repository: lights_repository)
|
|
52
|
+
end
|
|
48
53
|
end
|
|
49
54
|
end
|
|
50
55
|
end
|
|
@@ -15,11 +15,16 @@ module Stoplight
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
private def data_store
|
|
18
|
-
settings.data_store.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
if settings.data_store.is_a?(Stoplight::DataStore::Memory)
|
|
19
|
+
raise "Stoplight Admin requires a persistent data store, but the current data store is Memory. " \
|
|
20
|
+
"Please configure a different data store in your Stoplight configuration."
|
|
21
|
+
else
|
|
22
|
+
Stoplight::Wiring::LightBuilder.new(
|
|
23
|
+
{
|
|
24
|
+
data_store: settings.data_store,
|
|
25
|
+
config: Wiring::Light::DefaultConfig
|
|
26
|
+
}
|
|
27
|
+
).__send__(:data_store)
|
|
23
28
|
end
|
|
24
29
|
end
|
|
25
30
|
end
|
|
@@ -4,11 +4,11 @@ module Stoplight
|
|
|
4
4
|
class Admin
|
|
5
5
|
class LightsRepository
|
|
6
6
|
# @!attribute data_store
|
|
7
|
-
# @return [Stoplight::DataStore
|
|
7
|
+
# @return [Stoplight::Domain::DataStore]
|
|
8
8
|
attr_reader :data_store
|
|
9
9
|
private :data_store
|
|
10
10
|
|
|
11
|
-
# @param data_store [Stoplight::DataStore
|
|
11
|
+
# @param data_store [Stoplight::Domain::DataStore]
|
|
12
12
|
def initialize(data_store:)
|
|
13
13
|
@data_store = data_store
|
|
14
14
|
end
|
|
@@ -37,37 +37,49 @@ module Stoplight
|
|
|
37
37
|
# color
|
|
38
38
|
# @return [void]
|
|
39
39
|
def lock(name, color = nil)
|
|
40
|
-
|
|
40
|
+
config = build_config(name)
|
|
41
|
+
color ||= data_store.get_state_snapshot(config).color
|
|
41
42
|
|
|
42
|
-
case color
|
|
43
|
+
case color
|
|
43
44
|
when Stoplight::Color::GREEN
|
|
44
|
-
|
|
45
|
+
data_store.set_state(config, Stoplight::State::LOCKED_GREEN)
|
|
45
46
|
else
|
|
46
|
-
|
|
47
|
+
data_store.set_state(config, Stoplight::State::LOCKED_RED)
|
|
47
48
|
end
|
|
48
49
|
end
|
|
49
50
|
|
|
50
51
|
# @param name [String] unlocks light by its name
|
|
51
52
|
# @return [void]
|
|
52
53
|
def unlock(name)
|
|
53
|
-
|
|
54
|
+
config = build_config(name)
|
|
55
|
+
data_store.set_state(config, Domain::State::UNLOCKED)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @param name [String] removes light metadata by its name
|
|
59
|
+
# @return [void]
|
|
60
|
+
def remove(name)
|
|
61
|
+
config = build_config(name)
|
|
62
|
+
|
|
63
|
+
data_store.delete_light(config)
|
|
54
64
|
end
|
|
55
65
|
|
|
56
66
|
private def load_light(name)
|
|
57
|
-
|
|
67
|
+
config = build_config(name)
|
|
68
|
+
|
|
58
69
|
# failures, state
|
|
59
|
-
|
|
70
|
+
state_snapshot = data_store.get_state_snapshot(config)
|
|
71
|
+
metrics = data_store.get_metrics(config)
|
|
60
72
|
|
|
61
73
|
Light.new(
|
|
62
74
|
name: name,
|
|
63
|
-
color:
|
|
64
|
-
state:
|
|
65
|
-
failures: [
|
|
75
|
+
color: state_snapshot.color,
|
|
76
|
+
state: state_snapshot.locked_state,
|
|
77
|
+
failures: [metrics.last_error].compact
|
|
66
78
|
)
|
|
67
79
|
end
|
|
68
80
|
|
|
69
|
-
private def
|
|
70
|
-
|
|
81
|
+
private def build_config(name)
|
|
82
|
+
Wiring::Light::DefaultConfig.with(name:)
|
|
71
83
|
end
|
|
72
84
|
end
|
|
73
85
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="max-w-xl mr-5 p-6 border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
|
2
2
|
<div class="flex items-center">
|
|
3
|
-
<% light_name =
|
|
3
|
+
<% light_name = CGI.escape(light.name) %>
|
|
4
4
|
|
|
5
5
|
<div class="relative">
|
|
6
6
|
<div class="relative inline-flex items-center justify-center w-10 h-10 overflow-hidden bg-<%= color %>-100 rounded-full dark:bg-<%= color %>-600">
|
|
@@ -83,6 +83,18 @@
|
|
|
83
83
|
Lock Green
|
|
84
84
|
</a>
|
|
85
85
|
</li>
|
|
86
|
+
<li>
|
|
87
|
+
<a href="<%= url("/remove?names=#{light_name}") %>" data-turbo-method="post" data-turbo-confirm="Are you sure you want to remove this light?" class="flex items-center py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
|
88
|
+
<svg class="flex w-4 h-4 me-1.5 text-red-600 shrink-0" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
89
|
+
<polyline points="3 6 5 6 21 6"/>
|
|
90
|
+
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
|
|
91
|
+
<path d="M10 11v6"/>
|
|
92
|
+
<path d="M14 11v6"/>
|
|
93
|
+
<path d="M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"/>
|
|
94
|
+
</svg>
|
|
95
|
+
Remove
|
|
96
|
+
</a>
|
|
97
|
+
</li>
|
|
86
98
|
</ul>
|
|
87
99
|
</div>
|
|
88
100
|
</div>
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
<title>Stoplight Admin</title>
|
|
8
8
|
|
|
9
|
-
<script type="module" src="https://cdn.jsdelivr.net/npm/@hotwired/turbo@latest/dist/turbo.es2017-esm.min.js"></script>
|
|
10
|
-
<script type="module">
|
|
9
|
+
<script nonce="<%= nonce %>" type="module" src="https://cdn.jsdelivr.net/npm/@hotwired/turbo@latest/dist/turbo.es2017-esm.min.js"></script>
|
|
10
|
+
<script nonce="<%= nonce %>" type="module">
|
|
11
11
|
document.addEventListener("turbo:load", () => {
|
|
12
12
|
window.initFlowbite()
|
|
13
13
|
})
|
|
@@ -63,6 +63,6 @@
|
|
|
63
63
|
</footer>
|
|
64
64
|
</div>
|
|
65
65
|
|
|
66
|
-
<script src="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.js"></script>
|
|
66
|
+
<script nonce="<%= nonce %>" src="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.js"></script>
|
|
67
67
|
</body>
|
|
68
68
|
</html>
|
data/lib/stoplight/admin.rb
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "cgi/escape"
|
|
4
|
+
require "cgi/util" if RUBY_VERSION < "3.5"
|
|
5
|
+
|
|
3
6
|
begin
|
|
4
7
|
require "sinatra/base"
|
|
5
|
-
require "sinatra/contrib"
|
|
6
8
|
require "sinatra/json"
|
|
7
9
|
rescue LoadError
|
|
8
10
|
raise <<~WARN
|
|
9
|
-
"sinatra" and "sinatra-contrib" gems are unavailable and
|
|
11
|
+
"sinatra" and "sinatra-contrib" gems are unavailable and necessary for running Stoplight Admin panel
|
|
10
12
|
Please add them to your Gemfile and run `bundle install`:
|
|
11
13
|
gem "sinatra", required: false
|
|
12
14
|
gem "sinatra-contrib", require: false
|
|
@@ -25,13 +27,14 @@ module Stoplight
|
|
|
25
27
|
helpers Helpers
|
|
26
28
|
|
|
27
29
|
set :protection, except: %i[json_csrf]
|
|
28
|
-
set :data_store, proc { Stoplight.
|
|
30
|
+
set :data_store, proc { Stoplight.__stoplight__default_configuration.data_store }
|
|
29
31
|
set :views, File.join(__dir__, "admin", "views")
|
|
32
|
+
set :nonce, proc { |request| }
|
|
30
33
|
|
|
31
34
|
get "/" do
|
|
32
35
|
lights, stats = dependencies.stats_action.call
|
|
33
36
|
|
|
34
|
-
erb :index, locals: stats.merge(lights: lights)
|
|
37
|
+
erb :index, locals: stats.merge(lights: lights, nonce: settings.nonce(request))
|
|
35
38
|
end
|
|
36
39
|
|
|
37
40
|
get "/stats" do
|
|
@@ -63,5 +66,11 @@ module Stoplight
|
|
|
63
66
|
|
|
64
67
|
redirect to("/")
|
|
65
68
|
end
|
|
69
|
+
|
|
70
|
+
post "/remove" do
|
|
71
|
+
dependencies.remove_action.call(params)
|
|
72
|
+
|
|
73
|
+
redirect to("/")
|
|
74
|
+
end
|
|
66
75
|
end
|
|
67
76
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Stoplight
|
|
4
|
-
module
|
|
4
|
+
module Domain
|
|
5
5
|
# The +CompatibilityResult+ class represents the result of a compatibility check
|
|
6
6
|
# for a strategy. It provides methods to determine if the strategy is compatible
|
|
7
7
|
# and to retrieve error messages when it is not.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Domain
|
|
5
|
+
# A +Stoplight::Light+ configuration object.
|
|
6
|
+
#
|
|
7
|
+
# # @!attribute [r] name
|
|
8
|
+
# @return [String]
|
|
9
|
+
#
|
|
10
|
+
# @!attribute [r] cool_off_time - cool-off time in seconds
|
|
11
|
+
# @return [Numeric]
|
|
12
|
+
#
|
|
13
|
+
# @!attribute [r] threshold
|
|
14
|
+
# @return [Numeric]
|
|
15
|
+
#
|
|
16
|
+
# @!attribute [r] window_size
|
|
17
|
+
# @return [Numeric]
|
|
18
|
+
#
|
|
19
|
+
# @!attribute [r] tracked_errors
|
|
20
|
+
# @return [Array<StandardError>]
|
|
21
|
+
#
|
|
22
|
+
# @!attribute [r] skipped_errors
|
|
23
|
+
# @return [Array<Exception>]
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
26
|
+
Config = Data.define(
|
|
27
|
+
:name,
|
|
28
|
+
:cool_off_time,
|
|
29
|
+
:threshold,
|
|
30
|
+
:recovery_threshold,
|
|
31
|
+
:window_size,
|
|
32
|
+
:tracked_errors,
|
|
33
|
+
:skipped_errors
|
|
34
|
+
) do
|
|
35
|
+
class << self
|
|
36
|
+
# Creates a new NULL configuration object.
|
|
37
|
+
# @return [Stoplight::Domain::Config]
|
|
38
|
+
def empty
|
|
39
|
+
new(**members.map { |key| [key, nil] }.to_h)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Checks if the given error should be tracked
|
|
44
|
+
#
|
|
45
|
+
# @param error [#==] The error to check, e.g. an Exception, Class or Proc
|
|
46
|
+
# @return [Boolean]
|
|
47
|
+
def track_error?(error)
|
|
48
|
+
skip = skipped_errors.any? { |klass| klass === error }
|
|
49
|
+
track = tracked_errors.any? { |klass| klass === error }
|
|
50
|
+
|
|
51
|
+
!skip && track
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def cool_off_time_in_milliseconds
|
|
55
|
+
cool_off_time * 1_000
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Stoplight
|
|
4
|
-
module
|
|
4
|
+
module Domain
|
|
5
5
|
# @abstract
|
|
6
|
-
|
|
6
|
+
# :nocov:
|
|
7
|
+
class DataStore
|
|
7
8
|
METRICS_RETENTION_TIME = 60 * 60 * 24 # 1 day
|
|
8
9
|
|
|
9
10
|
# Retrieves the names of all lights stored in the data store.
|
|
@@ -13,26 +14,56 @@ module Stoplight
|
|
|
13
14
|
raise NotImplementedError
|
|
14
15
|
end
|
|
15
16
|
|
|
16
|
-
# Retrieves
|
|
17
|
+
# Retrieves metrics for a specific light configuration.
|
|
17
18
|
#
|
|
18
|
-
# @param config [Stoplight::
|
|
19
|
-
# @return [Stoplight::
|
|
20
|
-
def
|
|
19
|
+
# @param config [Stoplight::Domain::Config]
|
|
20
|
+
# @return [Stoplight::Domain::Metrics]
|
|
21
|
+
def get_metrics(config)
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Retrieves recovery metrics for a specific light configuration.
|
|
26
|
+
#
|
|
27
|
+
# @param config [Stoplight::Domain::Config]
|
|
28
|
+
# @return [Stoplight::Domain::Metrics]
|
|
29
|
+
def get_recovery_metrics(config)
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Retrieves State Snapshot for a specific light configuration.
|
|
34
|
+
#
|
|
35
|
+
# @param config [Stoplight::Domain::Config] The light configuration.
|
|
36
|
+
# @return [Stoplight::Domain::StateSnapshot]
|
|
37
|
+
def get_state_snapshot(config)
|
|
38
|
+
raise NotImplementedError
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Clears windowed metrics (successes/errors) to prevent
|
|
42
|
+
# stale failures from before recovery from affecting post-recovery decisions.
|
|
43
|
+
# Consecutive counts are intentionally preserved as they track current streaks.
|
|
44
|
+
#
|
|
45
|
+
# @param config [Stoplight::Domain::Config] The light configuration.
|
|
46
|
+
# @return [void]
|
|
47
|
+
def clear_metrics(config)
|
|
48
|
+
raise NotImplementedError
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def clear_recovery_metrics(config)
|
|
21
52
|
raise NotImplementedError
|
|
22
53
|
end
|
|
23
54
|
|
|
24
55
|
# Records a failure for a specific light configuration.
|
|
25
56
|
#
|
|
26
|
-
# @param config [Stoplight::
|
|
27
|
-
# @param
|
|
28
|
-
# @return [
|
|
29
|
-
def record_failure(config,
|
|
57
|
+
# @param config [Stoplight::Domain::Config]
|
|
58
|
+
# @param exception [Exception]
|
|
59
|
+
# @return [void]
|
|
60
|
+
def record_failure(config, exception)
|
|
30
61
|
raise NotImplementedError
|
|
31
62
|
end
|
|
32
63
|
|
|
33
64
|
# Records a success for a specific light configuration.
|
|
34
65
|
#
|
|
35
|
-
# @param config [Stoplight::
|
|
66
|
+
# @param config [Stoplight::Domain::Config]
|
|
36
67
|
# @return [void]
|
|
37
68
|
def record_success(config)
|
|
38
69
|
raise NotImplementedError
|
|
@@ -40,30 +71,42 @@ module Stoplight
|
|
|
40
71
|
|
|
41
72
|
# Records a failed recovery probe for a specific light configuration.
|
|
42
73
|
#
|
|
43
|
-
# @param config [Stoplight::
|
|
74
|
+
# @param config [Stoplight::Domain::Config]
|
|
44
75
|
# @param failure [Failure]
|
|
45
|
-
# @return [
|
|
76
|
+
# @return [void]
|
|
46
77
|
def record_recovery_probe_failure(config, failure)
|
|
47
78
|
raise NotImplementedError
|
|
48
79
|
end
|
|
49
80
|
|
|
50
81
|
# Records a successful recovery probe for a specific light configuration.
|
|
51
82
|
#
|
|
52
|
-
# @param config [Stoplight::
|
|
53
|
-
# @return [
|
|
83
|
+
# @param config [Stoplight::Domain::Config]
|
|
84
|
+
# @return [void]
|
|
54
85
|
def record_recovery_probe_success(config)
|
|
55
86
|
raise NotImplementedError
|
|
56
87
|
end
|
|
57
88
|
|
|
58
89
|
# Sets the state of a specific light configuration.
|
|
59
90
|
#
|
|
60
|
-
# @param config [Stoplight::
|
|
91
|
+
# @param config [Stoplight::Domain::Config]
|
|
61
92
|
# @param state [String] The new state to set.
|
|
62
93
|
# @return [String] The state that was set.
|
|
63
94
|
def set_state(config, state)
|
|
64
95
|
raise NotImplementedError
|
|
65
96
|
end
|
|
66
97
|
|
|
98
|
+
# Acquires recovery lock for serializing probe execution.
|
|
99
|
+
#
|
|
100
|
+
# @param config [Stoplight::Domain::Config]
|
|
101
|
+
# @return [Stoplight::Domain::LockToken, nil] Lock if acquired, nil if contended
|
|
102
|
+
def acquire_recovery_lock(config) = raise NotImplementedError
|
|
103
|
+
|
|
104
|
+
# Releases previously acquired lock.
|
|
105
|
+
#
|
|
106
|
+
# @param lock [Stoplight::Domain::RecoveryLockToken]
|
|
107
|
+
# @return [void]
|
|
108
|
+
def release_recovery_lock(lock) = raise NotImplementedError
|
|
109
|
+
|
|
67
110
|
# Transitions the Stoplight to the specified color.
|
|
68
111
|
#
|
|
69
112
|
# This method performs a color transition operation that works across distributed instances
|
|
@@ -71,7 +114,7 @@ module Stoplight
|
|
|
71
114
|
# is considered the "first" to perform the transition (and therefore responsible for
|
|
72
115
|
# triggering notifications).
|
|
73
116
|
#
|
|
74
|
-
# @param config [Stoplight::
|
|
117
|
+
# @param config [Stoplight::Domain::Config]
|
|
75
118
|
# @param color [String] The target color/state to transition to.
|
|
76
119
|
# Should be one of Stoplight::Color::GREEN, Stoplight::Color::YELLOW, or Stoplight::Color::RED.
|
|
77
120
|
#
|
|
@@ -87,6 +130,17 @@ module Stoplight
|
|
|
87
130
|
def transition_to_color(config, color)
|
|
88
131
|
raise NotImplementedError
|
|
89
132
|
end
|
|
133
|
+
|
|
134
|
+
# Deletes metadata (and related persistent state) for the given light.
|
|
135
|
+
#
|
|
136
|
+
# Implementations may choose to only remove metadata; metrics may expire via TTL.
|
|
137
|
+
#
|
|
138
|
+
# @param config [Stoplight::Domain::Config]
|
|
139
|
+
# @return [void]
|
|
140
|
+
def delete_light(config)
|
|
141
|
+
raise NotImplementedError
|
|
142
|
+
end
|
|
90
143
|
end
|
|
144
|
+
# :nocov:
|
|
91
145
|
end
|
|
92
146
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Domain
|
|
5
|
+
module Error
|
|
6
|
+
Base = Class.new(StandardError)
|
|
7
|
+
ConfigurationError = Class.new(Base)
|
|
8
|
+
IncorrectColor = Class.new(Base)
|
|
9
|
+
|
|
10
|
+
class RedLight < Base
|
|
11
|
+
# @!attribute light_name
|
|
12
|
+
# @return [String] The light's name
|
|
13
|
+
attr_reader :light_name
|
|
14
|
+
|
|
15
|
+
# @!attribute cool_off_time
|
|
16
|
+
# @return [Numeric] Cool-off period in seconds
|
|
17
|
+
attr_reader :cool_off_time
|
|
18
|
+
|
|
19
|
+
# @!attribute retry_after
|
|
20
|
+
# @return [Time] Absolute Time after which a recovery attempt can occur
|
|
21
|
+
attr_reader :retry_after
|
|
22
|
+
|
|
23
|
+
# Initializes a new RedLight error.
|
|
24
|
+
#
|
|
25
|
+
# @param light_name [String] The light's name
|
|
26
|
+
#
|
|
27
|
+
# @option cool_off_time [Numeric] Cool-off period in seconds
|
|
28
|
+
#
|
|
29
|
+
# @option retry_after [Time] Absolute Time after which a recovery attempt can occur
|
|
30
|
+
#
|
|
31
|
+
# @return [Stoplight::Error::RedLight]
|
|
32
|
+
def initialize(light_name, cool_off_time:, retry_after:)
|
|
33
|
+
@light_name = light_name
|
|
34
|
+
@cool_off_time = cool_off_time
|
|
35
|
+
@retry_after = retry_after
|
|
36
|
+
|
|
37
|
+
super(light_name)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "time"
|
|
5
|
+
|
|
6
|
+
module Stoplight
|
|
7
|
+
module Domain
|
|
8
|
+
# @api private
|
|
9
|
+
class Failure
|
|
10
|
+
# @return [String]
|
|
11
|
+
attr_reader :error_class
|
|
12
|
+
# @return [String]
|
|
13
|
+
attr_reader :error_message
|
|
14
|
+
# @return [Time]
|
|
15
|
+
attr_reader :time
|
|
16
|
+
|
|
17
|
+
# @param error [Exception]
|
|
18
|
+
# @return (see #initialize)
|
|
19
|
+
def self.from_error(error, time: Time.now)
|
|
20
|
+
new(error.class.name, error.message, time)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param error_class [String]
|
|
24
|
+
# @param error_message [String]
|
|
25
|
+
# @param time [Time]
|
|
26
|
+
def initialize(error_class, error_message, time)
|
|
27
|
+
@error_class = error_class
|
|
28
|
+
@error_message = error_message
|
|
29
|
+
@time = time
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
alias_method :occurred_at, :time
|
|
33
|
+
|
|
34
|
+
# @param other [Failure]
|
|
35
|
+
# @return [Boolean]
|
|
36
|
+
def ==(other)
|
|
37
|
+
other.is_a?(self.class) &&
|
|
38
|
+
error_class == other.error_class &&
|
|
39
|
+
error_message == other.error_message &&
|
|
40
|
+
time == other.time
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|