sdk-reforge 1.9.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 +7 -0
- data/.envrc.sample +3 -0
- data/.github/CODEOWNERS +2 -0
- data/.github/pull_request_template.md +8 -0
- data/.github/workflows/ruby.yml +48 -0
- data/.gitmodules +3 -0
- data/.rubocop.yml +13 -0
- data/.tool-versions +1 -0
- data/CHANGELOG.md +257 -0
- data/CODEOWNERS +1 -0
- data/Gemfile +29 -0
- data/Gemfile.lock +182 -0
- data/LICENSE.txt +20 -0
- data/README.md +105 -0
- data/Rakefile +63 -0
- data/VERSION +1 -0
- data/compile_protos.sh +20 -0
- data/dev/allocation_stats +60 -0
- data/dev/benchmark +40 -0
- data/dev/console +12 -0
- data/dev/script_setup.rb +18 -0
- data/lib/prefab_pb.rb +77 -0
- data/lib/reforge/caching_http_connection.rb +95 -0
- data/lib/reforge/client.rb +133 -0
- data/lib/reforge/config_client.rb +275 -0
- data/lib/reforge/config_client_presenter.rb +18 -0
- data/lib/reforge/config_loader.rb +67 -0
- data/lib/reforge/config_resolver.rb +84 -0
- data/lib/reforge/config_value_unwrapper.rb +123 -0
- data/lib/reforge/config_value_wrapper.rb +18 -0
- data/lib/reforge/context.rb +241 -0
- data/lib/reforge/context_shape.rb +20 -0
- data/lib/reforge/context_shape_aggregator.rb +70 -0
- data/lib/reforge/criteria_evaluator.rb +345 -0
- data/lib/reforge/duration.rb +58 -0
- data/lib/reforge/encryption.rb +65 -0
- data/lib/reforge/error.rb +6 -0
- data/lib/reforge/errors/env_var_parse_error.rb +11 -0
- data/lib/reforge/errors/initialization_timeout_error.rb +12 -0
- data/lib/reforge/errors/invalid_sdk_key_error.rb +19 -0
- data/lib/reforge/errors/missing_default_error.rb +13 -0
- data/lib/reforge/errors/missing_env_var_error.rb +11 -0
- data/lib/reforge/errors/uninitialized_error.rb +13 -0
- data/lib/reforge/evaluation.rb +53 -0
- data/lib/reforge/evaluation_summary_aggregator.rb +86 -0
- data/lib/reforge/example_contexts_aggregator.rb +77 -0
- data/lib/reforge/exponential_backoff.rb +21 -0
- data/lib/reforge/feature_flag_client.rb +43 -0
- data/lib/reforge/fixed_size_hash.rb +14 -0
- data/lib/reforge/http_connection.rb +45 -0
- data/lib/reforge/internal_logger.rb +43 -0
- data/lib/reforge/javascript_stub.rb +99 -0
- data/lib/reforge/local_config_parser.rb +151 -0
- data/lib/reforge/murmer3.rb +50 -0
- data/lib/reforge/options.rb +191 -0
- data/lib/reforge/periodic_sync.rb +74 -0
- data/lib/reforge/prefab.rb +120 -0
- data/lib/reforge/rate_limit_cache.rb +41 -0
- data/lib/reforge/resolved_config_presenter.rb +86 -0
- data/lib/reforge/semver.rb +132 -0
- data/lib/reforge/sse_config_client.rb +112 -0
- data/lib/reforge/time_helpers.rb +7 -0
- data/lib/reforge/weighted_value_resolver.rb +42 -0
- data/lib/reforge/yaml_config_parser.rb +34 -0
- data/lib/reforge-sdk.rb +57 -0
- data/test/fixtures/datafile.json +87 -0
- data/test/integration_test.rb +171 -0
- data/test/integration_test_helpers.rb +114 -0
- data/test/support/common_helpers.rb +201 -0
- data/test/support/mock_base_client.rb +41 -0
- data/test/support/mock_config_client.rb +19 -0
- data/test/support/mock_config_loader.rb +1 -0
- data/test/test_caching_http_connection.rb +218 -0
- data/test/test_client.rb +351 -0
- data/test/test_config_client.rb +84 -0
- data/test/test_config_loader.rb +82 -0
- data/test/test_config_resolver.rb +502 -0
- data/test/test_config_value_unwrapper.rb +270 -0
- data/test/test_config_value_wrapper.rb +42 -0
- data/test/test_context.rb +271 -0
- data/test/test_context_shape.rb +50 -0
- data/test/test_context_shape_aggregator.rb +150 -0
- data/test/test_criteria_evaluator.rb +1180 -0
- data/test/test_duration.rb +37 -0
- data/test/test_encryption.rb +16 -0
- data/test/test_evaluation_summary_aggregator.rb +162 -0
- data/test/test_example_contexts_aggregator.rb +233 -0
- data/test/test_exponential_backoff.rb +18 -0
- data/test/test_feature_flag_client.rb +16 -0
- data/test/test_fixed_size_hash.rb +119 -0
- data/test/test_helper.rb +17 -0
- data/test/test_integration.rb +75 -0
- data/test/test_internal_logger.rb +25 -0
- data/test/test_javascript_stub.rb +176 -0
- data/test/test_local_config_parser.rb +147 -0
- data/test/test_logger_initialization.rb +12 -0
- data/test/test_options.rb +93 -0
- data/test/test_prefab.rb +16 -0
- data/test/test_rate_limit_cache.rb +44 -0
- data/test/test_semver.rb +108 -0
- data/test/test_sse_config_client.rb +211 -0
- data/test/test_weighted_value_resolver.rb +71 -0
- metadata +345 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reforge
|
4
|
+
# A key-based rate limiter that considers a key to be fresh if it has been
|
5
|
+
# seen within the last `duration` seconds.
|
6
|
+
#
|
7
|
+
# This is used to rate limit the number of times we send a given context
|
8
|
+
# to the server.
|
9
|
+
#
|
10
|
+
# Because expected usage is to immediately `set` on a `fresh?` miss, we do
|
11
|
+
# not prune the data structure on `fresh?` calls. Instead, we manually invoke
|
12
|
+
# `prune` periodically from the cache consumer.
|
13
|
+
class RateLimitCache
|
14
|
+
attr_reader :data
|
15
|
+
|
16
|
+
def initialize(duration)
|
17
|
+
@data = Concurrent::Map.new
|
18
|
+
@duration = duration
|
19
|
+
end
|
20
|
+
|
21
|
+
def fresh?(key)
|
22
|
+
timestamp = @data[key]
|
23
|
+
|
24
|
+
return false unless timestamp
|
25
|
+
return false if Time.now.utc.to_i - timestamp > @duration
|
26
|
+
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def set(key)
|
31
|
+
@data[key] = Time.now.utc.to_i
|
32
|
+
end
|
33
|
+
|
34
|
+
def prune
|
35
|
+
now = Time.now.utc.to_i
|
36
|
+
@data.each_pair do |key, (timestamp, _)|
|
37
|
+
@data.delete(key) if now - timestamp > @duration
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reforge
|
4
|
+
class ResolvedConfigPresenter
|
5
|
+
class ConfigRow
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
attr_reader :key, :value, :match, :source
|
9
|
+
|
10
|
+
def initialize(key, value, match, source)
|
11
|
+
@key = key
|
12
|
+
@value = value
|
13
|
+
@match = match
|
14
|
+
@source = source
|
15
|
+
end
|
16
|
+
|
17
|
+
def <=>(other)
|
18
|
+
inspect <=> other.inspect
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
[@key, @value, @match, @source].inspect
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(resolver, lock, local_store)
|
27
|
+
@resolver = resolver
|
28
|
+
@lock = lock
|
29
|
+
@local_store = local_store
|
30
|
+
end
|
31
|
+
|
32
|
+
def each(&block)
|
33
|
+
to_h.each(&block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_h
|
37
|
+
hash = {}
|
38
|
+
|
39
|
+
Reforge::Context.with_context({}) do
|
40
|
+
@lock.with_read_lock do
|
41
|
+
@local_store.keys.sort.each do |k|
|
42
|
+
v = @local_store[k]
|
43
|
+
|
44
|
+
if v.nil?
|
45
|
+
hash[k] = ConfigRow.new(k, nil, nil, nil)
|
46
|
+
else
|
47
|
+
value = @resolver.evaluate(v[:config])&.reportable_value
|
48
|
+
hash[k] = ConfigRow.new(k, value, v[:match], v[:source])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
hash
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
str = "\n"
|
59
|
+
|
60
|
+
Reforge::Context.with_context({}) do
|
61
|
+
@lock.with_read_lock do
|
62
|
+
@local_store.keys.sort.each do |k|
|
63
|
+
v = @local_store[k]
|
64
|
+
elements = [k.slice(0..49).ljust(50)]
|
65
|
+
if v.nil?
|
66
|
+
elements << 'tombstone'
|
67
|
+
else
|
68
|
+
value = begin
|
69
|
+
@resolver.evaluate(v[:config])&.reportable_value
|
70
|
+
rescue StandardError => e
|
71
|
+
"ERROR EVALUATING: #{e.class} #{e.message}"
|
72
|
+
end
|
73
|
+
elements << value.to_s.slice(0..34).ljust(35)
|
74
|
+
elements << value.class.to_s.slice(0..6).ljust(7)
|
75
|
+
elements << "Match: #{v[:match]}".slice(0..29).ljust(30)
|
76
|
+
elements << "Source: #{v[:source]}"
|
77
|
+
end
|
78
|
+
str += elements.join(' | ') << "\n"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
str
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class SemanticVersion
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
SEMVER_PATTERN = /
|
7
|
+
^
|
8
|
+
(?<major>0|[1-9]\d*)
|
9
|
+
\.
|
10
|
+
(?<minor>0|[1-9]\d*)
|
11
|
+
\.
|
12
|
+
(?<patch>0|[1-9]\d*)
|
13
|
+
(?:-(?<prerelease>
|
14
|
+
(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
|
15
|
+
(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
|
16
|
+
))?
|
17
|
+
(?:\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?
|
18
|
+
$
|
19
|
+
/x
|
20
|
+
|
21
|
+
attr_reader :major, :minor, :patch, :prerelease, :build_metadata
|
22
|
+
|
23
|
+
def self.parse(version_string)
|
24
|
+
raise ArgumentError, "version string cannot be nil" if version_string.nil?
|
25
|
+
raise ArgumentError, "version string cannot be empty" if version_string.empty?
|
26
|
+
|
27
|
+
match = SEMVER_PATTERN.match(version_string)
|
28
|
+
raise ArgumentError, "invalid semantic version format: #{version_string}" unless match
|
29
|
+
|
30
|
+
new(
|
31
|
+
major: match[:major].to_i,
|
32
|
+
minor: match[:minor].to_i,
|
33
|
+
patch: match[:patch].to_i,
|
34
|
+
prerelease: match[:prerelease],
|
35
|
+
build_metadata: match[:buildmetadata]
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.parse_quietly(version_string)
|
40
|
+
parse(version_string)
|
41
|
+
rescue ArgumentError
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(major:, minor:, patch:, prerelease: nil, build_metadata: nil)
|
46
|
+
@major = major
|
47
|
+
@minor = minor
|
48
|
+
@patch = patch
|
49
|
+
@prerelease = prerelease
|
50
|
+
@build_metadata = build_metadata
|
51
|
+
end
|
52
|
+
|
53
|
+
def <=>(other)
|
54
|
+
return nil unless other.is_a?(SemanticVersion)
|
55
|
+
|
56
|
+
# Compare major.minor.patch
|
57
|
+
return major <=> other.major if major != other.major
|
58
|
+
return minor <=> other.minor if minor != other.minor
|
59
|
+
return patch <=> other.patch if patch != other.patch
|
60
|
+
|
61
|
+
# Compare pre-release versions
|
62
|
+
compare_prerelease(prerelease, other.prerelease)
|
63
|
+
end
|
64
|
+
|
65
|
+
def ==(other)
|
66
|
+
return false unless other.is_a?(SemanticVersion)
|
67
|
+
|
68
|
+
major == other.major &&
|
69
|
+
minor == other.minor &&
|
70
|
+
patch == other.patch &&
|
71
|
+
prerelease == other.prerelease
|
72
|
+
# Build metadata is ignored in equality checks
|
73
|
+
end
|
74
|
+
|
75
|
+
def eql?(other)
|
76
|
+
self == other
|
77
|
+
end
|
78
|
+
|
79
|
+
def hash
|
80
|
+
[major, minor, patch, prerelease].hash
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
result = "#{major}.#{minor}.#{patch}"
|
85
|
+
result += "-#{prerelease}" if prerelease
|
86
|
+
result += "+#{build_metadata}" if build_metadata
|
87
|
+
result
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def self.numeric?(str)
|
93
|
+
str.to_i.to_s == str
|
94
|
+
end
|
95
|
+
|
96
|
+
def compare_prerelease(pre1, pre2)
|
97
|
+
# If both are empty, they're equal
|
98
|
+
return 0 if pre1.nil? && pre2.nil?
|
99
|
+
|
100
|
+
# A version without prerelease has higher precedence
|
101
|
+
return 1 if pre1.nil?
|
102
|
+
return -1 if pre2.nil?
|
103
|
+
|
104
|
+
# Split into identifiers
|
105
|
+
ids1 = pre1.split('.')
|
106
|
+
ids2 = pre2.split('.')
|
107
|
+
|
108
|
+
# Compare each identifier until we find a difference
|
109
|
+
[ids1.length, ids2.length].min.times do |i|
|
110
|
+
cmp = compare_prerelease_identifiers(ids1[i], ids2[i])
|
111
|
+
return cmp if cmp != 0
|
112
|
+
end
|
113
|
+
|
114
|
+
# If all identifiers match up to the length of the shorter one,
|
115
|
+
# the longer one has higher precedence
|
116
|
+
ids1.length <=> ids2.length
|
117
|
+
end
|
118
|
+
|
119
|
+
def compare_prerelease_identifiers(id1, id2)
|
120
|
+
# If both are numeric, compare numerically
|
121
|
+
if self.class.numeric?(id1) && self.class.numeric?(id2)
|
122
|
+
return id1.to_i <=> id2.to_i
|
123
|
+
end
|
124
|
+
|
125
|
+
# If only one is numeric, numeric ones have lower precedence
|
126
|
+
return -1 if self.class.numeric?(id1)
|
127
|
+
return 1 if self.class.numeric?(id2)
|
128
|
+
|
129
|
+
# Neither is numeric, compare as strings
|
130
|
+
id1 <=> id2
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reforge
|
4
|
+
class SSEConfigClient
|
5
|
+
class Options
|
6
|
+
attr_reader :sse_read_timeout, :seconds_between_new_connection,
|
7
|
+
:sse_default_reconnect_time, :sleep_delay_for_new_connection_check,
|
8
|
+
:errors_to_close_connection
|
9
|
+
|
10
|
+
def initialize(sse_read_timeout: 300,
|
11
|
+
seconds_between_new_connection: 5,
|
12
|
+
sleep_delay_for_new_connection_check: 1,
|
13
|
+
sse_default_reconnect_time: SSE::Client::DEFAULT_RECONNECT_TIME,
|
14
|
+
errors_to_close_connection: [HTTP::ConnectionError])
|
15
|
+
@sse_read_timeout = sse_read_timeout
|
16
|
+
@seconds_between_new_connection = seconds_between_new_connection
|
17
|
+
@sse_default_reconnect_time = sse_default_reconnect_time
|
18
|
+
@sleep_delay_for_new_connection_check = sleep_delay_for_new_connection_check
|
19
|
+
@errors_to_close_connection = errors_to_close_connection
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
AUTH_USER = 'authuser'
|
24
|
+
LOG = Reforge::InternalLogger.new(self)
|
25
|
+
|
26
|
+
def initialize(prefab_options, config_loader, options = nil, logger = nil)
|
27
|
+
@prefab_options = prefab_options
|
28
|
+
@options = options || Options.new
|
29
|
+
@config_loader = config_loader
|
30
|
+
@connected = false
|
31
|
+
@logger = logger || LOG
|
32
|
+
end
|
33
|
+
|
34
|
+
def close
|
35
|
+
@retry_thread&.kill
|
36
|
+
@client&.close
|
37
|
+
end
|
38
|
+
|
39
|
+
def start(&load_configs)
|
40
|
+
if @prefab_options.sse_sources.empty?
|
41
|
+
@logger.debug 'No SSE sources configured'
|
42
|
+
return
|
43
|
+
end
|
44
|
+
|
45
|
+
@client = connect(&load_configs)
|
46
|
+
|
47
|
+
closed_count = 0
|
48
|
+
|
49
|
+
@retry_thread = Thread.new do
|
50
|
+
loop do
|
51
|
+
sleep @options.sleep_delay_for_new_connection_check
|
52
|
+
|
53
|
+
if @client.closed?
|
54
|
+
closed_count += @options.sleep_delay_for_new_connection_check
|
55
|
+
|
56
|
+
if closed_count > @options.seconds_between_new_connection
|
57
|
+
closed_count = 0
|
58
|
+
@logger.debug 'Reconnecting SSE client'
|
59
|
+
@client = connect(&load_configs)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def connect(&load_configs)
|
67
|
+
url = "#{source}/api/v2/sse/config"
|
68
|
+
@logger.debug "SSE Streaming Connect to #{url} start_at #{@config_loader.highwater_mark}"
|
69
|
+
|
70
|
+
SSE::Client.new(url,
|
71
|
+
headers: headers,
|
72
|
+
read_timeout: @options.sse_read_timeout,
|
73
|
+
reconnect_time: @options.sse_default_reconnect_time,
|
74
|
+
logger: Reforge::InternalLogger.new(SSE::Client)) do |client|
|
75
|
+
client.on_event do |event|
|
76
|
+
configs = PrefabProto::Configs.decode(Base64.decode64(event.data))
|
77
|
+
load_configs.call(configs, event, :sse)
|
78
|
+
end
|
79
|
+
|
80
|
+
client.on_error do |error|
|
81
|
+
@logger.error "SSE Streaming Error: #{error.inspect} for url #{url}"
|
82
|
+
|
83
|
+
if @options.errors_to_close_connection.any? { |klass| error.is_a?(klass) }
|
84
|
+
@logger.debug "Closing SSE connection for url #{url}"
|
85
|
+
client.close
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def headers
|
92
|
+
auth = "#{AUTH_USER}:#{@prefab_options.sdk_key}"
|
93
|
+
auth_string = Base64.strict_encode64(auth)
|
94
|
+
return {
|
95
|
+
'Last-Event-ID' => @config_loader.highwater_mark,
|
96
|
+
'Authorization' => "Basic #{auth_string}",
|
97
|
+
'Accept' => 'text/event-stream',
|
98
|
+
'X-Reforge-SDK-Version' => "sdk-ruby-#{Reforge::VERSION}"
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def source
|
103
|
+
@source_index = @source_index.nil? ? 0 : @source_index + 1
|
104
|
+
|
105
|
+
if @source_index >= @prefab_options.sse_sources.size
|
106
|
+
@source_index = 0
|
107
|
+
end
|
108
|
+
|
109
|
+
return @prefab_options.sse_sources[@source_index].sub(/(primary|secondary)\./, 'stream.').sub(/(belt|suspenders)\./, 'stream.')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reforge
|
4
|
+
class WeightedValueResolver
|
5
|
+
MAX_32_FLOAT = 4_294_967_294.0
|
6
|
+
|
7
|
+
def initialize(weights, config_key, context_hash_value)
|
8
|
+
@weights = weights
|
9
|
+
@config_key = config_key
|
10
|
+
@context_hash_value = context_hash_value
|
11
|
+
end
|
12
|
+
|
13
|
+
def resolve
|
14
|
+
percent = @context_hash_value ? user_percent : rand
|
15
|
+
|
16
|
+
index = variant_index(percent)
|
17
|
+
|
18
|
+
[@weights[index], index]
|
19
|
+
end
|
20
|
+
|
21
|
+
def user_percent
|
22
|
+
to_hash = "#{@config_key}#{@context_hash_value}"
|
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,34 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Reforge
|
4
|
+
class YAMLConfigParser
|
5
|
+
LOG = Reforge::InternalLogger.new(self)
|
6
|
+
|
7
|
+
def initialize(file, client)
|
8
|
+
@file = file
|
9
|
+
@client = client
|
10
|
+
end
|
11
|
+
|
12
|
+
def merge(config)
|
13
|
+
yaml = load
|
14
|
+
|
15
|
+
yaml.each do |k, v|
|
16
|
+
config = Reforge::LocalConfigParser.parse(k, v, config, @file)
|
17
|
+
end
|
18
|
+
|
19
|
+
config
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def load
|
25
|
+
if File.exist?(@file)
|
26
|
+
LOG.info "Load #{@file}"
|
27
|
+
YAML.load_file(@file)
|
28
|
+
else
|
29
|
+
LOG.info "No file #{@file}"
|
30
|
+
{}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/reforge-sdk.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reforge
|
4
|
+
NO_DEFAULT_PROVIDED = :no_default_provided
|
5
|
+
VERSION = File.read(File.dirname(__FILE__) + '/../VERSION').strip
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'semantic_logger'
|
9
|
+
require 'reforge/internal_logger'
|
10
|
+
require 'concurrent/atomics'
|
11
|
+
require 'concurrent'
|
12
|
+
require 'faraday'
|
13
|
+
require 'openssl'
|
14
|
+
require 'ld-eventsource'
|
15
|
+
require 'prefab_pb'
|
16
|
+
require 'reforge/time_helpers'
|
17
|
+
require 'reforge/error'
|
18
|
+
require 'reforge/duration'
|
19
|
+
require 'reforge/evaluation'
|
20
|
+
require 'reforge/encryption'
|
21
|
+
require 'reforge/exponential_backoff'
|
22
|
+
require 'reforge/errors/initialization_timeout_error'
|
23
|
+
require 'reforge/errors/invalid_sdk_key_error'
|
24
|
+
require 'reforge/errors/missing_default_error'
|
25
|
+
require 'reforge/errors/env_var_parse_error'
|
26
|
+
require 'reforge/errors/missing_env_var_error'
|
27
|
+
require 'reforge/errors/uninitialized_error'
|
28
|
+
require 'reforge/options'
|
29
|
+
require 'reforge/rate_limit_cache'
|
30
|
+
require 'reforge/context_shape_aggregator'
|
31
|
+
require 'reforge/example_contexts_aggregator'
|
32
|
+
require 'reforge/evaluation_summary_aggregator'
|
33
|
+
require 'reforge/weighted_value_resolver'
|
34
|
+
require 'reforge/config_value_wrapper'
|
35
|
+
require 'reforge/config_value_unwrapper'
|
36
|
+
require 'reforge/criteria_evaluator'
|
37
|
+
require 'reforge/config_loader'
|
38
|
+
require 'reforge/context_shape'
|
39
|
+
require 'reforge/local_config_parser'
|
40
|
+
require 'reforge/yaml_config_parser'
|
41
|
+
require 'reforge/resolved_config_presenter'
|
42
|
+
require 'reforge/config_resolver'
|
43
|
+
require 'reforge/http_connection'
|
44
|
+
require 'reforge/context'
|
45
|
+
require 'active_support/deprecation'
|
46
|
+
require 'active_support'
|
47
|
+
require 'reforge/sse_config_client'
|
48
|
+
require 'reforge/client'
|
49
|
+
require 'reforge/config_client_presenter'
|
50
|
+
require 'reforge/config_client'
|
51
|
+
require 'reforge/feature_flag_client'
|
52
|
+
require 'reforge/prefab'
|
53
|
+
require 'reforge/murmer3'
|
54
|
+
require 'reforge/javascript_stub'
|
55
|
+
require 'reforge/semver'
|
56
|
+
require 'reforge/fixed_size_hash'
|
57
|
+
require 'reforge/caching_http_connection'
|
@@ -0,0 +1,87 @@
|
|
1
|
+
{
|
2
|
+
"configs": [
|
3
|
+
{
|
4
|
+
"id": "16825372746571694",
|
5
|
+
"projectId": "202",
|
6
|
+
"key": "log-level",
|
7
|
+
"changedBy": {
|
8
|
+
"email": "jeffrey.chupp@prefab.cloud"
|
9
|
+
},
|
10
|
+
"configType": "DELETED"
|
11
|
+
},
|
12
|
+
{
|
13
|
+
"id": "17271108409487302",
|
14
|
+
"projectId": "202",
|
15
|
+
"key": "flag.list.environments",
|
16
|
+
"changedBy": {
|
17
|
+
"email": "jeffrey.chupp@prefab.cloud"
|
18
|
+
},
|
19
|
+
"rows": [
|
20
|
+
{
|
21
|
+
"projectEnvId": "308",
|
22
|
+
"values": [
|
23
|
+
{
|
24
|
+
"criteria": [
|
25
|
+
{
|
26
|
+
"propertyName": "user.key",
|
27
|
+
"operator": "PROP_IS_ONE_OF",
|
28
|
+
"valueToMatch": {
|
29
|
+
"stringList": {
|
30
|
+
"values": [
|
31
|
+
"5905ecd1-9bbf-4711-a663-4f713628a78c"
|
32
|
+
]
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
],
|
37
|
+
"value": {
|
38
|
+
"bool": true
|
39
|
+
}
|
40
|
+
},
|
41
|
+
{
|
42
|
+
"value": {
|
43
|
+
"bool": true
|
44
|
+
}
|
45
|
+
}
|
46
|
+
]
|
47
|
+
}
|
48
|
+
],
|
49
|
+
"allowableValues": [
|
50
|
+
{
|
51
|
+
"bool": false
|
52
|
+
},
|
53
|
+
{
|
54
|
+
"bool": true
|
55
|
+
}
|
56
|
+
],
|
57
|
+
"configType": "FEATURE_FLAG",
|
58
|
+
"valueType": "BOOL"
|
59
|
+
},
|
60
|
+
{
|
61
|
+
"id": "17271831941669987",
|
62
|
+
"projectId": "202",
|
63
|
+
"key": "my.test.string",
|
64
|
+
"changedBy": {
|
65
|
+
"userId": "3",
|
66
|
+
"apiKeyId": "481"
|
67
|
+
},
|
68
|
+
"rows": [
|
69
|
+
{
|
70
|
+
"values": [
|
71
|
+
{
|
72
|
+
"value": {
|
73
|
+
"string": "hello world"
|
74
|
+
}
|
75
|
+
}
|
76
|
+
]
|
77
|
+
}
|
78
|
+
],
|
79
|
+
"configType": "CONFIG",
|
80
|
+
"valueType": "STRING"
|
81
|
+
}
|
82
|
+
],
|
83
|
+
"configServicePointer": {
|
84
|
+
"projectId": "202",
|
85
|
+
"projectEnvId": "308"
|
86
|
+
}
|
87
|
+
}
|