sc4ry 0.1.7 → 0.2.1
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/.github/workflows/main.yml +27 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +47 -0
- data/Gemfile +9 -5
- data/README.md +283 -39
- data/Rakefile +41 -3
- data/VERSION +1 -0
- data/assets/images/sc4ry_workflow.png +0 -0
- data/bin/console +4 -3
- data/lib/sc4ry/backends/init.rb +3 -1
- data/lib/sc4ry/backends/memory.rb +41 -16
- data/lib/sc4ry/backends/redis.rb +59 -61
- data/lib/sc4ry/circuits.rb +240 -59
- data/lib/sc4ry/config.rb +99 -0
- data/lib/sc4ry/constants.rb +55 -0
- data/lib/sc4ry/dependencies.rb +14 -5
- data/lib/sc4ry/exceptions.rb +41 -8
- data/lib/sc4ry/exporters/init.rb +3 -1
- data/lib/sc4ry/helpers.rb +39 -30
- data/lib/sc4ry/logger.rb +55 -18
- data/lib/sc4ry/notifiers/init.rb +47 -19
- data/lib/sc4ry/notifiers/mattermost.rb +38 -31
- data/lib/sc4ry/notifiers/prometheus.rb +21 -19
- data/lib/sc4ry/run_controller.rb +41 -32
- data/lib/sc4ry/store.rb +87 -18
- data/lib/sc4ry/version.rb +8 -1
- data/lib/sc4ry.rb +3 -9
- data/samples/test.rb +159 -0
- data/sc4ry.gemspec +20 -16
- metadata +98 -8
- data/assets/logo_sc4ry.png +0 -0
data/lib/sc4ry/backends/redis.rb
CHANGED
@@ -1,65 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Sc4ry
|
2
|
-
|
3
|
-
|
4
|
-
|
4
|
+
module Backends
|
5
|
+
# Redis backend definition
|
6
|
+
class Redis
|
7
|
+
# Constructor
|
8
|
+
# @param [Hash] config Config map
|
9
|
+
# @return [Sc4ry::Backends::Redis] a Redis backend
|
10
|
+
def initialize(config)
|
11
|
+
@auth = config.slice(:auth)[:auth]
|
12
|
+
@config = config.slice(:host, :port, :db)
|
13
|
+
@be = ::Redis.new @config
|
14
|
+
@be.auth(@auth) if @auth
|
15
|
+
end
|
16
|
+
|
17
|
+
# return the list of find records in backend for a specific pattern
|
18
|
+
# @return [Array] list of record (for all hostname if hostname is specified)
|
19
|
+
def list
|
20
|
+
@be.keys('*').map(&:to_sym)
|
21
|
+
end
|
22
|
+
|
23
|
+
# return value of queried record
|
24
|
+
# @param key [Symbol] the name of the record
|
25
|
+
# @return [String] content value of record
|
26
|
+
def get(key:)
|
27
|
+
res = YAML.load(@be.get(key))
|
28
|
+
res[:exceptions].map! { |item| Object.const_get(item) if item.instance_of?(String) }
|
29
|
+
res
|
30
|
+
end
|
31
|
+
|
32
|
+
# defined and store value for specified key
|
33
|
+
# @param key [Symbol] :key the name of the record
|
34
|
+
# @param value [Symbol] :value the content value of the record
|
35
|
+
# @return [String] content value of record
|
36
|
+
def put(key:, value:)
|
37
|
+
data = value.dup
|
38
|
+
data[:exceptions].map! { |item| item.name.to_s if item.instance_of?(Class) }
|
39
|
+
@be.set key, data.to_yaml
|
40
|
+
end
|
41
|
+
|
42
|
+
# delete a specific record
|
43
|
+
# @param key [Symbol] the name of the record
|
44
|
+
# @return [Boolean] status of the operation
|
45
|
+
def del(key:)
|
46
|
+
@be.del key
|
47
|
+
end
|
48
|
+
|
49
|
+
# flush all records in backend
|
50
|
+
# @return [Boolean] status of the operation
|
51
|
+
def flush
|
52
|
+
@be.flushdb
|
53
|
+
end
|
5
54
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
@config = config.slice(:host, :port, :db)
|
12
|
-
@be = ::Redis.new @config
|
13
|
-
@be.auth(@auth) if @auth
|
14
|
-
end
|
15
|
-
|
16
|
-
# return the list of find records in backend for a specific pattern
|
17
|
-
# @return [Array] list of record (for all hostname if hostname is specified)
|
18
|
-
def list
|
19
|
-
return @store.keys('*')
|
20
|
-
end
|
21
|
-
|
22
|
-
|
23
|
-
# return value of queried record
|
24
|
-
# @param [Hash] options
|
25
|
-
# @option options [Symbol] :key the name of the record
|
26
|
-
# @return [String] content value of record
|
27
|
-
def get(options)
|
28
|
-
return @store.get(options[:key])
|
29
|
-
end
|
30
|
-
|
31
|
-
# defined and store value for specified key
|
32
|
-
# @param [Hash] options
|
33
|
-
# @option options [Symbol] :key the name of the record
|
34
|
-
# @option options [Symbol] :value the content value of the record
|
35
|
-
# @return [String] content value of record
|
36
|
-
def put(options)
|
37
|
-
@store.set options[:key], options[:value]
|
38
|
-
end
|
39
|
-
|
40
|
-
# delete a specific record
|
41
|
-
# @param [Hash] options
|
42
|
-
# @option options [Symbol] :key the name of the record
|
43
|
-
# @return [Boolean] status of the operation
|
44
|
-
def del(options)
|
45
|
-
@store.del options[:key]
|
46
|
-
end
|
47
|
-
|
48
|
-
# flush all records in backend
|
49
|
-
def flush
|
50
|
-
@store.flushdb
|
51
|
-
end
|
52
|
-
|
53
|
-
# verifiy a specific record existance
|
54
|
-
# @param [Hash] options
|
55
|
-
# @option options [Symbol] :key the name of the record
|
56
|
-
# @return [Boolean] presence of the record
|
57
|
-
def exist?(options)
|
58
|
-
return ( not @store.get(options[:key]).nil?)
|
59
|
-
end
|
60
|
-
|
61
|
-
|
55
|
+
# verifiy a specific record existence
|
56
|
+
# @param key [Symbol] the name of the record
|
57
|
+
# @return [Boolean] presence of the record
|
58
|
+
def exist?(key:)
|
59
|
+
!@be.get(key).nil?
|
62
60
|
end
|
63
|
-
|
64
61
|
end
|
65
|
-
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/sc4ry/circuits.rb
CHANGED
@@ -1,86 +1,258 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Sc4ry Module
|
4
|
+
# @note namespace
|
1
5
|
module Sc4ry
|
6
|
+
# Circuits and default configuration management class
|
2
7
|
class Circuits
|
8
|
+
include Sc4ry::Constants
|
9
|
+
include Sc4ry::Exceptions
|
10
|
+
|
11
|
+
@@circuits_store = Sc4ry::Store.instance
|
12
|
+
@@circuits_notifiers = Sc4ry::Notifiers
|
13
|
+
@@circuits_loggers = Sc4ry::Loggers
|
14
|
+
@@config = DEFAULT_CONFIG
|
15
|
+
|
16
|
+
# @!group forwarders
|
17
|
+
|
18
|
+
# Class method how forward the Notifiers class factory/manager
|
19
|
+
# @return [Sc4ry::Notifiers]
|
20
|
+
def self.notifiers
|
21
|
+
@@circuits_notifiers
|
22
|
+
end
|
23
|
+
|
24
|
+
# Class method how forward a Store manager class singleton
|
25
|
+
# @return [Sc4ry::Store]
|
26
|
+
def self.store
|
27
|
+
@@circuits_store
|
28
|
+
end
|
29
|
+
|
30
|
+
# Class method how forward the Logger manager class factory/manager
|
31
|
+
# @return [Sc4ry::Store]
|
32
|
+
def self.loggers
|
33
|
+
@@circuits_loggers
|
34
|
+
end
|
35
|
+
|
36
|
+
# @!endgroup
|
37
|
+
|
38
|
+
# @!group Default Sc4ry configuration management
|
39
|
+
|
40
|
+
# Class method how return de default Sc4ry config
|
41
|
+
# @return [Hash]
|
42
|
+
def self.default_config
|
43
|
+
@@config
|
44
|
+
end
|
45
|
+
|
46
|
+
# class method how merge a differential hash to default config
|
47
|
+
# @param [Hash] diff the differential hash config
|
48
|
+
# @example usage
|
49
|
+
# include Sc4ry
|
50
|
+
# Circuits.merge_default_config diff: {max_time: 20, notifiers: [:mattermost]}
|
51
|
+
def self.merge_default_config(diff:)
|
52
|
+
validator = Sc4ry::Config::Validator.new(definition: diff, from: @@config)
|
53
|
+
validator.validate!
|
54
|
+
@@config = validator.result
|
55
|
+
end
|
56
|
+
|
57
|
+
# class method for specifiying config by block
|
58
|
+
# @yield [Sc4ry::Config::ConfigMapper]
|
59
|
+
# @example usage
|
60
|
+
# include Sc4ry
|
61
|
+
# Circuits.configure do |spec|
|
62
|
+
# spec.max_failure_count = 3
|
63
|
+
# end
|
64
|
+
def self.configure
|
65
|
+
mapper = Sc4ry::Config::ConfigMapper.new(definition: @@config.dup)
|
66
|
+
yield(mapper)
|
67
|
+
validator = Sc4ry::Config::Validator.new(definition: mapper.config, from: @@config)
|
68
|
+
validator.validate!
|
69
|
+
@@config = validator.result
|
70
|
+
end
|
71
|
+
|
72
|
+
# old default config setter
|
73
|
+
# @deprecated use {.merge_default_config} instead
|
74
|
+
# @param [Hash] config
|
75
|
+
def self.default_config=(config)
|
76
|
+
warning_mess = 'DEPRECATED: Circuits.default_config= use Circuits.merge_default_config add: {<config_hash>}'
|
77
|
+
Sc4ry::Helpers.log level: :warn,
|
78
|
+
message: warning_mess
|
79
|
+
Circuits.merge_default_config(diff: config)
|
80
|
+
end
|
3
81
|
|
4
|
-
|
82
|
+
# @!endgroup
|
5
83
|
|
6
|
-
|
7
|
-
:timeout_value => 20,
|
8
|
-
:timeout => false,
|
9
|
-
:max_timeout_count => 5,
|
10
|
-
:max_time => 10,
|
11
|
-
:max_overtime_count => 3,
|
12
|
-
:check_delay => 30,
|
13
|
-
:notifiers => [],
|
14
|
-
:forward_unknown_exceptions => true,
|
15
|
-
:raise_on_opening => false,
|
16
|
-
:exceptions => [StandardError, RuntimeError]
|
17
|
-
}
|
84
|
+
# @!group Circuits management
|
18
85
|
|
19
|
-
|
20
|
-
|
86
|
+
# class method for registering a new circuit, cloud work with a block
|
87
|
+
# @yield [Sc4ry::Config::ConfigMapper]
|
88
|
+
# @param [Symbol] circuit a circuit name
|
89
|
+
# @param [Hash] config a config override on default config for the circuit
|
90
|
+
# @example usage
|
91
|
+
# include Sc4ry
|
92
|
+
# Circuits.register circuit: :mycircuit, config: {raise_on_opening: true, timeout: true}
|
93
|
+
# # or
|
94
|
+
# Circuits.register circuit: :mycircuit do |spec|
|
95
|
+
# spec.raise_on_opening = true
|
96
|
+
# spec.timeout = true
|
97
|
+
# end
|
98
|
+
# @return [Hash] the full config of the circuit after merge on default
|
99
|
+
# @raise [Sc4ryGenericError] if use config keyword with a block
|
100
|
+
# @raise [Sc4ryGenericError] if circuit already exist in current store.
|
101
|
+
def self.register(circuit:, config: {})
|
102
|
+
if !config.empty? && block_given?
|
103
|
+
raise Sc4ryGenericError,
|
104
|
+
'config: keyword must not be defined when block is given'
|
105
|
+
end
|
106
|
+
|
107
|
+
if block_given?
|
108
|
+
mapper = Sc4ry::Config::ConfigMapper.new(definition: @@config.dup)
|
109
|
+
yield(mapper)
|
110
|
+
validator = Sc4ry::Config::Validator.new(definition: mapper.config, from: @@config.dup)
|
111
|
+
else
|
112
|
+
validator = Sc4ry::Config::Validator.new(definition: config, from: @@config.dup)
|
113
|
+
end
|
114
|
+
validator.validate!
|
115
|
+
Sc4ry::Helpers.log level: :debug, message: "Circuit #{circuit} : registered"
|
116
|
+
raise Sc4ryGenericError, "Circuit: #{circuit} already exist in store" if @@circuits_store.exist? key: circuit
|
117
|
+
|
118
|
+
@@circuits_store.put key: circuit, value: validator.result
|
119
|
+
validator.result
|
21
120
|
end
|
22
121
|
|
23
|
-
|
24
|
-
|
25
|
-
|
122
|
+
# class method how list all circuits in current store
|
123
|
+
# @example usage
|
124
|
+
# include Sc4ry
|
125
|
+
# circuits = Circuits.list
|
126
|
+
# @return [Array] the list of [Symbol] circuits name
|
127
|
+
def self.list
|
128
|
+
@@circuits_store.list
|
129
|
+
end
|
26
130
|
|
131
|
+
# class method how flush all circuits in current store
|
132
|
+
# @example usage
|
133
|
+
# include Sc4ry
|
134
|
+
# Circuits.flush
|
135
|
+
# @return [true,false]
|
136
|
+
def self.flush
|
137
|
+
@@circuits_store.flush
|
27
138
|
end
|
28
139
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
140
|
+
# class method for unregistering a circuit
|
141
|
+
# @param [Symbol] circuit a circuit name
|
142
|
+
# @example usage
|
143
|
+
# include Sc4ry
|
144
|
+
# Circuits.unregister circuit: :mycircuit
|
145
|
+
# @raise [Sc4ryGenericError] if circuit not found in current store.
|
146
|
+
# @return [true,false]
|
147
|
+
def self.unregister(circuit:)
|
148
|
+
raise Sc4ryGenericError, "Circuit #{circuit} not found" unless Circuits.list.include? circuit
|
149
|
+
|
150
|
+
@@circuits_store.del key: circuit
|
151
|
+
Sc4ry::Helpers.log level: :debug, message: "Circuit #{circuit} : unregistered"
|
152
|
+
true
|
35
153
|
end
|
36
154
|
|
37
|
-
|
38
|
-
|
155
|
+
# class method for get a specific circuit by circuit name
|
156
|
+
# @param [Symbol] circuit a circuit name
|
157
|
+
# @example usage
|
158
|
+
# include Sc4ry
|
159
|
+
# Circuits.get circuit: :mycircuit
|
160
|
+
# @return [Hash] the circuit record in current store included values and status if the circuit have already run.
|
161
|
+
def self.get(circuit:)
|
162
|
+
@@circuits_store.get key: circuit
|
39
163
|
end
|
40
164
|
|
165
|
+
# class method for update the config of a specific circuit by circuit name
|
166
|
+
# @param [Symbol] circuit a circuit name
|
167
|
+
# @param [Hash] config a config hash to merge on current config
|
168
|
+
# @example usage
|
169
|
+
# include Sc4ry
|
170
|
+
# Circuits.update_config circuit: :mycircuit, config: {}
|
171
|
+
# @note : <b>important</b> updating config will reset status and values !
|
172
|
+
# @return [Hash] new config for this circuit
|
173
|
+
def self.update_config(circuit:, config: { forward_unknown_exceptions: false })
|
174
|
+
raise Sc4ryGenericError, "Circuit #{circuit} not found" unless Circuits.list.include? circuit
|
175
|
+
|
176
|
+
save = @@circuits_store.get key: circuit
|
177
|
+
save.delete_if { |key, _val| %i[status values].include? key }
|
178
|
+
Circuits.unregister(circuit: circuit)
|
179
|
+
save.merge! config
|
180
|
+
Circuits.register circuit: circuit, config: save
|
181
|
+
end
|
41
182
|
|
42
|
-
|
43
|
-
|
183
|
+
# class method for get the status of a specific circuit by circuit name
|
184
|
+
# @param [Symbol] circuit a circuit name
|
185
|
+
# @example usage
|
186
|
+
# include Sc4ry
|
187
|
+
# Circuits.status circuit: :mycircuit
|
188
|
+
# @return [Symbol] status must in [:open,:half_open,:closed,:never_run]
|
189
|
+
def self.status(circuit:)
|
190
|
+
data = @@circuits_store.get key: circuit
|
191
|
+
data.include?(:status) ? data[:status][:general] : :never_run
|
44
192
|
end
|
45
193
|
|
46
|
-
|
194
|
+
# class method for running circuit, need a block
|
195
|
+
# @yield [Proc]
|
196
|
+
# @param [Symbol] circuit a circuit name
|
197
|
+
# @example usage
|
198
|
+
# include Sc4ry
|
199
|
+
# Circuits.run circuit: :mycircuit do
|
200
|
+
# # [...] your code like a Restclient.get("URL")
|
201
|
+
# end
|
202
|
+
# # or
|
203
|
+
# Circuits.run do
|
204
|
+
# # [...] your code like a Restclient.get("URL")
|
205
|
+
# # running with the first define circuit (use only on a one circuit usage)
|
206
|
+
# end
|
207
|
+
# @return [Hash] a result like ":general=>:open, :failure_count=>X, :overtime_count=>X, :timeout_count=>X"
|
208
|
+
# @raise [Sc4ryGenericError] if circuit already not exit, block is missing or store empty
|
209
|
+
def self.run(circuit: nil, &block)
|
47
210
|
circuits_list = Circuits.list
|
48
|
-
raise
|
49
|
-
raise
|
50
|
-
|
51
|
-
|
52
|
-
|
211
|
+
raise Sc4ryGenericError, 'No circuit block given' unless block_given?
|
212
|
+
raise Sc4ryGenericError, 'No circuits defined' if circuits_list.empty?
|
213
|
+
|
214
|
+
circuit_name = circuit || circuits_list.first
|
215
|
+
raise Sc4ryGenericError, "Circuit #{circuit_name} not found" unless circuits_list.include? circuit_name
|
216
|
+
|
217
|
+
circuit_to_run = Circuits.get circuit: circuit_name
|
53
218
|
skip = false
|
54
|
-
if
|
55
|
-
|
56
|
-
|
57
|
-
skip = true if ((@now - circuit[:values].last[:end_time]) < circuit[:check_delay])
|
58
|
-
end
|
219
|
+
if circuit_to_run.include?(:status) && (circuit_to_run[:status][:general] == :open)
|
220
|
+
@now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
221
|
+
skip = true if (@now - circuit_to_run[:values].last[:end_time]) < circuit_to_run[:check_delay]
|
59
222
|
end
|
60
|
-
unless skip
|
61
|
-
controller = Sc4ry::RunController.new(
|
223
|
+
unless skip
|
224
|
+
controller = Sc4ry::RunController.new(circuit_to_run)
|
62
225
|
Circuits.control circuit: circuit_name, values: controller.run(block: block)
|
63
226
|
end
|
64
|
-
|
65
|
-
|
227
|
+
result = @@circuits_store.get key: circuit_name
|
228
|
+
Sc4ry::Helpers.log level: :debug, message: "Circuit #{circuit_name} : status #{result[:status]}"
|
229
|
+
result
|
66
230
|
end
|
67
231
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
232
|
+
# @!endgroup
|
233
|
+
|
234
|
+
# the private class method to control circuits running status
|
235
|
+
# @param [Symbol] circuit the name the circuit to control
|
236
|
+
# @param [Hash] values the resut value of a run
|
237
|
+
# @return [Boolean]
|
238
|
+
def self.control(circuit:, values:)
|
239
|
+
data = @@circuits_store.get key: circuit
|
240
|
+
unless data.include? :status
|
241
|
+
data[:status] =
|
242
|
+
{ general: :closed, failure_count: 0, overtime_count: 0, timeout_count: 0 }
|
243
|
+
end
|
244
|
+
data[:values] = [] unless data.include? :values
|
73
245
|
level = [data[:max_failure_count].to_i, data[:max_timeout_count].to_i, data[:max_overtime_count].to_i].max
|
74
246
|
data[:values].shift if data[:values].size > level
|
75
|
-
data[:values].push
|
247
|
+
data[:values].push values
|
76
248
|
worst_status = []
|
77
|
-
[
|
78
|
-
if
|
249
|
+
%w[failure overtime timeout].each do |control|
|
250
|
+
if values[control.to_sym] == true
|
79
251
|
data[:status]["#{control}_count".to_sym] += 1
|
80
252
|
else
|
81
253
|
data[:status]["#{control}_count".to_sym] = 0
|
82
254
|
end
|
83
|
-
|
255
|
+
|
84
256
|
case data[:status]["#{control}_count".to_sym]
|
85
257
|
when 0
|
86
258
|
worst_status.push :closed
|
@@ -91,14 +263,23 @@ module Sc4ry
|
|
91
263
|
end
|
92
264
|
end
|
93
265
|
save = data[:status][:general]
|
94
|
-
[
|
266
|
+
%i[closed half_open open].each do |status|
|
95
267
|
data[:status][:general] = status if worst_status.include? status
|
96
268
|
end
|
97
|
-
if save != data[:status][:general]
|
98
|
-
raise
|
99
|
-
|
100
|
-
|
101
|
-
|
269
|
+
if save != data[:status][:general]
|
270
|
+
raise CircuitBreaked if data[:status][:general] == :open && data[:raise_on_opening]
|
271
|
+
|
272
|
+
if data[:status][:general] == :open
|
273
|
+
Sc4ry::Helpers.log level: :error,
|
274
|
+
message: "Circuit #{circuit} : breacking ! "
|
275
|
+
end
|
276
|
+
if data[:status][:general] == :closed
|
277
|
+
Sc4ry::Helpers.log level: :info,
|
278
|
+
message: "Circuit #{circuit} : is now closed"
|
279
|
+
end
|
280
|
+
Sc4ry::Helpers.notify circuit: circuit, config: data
|
281
|
+
end
|
282
|
+
@@circuits_store.put key: circuit, value: data
|
102
283
|
end
|
103
284
|
end
|
104
|
-
end
|
285
|
+
end
|
data/lib/sc4ry/config.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Sc4ry module
|
4
|
+
# @note namespace
|
5
|
+
module Sc4ry
|
6
|
+
# Sc4ry::Config module
|
7
|
+
# @note namespace
|
8
|
+
module Config
|
9
|
+
# Configuration validator for Sc4ry default and circuits configuration
|
10
|
+
# @private
|
11
|
+
class Validator
|
12
|
+
# accessor on Circuit definition given for validation
|
13
|
+
attr_reader :input
|
14
|
+
|
15
|
+
# accessor on circuit initial definition from default config or given in construction by from keyword
|
16
|
+
# @note altered by reference to final result mapping with {#validate!}
|
17
|
+
attr_reader :config
|
18
|
+
|
19
|
+
def result
|
20
|
+
@config
|
21
|
+
end
|
22
|
+
|
23
|
+
include Sc4ry::Constants
|
24
|
+
include Sc4ry::Exceptions
|
25
|
+
# Validator constructor
|
26
|
+
# @param [Hash] definition the config hash to merge and validate
|
27
|
+
# @param [Hash] from config hash merged on origin (default : the Sc4ry base config from Constants )
|
28
|
+
# @return [Validator] a new instance of Validator
|
29
|
+
def initialize(definition:, from: DEFAULT_CONFIG)
|
30
|
+
@config = from
|
31
|
+
@input = definition
|
32
|
+
end
|
33
|
+
|
34
|
+
# Validation method, alter by reference the config attribut
|
35
|
+
# @raise ConfigError if un unknown key is given in definition to merge.
|
36
|
+
def validate!
|
37
|
+
unknown_keys = @input.keys.difference @config.keys
|
38
|
+
raise ConfigError, "Unknown keys in config set : #{unknown_keys}" unless unknown_keys.empty?
|
39
|
+
|
40
|
+
validate_formats
|
41
|
+
@config.merge! @input
|
42
|
+
format_exceptions
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Validation private sub method
|
48
|
+
# @raise ConfigError if proposed values haven't the good format and deeply in array
|
49
|
+
def validate_formats
|
50
|
+
@input.each do |spec, value|
|
51
|
+
unless DEFAULT_CONFIG_FORMATS[spec][:proc].call(value)
|
52
|
+
raise ConfigError,
|
53
|
+
"#{spec} value #{DEFAULT_CONFIG_FORMATS[spec][:desc]}"
|
54
|
+
end
|
55
|
+
|
56
|
+
next unless DEFAULT_CONFIG_FORMATS[spec].include? :list
|
57
|
+
|
58
|
+
value.each do |item|
|
59
|
+
unless DEFAULT_CONFIG_FORMATS[spec][:list].include? item
|
60
|
+
raise ConfigError,
|
61
|
+
"#{spec} value must be in #{DEFAULT_CONFIG_FORMATS[spec][:list]}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# adapter for exception key in config String to Constant Class Name if need
|
68
|
+
# @note by reference
|
69
|
+
def format_exceptions
|
70
|
+
@config[:exceptions].map! { |item| item.instance_of?(String) ? Object.const_get(item) : item }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Config Data mapper for block yielding methods for configuration
|
75
|
+
# @note work for/with {Sc4ry::Circuits.configure} and {Sc4ry::Circuits.register} when block given
|
76
|
+
class ConfigMapper
|
77
|
+
include Sc4ry::Constants
|
78
|
+
|
79
|
+
# config from given definition passed in constructor
|
80
|
+
attr_reader :config
|
81
|
+
|
82
|
+
# the mapping constructor from a given definition or the default From Sc4ry config (Constant)
|
83
|
+
# @param [Hash] definition a config hash
|
84
|
+
# @note creating dynamically accessors on config record given in definition
|
85
|
+
def initialize(definition: DEFAULT_CONFIG)
|
86
|
+
@config = definition
|
87
|
+
@config.each do |key, _value|
|
88
|
+
define_singleton_method "#{key}=".to_sym do |val|
|
89
|
+
key = __method__.to_s.chop.to_sym
|
90
|
+
@config[key] = val
|
91
|
+
end
|
92
|
+
define_singleton_method key do
|
93
|
+
return @config[__method__]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Sc4ry module
|
4
|
+
# @note namespace
|
5
|
+
module Sc4ry
|
6
|
+
# Sc4ry::Constants module
|
7
|
+
# @note namespace
|
8
|
+
module Constants
|
9
|
+
# notifiers available in Sc4ry natively
|
10
|
+
CURRENT_NOTIFIERS = %i[prometheus mattermost]
|
11
|
+
|
12
|
+
# the Sc4ry default config entries and values
|
13
|
+
DEFAULT_CONFIG = {
|
14
|
+
max_failure_count: 5,
|
15
|
+
timeout_value: 20,
|
16
|
+
timeout: false,
|
17
|
+
max_timeout_count: 5,
|
18
|
+
max_time: 10,
|
19
|
+
max_overtime_count: 3,
|
20
|
+
check_delay: 30,
|
21
|
+
notifiers: [],
|
22
|
+
forward_unknown_exceptions: true,
|
23
|
+
raise_on_opening: false,
|
24
|
+
exceptions: [StandardError, RuntimeError]
|
25
|
+
}
|
26
|
+
|
27
|
+
# Default config supported entries with format and Proc checker for {Sc4ry::Config::Validator}
|
28
|
+
DEFAULT_CONFIG_FORMATS = {
|
29
|
+
max_failure_count: { proc: proc { |item| item.instance_of?(Integer) }, desc: 'must be an Integer' },
|
30
|
+
timeout_value: { proc: proc { |item| item.instance_of?(Integer) }, desc: 'must be an Integer' },
|
31
|
+
timeout: { proc: proc { |item| [true, false].include? item }, desc: 'must be a Boolean' },
|
32
|
+
max_timeout_count: { proc: proc { |item| item.instance_of?(Integer) }, desc: 'must be an Integer' },
|
33
|
+
max_time: { proc: proc { |item| item.instance_of?(Integer) }, desc: 'must be an Integer' },
|
34
|
+
max_overtime_count: { proc: proc { |item| item.instance_of?(Integer) }, desc: 'must be an Integer' },
|
35
|
+
check_delay: { proc: proc { |item| item.instance_of?(Integer) }, desc: 'must be an Integer' },
|
36
|
+
notifiers: {
|
37
|
+
proc: proc do |item|
|
38
|
+
item.instance_of?(Array) and item.select { |val| val.instance_of?(Symbol) }.size == item.size
|
39
|
+
end,
|
40
|
+
desc: 'must be an Array of Symbol',
|
41
|
+
list: CURRENT_NOTIFIERS
|
42
|
+
},
|
43
|
+
forward_unknown_exceptions: { proc: proc { |item| [true, false].include? item }, desc: 'must be a Boolean' },
|
44
|
+
raise_on_opening: { proc: proc { |item| [true, false].include? item }, desc: 'must be a Boolean' },
|
45
|
+
exceptions: {
|
46
|
+
proc: proc do |item|
|
47
|
+
item.instance_of?(Array) and item.select do |val|
|
48
|
+
[Class, String].include? val.class
|
49
|
+
end.size == item.size
|
50
|
+
end,
|
51
|
+
desc: 'must be an Array of Exception(Class) or String'
|
52
|
+
}
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
data/lib/sc4ry/dependencies.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubygems depends
|
1
4
|
require 'rest-client'
|
2
5
|
require 'prometheus/client'
|
3
6
|
require 'prometheus/client/push'
|
7
|
+
require 'redis'
|
8
|
+
require 'version'
|
4
9
|
|
5
|
-
|
10
|
+
# Stdlibs depends
|
11
|
+
require 'logger'
|
6
12
|
require 'timeout'
|
7
13
|
require 'forwardable'
|
8
14
|
require 'singleton'
|
@@ -11,15 +17,18 @@ require 'openssl'
|
|
11
17
|
require 'net/http'
|
12
18
|
require 'uri'
|
13
19
|
require 'json'
|
20
|
+
require 'yaml'
|
14
21
|
|
15
|
-
|
16
|
-
|
22
|
+
# Sc4ry internal depends
|
17
23
|
require_relative 'helpers'
|
18
24
|
require_relative 'exceptions'
|
19
25
|
require_relative 'logger'
|
26
|
+
require_relative 'constants'
|
27
|
+
require_relative 'config'
|
28
|
+
require_relative 'notifiers/init'
|
29
|
+
require_relative 'exporters/init'
|
20
30
|
require_relative 'backends/init'
|
31
|
+
|
21
32
|
require_relative 'store'
|
22
33
|
require_relative 'run_controller'
|
23
34
|
require_relative 'circuits'
|
24
|
-
require_relative 'notifiers/init'
|
25
|
-
require_relative 'exporters/init'
|