Skip to content

Commit bd5edb0

Browse files
LaunchDarklyReleaseBotjacobthemytheli-darklyLaunchDarklyCIapache-hb
authored
prepare 8.0.0 release (#227)
## [8.0.0] - 2023-10-17 The latest version of this SDK supports the ability to manage migrations or modernizations, using migration flags. You might use this functionality if you are optimizing queries, upgrading to new tech stacks, migrating from one database to another, or other similar technology changes. Migration flags are part of LaunchDarkly's Early Access Program. This feature is available to all LaunchDarkly customers but may undergo additional changes before it is finalized. For detailed information about this version, refer to the list below. For information on how to upgrade from the previous version, read the [migration guide](https://docs.launchdarkly.com/sdk/server-side/ruby/migration-7-to-8). ### Added: - A new `Migrator` type which provides an out-of-the-box configurable migration framework. - For more advanced use cases, added new `migration_variation` and `track_migration_op` methods on `LDClient`. ### Removed: - Ruby 2.7 support was removed. - The legacy user format for contexts is no longer supported. To learn more, read the [Contexts documentation](https://docs.launchdarkly.com/guides/flags/intro-contexts). - Previously deprecated config options `user_keys_capacity`, `user_keys_flush_interval`, `private_attribute_names`, `default_user_keys_capacity`, `user_cache_size`, `user_cache_time`, and `default_user_keys_flush_interval` have been removed. - Previously deprecated test data flag builder methods `variation_for_all_users`, `value_for_all_users`, and `clear_user_targets` have been removed. --------- Co-authored-by: Jacob Smith <[email protected]> Co-authored-by: Eli Bishop <[email protected]> Co-authored-by: LaunchDarklyCI <[email protected]> Co-authored-by: Elliot <[email protected]> Co-authored-by: Ben Woskow <[email protected]> Co-authored-by: Ben Woskow <[email protected]> Co-authored-by: hroederld <[email protected]> Co-authored-by: Kerrie Martinez <[email protected]> Co-authored-by: pellyg-ld <[email protected]> Co-authored-by: Sam Stokes <[email protected]> Co-authored-by: LaunchDarklyReleaseBot <[email protected]> Co-authored-by: Ember Stevens <[email protected]> Co-authored-by: ember-stevens <[email protected]> Co-authored-by: Louis Chan <[email protected]> Co-authored-by: Matthew M. Keeler <[email protected]> Co-authored-by: Ben Levy <[email protected]> Co-authored-by: Ben Levy <[email protected]> Co-authored-by: Matthew M. Keeler <[email protected]> Co-authored-by: Louis Chan <[email protected]> Co-authored-by: Matt Hooks <[email protected]>
1 parent 375da8f commit bd5edb0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2910
-400
lines changed

Diff for: .circleci/config.yml

-3
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ workflows:
1212
after-install-rubocop:
1313
- run: gem install rubocop-performance
1414
- build-test-windows
15-
- build-test-linux:
16-
name: Ruby 2.7
17-
docker-image: cimg/ruby:2.7
1815
- build-test-linux:
1916
name: Ruby 3.0
2017
docker-image: cimg/ruby:3.0

Diff for: .ldrelease/config.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ publications:
1818

1919
jobs:
2020
- docker:
21-
image: ruby:2.7-buster
21+
image: ruby:3.0-buster
2222
template:
2323
name: ruby
2424

Diff for: .rubocop.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ require:
22
- rubocop-performance
33

44
AllCops:
5-
TargetRubyVersion: 2.7
5+
TargetRubyVersion: 3.0
66
Include:
77
- lib/**/*.rb
88
- spec/**/*.rb

Diff for: contract-tests/Gemfile

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ gem 'sinatra', '~> 2.1'
66
# Sinatra can work with several server frameworks. In JRuby, we have to use glassfish (which
77
# is only available in JRuby). Otherwise we use thin (which is not available in JRuby).
88
gem 'glassfish', :platforms => :jruby
9-
gem 'thin', :platforms => :ruby
9+
gem 'http', '~> 5.1'
1010
gem 'json'
1111
gem 'rubocop', '~> 1.37', group: 'development'
1212
gem 'rubocop-performance', '~> 1.15', group: 'development'
13+
gem 'thin', :platforms => :ruby

Diff for: contract-tests/client_entity.rb

+53-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require 'net/http'
44
require 'launchdarkly-server-sdk'
55
require './big_segment_store_fixture'
6+
require 'http'
67

78
class ClientEntity
89
def initialize(log, config)
@@ -77,12 +78,12 @@ def evaluate(params)
7778
response = {}
7879

7980
if params[:detail]
80-
detail = @client.variation_detail(params[:flagKey], params[:context] || params[:user], params[:defaultValue])
81+
detail = @client.variation_detail(params[:flagKey], params[:context], params[:defaultValue])
8182
response[:value] = detail.value
8283
response[:variationIndex] = detail.variation_index
8384
response[:reason] = detail.reason
8485
else
85-
response[:value] = @client.variation(params[:flagKey], params[:context] || params[:user], params[:defaultValue])
86+
response[:value] = @client.variation(params[:flagKey], params[:context], params[:defaultValue])
8687
end
8788

8889
response
@@ -94,19 +95,65 @@ def evaluate_all(params)
9495
opts[:with_reasons] = params[:withReasons] || false
9596
opts[:details_only_for_tracked_flags] = params[:detailsOnlyForTrackedFlags] || false
9697

97-
@client.all_flags_state(params[:context] || params[:user], opts)
98+
@client.all_flags_state(params[:context], opts)
99+
end
100+
101+
def migration_variation(params)
102+
default_stage = params[:defaultStage]
103+
default_stage = default_stage.to_sym if default_stage.respond_to? :to_sym
104+
stage, _ = @client.migration_variation(params[:key], params[:context], default_stage)
105+
stage
106+
end
107+
108+
def migration_operation(params)
109+
builder = LaunchDarkly::Migrations::MigratorBuilder.new(@client)
110+
builder.read_execution_order(params[:readExecutionOrder].to_sym)
111+
builder.track_latency(params[:trackLatency])
112+
builder.track_errors(params[:trackErrors])
113+
114+
callback = ->(endpoint) {
115+
->(payload) {
116+
response = HTTP.post(endpoint, body: payload)
117+
118+
if response.status.success?
119+
LaunchDarkly::Result.success(response.body.to_s)
120+
else
121+
LaunchDarkly::Result.fail("requested failed with status code #{response.status}")
122+
end
123+
}
124+
}
125+
126+
consistency = nil
127+
if params[:trackConsistency]
128+
consistency = ->(lhs, rhs) { lhs == rhs }
129+
end
130+
131+
builder.read(callback.call(params[:oldEndpoint]), callback.call(params[:newEndpoint]), consistency)
132+
builder.write(callback.call(params[:oldEndpoint]), callback.call(params[:newEndpoint]))
133+
134+
migrator = builder.build
135+
136+
return migrator if migrator.is_a? String
137+
138+
if params[:operation] == LaunchDarkly::Migrations::OP_READ.to_s
139+
result = migrator.read(params[:key], params[:context], params[:defaultStage].to_sym, params[:payload])
140+
result.success? ? result.value : result.error
141+
else
142+
result = migrator.write(params[:key], params[:context], params[:defaultStage].to_sym, params[:payload])
143+
result.authoritative.success? ? result.authoritative.value : result.authoritative.error
144+
end
98145
end
99146

100147
def secure_mode_hash(params)
101-
@client.secure_mode_hash(params[:context] || params[:user])
148+
@client.secure_mode_hash(params[:context])
102149
end
103150

104151
def track(params)
105-
@client.track(params[:eventKey], params[:context] || params[:user], params[:data], params[:metricValue])
152+
@client.track(params[:eventKey], params[:context], params[:data], params[:metricValue])
106153
end
107154

108155
def identify(params)
109-
@client.identify(params[:context] || params[:user])
156+
@client.identify(params[:context])
110157
end
111158

112159
def flush_events

Diff for: contract-tests/service.rb

+8-1
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@
3232
'all-flags-details-only-for-tracked-flags',
3333
'filtering',
3434
'secure-mode-hash',
35-
'user-type',
3635
'tags',
36+
'migrations',
37+
'event-sampling',
3738
],
3839
}.to_json
3940
end
@@ -102,6 +103,12 @@
102103
when "getBigSegmentStoreStatus"
103104
status = client.get_big_segment_store_status
104105
return [200, nil, status.to_json]
106+
when "migrationVariation"
107+
response = {:result => client.migration_variation(params[:migrationVariation]).to_s}
108+
return [200, nil, response.to_json]
109+
when "migrationOperation"
110+
response = {:result => client.migration_operation(params[:migrationOperation]).to_s}
111+
return [200, nil, response.to_json]
105112
end
106113

107114
return [400, nil, {:error => "Unknown command requested"}.to_json]

Diff for: launchdarkly-server-sdk.gemspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
1919
spec.files = FileList["lib/**/*", "README.md", "LICENSE.txt"]
2020
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
2121
spec.require_paths = ["lib"]
22-
spec.required_ruby_version = ">= 2.7.0"
22+
spec.required_ruby_version = ">= 3.0.0"
2323

2424
spec.add_development_dependency "aws-sdk-dynamodb", "~> 1.57"
2525
spec.add_development_dependency "bundler", "2.2.33"

Diff for: lib/ldclient-rb.rb

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module LaunchDarkly
99
require "ldclient-rb/interfaces"
1010
require "ldclient-rb/util"
1111
require "ldclient-rb/flags_state"
12+
require "ldclient-rb/migrations"
1213
require "ldclient-rb/ldclient"
1314
require "ldclient-rb/cache_store"
1415
require "ldclient-rb/expiring_cache"

Diff for: lib/ldclient-rb/config.rb

+3-68
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,6 @@ class Config
1313
#
1414
# Constructor for creating custom LaunchDarkly configurations.
1515
#
16-
# `user_keys_capacity` and `user_keys_flush_interval` are deprecated
17-
# configuration options. They exist to maintain backwards compatibility
18-
# with previous configurations. Newer code should prefer their replacement
19-
# options -- `context_keys_capacity` and `context_keys_flush_interval`.
20-
#
21-
# In the event both the user and context variations are provided, the
22-
# context specific configuration option will take precedence.
23-
#
24-
# Similarly, `private_attribute_names` is deprecated. Newer code should
25-
# prefer `private_attributes`. If both are provided, `private_attributes`
26-
# will take precedence.
27-
#
2816
# @param opts [Hash] the configuration options
2917
# @option opts [Logger] :logger See {#logger}.
3018
# @option opts [String] :base_uri ("https://sdk.launchdarkly.com") See {#base_uri}.
@@ -42,12 +30,9 @@ class Config
4230
# @option opts [Float] :poll_interval (30) See {#poll_interval}.
4331
# @option opts [Boolean] :stream (true) See {#stream?}.
4432
# @option opts [Boolean] all_attributes_private (false) See {#all_attributes_private}.
45-
# @option opts [Array] :private_attribute_names See {#private_attribute_names}.
4633
# @option opts [Array] :private_attributes See {#private_attributes}.
4734
# @option opts [Boolean] :send_events (true) See {#send_events}.
48-
# @option opts [Integer] :user_keys_capacity (1000) See {#user_keys_capacity}.
4935
# @option opts [Integer] :context_keys_capacity (1000) See {#context_keys_capacity}.
50-
# @option opts [Float] :user_keys_flush_interval (300) See {#user_keys_flush_interval}.
5136
# @option opts [Float] :context_keys_flush_interval (300) See {#context_keys_flush_interval}.
5237
# @option opts [Object] :data_source See {#data_source}.
5338
# @option opts [Boolean] :diagnostic_opt_out (false) See {#diagnostic_opt_out?}.
@@ -76,10 +61,10 @@ def initialize(opts = {})
7661
@offline = opts.has_key?(:offline) ? opts[:offline] : Config.default_offline
7762
@poll_interval = opts.has_key?(:poll_interval) && opts[:poll_interval] > Config.default_poll_interval ? opts[:poll_interval] : Config.default_poll_interval
7863
@all_attributes_private = opts[:all_attributes_private] || false
79-
@private_attributes = opts[:private_attributes] || opts[:private_attribute_names] || []
64+
@private_attributes = opts[:private_attributes] || []
8065
@send_events = opts.has_key?(:send_events) ? opts[:send_events] : Config.default_send_events
81-
@context_keys_capacity = opts[:context_keys_capacity] || opts[:user_keys_capacity] || Config.default_context_keys_capacity
82-
@context_keys_flush_interval = opts[:context_keys_flush_interval] || opts[:user_keys_flush_interval] || Config.default_user_keys_flush_interval
66+
@context_keys_capacity = opts[:context_keys_capacity] || Config.default_context_keys_capacity
67+
@context_keys_flush_interval = opts[:context_keys_flush_interval] || Config.default_context_keys_flush_interval
8368
@data_source = opts[:data_source]
8469
@diagnostic_opt_out = opts.has_key?(:diagnostic_opt_out) && opts[:diagnostic_opt_out]
8570
@diagnostic_recording_interval = opts.has_key?(:diagnostic_recording_interval) && opts[:diagnostic_recording_interval] > Config.minimum_diagnostic_recording_interval ?
@@ -258,14 +243,6 @@ def offline?
258243
#
259244
attr_reader :private_attributes
260245

261-
#
262-
# @deprecated Backwards compatibility alias for #private_attributes.
263-
#
264-
# @return [Integer]
265-
# @see #private_attributes
266-
#
267-
alias :private_attribute_names :private_attributes
268-
269246
#
270247
# Whether to send events back to LaunchDarkly. This differs from {#offline?} in that it affects
271248
# only the sending of client-side events, not streaming or polling for events from the server.
@@ -281,29 +258,13 @@ def offline?
281258
#
282259
attr_reader :context_keys_capacity
283260

284-
#
285-
# @deprecated Backwards compatibility alias for #context_keys_capacity.
286-
#
287-
# @return [Integer]
288-
# @see #context_keys_flush_interval
289-
#
290-
alias :user_keys_capacity :context_keys_capacity
291-
292261
#
293262
# The interval in seconds at which the event processor will reset its set of known context keys.
294263
# @return [Float]
295264
# @see #context_keys_capacity
296265
#
297266
attr_reader :context_keys_flush_interval
298267

299-
#
300-
# @deprecated Backwards compatibility alias for #context_keys_flush_interval.
301-
#
302-
# @return [Integer]
303-
# @see #context_keys_flush_interval
304-
#
305-
alias :user_keys_flush_interval :context_keys_flush_interval
306-
307268
#
308269
# An object that is responsible for receiving feature flag data from LaunchDarkly. By default,
309270
# the client uses its standard polling or streaming implementation; this is customizable for
@@ -570,18 +531,6 @@ def self.default_context_keys_flush_interval
570531
300
571532
end
572533

573-
class << self
574-
#
575-
# @deprecated Backwards compatibility alias for #default_context_keys_capacity
576-
#
577-
alias :default_user_keys_capacity :default_context_keys_capacity
578-
579-
#
580-
# @deprecated Backwards compatibility alias for #default_context_keys_flush_interval
581-
#
582-
alias :default_user_keys_flush_interval :default_context_keys_flush_interval
583-
end
584-
585534
#
586535
# The default value for {#diagnostic_recording_interval}.
587536
# @return [Float] 900
@@ -647,25 +596,11 @@ def initialize(store:, context_cache_size: nil, context_cache_time: nil, status_
647596
# @return [Integer]
648597
attr_reader :context_cache_size
649598

650-
#
651-
# @deprecated Backwards compatibility alias for #context_cache_size
652-
#
653-
# @return [Integer]
654-
#
655-
alias :user_cache_size :context_cache_size
656-
657599
# The maximum length of time (in seconds) that the Big Segment state for a context will be cached
658600
# by the SDK.
659601
# @return [Float]
660602
attr_reader :context_cache_time
661603

662-
#
663-
# @deprecated Backwards compatibility alias for #context_cache_time
664-
#
665-
# @return [Float]
666-
#
667-
alias :user_cache_time :context_cache_time
668-
669604
# The interval (in seconds) at which the SDK will poll the Big Segment store to make sure it is
670605
# available and to determine how long ago it was updated.
671606
# @return [Float]

Diff for: lib/ldclient-rb/context.rb

-47
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,6 @@ def self.with_key(key, kind = KIND_DEFAULT)
324324
#
325325
def self.create(data)
326326
return create_invalid_context(ERR_NOT_HASH) unless data.is_a?(Hash)
327-
return create_legacy_context(data) unless data.has_key?(:kind)
328327

329328
kind = data[:kind]
330329
if kind == KIND_MULTI
@@ -394,52 +393,6 @@ def self.create_multi(contexts)
394393
new(nil, nil, nil, nil, false, nil, nil, error)
395394
end
396395

397-
#
398-
# @param data [Hash]
399-
# @return [LDContext]
400-
#
401-
private_class_method def self.create_legacy_context(data)
402-
warn("DEPRECATED: legacy user format will be removed in 8.0.0", uplevel: 1)
403-
404-
key = data[:key]
405-
406-
# Legacy users are allowed to have "" as a key but they cannot have nil as a key.
407-
return create_invalid_context(ERR_KEY_EMPTY) if key.nil?
408-
409-
name = data[:name]
410-
name_error = LaunchDarkly::Impl::Context.validate_name(name)
411-
return create_invalid_context(name_error) unless name_error.nil?
412-
413-
anonymous = data[:anonymous]
414-
anonymous_error = LaunchDarkly::Impl::Context.validate_anonymous(anonymous, true)
415-
return create_invalid_context(anonymous_error) unless anonymous_error.nil?
416-
417-
custom = data[:custom]
418-
unless custom.nil? || custom.is_a?(Hash)
419-
return create_invalid_context(ERR_CUSTOM_NON_HASH)
420-
end
421-
422-
# We only need to create an attribute hash if one of these keys exist.
423-
# Everything else is stored in dedicated instance variables.
424-
attributes = custom.clone
425-
data.each do |k, v|
426-
case k
427-
when :ip, :email, :avatar, :firstName, :lastName, :country
428-
attributes ||= {}
429-
attributes[k] = v.clone
430-
else
431-
next
432-
end
433-
end
434-
435-
private_attributes = data[:privateAttributeNames]
436-
if private_attributes && !private_attributes.is_a?(Array)
437-
return create_invalid_context(ERR_PRIVATE_NON_ARRAY)
438-
end
439-
440-
new(key.to_s, key.to_s, KIND_DEFAULT, name, anonymous, attributes, private_attributes)
441-
end
442-
443396
#
444397
# @param data [Hash]
445398
# @param kind [String]

Diff for: lib/ldclient-rb/evaluation_detail.rb

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
module LaunchDarkly
32
# An object returned by {LDClient#variation_detail}, combining the result of a flag evaluation with
43
# an explanation of how it was calculated.
@@ -13,6 +12,7 @@ class EvaluationDetail
1312
def initialize(value, variation_index, reason)
1413
raise ArgumentError.new("variation_index must be a number") if !variation_index.nil? && !(variation_index.is_a? Numeric)
1514
raise ArgumentError.new("reason must be an EvaluationReason") unless reason.is_a? EvaluationReason
15+
1616
@value = value
1717
@variation_index = variation_index
1818
@reason = reason
@@ -100,6 +100,10 @@ class EvaluationReason
100100
# a rule specified a nonexistent variation. An error message will always be logged in this case.
101101
ERROR_MALFORMED_FLAG = :MALFORMED_FLAG
102102

103+
# Value for {#error_kind} indicating that there was an inconsistency between the expected type of the flag, and the
104+
# actual type of the variation evaluated.
105+
ERROR_WRONG_TYPE = :WRONG_TYPE
106+
103107
# Value for {#error_kind} indicating that the caller passed `nil` for the context parameter, or the
104108
# context was invalid.
105109
ERROR_USER_NOT_SPECIFIED = :USER_NOT_SPECIFIED

0 commit comments

Comments
 (0)