launchdarkly-server-sdk 7.3.2 → 8.1.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 +3 -68
- data/lib/ldclient-rb/context.rb +65 -52
- data/lib/ldclient-rb/evaluation_detail.rb +5 -1
- data/lib/ldclient-rb/events.rb +78 -7
- data/lib/ldclient-rb/impl/big_segments.rb +1 -1
- data/lib/ldclient-rb/impl/context_filter.rb +1 -1
- data/lib/ldclient-rb/impl/event_types.rb +61 -3
- data/lib/ldclient-rb/impl/migrations/migrator.rb +287 -0
- data/lib/ldclient-rb/impl/migrations/tracker.rb +136 -0
- data/lib/ldclient-rb/impl/model/feature_flag.rb +23 -0
- data/lib/ldclient-rb/impl/sampler.rb +25 -0
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +84 -15
- data/lib/ldclient-rb/interfaces.rb +97 -0
- data/lib/ldclient-rb/ldclient.rb +106 -16
- data/lib/ldclient-rb/migrations.rb +230 -0
- data/lib/ldclient-rb/reference.rb +11 -0
- data/lib/ldclient-rb/util.rb +63 -0
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +1 -0
- metadata +8 -4
@@ -788,5 +788,102 @@ module LaunchDarkly
|
|
788
788
|
end
|
789
789
|
end
|
790
790
|
end
|
791
|
+
|
792
|
+
#
|
793
|
+
# Namespace for feature-flag based technology migration support.
|
794
|
+
#
|
795
|
+
module Migrations
|
796
|
+
#
|
797
|
+
# A migrator is the interface through which migration support is executed. A migrator is configured through the
|
798
|
+
# {LaunchDarkly::Migrations::MigratorBuilder} class.
|
799
|
+
#
|
800
|
+
module Migrator
|
801
|
+
#
|
802
|
+
# Uses the provided flag key and context to execute a migration-backed read operation.
|
803
|
+
#
|
804
|
+
# @param key [String]
|
805
|
+
# @param context [LaunchDarkly::LDContext]
|
806
|
+
# @param default_stage [Symbol]
|
807
|
+
# @param payload [Object, nil]
|
808
|
+
#
|
809
|
+
# @return [LaunchDarkly::Migrations::OperationResult]
|
810
|
+
#
|
811
|
+
def read(key, context, default_stage, payload = nil) end
|
812
|
+
|
813
|
+
#
|
814
|
+
# Uses the provided flag key and context to execute a migration-backed write operation.
|
815
|
+
#
|
816
|
+
# @param key [String]
|
817
|
+
# @param context [LaunchDarkly::LDContext]
|
818
|
+
# @param default_stage [Symbol]
|
819
|
+
# @param payload [Object, nil]
|
820
|
+
#
|
821
|
+
# @return [LaunchDarkly::Migrations::WriteResult]
|
822
|
+
#
|
823
|
+
def write(key, context, default_stage, payload = nil) end
|
824
|
+
end
|
825
|
+
|
826
|
+
#
|
827
|
+
# An OpTracker is responsible for managing the collection of measurements that which a user might wish to record
|
828
|
+
# throughout a migration-assisted operation.
|
829
|
+
#
|
830
|
+
# Example measurements include latency, errors, and consistency.
|
831
|
+
#
|
832
|
+
# This data can be provided to the {LaunchDarkly::LDClient.track_migration_op} method to relay this metric
|
833
|
+
# information upstream to LaunchDarkly services.
|
834
|
+
#
|
835
|
+
module OpTracker
|
836
|
+
#
|
837
|
+
# Sets the migration related operation associated with these tracking measurements.
|
838
|
+
#
|
839
|
+
# @param [Symbol] op The read or write operation symbol.
|
840
|
+
#
|
841
|
+
def operation(op) end
|
842
|
+
|
843
|
+
#
|
844
|
+
# Allows recording which origins were called during a migration.
|
845
|
+
#
|
846
|
+
# @param [Symbol] origin Designation for the old or new origin.
|
847
|
+
#
|
848
|
+
def invoked(origin) end
|
849
|
+
|
850
|
+
#
|
851
|
+
# Allows recording the results of a consistency check.
|
852
|
+
#
|
853
|
+
# This method accepts a callable which should take no parameters and return a single boolean to represent the
|
854
|
+
# consistency check results for a read operation.
|
855
|
+
#
|
856
|
+
# A callable is provided in case sampling rules do not require consistency checking to run. In this case, we can
|
857
|
+
# avoid the overhead of a function by not using the callable.
|
858
|
+
#
|
859
|
+
# @param [#call] is_consistent closure to return result of comparison check
|
860
|
+
#
|
861
|
+
def consistent(is_consistent) end
|
862
|
+
|
863
|
+
#
|
864
|
+
# Allows recording whether an error occurred during the operation.
|
865
|
+
#
|
866
|
+
# @param [Symbol] origin Designation for the old or new origin.
|
867
|
+
#
|
868
|
+
def error(origin) end
|
869
|
+
|
870
|
+
#
|
871
|
+
# Allows tracking the recorded latency for an individual operation.
|
872
|
+
#
|
873
|
+
# @param [Symbol] origin Designation for the old or new origin.
|
874
|
+
# @param [Float] duration Duration measurement in milliseconds (ms).
|
875
|
+
#
|
876
|
+
def latency(origin, duration) end
|
877
|
+
|
878
|
+
#
|
879
|
+
# Creates an instance of {LaunchDarkly::Impl::MigrationOpEventData}.
|
880
|
+
#
|
881
|
+
# @return [LaunchDarkly::Impl::MigrationOpEvent, String] A migration op event or a string describing the error.
|
882
|
+
# failure.
|
883
|
+
#
|
884
|
+
def build
|
885
|
+
end
|
886
|
+
end
|
887
|
+
end
|
791
888
|
end
|
792
889
|
end
|
data/lib/ldclient-rb/ldclient.rb
CHANGED
@@ -6,8 +6,10 @@ require "ldclient-rb/impl/diagnostic_events"
|
|
6
6
|
require "ldclient-rb/impl/evaluator"
|
7
7
|
require "ldclient-rb/impl/flag_tracker"
|
8
8
|
require "ldclient-rb/impl/store_client_wrapper"
|
9
|
+
require "ldclient-rb/impl/migrations/tracker"
|
9
10
|
require "concurrent/atomics"
|
10
11
|
require "digest/sha1"
|
12
|
+
require "forwardable"
|
11
13
|
require "logger"
|
12
14
|
require "benchmark"
|
13
15
|
require "json"
|
@@ -20,6 +22,10 @@ module LaunchDarkly
|
|
20
22
|
#
|
21
23
|
class LDClient
|
22
24
|
include Impl
|
25
|
+
extend Forwardable
|
26
|
+
|
27
|
+
def_delegators :@config, :logger
|
28
|
+
|
23
29
|
#
|
24
30
|
# Creates a new client instance that connects to LaunchDarkly. A custom
|
25
31
|
# configuration parameter can also supplied to specify advanced options,
|
@@ -149,7 +155,7 @@ module LaunchDarkly
|
|
149
155
|
# @return [String, nil] a hash string or nil if the provided context was invalid
|
150
156
|
#
|
151
157
|
def secure_mode_hash(context)
|
152
|
-
context = Impl::Context
|
158
|
+
context = Impl::Context.make_context(context)
|
153
159
|
unless context.valid?
|
154
160
|
@config.logger.warn("secure_mode_hash called with invalid context: #{context.error}")
|
155
161
|
return nil
|
@@ -189,10 +195,11 @@ module LaunchDarkly
|
|
189
195
|
# @param default the default value of the flag; this is used if there is an error
|
190
196
|
# condition making it impossible to find or evaluate the flag
|
191
197
|
#
|
192
|
-
# @return the variation for the provided context, or the default value if there's an
|
198
|
+
# @return the variation for the provided context, or the default value if there's an error
|
193
199
|
#
|
194
200
|
def variation(key, context, default)
|
195
|
-
|
201
|
+
detail, _, _, = variation_with_flag(key, context, default)
|
202
|
+
detail.value
|
196
203
|
end
|
197
204
|
|
198
205
|
#
|
@@ -219,7 +226,43 @@ module LaunchDarkly
|
|
219
226
|
# @return [EvaluationDetail] an object describing the result
|
220
227
|
#
|
221
228
|
def variation_detail(key, context, default)
|
222
|
-
evaluate_internal(key, context, default, true)
|
229
|
+
detail, _, _ = evaluate_internal(key, context, default, true)
|
230
|
+
detail
|
231
|
+
end
|
232
|
+
|
233
|
+
#
|
234
|
+
# This method returns the migration stage of the migration feature flag for the given evaluation context.
|
235
|
+
#
|
236
|
+
# This method returns the default stage if there is an error or the flag does not exist. If the default stage is not
|
237
|
+
# a valid stage, then a default stage of 'off' will be used instead.
|
238
|
+
#
|
239
|
+
# @param key [String]
|
240
|
+
# @param context [LDContext]
|
241
|
+
# @param default_stage [Symbol]
|
242
|
+
#
|
243
|
+
# @return [Array<Symbol, Interfaces::Migrations::OpTracker>]
|
244
|
+
#
|
245
|
+
def migration_variation(key, context, default_stage)
|
246
|
+
unless Migrations::VALID_STAGES.include? default_stage
|
247
|
+
@config.logger.error { "[LDClient] default_stage #{default_stage} is not a valid stage; continuing with 'off' as default" }
|
248
|
+
default_stage = Migrations::STAGE_OFF
|
249
|
+
end
|
250
|
+
|
251
|
+
context = Impl::Context::make_context(context)
|
252
|
+
detail, flag, _ = variation_with_flag(key, context, default_stage.to_s)
|
253
|
+
|
254
|
+
stage = detail.value
|
255
|
+
stage = stage.to_sym if stage.respond_to? :to_sym
|
256
|
+
|
257
|
+
if Migrations::VALID_STAGES.include?(stage)
|
258
|
+
tracker = Impl::Migrations::OpTracker.new(@config.logger, key, flag, context, detail, default_stage)
|
259
|
+
return stage, tracker
|
260
|
+
end
|
261
|
+
|
262
|
+
detail = LaunchDarkly::Impl::Evaluator.error_result(LaunchDarkly::EvaluationReason::ERROR_WRONG_TYPE, default_stage.to_s)
|
263
|
+
tracker = Impl::Migrations::OpTracker.new(@config.logger, key, flag, context, detail, default_stage)
|
264
|
+
|
265
|
+
[default_stage, tracker]
|
223
266
|
end
|
224
267
|
|
225
268
|
#
|
@@ -282,6 +325,32 @@ module LaunchDarkly
|
|
282
325
|
@event_processor.record_custom_event(context, event_name, data, metric_value)
|
283
326
|
end
|
284
327
|
|
328
|
+
#
|
329
|
+
# Tracks the results of a migrations operation. This event includes measurements which can be used to enhance the
|
330
|
+
# observability of a migration within the LaunchDarkly UI.
|
331
|
+
#
|
332
|
+
# This event should be generated through {Interfaces::Migrations::OpTracker}. If you are using the
|
333
|
+
# {Interfaces::Migrations::Migrator} to handle migrations, this event will be created and emitted
|
334
|
+
# automatically.
|
335
|
+
#
|
336
|
+
# @param tracker [LaunchDarkly::Interfaces::Migrations::OpTracker]
|
337
|
+
#
|
338
|
+
def track_migration_op(tracker)
|
339
|
+
unless tracker.is_a? LaunchDarkly::Interfaces::Migrations::OpTracker
|
340
|
+
@config.logger.error { "invalid op tracker received in track_migration_op" }
|
341
|
+
return
|
342
|
+
end
|
343
|
+
|
344
|
+
event = tracker.build
|
345
|
+
if event.is_a? String
|
346
|
+
@config.logger.error { "[LDClient] Error occurred generating migration op event; #{event}" }
|
347
|
+
return
|
348
|
+
end
|
349
|
+
|
350
|
+
|
351
|
+
@event_processor.record_migration_op_event(event)
|
352
|
+
end
|
353
|
+
|
285
354
|
#
|
286
355
|
# Returns a {FeatureFlagsState} object that encapsulates the state of all feature flags for a given context,
|
287
356
|
# including the flag values and also metadata that can be used on the front end. This method does not
|
@@ -431,24 +500,41 @@ module LaunchDarkly
|
|
431
500
|
end
|
432
501
|
end
|
433
502
|
|
503
|
+
#
|
504
|
+
# @param key [String]
|
434
505
|
# @param context [Hash, LDContext]
|
435
|
-
# @
|
506
|
+
# @param default [Object]
|
507
|
+
#
|
508
|
+
# @return [Array<EvaluationDetail, [LaunchDarkly::Impl::Model::FeatureFlag, nil], [String, nil]>]
|
509
|
+
#
|
510
|
+
def variation_with_flag(key, context, default)
|
511
|
+
evaluate_internal(key, context, default, false)
|
512
|
+
end
|
513
|
+
|
514
|
+
#
|
515
|
+
# @param key [String]
|
516
|
+
# @param context [Hash, LDContext]
|
517
|
+
# @param default [Object]
|
518
|
+
# @param with_reasons [Boolean]
|
519
|
+
#
|
520
|
+
# @return [Array<EvaluationDetail, [LaunchDarkly::Impl::Model::FeatureFlag, nil], [String, nil]>]
|
521
|
+
#
|
436
522
|
def evaluate_internal(key, context, default, with_reasons)
|
437
523
|
if @config.offline?
|
438
|
-
return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
|
524
|
+
return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default), nil, nil
|
439
525
|
end
|
440
526
|
|
441
527
|
if context.nil?
|
442
528
|
@config.logger.error { "[LDClient] Must specify context" }
|
443
529
|
detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
|
444
|
-
return detail
|
530
|
+
return detail, nil, "no context provided"
|
445
531
|
end
|
446
532
|
|
447
533
|
context = Impl::Context::make_context(context)
|
448
534
|
unless context.valid?
|
449
535
|
@config.logger.error { "[LDClient] Context was invalid for evaluation of flag '#{key}' (#{context.error}); returning default value" }
|
450
536
|
detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
|
451
|
-
return detail
|
537
|
+
return detail, nil, context.error
|
452
538
|
end
|
453
539
|
|
454
540
|
unless initialized?
|
@@ -458,7 +544,7 @@ module LaunchDarkly
|
|
458
544
|
@config.logger.error { "[LDClient] Client has not finished initializing; feature store unavailable, returning default value" }
|
459
545
|
detail = Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
|
460
546
|
record_unknown_flag_eval(key, context, default, detail.reason, with_reasons)
|
461
|
-
return detail
|
547
|
+
return detail, nil, "client not initialized"
|
462
548
|
end
|
463
549
|
end
|
464
550
|
|
@@ -472,7 +558,7 @@ module LaunchDarkly
|
|
472
558
|
@config.logger.info { "[LDClient] Unknown feature flag \"#{key}\". Returning default value" }
|
473
559
|
detail = Evaluator.error_result(EvaluationReason::ERROR_FLAG_NOT_FOUND, default)
|
474
560
|
record_unknown_flag_eval(key, context, default, detail.reason, with_reasons)
|
475
|
-
return detail
|
561
|
+
return detail, nil, "feature flag not found"
|
476
562
|
end
|
477
563
|
|
478
564
|
begin
|
@@ -487,12 +573,12 @@ module LaunchDarkly
|
|
487
573
|
detail = EvaluationDetail.new(default, nil, detail.reason)
|
488
574
|
end
|
489
575
|
record_flag_eval(feature, context, detail, default, with_reasons)
|
490
|
-
detail
|
576
|
+
[detail, feature, nil]
|
491
577
|
rescue => exn
|
492
578
|
Util.log_exception(@config.logger, "Error evaluating feature flag \"#{key}\"", exn)
|
493
579
|
detail = Evaluator.error_result(EvaluationReason::ERROR_EXCEPTION, default)
|
494
580
|
record_flag_eval_error(feature, context, default, detail.reason, with_reasons)
|
495
|
-
detail
|
581
|
+
[detail, feature, exn.to_s]
|
496
582
|
end
|
497
583
|
end
|
498
584
|
|
@@ -508,7 +594,9 @@ module LaunchDarkly
|
|
508
594
|
default,
|
509
595
|
add_experiment_data || flag[:trackEvents] || false,
|
510
596
|
flag[:debugEventsUntilDate],
|
511
|
-
nil
|
597
|
+
nil,
|
598
|
+
flag[:samplingRatio],
|
599
|
+
!!flag[:excludeFromSummaries]
|
512
600
|
)
|
513
601
|
end
|
514
602
|
|
@@ -524,13 +612,15 @@ module LaunchDarkly
|
|
524
612
|
nil,
|
525
613
|
add_experiment_data || prereq_flag[:trackEvents] || false,
|
526
614
|
prereq_flag[:debugEventsUntilDate],
|
527
|
-
prereq_of_flag[:key]
|
615
|
+
prereq_of_flag[:key],
|
616
|
+
prereq_flag[:samplingRatio],
|
617
|
+
!!prereq_flag[:excludeFromSummaries]
|
528
618
|
)
|
529
619
|
end
|
530
620
|
|
531
621
|
private def record_flag_eval_error(flag, context, default, reason, with_reasons)
|
532
622
|
@event_processor.record_eval_event(context, flag[:key], flag[:version], nil, default, with_reasons ? reason : nil, default,
|
533
|
-
flag[:trackEvents], flag[:debugEventsUntilDate], nil)
|
623
|
+
flag[:trackEvents], flag[:debugEventsUntilDate], nil, flag[:samplingRatio], !!flag[:excludeFromSummaries])
|
534
624
|
end
|
535
625
|
|
536
626
|
#
|
@@ -542,7 +632,7 @@ module LaunchDarkly
|
|
542
632
|
#
|
543
633
|
private def record_unknown_flag_eval(flag_key, context, default, reason, with_reasons)
|
544
634
|
@event_processor.record_eval_event(context, flag_key, nil, nil, default, with_reasons ? reason : nil, default,
|
545
|
-
false, nil, nil)
|
635
|
+
false, nil, nil, 1, false)
|
546
636
|
end
|
547
637
|
|
548
638
|
private def experiment?(flag, reason)
|
@@ -0,0 +1,230 @@
|
|
1
|
+
require 'ldclient-rb/impl/migrations/migrator'
|
2
|
+
|
3
|
+
module LaunchDarkly
|
4
|
+
#
|
5
|
+
# Namespace for feature-flag based technology migration support.
|
6
|
+
#
|
7
|
+
module Migrations
|
8
|
+
# Symbol representing the old origin, or the old technology source you are migrating away from.
|
9
|
+
ORIGIN_OLD = :old
|
10
|
+
# Symbol representing the new origin, or the new technology source you are migrating towards.
|
11
|
+
ORIGIN_NEW = :new
|
12
|
+
|
13
|
+
# Symbol defining a read-related operation
|
14
|
+
OP_READ = :read
|
15
|
+
# Symbol defining a write-related operation
|
16
|
+
OP_WRITE = :write
|
17
|
+
|
18
|
+
STAGE_OFF = :off
|
19
|
+
STAGE_DUALWRITE = :dualwrite
|
20
|
+
STAGE_SHADOW = :shadow
|
21
|
+
STAGE_LIVE = :live
|
22
|
+
STAGE_RAMPDOWN = :rampdown
|
23
|
+
STAGE_COMPLETE = :complete
|
24
|
+
|
25
|
+
VALID_OPERATIONS = [
|
26
|
+
OP_READ,
|
27
|
+
OP_WRITE,
|
28
|
+
]
|
29
|
+
|
30
|
+
VALID_ORIGINS = [
|
31
|
+
ORIGIN_OLD,
|
32
|
+
ORIGIN_NEW,
|
33
|
+
]
|
34
|
+
|
35
|
+
VALID_STAGES = [
|
36
|
+
STAGE_OFF,
|
37
|
+
STAGE_DUALWRITE,
|
38
|
+
STAGE_SHADOW,
|
39
|
+
STAGE_LIVE,
|
40
|
+
STAGE_RAMPDOWN,
|
41
|
+
STAGE_COMPLETE,
|
42
|
+
]
|
43
|
+
|
44
|
+
#
|
45
|
+
# The OperationResult wraps the {LaunchDarkly::Result} class to tie an operation origin to a result.
|
46
|
+
#
|
47
|
+
class OperationResult
|
48
|
+
extend Forwardable
|
49
|
+
def_delegators :@result, :value, :error, :exception, :success?
|
50
|
+
|
51
|
+
#
|
52
|
+
# @param origin [Symbol]
|
53
|
+
# @param result [LaunchDarkly::Result]
|
54
|
+
#
|
55
|
+
def initialize(origin, result)
|
56
|
+
@origin = origin
|
57
|
+
@result = result
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# @return [Symbol] The origin this result is associated with.
|
62
|
+
#
|
63
|
+
attr_reader :origin
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# A write result contains the operation results against both the authoritative and non-authoritative origins.
|
68
|
+
#
|
69
|
+
# Authoritative writes are always executed first. In the event of a failure, the non-authoritative write will not
|
70
|
+
# be executed, resulting in a nil value in the final WriteResult.
|
71
|
+
#
|
72
|
+
class WriteResult
|
73
|
+
#
|
74
|
+
# @param authoritative [OperationResult]
|
75
|
+
# @param nonauthoritative [OperationResult, nil]
|
76
|
+
#
|
77
|
+
def initialize(authoritative, nonauthoritative = nil)
|
78
|
+
@authoritative = authoritative
|
79
|
+
@nonauthoritative = nonauthoritative
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Returns the operation result for the authoritative origin.
|
84
|
+
#
|
85
|
+
# @return [OperationResult]
|
86
|
+
#
|
87
|
+
attr_reader :authoritative
|
88
|
+
|
89
|
+
#
|
90
|
+
# Returns the operation result for the non-authoritative origin.
|
91
|
+
#
|
92
|
+
# This result might be nil as the non-authoritative write does not execute in every stage, and will not execute
|
93
|
+
# if the authoritative write failed.
|
94
|
+
#
|
95
|
+
# @return [OperationResult, nil]
|
96
|
+
#
|
97
|
+
attr_reader :nonauthoritative
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
#
|
102
|
+
# The migration builder is used to configure and construct an instance of a
|
103
|
+
# {LaunchDarkly::Interfaces::Migrations::Migrator}. This migrator can be used to perform LaunchDarkly assisted
|
104
|
+
# technology migrations through the use of migration-based feature flags.
|
105
|
+
#
|
106
|
+
class MigratorBuilder
|
107
|
+
EXECUTION_SERIAL = :serial
|
108
|
+
EXECUTION_RANDOM = :random
|
109
|
+
EXECUTION_PARALLEL = :parallel
|
110
|
+
|
111
|
+
VALID_EXECUTION_ORDERS = [EXECUTION_SERIAL, EXECUTION_RANDOM, EXECUTION_PARALLEL]
|
112
|
+
private_constant :VALID_EXECUTION_ORDERS
|
113
|
+
|
114
|
+
#
|
115
|
+
# @param client [LaunchDarkly::LDClient]
|
116
|
+
#
|
117
|
+
def initialize(client)
|
118
|
+
@client = client
|
119
|
+
|
120
|
+
# Default settings as required by the spec
|
121
|
+
@read_execution_order = EXECUTION_PARALLEL
|
122
|
+
@measure_latency = true
|
123
|
+
@measure_errors = true
|
124
|
+
|
125
|
+
@read_config = nil # @type [LaunchDarkly::Impl::Migrations::MigrationConfig, nil]
|
126
|
+
@write_config = nil # @type [LaunchDarkly::Impl::Migrations::MigrationConfig, nil]
|
127
|
+
end
|
128
|
+
|
129
|
+
#
|
130
|
+
# The read execution order influences the parallelism and execution order for read operations involving multiple
|
131
|
+
# origins.
|
132
|
+
#
|
133
|
+
# @param order [Symbol]
|
134
|
+
#
|
135
|
+
def read_execution_order(order)
|
136
|
+
return unless VALID_EXECUTION_ORDERS.include? order
|
137
|
+
|
138
|
+
@read_execution_order = order
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# Enable or disable latency tracking for migration operations. This latency information can be sent upstream to
|
143
|
+
# LaunchDarkly to enhance migration visibility.
|
144
|
+
#
|
145
|
+
# @param enabled [Boolean]
|
146
|
+
#
|
147
|
+
def track_latency(enabled)
|
148
|
+
@measure_latency = !!enabled
|
149
|
+
end
|
150
|
+
|
151
|
+
#
|
152
|
+
# Enable or disable error tracking for migration operations. This error information can be sent upstream to
|
153
|
+
# LaunchDarkly to enhance migration visibility.
|
154
|
+
#
|
155
|
+
# @param enabled [Boolean]
|
156
|
+
#
|
157
|
+
def track_errors(enabled)
|
158
|
+
@measure_errors = !!enabled
|
159
|
+
end
|
160
|
+
|
161
|
+
#
|
162
|
+
# Read can be used to configure the migration-read behavior of the resulting
|
163
|
+
# {LaunchDarkly::Interfaces::Migrations::Migrator} instance.
|
164
|
+
#
|
165
|
+
# Users are required to provide two different read methods -- one to read from the old migration origin, and one
|
166
|
+
# to read from the new origin. Additionally, customers can opt-in to consistency tracking by providing a
|
167
|
+
# comparison function.
|
168
|
+
#
|
169
|
+
# Depending on the migration stage, one or both of these read methods may be called.
|
170
|
+
#
|
171
|
+
# The read methods should accept a single nullable parameter. This parameter is a payload passed through the
|
172
|
+
# {LaunchDarkly::Interfaces::Migrations::Migrator#read} method. This method should return a {LaunchDarkly::Result}
|
173
|
+
# instance.
|
174
|
+
#
|
175
|
+
# The consistency method should accept 2 parameters of any type. These parameters are the results of executing the
|
176
|
+
# read operation against the old and new origins. If both operations were successful, the consistency method will
|
177
|
+
# be invoked. This method should return true if the two parameters are equal, or false otherwise.
|
178
|
+
#
|
179
|
+
# @param old_read [#call]
|
180
|
+
# @param new_read [#call]
|
181
|
+
# @param comparison [#call, nil]
|
182
|
+
#
|
183
|
+
def read(old_read, new_read, comparison = nil)
|
184
|
+
return unless old_read.respond_to?(:call) && old_read.arity == 1
|
185
|
+
return unless new_read.respond_to?(:call) && new_read.arity == 1
|
186
|
+
return unless comparison.nil? || (comparison.respond_to?(:call) && comparison.arity == 2)
|
187
|
+
|
188
|
+
@read_config = LaunchDarkly::Impl::Migrations::MigrationConfig.new(old_read, new_read, comparison)
|
189
|
+
end
|
190
|
+
|
191
|
+
#
|
192
|
+
# Write can be used to configure the migration-write behavior of the resulting
|
193
|
+
# {LaunchDarkly::Interfaces::Migrations::Migrator} instance.
|
194
|
+
#
|
195
|
+
# Users are required to provide two different write methods -- one to write to the old migration origin, and one
|
196
|
+
# to write to the new origin.
|
197
|
+
#
|
198
|
+
# Depending on the migration stage, one or both of these write methods may be called.
|
199
|
+
#
|
200
|
+
# The write methods should accept a single nullable parameter. This parameter is a payload passed through the
|
201
|
+
# {LaunchDarkly::Interfaces::Migrations::Migrator#write} method. This method should return a {LaunchDarkly::Result}
|
202
|
+
# instance.
|
203
|
+
#
|
204
|
+
# @param old_write [#call]
|
205
|
+
# @param new_write [#call]
|
206
|
+
#
|
207
|
+
def write(old_write, new_write)
|
208
|
+
return unless old_write.respond_to?(:call) && old_write.arity == 1
|
209
|
+
return unless new_write.respond_to?(:call) && new_write.arity == 1
|
210
|
+
|
211
|
+
@write_config = LaunchDarkly::Impl::Migrations::MigrationConfig.new(old_write, new_write, nil)
|
212
|
+
end
|
213
|
+
|
214
|
+
#
|
215
|
+
# Build constructs a {LaunchDarkly::Interfaces::Migrations::Migrator} instance to support migration-based reads
|
216
|
+
# and writes. A string describing any failure conditions will be returned if the build fails.
|
217
|
+
#
|
218
|
+
# @return [LaunchDarkly::Interfaces::Migrations::Migrator, string]
|
219
|
+
#
|
220
|
+
def build
|
221
|
+
return "client not provided" if @client.nil?
|
222
|
+
return "read configuration not provided" if @read_config.nil?
|
223
|
+
return "write configuration not provided" if @write_config.nil?
|
224
|
+
|
225
|
+
LaunchDarkly::Impl::Migrations::Migrator.new(@client, @read_execution_order, @read_config, @write_config, @measure_latency, @measure_errors)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
end
|
@@ -109,6 +109,8 @@ module LaunchDarkly
|
|
109
109
|
end
|
110
110
|
private_class_method :new
|
111
111
|
|
112
|
+
protected attr_reader :components
|
113
|
+
|
112
114
|
#
|
113
115
|
# Creates a Reference from a string. For the supported syntax and examples,
|
114
116
|
# see comments on the Reference type.
|
@@ -227,6 +229,15 @@ module LaunchDarkly
|
|
227
229
|
@components[index]
|
228
230
|
end
|
229
231
|
|
232
|
+
def ==(other)
|
233
|
+
self.error == other.error && self.components == other.components
|
234
|
+
end
|
235
|
+
alias eql? ==
|
236
|
+
|
237
|
+
def hash
|
238
|
+
([error] + components).hash
|
239
|
+
end
|
240
|
+
|
230
241
|
#
|
231
242
|
# Performs unescaping of attribute reference path components:
|
232
243
|
#
|
data/lib/ldclient-rb/util.rb
CHANGED
@@ -2,6 +2,69 @@ require "uri"
|
|
2
2
|
require "http"
|
3
3
|
|
4
4
|
module LaunchDarkly
|
5
|
+
#
|
6
|
+
# A Result is used to reflect the outcome of any operation.
|
7
|
+
#
|
8
|
+
# Results can either be considered a success or a failure.
|
9
|
+
#
|
10
|
+
# In the event of success, the Result will contain an option, nullable value to hold any success value back to the
|
11
|
+
# calling function.
|
12
|
+
#
|
13
|
+
# If the operation fails, the Result will contain an error describing the value.
|
14
|
+
#
|
15
|
+
class Result
|
16
|
+
#
|
17
|
+
# Create a successful result with the provided value.
|
18
|
+
#
|
19
|
+
# @param value [Object, nil]
|
20
|
+
# @return [Result]
|
21
|
+
#
|
22
|
+
def self.success(value)
|
23
|
+
Result.new(value)
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Create a failed result with the provided error description.
|
28
|
+
#
|
29
|
+
# @param error [String]
|
30
|
+
# @param exception [Exception, nil]
|
31
|
+
# @return [Result]
|
32
|
+
#
|
33
|
+
def self.fail(error, exception = nil)
|
34
|
+
Result.new(nil, error, exception)
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Was this result successful or did it encounter an error?
|
39
|
+
#
|
40
|
+
# @return [Boolean]
|
41
|
+
#
|
42
|
+
def success?
|
43
|
+
@error.nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# @return [Object, nil] The value returned from the operation if it was successful; nil otherwise.
|
48
|
+
#
|
49
|
+
attr_reader :value
|
50
|
+
|
51
|
+
#
|
52
|
+
# @return [String, nil] An error description of the failure; nil otherwise
|
53
|
+
#
|
54
|
+
attr_reader :error
|
55
|
+
|
56
|
+
#
|
57
|
+
# @return [Exception, nil] An optional exception which caused the failure
|
58
|
+
#
|
59
|
+
attr_reader :exception
|
60
|
+
|
61
|
+
private def initialize(value, error = nil, exception = nil)
|
62
|
+
@value = value
|
63
|
+
@error = error
|
64
|
+
@exception = exception
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
5
68
|
# @private
|
6
69
|
module Util
|
7
70
|
#
|
data/lib/ldclient-rb/version.rb
CHANGED
data/lib/ldclient-rb.rb
CHANGED
@@ -9,6 +9,7 @@ require "ldclient-rb/version"
|
|
9
9
|
require "ldclient-rb/interfaces"
|
10
10
|
require "ldclient-rb/util"
|
11
11
|
require "ldclient-rb/flags_state"
|
12
|
+
require "ldclient-rb/migrations"
|
12
13
|
require "ldclient-rb/ldclient"
|
13
14
|
require "ldclient-rb/cache_store"
|
14
15
|
require "ldclient-rb/expiring_cache"
|