launchdarkly-server-sdk 8.11.2 → 8.11.3
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/lib/ldclient-rb/config.rb +66 -3
- data/lib/ldclient-rb/context.rb +1 -1
- data/lib/ldclient-rb/data_system.rb +243 -0
- data/lib/ldclient-rb/events.rb +34 -19
- data/lib/ldclient-rb/flags_state.rb +1 -1
- data/lib/ldclient-rb/impl/big_segments.rb +4 -4
- data/lib/ldclient-rb/impl/cache_store.rb +44 -0
- data/lib/ldclient-rb/impl/data_source/polling.rb +108 -0
- data/lib/ldclient-rb/impl/data_source/requestor.rb +106 -0
- data/lib/ldclient-rb/impl/data_source/status_provider.rb +78 -0
- data/lib/ldclient-rb/impl/data_source/stream.rb +198 -0
- data/lib/ldclient-rb/impl/data_source.rb +3 -3
- data/lib/ldclient-rb/impl/data_store/data_kind.rb +108 -0
- data/lib/ldclient-rb/impl/data_store/feature_store_client_wrapper.rb +187 -0
- data/lib/ldclient-rb/impl/data_store/in_memory_feature_store.rb +130 -0
- data/lib/ldclient-rb/impl/data_store/status_provider.rb +82 -0
- data/lib/ldclient-rb/impl/data_store/store.rb +371 -0
- data/lib/ldclient-rb/impl/data_store.rb +11 -97
- data/lib/ldclient-rb/impl/data_system/fdv1.rb +20 -7
- data/lib/ldclient-rb/impl/data_system/fdv2.rb +471 -0
- data/lib/ldclient-rb/impl/data_system/polling.rb +601 -0
- data/lib/ldclient-rb/impl/data_system/protocolv2.rb +264 -0
- data/lib/ldclient-rb/impl/dependency_tracker.rb +21 -9
- data/lib/ldclient-rb/impl/evaluator.rb +3 -2
- data/lib/ldclient-rb/impl/event_sender.rb +4 -3
- data/lib/ldclient-rb/impl/expiring_cache.rb +79 -0
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +8 -8
- data/lib/ldclient-rb/impl/integrations/test_data/test_data_source_v2.rb +288 -0
- data/lib/ldclient-rb/impl/memoized_value.rb +34 -0
- data/lib/ldclient-rb/impl/migrations/migrator.rb +2 -1
- data/lib/ldclient-rb/impl/migrations/tracker.rb +2 -1
- data/lib/ldclient-rb/impl/model/serialization.rb +6 -6
- data/lib/ldclient-rb/impl/non_blocking_thread_pool.rb +48 -0
- data/lib/ldclient-rb/impl/repeating_task.rb +2 -2
- data/lib/ldclient-rb/impl/simple_lru_cache.rb +27 -0
- data/lib/ldclient-rb/impl/util.rb +65 -0
- data/lib/ldclient-rb/impl.rb +1 -2
- data/lib/ldclient-rb/in_memory_store.rb +1 -18
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +9 -9
- data/lib/ldclient-rb/integrations/test_data.rb +11 -11
- data/lib/ldclient-rb/integrations/test_data_v2/flag_builder_v2.rb +582 -0
- data/lib/ldclient-rb/integrations/test_data_v2.rb +248 -0
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +3 -2
- data/lib/ldclient-rb/interfaces/data_system.rb +755 -0
- data/lib/ldclient-rb/interfaces/feature_store.rb +3 -0
- data/lib/ldclient-rb/ldclient.rb +55 -131
- data/lib/ldclient-rb/util.rb +11 -70
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +8 -17
- metadata +35 -17
- data/lib/ldclient-rb/cache_store.rb +0 -45
- data/lib/ldclient-rb/expiring_cache.rb +0 -77
- data/lib/ldclient-rb/memoized_value.rb +0 -32
- data/lib/ldclient-rb/non_blocking_thread_pool.rb +0 -46
- data/lib/ldclient-rb/polling.rb +0 -102
- data/lib/ldclient-rb/requestor.rb +0 -102
- data/lib/ldclient-rb/simple_lru_cache.rb +0 -25
- data/lib/ldclient-rb/stream.rb +0 -197
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
require 'ldclient-rb/impl/integrations/test_data/test_data_source_v2'
|
|
2
|
+
require 'ldclient-rb/impl/model/feature_flag'
|
|
3
|
+
require 'ldclient-rb/integrations/test_data_v2/flag_builder_v2'
|
|
4
|
+
require 'concurrent/atomics'
|
|
5
|
+
|
|
6
|
+
module LaunchDarkly
|
|
7
|
+
module Integrations
|
|
8
|
+
#
|
|
9
|
+
# A mechanism for providing dynamically updatable feature flag state in a
|
|
10
|
+
# simplified form to an SDK client in test scenarios using the FDv2 protocol.
|
|
11
|
+
#
|
|
12
|
+
# This type is not stable, and not subject to any backwards
|
|
13
|
+
# compatibility guarantees or semantic versioning. It is not suitable for production usage.
|
|
14
|
+
#
|
|
15
|
+
# Do not use it.
|
|
16
|
+
# You have been warned.
|
|
17
|
+
#
|
|
18
|
+
# Unlike {LaunchDarkly::Integrations::FileData}, this mechanism does not use any external resources. It
|
|
19
|
+
# provides only the data that the application has put into it using the {#update} method.
|
|
20
|
+
#
|
|
21
|
+
# @example
|
|
22
|
+
# require 'ldclient-rb/integrations/test_data_v2'
|
|
23
|
+
#
|
|
24
|
+
# td = LaunchDarkly::Integrations::TestDataV2.data_source
|
|
25
|
+
# td.update(td.flag('flag-key-1').variation_for_all(true))
|
|
26
|
+
#
|
|
27
|
+
# # Configure the data system with TestDataV2 as both initializer and synchronizer
|
|
28
|
+
# # Note: This example assumes FDv2 data system configuration is available
|
|
29
|
+
# # data_config = LaunchDarkly::Impl::DataSystem::Config.custom
|
|
30
|
+
# # data_config.initializers([td.method(:build_initializer)])
|
|
31
|
+
# # data_config.synchronizers(td.method(:build_synchronizer))
|
|
32
|
+
#
|
|
33
|
+
# # config = LaunchDarkly::Config.new(
|
|
34
|
+
# # sdk_key,
|
|
35
|
+
# # datasystem_config: data_config.build
|
|
36
|
+
# # )
|
|
37
|
+
#
|
|
38
|
+
# # flags can be updated at any time:
|
|
39
|
+
# td.update(td.flag('flag-key-1')
|
|
40
|
+
# .variation_for_user('some-user-key', true)
|
|
41
|
+
# .fallthrough_variation(false))
|
|
42
|
+
#
|
|
43
|
+
# The above example uses a simple boolean flag, but more complex configurations are possible using
|
|
44
|
+
# the methods of the {FlagBuilderV2} that is returned by {#flag}. {FlagBuilderV2}
|
|
45
|
+
# supports many of the ways a flag can be configured on the LaunchDarkly dashboard, but does not
|
|
46
|
+
# currently support 1. rule operators other than "in" and "not in", or 2. percentage rollouts.
|
|
47
|
+
#
|
|
48
|
+
# If the same `TestDataV2` instance is used to configure multiple `LDClient` instances,
|
|
49
|
+
# any changes made to the data will propagate to all of the `LDClient` instances.
|
|
50
|
+
#
|
|
51
|
+
class TestDataV2
|
|
52
|
+
# Creates a new instance of the test data source.
|
|
53
|
+
#
|
|
54
|
+
# @return [TestDataV2] a new configurable test data source
|
|
55
|
+
def self.data_source
|
|
56
|
+
self.new
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @api private
|
|
60
|
+
def initialize
|
|
61
|
+
@flag_builders = Hash.new
|
|
62
|
+
@current_flags = Hash.new
|
|
63
|
+
@current_segments = Hash.new
|
|
64
|
+
@lock = Concurrent::ReadWriteLock.new
|
|
65
|
+
@instances = Array.new
|
|
66
|
+
@version = 0
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
#
|
|
70
|
+
# Creates or copies a {FlagBuilderV2} for building a test flag configuration.
|
|
71
|
+
#
|
|
72
|
+
# If this flag key has already been defined in this `TestDataV2` instance, then the builder
|
|
73
|
+
# starts with the same configuration that was last provided for this flag.
|
|
74
|
+
#
|
|
75
|
+
# Otherwise, it starts with a new default configuration in which the flag has `true` and
|
|
76
|
+
# `false` variations, is `true` for all contexts when targeting is turned on and
|
|
77
|
+
# `false` otherwise, and currently has targeting turned on. You can change any of those
|
|
78
|
+
# properties, and provide more complex behavior, using the {FlagBuilderV2} methods.
|
|
79
|
+
#
|
|
80
|
+
# Once you have set the desired configuration, pass the builder to {#update}.
|
|
81
|
+
#
|
|
82
|
+
# @param key [String] the flag key
|
|
83
|
+
# @return [FlagBuilderV2] a flag configuration builder
|
|
84
|
+
#
|
|
85
|
+
def flag(key)
|
|
86
|
+
existing_builder = @lock.with_read_lock do
|
|
87
|
+
if @flag_builders.key?(key) && !@flag_builders[key].nil?
|
|
88
|
+
@flag_builders[key]
|
|
89
|
+
else
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if existing_builder.nil?
|
|
95
|
+
LaunchDarkly::Integrations::TestDataV2::FlagBuilderV2.new(key).boolean_flag
|
|
96
|
+
else
|
|
97
|
+
existing_builder.clone
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
#
|
|
102
|
+
# Updates the test data with the specified flag configuration.
|
|
103
|
+
#
|
|
104
|
+
# This has the same effect as if a flag were added or modified on the LaunchDarkly dashboard.
|
|
105
|
+
# It immediately propagates the flag change to any `LDClient` instance(s) that you have
|
|
106
|
+
# already configured to use this `TestDataV2`. If no `LDClient` has been started yet,
|
|
107
|
+
# it simply adds this flag to the test data which will be provided to any `LDClient` that
|
|
108
|
+
# you subsequently configure.
|
|
109
|
+
#
|
|
110
|
+
# Any subsequent changes to this {FlagBuilderV2} instance do not affect the test data,
|
|
111
|
+
# unless you call {#update} again.
|
|
112
|
+
#
|
|
113
|
+
# @param flag_builder [FlagBuilderV2] a flag configuration builder
|
|
114
|
+
# @return [TestDataV2] the TestDataV2 instance
|
|
115
|
+
#
|
|
116
|
+
def update(flag_builder)
|
|
117
|
+
instances_copy = []
|
|
118
|
+
new_flag = nil
|
|
119
|
+
@lock.with_write_lock do
|
|
120
|
+
old_flag = @current_flags[flag_builder._key]
|
|
121
|
+
old_version = old_flag ? old_flag[:version] : 0
|
|
122
|
+
|
|
123
|
+
new_flag = flag_builder.build(old_version + 1)
|
|
124
|
+
|
|
125
|
+
@current_flags[flag_builder._key] = new_flag
|
|
126
|
+
@flag_builders[flag_builder._key] = flag_builder.clone
|
|
127
|
+
|
|
128
|
+
# Create a copy of instances while holding the lock to avoid race conditions
|
|
129
|
+
instances_copy = @instances.dup
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
instances_copy.each do |instance|
|
|
133
|
+
instance.upsert_flag(new_flag)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
self
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @api private
|
|
140
|
+
def make_init_data
|
|
141
|
+
@lock.with_read_lock do
|
|
142
|
+
{
|
|
143
|
+
flags: @current_flags.dup,
|
|
144
|
+
segments: @current_segments.dup,
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @api private
|
|
150
|
+
def get_version
|
|
151
|
+
@lock.with_write_lock do
|
|
152
|
+
version = @version
|
|
153
|
+
@version += 1
|
|
154
|
+
version
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @api private
|
|
159
|
+
# @param instance [LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2] the TestDataSourceV2 instance to remove
|
|
160
|
+
def closed_instance(instance)
|
|
161
|
+
@lock.with_write_lock do
|
|
162
|
+
@instances.delete(instance) if @instances.include?(instance)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# @api private
|
|
167
|
+
# @param instance [LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2] the TestDataSourceV2 instance to add
|
|
168
|
+
def add_instance(instance)
|
|
169
|
+
@lock.with_write_lock do
|
|
170
|
+
@instances.push(instance)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
#
|
|
175
|
+
# Copies a full segment data model object into the test data.
|
|
176
|
+
#
|
|
177
|
+
# It immediately propagates the change to any `LDClient` instance(s) that you have already
|
|
178
|
+
# configured to use this `TestDataV2`. If no `LDClient` has been started yet, it simply adds
|
|
179
|
+
# this segment to the test data which will be provided to any LDClient that you subsequently
|
|
180
|
+
# configure.
|
|
181
|
+
#
|
|
182
|
+
# This method is currently the only way to inject segment data, since there is no builder
|
|
183
|
+
# API for segments. It is mainly intended for the SDK's own tests of segment functionality,
|
|
184
|
+
# since application tests that need to produce a desired evaluation state could do so more easily
|
|
185
|
+
# by just setting flag values.
|
|
186
|
+
#
|
|
187
|
+
# @param segment [Hash, LaunchDarkly::Impl::Model::Segment] the segment configuration as a hash or
|
|
188
|
+
# a Segment model object.
|
|
189
|
+
# @return [TestDataV2] the TestDataV2 instance
|
|
190
|
+
#
|
|
191
|
+
def use_preconfigured_segment(segment)
|
|
192
|
+
instances_copy = []
|
|
193
|
+
segment_key = nil
|
|
194
|
+
updated_segment = nil
|
|
195
|
+
|
|
196
|
+
@lock.with_write_lock do
|
|
197
|
+
# Convert to hash and normalize keys to symbols
|
|
198
|
+
segment_hash = if segment.is_a?(Hash)
|
|
199
|
+
segment.transform_keys(&:to_sym)
|
|
200
|
+
else
|
|
201
|
+
segment.as_json
|
|
202
|
+
end
|
|
203
|
+
segment_key = segment_hash[:key]
|
|
204
|
+
|
|
205
|
+
old_segment = @current_segments[segment_key]
|
|
206
|
+
old_version = old_segment ? old_segment[:version] : 0
|
|
207
|
+
|
|
208
|
+
updated_segment = segment_hash.dup
|
|
209
|
+
updated_segment[:version] = old_version + 1
|
|
210
|
+
|
|
211
|
+
@current_segments[segment_key] = updated_segment
|
|
212
|
+
|
|
213
|
+
# Create a copy of instances while holding the lock to avoid race conditions
|
|
214
|
+
instances_copy = @instances.dup
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
instances_copy.each do |instance|
|
|
218
|
+
instance.upsert_segment(updated_segment)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
self
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
#
|
|
225
|
+
# Creates an initializer that can be used with the FDv2 data system.
|
|
226
|
+
#
|
|
227
|
+
# @param sdk_key [String] the SDK key
|
|
228
|
+
# @param config [LaunchDarkly::Config] the SDK configuration
|
|
229
|
+
# @return [LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2] a test data initializer
|
|
230
|
+
#
|
|
231
|
+
def build_initializer(sdk_key, config)
|
|
232
|
+
LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2.new(self)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
#
|
|
236
|
+
# Creates a synchronizer that can be used with the FDv2 data system.
|
|
237
|
+
#
|
|
238
|
+
# @param sdk_key [String] the SDK key
|
|
239
|
+
# @param config [LaunchDarkly::Config] the SDK configuration
|
|
240
|
+
# @return [LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2] a test data synchronizer
|
|
241
|
+
#
|
|
242
|
+
def build_synchronizer(sdk_key, config)
|
|
243
|
+
LaunchDarkly::Impl::Integrations::TestData::TestDataSourceV2.new(self)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "concurrent/atomics"
|
|
2
2
|
|
|
3
|
-
require "ldclient-rb/expiring_cache"
|
|
3
|
+
require "ldclient-rb/impl/expiring_cache"
|
|
4
|
+
require "ldclient-rb/interfaces"
|
|
4
5
|
|
|
5
6
|
module LaunchDarkly
|
|
6
7
|
module Integrations
|
|
@@ -37,7 +38,7 @@ module LaunchDarkly
|
|
|
37
38
|
expiration_seconds = opts[:expiration] || 15
|
|
38
39
|
if expiration_seconds > 0
|
|
39
40
|
capacity = opts[:capacity] || 1000
|
|
40
|
-
@cache = ExpiringCache.new(capacity, expiration_seconds)
|
|
41
|
+
@cache = Impl::ExpiringCache.new(capacity, expiration_seconds)
|
|
41
42
|
else
|
|
42
43
|
@cache = nil
|
|
43
44
|
end
|