launchdarkly-server-sdk 6.2.5 → 7.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -2
- data/lib/ldclient-rb/config.rb +203 -43
- data/lib/ldclient-rb/context.rb +487 -0
- data/lib/ldclient-rb/evaluation_detail.rb +85 -26
- data/lib/ldclient-rb/events.rb +185 -146
- data/lib/ldclient-rb/flags_state.rb +25 -14
- data/lib/ldclient-rb/impl/big_segments.rb +117 -0
- data/lib/ldclient-rb/impl/context.rb +96 -0
- data/lib/ldclient-rb/impl/context_filter.rb +145 -0
- data/lib/ldclient-rb/impl/diagnostic_events.rb +9 -10
- data/lib/ldclient-rb/impl/evaluator.rb +428 -132
- data/lib/ldclient-rb/impl/evaluator_bucketing.rb +40 -41
- data/lib/ldclient-rb/impl/evaluator_helpers.rb +50 -0
- data/lib/ldclient-rb/impl/evaluator_operators.rb +26 -55
- data/lib/ldclient-rb/impl/event_sender.rb +6 -6
- data/lib/ldclient-rb/impl/event_summarizer.rb +68 -0
- data/lib/ldclient-rb/impl/event_types.rb +78 -0
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +7 -7
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +92 -28
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +212 -0
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +165 -32
- data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +40 -0
- data/lib/ldclient-rb/impl/model/clause.rb +39 -0
- data/lib/ldclient-rb/impl/model/feature_flag.rb +213 -0
- data/lib/ldclient-rb/impl/model/preprocessed_data.rb +64 -0
- data/lib/ldclient-rb/impl/model/segment.rb +126 -0
- data/lib/ldclient-rb/impl/model/serialization.rb +54 -44
- data/lib/ldclient-rb/impl/repeating_task.rb +47 -0
- data/lib/ldclient-rb/impl/store_data_set_sorter.rb +2 -2
- data/lib/ldclient-rb/impl/unbounded_pool.rb +1 -1
- data/lib/ldclient-rb/impl/util.rb +62 -1
- data/lib/ldclient-rb/in_memory_store.rb +2 -2
- data/lib/ldclient-rb/integrations/consul.rb +9 -2
- data/lib/ldclient-rb/integrations/dynamodb.rb +47 -2
- data/lib/ldclient-rb/integrations/file_data.rb +108 -0
- data/lib/ldclient-rb/integrations/redis.rb +43 -3
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +594 -0
- data/lib/ldclient-rb/integrations/test_data.rb +213 -0
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +14 -9
- data/lib/ldclient-rb/integrations.rb +2 -51
- data/lib/ldclient-rb/interfaces.rb +151 -1
- data/lib/ldclient-rb/ldclient.rb +175 -133
- data/lib/ldclient-rb/memoized_value.rb +1 -1
- data/lib/ldclient-rb/non_blocking_thread_pool.rb +1 -1
- data/lib/ldclient-rb/polling.rb +22 -41
- data/lib/ldclient-rb/reference.rb +274 -0
- data/lib/ldclient-rb/requestor.rb +7 -7
- data/lib/ldclient-rb/stream.rb +9 -9
- data/lib/ldclient-rb/util.rb +11 -17
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +2 -4
- metadata +49 -23
- data/lib/ldclient-rb/event_summarizer.rb +0 -55
- data/lib/ldclient-rb/file_data_source.rb +0 -314
- data/lib/ldclient-rb/impl/event_factory.rb +0 -126
- data/lib/ldclient-rb/newrelic.rb +0 -17
- data/lib/ldclient-rb/redis_store.rb +0 -88
- data/lib/ldclient-rb/user_filter.rb +0 -52
@@ -1,7 +1,10 @@
|
|
1
|
-
|
2
1
|
module LaunchDarkly
|
3
2
|
module Impl
|
4
3
|
module Util
|
4
|
+
def self.bool?(aObject)
|
5
|
+
[true,false].include? aObject
|
6
|
+
end
|
7
|
+
|
5
8
|
def self.current_time_millis
|
6
9
|
(Time.now.to_f * 1000).to_i
|
7
10
|
end
|
@@ -12,8 +15,66 @@ module LaunchDarkly
|
|
12
15
|
ret["X-LaunchDarkly-Wrapper"] = config.wrapper_name +
|
13
16
|
(config.wrapper_version ? "/" + config.wrapper_version : "")
|
14
17
|
end
|
18
|
+
|
19
|
+
app_value = application_header_value config.application
|
20
|
+
ret["X-LaunchDarkly-Tags"] = app_value unless app_value.nil? || app_value.empty?
|
21
|
+
|
15
22
|
ret
|
16
23
|
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Generate an HTTP Header value containing the application meta information (@see #application).
|
27
|
+
#
|
28
|
+
# @return [String]
|
29
|
+
#
|
30
|
+
def self.application_header_value(application)
|
31
|
+
parts = []
|
32
|
+
unless application[:id].empty?
|
33
|
+
parts << "application-id/#{application[:id]}"
|
34
|
+
end
|
35
|
+
|
36
|
+
unless application[:version].empty?
|
37
|
+
parts << "application-version/#{application[:version]}"
|
38
|
+
end
|
39
|
+
|
40
|
+
parts.join(" ")
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# @param value [String]
|
45
|
+
# @param name [Symbol]
|
46
|
+
# @param logger [Logger]
|
47
|
+
# @return [String]
|
48
|
+
#
|
49
|
+
def self.validate_application_value(value, name, logger)
|
50
|
+
value = value.to_s
|
51
|
+
|
52
|
+
return "" if value.empty?
|
53
|
+
|
54
|
+
if value.length > 64
|
55
|
+
logger.warn { "Value of application[#{name}] was longer than 64 characters and was discarded" }
|
56
|
+
return ""
|
57
|
+
end
|
58
|
+
|
59
|
+
if /[^a-zA-Z0-9._-]/.match?(value)
|
60
|
+
logger.warn { "Value of application[#{name}] contained invalid characters and was discarded" }
|
61
|
+
return ""
|
62
|
+
end
|
63
|
+
|
64
|
+
value
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# @param app [Hash]
|
69
|
+
# @param logger [Logger]
|
70
|
+
# @return [Hash]
|
71
|
+
#
|
72
|
+
def self.validate_application_info(app, logger)
|
73
|
+
{
|
74
|
+
id: validate_application_value(app[:id], :id, logger),
|
75
|
+
version: validate_application_value(app[:version], :version, logger),
|
76
|
+
}
|
77
|
+
end
|
17
78
|
end
|
18
79
|
end
|
19
80
|
end
|
@@ -14,13 +14,13 @@ module LaunchDarkly
|
|
14
14
|
FEATURES = {
|
15
15
|
namespace: "features",
|
16
16
|
priority: 1, # that is, features should be stored after segments
|
17
|
-
get_dependency_keys: lambda { |flag| (flag[:prerequisites] || []).map { |p| p[:key] } }
|
17
|
+
get_dependency_keys: lambda { |flag| (flag[:prerequisites] || []).map { |p| p[:key] } },
|
18
18
|
}.freeze
|
19
19
|
|
20
20
|
# @private
|
21
21
|
SEGMENTS = {
|
22
22
|
namespace: "segments",
|
23
|
-
priority: 0
|
23
|
+
priority: 0,
|
24
24
|
}.freeze
|
25
25
|
|
26
26
|
#
|
@@ -3,6 +3,13 @@ require "ldclient-rb/integrations/util/store_wrapper"
|
|
3
3
|
|
4
4
|
module LaunchDarkly
|
5
5
|
module Integrations
|
6
|
+
#
|
7
|
+
# Integration with [Consul](https://www.consul.io/).
|
8
|
+
#
|
9
|
+
# Note that in order to use this integration, you must first install the gem `diplomat`.
|
10
|
+
#
|
11
|
+
# @since 5.5.0
|
12
|
+
#
|
6
13
|
module Consul
|
7
14
|
#
|
8
15
|
# Default value for the `prefix` option for {new_feature_store}.
|
@@ -29,9 +36,9 @@ module LaunchDarkly
|
|
29
36
|
# @option opts [Integer] :capacity (1000) maximum number of items in the cache
|
30
37
|
# @return [LaunchDarkly::Interfaces::FeatureStore] a feature store object
|
31
38
|
#
|
32
|
-
def self.new_feature_store(opts
|
39
|
+
def self.new_feature_store(opts = {})
|
33
40
|
core = LaunchDarkly::Impl::Integrations::Consul::ConsulFeatureStoreCore.new(opts)
|
34
|
-
|
41
|
+
LaunchDarkly::Integrations::Util::CachingStoreWrapper.new(core, opts)
|
35
42
|
end
|
36
43
|
end
|
37
44
|
end
|
@@ -3,6 +3,14 @@ require "ldclient-rb/integrations/util/store_wrapper"
|
|
3
3
|
|
4
4
|
module LaunchDarkly
|
5
5
|
module Integrations
|
6
|
+
#
|
7
|
+
# Integration with [DynamoDB](https://aws.amazon.com/dynamodb/).
|
8
|
+
#
|
9
|
+
# Note that in order to use this integration, you must first install one of the AWS SDK gems: either
|
10
|
+
# `aws-sdk-dynamodb`, or the full `aws-sdk`.
|
11
|
+
#
|
12
|
+
# @since 5.5.0
|
13
|
+
#
|
6
14
|
module DynamoDB
|
7
15
|
#
|
8
16
|
# Creates a DynamoDB-backed persistent feature store. For more details about how and why you can
|
@@ -38,9 +46,46 @@ module LaunchDarkly
|
|
38
46
|
# @option opts [Integer] :capacity (1000) maximum number of items in the cache
|
39
47
|
# @return [LaunchDarkly::Interfaces::FeatureStore] a feature store object
|
40
48
|
#
|
41
|
-
def self.new_feature_store(table_name, opts)
|
49
|
+
def self.new_feature_store(table_name, opts = {})
|
42
50
|
core = LaunchDarkly::Impl::Integrations::DynamoDB::DynamoDBFeatureStoreCore.new(table_name, opts)
|
43
|
-
|
51
|
+
LaunchDarkly::Integrations::Util::CachingStoreWrapper.new(core, opts)
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Creates a DynamoDB-backed Big Segment store.
|
56
|
+
#
|
57
|
+
# Big Segments are a specific type of segments. For more information, read the LaunchDarkly
|
58
|
+
# documentation: https://docs.launchdarkly.com/home/users/big-segments
|
59
|
+
#
|
60
|
+
# To use this method, you must first install one of the AWS SDK gems: either `aws-sdk-dynamodb`, or
|
61
|
+
# the full `aws-sdk`. Then, put the object returned by this method into the `store` property of your
|
62
|
+
# Big Segments configuration (see `Config`).
|
63
|
+
#
|
64
|
+
# @example Configuring Big Segments
|
65
|
+
# store = LaunchDarkly::Integrations::DynamoDB::new_big_segment_store("my-table-name")
|
66
|
+
# config = LaunchDarkly::Config.new(big_segments: LaunchDarkly::BigSegmentsConfig.new(store: store)
|
67
|
+
# client = LaunchDarkly::LDClient.new(my_sdk_key, config)
|
68
|
+
#
|
69
|
+
# Note that the specified table must already exist in DynamoDB. It must have a partition key called
|
70
|
+
# "namespace", and a sort key called "key" (both strings). The SDK does not create the table
|
71
|
+
# automatically because it has no way of knowing what additional properties (such as permissions
|
72
|
+
# and throughput) you would want it to have.
|
73
|
+
#
|
74
|
+
# By default, the DynamoDB client will try to get your AWS credentials and region name from
|
75
|
+
# environment variables and/or local configuration files, as described in the AWS SDK documentation.
|
76
|
+
# You can also specify any supported AWS SDK options in `dynamodb_opts`-- or, provide an
|
77
|
+
# already-configured DynamoDB client in `existing_client`.
|
78
|
+
#
|
79
|
+
# @param opts [Hash] the configuration options (these are all the same as for `new_feature_store`,
|
80
|
+
# except that there are no caching parameters)
|
81
|
+
# @option opts [Hash] :dynamodb_opts options to pass to the DynamoDB client constructor (ignored if you specify `:existing_client`)
|
82
|
+
# @option opts [Object] :existing_client an already-constructed DynamoDB client for the feature store to use
|
83
|
+
# @option opts [String] :prefix namespace prefix to add to all keys used by LaunchDarkly
|
84
|
+
# @option opts [Logger] :logger a `Logger` instance; defaults to `Config.default_logger`
|
85
|
+
# @return [LaunchDarkly::Interfaces::BigSegmentStore] a Big Segment store object
|
86
|
+
#
|
87
|
+
def self.new_big_segment_store(table_name, opts)
|
88
|
+
LaunchDarkly::Impl::Integrations::DynamoDB::DynamoDBBigSegmentStore.new(table_name, opts)
|
44
89
|
end
|
45
90
|
end
|
46
91
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'ldclient-rb/impl/integrations/file_data_source'
|
2
|
+
|
3
|
+
module LaunchDarkly
|
4
|
+
module Integrations
|
5
|
+
#
|
6
|
+
# Provides a way to use local files as a source of feature flag state. This allows using a
|
7
|
+
# predetermined feature flag state without an actual LaunchDarkly connection.
|
8
|
+
#
|
9
|
+
# Reading flags from a file is only intended for pre-production environments. Production
|
10
|
+
# environments should always be configured to receive flag updates from LaunchDarkly.
|
11
|
+
#
|
12
|
+
# To use this component, call {FileData#data_source}, and store its return value in the
|
13
|
+
# {Config#data_source} property of your LaunchDarkly client configuration. In the options
|
14
|
+
# to `data_source`, set `paths` to the file path(s) of your data file(s):
|
15
|
+
#
|
16
|
+
# file_source = LaunchDarkly::Integrations::FileData.data_source(paths: [ myFilePath ])
|
17
|
+
# config = LaunchDarkly::Config.new(data_source: file_source)
|
18
|
+
#
|
19
|
+
# This will cause the client not to connect to LaunchDarkly to get feature flags. The
|
20
|
+
# client may still make network connections to send analytics events, unless you have disabled
|
21
|
+
# this with {Config#send_events} or {Config#offline?}.
|
22
|
+
#
|
23
|
+
# Flag data files can be either JSON or YAML. They contain an object with three possible
|
24
|
+
# properties:
|
25
|
+
#
|
26
|
+
# - `flags`: Feature flag definitions.
|
27
|
+
# - `flagValues`: Simplified feature flags that contain only a value.
|
28
|
+
# - `segments`: Context segment definitions.
|
29
|
+
#
|
30
|
+
# The format of the data in `flags` and `segments` is defined by the LaunchDarkly application
|
31
|
+
# and is subject to change. Rather than trying to construct these objects yourself, it is simpler
|
32
|
+
# to request existing flags directly from the LaunchDarkly server in JSON format, and use this
|
33
|
+
# output as the starting point for your file. In Linux you would do this:
|
34
|
+
#
|
35
|
+
# ```
|
36
|
+
# curl -H "Authorization: YOUR_SDK_KEY" https://sdk.launchdarkly.com/sdk/latest-all
|
37
|
+
# ```
|
38
|
+
#
|
39
|
+
# The output will look something like this (but with many more properties):
|
40
|
+
#
|
41
|
+
# {
|
42
|
+
# "flags": {
|
43
|
+
# "flag-key-1": {
|
44
|
+
# "key": "flag-key-1",
|
45
|
+
# "on": true,
|
46
|
+
# "variations": [ "a", "b" ]
|
47
|
+
# }
|
48
|
+
# },
|
49
|
+
# "segments": {
|
50
|
+
# "segment-key-1": {
|
51
|
+
# "key": "segment-key-1",
|
52
|
+
# "includes": [ "user-key-1" ]
|
53
|
+
# }
|
54
|
+
# }
|
55
|
+
# }
|
56
|
+
#
|
57
|
+
# Data in this format allows the SDK to exactly duplicate all the kinds of flag behavior supported
|
58
|
+
# by LaunchDarkly. However, in many cases you will not need this complexity, but will just want to
|
59
|
+
# set specific flag keys to specific values. For that, you can use a much simpler format:
|
60
|
+
#
|
61
|
+
# {
|
62
|
+
# "flagValues": {
|
63
|
+
# "my-string-flag-key": "value-1",
|
64
|
+
# "my-boolean-flag-key": true,
|
65
|
+
# "my-integer-flag-key": 3
|
66
|
+
# }
|
67
|
+
# }
|
68
|
+
#
|
69
|
+
# Or, in YAML:
|
70
|
+
#
|
71
|
+
# flagValues:
|
72
|
+
# my-string-flag-key: "value-1"
|
73
|
+
# my-boolean-flag-key: true
|
74
|
+
# my-integer-flag-key: 1
|
75
|
+
#
|
76
|
+
# It is also possible to specify both "flags" and "flagValues", if you want some flags
|
77
|
+
# to have simple values and others to have complex behavior. However, it is an error to use the
|
78
|
+
# same flag key or segment key more than once, either in a single file or across multiple files.
|
79
|
+
#
|
80
|
+
# If the data source encounters any error in any file-- malformed content, a missing file, or a
|
81
|
+
# duplicate key-- it will not load flags from any of the files.
|
82
|
+
#
|
83
|
+
module FileData
|
84
|
+
#
|
85
|
+
# Returns a factory for the file data source component.
|
86
|
+
#
|
87
|
+
# @param options [Hash] the configuration options
|
88
|
+
# @option options [Array] :paths The paths of the source files for loading flag data. These
|
89
|
+
# may be absolute paths or relative to the current working directory.
|
90
|
+
# @option options [Boolean] :auto_update True if the data source should watch for changes to
|
91
|
+
# the source file(s) and reload flags whenever there is a change. Auto-updating will only
|
92
|
+
# work if all of the files you specified have valid directory paths at startup time.
|
93
|
+
# Note that the default implementation of this feature is based on polling the filesystem,
|
94
|
+
# which may not perform well. If you install the 'listen' gem (not included by default, to
|
95
|
+
# avoid adding unwanted dependencies to the SDK), its native file watching mechanism will be
|
96
|
+
# used instead. However, 'listen' will not be used in JRuby 9.1 due to a known instability.
|
97
|
+
# @option options [Float] :poll_interval The minimum interval, in seconds, between checks for
|
98
|
+
# file modifications - used only if auto_update is true, and if the native file-watching
|
99
|
+
# mechanism from 'listen' is not being used. The default value is 1 second.
|
100
|
+
# @return an object that can be stored in {Config#data_source}
|
101
|
+
#
|
102
|
+
def self.data_source(options={})
|
103
|
+
lambda { |sdk_key, config|
|
104
|
+
Impl::Integrations::FileDataSourceImpl.new(config.feature_store, config.logger, options) }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -1,7 +1,15 @@
|
|
1
|
-
require "ldclient-rb/
|
1
|
+
require "ldclient-rb/impl/integrations/redis_impl"
|
2
2
|
|
3
3
|
module LaunchDarkly
|
4
4
|
module Integrations
|
5
|
+
#
|
6
|
+
# Integration with [Redis](https://redis.io/).
|
7
|
+
#
|
8
|
+
# Note that in order to use this integration, you must first install the `redis` and `connection-pool`
|
9
|
+
# gems.
|
10
|
+
#
|
11
|
+
# @since 5.5.0
|
12
|
+
#
|
5
13
|
module Redis
|
6
14
|
#
|
7
15
|
# Default value for the `redis_url` option for {new_feature_store}. This points to an instance of
|
@@ -50,8 +58,40 @@ module LaunchDarkly
|
|
50
58
|
# lifecycle to be independent of the SDK client
|
51
59
|
# @return [LaunchDarkly::Interfaces::FeatureStore] a feature store object
|
52
60
|
#
|
53
|
-
def self.new_feature_store(opts)
|
54
|
-
|
61
|
+
def self.new_feature_store(opts = {})
|
62
|
+
LaunchDarkly::Impl::Integrations::Redis::RedisFeatureStore.new(opts)
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Creates a Redis-backed Big Segment store.
|
67
|
+
#
|
68
|
+
# Big Segments are a specific type of segments. For more information, read the LaunchDarkly
|
69
|
+
# documentation: https://docs.launchdarkly.com/home/users/big-segments
|
70
|
+
#
|
71
|
+
# To use this method, you must first have the `redis` and `connection-pool` gems installed. Then,
|
72
|
+
# put the object returned by this method into the `store` property of your Big Segments configuration
|
73
|
+
# (see `Config`).
|
74
|
+
#
|
75
|
+
# @example Configuring Big Segments
|
76
|
+
# store = LaunchDarkly::Integrations::Redis::new_big_segment_store(redis_url: "redis://my-server")
|
77
|
+
# config = LaunchDarkly::Config.new(big_segments: LaunchDarkly::BigSegmentsConfig.new(store: store)
|
78
|
+
# client = LaunchDarkly::LDClient.new(my_sdk_key, config)
|
79
|
+
#
|
80
|
+
# @param opts [Hash] the configuration options (these are all the same as for `new_feature_store`,
|
81
|
+
# except that there are no caching parameters)
|
82
|
+
# @option opts [String] :redis_url (default_redis_url) URL of the Redis instance (shortcut for omitting `redis_opts`)
|
83
|
+
# @option opts [Hash] :redis_opts options to pass to the Redis constructor (if you want to specify more than just `redis_url`)
|
84
|
+
# @option opts [String] :prefix (default_prefix) namespace prefix to add to all hash keys used by LaunchDarkly
|
85
|
+
# @option opts [Logger] :logger a `Logger` instance; defaults to `Config.default_logger`
|
86
|
+
# @option opts [Integer] :max_connections size of the Redis connection pool
|
87
|
+
# @option opts [Object] :pool custom connection pool, if desired
|
88
|
+
# @option opts [Boolean] :pool_shutdown_on_close whether calling `close` should shutdown the custom connection pool;
|
89
|
+
# this is true by default, and should be set to false only if you are managing the pool yourself and want its
|
90
|
+
# lifecycle to be independent of the SDK client
|
91
|
+
# @return [LaunchDarkly::Interfaces::BigSegmentStore] a Big Segment store object
|
92
|
+
#
|
93
|
+
def self.new_big_segment_store(opts)
|
94
|
+
LaunchDarkly::Impl::Integrations::Redis::RedisBigSegmentStore.new(opts)
|
55
95
|
end
|
56
96
|
end
|
57
97
|
end
|