launchdarkly-server-sdk 8.15.0 → 8.16.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/lib/ldclient-rb/config.rb +4 -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 +54 -4
- data/lib/ldclient-rb/impl/data_source/polling.rb +4 -1
- data/lib/ldclient-rb/ldclient.rb +73 -4
- 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: b7bea0f24a78b42aa3494b0c36a0523a91671e7db9c3bb264011bc6791c020f7
|
|
4
|
+
data.tar.gz: fefb72d9bbf9febfb214268935bb289fe3175447bb56529d67a5211ee0b689d5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2c17d728ec723fb03031bea1b6a96f32ce030202ae29ab1dc094cdfad8602489bd69370d95fefd8b4a17fd29b07fac868e04146b3cabb0254b23640ea2256952
|
|
7
|
+
data.tar.gz: 9fbed6b65e5ec58597e2b09823c9f6ea25ce487a51061910480a123ed8e47eb188adc24e87cf675aed83ae99bfbf2f1d9dfc83ce4c4bb88e4e930bcf23a6a184
|
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>]
|
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|
|
|
@@ -78,9 +83,14 @@ module LaunchDarkly
|
|
|
78
83
|
end
|
|
79
84
|
|
|
80
85
|
context.get_custom_attribute_names.each do |attribute|
|
|
86
|
+
unless attribute.is_a?(Symbol)
|
|
87
|
+
log_non_symbol_name(attribute)
|
|
88
|
+
next
|
|
89
|
+
end
|
|
90
|
+
|
|
81
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
|
|
|
@@ -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
|
|
@@ -67,8 +67,11 @@ module LaunchDarkly
|
|
|
67
67
|
error_info
|
|
68
68
|
)
|
|
69
69
|
else
|
|
70
|
-
|
|
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.
|
|
71
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
|
|
72
75
|
end
|
|
73
76
|
rescue StandardError => e
|
|
74
77
|
Impl::Util.log_exception(@config.logger, "Exception while polling", e)
|
data/lib/ldclient-rb/ldclient.rb
CHANGED
|
@@ -33,10 +33,6 @@ module LaunchDarkly
|
|
|
33
33
|
|
|
34
34
|
def_delegators :@config, :logger
|
|
35
35
|
|
|
36
|
-
# @!method flush
|
|
37
|
-
# Delegates to {LaunchDarkly::EventProcessorMethods#flush}.
|
|
38
|
-
def_delegator :@event_processor, :flush
|
|
39
|
-
|
|
40
36
|
# @!method data_store_status_provider
|
|
41
37
|
# Delegates to the data system {LaunchDarkly::Impl::DataSystem#data_store_status_provider}.
|
|
42
38
|
# @return [LaunchDarkly::Interfaces::DataStore::StatusProvider]
|
|
@@ -79,6 +75,12 @@ module LaunchDarkly
|
|
|
79
75
|
config.instance_id = SecureRandom.uuid
|
|
80
76
|
@config = config
|
|
81
77
|
|
|
78
|
+
# The pid of the process that owns this client's background threads. A child process that
|
|
79
|
+
# inherits this client after a fork has a different pid. See #check_forked.
|
|
80
|
+
@owner_pid = Process.pid
|
|
81
|
+
# The pid of the process in which the fork warning was last logged, or nil.
|
|
82
|
+
@fork_warned_pid = Concurrent::AtomicReference.new(nil)
|
|
83
|
+
|
|
82
84
|
start_up(wait_for_sec)
|
|
83
85
|
end
|
|
84
86
|
|
|
@@ -97,6 +99,10 @@ module LaunchDarkly
|
|
|
97
99
|
# reason, it is recommended that any listener or hook integrations be added postfork unless you are certain it can
|
|
98
100
|
# survive the forking process.
|
|
99
101
|
#
|
|
102
|
+
# If the SDK is used in a forked child process before this method is called, it logs a warning once per process.
|
|
103
|
+
# The warning says that flag updates and analytics events will not work until `postfork` is called. Calling this
|
|
104
|
+
# method clears that condition for the current process.
|
|
105
|
+
#
|
|
100
106
|
# @param wait_for_sec [Float] maximum time (in seconds) to wait for initialization
|
|
101
107
|
#
|
|
102
108
|
def postfork(wait_for_sec = 5)
|
|
@@ -105,9 +111,51 @@ module LaunchDarkly
|
|
|
105
111
|
@big_segment_store_manager = nil
|
|
106
112
|
@flag_tracker = nil
|
|
107
113
|
|
|
114
|
+
@owner_pid = Process.pid
|
|
115
|
+
@fork_warned_pid.set(nil)
|
|
116
|
+
|
|
108
117
|
start_up(wait_for_sec)
|
|
109
118
|
end
|
|
110
119
|
|
|
120
|
+
FORK_WARNING_MESSAGE = "[LDClient] This process was forked after the LDClient was created. Background threads do not " +
|
|
121
|
+
"survive a fork, so flag updates and analytics events will not work in this process. Call LDClient#postfork after forking."
|
|
122
|
+
private_constant :FORK_WARNING_MESSAGE
|
|
123
|
+
|
|
124
|
+
#
|
|
125
|
+
# Log a warning once per process if this client is used in a process that was forked after the client was created.
|
|
126
|
+
#
|
|
127
|
+
# Ruby threads do not survive a fork. A child process that inherits a client has no stream thread, no event
|
|
128
|
+
# dispatcher thread, and no timer tasks, so flag updates and analytics events stop working. The SDK does not
|
|
129
|
+
# repair this by itself; the application must call {#postfork}.
|
|
130
|
+
#
|
|
131
|
+
# The check compares the current pid with the pid recorded in the constructor or in {#postfork}. It is a single
|
|
132
|
+
# `getpid` call, which is cheap compared to a flag evaluation. The "already warned" state is stored as the pid in
|
|
133
|
+
# which the warning was logged, not as a boolean. A boolean would be copied into every child on fork, so a
|
|
134
|
+
# grandchild process would inherit "already warned" from its parent and never get its own warning.
|
|
135
|
+
#
|
|
136
|
+
private def check_forked
|
|
137
|
+
pid = Process.pid
|
|
138
|
+
return if pid == @owner_pid
|
|
139
|
+
return unless fork_breaks_client?
|
|
140
|
+
|
|
141
|
+
if @fork_warned_pid.get_and_set(pid) != pid
|
|
142
|
+
@config.logger.warn { FORK_WARNING_MESSAGE }
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
#
|
|
147
|
+
# Return true if this configuration needs background threads. Offline mode has none. LDD mode with events
|
|
148
|
+
# disabled reads flags from the persistent store and sends nothing, so a fork does not break it.
|
|
149
|
+
#
|
|
150
|
+
# @return [Boolean]
|
|
151
|
+
#
|
|
152
|
+
private def fork_breaks_client?
|
|
153
|
+
return false if @config.offline?
|
|
154
|
+
return false if @config.use_ldd? && !@config.send_events
|
|
155
|
+
|
|
156
|
+
true
|
|
157
|
+
end
|
|
158
|
+
|
|
111
159
|
private def start_up(wait_for_sec)
|
|
112
160
|
environment_metadata = get_environment_metadata
|
|
113
161
|
plugin_hooks = get_plugin_hooks(environment_metadata)
|
|
@@ -281,6 +329,7 @@ module LaunchDarkly
|
|
|
281
329
|
# @return the variation for the provided context, or the default value if there's an error
|
|
282
330
|
#
|
|
283
331
|
def variation(key, context, default)
|
|
332
|
+
check_forked
|
|
284
333
|
context = Impl::Context::make_context(context)
|
|
285
334
|
result = evaluate_with_hooks(key, context, default, :variation) do
|
|
286
335
|
detail, _, _ = variation_with_flag(key, context, default)
|
|
@@ -314,6 +363,7 @@ module LaunchDarkly
|
|
|
314
363
|
# @return [EvaluationDetail] an object describing the result
|
|
315
364
|
#
|
|
316
365
|
def variation_detail(key, context, default)
|
|
366
|
+
check_forked
|
|
317
367
|
context = Impl::Context::make_context(context)
|
|
318
368
|
result = evaluate_with_hooks(key, context, default, :variation_detail) do
|
|
319
369
|
detail, _, _ = evaluate_internal(key, context, default, true)
|
|
@@ -439,6 +489,7 @@ module LaunchDarkly
|
|
|
439
489
|
# @return [Array<Symbol, Interfaces::Migrations::OpTracker>]
|
|
440
490
|
#
|
|
441
491
|
def migration_variation(key, context, default_stage)
|
|
492
|
+
check_forked
|
|
442
493
|
unless Migrations::VALID_STAGES.include? default_stage
|
|
443
494
|
@config.logger.error { "[LDClient] default_stage #{default_stage} is not a valid stage; continuing with 'off' as default" }
|
|
444
495
|
default_stage = Migrations::STAGE_OFF
|
|
@@ -480,6 +531,7 @@ module LaunchDarkly
|
|
|
480
531
|
# @return [void]
|
|
481
532
|
#
|
|
482
533
|
def identify(context)
|
|
534
|
+
check_forked
|
|
483
535
|
context = LaunchDarkly::Impl::Context.make_context(context)
|
|
484
536
|
unless context.valid?
|
|
485
537
|
@config.logger.warn("Identify called with invalid context: #{context.error}")
|
|
@@ -516,6 +568,7 @@ module LaunchDarkly
|
|
|
516
568
|
# @return [void]
|
|
517
569
|
#
|
|
518
570
|
def track(event_name, context, data = nil, metric_value = nil)
|
|
571
|
+
check_forked
|
|
519
572
|
context = LaunchDarkly::Impl::Context.make_context(context)
|
|
520
573
|
unless context.valid?
|
|
521
574
|
@config.logger.warn("Track called with invalid context: #{context.error}")
|
|
@@ -536,6 +589,7 @@ module LaunchDarkly
|
|
|
536
589
|
# @param tracker [LaunchDarkly::Interfaces::Migrations::OpTracker]
|
|
537
590
|
#
|
|
538
591
|
def track_migration_op(tracker)
|
|
592
|
+
check_forked
|
|
539
593
|
unless tracker.is_a? LaunchDarkly::Interfaces::Migrations::OpTracker
|
|
540
594
|
@config.logger.error { "invalid op tracker received in track_migration_op" }
|
|
541
595
|
return
|
|
@@ -571,6 +625,8 @@ module LaunchDarkly
|
|
|
571
625
|
def all_flags_state(context, options={})
|
|
572
626
|
return FeatureFlagsState.new(false) if @config.offline?
|
|
573
627
|
|
|
628
|
+
check_forked
|
|
629
|
+
|
|
574
630
|
unless initialized?
|
|
575
631
|
if @data_system.store.initialized?
|
|
576
632
|
@config.logger.warn { "Called all_flags_state before client initialization; using last known values from data store" }
|
|
@@ -628,11 +684,24 @@ module LaunchDarkly
|
|
|
628
684
|
state
|
|
629
685
|
end
|
|
630
686
|
|
|
687
|
+
#
|
|
688
|
+
# Tells the client that all pending analytics events should be delivered as soon as possible.
|
|
689
|
+
#
|
|
690
|
+
# Delegates to {LaunchDarkly::EventProcessorMethods#flush}.
|
|
691
|
+
#
|
|
692
|
+
# @return [void]
|
|
693
|
+
#
|
|
694
|
+
def flush
|
|
695
|
+
check_forked
|
|
696
|
+
@event_processor.flush
|
|
697
|
+
end
|
|
698
|
+
|
|
631
699
|
#
|
|
632
700
|
# Releases all network connections and other resources held by the client, making it no longer usable.
|
|
633
701
|
#
|
|
634
702
|
# @return [void]
|
|
635
703
|
def close
|
|
704
|
+
check_forked
|
|
636
705
|
@config.logger.info { "[LDClient] Closing LaunchDarkly client..." }
|
|
637
706
|
@data_system.stop
|
|
638
707
|
@event_processor.stop
|
|
@@ -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,14 +1,14 @@
|
|
|
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.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- LaunchDarkly
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: aws-sdk-dynamodb
|