prefab-cloud-ruby 0.19.0 → 0.21.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/.envrc.sample +3 -0
- data/.github/workflows/ruby.yml +4 -0
- data/.gitmodules +3 -0
- data/Gemfile +12 -12
- data/Gemfile.lock +16 -14
- data/README.md +1 -1
- data/Rakefile +13 -14
- data/VERSION +1 -1
- data/lib/prefab/auth_interceptor.rb +2 -1
- data/lib/prefab/cancellable_interceptor.rb +8 -7
- data/lib/prefab/client.rb +33 -24
- data/lib/prefab/config_client.rb +55 -66
- data/lib/prefab/config_loader.rb +7 -114
- data/lib/prefab/config_resolver.rb +27 -57
- data/lib/prefab/config_value_unwrapper.rb +23 -0
- data/lib/prefab/criteria_evaluator.rb +96 -0
- data/lib/prefab/errors/invalid_api_key_error.rb +1 -1
- data/lib/prefab/feature_flag_client.rb +13 -145
- data/lib/prefab/internal_logger.rb +6 -5
- data/lib/prefab/local_config_parser.rb +110 -0
- data/lib/prefab/logger_client.rb +30 -36
- data/lib/prefab/murmer3.rb +3 -4
- data/lib/prefab/noop_cache.rb +5 -7
- data/lib/prefab/noop_stats.rb +2 -3
- data/lib/prefab/options.rb +11 -9
- data/lib/prefab/ratelimit_client.rb +11 -13
- data/lib/prefab/sse_logger.rb +3 -2
- data/lib/prefab/weighted_value_resolver.rb +42 -0
- data/lib/prefab/yaml_config_parser.rb +32 -0
- data/lib/prefab-cloud-ruby.rb +7 -2
- data/lib/prefab_pb.rb +49 -43
- data/lib/prefab_services_pb.rb +0 -1
- data/prefab-cloud-ruby.gemspec +28 -19
- data/test/.prefab.unit_tests.config.yaml +3 -2
- data/test/integration_test.rb +98 -0
- data/test/integration_test_helpers.rb +37 -0
- data/test/test_client.rb +32 -31
- data/test/test_config_client.rb +21 -20
- data/test/test_config_loader.rb +48 -37
- data/test/test_config_resolver.rb +312 -135
- data/test/test_config_value_unwrapper.rb +83 -0
- data/test/test_criteria_evaluator.rb +533 -0
- data/test/test_feature_flag_client.rb +35 -347
- data/test/test_helper.rb +18 -14
- data/test/test_integration.rb +33 -0
- data/test/test_local_config_parser.rb +78 -0
- data/test/test_logger.rb +90 -42
- data/test/test_weighted_value_resolver.rb +65 -0
- metadata +24 -27
- data/lib/prefab/config_helper.rb +0 -31
- data/run_test_harness_server.sh +0 -8
- data/test/harness_server.rb +0 -64
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class LocalConfigParser
|
5
|
+
class << self
|
6
|
+
def parse(key, value, config, file)
|
7
|
+
if value.instance_of?(Hash)
|
8
|
+
if value['feature_flag']
|
9
|
+
config[key] = feature_flag_config(file, key, value)
|
10
|
+
else
|
11
|
+
value.each do |nest_key, nest_value|
|
12
|
+
nested_key = "#{key}.#{nest_key}"
|
13
|
+
nested_key = key if nest_key == '_'
|
14
|
+
parse(nested_key, nest_value, config, file)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
else
|
18
|
+
config[key] = {
|
19
|
+
source: file,
|
20
|
+
match: 'default',
|
21
|
+
config: Prefab::Config.new(
|
22
|
+
config_type: :CONFIG,
|
23
|
+
key: key,
|
24
|
+
rows: [
|
25
|
+
Prefab::ConfigRow.new(values: [
|
26
|
+
Prefab::ConditionalValue.new(value: value_from(key, value))
|
27
|
+
])
|
28
|
+
]
|
29
|
+
)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
config
|
34
|
+
end
|
35
|
+
|
36
|
+
def value_from(key, raw)
|
37
|
+
case raw
|
38
|
+
when String
|
39
|
+
if key.to_s.start_with? Prefab::LoggerClient::BASE_KEY
|
40
|
+
prefab_log_level_resolve = Prefab::LogLevel.resolve(raw.upcase.to_sym) || Prefab::LogLevel::NOT_SET_LOG_LEVEL
|
41
|
+
{ log_level: prefab_log_level_resolve }
|
42
|
+
else
|
43
|
+
{ string: raw }
|
44
|
+
end
|
45
|
+
when Integer
|
46
|
+
{ int: raw }
|
47
|
+
when TrueClass, FalseClass
|
48
|
+
{ bool: raw }
|
49
|
+
when Float
|
50
|
+
{ double: raw }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def feature_flag_config(file, key, value)
|
55
|
+
criterion = (parse_criterion(value['criterion']) if value['criterion'])
|
56
|
+
|
57
|
+
variant = Prefab::ConfigValue.new(value_from(key, value['value']))
|
58
|
+
|
59
|
+
row = Prefab::ConfigRow.new(
|
60
|
+
values: [
|
61
|
+
Prefab::ConditionalValue.new(
|
62
|
+
criteria: [criterion].compact,
|
63
|
+
value: Prefab::ConfigValue.new(
|
64
|
+
weighted_values: Prefab::WeightedValues.new(weighted_values: [
|
65
|
+
Prefab::WeightedValue.new(
|
66
|
+
weight: 1000,
|
67
|
+
value: variant
|
68
|
+
)
|
69
|
+
])
|
70
|
+
)
|
71
|
+
)
|
72
|
+
]
|
73
|
+
)
|
74
|
+
|
75
|
+
raise Prefab::Error, "Feature flag config `#{key}` #{file} must have a `value`" unless value.key?('value')
|
76
|
+
|
77
|
+
{
|
78
|
+
source: file,
|
79
|
+
match: key,
|
80
|
+
config: Prefab::Config.new(
|
81
|
+
config_type: :FEATURE_FLAG,
|
82
|
+
key: key,
|
83
|
+
allowable_values: [variant],
|
84
|
+
rows: [row]
|
85
|
+
)
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_criterion(criterion)
|
90
|
+
Prefab::Criterion.new(operator: criterion['operator'],
|
91
|
+
property_name: parse_property(criterion),
|
92
|
+
value_to_match: parse_value_to_match(criterion['values']))
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_property(criterion)
|
96
|
+
if criterion['operator'] == 'LOOKUP_KEY_IN'
|
97
|
+
Prefab::CriteriaEvaluator::LOOKUP_KEY
|
98
|
+
else
|
99
|
+
criterion['property']
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def parse_value_to_match(values)
|
104
|
+
raise "Can't handle #{values}" unless values.instance_of?(Array)
|
105
|
+
|
106
|
+
Prefab::ConfigValue.new(string_list: Prefab::StringList.new(values: values))
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/prefab/logger_client.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Prefab
|
3
4
|
class LoggerClient < Logger
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
INTERNAL_PREFIX = "cloud.prefab.client"
|
5
|
+
SEP = '.'
|
6
|
+
BASE_KEY = 'log-level'
|
7
|
+
UNKNOWN_PATH = 'unknown.'
|
8
|
+
INTERNAL_PREFIX = 'cloud.prefab.client'
|
9
9
|
|
10
10
|
LOG_LEVEL_LOOKUPS = {
|
11
11
|
Prefab::LogLevel::NOT_SET_LOG_LEVEL => Logger::DEBUG,
|
@@ -33,25 +33,21 @@ module Prefab
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def log_internal(message, path = nil, progname, severity, &block)
|
36
|
-
if path
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
36
|
+
path = if path
|
37
|
+
"#{INTERNAL_PREFIX}.#{path}"
|
38
|
+
else
|
39
|
+
INTERNAL_PREFIX
|
40
|
+
end
|
41
41
|
|
42
42
|
log(message, path, progname, severity, &block)
|
43
43
|
end
|
44
44
|
|
45
|
-
def log(message, path, progname, severity
|
46
|
-
level = level_of(path)
|
47
|
-
progname = "#{path}: #{progname}"
|
45
|
+
def log(message, path, progname, severity)
|
48
46
|
severity ||= Logger::UNKNOWN
|
49
|
-
if @logdev.nil? || severity <
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
progname = @progname
|
54
|
-
end
|
47
|
+
return true if @logdev.nil? || severity < level_of(path) || @silences[local_log_id]
|
48
|
+
|
49
|
+
progname = "#{path}: #{progname || @progname}"
|
50
|
+
|
55
51
|
if message.nil?
|
56
52
|
if block_given?
|
57
53
|
message = yield
|
@@ -60,8 +56,10 @@ module Prefab
|
|
60
56
|
progname = @progname
|
61
57
|
end
|
62
58
|
end
|
59
|
+
|
63
60
|
@logdev.write(
|
64
|
-
format_message(format_severity(severity), Time.now, progname, message)
|
61
|
+
format_message(format_severity(severity), Time.now, progname, message)
|
62
|
+
)
|
65
63
|
true
|
66
64
|
end
|
67
65
|
|
@@ -86,23 +84,23 @@ module Prefab
|
|
86
84
|
end
|
87
85
|
|
88
86
|
def debug?
|
89
|
-
true
|
87
|
+
true
|
90
88
|
end
|
91
89
|
|
92
90
|
def info?
|
93
|
-
true
|
91
|
+
true
|
94
92
|
end
|
95
93
|
|
96
94
|
def warn?
|
97
|
-
true
|
95
|
+
true
|
98
96
|
end
|
99
97
|
|
100
98
|
def error?
|
101
|
-
true
|
99
|
+
true
|
102
100
|
end
|
103
101
|
|
104
102
|
def fatal?
|
105
|
-
true
|
103
|
+
true
|
106
104
|
end
|
107
105
|
|
108
106
|
def level
|
@@ -129,13 +127,10 @@ module Prefab
|
|
129
127
|
# Find the closest match to 'log_level.path' in config
|
130
128
|
def level_of(path)
|
131
129
|
closest_log_level_match = @config_client.get(BASE_KEY, :WARN)
|
132
|
-
path.split(SEP).
|
130
|
+
path.split(SEP).each_with_object([BASE_KEY]) do |n, memo|
|
133
131
|
memo << n
|
134
132
|
val = @config_client.get(memo.join(SEP), nil)
|
135
|
-
unless val.nil?
|
136
|
-
closest_log_level_match = val
|
137
|
-
end
|
138
|
-
memo
|
133
|
+
closest_log_level_match = val unless val.nil?
|
139
134
|
end
|
140
135
|
closest_log_level_match_int = Prefab::LogLevel.resolve(closest_log_level_match)
|
141
136
|
LOG_LEVEL_LOOKUPS[closest_log_level_match_int]
|
@@ -151,10 +146,10 @@ module Prefab
|
|
151
146
|
def get_path(absolute_path, base_label)
|
152
147
|
path = (absolute_path || UNKNOWN_PATH).dup
|
153
148
|
path.slice! Dir.pwd
|
154
|
-
path.gsub!(
|
149
|
+
path.gsub!(%r{(.*)?(?=/lib)}im, '') # replace everything before first lib
|
155
150
|
|
156
|
-
path = path.gsub(
|
157
|
-
path.slice!
|
151
|
+
path = path.gsub('/', SEP).gsub(/.rb.*/, '') + SEP + base_label
|
152
|
+
path.slice! '.lib'
|
158
153
|
path.slice! SEP
|
159
154
|
path
|
160
155
|
end
|
@@ -163,9 +158,8 @@ module Prefab
|
|
163
158
|
# StubConfigClient to be used while config client initializes
|
164
159
|
# since it may log
|
165
160
|
class BootstrappingConfigClient
|
166
|
-
def get(
|
167
|
-
ENV[
|
161
|
+
def get(_key, default = nil)
|
162
|
+
ENV['PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL'] ? ENV['PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL'].upcase.to_sym : default
|
168
163
|
end
|
169
164
|
end
|
170
165
|
end
|
171
|
-
|
data/lib/prefab/murmer3.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
class Murmur3
|
3
4
|
## MurmurHash3 was written by Austin Appleby, and is placed in the public
|
4
5
|
## domain. The author hereby disclaims copyright to this source code.
|
@@ -9,7 +10,6 @@ class Murmur3
|
|
9
10
|
((x << r) | (x >> (32 - r))) & MASK32
|
10
11
|
end
|
11
12
|
|
12
|
-
|
13
13
|
def self.murmur3_32_fmix(h)
|
14
14
|
h &= MASK32
|
15
15
|
h ^= h >> 16
|
@@ -25,7 +25,7 @@ class Murmur3
|
|
25
25
|
(k1 * 0x1b873593) & MASK32
|
26
26
|
end
|
27
27
|
|
28
|
-
def self.murmur3_32(str, seed=0)
|
28
|
+
def self.murmur3_32(str, seed = 0)
|
29
29
|
h1 = seed
|
30
30
|
numbers = str.unpack('V*C*')
|
31
31
|
tailn = str.length % 4
|
@@ -33,7 +33,7 @@ class Murmur3
|
|
33
33
|
for k1 in numbers
|
34
34
|
h1 ^= murmur3_32__mmix(k1)
|
35
35
|
h1 = murmur3_32_rotl(h1, 13)
|
36
|
-
h1 = (h1*5 + 0xe6546b64) & MASK32
|
36
|
+
h1 = (h1 * 5 + 0xe6546b64) & MASK32
|
37
37
|
end
|
38
38
|
|
39
39
|
unless tail.empty?
|
@@ -47,5 +47,4 @@ class Murmur3
|
|
47
47
|
h1 ^= str.length
|
48
48
|
murmur3_32_fmix(h1)
|
49
49
|
end
|
50
|
-
|
51
50
|
end
|
data/lib/prefab/noop_cache.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Prefab
|
3
4
|
class NoopCache
|
4
|
-
def fetch(
|
5
|
+
def fetch(_name, _opts)
|
5
6
|
yield
|
6
7
|
end
|
7
8
|
|
8
|
-
def write(name, value, opts=nil)
|
9
|
-
end
|
9
|
+
def write(name, value, opts = nil); end
|
10
10
|
|
11
|
-
def read(name)
|
12
|
-
end
|
11
|
+
def read(name); end
|
13
12
|
|
14
|
-
def delete(name)
|
15
|
-
end
|
13
|
+
def delete(name); end
|
16
14
|
end
|
17
15
|
end
|
data/lib/prefab/noop_stats.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
module Prefab
|
3
2
|
|
3
|
+
module Prefab
|
4
4
|
class NoopStats
|
5
5
|
# receives increment("prefab.ratelimit.limitcheck", {:tags=>["policy_group:page_view", "pass:true"]})
|
6
|
-
def increment(name, opts={})
|
7
|
-
end
|
6
|
+
def increment(name, opts = {}); end
|
8
7
|
end
|
9
8
|
end
|
data/lib/prefab/options.rb
CHANGED
@@ -19,17 +19,19 @@ module Prefab
|
|
19
19
|
attr_reader :prefab_envs
|
20
20
|
|
21
21
|
DEFAULT_LOG_FORMATTER = proc { |severity, datetime, progname, msg|
|
22
|
-
"#{severity.ljust(5)} #{datetime}
|
22
|
+
"#{severity.ljust(5)} #{datetime}:#{' ' if progname}#{progname} #{msg}\n"
|
23
23
|
}
|
24
24
|
|
25
25
|
module ON_INITIALIZATION_FAILURE
|
26
26
|
RAISE = 1
|
27
27
|
RETURN = 2
|
28
28
|
end
|
29
|
+
|
29
30
|
module ON_NO_DEFAULT
|
30
31
|
RAISE = 1
|
31
32
|
RETURN_NIL = 2
|
32
33
|
end
|
34
|
+
|
33
35
|
module DATASOURCES
|
34
36
|
ALL = 1
|
35
37
|
LOCAL_ONLY = 2
|
@@ -40,20 +42,20 @@ module Prefab
|
|
40
42
|
logdev: $stdout,
|
41
43
|
stats: NoopStats.new, # receives increment("prefab.limitcheck", {:tags=>["policy_group:page_view", "pass:true"]})
|
42
44
|
shared_cache: NoopCache.new, # Something that quacks like Rails.cache ideally memcached
|
43
|
-
namespace:
|
45
|
+
namespace: '',
|
44
46
|
log_formatter: DEFAULT_LOG_FORMATTER,
|
45
47
|
log_prefix: nil,
|
46
|
-
prefab_api_url: ENV[
|
47
|
-
prefab_grpc_url: ENV[
|
48
|
+
prefab_api_url: ENV['PREFAB_API_URL'] || 'https://api.prefab.cloud',
|
49
|
+
prefab_grpc_url: ENV['PREFAB_GRPC_URL'] || 'grpc.prefab.cloud:443',
|
48
50
|
on_no_default: ON_NO_DEFAULT::RAISE, # options :raise, :warn_and_return_nil,
|
49
51
|
initialization_timeout_sec: 10, # how long to wait before on_init_failure
|
50
|
-
on_init_failure: ON_INITIALIZATION_FAILURE::RAISE, #options :unlock_and_continue, :lock_and_keep_trying, :raise
|
52
|
+
on_init_failure: ON_INITIALIZATION_FAILURE::RAISE, # options :unlock_and_continue, :lock_and_keep_trying, :raise
|
51
53
|
# new_config_callback: nil, #callback method
|
52
54
|
# live_override_url: nil,
|
53
|
-
prefab_datasources: ENV['PREFAB_DATASOURCES'] ==
|
55
|
+
prefab_datasources: ENV['PREFAB_DATASOURCES'] == 'LOCAL_ONLY' ? DATASOURCES::LOCAL_ONLY : DATASOURCES::ALL,
|
54
56
|
prefab_config_override_dir: Dir.home,
|
55
|
-
prefab_config_classpath_dir:
|
56
|
-
prefab_envs:
|
57
|
+
prefab_config_classpath_dir: '.',
|
58
|
+
prefab_envs: ENV['PREFAB_ENVS'].nil? ? [] : ENV['PREFAB_ENVS'].split(',')
|
57
59
|
)
|
58
60
|
# debugger
|
59
61
|
@api_key = api_key
|
@@ -80,7 +82,7 @@ module Prefab
|
|
80
82
|
|
81
83
|
# https://api.prefab.cloud -> https://api-prefab-cloud.global.ssl.fastly.net
|
82
84
|
def url_for_api_cdn
|
83
|
-
ENV['PREFAB_CDN_URL'] || "#{@prefab_api_url.gsub(/\./,
|
85
|
+
ENV['PREFAB_CDN_URL'] || "#{@prefab_api_url.gsub(/\./, '-')}.global.ssl.fastly.net"
|
84
86
|
end
|
85
87
|
end
|
86
88
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Prefab
|
3
4
|
class RateLimitClient
|
4
|
-
|
5
5
|
def initialize(base_client, timeout)
|
6
6
|
@timeout = timeout
|
7
7
|
@base_client = base_client
|
@@ -9,14 +9,14 @@ module Prefab
|
|
9
9
|
|
10
10
|
def pass?(group)
|
11
11
|
result = acquire([group], 1)
|
12
|
-
|
12
|
+
result.passed
|
13
13
|
end
|
14
14
|
|
15
15
|
def acquire(groups, acquire_amount, allow_partial_response: false, on_error: :log_and_pass)
|
16
|
-
expiry_cache_key = "prefab.ratelimit.expiry:#{groups.join(
|
16
|
+
expiry_cache_key = "prefab.ratelimit.expiry:#{groups.join('.')}"
|
17
17
|
expiry = @base_client.shared_cache.read(expiry_cache_key)
|
18
18
|
if !expiry.nil? && Integer(expiry) > Time.now.utc.to_f * 1000
|
19
|
-
@base_client.stats.increment(
|
19
|
+
@base_client.stats.increment('prefab.ratelimit.limitcheck.expirycache.hit', tags: [])
|
20
20
|
return Prefab::LimitResponse.new(passed: false, amount: 0)
|
21
21
|
end
|
22
22
|
|
@@ -27,16 +27,17 @@ module Prefab
|
|
27
27
|
allow_partial_response: allow_partial_response
|
28
28
|
)
|
29
29
|
|
30
|
-
result = @base_client.request Prefab::RateLimitService, :limit_check, req_options: {timeout: @timeout},
|
30
|
+
result = @base_client.request Prefab::RateLimitService, :limit_check, req_options: { timeout: @timeout },
|
31
|
+
params: req
|
31
32
|
|
32
33
|
reset = result.limit_reset_at
|
33
34
|
@base_client.shared_cache.write(expiry_cache_key, reset) unless reset < 1 # protobuf default int to 0
|
34
35
|
|
35
|
-
@base_client.stats.increment(
|
36
|
+
@base_client.stats.increment('prefab.ratelimit.limitcheck',
|
37
|
+
tags: ["policy_group:#{result.policy_group}", "pass:#{result.passed}"])
|
36
38
|
|
37
39
|
result
|
38
|
-
|
39
|
-
rescue => e
|
40
|
+
rescue StandardError => e
|
40
41
|
handle_error(e, on_error, groups)
|
41
42
|
end
|
42
43
|
|
@@ -48,9 +49,7 @@ module Prefab
|
|
48
49
|
limit: limit,
|
49
50
|
burst: burst
|
50
51
|
)
|
51
|
-
unless safety_level.nil?
|
52
|
-
limit_definition.safety_level = safety_level
|
53
|
-
end
|
52
|
+
limit_definition.safety_level = safety_level unless safety_level.nil?
|
54
53
|
config_value = Prefab::ConfigValue.new(limit_definition: limit_definition)
|
55
54
|
config_delta = Prefab::ConfigClient.value_to_delta(key, config_value)
|
56
55
|
upsert_req = Prefab::UpsertRequest.new(config_delta: config_delta)
|
@@ -61,7 +60,7 @@ module Prefab
|
|
61
60
|
private
|
62
61
|
|
63
62
|
def handle_error(e, on_error, groups)
|
64
|
-
@base_client.stats.increment(
|
63
|
+
@base_client.stats.increment('prefab.ratelimit.error', tags: ['type:limit'])
|
65
64
|
|
66
65
|
message = "ratelimit for #{groups} error: #{e.message}"
|
67
66
|
case on_error
|
@@ -77,4 +76,3 @@ module Prefab
|
|
77
76
|
end
|
78
77
|
end
|
79
78
|
end
|
80
|
-
|
data/lib/prefab/sse_logger.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Prefab
|
3
4
|
class SseLogger < InternalLogger
|
4
5
|
def initialize(logger)
|
5
|
-
super(
|
6
|
+
super('sse', logger)
|
6
7
|
end
|
7
8
|
|
8
9
|
# The SSE::Client warns on a perfectly normal stream disconnect, recast to info
|
9
|
-
def warn(progname = nil
|
10
|
+
def warn(progname = nil)
|
10
11
|
@logger.log_internal yield, @path, progname, INFO
|
11
12
|
end
|
12
13
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class WeightedValueResolver
|
5
|
+
MAX_32_FLOAT = 4_294_967_294.0
|
6
|
+
|
7
|
+
def initialize(weights, config_key, lookup_key)
|
8
|
+
@weights = weights
|
9
|
+
@config_key = config_key
|
10
|
+
@lookup_key = lookup_key
|
11
|
+
end
|
12
|
+
|
13
|
+
def resolve
|
14
|
+
percent = @lookup_key ? user_percent : rand
|
15
|
+
|
16
|
+
index = variant_index(percent)
|
17
|
+
|
18
|
+
@weights[index]
|
19
|
+
end
|
20
|
+
|
21
|
+
def user_percent
|
22
|
+
to_hash = "#{@config_key}#{@lookup_key}"
|
23
|
+
int_value = Murmur3.murmur3_32(to_hash)
|
24
|
+
int_value / MAX_32_FLOAT
|
25
|
+
end
|
26
|
+
|
27
|
+
def variant_index(percent_through_distribution)
|
28
|
+
distribution_space = @weights.inject(0) { |sum, v| sum + v.weight }
|
29
|
+
bucket = distribution_space * percent_through_distribution
|
30
|
+
|
31
|
+
sum = 0
|
32
|
+
@weights.each_with_index do |variant_weight, index|
|
33
|
+
return index if bucket < sum + variant_weight.weight
|
34
|
+
|
35
|
+
sum += variant_weight.weight
|
36
|
+
end
|
37
|
+
|
38
|
+
# In the event that all weights are zero, return the last variant
|
39
|
+
@weights.size - 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class YAMLConfigParser
|
5
|
+
def initialize(file, client)
|
6
|
+
@file = file
|
7
|
+
@client = client
|
8
|
+
end
|
9
|
+
|
10
|
+
def merge(config)
|
11
|
+
yaml = load
|
12
|
+
|
13
|
+
yaml.each do |k, v|
|
14
|
+
config = Prefab::LocalConfigParser.parse(k, v, config, @file)
|
15
|
+
end
|
16
|
+
|
17
|
+
config
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def load
|
23
|
+
if File.exist?(@file)
|
24
|
+
@client.log_internal Logger::INFO, "Load #{@file}"
|
25
|
+
YAML.load_file(@file)
|
26
|
+
else
|
27
|
+
@client.log_internal Logger::INFO, "No file #{@file}"
|
28
|
+
{}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/prefab-cloud-ruby.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require 'concurrent/atomics'
|
3
4
|
require 'concurrent'
|
4
5
|
require 'faraday'
|
5
6
|
require 'openssl'
|
@@ -14,8 +15,12 @@ require 'prefab_services_pb'
|
|
14
15
|
require 'prefab/options'
|
15
16
|
require 'prefab/internal_logger'
|
16
17
|
require 'prefab/sse_logger'
|
17
|
-
require 'prefab/
|
18
|
+
require 'prefab/weighted_value_resolver'
|
19
|
+
require 'prefab/config_value_unwrapper'
|
20
|
+
require 'prefab/criteria_evaluator'
|
18
21
|
require 'prefab/config_loader'
|
22
|
+
require 'prefab/local_config_parser'
|
23
|
+
require 'prefab/yaml_config_parser'
|
19
24
|
require 'prefab/config_resolver'
|
20
25
|
require 'prefab/client'
|
21
26
|
require 'prefab/ratelimit_client'
|