ldclient-rb 0.0.5 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -1
- data/lib/ldclient-rb/config.rb +74 -2
- data/lib/ldclient-rb/ldclient.rb +123 -28
- data/lib/ldclient-rb/store.rb +23 -0
- data/lib/ldclient-rb/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 034158ae75d84ca63871a57441af1db86c2e7ec7
|
4
|
+
data.tar.gz: 7aaaac7d020178559cd60bc5252e297773c22aa4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a066c4302d2ade5a8092be3871e99d352d2b65ace9aee25438599b44930db12aab50da9b3e62a8abebd2dffa274b1ffe88f7623c0f90fbe477432af234f757c
|
7
|
+
data.tar.gz: 1bd7cc1e215dd8640ad89734fd313027fe578a22dead9761be038172d28f9dbc2f80282b26d60c0b6b7ab6d56eb0dbe81dc85a174a95bd669c2eb74e2caf88a0
|
data/README.md
CHANGED
@@ -4,10 +4,15 @@ LaunchDarkly SDK for Ruby
|
|
4
4
|
Quick setup
|
5
5
|
-----------
|
6
6
|
|
7
|
-
|
7
|
+
0. Install the Ruby SDK with `gem`
|
8
8
|
|
9
9
|
gem install ldclient-rb
|
10
10
|
|
11
|
+
1. Require the LaunchDarkly client:
|
12
|
+
|
13
|
+
require 'ldclient-rb'
|
14
|
+
|
15
|
+
|
11
16
|
2. Create a new LDClient with your API key:
|
12
17
|
|
13
18
|
client = LaunchDarkly::LDClient.new("your_api_key")
|
data/lib/ldclient-rb/config.rb
CHANGED
@@ -1,30 +1,102 @@
|
|
1
1
|
require 'logger'
|
2
2
|
|
3
3
|
module LaunchDarkly
|
4
|
+
|
5
|
+
#
|
6
|
+
# This class exposes advanced configuration options for the LaunchDarkly client library. Most users
|
7
|
+
# will not need to use a custom configuration-- the default configuration sets sane defaults for most use cases.
|
8
|
+
#
|
9
|
+
#
|
4
10
|
class Config
|
11
|
+
#
|
12
|
+
# Constructor for creating custom LaunchDarkly configurations.
|
13
|
+
#
|
14
|
+
# @param opts [Hash] the configuration options
|
15
|
+
# @option opts [Logger] :logger A logger to use for messages from the LaunchDarkly client. Defaults to the Rails logger in a Rails environment, or stdout otherwise.
|
16
|
+
# @option opts [String] :base_uri ("https://app.launchdarkly.com") The base URL for the LaunchDarkly server. Most users should use the default value.
|
17
|
+
# @option opts [Integer] :capacity (10000) The capacity of the events buffer. The client buffers up to this many events in memory before flushing. If the capacity is exceeded before the buffer is flushed, events will be discarded.
|
18
|
+
# @option opts [Integer] :flush_interval (30) The number of seconds between flushes of the event buffer.
|
19
|
+
# @option opts [Object] :store A cache store for the Faraday HTTP caching library. Defaults to the Rails cache in a Rails environment, or a thread-safe in-memory store otherwise.
|
20
|
+
#
|
21
|
+
# @return [type] [description]
|
5
22
|
def initialize(opts = {})
|
6
|
-
@logger = opts[:logger] || Config.default_logger
|
7
23
|
@base_uri = opts[:base_uri] || Config.default_base_uri
|
24
|
+
@capacity = opts[:capacity] || Config.default_capacity
|
25
|
+
@logger = opts[:logger] || Config.default_logger
|
26
|
+
@store = opts[:store] || Config.default_store
|
27
|
+
@flush_interval = opts[:flush_interval] || Config.default_flush_interval
|
8
28
|
end
|
9
29
|
|
30
|
+
#
|
31
|
+
# The base URL for the LaunchDarkly server.
|
32
|
+
#
|
33
|
+
# @return [String] The configured base URL for the LaunchDarkly server.
|
10
34
|
def base_uri
|
11
35
|
@base_uri
|
12
36
|
end
|
13
37
|
|
38
|
+
#
|
39
|
+
# The number of seconds between flushes of the event buffer. Decreasing the flush interval means
|
40
|
+
# that the event buffer is less likely to reach capacity.
|
41
|
+
#
|
42
|
+
# @return [Integer] The configured number of seconds between flushes of the event buffer.
|
43
|
+
def flush_interval
|
44
|
+
@flush_interval
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# The configured logger for the LaunchDarkly client. The client library uses the log to
|
49
|
+
# print warning and error messages.
|
50
|
+
#
|
51
|
+
# @return [Logger] The configured logger
|
14
52
|
def logger
|
15
53
|
@logger
|
16
54
|
end
|
17
55
|
|
56
|
+
#
|
57
|
+
# The capacity of the events buffer. The client buffers up to this many events in memory before flushing. If the capacity is exceeded before the buffer is flushed, events will be discarded.
|
58
|
+
# Increasing the capacity means that events are less likely to be discarded, at the cost of consuming more memory.
|
59
|
+
#
|
60
|
+
# @return [Integer] The configured capacity of the event buffer
|
61
|
+
def capacity
|
62
|
+
@capacity
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# The store for the Faraday HTTP caching library. Stores should respond to 'read' and 'write' requests.
|
67
|
+
#
|
68
|
+
# @return [Object] The configured store for the Faraday HTTP caching library.
|
69
|
+
def store
|
70
|
+
@store
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# The default LaunchDarkly client configuration. This configuration sets reasonable defaults for most users.
|
75
|
+
#
|
76
|
+
# @return [Config] The default LaunchDarkly configuration.
|
18
77
|
def self.default
|
19
|
-
Config.new(
|
78
|
+
Config.new()
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.default_capacity
|
82
|
+
10000
|
20
83
|
end
|
21
84
|
|
22
85
|
def self.default_base_uri
|
23
86
|
"https://app.launchdarkly.com"
|
24
87
|
end
|
25
88
|
|
89
|
+
def self.default_store
|
90
|
+
defined?(Rails) && Rails.respond_to?(:cache) ? Rails.cache : ThreadSafeMemoryStore.new
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.default_flush_interval
|
94
|
+
30
|
95
|
+
end
|
96
|
+
|
26
97
|
def self.default_logger
|
27
98
|
defined?(Rails) && Rails.respond_to?(:logger) ? Rails.logger : ::Logger.new($stdout)
|
28
99
|
end
|
100
|
+
|
29
101
|
end
|
30
102
|
end
|
data/lib/ldclient-rb/ldclient.rb
CHANGED
@@ -1,32 +1,126 @@
|
|
1
1
|
require 'faraday/http_cache'
|
2
2
|
require 'json'
|
3
3
|
require 'digest/sha1'
|
4
|
+
require 'thread'
|
5
|
+
require 'logger'
|
4
6
|
|
5
7
|
module LaunchDarkly
|
8
|
+
#
|
9
|
+
# A client for the LaunchDarkly API. Client instances are thread-safe. Users
|
10
|
+
# should create a single client instance for the lifetime of the application.
|
11
|
+
#
|
12
|
+
#
|
6
13
|
class LDClient
|
7
14
|
|
8
|
-
|
9
|
-
|
15
|
+
#
|
16
|
+
# Creates a new client instance that connects to LaunchDarkly. A custom
|
17
|
+
# configuration parameter can also supplied to specify advanced options,
|
18
|
+
# but for most use cases, the default configuration is appropriate.
|
19
|
+
#
|
20
|
+
#
|
21
|
+
# @param api_key [String] the API key for your LaunchDarkly account
|
22
|
+
# @param config [Config] an optional client configuration object
|
23
|
+
#
|
24
|
+
# @return [LDClient] The LaunchDarkly client instance
|
10
25
|
def initialize(api_key, config = Config.default)
|
11
|
-
|
26
|
+
@queue = Queue.new
|
12
27
|
@api_key = api_key
|
13
28
|
@config = config
|
14
29
|
@client = Faraday.new do |builder|
|
15
|
-
builder.use :http_cache, store: store
|
30
|
+
builder.use :http_cache, store: @config.store
|
16
31
|
|
17
32
|
builder.adapter Faraday.default_adapter
|
18
33
|
end
|
34
|
+
|
35
|
+
Thread.new do
|
36
|
+
while true do
|
37
|
+
events = []
|
38
|
+
num_events = @queue.length()
|
39
|
+
num_events.times do
|
40
|
+
events << @queue.pop()
|
41
|
+
end
|
42
|
+
|
43
|
+
if !events.empty?()
|
44
|
+
res =
|
45
|
+
@client.post (@config.base_uri + "/api/events/bulk") do |req|
|
46
|
+
req.headers['Authorization'] = 'api_key ' + @api_key
|
47
|
+
req.headers['User-Agent'] = 'RubyClient/' + LaunchDarkly::VERSION
|
48
|
+
req.headers['Content-Type'] = 'application/json'
|
49
|
+
req.body = events.to_json
|
50
|
+
end
|
51
|
+
if res.status != 200
|
52
|
+
@config.logger.error("Unexpected status code while processing events: " + res.status)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
sleep(@config.flush_interval)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
19
60
|
end
|
20
61
|
|
62
|
+
#
|
63
|
+
# Calculates the value of a feature flag for a given user. At a minimum, the user hash
|
64
|
+
# should contain a +:key+ .
|
65
|
+
#
|
66
|
+
# @example Basic user hash
|
67
|
+
# {:key => "user@example.com"}
|
68
|
+
#
|
69
|
+
# For authenticated users, the +:key+ should be the unique identifier for your user. For anonymous users,
|
70
|
+
# the +:key+ should be a session identifier or cookie. In either case, the only requirement is that the key
|
71
|
+
# is unique to a user.
|
72
|
+
#
|
73
|
+
# You can also pass IP addresses and country codes in the user hash.
|
74
|
+
#
|
75
|
+
# @example More complete user hash
|
76
|
+
# {:key => "user@example.com", :ip => "127.0.0.1", :country => "US"}
|
77
|
+
#
|
78
|
+
# Countries should be sent as ISO 3166-1 alpha-2 codes.
|
79
|
+
#
|
80
|
+
# The user hash can contain arbitrary custom attributes stored in a +:custom+ sub-hash:
|
81
|
+
#
|
82
|
+
# @example A user hash with custom attributes
|
83
|
+
# {:key => "user@example.com", :custom => {:customer_rank => 1000, :groups => ["google", "microsoft"]}}
|
84
|
+
#
|
85
|
+
# Attribute values in the custom hash can be integers, booleans, strings, or lists of integers, booleans, or strings.
|
86
|
+
#
|
87
|
+
# @param key [String] the unique feature key for the feature flag, as shown on the LaunchDarkly dashboard
|
88
|
+
# @param user [Hash] a hash containing parameters for the end user requesting the flag
|
89
|
+
# @param default=false [Boolean] the default value of the flag
|
90
|
+
#
|
91
|
+
# @return [Boolean] whether or not the flag should be enabled, or the default value if the flag is disabled on the LaunchDarkly control panel
|
21
92
|
def get_flag?(key, user, default=false)
|
22
93
|
begin
|
23
|
-
get_flag_int(key, user, default)
|
94
|
+
value = get_flag_int(key, user, default)
|
95
|
+
add_event({:kind => 'feature', :key => key, :user => user, :value => value})
|
96
|
+
return value
|
24
97
|
rescue StandardError => error
|
25
98
|
@config.logger.error("Unhandled exception in get_flag: " + error.message)
|
26
99
|
default
|
27
100
|
end
|
28
101
|
end
|
29
102
|
|
103
|
+
def add_event(event)
|
104
|
+
if @queue.length() < @config.capacity
|
105
|
+
event[:creationDate] = (Time.now.to_f * 1000).to_i
|
106
|
+
@queue.push(event)
|
107
|
+
else
|
108
|
+
@config.logger.warn("Exceeded event queue capacity. Increase capacity to avoid dropping events.")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# Tracks that a user performed an event
|
114
|
+
#
|
115
|
+
# @param event_name [String] The name of the event
|
116
|
+
# @param user [Hash] The user that performed the event. This should be the same user hash used in calls to {#get_flag?}
|
117
|
+
# @param data [Hash] A hash containing any additional data associated with the event
|
118
|
+
#
|
119
|
+
# @return [void]
|
120
|
+
def send_event(event_name, user, data)
|
121
|
+
add_event({:kind => 'custom', :key => event_name, :user => user, :data => data })
|
122
|
+
end
|
123
|
+
|
30
124
|
def get_flag_int(key, user, default)
|
31
125
|
|
32
126
|
unless user
|
@@ -56,7 +150,7 @@ module LaunchDarkly
|
|
56
150
|
end
|
57
151
|
|
58
152
|
|
59
|
-
feature = JSON.parse(res.body)
|
153
|
+
feature = JSON.parse(res.body, :symbolize_names => true)
|
60
154
|
|
61
155
|
val = evaluate(feature, user)
|
62
156
|
|
@@ -64,43 +158,44 @@ module LaunchDarkly
|
|
64
158
|
end
|
65
159
|
|
66
160
|
def param_for_user(feature, user)
|
67
|
-
if user.has_key?
|
68
|
-
id_hash = user[
|
161
|
+
if user.has_key? :key
|
162
|
+
id_hash = user[:key]
|
69
163
|
else
|
70
164
|
return nil
|
71
165
|
end
|
72
166
|
|
73
|
-
if user.has_key?
|
74
|
-
id_hash += '.' + user[
|
167
|
+
if user.has_key? :secondary
|
168
|
+
id_hash += '.' + user[:secondary]
|
75
169
|
end
|
76
170
|
|
77
|
-
hash_key = "%s.%s.%s" % [feature[
|
171
|
+
hash_key = "%s.%s.%s" % [feature[:key], feature[:salt], id_hash]
|
172
|
+
|
78
173
|
hash_val = (Digest::SHA1.hexdigest(hash_key))[0..14]
|
79
|
-
return hash_val.to_i(16) /
|
174
|
+
return hash_val.to_i(16) / Float(0xFFFFFFFFFFFFFFF)
|
80
175
|
end
|
81
176
|
|
82
177
|
def match_target?(target, user)
|
83
|
-
attrib = target[
|
178
|
+
attrib = target[:attribute].to_sym
|
84
179
|
|
85
|
-
if attrib ==
|
180
|
+
if attrib == :key or attrib == :ip or attrib == :country
|
86
181
|
if user[attrib]
|
87
182
|
u_value = user[attrib]
|
88
|
-
return target[
|
183
|
+
return target[:values].include? u_value
|
89
184
|
else
|
90
185
|
return false
|
91
186
|
end
|
92
187
|
else # custom attribute
|
93
|
-
unless user.has_key?
|
188
|
+
unless user.has_key? :custom
|
94
189
|
return false
|
95
190
|
end
|
96
|
-
unless user[
|
191
|
+
unless user[:custom].include? attrib
|
97
192
|
return false
|
98
193
|
end
|
99
|
-
u_value = user[
|
194
|
+
u_value = user[:custom][attrib]
|
100
195
|
if u_value.is_a? String or u_value.is_a? Numeric
|
101
|
-
return target[
|
196
|
+
return target[:values].include? u_value
|
102
197
|
elsif u_value.is_a? Array
|
103
|
-
return ! ((target[
|
198
|
+
return ! ((target[:values] & u_value).empty?)
|
104
199
|
end
|
105
200
|
|
106
201
|
return false
|
@@ -109,7 +204,7 @@ module LaunchDarkly
|
|
109
204
|
end
|
110
205
|
|
111
206
|
def match_variation?(variation, user)
|
112
|
-
variation[
|
207
|
+
variation[:targets].each do |target|
|
113
208
|
if match_target?(target, user)
|
114
209
|
return true
|
115
210
|
end
|
@@ -118,7 +213,7 @@ module LaunchDarkly
|
|
118
213
|
end
|
119
214
|
|
120
215
|
def evaluate(feature, user)
|
121
|
-
unless feature[
|
216
|
+
unless feature[:on]
|
122
217
|
return nil
|
123
218
|
end
|
124
219
|
|
@@ -128,18 +223,18 @@ module LaunchDarkly
|
|
128
223
|
return nil
|
129
224
|
end
|
130
225
|
|
131
|
-
feature[
|
226
|
+
feature[:variations].each do |variation|
|
132
227
|
if match_variation?(variation, user)
|
133
|
-
return variation[
|
228
|
+
return variation[:value]
|
134
229
|
end
|
135
230
|
end
|
136
231
|
|
137
232
|
total = 0.0
|
138
|
-
feature[
|
139
|
-
total += variation[
|
233
|
+
feature[:variations].each do |variation|
|
234
|
+
total += variation[:weight].to_f / 100.0
|
140
235
|
|
141
236
|
if param < total
|
142
|
-
return variation[
|
237
|
+
return variation[:value]
|
143
238
|
end
|
144
239
|
end
|
145
240
|
|
@@ -147,7 +242,7 @@ module LaunchDarkly
|
|
147
242
|
|
148
243
|
end
|
149
244
|
|
150
|
-
private :get_flag_int, :param_for_user, :match_target?, :match_variation?, :evaluate
|
245
|
+
private :add_event, :get_flag_int, :param_for_user, :match_target?, :match_variation?, :evaluate
|
151
246
|
|
152
247
|
|
153
248
|
end
|
data/lib/ldclient-rb/store.rb
CHANGED
@@ -1,15 +1,38 @@
|
|
1
1
|
require 'thread_safe'
|
2
2
|
|
3
3
|
module LaunchDarkly
|
4
|
+
|
5
|
+
# A thread-safe in-memory store suitable for use
|
6
|
+
# with the Faraday caching HTTP client. Uses the
|
7
|
+
# Threadsafe gem as the underlying cache.
|
8
|
+
#
|
9
|
+
# @see https://github.com/plataformatec/faraday-http-cache
|
10
|
+
# @see https://github.com/ruby-concurrency/thread_safe
|
11
|
+
#
|
4
12
|
class ThreadSafeMemoryStore
|
13
|
+
#
|
14
|
+
# Default constructor
|
15
|
+
#
|
16
|
+
# @return [ThreadSafeMemoryStore] a new store
|
5
17
|
def initialize
|
6
18
|
@cache = ThreadSafe::Cache.new
|
7
19
|
end
|
8
20
|
|
21
|
+
#
|
22
|
+
# Read a value from the cache
|
23
|
+
# @param key [Object] the cache key
|
24
|
+
#
|
25
|
+
# @return [Object] the cache value
|
9
26
|
def read(key)
|
10
27
|
@cache[key]
|
11
28
|
end
|
12
29
|
|
30
|
+
#
|
31
|
+
# Store a value in the cache
|
32
|
+
# @param key [Object] the cache key
|
33
|
+
# @param value [Object] the value to associate with the key
|
34
|
+
#
|
35
|
+
# @return [Object] the value
|
13
36
|
def write(key, value)
|
14
37
|
@cache[key] = value
|
15
38
|
end
|
data/lib/ldclient-rb/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ldclient-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Catamorphic Co
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-10-
|
11
|
+
date: 2014-10-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -137,3 +137,4 @@ signing_key:
|
|
137
137
|
specification_version: 4
|
138
138
|
summary: LaunchDarkly SDK for Ruby
|
139
139
|
test_files: []
|
140
|
+
has_rdoc:
|