launchdarkly-server-sdk 7.3.0 → 8.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ldclient-rb/config.rb +3 -68
- data/lib/ldclient-rb/context.rb +0 -47
- 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/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/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,
|
@@ -148,7 +154,7 @@ module LaunchDarkly
|
|
148
154
|
# @return [String, nil] a hash string or nil if the provided context was invalid
|
149
155
|
#
|
150
156
|
def secure_mode_hash(context)
|
151
|
-
context = Impl::Context
|
157
|
+
context = Impl::Context.make_context(context)
|
152
158
|
unless context.valid?
|
153
159
|
@config.logger.warn("secure_mode_hash called with invalid context: #{context.error}")
|
154
160
|
return nil
|
@@ -188,10 +194,11 @@ module LaunchDarkly
|
|
188
194
|
# @param default the default value of the flag; this is used if there is an error
|
189
195
|
# condition making it impossible to find or evaluate the flag
|
190
196
|
#
|
191
|
-
# @return the variation for the provided context, or the default value if there's an
|
197
|
+
# @return the variation for the provided context, or the default value if there's an error
|
192
198
|
#
|
193
199
|
def variation(key, context, default)
|
194
|
-
|
200
|
+
detail, _, _, = variation_with_flag(key, context, default)
|
201
|
+
detail.value
|
195
202
|
end
|
196
203
|
|
197
204
|
#
|
@@ -218,7 +225,43 @@ module LaunchDarkly
|
|
218
225
|
# @return [EvaluationDetail] an object describing the result
|
219
226
|
#
|
220
227
|
def variation_detail(key, context, default)
|
221
|
-
evaluate_internal(key, context, default, true)
|
228
|
+
detail, _, _ = evaluate_internal(key, context, default, true)
|
229
|
+
detail
|
230
|
+
end
|
231
|
+
|
232
|
+
#
|
233
|
+
# This method returns the migration stage of the migration feature flag for the given evaluation context.
|
234
|
+
#
|
235
|
+
# This method returns the default stage if there is an error or the flag does not exist. If the default stage is not
|
236
|
+
# a valid stage, then a default stage of 'off' will be used instead.
|
237
|
+
#
|
238
|
+
# @param key [String]
|
239
|
+
# @param context [LDContext]
|
240
|
+
# @param default_stage [Symbol]
|
241
|
+
#
|
242
|
+
# @return [Array<Symbol, Interfaces::Migrations::OpTracker>]
|
243
|
+
#
|
244
|
+
def migration_variation(key, context, default_stage)
|
245
|
+
unless Migrations::VALID_STAGES.include? default_stage
|
246
|
+
@config.logger.error { "[LDClient] default_stage #{default_stage} is not a valid stage; continuing with 'off' as default" }
|
247
|
+
default_stage = Migrations::STAGE_OFF
|
248
|
+
end
|
249
|
+
|
250
|
+
context = Impl::Context::make_context(context)
|
251
|
+
detail, flag, _ = variation_with_flag(key, context, default_stage.to_s)
|
252
|
+
|
253
|
+
stage = detail.value
|
254
|
+
stage = stage.to_sym if stage.respond_to? :to_sym
|
255
|
+
|
256
|
+
if Migrations::VALID_STAGES.include?(stage)
|
257
|
+
tracker = Impl::Migrations::OpTracker.new(@config.logger, key, flag, context, detail, default_stage)
|
258
|
+
return stage, tracker
|
259
|
+
end
|
260
|
+
|
261
|
+
detail = LaunchDarkly::Impl::Evaluator.error_result(LaunchDarkly::EvaluationReason::ERROR_WRONG_TYPE, default_stage.to_s)
|
262
|
+
tracker = Impl::Migrations::OpTracker.new(@config.logger, key, flag, context, detail, default_stage)
|
263
|
+
|
264
|
+
[default_stage, tracker]
|
222
265
|
end
|
223
266
|
|
224
267
|
#
|
@@ -281,6 +324,32 @@ module LaunchDarkly
|
|
281
324
|
@event_processor.record_custom_event(context, event_name, data, metric_value)
|
282
325
|
end
|
283
326
|
|
327
|
+
#
|
328
|
+
# Tracks the results of a migrations operation. This event includes measurements which can be used to enhance the
|
329
|
+
# observability of a migration within the LaunchDarkly UI.
|
330
|
+
#
|
331
|
+
# This event should be generated through {Interfaces::Migrations::OpTracker}. If you are using the
|
332
|
+
# {Interfaces::Migrations::Migrator} to handle migrations, this event will be created and emitted
|
333
|
+
# automatically.
|
334
|
+
#
|
335
|
+
# @param tracker [LaunchDarkly::Interfaces::Migrations::OpTracker]
|
336
|
+
#
|
337
|
+
def track_migration_op(tracker)
|
338
|
+
unless tracker.is_a? LaunchDarkly::Interfaces::Migrations::OpTracker
|
339
|
+
@config.logger.error { "invalid op tracker received in track_migration_op" }
|
340
|
+
return
|
341
|
+
end
|
342
|
+
|
343
|
+
event = tracker.build
|
344
|
+
if event.is_a? String
|
345
|
+
@config.logger.error { "[LDClient] Error occurred generating migration op event; #{event}" }
|
346
|
+
return
|
347
|
+
end
|
348
|
+
|
349
|
+
|
350
|
+
@event_processor.record_migration_op_event(event)
|
351
|
+
end
|
352
|
+
|
284
353
|
#
|
285
354
|
# Returns a {FeatureFlagsState} object that encapsulates the state of all feature flags for a given context,
|
286
355
|
# including the flag values and also metadata that can be used on the front end. This method does not
|
@@ -430,24 +499,41 @@ module LaunchDarkly
|
|
430
499
|
end
|
431
500
|
end
|
432
501
|
|
502
|
+
#
|
503
|
+
# @param key [String]
|
433
504
|
# @param context [Hash, LDContext]
|
434
|
-
# @
|
505
|
+
# @param default [Object]
|
506
|
+
#
|
507
|
+
# @return [Array<EvaluationDetail, [LaunchDarkly::Impl::Model::FeatureFlag, nil], [String, nil]>]
|
508
|
+
#
|
509
|
+
def variation_with_flag(key, context, default)
|
510
|
+
evaluate_internal(key, context, default, false)
|
511
|
+
end
|
512
|
+
|
513
|
+
#
|
514
|
+
# @param key [String]
|
515
|
+
# @param context [Hash, LDContext]
|
516
|
+
# @param default [Object]
|
517
|
+
# @param with_reasons [Boolean]
|
518
|
+
#
|
519
|
+
# @return [Array<EvaluationDetail, [LaunchDarkly::Impl::Model::FeatureFlag, nil], [String, nil]>]
|
520
|
+
#
|
435
521
|
def evaluate_internal(key, context, default, with_reasons)
|
436
522
|
if @config.offline?
|
437
|
-
return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
|
523
|
+
return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default), nil, nil
|
438
524
|
end
|
439
525
|
|
440
526
|
if context.nil?
|
441
527
|
@config.logger.error { "[LDClient] Must specify context" }
|
442
528
|
detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
|
443
|
-
return detail
|
529
|
+
return detail, nil, "no context provided"
|
444
530
|
end
|
445
531
|
|
446
532
|
context = Impl::Context::make_context(context)
|
447
533
|
unless context.valid?
|
448
534
|
@config.logger.error { "[LDClient] Context was invalid for evaluation of flag '#{key}' (#{context.error}); returning default value" }
|
449
535
|
detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
|
450
|
-
return detail
|
536
|
+
return detail, nil, context.error
|
451
537
|
end
|
452
538
|
|
453
539
|
unless initialized?
|
@@ -457,7 +543,7 @@ module LaunchDarkly
|
|
457
543
|
@config.logger.error { "[LDClient] Client has not finished initializing; feature store unavailable, returning default value" }
|
458
544
|
detail = Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
|
459
545
|
record_unknown_flag_eval(key, context, default, detail.reason, with_reasons)
|
460
|
-
return detail
|
546
|
+
return detail, nil, "client not initialized"
|
461
547
|
end
|
462
548
|
end
|
463
549
|
|
@@ -471,7 +557,7 @@ module LaunchDarkly
|
|
471
557
|
@config.logger.info { "[LDClient] Unknown feature flag \"#{key}\". Returning default value" }
|
472
558
|
detail = Evaluator.error_result(EvaluationReason::ERROR_FLAG_NOT_FOUND, default)
|
473
559
|
record_unknown_flag_eval(key, context, default, detail.reason, with_reasons)
|
474
|
-
return detail
|
560
|
+
return detail, nil, "feature flag not found"
|
475
561
|
end
|
476
562
|
|
477
563
|
begin
|
@@ -486,12 +572,12 @@ module LaunchDarkly
|
|
486
572
|
detail = EvaluationDetail.new(default, nil, detail.reason)
|
487
573
|
end
|
488
574
|
record_flag_eval(feature, context, detail, default, with_reasons)
|
489
|
-
detail
|
575
|
+
[detail, feature, nil]
|
490
576
|
rescue => exn
|
491
577
|
Util.log_exception(@config.logger, "Error evaluating feature flag \"#{key}\"", exn)
|
492
578
|
detail = Evaluator.error_result(EvaluationReason::ERROR_EXCEPTION, default)
|
493
579
|
record_flag_eval_error(feature, context, default, detail.reason, with_reasons)
|
494
|
-
detail
|
580
|
+
[detail, feature, exn.to_s]
|
495
581
|
end
|
496
582
|
end
|
497
583
|
|
@@ -507,7 +593,9 @@ module LaunchDarkly
|
|
507
593
|
default,
|
508
594
|
add_experiment_data || flag[:trackEvents] || false,
|
509
595
|
flag[:debugEventsUntilDate],
|
510
|
-
nil
|
596
|
+
nil,
|
597
|
+
flag[:samplingRatio],
|
598
|
+
!!flag[:excludeFromSummaries]
|
511
599
|
)
|
512
600
|
end
|
513
601
|
|
@@ -523,13 +611,15 @@ module LaunchDarkly
|
|
523
611
|
nil,
|
524
612
|
add_experiment_data || prereq_flag[:trackEvents] || false,
|
525
613
|
prereq_flag[:debugEventsUntilDate],
|
526
|
-
prereq_of_flag[:key]
|
614
|
+
prereq_of_flag[:key],
|
615
|
+
prereq_flag[:samplingRatio],
|
616
|
+
!!prereq_flag[:excludeFromSummaries]
|
527
617
|
)
|
528
618
|
end
|
529
619
|
|
530
620
|
private def record_flag_eval_error(flag, context, default, reason, with_reasons)
|
531
621
|
@event_processor.record_eval_event(context, flag[:key], flag[:version], nil, default, with_reasons ? reason : nil, default,
|
532
|
-
flag[:trackEvents], flag[:debugEventsUntilDate], nil)
|
622
|
+
flag[:trackEvents], flag[:debugEventsUntilDate], nil, flag[:samplingRatio], !!flag[:excludeFromSummaries])
|
533
623
|
end
|
534
624
|
|
535
625
|
#
|
@@ -541,7 +631,7 @@ module LaunchDarkly
|
|
541
631
|
#
|
542
632
|
private def record_unknown_flag_eval(flag_key, context, default, reason, with_reasons)
|
543
633
|
@event_processor.record_eval_event(context, flag_key, nil, nil, default, with_reasons ? reason : nil, default,
|
544
|
-
false, nil, nil)
|
634
|
+
false, nil, nil, 1, false)
|
545
635
|
end
|
546
636
|
|
547
637
|
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
|
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"
|