launchdarkly-server-sdk 6.2.5 → 6.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ldclient-rb/config.rb +78 -1
- data/lib/ldclient-rb/evaluation_detail.rb +67 -8
- data/lib/ldclient-rb/file_data_source.rb +9 -300
- data/lib/ldclient-rb/impl/big_segments.rb +117 -0
- data/lib/ldclient-rb/impl/evaluator.rb +80 -28
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +82 -18
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +212 -0
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +84 -31
- data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +40 -0
- data/lib/ldclient-rb/impl/repeating_task.rb +47 -0
- data/lib/ldclient-rb/impl/util.rb +4 -1
- data/lib/ldclient-rb/integrations/consul.rb +7 -0
- data/lib/ldclient-rb/integrations/dynamodb.rb +46 -1
- data/lib/ldclient-rb/integrations/file_data.rb +108 -0
- data/lib/ldclient-rb/integrations/redis.rb +40 -0
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +438 -0
- data/lib/ldclient-rb/integrations/test_data.rb +209 -0
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +5 -0
- data/lib/ldclient-rb/integrations.rb +2 -51
- data/lib/ldclient-rb/interfaces.rb +151 -1
- data/lib/ldclient-rb/ldclient.rb +16 -2
- data/lib/ldclient-rb/polling.rb +22 -41
- data/lib/ldclient-rb/util.rb +1 -1
- data/lib/ldclient-rb/version.rb +1 -1
- metadata +10 -3
@@ -1,55 +1,6 @@
|
|
1
1
|
require "ldclient-rb/integrations/consul"
|
2
2
|
require "ldclient-rb/integrations/dynamodb"
|
3
|
+
require "ldclient-rb/integrations/file_data"
|
3
4
|
require "ldclient-rb/integrations/redis"
|
5
|
+
require "ldclient-rb/integrations/test_data"
|
4
6
|
require "ldclient-rb/integrations/util/store_wrapper"
|
5
|
-
|
6
|
-
module LaunchDarkly
|
7
|
-
#
|
8
|
-
# Tools for connecting the LaunchDarkly client to other software.
|
9
|
-
#
|
10
|
-
module Integrations
|
11
|
-
#
|
12
|
-
# Integration with [Consul](https://www.consul.io/).
|
13
|
-
#
|
14
|
-
# Note that in order to use this integration, you must first install the gem `diplomat`.
|
15
|
-
#
|
16
|
-
# @since 5.5.0
|
17
|
-
#
|
18
|
-
module Consul
|
19
|
-
# code is in ldclient-rb/impl/integrations/consul_impl
|
20
|
-
end
|
21
|
-
|
22
|
-
#
|
23
|
-
# Integration with [DynamoDB](https://aws.amazon.com/dynamodb/).
|
24
|
-
#
|
25
|
-
# Note that in order to use this integration, you must first install one of the AWS SDK gems: either
|
26
|
-
# `aws-sdk-dynamodb`, or the full `aws-sdk`.
|
27
|
-
#
|
28
|
-
# @since 5.5.0
|
29
|
-
#
|
30
|
-
module DynamoDB
|
31
|
-
# code is in ldclient-rb/impl/integrations/dynamodb_impl
|
32
|
-
end
|
33
|
-
|
34
|
-
#
|
35
|
-
# Integration with [Redis](https://redis.io/).
|
36
|
-
#
|
37
|
-
# Note that in order to use this integration, you must first install the `redis` and `connection-pool`
|
38
|
-
# gems.
|
39
|
-
#
|
40
|
-
# @since 5.5.0
|
41
|
-
#
|
42
|
-
module Redis
|
43
|
-
# code is in ldclient-rb/impl/integrations/redis_impl
|
44
|
-
end
|
45
|
-
|
46
|
-
#
|
47
|
-
# Support code that may be helpful in creating integrations.
|
48
|
-
#
|
49
|
-
# @since 5.5.0
|
50
|
-
#
|
51
|
-
module Util
|
52
|
-
# code is in ldclient-rb/integrations/util/
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require "observer"
|
1
2
|
|
2
3
|
module LaunchDarkly
|
3
4
|
#
|
@@ -120,7 +121,8 @@ module LaunchDarkly
|
|
120
121
|
#
|
121
122
|
# The client has its own standard implementation, which uses either a streaming connection or
|
122
123
|
# polling depending on your configuration. Normally you will not need to use another one
|
123
|
-
# except for testing purposes.
|
124
|
+
# except for testing purposes. Two such test fixtures are {LaunchDarkly::Integrations::FileData}
|
125
|
+
# and {LaunchDarkly::Integrations::TestData}.
|
124
126
|
#
|
125
127
|
module DataSource
|
126
128
|
#
|
@@ -149,5 +151,153 @@ module LaunchDarkly
|
|
149
151
|
def stop
|
150
152
|
end
|
151
153
|
end
|
154
|
+
|
155
|
+
module BigSegmentStore
|
156
|
+
#
|
157
|
+
# Returns information about the overall state of the store. This method will be called only
|
158
|
+
# when the SDK needs the latest state, so it should not be cached.
|
159
|
+
#
|
160
|
+
# @return [BigSegmentStoreMetadata]
|
161
|
+
#
|
162
|
+
def get_metadata
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
# Queries the store for a snapshot of the current segment state for a specific user.
|
167
|
+
#
|
168
|
+
# The user_hash is a base64-encoded string produced by hashing the user key as defined by
|
169
|
+
# the Big Segments specification; the store implementation does not need to know the details
|
170
|
+
# of how this is done, because it deals only with already-hashed keys, but the string can be
|
171
|
+
# assumed to only contain characters that are valid in base64.
|
172
|
+
#
|
173
|
+
# The return value should be either a Hash, or nil if the user is not referenced in any big
|
174
|
+
# segments. Each key in the Hash is a "segment reference", which is how segments are
|
175
|
+
# identified in Big Segment data. This string is not identical to the segment key-- the SDK
|
176
|
+
# will add other information. The store implementation should not be concerned with the
|
177
|
+
# format of the string. Each value in the Hash is true if the user is explicitly included in
|
178
|
+
# the segment, false if the user is explicitly excluded from the segment-- and is not also
|
179
|
+
# explicitly included (that is, if both an include and an exclude existed in the data, the
|
180
|
+
# include would take precedence). If the user's status in a particular segment is undefined,
|
181
|
+
# there should be no key or value for that segment.
|
182
|
+
#
|
183
|
+
# This Hash may be cached by the SDK, so it should not be modified after it is created. It
|
184
|
+
# is a snapshot of the segment membership state at one point in time.
|
185
|
+
#
|
186
|
+
# @param user_hash [String]
|
187
|
+
# @return [Hash] true/false values for Big Segments that reference this user
|
188
|
+
#
|
189
|
+
def get_membership(user_hash)
|
190
|
+
end
|
191
|
+
|
192
|
+
#
|
193
|
+
# Performs any necessary cleanup to shut down the store when the client is being shut down.
|
194
|
+
#
|
195
|
+
# @return [void]
|
196
|
+
#
|
197
|
+
def stop
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
#
|
202
|
+
# Values returned by {BigSegmentStore#get_metadata}.
|
203
|
+
#
|
204
|
+
class BigSegmentStoreMetadata
|
205
|
+
def initialize(last_up_to_date)
|
206
|
+
@last_up_to_date = last_up_to_date
|
207
|
+
end
|
208
|
+
|
209
|
+
# The Unix epoch millisecond timestamp of the last update to the {BigSegmentStore}. It is
|
210
|
+
# nil if the store has never been updated.
|
211
|
+
#
|
212
|
+
# @return [Integer|nil]
|
213
|
+
attr_reader :last_up_to_date
|
214
|
+
end
|
215
|
+
|
216
|
+
#
|
217
|
+
# Information about the status of a Big Segment store, provided by {BigSegmentStoreStatusProvider}.
|
218
|
+
#
|
219
|
+
# Big Segments are a specific type of user segments. For more information, read the LaunchDarkly
|
220
|
+
# documentation: https://docs.launchdarkly.com/home/users/big-segments
|
221
|
+
#
|
222
|
+
class BigSegmentStoreStatus
|
223
|
+
def initialize(available, stale)
|
224
|
+
@available = available
|
225
|
+
@stale = stale
|
226
|
+
end
|
227
|
+
|
228
|
+
# True if the Big Segment store is able to respond to queries, so that the SDK can evaluate
|
229
|
+
# whether a user is in a segment or not.
|
230
|
+
#
|
231
|
+
# If this property is false, the store is not able to make queries (for instance, it may not have
|
232
|
+
# a valid database connection). In this case, the SDK will treat any reference to a Big Segment
|
233
|
+
# as if no users are included in that segment. Also, the {EvaluationReason} associated with
|
234
|
+
# with any flag evaluation that references a Big Segment when the store is not available will
|
235
|
+
# have a `big_segments_status` of `STORE_ERROR`.
|
236
|
+
#
|
237
|
+
# @return [Boolean]
|
238
|
+
attr_reader :available
|
239
|
+
|
240
|
+
# True if the Big Segment store is available, but has not been updated within the amount of time
|
241
|
+
# specified by {BigSegmentsConfig#stale_after}.
|
242
|
+
#
|
243
|
+
# This may indicate that the LaunchDarkly Relay Proxy, which populates the store, has stopped
|
244
|
+
# running or has become unable to receive fresh data from LaunchDarkly. Any feature flag
|
245
|
+
# evaluations that reference a Big Segment will be using the last known data, which may be out
|
246
|
+
# of date. Also, the {EvaluationReason} associated with those evaluations will have a
|
247
|
+
# `big_segments_status` of `STALE`.
|
248
|
+
#
|
249
|
+
# @return [Boolean]
|
250
|
+
attr_reader :stale
|
251
|
+
|
252
|
+
def ==(other)
|
253
|
+
self.available == other.available && self.stale == other.stale
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
#
|
258
|
+
# An interface for querying the status of a Big Segment store.
|
259
|
+
#
|
260
|
+
# The Big Segment store is the component that receives information about Big Segments, normally
|
261
|
+
# from a database populated by the LaunchDarkly Relay Proxy. Big Segments are a specific type
|
262
|
+
# of user segments. For more information, read the LaunchDarkly documentation:
|
263
|
+
# https://docs.launchdarkly.com/home/users/big-segments
|
264
|
+
#
|
265
|
+
# An implementation of this interface is returned by {LDClient#big_segment_store_status_provider}.
|
266
|
+
# Application code never needs to implement this interface.
|
267
|
+
#
|
268
|
+
# There are two ways to interact with the status. One is to simply get the current status; if its
|
269
|
+
# `available` property is true, then the SDK is able to evaluate user membership in Big Segments,
|
270
|
+
# and the `stale`` property indicates whether the data might be out of date.
|
271
|
+
#
|
272
|
+
# The other way is to subscribe to status change notifications. Applications may wish to know if
|
273
|
+
# there is an outage in the Big Segment store, or if it has become stale (the Relay Proxy has
|
274
|
+
# stopped updating it with new data), since then flag evaluations that reference a Big Segment
|
275
|
+
# might return incorrect values. To allow finding out about status changes as soon as possible,
|
276
|
+
# `BigSegmentStoreStatusProvider` mixes in Ruby's
|
277
|
+
# [Observable](https://docs.ruby-lang.org/en/2.5.0/Observable.html) module to provide standard
|
278
|
+
# methods such as `add_observer`. Observers will be called with a new {BigSegmentStoreStatus}
|
279
|
+
# value whenever the status changes.
|
280
|
+
#
|
281
|
+
# @example Getting the current status
|
282
|
+
# status = client.big_segment_store_status_provider.status
|
283
|
+
#
|
284
|
+
# @example Subscribing to status notifications
|
285
|
+
# client.big_segment_store_status_provider.add_observer(self, :big_segments_status_changed)
|
286
|
+
#
|
287
|
+
# def big_segments_status_changed(new_status)
|
288
|
+
# puts "Big segment store status is now: #{new_status}"
|
289
|
+
# end
|
290
|
+
#
|
291
|
+
module BigSegmentStoreStatusProvider
|
292
|
+
include Observable
|
293
|
+
#
|
294
|
+
# Gets the current status of the store, if known.
|
295
|
+
#
|
296
|
+
# @return [BigSegmentStoreStatus] the status, or nil if the SDK has not yet queried the Big
|
297
|
+
# Segment store status
|
298
|
+
#
|
299
|
+
def status
|
300
|
+
end
|
301
|
+
end
|
152
302
|
end
|
153
303
|
end
|
data/lib/ldclient-rb/ldclient.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require "ldclient-rb/impl/big_segments"
|
1
2
|
require "ldclient-rb/impl/diagnostic_events"
|
2
3
|
require "ldclient-rb/impl/evaluator"
|
3
4
|
require "ldclient-rb/impl/event_factory"
|
@@ -57,10 +58,14 @@ module LaunchDarkly
|
|
57
58
|
updated_config.instance_variable_set(:@feature_store, @store)
|
58
59
|
@config = updated_config
|
59
60
|
|
61
|
+
@big_segment_store_manager = Impl::BigSegmentStoreManager.new(config.big_segments, @config.logger)
|
62
|
+
@big_segment_store_status_provider = @big_segment_store_manager.status_provider
|
63
|
+
|
60
64
|
get_flag = lambda { |key| @store.get(FEATURES, key) }
|
61
65
|
get_segment = lambda { |key| @store.get(SEGMENTS, key) }
|
62
|
-
|
63
|
-
|
66
|
+
get_big_segments_membership = lambda { |key| @big_segment_store_manager.get_user_membership(key) }
|
67
|
+
@evaluator = LaunchDarkly::Impl::Evaluator.new(get_flag, get_segment, get_big_segments_membership, @config.logger)
|
68
|
+
|
64
69
|
if !@config.offline? && @config.send_events && !@config.diagnostic_opt_out?
|
65
70
|
diagnostic_accumulator = Impl::DiagnosticAccumulator.new(Impl::DiagnosticAccumulator.create_diagnostic_id(sdk_key))
|
66
71
|
else
|
@@ -375,9 +380,18 @@ module LaunchDarkly
|
|
375
380
|
@config.logger.info { "[LDClient] Closing LaunchDarkly client..." }
|
376
381
|
@data_source.stop
|
377
382
|
@event_processor.stop
|
383
|
+
@big_segment_store_manager.stop
|
378
384
|
@store.stop
|
379
385
|
end
|
380
386
|
|
387
|
+
#
|
388
|
+
# Returns an interface for tracking the status of a Big Segment store.
|
389
|
+
#
|
390
|
+
# The {BigSegmentStoreStatusProvider} has methods for checking whether the Big Segment store
|
391
|
+
# is (as far as the SDK knows) currently operational and tracking changes in this status.
|
392
|
+
#
|
393
|
+
attr_reader :big_segment_store_status_provider
|
394
|
+
|
381
395
|
private
|
382
396
|
|
383
397
|
def create_default_data_source(sdk_key, config, diagnostic_accumulator)
|
data/lib/ldclient-rb/polling.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "ldclient-rb/impl/repeating_task"
|
2
|
+
|
1
3
|
require "concurrent/atomics"
|
2
4
|
require "thread"
|
3
5
|
|
@@ -9,8 +11,8 @@ module LaunchDarkly
|
|
9
11
|
@requestor = requestor
|
10
12
|
@initialized = Concurrent::AtomicBoolean.new(false)
|
11
13
|
@started = Concurrent::AtomicBoolean.new(false)
|
12
|
-
@stopped = Concurrent::AtomicBoolean.new(false)
|
13
14
|
@ready = Concurrent::Event.new
|
15
|
+
@task = Impl::RepeatingTask.new(@config.poll_interval, 0, -> { self.poll }, @config.logger)
|
14
16
|
end
|
15
17
|
|
16
18
|
def initialized?
|
@@ -20,56 +22,35 @@ module LaunchDarkly
|
|
20
22
|
def start
|
21
23
|
return @ready unless @started.make_true
|
22
24
|
@config.logger.info { "[LDClient] Initializing polling connection" }
|
23
|
-
|
25
|
+
@task.start
|
24
26
|
@ready
|
25
27
|
end
|
26
28
|
|
27
29
|
def stop
|
28
|
-
|
29
|
-
|
30
|
-
@worker.run # causes the thread to wake up if it's currently in a sleep
|
31
|
-
@worker.join
|
32
|
-
end
|
33
|
-
@config.logger.info { "[LDClient] Polling connection stopped" }
|
34
|
-
end
|
30
|
+
@task.stop
|
31
|
+
@config.logger.info { "[LDClient] Polling connection stopped" }
|
35
32
|
end
|
36
33
|
|
37
34
|
def poll
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
@
|
43
|
-
|
44
|
-
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def create_worker
|
49
|
-
@worker = Thread.new do
|
50
|
-
@config.logger.debug { "[LDClient] Starting polling worker" }
|
51
|
-
while !@stopped.value do
|
52
|
-
started_at = Time.now
|
53
|
-
begin
|
54
|
-
poll
|
55
|
-
rescue UnexpectedResponseError => e
|
56
|
-
message = Util.http_error_message(e.status, "polling request", "will retry")
|
57
|
-
@config.logger.error { "[LDClient] #{message}" };
|
58
|
-
if !Util.http_error_recoverable?(e.status)
|
59
|
-
@ready.set # if client was waiting on us, make it stop waiting - has no effect if already set
|
60
|
-
stop
|
61
|
-
end
|
62
|
-
rescue StandardError => exn
|
63
|
-
Util.log_exception(@config.logger, "Exception while polling", exn)
|
64
|
-
end
|
65
|
-
delta = @config.poll_interval - (Time.now - started_at)
|
66
|
-
if delta > 0
|
67
|
-
sleep(delta)
|
35
|
+
begin
|
36
|
+
all_data = @requestor.request_all_data
|
37
|
+
if all_data
|
38
|
+
@config.feature_store.init(all_data)
|
39
|
+
if @initialized.make_true
|
40
|
+
@config.logger.info { "[LDClient] Polling connection initialized" }
|
41
|
+
@ready.set
|
68
42
|
end
|
69
43
|
end
|
44
|
+
rescue UnexpectedResponseError => e
|
45
|
+
message = Util.http_error_message(e.status, "polling request", "will retry")
|
46
|
+
@config.logger.error { "[LDClient] #{message}" };
|
47
|
+
if !Util.http_error_recoverable?(e.status)
|
48
|
+
@ready.set # if client was waiting on us, make it stop waiting - has no effect if already set
|
49
|
+
stop
|
50
|
+
end
|
51
|
+
rescue StandardError => e
|
52
|
+
Util.log_exception(@config.logger, "Exception while polling", e)
|
70
53
|
end
|
71
54
|
end
|
72
|
-
|
73
|
-
private :poll, :create_worker
|
74
55
|
end
|
75
56
|
end
|
data/lib/ldclient-rb/util.rb
CHANGED
data/lib/ldclient-rb/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: launchdarkly-server-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.
|
4
|
+
version: 6.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- LaunchDarkly
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-dynamodb
|
@@ -260,6 +260,7 @@ files:
|
|
260
260
|
- lib/ldclient-rb/file_data_source.rb
|
261
261
|
- lib/ldclient-rb/flags_state.rb
|
262
262
|
- lib/ldclient-rb/impl.rb
|
263
|
+
- lib/ldclient-rb/impl/big_segments.rb
|
263
264
|
- lib/ldclient-rb/impl/diagnostic_events.rb
|
264
265
|
- lib/ldclient-rb/impl/evaluator.rb
|
265
266
|
- lib/ldclient-rb/impl/evaluator_bucketing.rb
|
@@ -268,8 +269,11 @@ files:
|
|
268
269
|
- lib/ldclient-rb/impl/event_sender.rb
|
269
270
|
- lib/ldclient-rb/impl/integrations/consul_impl.rb
|
270
271
|
- lib/ldclient-rb/impl/integrations/dynamodb_impl.rb
|
272
|
+
- lib/ldclient-rb/impl/integrations/file_data_source.rb
|
271
273
|
- lib/ldclient-rb/impl/integrations/redis_impl.rb
|
274
|
+
- lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb
|
272
275
|
- lib/ldclient-rb/impl/model/serialization.rb
|
276
|
+
- lib/ldclient-rb/impl/repeating_task.rb
|
273
277
|
- lib/ldclient-rb/impl/store_client_wrapper.rb
|
274
278
|
- lib/ldclient-rb/impl/store_data_set_sorter.rb
|
275
279
|
- lib/ldclient-rb/impl/unbounded_pool.rb
|
@@ -278,7 +282,10 @@ files:
|
|
278
282
|
- lib/ldclient-rb/integrations.rb
|
279
283
|
- lib/ldclient-rb/integrations/consul.rb
|
280
284
|
- lib/ldclient-rb/integrations/dynamodb.rb
|
285
|
+
- lib/ldclient-rb/integrations/file_data.rb
|
281
286
|
- lib/ldclient-rb/integrations/redis.rb
|
287
|
+
- lib/ldclient-rb/integrations/test_data.rb
|
288
|
+
- lib/ldclient-rb/integrations/test_data/flag_builder.rb
|
282
289
|
- lib/ldclient-rb/integrations/util/store_wrapper.rb
|
283
290
|
- lib/ldclient-rb/interfaces.rb
|
284
291
|
- lib/ldclient-rb/ldclient.rb
|
@@ -312,7 +319,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
312
319
|
- !ruby/object:Gem::Version
|
313
320
|
version: '0'
|
314
321
|
requirements: []
|
315
|
-
rubygems_version: 3.2.
|
322
|
+
rubygems_version: 3.2.33
|
316
323
|
signing_key:
|
317
324
|
specification_version: 4
|
318
325
|
summary: LaunchDarkly SDK for Ruby
|