launchdarkly-server-sdk 8.14.0-java → 8.15.1-java
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 +28 -0
- data/lib/ldclient-rb/context.rb +29 -0
- data/lib/ldclient-rb/events.rb +1 -1
- data/lib/ldclient-rb/impl/context_filter.rb +61 -11
- data/lib/ldclient-rb/impl/data_source/polling.rb +18 -2
- data/lib/ldclient-rb/impl/data_source/requestor.rb +13 -3
- data/lib/ldclient-rb/impl/data_source/stream.rb +2 -0
- data/lib/ldclient-rb/impl/data_source.rb +37 -0
- data/lib/ldclient-rb/impl/data_system/fdv1.rb +5 -0
- data/lib/ldclient-rb/impl/data_system/fdv2.rb +25 -1
- data/lib/ldclient-rb/impl/data_system/polling.rb +1 -1
- data/lib/ldclient-rb/impl/data_system.rb +9 -0
- data/lib/ldclient-rb/integrations/file_data.rb +1 -1
- data/lib/ldclient-rb/interfaces/hooks.rb +11 -1
- data/lib/ldclient-rb/ldclient.rb +1 -1
- data/lib/ldclient-rb/reference.rb +4 -0
- 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: b11944b433bea0b0d731f8640f8fd4e83499e9bb3e6f80d480b21d90f1a40cfd
|
|
4
|
+
data.tar.gz: 16cdb4c8fd88ae05176d515405692e0a7f00bf438391a8a306b52d670c0fcd78
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aab9b715aa8d7dd4de84c4d766bb4353b2ea0acf329b6e6c412c6602729791ea00f0125bb90e1f82a721cf8f0f3560d66856490d0449c261443ac18ad601fd30
|
|
7
|
+
data.tar.gz: cff21015700458dac3bf4aceb3442ba1c84f139bf9df733fb3417b5676458ecf27af4ef7c4890f4c517a6095affc6f25fab0c6ceb651a816a8886d5c283f8ed8
|
data/lib/ldclient-rb/config.rb
CHANGED
|
@@ -262,6 +262,10 @@ module LaunchDarkly
|
|
|
262
262
|
# You can also specify the same behavior for an individual flag evaluation
|
|
263
263
|
# by providing the context object with a list of private attributes.
|
|
264
264
|
#
|
|
265
|
+
# Each entry is an attribute reference. A reference addresses attributes by
|
|
266
|
+
# symbol name, so it cannot make an attribute private if that attribute was
|
|
267
|
+
# given a string name. Refer to {LDContext} for the symbol requirement.
|
|
268
|
+
#
|
|
265
269
|
# @see https://docs.launchdarkly.com/sdk/features/user-context-config#using-private-attributes
|
|
266
270
|
#
|
|
267
271
|
# @return [Array<String>]
|
|
@@ -399,6 +403,30 @@ module LaunchDarkly
|
|
|
399
403
|
#
|
|
400
404
|
attr_reader :wrapper_version
|
|
401
405
|
|
|
406
|
+
#
|
|
407
|
+
# Returns a copy of this configuration with different wrapper information.
|
|
408
|
+
#
|
|
409
|
+
# This is intended for use by wrapper libraries, such as the LaunchDarkly OpenFeature provider, which need to
|
|
410
|
+
# identify themselves rather than the application which created the configuration.
|
|
411
|
+
#
|
|
412
|
+
# The copy is shallow: it shares the objects the original configuration references, such as the feature store,
|
|
413
|
+
# the logger, and the socket factory. Mutating one of those objects affects both configurations.
|
|
414
|
+
#
|
|
415
|
+
# @param wrapper_name [String, nil] see {#wrapper_name}
|
|
416
|
+
# @param wrapper_version [String, nil] see {#wrapper_version}
|
|
417
|
+
#
|
|
418
|
+
# @return [LaunchDarkly::Config]
|
|
419
|
+
#
|
|
420
|
+
def with_wrapper_information(wrapper_name, wrapper_version = nil)
|
|
421
|
+
updated = dup
|
|
422
|
+
updated.wrapper_name = wrapper_name
|
|
423
|
+
updated.wrapper_version = wrapper_version
|
|
424
|
+
|
|
425
|
+
updated
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
protected attr_writer :wrapper_name, :wrapper_version
|
|
429
|
+
|
|
402
430
|
#
|
|
403
431
|
# The factory used to construct sockets for HTTP operations. The factory must
|
|
404
432
|
# provide the method `open(uri, timeout)`. The `open` method must return a
|
data/lib/ldclient-rb/context.rb
CHANGED
|
@@ -6,6 +6,17 @@ module LaunchDarkly
|
|
|
6
6
|
# LDContext is a collection of attributes that can be referenced in flag
|
|
7
7
|
# evaluations and analytics events.
|
|
8
8
|
#
|
|
9
|
+
# Every attribute name must be a symbol. This applies to the built-in
|
|
10
|
+
# properties, such as :key and :kind, and to custom attributes at every level
|
|
11
|
+
# of nesting. The SDK addresses attributes by symbol, so it cannot find an
|
|
12
|
+
# attribute that has a string name. Such an attribute is invisible to flag
|
|
13
|
+
# evaluation and to private attribute redaction.
|
|
14
|
+
#
|
|
15
|
+
# Take care with data that arrives as JSON. `JSON.parse` returns string keys
|
|
16
|
+
# by default, so pass `symbolize_names: true` before you build a context from
|
|
17
|
+
# it. Note that the `"name": value` form in a hash literal already produces a
|
|
18
|
+
# symbol, so a hand-written literal is safe.
|
|
19
|
+
#
|
|
9
20
|
# To create an LDContext of a single kind, such as a user, you may use
|
|
10
21
|
# {LDContext#create} or {LDContext#with_key}.
|
|
11
22
|
#
|
|
@@ -148,6 +159,9 @@ module LaunchDarkly
|
|
|
148
159
|
#
|
|
149
160
|
# Return an array of top level attribute keys (excluding built-in attributes)
|
|
150
161
|
#
|
|
162
|
+
# The keys come back in the form the caller supplied. A string key is
|
|
163
|
+
# returned as a string, even though the SDK cannot address it.
|
|
164
|
+
#
|
|
151
165
|
# @return [Array<Symbol>]
|
|
152
166
|
#
|
|
153
167
|
def get_custom_attribute_names
|
|
@@ -172,6 +186,9 @@ module LaunchDarkly
|
|
|
172
186
|
# values out of JSON objects or arrays, such as "/address/street". Use
|
|
173
187
|
# {#get_value_for_reference} for that purpose.
|
|
174
188
|
#
|
|
189
|
+
# The lookup treats the name as a symbol. An attribute that was stored under
|
|
190
|
+
# a string name is therefore not found, and the result is nil.
|
|
191
|
+
#
|
|
175
192
|
# If the value is found, the return value is the attribute value;
|
|
176
193
|
# otherwise, it is nil.
|
|
177
194
|
#
|
|
@@ -200,6 +217,10 @@ module LaunchDarkly
|
|
|
200
217
|
# Use {#individual_context} to inspect a Context for a particular kind and
|
|
201
218
|
# then get its attributes.
|
|
202
219
|
#
|
|
220
|
+
# A Reference holds its path components as symbols, so each step of the
|
|
221
|
+
# lookup matches a symbol key. A path that crosses an attribute with a
|
|
222
|
+
# string name resolves to nil.
|
|
223
|
+
#
|
|
203
224
|
# If the value is found, the return value is the attribute value;
|
|
204
225
|
# otherwise, it is nil.
|
|
205
226
|
#
|
|
@@ -440,6 +461,14 @@ module LaunchDarkly
|
|
|
440
461
|
# {https://docs.launchdarkly.com/sdk/features/user-config SDK
|
|
441
462
|
# documentation}.
|
|
442
463
|
#
|
|
464
|
+
# Every key in the hash must be a symbol, at the top level and inside any
|
|
465
|
+
# nested attribute value. Refer to the {LDContext} class documentation for
|
|
466
|
+
# why, and for the JSON.parse caveat.
|
|
467
|
+
#
|
|
468
|
+
# A string key does not make the context invalid, with two exceptions. The
|
|
469
|
+
# context requires a symbol :kind and a symbol :key, so a hash that supplies
|
|
470
|
+
# those as strings is invalid.
|
|
471
|
+
#
|
|
443
472
|
# @param data [Hash]
|
|
444
473
|
# @return [LDContext]
|
|
445
474
|
#
|
data/lib/ldclient-rb/events.rb
CHANGED
|
@@ -487,7 +487,7 @@ module LaunchDarkly
|
|
|
487
487
|
SUMMARY_KIND = 'summary'
|
|
488
488
|
|
|
489
489
|
def initialize(config)
|
|
490
|
-
@context_filter = LaunchDarkly::Impl::ContextFilter.new(config.all_attributes_private, config.private_attributes)
|
|
490
|
+
@context_filter = LaunchDarkly::Impl::ContextFilter.new(config.all_attributes_private, config.private_attributes, config.logger)
|
|
491
491
|
end
|
|
492
492
|
|
|
493
493
|
# Transforms events into the format used for event sending.
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
require "concurrent/atomics"
|
|
2
|
+
|
|
1
3
|
module LaunchDarkly
|
|
2
4
|
module Impl
|
|
3
5
|
class ContextFilter
|
|
4
6
|
#
|
|
5
7
|
# @param all_attributes_private [Boolean]
|
|
6
8
|
# @param private_attributes [Array<String>]
|
|
9
|
+
# @param logger [Logger, nil]
|
|
7
10
|
#
|
|
8
|
-
def initialize(all_attributes_private, private_attributes)
|
|
11
|
+
def initialize(all_attributes_private, private_attributes, logger = nil)
|
|
9
12
|
@all_attributes_private = all_attributes_private
|
|
13
|
+
@logger = logger
|
|
14
|
+
@non_symbol_name_logged = Concurrent::AtomicBoolean.new(false)
|
|
10
15
|
|
|
11
16
|
@private_attributes = []
|
|
12
17
|
private_attributes.each do |attribute|
|
|
@@ -70,17 +75,22 @@ module LaunchDarkly
|
|
|
70
75
|
filtered[:anonymous] = true if anonymous
|
|
71
76
|
|
|
72
77
|
redacted = []
|
|
73
|
-
private_attributes = @private_attributes
|
|
78
|
+
private_attributes = @private_attributes + context.private_attributes
|
|
74
79
|
|
|
75
80
|
name = context.get_value(:name)
|
|
76
|
-
if !name.nil? && !check_whole_attribute_private(:name, private_attributes, redacted, anonymous && redact_anonymous)
|
|
81
|
+
if !name.nil? && !check_whole_attribute_private(Reference.create_literal(:name), private_attributes, redacted, anonymous && redact_anonymous)
|
|
77
82
|
filtered[:name] = name
|
|
78
83
|
end
|
|
79
84
|
|
|
80
85
|
context.get_custom_attribute_names.each do |attribute|
|
|
81
|
-
unless
|
|
86
|
+
unless attribute.is_a?(Symbol)
|
|
87
|
+
log_non_symbol_name(attribute)
|
|
88
|
+
next
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
unless check_whole_attribute_private(Reference.create_literal(attribute), private_attributes, redacted, anonymous && redact_anonymous)
|
|
82
92
|
value = context.get_value(attribute)
|
|
83
|
-
filtered[attribute] = redact_json_value(nil, attribute, value, private_attributes, redacted)
|
|
93
|
+
filtered[attribute] = redact_json_value(nil, attribute, value, private_attributes, redacted, [])
|
|
84
94
|
end
|
|
85
95
|
end
|
|
86
96
|
|
|
@@ -92,7 +102,7 @@ module LaunchDarkly
|
|
|
92
102
|
#
|
|
93
103
|
# Check if an entire attribute should be redacted.
|
|
94
104
|
#
|
|
95
|
-
# @param attribute [
|
|
105
|
+
# @param attribute [Reference]
|
|
96
106
|
# @param private_attributes [Array<Reference>]
|
|
97
107
|
# @param redacted [Array<Symbol>]
|
|
98
108
|
# @param redact_all [Boolean]
|
|
@@ -100,13 +110,13 @@ module LaunchDarkly
|
|
|
100
110
|
#
|
|
101
111
|
private def check_whole_attribute_private(attribute, private_attributes, redacted, redact_all)
|
|
102
112
|
if @all_attributes_private || redact_all
|
|
103
|
-
redacted << attribute
|
|
113
|
+
redacted << attribute.raw_path.to_sym
|
|
104
114
|
return true
|
|
105
115
|
end
|
|
106
116
|
|
|
107
117
|
private_attributes.each do |private_attribute|
|
|
108
|
-
if private_attribute.component(0) == attribute && private_attribute.depth == 1
|
|
109
|
-
redacted << attribute
|
|
118
|
+
if private_attribute.component(0) == attribute.component(0) && private_attribute.depth == 1
|
|
119
|
+
redacted << attribute.raw_path.to_sym
|
|
110
120
|
return true
|
|
111
121
|
end
|
|
112
122
|
end
|
|
@@ -122,16 +132,34 @@ module LaunchDarkly
|
|
|
122
132
|
# @param value [any]
|
|
123
133
|
# @param private_attributes [Array<Reference>]
|
|
124
134
|
# @param redacted [Array<Symbol>]
|
|
135
|
+
# @param visited [Array<Hash>]
|
|
125
136
|
# @return [any]
|
|
126
137
|
#
|
|
127
|
-
private def redact_json_value(parent_path, name, value, private_attributes, redacted)
|
|
138
|
+
private def redact_json_value(parent_path, name, value, private_attributes, redacted, visited)
|
|
128
139
|
return value unless value.is_a?(Hash)
|
|
129
140
|
|
|
130
141
|
ret = {}
|
|
131
142
|
current_path = parent_path.clone || []
|
|
132
143
|
current_path << name
|
|
133
144
|
|
|
145
|
+
# The chain of hashes from the attribute root down to this value. A
|
|
146
|
+
# nested value that points back into this chain is a cycle and is
|
|
147
|
+
# omitted.
|
|
148
|
+
#
|
|
149
|
+
# The comparison is by object identity, which keeps the check cheap.
|
|
150
|
+
# Context creation copies each top-level attribute value, so a cycle
|
|
151
|
+
# through that value appears once in the output before it is cut.
|
|
152
|
+
# Private attribute references match the paths that appear in the
|
|
153
|
+
# output. Cyclic values are not valid context data; this check only
|
|
154
|
+
# prevents unbounded recursion.
|
|
155
|
+
branch = visited + [value]
|
|
156
|
+
|
|
134
157
|
value.each do |k, v|
|
|
158
|
+
unless k.is_a?(Symbol)
|
|
159
|
+
log_non_symbol_name(k)
|
|
160
|
+
next
|
|
161
|
+
end
|
|
162
|
+
|
|
135
163
|
was_redacted = false
|
|
136
164
|
private_attributes.each do |private_attribute|
|
|
137
165
|
next unless private_attribute.depth == (current_path.count + 1)
|
|
@@ -155,12 +183,34 @@ module LaunchDarkly
|
|
|
155
183
|
end
|
|
156
184
|
|
|
157
185
|
unless was_redacted
|
|
158
|
-
|
|
186
|
+
next if v.is_a?(Hash) && branch.any? { |seen| seen.equal?(v) }
|
|
187
|
+
|
|
188
|
+
ret[k] = redact_json_value(current_path, k, v, private_attributes, redacted, branch)
|
|
159
189
|
end
|
|
160
190
|
end
|
|
161
191
|
|
|
162
192
|
ret
|
|
163
193
|
end
|
|
194
|
+
|
|
195
|
+
#
|
|
196
|
+
# Log the first attribute found with a non-symbol name. Later
|
|
197
|
+
# occurrences are not logged to prevent log spam.
|
|
198
|
+
#
|
|
199
|
+
# Attribute references cannot address non-symbol names, so these
|
|
200
|
+
# attributes cannot be evaluated or redacted. They are omitted from
|
|
201
|
+
# analytics events.
|
|
202
|
+
#
|
|
203
|
+
# @param name [any]
|
|
204
|
+
#
|
|
205
|
+
private def log_non_symbol_name(name)
|
|
206
|
+
return if @logger.nil?
|
|
207
|
+
return unless @non_symbol_name_logged.make_true
|
|
208
|
+
|
|
209
|
+
@logger.error do
|
|
210
|
+
"[LDClient] Context attributes with non-symbol names cannot be evaluated or redacted, " \
|
|
211
|
+
"so they are omitted from analytics events (first occurrence: #{name.inspect}). This message is logged once."
|
|
212
|
+
end
|
|
213
|
+
end
|
|
164
214
|
end
|
|
165
215
|
end
|
|
166
216
|
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require "ldclient-rb/impl/data_source"
|
|
1
2
|
require "ldclient-rb/impl/repeating_task"
|
|
2
3
|
require "ldclient-rb/impl/util"
|
|
3
4
|
|
|
@@ -35,7 +36,8 @@ module LaunchDarkly
|
|
|
35
36
|
|
|
36
37
|
def poll
|
|
37
38
|
begin
|
|
38
|
-
all_data =
|
|
39
|
+
all_data, headers = request_all_data
|
|
40
|
+
DataSource.record_environment_id(@config.data_source_update_sink, headers)
|
|
39
41
|
if all_data
|
|
40
42
|
update_sink_or_data_store.init(all_data)
|
|
41
43
|
if @initialized.make_true
|
|
@@ -65,8 +67,11 @@ module LaunchDarkly
|
|
|
65
67
|
error_info
|
|
66
68
|
)
|
|
67
69
|
else
|
|
68
|
-
|
|
70
|
+
# Publish the OFF status before releasing anyone waiting on the
|
|
71
|
+
# ready event, so a client that returns from start can rely on the
|
|
72
|
+
# data source status already reflecting the failure.
|
|
69
73
|
stop_with_error_info error_info
|
|
74
|
+
@ready.set # if client was waiting on us, make it stop waiting - has no effect if already set
|
|
70
75
|
end
|
|
71
76
|
rescue StandardError => e
|
|
72
77
|
Impl::Util.log_exception(@config.logger, "Exception while polling", e)
|
|
@@ -92,6 +97,17 @@ module LaunchDarkly
|
|
|
92
97
|
@config.data_source_update_sink || @config.feature_store
|
|
93
98
|
end
|
|
94
99
|
|
|
100
|
+
#
|
|
101
|
+
# Requestors provided by application code may not be able to report the response headers.
|
|
102
|
+
#
|
|
103
|
+
# @return [Array(Hash, HTTP::Headers, nil)]
|
|
104
|
+
#
|
|
105
|
+
private def request_all_data
|
|
106
|
+
return @requestor.request_all_data_with_headers if @requestor.respond_to?(:request_all_data_with_headers)
|
|
107
|
+
|
|
108
|
+
[@requestor.request_all_data, nil]
|
|
109
|
+
end
|
|
110
|
+
|
|
95
111
|
#
|
|
96
112
|
# @param [LaunchDarkly::Interfaces::DataSource::ErrorInfo, nil] error_info
|
|
97
113
|
#
|
|
@@ -40,8 +40,18 @@ module LaunchDarkly
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def request_all_data()
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
request_all_data_with_headers.first
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
#
|
|
47
|
+
# Requests the full data set, also returning the headers of the response it was retrieved from.
|
|
48
|
+
#
|
|
49
|
+
# @return [Array(Hash, HTTP::Headers)]
|
|
50
|
+
#
|
|
51
|
+
def request_all_data_with_headers
|
|
52
|
+
body, headers = make_request("/sdk/latest-all")
|
|
53
|
+
all_data = JSON.parse(body, symbolize_names: true)
|
|
54
|
+
[Impl::Model.make_all_store_data(all_data, @config.logger), headers]
|
|
45
55
|
end
|
|
46
56
|
|
|
47
57
|
def stop
|
|
@@ -80,7 +90,7 @@ module LaunchDarkly
|
|
|
80
90
|
etag = response.headers["etag"]
|
|
81
91
|
@cache.write(uri, CacheEntry.new(etag, body)) unless etag.nil?
|
|
82
92
|
end
|
|
83
|
-
body
|
|
93
|
+
[body, response.headers]
|
|
84
94
|
end
|
|
85
95
|
|
|
86
96
|
def fix_encoding(body, content_type)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require "ldclient-rb/impl/data_source"
|
|
1
2
|
require "ldclient-rb/impl/model/serialization"
|
|
2
3
|
require "ldclient-rb/impl/util"
|
|
3
4
|
require "ldclient-rb/in_memory_store"
|
|
@@ -54,6 +55,7 @@ module LaunchDarkly
|
|
|
54
55
|
|
|
55
56
|
uri = Impl::Util.add_payload_filter_key(@config.stream_uri + "/all", @config)
|
|
56
57
|
@es = SSE::Client.new(uri, **opts) do |conn|
|
|
58
|
+
conn.on_connect { |response_headers| DataSource.record_environment_id(@data_source_update_sink, response_headers) }
|
|
57
59
|
conn.on_event { |event| process_message(event) }
|
|
58
60
|
conn.on_error { |err|
|
|
59
61
|
log_connection_result(false)
|
|
@@ -7,6 +7,27 @@ require "set"
|
|
|
7
7
|
module LaunchDarkly
|
|
8
8
|
module Impl
|
|
9
9
|
module DataSource
|
|
10
|
+
LD_ENVID_HEADER = "X-LD-EnvID"
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# Records the environment ID reported by LaunchDarkly, when the response headers provide one and the sink is
|
|
14
|
+
# able to hold it. Externally implemented sinks are not required to support this.
|
|
15
|
+
#
|
|
16
|
+
# @param sink [LaunchDarkly::Interfaces::DataSource::UpdateSink, nil]
|
|
17
|
+
# @param headers [#[], nil]
|
|
18
|
+
# @return [void]
|
|
19
|
+
#
|
|
20
|
+
def self.record_environment_id(sink, headers)
|
|
21
|
+
return if headers.nil? || !sink.respond_to?(:set_environment_id)
|
|
22
|
+
|
|
23
|
+
environment_id = headers[LD_ENVID_HEADER]
|
|
24
|
+
# The http gem returns arrays for repeated headers; normalize to a string.
|
|
25
|
+
environment_id = environment_id.first if environment_id.is_a?(Array)
|
|
26
|
+
return unless environment_id.is_a?(String) && !environment_id.empty?
|
|
27
|
+
|
|
28
|
+
sink.set_environment_id(environment_id)
|
|
29
|
+
end
|
|
30
|
+
|
|
10
31
|
class StatusProvider
|
|
11
32
|
include LaunchDarkly::Interfaces::DataSource::StatusProvider
|
|
12
33
|
|
|
@@ -41,12 +62,28 @@ module LaunchDarkly
|
|
|
41
62
|
@dependency_tracker = LaunchDarkly::Impl::DependencyTracker.new
|
|
42
63
|
|
|
43
64
|
@mutex = Mutex.new
|
|
65
|
+
@environment_id = nil
|
|
44
66
|
@current_status = LaunchDarkly::Interfaces::DataSource::Status.new(
|
|
45
67
|
LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING,
|
|
46
68
|
Time.now,
|
|
47
69
|
nil)
|
|
48
70
|
end
|
|
49
71
|
|
|
72
|
+
#
|
|
73
|
+
# @return [String, nil] The environment ID reported by LaunchDarkly, if known
|
|
74
|
+
#
|
|
75
|
+
def environment_id
|
|
76
|
+
@mutex.synchronize { @environment_id }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
#
|
|
80
|
+
# @param environment_id [String]
|
|
81
|
+
# @return [void]
|
|
82
|
+
#
|
|
83
|
+
def set_environment_id(environment_id)
|
|
84
|
+
@mutex.synchronize { @environment_id = environment_id }
|
|
85
|
+
end
|
|
86
|
+
|
|
50
87
|
def init(all_data)
|
|
51
88
|
old_data = nil
|
|
52
89
|
monitor_store_update do
|
|
@@ -100,6 +100,9 @@ module LaunchDarkly
|
|
|
100
100
|
@store.with_persistence(wrapper, writable, @data_store_status_provider)
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
+
@environment_id = nil
|
|
104
|
+
@environment_id_lock = Mutex.new
|
|
105
|
+
|
|
103
106
|
# Threading
|
|
104
107
|
@stop_event = Concurrent::Event.new
|
|
105
108
|
@ready_event = Concurrent::Event.new
|
|
@@ -185,6 +188,11 @@ module LaunchDarkly
|
|
|
185
188
|
@flag_change_broadcaster
|
|
186
189
|
end
|
|
187
190
|
|
|
191
|
+
# (see DataSystem#environment_id)
|
|
192
|
+
def environment_id
|
|
193
|
+
@environment_id_lock.synchronize { @environment_id }
|
|
194
|
+
end
|
|
195
|
+
|
|
188
196
|
# (see DataSystem#data_availability)
|
|
189
197
|
def data_availability
|
|
190
198
|
return DataAvailability::REFRESHED if @store.selector.defined?
|
|
@@ -270,6 +278,7 @@ module LaunchDarkly
|
|
|
270
278
|
if basis_result.success?
|
|
271
279
|
basis = basis_result.value
|
|
272
280
|
@logger.info { "[LDClient] Initialized via #{initializer.name}" }
|
|
281
|
+
record_environment_id(basis.environment_id)
|
|
273
282
|
|
|
274
283
|
# Apply the basis to the store regardless of whether fallback was signalled.
|
|
275
284
|
# If the server returned a valid payload alongside the directive we still want
|
|
@@ -460,7 +469,10 @@ module LaunchDarkly
|
|
|
460
469
|
@store.apply(update.change_set, true) if update.change_set
|
|
461
470
|
|
|
462
471
|
# Set ready event on valid update
|
|
463
|
-
|
|
472
|
+
if update.state == LaunchDarkly::Interfaces::DataSource::Status::VALID
|
|
473
|
+
@ready_event.set
|
|
474
|
+
record_environment_id(update.environment_id)
|
|
475
|
+
end
|
|
464
476
|
|
|
465
477
|
# Update status
|
|
466
478
|
@data_source_status_provider.update_status(update.state, update.error)
|
|
@@ -481,6 +493,18 @@ module LaunchDarkly
|
|
|
481
493
|
SyncResult::REMOVE
|
|
482
494
|
end
|
|
483
495
|
|
|
496
|
+
#
|
|
497
|
+
# Retains the environment ID reported by a successful connection to LaunchDarkly.
|
|
498
|
+
#
|
|
499
|
+
# @param environment_id [String, nil]
|
|
500
|
+
# @return [void]
|
|
501
|
+
#
|
|
502
|
+
def record_environment_id(environment_id)
|
|
503
|
+
return unless environment_id.is_a?(String) && !environment_id.empty?
|
|
504
|
+
|
|
505
|
+
@environment_id_lock.synchronize { @environment_id = environment_id }
|
|
506
|
+
end
|
|
507
|
+
|
|
484
508
|
#
|
|
485
509
|
# Determine if we should fallback to the next synchronizer.
|
|
486
510
|
#
|
|
@@ -313,7 +313,7 @@ module LaunchDarkly
|
|
|
313
313
|
query_params << ["filter", @config.payload_filter_key] unless @config.payload_filter_key.nil?
|
|
314
314
|
|
|
315
315
|
if selector && selector.defined?
|
|
316
|
-
query_params << ["
|
|
316
|
+
query_params << ["basis", selector.state]
|
|
317
317
|
end
|
|
318
318
|
|
|
319
319
|
uri = @poll_uri
|
|
@@ -119,6 +119,15 @@ module LaunchDarkly
|
|
|
119
119
|
raise NotImplementedError, "#{self.class} must implement #set_diagnostic_accumulator"
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
+
#
|
|
123
|
+
# Returns the ID of the environment the SDK is connected to, if LaunchDarkly has reported one.
|
|
124
|
+
#
|
|
125
|
+
# @return [String, nil]
|
|
126
|
+
#
|
|
127
|
+
def environment_id
|
|
128
|
+
raise NotImplementedError, "#{self.class} must implement #environment_id"
|
|
129
|
+
end
|
|
130
|
+
|
|
122
131
|
#
|
|
123
132
|
# Represents the availability of data in the SDK.
|
|
124
133
|
#
|
|
@@ -70,17 +70,27 @@ module LaunchDarkly
|
|
|
70
70
|
attr_reader :default_value
|
|
71
71
|
attr_reader :method
|
|
72
72
|
|
|
73
|
+
#
|
|
74
|
+
# The ID of the LaunchDarkly environment the SDK is connected to, if known. This is only available once the
|
|
75
|
+
# SDK has received data from LaunchDarkly.
|
|
76
|
+
#
|
|
77
|
+
# @return [String, nil]
|
|
78
|
+
#
|
|
79
|
+
attr_reader :environment_id
|
|
80
|
+
|
|
73
81
|
#
|
|
74
82
|
# @param key [String]
|
|
75
83
|
# @param context [LaunchDarkly::LDContext]
|
|
76
84
|
# @param default_value [any]
|
|
77
85
|
# @param method [Symbol]
|
|
86
|
+
# @param environment_id [String, nil]
|
|
78
87
|
#
|
|
79
|
-
def initialize(key, context, default_value, method)
|
|
88
|
+
def initialize(key, context, default_value, method, environment_id = nil)
|
|
80
89
|
@key = key
|
|
81
90
|
@context = context
|
|
82
91
|
@default_value = default_value
|
|
83
92
|
@method = method
|
|
93
|
+
@environment_id = environment_id
|
|
84
94
|
end
|
|
85
95
|
end
|
|
86
96
|
end
|
data/lib/ldclient-rb/ldclient.rb
CHANGED
|
@@ -421,7 +421,7 @@ module LaunchDarkly
|
|
|
421
421
|
# Hooks can be added and we want to ensure all correct stages for a given hook execute. For example, we do not
|
|
422
422
|
# want to trigger the after_evaluation method without also triggering the before_evaluation method.
|
|
423
423
|
hooks = @hooks.dup
|
|
424
|
-
evaluation_series_context = Interfaces::Hooks::EvaluationSeriesContext.new(key, context, default, method)
|
|
424
|
+
evaluation_series_context = Interfaces::Hooks::EvaluationSeriesContext.new(key, context, default, method, @data_system.environment_id)
|
|
425
425
|
|
|
426
426
|
[hooks, evaluation_series_context]
|
|
427
427
|
end
|
|
@@ -12,6 +12,10 @@ module LaunchDarkly
|
|
|
12
12
|
# or to identify an attribute or nested value that should be considered
|
|
13
13
|
# private.
|
|
14
14
|
#
|
|
15
|
+
# A Reference holds its path components as symbols, whichever form the input
|
|
16
|
+
# string used. It therefore addresses only context attributes that have symbol
|
|
17
|
+
# names, which is the form a context requires.
|
|
18
|
+
#
|
|
15
19
|
# Parsing and validation are done at the time that the Reference is
|
|
16
20
|
# constructed. If a Reference instance was created from an invalid string, it
|
|
17
21
|
# is considered invalid and its {Reference#error} attribute will return a
|
data/lib/ldclient-rb/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: launchdarkly-server-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 8.
|
|
4
|
+
version: 8.15.1
|
|
5
5
|
platform: java
|
|
6
6
|
authors:
|
|
7
7
|
- LaunchDarkly
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-08-28 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: aws-sdk-dynamodb
|