launchdarkly-server-sdk 8.13.0 → 8.14.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/README.md +1 -1
- data/lib/ldclient-rb/config.rb +0 -3
- data/lib/ldclient-rb/data_system/polling_data_source_builder.rb +7 -5
- data/lib/ldclient-rb/impl/data_store/feature_store_client_wrapper.rb +12 -0
- data/lib/ldclient-rb/impl/data_store/store.rb +24 -0
- data/lib/ldclient-rb/impl/data_system/fdv2.rb +67 -12
- data/lib/ldclient-rb/impl/data_system/polling.rb +99 -32
- data/lib/ldclient-rb/impl/data_system/protocolv2.rb +3 -29
- data/lib/ldclient-rb/impl/data_system/streaming.rb +54 -31
- data/lib/ldclient-rb/impl/data_system.rb +6 -5
- data/lib/ldclient-rb/impl/integrations/file_data_source_v2.rb +29 -22
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +4 -0
- data/lib/ldclient-rb/impl/integrations/test_data/test_data_source_v2.rb +53 -48
- data/lib/ldclient-rb/integrations/consul.rb +6 -2
- data/lib/ldclient-rb/integrations/dynamodb.rb +6 -2
- data/lib/ldclient-rb/integrations/file_data.rb +0 -3
- data/lib/ldclient-rb/integrations/redis.rb +6 -2
- data/lib/ldclient-rb/integrations/test_data_v2.rb +0 -3
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +39 -19
- data/lib/ldclient-rb/interfaces/data_system.rb +60 -57
- data/lib/ldclient-rb/util.rb +3 -2
- data/lib/ldclient-rb/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 30ec609a22208597d110a93cf4ea65b4b20a723cfccf5b3db743921aeb27a40a
|
|
4
|
+
data.tar.gz: 7833879176def90e31a71756cc27b90135985d2a75c305582ef3d09ad504ff72
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c607be8b3926d090a243453c39375df4892c7ea49cb8a5f6c366bd47d4299c6f9ff525f2cb8e94c535b6a1dfa54a9f348ba4a59477e8eac8a420c1ce85fae777
|
|
7
|
+
data.tar.gz: 80d5b45d30922ce0c24f3ed56b7ca9f0e0d57e98e972600161b24517e89318da43fc94ba11219558467bf4e9088114b7b3b863b420ab0f9b1a88cc4f2805b018
|
data/README.md
CHANGED
|
@@ -43,7 +43,7 @@ We encourage pull requests and other contributions from the community. Check out
|
|
|
43
43
|
Verifying SDK build provenance with the SLSA framework
|
|
44
44
|
------------
|
|
45
45
|
|
|
46
|
-
LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages. To learn more, see the [provenance guide](PROVENANCE.md).
|
|
46
|
+
LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages. To learn more, see the [provenance guide](PROVENANCE.md).
|
|
47
47
|
|
|
48
48
|
About LaunchDarkly
|
|
49
49
|
-----------
|
data/lib/ldclient-rb/config.rb
CHANGED
|
@@ -697,9 +697,6 @@ module LaunchDarkly
|
|
|
697
697
|
#
|
|
698
698
|
# Configuration for LaunchDarkly's data acquisition strategy.
|
|
699
699
|
#
|
|
700
|
-
# This is not stable and is not subject to any backwards compatibility guarantees
|
|
701
|
-
# or semantic versioning. It is not suitable for production usage.
|
|
702
|
-
#
|
|
703
700
|
class DataSystemConfig
|
|
704
701
|
#
|
|
705
702
|
# @param initializers [Array<#build(String, Config)>, nil] The (optional) array of builders
|
|
@@ -21,9 +21,9 @@ module LaunchDarkly
|
|
|
21
21
|
# include LaunchDarkly::DataSystem::Requester
|
|
22
22
|
#
|
|
23
23
|
# def fetch(selector)
|
|
24
|
-
# # Fetch data and return a Result
|
|
25
|
-
# #
|
|
26
|
-
# LaunchDarkly::Result.success(
|
|
24
|
+
# # Fetch data and return a Result whose value is a ChangeSet and
|
|
25
|
+
# # whose headers carry any response metadata (e.g. directives).
|
|
26
|
+
# LaunchDarkly::Result.success(change_set, response_headers)
|
|
27
27
|
# end
|
|
28
28
|
#
|
|
29
29
|
# def stop
|
|
@@ -43,8 +43,10 @@ module LaunchDarkly
|
|
|
43
43
|
# @param selector [LaunchDarkly::Interfaces::DataSystem::Selector, nil]
|
|
44
44
|
# The selector describing what data to fetch. May be nil if no
|
|
45
45
|
# selector is available (e.g., on the first request).
|
|
46
|
-
# @return [LaunchDarkly::Result] A Result
|
|
47
|
-
#
|
|
46
|
+
# @return [LaunchDarkly::Result] A Result whose `value` is a
|
|
47
|
+
# {LaunchDarkly::Interfaces::DataSystem::ChangeSet} on success and
|
|
48
|
+
# whose `headers` carry response headers in either case (so callers
|
|
49
|
+
# can inspect directives such as `X-LD-FD-Fallback`).
|
|
48
50
|
#
|
|
49
51
|
def fetch(selector)
|
|
50
52
|
raise NotImplementedError
|
|
@@ -65,6 +65,18 @@ module LaunchDarkly
|
|
|
65
65
|
@store.initialized?
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
#
|
|
69
|
+
# Passthrough for the FDv2 store coordinator. If the wrapped store supports
|
|
70
|
+
# disabling its cache (e.g. {LaunchDarkly::Integrations::Util::CachingStoreWrapper}),
|
|
71
|
+
# forward the call; otherwise no-op.
|
|
72
|
+
#
|
|
73
|
+
# @return [void]
|
|
74
|
+
#
|
|
75
|
+
def disable_cache
|
|
76
|
+
return unless @store.respond_to?(:disable_cache)
|
|
77
|
+
wrapper { @store.disable_cache }
|
|
78
|
+
end
|
|
79
|
+
|
|
68
80
|
# (see LaunchDarkly::Interfaces::FeatureStore#stop)
|
|
69
81
|
def stop
|
|
70
82
|
poller_to_stop = nil
|
|
@@ -221,6 +221,12 @@ module LaunchDarkly
|
|
|
221
221
|
# Switch to memory store as active
|
|
222
222
|
@active_store = @memory_store
|
|
223
223
|
|
|
224
|
+
# In-memory store is now authoritative. Disable the persistent-store cache
|
|
225
|
+
# so we don't hold a duplicate copy of every flag. Done before the
|
|
226
|
+
# persistent_store.init below so the wrapper can skip its cache-population
|
|
227
|
+
# loop on this very call.
|
|
228
|
+
disable_persistent_cache
|
|
229
|
+
|
|
224
230
|
# Persist to persistent store if configured and writable
|
|
225
231
|
@persistent_store.init(collections) if should_persist?
|
|
226
232
|
|
|
@@ -271,6 +277,24 @@ module LaunchDarkly
|
|
|
271
277
|
send_change_events(affected_items) unless affected_items.empty?
|
|
272
278
|
end
|
|
273
279
|
|
|
280
|
+
#
|
|
281
|
+
# Disable the persistent store's in-memory cache, if it has one. The FDv2 in-memory
|
|
282
|
+
# store is now the source of truth, so the cache is dead weight and would just
|
|
283
|
+
# hold a duplicate copy of every flag.
|
|
284
|
+
#
|
|
285
|
+
# @return [void]
|
|
286
|
+
#
|
|
287
|
+
private def disable_persistent_cache
|
|
288
|
+
return if @persistent_store.nil?
|
|
289
|
+
return unless @persistent_store.respond_to?(:disable_cache)
|
|
290
|
+
|
|
291
|
+
begin
|
|
292
|
+
@persistent_store.disable_cache
|
|
293
|
+
rescue => e
|
|
294
|
+
@logger.warn { "[LDClient] Failed to disable persistent store cache: #{e.message}" }
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
274
298
|
#
|
|
275
299
|
# Returns whether data should be persisted to the persistent store.
|
|
276
300
|
#
|
|
@@ -214,8 +214,23 @@ module LaunchDarkly
|
|
|
214
214
|
nil
|
|
215
215
|
)
|
|
216
216
|
|
|
217
|
-
# Run initializers first
|
|
218
|
-
|
|
217
|
+
# Run initializers first. If an initializer signals the
|
|
218
|
+
# server-directed FDv1 Fallback Directive, switch terminally to
|
|
219
|
+
# the FDv1 Fallback Synchronizer (or transition to OFF if none
|
|
220
|
+
# is configured) before entering the synchronizer phase.
|
|
221
|
+
if run_initializers
|
|
222
|
+
if @fdv1_fallback_synchronizer_builder
|
|
223
|
+
@logger.warn { "[LDClient] Falling back to FDv1 protocol" }
|
|
224
|
+
@synchronizer_builders = [@fdv1_fallback_synchronizer_builder]
|
|
225
|
+
else
|
|
226
|
+
@logger.warn { "[LDClient] Initializer requested FDv1 fallback but none configured" }
|
|
227
|
+
@synchronizer_builders = []
|
|
228
|
+
@data_source_status_provider.update_status(
|
|
229
|
+
LaunchDarkly::Interfaces::DataSource::Status::OFF,
|
|
230
|
+
@data_source_status_provider.status.last_error
|
|
231
|
+
)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
219
234
|
|
|
220
235
|
# Run synchronizers
|
|
221
236
|
run_synchronizers
|
|
@@ -228,39 +243,70 @@ module LaunchDarkly
|
|
|
228
243
|
#
|
|
229
244
|
# Run initializers to get initial data.
|
|
230
245
|
#
|
|
231
|
-
#
|
|
246
|
+
# Each initializer is tried in order until one succeeds, the system
|
|
247
|
+
# is stopped, or an initializer signals the server-directed FDv1
|
|
248
|
+
# Fallback Directive. When fallback is signalled alongside a valid
|
|
249
|
+
# payload, that payload is applied before returning so evaluations
|
|
250
|
+
# can serve the server-provided data while the FDv1 synchronizer
|
|
251
|
+
# spins up. The method returns true when fallback was requested so
|
|
252
|
+
# that the caller can switch the synchronizer list.
|
|
253
|
+
#
|
|
254
|
+
# @return [Boolean] true when an initializer requested FDv1 fallback.
|
|
232
255
|
#
|
|
233
256
|
def run_initializers
|
|
234
|
-
return unless @data_system_config.initializers
|
|
257
|
+
return false unless @data_system_config.initializers
|
|
235
258
|
|
|
236
259
|
@data_system_config.initializers.each do |initializer_builder|
|
|
237
|
-
return if @stop_event.set?
|
|
260
|
+
return false if @stop_event.set?
|
|
238
261
|
|
|
239
262
|
begin
|
|
240
263
|
initializer = initializer_builder.build(@sdk_key, @config)
|
|
241
264
|
@logger.info { "[LDClient] Attempting to initialize via #{initializer.name}" }
|
|
242
265
|
|
|
243
|
-
|
|
266
|
+
fetch_result = initializer.fetch(@store)
|
|
267
|
+
fallback = fetch_result.fallback_to_fdv1
|
|
268
|
+
basis_result = fetch_result.result
|
|
244
269
|
|
|
245
270
|
if basis_result.success?
|
|
246
271
|
basis = basis_result.value
|
|
247
272
|
@logger.info { "[LDClient] Initialized via #{initializer.name}" }
|
|
248
273
|
|
|
249
|
-
# Apply the basis to the store
|
|
274
|
+
# Apply the basis to the store regardless of whether fallback was signalled.
|
|
275
|
+
# If the server returned a valid payload alongside the directive we still want
|
|
276
|
+
# evaluations to serve that data while the FDv1 synchronizer spins up.
|
|
250
277
|
@store.apply(basis.change_set, basis.persist)
|
|
251
278
|
|
|
252
|
-
# Set ready event if and only if a selector is defined for the changeset
|
|
279
|
+
# Set ready event if and only if a selector is defined for the changeset.
|
|
253
280
|
if basis.change_set.selector && basis.change_set.selector.defined?
|
|
254
281
|
@ready_event.set
|
|
255
|
-
return
|
|
282
|
+
return fallback
|
|
256
283
|
end
|
|
257
284
|
else
|
|
258
285
|
@logger.warn { "[LDClient] Initializer #{initializer.name} failed: #{basis_result.error}" }
|
|
286
|
+
if fallback
|
|
287
|
+
# Record the underlying initializer error so that, if no FDv1 fallback is
|
|
288
|
+
# configured, the subsequent transition to OFF carries it as last_error.
|
|
289
|
+
@data_source_status_provider.update_status(
|
|
290
|
+
LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING,
|
|
291
|
+
LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(
|
|
292
|
+
LaunchDarkly::Interfaces::DataSource::ErrorInfo::UNKNOWN,
|
|
293
|
+
0,
|
|
294
|
+
basis_result.error || "",
|
|
295
|
+
Time.now
|
|
296
|
+
)
|
|
297
|
+
)
|
|
298
|
+
end
|
|
259
299
|
end
|
|
300
|
+
|
|
301
|
+
# The fallback directive takes precedence over the regular failover algorithm,
|
|
302
|
+
# so do not fall through to the next initializer when it is set.
|
|
303
|
+
return true if fallback
|
|
260
304
|
rescue => e
|
|
261
305
|
@logger.error { "[LDClient] Initializer failed with exception: #{e.message}" }
|
|
262
306
|
end
|
|
263
307
|
end
|
|
308
|
+
|
|
309
|
+
false
|
|
264
310
|
end
|
|
265
311
|
|
|
266
312
|
#
|
|
@@ -313,12 +359,21 @@ module LaunchDarkly
|
|
|
313
359
|
case sync_result
|
|
314
360
|
when SyncResult::FDV1
|
|
315
361
|
if @fdv1_fallback_synchronizer_builder
|
|
362
|
+
@logger.warn { "[LDClient] Falling back to FDv1 protocol" }
|
|
316
363
|
@synchronizer_builders = [@fdv1_fallback_synchronizer_builder]
|
|
317
364
|
current_index = 0
|
|
318
365
|
next
|
|
319
366
|
end
|
|
320
|
-
# No FDv1 fallback configured
|
|
321
|
-
|
|
367
|
+
# No FDv1 fallback configured: the data system must HALT
|
|
368
|
+
# rather than fall through to the next FDv2 synchronizer.
|
|
369
|
+
# Continuing to retry would reopen the connection that just
|
|
370
|
+
# delivered the directive.
|
|
371
|
+
@logger.warn { "[LDClient] Synchronizer requested FDv1 fallback but none configured; halting data system" }
|
|
372
|
+
@data_source_status_provider.update_status(
|
|
373
|
+
LaunchDarkly::Interfaces::DataSource::Status::OFF,
|
|
374
|
+
@data_source_status_provider.status.last_error
|
|
375
|
+
)
|
|
376
|
+
break
|
|
322
377
|
when SyncResult::RECOVER
|
|
323
378
|
@logger.info { "[LDClient] Recovery condition met, returning to primary synchronizer" }
|
|
324
379
|
current_index = 0
|
|
@@ -410,7 +465,7 @@ module LaunchDarkly
|
|
|
410
465
|
# Update status
|
|
411
466
|
@data_source_status_provider.update_status(update.state, update.error)
|
|
412
467
|
|
|
413
|
-
return SyncResult::FDV1 if update.
|
|
468
|
+
return SyncResult::FDV1 if update.fallback_to_fdv1
|
|
414
469
|
|
|
415
470
|
return SyncResult::REMOVE if update.state == LaunchDarkly::Interfaces::DataSource::Status::OFF
|
|
416
471
|
end
|
|
@@ -21,6 +21,60 @@ module LaunchDarkly
|
|
|
21
21
|
LD_ENVID_HEADER = "X-LD-EnvID"
|
|
22
22
|
LD_FD_FALLBACK_HEADER = "X-LD-FD-Fallback"
|
|
23
23
|
|
|
24
|
+
#
|
|
25
|
+
# Reports whether the response headers signal that the SDK should fall
|
|
26
|
+
# back to the FDv1 protocol. Lookup is case-insensitive so callers do
|
|
27
|
+
# not need to know whether the header map preserves canonical casing
|
|
28
|
+
# (e.g. {HTTP::Headers}) or has been normalized to lowercase (e.g.
|
|
29
|
+
# {HTTPPollingRequester#fetch}).
|
|
30
|
+
#
|
|
31
|
+
# @param headers [#[], Hash, nil]
|
|
32
|
+
# @return [Boolean]
|
|
33
|
+
#
|
|
34
|
+
def self.fdv1_fallback_requested?(headers)
|
|
35
|
+
return false if headers.nil?
|
|
36
|
+
value = lookup_header(headers, LD_FD_FALLBACK_HEADER)
|
|
37
|
+
# http gem returns arrays for repeated headers; normalize to a string.
|
|
38
|
+
value = value.first if value.is_a?(Array)
|
|
39
|
+
value == 'true'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
#
|
|
43
|
+
# Performs a case-insensitive header lookup that works with both
|
|
44
|
+
# case-insensitive header containers (e.g. `HTTP::Headers`) and plain
|
|
45
|
+
# Ruby hashes -- including hashes whose keys we have downcased
|
|
46
|
+
# ourselves before reaching this code path.
|
|
47
|
+
#
|
|
48
|
+
# @param headers [#[], Hash]
|
|
49
|
+
# @param name [String]
|
|
50
|
+
# @return [String, Array, nil]
|
|
51
|
+
#
|
|
52
|
+
def self.lookup_header(headers, name)
|
|
53
|
+
return nil if headers.nil?
|
|
54
|
+
|
|
55
|
+
if headers.is_a?(Hash)
|
|
56
|
+
# Plain hash: try canonical case, then exact lowercase, then a
|
|
57
|
+
# case-insensitive scan as a final fallback.
|
|
58
|
+
value = headers[name]
|
|
59
|
+
return value unless value.nil?
|
|
60
|
+
|
|
61
|
+
downcased = name.downcase
|
|
62
|
+
value = headers[downcased]
|
|
63
|
+
return value unless value.nil?
|
|
64
|
+
|
|
65
|
+
headers.each_pair do |key, val|
|
|
66
|
+
return val if key.to_s.downcase == downcased
|
|
67
|
+
end
|
|
68
|
+
return nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Non-hash container (e.g. HTTP::Headers). Lookup via [] is
|
|
72
|
+
# already case-insensitive on those types.
|
|
73
|
+
return headers[name] if headers.respond_to?(:[])
|
|
74
|
+
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
24
78
|
#
|
|
25
79
|
# PollingDataSource is a data source that can retrieve information from
|
|
26
80
|
# LaunchDarkly either as an Initializer or as a Synchronizer.
|
|
@@ -46,10 +100,12 @@ module LaunchDarkly
|
|
|
46
100
|
end
|
|
47
101
|
|
|
48
102
|
#
|
|
49
|
-
# Fetch returns a
|
|
103
|
+
# Fetch returns a {LaunchDarkly::Interfaces::DataSystem::FetchResult}
|
|
104
|
+
# wrapping a Basis (or an error) and the FDv1 Fallback Directive
|
|
105
|
+
# signal carried on the server response.
|
|
50
106
|
#
|
|
51
107
|
# @param ss [LaunchDarkly::Interfaces::DataSystem::SelectorStore]
|
|
52
|
-
# @return [LaunchDarkly::Interfaces::DataSystem::
|
|
108
|
+
# @return [LaunchDarkly::Interfaces::DataSystem::FetchResult]
|
|
53
109
|
#
|
|
54
110
|
def fetch(ss)
|
|
55
111
|
poll(ss)
|
|
@@ -71,16 +127,10 @@ module LaunchDarkly
|
|
|
71
127
|
|
|
72
128
|
until @stop.set?
|
|
73
129
|
result = @requester.fetch(ss.selector)
|
|
130
|
+
fallback = LaunchDarkly::Impl::DataSystem.fdv1_fallback_requested?(result.headers)
|
|
131
|
+
envid = LaunchDarkly::Impl::DataSystem.lookup_header(result.headers, LD_ENVID_HEADER)
|
|
74
132
|
|
|
75
133
|
if !result.success?
|
|
76
|
-
fallback = false
|
|
77
|
-
envid = nil
|
|
78
|
-
|
|
79
|
-
if result.headers
|
|
80
|
-
fallback = result.headers[LD_FD_FALLBACK_HEADER] == 'true'
|
|
81
|
-
envid = result.headers[LD_ENVID_HEADER]
|
|
82
|
-
end
|
|
83
|
-
|
|
84
134
|
if result.exception.is_a?(LaunchDarkly::Impl::DataSource::UnexpectedResponseError)
|
|
85
135
|
error_info = LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(
|
|
86
136
|
LaunchDarkly::Interfaces::DataSource::ErrorInfo::ERROR_RESPONSE,
|
|
@@ -99,7 +149,7 @@ module LaunchDarkly
|
|
|
99
149
|
state: LaunchDarkly::Interfaces::DataSource::Status::OFF,
|
|
100
150
|
error: error_info,
|
|
101
151
|
environment_id: envid,
|
|
102
|
-
|
|
152
|
+
fallback_to_fdv1: true
|
|
103
153
|
)
|
|
104
154
|
break
|
|
105
155
|
end
|
|
@@ -108,7 +158,7 @@ module LaunchDarkly
|
|
|
108
158
|
state: LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED,
|
|
109
159
|
error: error_info,
|
|
110
160
|
environment_id: envid,
|
|
111
|
-
|
|
161
|
+
fallback_to_fdv1: false
|
|
112
162
|
)
|
|
113
163
|
@interrupt_event.wait(@poll_interval)
|
|
114
164
|
next
|
|
@@ -118,7 +168,7 @@ module LaunchDarkly
|
|
|
118
168
|
state: LaunchDarkly::Interfaces::DataSource::Status::OFF,
|
|
119
169
|
error: error_info,
|
|
120
170
|
environment_id: envid,
|
|
121
|
-
|
|
171
|
+
fallback_to_fdv1: fallback
|
|
122
172
|
)
|
|
123
173
|
break
|
|
124
174
|
end
|
|
@@ -136,7 +186,7 @@ module LaunchDarkly
|
|
|
136
186
|
state: LaunchDarkly::Interfaces::DataSource::Status::OFF,
|
|
137
187
|
error: error_info,
|
|
138
188
|
environment_id: envid,
|
|
139
|
-
|
|
189
|
+
fallback_to_fdv1: true
|
|
140
190
|
)
|
|
141
191
|
break
|
|
142
192
|
end
|
|
@@ -145,16 +195,14 @@ module LaunchDarkly
|
|
|
145
195
|
state: LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED,
|
|
146
196
|
error: error_info,
|
|
147
197
|
environment_id: envid,
|
|
148
|
-
|
|
198
|
+
fallback_to_fdv1: false
|
|
149
199
|
)
|
|
150
200
|
else
|
|
151
|
-
change_set, headers = result.value
|
|
152
|
-
fallback = headers[LD_FD_FALLBACK_HEADER] == 'true'
|
|
153
201
|
yield LaunchDarkly::Interfaces::DataSystem::Update.new(
|
|
154
202
|
state: LaunchDarkly::Interfaces::DataSource::Status::VALID,
|
|
155
|
-
change_set:
|
|
156
|
-
environment_id:
|
|
157
|
-
|
|
203
|
+
change_set: result.value,
|
|
204
|
+
environment_id: envid,
|
|
205
|
+
fallback_to_fdv1: fallback
|
|
158
206
|
)
|
|
159
207
|
end
|
|
160
208
|
|
|
@@ -177,10 +225,14 @@ module LaunchDarkly
|
|
|
177
225
|
|
|
178
226
|
#
|
|
179
227
|
# @param ss [LaunchDarkly::Interfaces::DataSystem::SelectorStore]
|
|
180
|
-
# @return [LaunchDarkly::
|
|
228
|
+
# @return [LaunchDarkly::Interfaces::DataSystem::FetchResult]
|
|
181
229
|
#
|
|
182
230
|
private def poll(ss)
|
|
231
|
+
# Default to false so the rescue clause has a defined value even when an
|
|
232
|
+
# exception is raised before the header read below has had a chance to run.
|
|
233
|
+
fallback = false
|
|
183
234
|
result = @requester.fetch(ss.selector)
|
|
235
|
+
fallback = LaunchDarkly::Impl::DataSystem.fdv1_fallback_requested?(result.headers)
|
|
184
236
|
|
|
185
237
|
unless result.success?
|
|
186
238
|
if result.exception.is_a?(LaunchDarkly::Impl::DataSource::UnexpectedResponseError)
|
|
@@ -189,15 +241,20 @@ module LaunchDarkly
|
|
|
189
241
|
status_code, "polling request", "will retry"
|
|
190
242
|
)
|
|
191
243
|
@logger.warn { "[LDClient] #{http_error_message_result}" } if Impl::Util.http_error_recoverable?(status_code)
|
|
192
|
-
return LaunchDarkly::
|
|
244
|
+
return LaunchDarkly::Interfaces::DataSystem::FetchResult.new(
|
|
245
|
+
result: LaunchDarkly::Result.fail(http_error_message_result, result.exception),
|
|
246
|
+
fallback_to_fdv1: fallback
|
|
247
|
+
)
|
|
193
248
|
end
|
|
194
249
|
|
|
195
|
-
return LaunchDarkly::
|
|
250
|
+
return LaunchDarkly::Interfaces::DataSystem::FetchResult.new(
|
|
251
|
+
result: LaunchDarkly::Result.fail(result.error || 'Failed to request payload', result.exception),
|
|
252
|
+
fallback_to_fdv1: fallback
|
|
253
|
+
)
|
|
196
254
|
end
|
|
197
255
|
|
|
198
|
-
change_set
|
|
199
|
-
|
|
200
|
-
env_id = headers[LD_ENVID_HEADER]
|
|
256
|
+
change_set = result.value
|
|
257
|
+
env_id = LaunchDarkly::Impl::DataSystem.lookup_header(result.headers, LD_ENVID_HEADER)
|
|
201
258
|
env_id = nil unless env_id.is_a?(String)
|
|
202
259
|
|
|
203
260
|
basis = LaunchDarkly::Interfaces::DataSystem::Basis.new(
|
|
@@ -206,12 +263,22 @@ module LaunchDarkly
|
|
|
206
263
|
environment_id: env_id
|
|
207
264
|
)
|
|
208
265
|
|
|
209
|
-
LaunchDarkly::
|
|
266
|
+
LaunchDarkly::Interfaces::DataSystem::FetchResult.new(
|
|
267
|
+
result: LaunchDarkly::Result.success(basis),
|
|
268
|
+
fallback_to_fdv1: fallback
|
|
269
|
+
)
|
|
210
270
|
rescue => e
|
|
271
|
+
# An exception can fire after we have already read the response
|
|
272
|
+
# headers (e.g. a malformed change_set whose selector is nil makes
|
|
273
|
+
# change_set.selector.defined? raise). Carry the computed fallback
|
|
274
|
+
# signal through so the directive is not silently dropped.
|
|
211
275
|
msg = "Error: Exception encountered when updating flags. #{e}"
|
|
212
276
|
@logger.error { "[LDClient] #{msg}" }
|
|
213
277
|
@logger.debug { "[LDClient] Exception trace: #{e.backtrace}" }
|
|
214
|
-
LaunchDarkly::
|
|
278
|
+
LaunchDarkly::Interfaces::DataSystem::FetchResult.new(
|
|
279
|
+
result: LaunchDarkly::Result.fail(msg, e),
|
|
280
|
+
fallback_to_fdv1: fallback
|
|
281
|
+
)
|
|
215
282
|
end
|
|
216
283
|
end
|
|
217
284
|
|
|
@@ -273,7 +340,7 @@ module LaunchDarkly
|
|
|
273
340
|
end
|
|
274
341
|
|
|
275
342
|
if status == 304
|
|
276
|
-
return LaunchDarkly::Result.success(
|
|
343
|
+
return LaunchDarkly::Result.success(LaunchDarkly::Interfaces::DataSystem::ChangeSetBuilder.no_changes, response_headers)
|
|
277
344
|
end
|
|
278
345
|
|
|
279
346
|
body = response.to_s
|
|
@@ -285,7 +352,7 @@ module LaunchDarkly
|
|
|
285
352
|
|
|
286
353
|
changeset_result = LaunchDarkly::Impl::DataSystem.polling_payload_to_changeset(data)
|
|
287
354
|
if changeset_result.success?
|
|
288
|
-
LaunchDarkly::Result.success(
|
|
355
|
+
LaunchDarkly::Result.success(changeset_result.value, response_headers)
|
|
289
356
|
else
|
|
290
357
|
LaunchDarkly::Result.fail(changeset_result.error, changeset_result.exception, response_headers)
|
|
291
358
|
end
|
|
@@ -361,7 +428,7 @@ module LaunchDarkly
|
|
|
361
428
|
end
|
|
362
429
|
|
|
363
430
|
if status == 304
|
|
364
|
-
return LaunchDarkly::Result.success(
|
|
431
|
+
return LaunchDarkly::Result.success(LaunchDarkly::Interfaces::DataSystem::ChangeSetBuilder.no_changes, response_headers)
|
|
365
432
|
end
|
|
366
433
|
|
|
367
434
|
body = response.to_s
|
|
@@ -373,7 +440,7 @@ module LaunchDarkly
|
|
|
373
440
|
|
|
374
441
|
changeset_result = LaunchDarkly::Impl::DataSystem.fdv1_polling_payload_to_changeset(data)
|
|
375
442
|
if changeset_result.success?
|
|
376
|
-
LaunchDarkly::Result.success(
|
|
443
|
+
LaunchDarkly::Result.success(changeset_result.value, response_headers)
|
|
377
444
|
else
|
|
378
445
|
LaunchDarkly::Result.fail(changeset_result.error, changeset_result.exception, response_headers)
|
|
379
446
|
end
|
|
@@ -12,9 +12,6 @@ module LaunchDarkly
|
|
|
12
12
|
#
|
|
13
13
|
# DeleteObject specifies the deletion of a particular object.
|
|
14
14
|
#
|
|
15
|
-
# This type is not stable, and not subject to any backwards
|
|
16
|
-
# compatibility guarantees or semantic versioning. It is not suitable for production usage.
|
|
17
|
-
#
|
|
18
15
|
class DeleteObject
|
|
19
16
|
# @return [Integer] The version
|
|
20
17
|
attr_reader :version
|
|
@@ -79,9 +76,6 @@ module LaunchDarkly
|
|
|
79
76
|
#
|
|
80
77
|
# PutObject specifies the addition of a particular object with upsert semantics.
|
|
81
78
|
#
|
|
82
|
-
# This type is not stable, and not subject to any backwards
|
|
83
|
-
# compatibility guarantees or semantic versioning. It is not suitable for production usage.
|
|
84
|
-
#
|
|
85
79
|
class PutObject
|
|
86
80
|
# @return [Integer] The version
|
|
87
81
|
attr_reader :version
|
|
@@ -153,28 +147,15 @@ module LaunchDarkly
|
|
|
153
147
|
#
|
|
154
148
|
# Goodbye represents a goodbye event.
|
|
155
149
|
#
|
|
156
|
-
# This type is not stable, and not subject to any backwards
|
|
157
|
-
# compatibility guarantees or semantic versioning. It is not suitable for production usage.
|
|
158
|
-
#
|
|
159
150
|
class Goodbye
|
|
160
151
|
# @return [String] The reason for goodbye
|
|
161
152
|
attr_reader :reason
|
|
162
153
|
|
|
163
|
-
# @return [Boolean] Whether the goodbye is silent
|
|
164
|
-
attr_reader :silent
|
|
165
|
-
|
|
166
|
-
# @return [Boolean] Whether this represents a catastrophic failure
|
|
167
|
-
attr_reader :catastrophe
|
|
168
|
-
|
|
169
154
|
#
|
|
170
155
|
# @param reason [String] The reason for goodbye
|
|
171
|
-
# @param silent [Boolean] Whether the goodbye is silent
|
|
172
|
-
# @param catastrophe [Boolean] Whether this represents a catastrophic failure
|
|
173
156
|
#
|
|
174
|
-
def initialize(reason
|
|
157
|
+
def initialize(reason:)
|
|
175
158
|
@reason = reason
|
|
176
|
-
@silent = silent
|
|
177
|
-
@catastrophe = catastrophe
|
|
178
159
|
end
|
|
179
160
|
|
|
180
161
|
#
|
|
@@ -185,8 +166,6 @@ module LaunchDarkly
|
|
|
185
166
|
def to_h
|
|
186
167
|
{
|
|
187
168
|
reason: @reason,
|
|
188
|
-
silent: @silent,
|
|
189
|
-
catastrophe: @catastrophe,
|
|
190
169
|
}
|
|
191
170
|
end
|
|
192
171
|
|
|
@@ -199,21 +178,16 @@ module LaunchDarkly
|
|
|
199
178
|
#
|
|
200
179
|
def self.from_h(data)
|
|
201
180
|
reason = data[:reason]
|
|
202
|
-
silent = data[:silent]
|
|
203
|
-
catastrophe = data[:catastrophe]
|
|
204
181
|
|
|
205
|
-
raise ArgumentError, "Missing required fields in Goodbye" if reason.nil?
|
|
182
|
+
raise ArgumentError, "Missing required fields in Goodbye" if reason.nil?
|
|
206
183
|
|
|
207
|
-
new(reason: reason
|
|
184
|
+
new(reason: reason)
|
|
208
185
|
end
|
|
209
186
|
end
|
|
210
187
|
|
|
211
188
|
#
|
|
212
189
|
# Error represents an error event.
|
|
213
190
|
#
|
|
214
|
-
# This type is not stable, and not subject to any backwards
|
|
215
|
-
# compatibility guarantees or semantic versioning. It is not suitable for production usage.
|
|
216
|
-
#
|
|
217
191
|
class Error
|
|
218
192
|
# @return [String] The payload ID
|
|
219
193
|
attr_reader :payload_id
|