stoplight 0.4.1 → 0.5.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/CHANGELOG.md +15 -0
- data/LICENSE.md +1 -1
- data/README.md +66 -63
- data/lib/stoplight.rb +10 -15
- data/lib/stoplight/color.rb +9 -0
- data/lib/stoplight/data_store.rb +0 -146
- data/lib/stoplight/data_store/base.rb +7 -130
- data/lib/stoplight/data_store/memory.rb +25 -100
- data/lib/stoplight/data_store/redis.rb +61 -119
- data/lib/stoplight/default.rb +34 -0
- data/lib/stoplight/error.rb +0 -42
- data/lib/stoplight/failure.rb +21 -25
- data/lib/stoplight/light.rb +42 -127
- data/lib/stoplight/light/runnable.rb +97 -0
- data/lib/stoplight/notifier/base.rb +1 -4
- data/lib/stoplight/notifier/hip_chat.rb +17 -32
- data/lib/stoplight/notifier/io.rb +9 -9
- data/lib/stoplight/state.rb +9 -0
- data/spec/spec_helper.rb +2 -3
- data/spec/stoplight/color_spec.rb +39 -0
- data/spec/stoplight/data_store/base_spec.rb +56 -36
- data/spec/stoplight/data_store/memory_spec.rb +120 -2
- data/spec/stoplight/data_store/redis_spec.rb +123 -24
- data/spec/stoplight/data_store_spec.rb +2 -69
- data/spec/stoplight/default_spec.rb +86 -0
- data/spec/stoplight/error_spec.rb +29 -0
- data/spec/stoplight/failure_spec.rb +61 -51
- data/spec/stoplight/light/runnable_spec.rb +234 -0
- data/spec/stoplight/light_spec.rb +143 -191
- data/spec/stoplight/notifier/base_spec.rb +8 -11
- data/spec/stoplight/notifier/hip_chat_spec.rb +66 -55
- data/spec/stoplight/notifier/io_spec.rb +49 -21
- data/spec/stoplight/notifier_spec.rb +3 -0
- data/spec/stoplight/state_spec.rb +39 -0
- data/spec/stoplight_spec.rb +2 -65
- metadata +55 -19
- data/spec/support/data_store.rb +0 -36
- data/spec/support/fakeredis.rb +0 -3
- data/spec/support/hipchat.rb +0 -3
@@ -3,158 +3,35 @@
|
|
3
3
|
module Stoplight
|
4
4
|
module DataStore
|
5
5
|
class Base
|
6
|
-
# @return [Array<String>]
|
7
6
|
def names
|
8
7
|
fail NotImplementedError
|
9
8
|
end
|
10
9
|
|
11
|
-
def
|
10
|
+
def get_all(_light)
|
12
11
|
fail NotImplementedError
|
13
12
|
end
|
14
13
|
|
15
|
-
|
16
|
-
def clear(_name)
|
14
|
+
def get_failures(_light)
|
17
15
|
fail NotImplementedError
|
18
16
|
end
|
19
17
|
|
20
|
-
|
21
|
-
def sync(_name)
|
18
|
+
def record_failure(_light, _failure)
|
22
19
|
fail NotImplementedError
|
23
20
|
end
|
24
21
|
|
25
|
-
|
26
|
-
def greenify(_name)
|
22
|
+
def clear_failures(_light)
|
27
23
|
fail NotImplementedError
|
28
24
|
end
|
29
25
|
|
30
|
-
|
31
|
-
|
32
|
-
# @param name [String]
|
33
|
-
# @return [Boolean]
|
34
|
-
def green?(name)
|
35
|
-
color = get_color(name)
|
36
|
-
DataStore.validate_color!(color)
|
37
|
-
color == COLOR_GREEN
|
38
|
-
end
|
39
|
-
|
40
|
-
# @param name [String]
|
41
|
-
# @return [Boolean]
|
42
|
-
def yellow?(name)
|
43
|
-
color = get_color(name)
|
44
|
-
DataStore.validate_color!(color)
|
45
|
-
color == COLOR_YELLOW
|
46
|
-
end
|
47
|
-
|
48
|
-
# @param name [String]
|
49
|
-
# @return [Boolean]
|
50
|
-
def red?(name)
|
51
|
-
color = get_color(name)
|
52
|
-
DataStore.validate_color!(color)
|
53
|
-
color == COLOR_RED
|
54
|
-
end
|
55
|
-
|
56
|
-
# @param _name [String]
|
57
|
-
# @return [String]
|
58
|
-
def get_color(_name)
|
59
|
-
fail NotImplementedError
|
60
|
-
end
|
61
|
-
|
62
|
-
# @group Attempts
|
63
|
-
|
64
|
-
# @param _name [String]
|
65
|
-
# @return [Integer]
|
66
|
-
def get_attempts(_name)
|
67
|
-
fail NotImplementedError
|
68
|
-
end
|
69
|
-
|
70
|
-
# @param _name [String]
|
71
|
-
# @return [Integer]
|
72
|
-
def record_attempt(_name)
|
73
|
-
fail NotImplementedError
|
74
|
-
end
|
75
|
-
|
76
|
-
# @param _name [String]
|
77
|
-
def clear_attempts(_name)
|
78
|
-
fail NotImplementedError
|
79
|
-
end
|
80
|
-
|
81
|
-
# @group Failures
|
82
|
-
|
83
|
-
# @param _name [String]
|
84
|
-
# @return [Array<Failure>]
|
85
|
-
def get_failures(_name)
|
86
|
-
fail NotImplementedError
|
87
|
-
end
|
88
|
-
|
89
|
-
# @param _name [String]
|
90
|
-
# @param _failure [Failure]
|
91
|
-
# @return [Failure]
|
92
|
-
def record_failure(_name, _failure)
|
93
|
-
fail NotImplementedError
|
94
|
-
end
|
95
|
-
|
96
|
-
# @param _name [String]
|
97
|
-
def clear_failures(_name)
|
98
|
-
fail NotImplementedError
|
99
|
-
end
|
100
|
-
|
101
|
-
# @group States
|
102
|
-
|
103
|
-
# @param _name [String]
|
104
|
-
# @return [String]
|
105
|
-
def get_state(_name)
|
106
|
-
fail NotImplementedError
|
107
|
-
end
|
108
|
-
|
109
|
-
# @param _name [String]
|
110
|
-
# @param _state [String]
|
111
|
-
# @return [String]
|
112
|
-
def set_state(_name, _state)
|
113
|
-
fail NotImplementedError
|
114
|
-
end
|
115
|
-
|
116
|
-
# @param _name [String]
|
117
|
-
def clear_state(_name)
|
118
|
-
fail NotImplementedError
|
119
|
-
end
|
120
|
-
|
121
|
-
# @group Thresholds
|
122
|
-
|
123
|
-
# @param _name [String]
|
124
|
-
# @return [Integer]
|
125
|
-
def get_threshold(_name)
|
126
|
-
fail NotImplementedError
|
127
|
-
end
|
128
|
-
|
129
|
-
# @param _name [String]
|
130
|
-
# @param _threshold [Integer]
|
131
|
-
# @return [Integer]
|
132
|
-
def set_threshold(_name, _threshold)
|
133
|
-
fail NotImplementedError
|
134
|
-
end
|
135
|
-
|
136
|
-
# @param _name [String]
|
137
|
-
def clear_threshold(_name)
|
138
|
-
fail NotImplementedError
|
139
|
-
end
|
140
|
-
|
141
|
-
# @group Timeouts
|
142
|
-
|
143
|
-
# @param _name [String]
|
144
|
-
# @return [Integer]
|
145
|
-
def get_timeout(_name)
|
26
|
+
def get_state(_light)
|
146
27
|
fail NotImplementedError
|
147
28
|
end
|
148
29
|
|
149
|
-
|
150
|
-
# @param _timeout [Integer]
|
151
|
-
# @return [Integer]
|
152
|
-
def set_timeout(_name, _timeout)
|
30
|
+
def set_state(_light, _state)
|
153
31
|
fail NotImplementedError
|
154
32
|
end
|
155
33
|
|
156
|
-
|
157
|
-
def clear_timeout(_name)
|
34
|
+
def clear_state(_light)
|
158
35
|
fail NotImplementedError
|
159
36
|
end
|
160
37
|
end
|
@@ -8,126 +8,51 @@ module Stoplight
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def names
|
11
|
-
|
11
|
+
(all_failures.keys + all_states.keys).uniq
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
|
16
|
-
.select { |name| get_failures(name).empty? }
|
17
|
-
.each { |name| clear(name) }
|
14
|
+
def get_all(light)
|
15
|
+
[get_failures(light), get_state(light)]
|
18
16
|
end
|
19
17
|
|
20
|
-
def
|
21
|
-
|
22
|
-
clear_failures(name)
|
23
|
-
clear_state(name)
|
24
|
-
clear_threshold(name)
|
25
|
-
clear_timeout(name)
|
18
|
+
def get_failures(light)
|
19
|
+
all_failures[light.name] || []
|
26
20
|
end
|
27
21
|
|
28
|
-
def
|
29
|
-
|
22
|
+
def record_failure(light, failure)
|
23
|
+
failures = get_failures(light).unshift(failure).first(light.threshold)
|
24
|
+
all_failures[light.name] = failures
|
25
|
+
failures.size
|
30
26
|
end
|
31
27
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
28
|
+
def clear_failures(light)
|
29
|
+
failures = get_failures(light)
|
30
|
+
all_failures.delete(light.name)
|
31
|
+
failures
|
35
32
|
end
|
36
33
|
|
37
|
-
def
|
38
|
-
|
39
|
-
threshold = get_threshold(name)
|
40
|
-
failures = get_failures(name)
|
41
|
-
timeout = get_timeout(name)
|
42
|
-
DataStore.colorize(state, threshold, failures, timeout)
|
34
|
+
def get_state(light)
|
35
|
+
all_states[light.name] || State::UNLOCKED
|
43
36
|
end
|
44
37
|
|
45
|
-
def
|
46
|
-
|
38
|
+
def set_state(light, state)
|
39
|
+
all_states[light.name] = state
|
47
40
|
end
|
48
41
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
def clear_attempts(name)
|
54
|
-
attempts.delete(name)
|
55
|
-
end
|
56
|
-
|
57
|
-
def get_failures(name)
|
58
|
-
@data.fetch(DataStore.failures_key(name), DEFAULT_FAILURES)
|
59
|
-
end
|
60
|
-
|
61
|
-
def record_failure(name, failure)
|
62
|
-
DataStore.validate_failure!(failure)
|
63
|
-
@data[DataStore.failures_key(name)] ||= DEFAULT_FAILURES.dup
|
64
|
-
@data[DataStore.failures_key(name)].push(failure)
|
65
|
-
failure
|
66
|
-
end
|
67
|
-
|
68
|
-
def clear_failures(name)
|
69
|
-
@data.delete(DataStore.failures_key(name))
|
70
|
-
end
|
71
|
-
|
72
|
-
def get_state(name)
|
73
|
-
states[name]
|
74
|
-
end
|
75
|
-
|
76
|
-
def set_state(name, state)
|
77
|
-
DataStore.validate_state!(state)
|
78
|
-
states[name] = state
|
79
|
-
end
|
80
|
-
|
81
|
-
def clear_state(name)
|
82
|
-
states.delete(name)
|
83
|
-
end
|
84
|
-
|
85
|
-
def get_threshold(name)
|
86
|
-
thresholds[name]
|
87
|
-
end
|
88
|
-
|
89
|
-
def set_threshold(name, threshold)
|
90
|
-
DataStore.validate_threshold!(threshold)
|
91
|
-
thresholds[name] = threshold
|
92
|
-
end
|
93
|
-
|
94
|
-
def clear_threshold(name)
|
95
|
-
thresholds.delete(name)
|
96
|
-
end
|
97
|
-
|
98
|
-
def get_timeout(name)
|
99
|
-
timeouts[name]
|
100
|
-
end
|
101
|
-
|
102
|
-
def set_timeout(name, timeout)
|
103
|
-
DataStore.validate_timeout!(timeout)
|
104
|
-
timeouts[name] = timeout
|
105
|
-
end
|
106
|
-
|
107
|
-
def clear_timeout(name)
|
108
|
-
timeouts.delete(name)
|
42
|
+
def clear_state(light)
|
43
|
+
state = get_state(light)
|
44
|
+
all_states.delete(light.name)
|
45
|
+
state
|
109
46
|
end
|
110
47
|
|
111
48
|
private
|
112
49
|
|
113
|
-
|
114
|
-
|
115
|
-
@data[DataStore.attempts_key] ||= Hash.new(DEFAULT_ATTEMPTS)
|
116
|
-
end
|
117
|
-
|
118
|
-
# @return [Hash{String => String}]
|
119
|
-
def states
|
120
|
-
@data[DataStore.states_key] ||= Hash.new(DEFAULT_STATE)
|
121
|
-
end
|
122
|
-
|
123
|
-
# @return [Hash{String => Integer}]
|
124
|
-
def thresholds
|
125
|
-
@data[DataStore.thresholds_key] ||= Hash.new(DEFAULT_THRESHOLD)
|
50
|
+
def all_failures
|
51
|
+
@data['failures'] ||= {}
|
126
52
|
end
|
127
53
|
|
128
|
-
|
129
|
-
|
130
|
-
@data[DataStore.timeouts_key] ||= Hash.new(DEFAULT_TIMEOUT)
|
54
|
+
def all_states
|
55
|
+
@data['states'] ||= {}
|
131
56
|
end
|
132
57
|
end
|
133
58
|
end
|
@@ -1,171 +1,113 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
# rubocop:disable Metrics/ClassLength
|
3
|
-
|
4
|
-
require 'json'
|
5
2
|
|
6
3
|
module Stoplight
|
7
4
|
module DataStore
|
8
5
|
class Redis < Base
|
6
|
+
KEY_PREFIX = 'stoplight'.freeze
|
7
|
+
KEY_SEPARATOR = ':'.freeze
|
8
|
+
|
9
9
|
def initialize(redis)
|
10
10
|
@redis = redis
|
11
11
|
end
|
12
12
|
|
13
13
|
def names
|
14
|
-
@redis.hkeys(
|
15
|
-
end
|
16
|
-
|
17
|
-
def clear_stale
|
18
|
-
names
|
19
|
-
.select { |name| get_failures(name).empty? }
|
20
|
-
.each { |name| clear(name) }
|
21
|
-
end
|
14
|
+
state_names = @redis.hkeys(states_key)
|
22
15
|
|
23
|
-
|
24
|
-
@redis.
|
25
|
-
|
26
|
-
clear_failures(name)
|
27
|
-
clear_state(name)
|
28
|
-
clear_threshold(name)
|
29
|
-
clear_timeout(name)
|
16
|
+
pattern = key('failures', '*')
|
17
|
+
failure_names = @redis.scan_each(match: pattern).to_a.map do |key|
|
18
|
+
key.split(KEY_SEPARATOR).last
|
30
19
|
end
|
31
|
-
end
|
32
20
|
|
33
|
-
|
34
|
-
threshold = @redis.hget(DataStore.thresholds_key, name)
|
35
|
-
threshold = normalize_threshold(threshold)
|
36
|
-
@redis.hset(DataStore.thresholds_key, name, threshold)
|
37
|
-
rescue ::Redis::BaseError => error
|
38
|
-
raise Error::BadDataStore, error
|
21
|
+
(state_names + failure_names).uniq
|
39
22
|
end
|
40
23
|
|
41
|
-
def
|
42
|
-
@redis.
|
43
|
-
|
44
|
-
|
24
|
+
def get_all(light)
|
25
|
+
failures, state = @redis.multi do
|
26
|
+
query_failures(light)
|
27
|
+
@redis.hget('stoplight:states', light.name)
|
45
28
|
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def get_color(name)
|
49
|
-
DataStore.colorize(*colorize_args(name))
|
50
|
-
end
|
51
29
|
|
52
|
-
|
53
|
-
|
30
|
+
[
|
31
|
+
normalize_failures(failures, light.error_notifier),
|
32
|
+
normalize_state(state)
|
33
|
+
]
|
54
34
|
end
|
55
35
|
|
56
|
-
def
|
57
|
-
|
36
|
+
def get_failures(light)
|
37
|
+
normalize_failures(query_failures(light), light.error_notifier)
|
58
38
|
end
|
59
39
|
|
60
|
-
def
|
61
|
-
@redis.
|
62
|
-
|
40
|
+
def record_failure(light, failure)
|
41
|
+
size, _ = @redis.multi do
|
42
|
+
@redis.lpush(failures_key(light), failure.to_json)
|
43
|
+
@redis.ltrim(failures_key(light), 0, light.threshold - 1)
|
44
|
+
end
|
63
45
|
|
64
|
-
|
65
|
-
normalize_failures(@redis.lrange(DataStore.failures_key(name), 0, -1))
|
46
|
+
size
|
66
47
|
end
|
67
48
|
|
68
|
-
def
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
49
|
+
def clear_failures(light)
|
50
|
+
failures, _ = @redis.multi do
|
51
|
+
query_failures(light)
|
52
|
+
@redis.del(failures_key(light))
|
53
|
+
end
|
73
54
|
|
74
|
-
|
75
|
-
@redis.del(DataStore.failures_key(name))
|
55
|
+
normalize_failures(failures, light.error_notifier)
|
76
56
|
end
|
77
57
|
|
78
|
-
def get_state(
|
79
|
-
|
58
|
+
def get_state(light)
|
59
|
+
query_state(light) || State::UNLOCKED
|
80
60
|
end
|
81
61
|
|
82
|
-
def set_state(
|
83
|
-
|
84
|
-
@redis.hset(DataStore.states_key, name, state)
|
62
|
+
def set_state(light, state)
|
63
|
+
@redis.hset(states_key, light.name, state)
|
85
64
|
state
|
86
65
|
end
|
87
66
|
|
88
|
-
def clear_state(
|
89
|
-
@redis.
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
normalize_threshold(@redis.hget(DataStore.thresholds_key, name))
|
94
|
-
end
|
95
|
-
|
96
|
-
def set_threshold(name, threshold)
|
97
|
-
DataStore.validate_threshold!(threshold)
|
98
|
-
@redis.hset(DataStore.thresholds_key, name, threshold)
|
99
|
-
threshold
|
100
|
-
end
|
101
|
-
|
102
|
-
def clear_threshold(name)
|
103
|
-
@redis.hdel(DataStore.thresholds_key, name)
|
104
|
-
end
|
105
|
-
|
106
|
-
def get_timeout(name)
|
107
|
-
normalize_timeout(@redis.hget(DataStore.timeouts_key, name))
|
108
|
-
end
|
109
|
-
|
110
|
-
def set_timeout(name, timeout)
|
111
|
-
DataStore.validate_timeout!(timeout)
|
112
|
-
@redis.hset(DataStore.timeouts_key, name, timeout)
|
113
|
-
timeout
|
114
|
-
end
|
67
|
+
def clear_state(light)
|
68
|
+
state, _ = @redis.multi do
|
69
|
+
query_state(light)
|
70
|
+
@redis.hdel(states_key, light.name)
|
71
|
+
end
|
115
72
|
|
116
|
-
|
117
|
-
@redis.hdel(DataStore.timeouts_key, name)
|
73
|
+
normalize_state(state)
|
118
74
|
end
|
119
75
|
|
120
76
|
private
|
121
77
|
|
122
|
-
def
|
123
|
-
|
124
|
-
@redis.hget(DataStore.states_key, name)
|
125
|
-
@redis.hget(DataStore.thresholds_key, name)
|
126
|
-
@redis.lrange(DataStore.failures_key(name), 0, -1)
|
127
|
-
@redis.hget(DataStore.timeouts_key, name)
|
128
|
-
end
|
129
|
-
normalize_colorize_args(state, threshold, failures, timeout)
|
78
|
+
def query_failures(light)
|
79
|
+
@redis.lrange(failures_key(light), 0, -1)
|
130
80
|
end
|
131
81
|
|
132
|
-
def
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
82
|
+
def normalize_failures(failures, error_notifier)
|
83
|
+
failures.map do |json|
|
84
|
+
begin
|
85
|
+
Failure.from_json(json)
|
86
|
+
rescue => error
|
87
|
+
error_notifier.call(error)
|
88
|
+
Failure.from_error(error)
|
89
|
+
end
|
90
|
+
end
|
139
91
|
end
|
140
92
|
|
141
|
-
|
142
|
-
|
143
|
-
def normalize_attempts(attempts)
|
144
|
-
attempts ? attempts.to_i : DEFAULT_ATTEMPTS
|
93
|
+
def query_state(light)
|
94
|
+
@redis.hget(states_key, light.name)
|
145
95
|
end
|
146
96
|
|
147
|
-
|
148
|
-
|
149
|
-
def normalize_failures(failures)
|
150
|
-
failures.map { |json| Failure.from_json(json) }
|
97
|
+
def normalize_state(state)
|
98
|
+
state || State::UNLOCKED
|
151
99
|
end
|
152
100
|
|
153
|
-
|
154
|
-
|
155
|
-
def normalize_state(state)
|
156
|
-
state || DEFAULT_STATE
|
101
|
+
def failures_key(light)
|
102
|
+
key('failures', light.name)
|
157
103
|
end
|
158
104
|
|
159
|
-
|
160
|
-
|
161
|
-
def normalize_threshold(threshold)
|
162
|
-
threshold ? threshold.to_i : DEFAULT_THRESHOLD
|
105
|
+
def states_key
|
106
|
+
key('states')
|
163
107
|
end
|
164
108
|
|
165
|
-
|
166
|
-
|
167
|
-
def normalize_timeout(timeout)
|
168
|
-
timeout ? timeout.to_i : DEFAULT_TIMEOUT
|
109
|
+
def key(*pieces)
|
110
|
+
([KEY_PREFIX] + pieces).join(KEY_SEPARATOR)
|
169
111
|
end
|
170
112
|
end
|
171
113
|
end
|