configcat 3.1.0 → 5.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/configcat/autopollingcachepolicy.rb +9 -4
- data/lib/configcat/configcache.rb +5 -5
- data/lib/configcat/configcatclient.rb +122 -36
- data/lib/configcat/configfetcher.rb +100 -11
- data/lib/configcat/constants.rb +17 -0
- data/lib/configcat/datagovernance.rb +10 -0
- data/lib/configcat/interfaces.rb +2 -2
- data/lib/configcat/lazyloadingcachepolicy.rb +7 -4
- data/lib/configcat/localdictionarydatasource.rb +20 -0
- data/lib/configcat/localfiledatasource.rb +49 -0
- data/lib/configcat/manualpollingcachepolicy.rb +5 -3
- data/lib/configcat/overridedatasource.rb +32 -0
- data/lib/configcat/rolloutevaluator.rb +129 -121
- data/lib/configcat/user.rb +19 -0
- data/lib/configcat/version.rb +1 -1
- data/lib/configcat.rb +68 -17
- metadata +11 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 980bb5bf4d92b6a479a10727b25ea488461317569f3b60943642db8fa9960898
|
4
|
+
data.tar.gz: 9de2dc2d9fe881372f70e862bb72af396a9ea99b81defabf3c82e1b19423078e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d1e19a4729ef760d686a4e47c2efd2d82464adfa652caf2782b4ebcfefc3edc7f27baddfdcdd9e66264ee0cd13d9806a51b8c171d74cdd21b6088073aff0368
|
7
|
+
data.tar.gz: 1640d5a7a23f9af9461ee38bfb6a789cbed6d898c582bb325f18c27b20dec7944c60dbbe9dc749800a0325be844a252225dfb769f89acd157f644bb1d1a93ef9
|
@@ -1,9 +1,10 @@
|
|
1
1
|
require 'configcat/interfaces'
|
2
|
+
require 'configcat/constants'
|
2
3
|
require 'concurrent'
|
3
4
|
|
4
5
|
module ConfigCat
|
5
6
|
class AutoPollingCachePolicy < CachePolicy
|
6
|
-
def initialize(config_fetcher, config_cache, poll_interval_seconds=60, max_init_wait_time_seconds=5, on_configuration_changed_callback=nil)
|
7
|
+
def initialize(config_fetcher, config_cache, cache_key, poll_interval_seconds=60, max_init_wait_time_seconds=5, on_configuration_changed_callback=nil)
|
7
8
|
if poll_interval_seconds < 1
|
8
9
|
poll_interval_seconds = 1
|
9
10
|
end
|
@@ -12,6 +13,7 @@ module ConfigCat
|
|
12
13
|
end
|
13
14
|
@_config_fetcher = config_fetcher
|
14
15
|
@_config_cache = config_cache
|
16
|
+
@_cache_key = cache_key
|
15
17
|
@_poll_interval_seconds = poll_interval_seconds
|
16
18
|
@_max_init_wait_time_seconds = max_init_wait_time_seconds
|
17
19
|
@_on_configuration_changed_callback = on_configuration_changed_callback
|
@@ -40,7 +42,7 @@ module ConfigCat
|
|
40
42
|
end
|
41
43
|
begin
|
42
44
|
@_lock.acquire_read_lock()
|
43
|
-
return @_config_cache.get()
|
45
|
+
return @_config_cache.get(@_cache_key)
|
44
46
|
ensure
|
45
47
|
@_lock.release_read_lock()
|
46
48
|
end
|
@@ -52,7 +54,7 @@ module ConfigCat
|
|
52
54
|
|
53
55
|
begin
|
54
56
|
@_lock.acquire_read_lock()
|
55
|
-
old_configuration = @_config_cache.get()
|
57
|
+
old_configuration = @_config_cache.get(@_cache_key)
|
56
58
|
ensure
|
57
59
|
@_lock.release_read_lock()
|
58
60
|
end
|
@@ -62,7 +64,7 @@ module ConfigCat
|
|
62
64
|
if configuration != old_configuration
|
63
65
|
begin
|
64
66
|
@_lock.acquire_write_lock()
|
65
|
-
@_config_cache.set(configuration)
|
67
|
+
@_config_cache.set(@_cache_key, configuration)
|
66
68
|
@_initialized = true
|
67
69
|
ensure
|
68
70
|
@_lock.release_write_lock()
|
@@ -80,6 +82,9 @@ module ConfigCat
|
|
80
82
|
if !@_initialized && !old_configuration.equal?(nil)
|
81
83
|
@_initialized = true
|
82
84
|
end
|
85
|
+
rescue Timeout::Error => e
|
86
|
+
ConfigCat.logger.error("Request timed out. Timeout values: [open: %ss, read: %ss]" %
|
87
|
+
[@_config_fetcher.get_open_timeout(), @_config_fetcher.get_read_timeout()])
|
83
88
|
rescue Exception => e
|
84
89
|
ConfigCat.logger.error("Double-check your SDK Key at https://app.configcat.com/sdkkey.")
|
85
90
|
ConfigCat.logger.error "threw exception #{e.class}:'#{e}'"
|
@@ -3,15 +3,15 @@ require 'configcat/interfaces'
|
|
3
3
|
module ConfigCat
|
4
4
|
class InMemoryConfigCache < ConfigCache
|
5
5
|
def initialize()
|
6
|
-
@_value =
|
6
|
+
@_value = {}
|
7
7
|
end
|
8
8
|
|
9
|
-
def get()
|
10
|
-
return @_value
|
9
|
+
def get(key)
|
10
|
+
return @_value.fetch(key, nil)
|
11
11
|
end
|
12
12
|
|
13
|
-
def set(value)
|
14
|
-
@_value = value
|
13
|
+
def set(key, value)
|
14
|
+
@_value[key] = value
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -5,25 +5,43 @@ require 'configcat/autopollingcachepolicy'
|
|
5
5
|
require 'configcat/manualpollingcachepolicy'
|
6
6
|
require 'configcat/lazyloadingcachepolicy'
|
7
7
|
require 'configcat/rolloutevaluator'
|
8
|
+
require 'configcat/datagovernance'
|
9
|
+
|
8
10
|
|
9
11
|
module ConfigCat
|
10
12
|
KeyValue = Struct.new(:key, :value)
|
11
13
|
class ConfigCatClient
|
14
|
+
@@sdk_keys = []
|
15
|
+
|
12
16
|
def initialize(sdk_key,
|
13
|
-
poll_interval_seconds:60,
|
14
|
-
max_init_wait_time_seconds:5,
|
15
|
-
on_configuration_changed_callback:nil,
|
16
|
-
cache_time_to_live_seconds:60,
|
17
|
-
config_cache_class:nil,
|
18
|
-
base_url:nil,
|
19
|
-
proxy_address:nil,
|
20
|
-
proxy_port:nil,
|
21
|
-
proxy_user:nil,
|
22
|
-
proxy_pass:nil
|
17
|
+
poll_interval_seconds: 60,
|
18
|
+
max_init_wait_time_seconds: 5,
|
19
|
+
on_configuration_changed_callback: nil,
|
20
|
+
cache_time_to_live_seconds: 60,
|
21
|
+
config_cache_class: nil,
|
22
|
+
base_url: nil,
|
23
|
+
proxy_address: nil,
|
24
|
+
proxy_port: nil,
|
25
|
+
proxy_user: nil,
|
26
|
+
proxy_pass: nil,
|
27
|
+
open_timeout: 10,
|
28
|
+
read_timeout: 30,
|
29
|
+
flag_overrides: nil,
|
30
|
+
data_governance: DataGovernance::GLOBAL)
|
23
31
|
if sdk_key === nil
|
24
32
|
raise ConfigCatClientException, "SDK Key is required."
|
25
33
|
end
|
34
|
+
|
35
|
+
if @@sdk_keys.include?(sdk_key)
|
36
|
+
ConfigCat.logger.warn("A ConfigCat Client is already initialized with sdk_key %s. "\
|
37
|
+
"We strongly recommend you to use the ConfigCat Client as "\
|
38
|
+
"a Singleton object in your application." % sdk_key)
|
39
|
+
else
|
40
|
+
@@sdk_keys.push(sdk_key)
|
41
|
+
end
|
42
|
+
|
26
43
|
@_sdk_key = sdk_key
|
44
|
+
@_override_data_source = flag_overrides
|
27
45
|
|
28
46
|
if config_cache_class
|
29
47
|
@_config_cache = config_cache_class.new()
|
@@ -31,23 +49,35 @@ module ConfigCat
|
|
31
49
|
@_config_cache = InMemoryConfigCache.new()
|
32
50
|
end
|
33
51
|
|
34
|
-
if
|
35
|
-
@_config_fetcher =
|
36
|
-
@_cache_policy =
|
52
|
+
if !@_override_data_source.equal?(nil) && @_override_data_source.get_behaviour() == OverrideBehaviour::LOCAL_ONLY
|
53
|
+
@_config_fetcher = nil
|
54
|
+
@_cache_policy = nil
|
55
|
+
elsif poll_interval_seconds > 0
|
56
|
+
@_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "p", base_url: base_url,
|
57
|
+
proxy_address: proxy_address, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass,
|
58
|
+
open_timeout: open_timeout, read_timeout: read_timeout,
|
59
|
+
data_governance: data_governance)
|
60
|
+
@_cache_policy = AutoPollingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key(), poll_interval_seconds, max_init_wait_time_seconds, on_configuration_changed_callback)
|
61
|
+
elsif cache_time_to_live_seconds > 0
|
62
|
+
@_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "l", base_url: base_url,
|
63
|
+
proxy_address: proxy_address, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass,
|
64
|
+
open_timeout: open_timeout, read_timeout: read_timeout,
|
65
|
+
data_governance: data_governance)
|
66
|
+
@_cache_policy = LazyLoadingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key(), cache_time_to_live_seconds)
|
37
67
|
else
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
@_cache_policy = ManualPollingCachePolicy.new(@_config_fetcher, @_config_cache)
|
44
|
-
end
|
68
|
+
@_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "m", base_url: base_url,
|
69
|
+
proxy_address: proxy_address, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass,
|
70
|
+
open_timeout: open_timeout, read_timeout: read_timeout,
|
71
|
+
data_governance: data_governance)
|
72
|
+
@_cache_policy = ManualPollingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key())
|
45
73
|
end
|
46
74
|
end
|
47
75
|
|
48
76
|
def get_value(key, default_value, user=nil)
|
49
|
-
config =
|
77
|
+
config = _get_settings()
|
50
78
|
if config === nil
|
79
|
+
ConfigCat.logger.warn("Evaluating get_value('%s') failed. Cache is empty. "\
|
80
|
+
"Returning default_value in your get_value call: [%s]." % [key, default_value.to_s])
|
51
81
|
return default_value
|
52
82
|
end
|
53
83
|
value, variation_id = RolloutEvaluator.evaluate(key, user, default_value, nil, config)
|
@@ -55,15 +85,19 @@ module ConfigCat
|
|
55
85
|
end
|
56
86
|
|
57
87
|
def get_all_keys()
|
58
|
-
config =
|
88
|
+
config = _get_settings()
|
59
89
|
if config === nil
|
60
90
|
return []
|
61
91
|
end
|
62
|
-
|
92
|
+
feature_flags = config.fetch(FEATURE_FLAGS, nil)
|
93
|
+
if feature_flags === nil
|
94
|
+
return []
|
95
|
+
end
|
96
|
+
return feature_flags.keys
|
63
97
|
end
|
64
98
|
|
65
99
|
def get_variation_id(key, default_variation_id, user=nil)
|
66
|
-
config =
|
100
|
+
config = _get_settings()
|
67
101
|
if config === nil
|
68
102
|
ConfigCat.logger.warn("Evaluating get_variation_id('%s') failed. Cache is empty. "\
|
69
103
|
"Returning default_variation_id in your get_variation_id call: [%s]." %
|
@@ -87,39 +121,91 @@ module ConfigCat
|
|
87
121
|
end
|
88
122
|
|
89
123
|
def get_key_and_value(variation_id)
|
90
|
-
config =
|
124
|
+
config = _get_settings()
|
91
125
|
if config === nil
|
92
126
|
ConfigCat.logger.warn("Evaluating get_variation_id('%s') failed. Cache is empty. Returning nil." % variation_id)
|
93
127
|
return nil
|
94
128
|
end
|
95
|
-
|
96
|
-
|
97
|
-
|
129
|
+
|
130
|
+
feature_flags = config.fetch(FEATURE_FLAGS, nil)
|
131
|
+
if feature_flags === nil
|
132
|
+
ConfigCat.logger.warn("Evaluating get_key_and_value('%s') failed. Cache is empty. Returning None." % variation_id)
|
133
|
+
return nil
|
134
|
+
end
|
135
|
+
|
136
|
+
for key, value in feature_flags
|
137
|
+
if variation_id == value.fetch(VARIATION_ID, nil)
|
138
|
+
return KeyValue.new(key, value[VALUE])
|
98
139
|
end
|
99
140
|
|
100
|
-
rollout_rules = value.fetch(
|
141
|
+
rollout_rules = value.fetch(ROLLOUT_RULES, [])
|
101
142
|
for rollout_rule in rollout_rules
|
102
|
-
if variation_id == rollout_rule.fetch(
|
103
|
-
return KeyValue.new(key, rollout_rule[
|
143
|
+
if variation_id == rollout_rule.fetch(VARIATION_ID, nil)
|
144
|
+
return KeyValue.new(key, rollout_rule[VALUE])
|
104
145
|
end
|
105
146
|
end
|
106
147
|
|
107
|
-
rollout_percentage_items = value.fetch(
|
148
|
+
rollout_percentage_items = value.fetch(ROLLOUT_PERCENTAGE_ITEMS, [])
|
108
149
|
for rollout_percentage_item in rollout_percentage_items
|
109
|
-
if variation_id == rollout_percentage_item.fetch(
|
110
|
-
return KeyValue.new(key, rollout_percentage_item[
|
150
|
+
if variation_id == rollout_percentage_item.fetch(VARIATION_ID, nil)
|
151
|
+
return KeyValue.new(key, rollout_percentage_item[VALUE])
|
111
152
|
end
|
112
153
|
end
|
113
154
|
end
|
114
155
|
end
|
115
156
|
|
157
|
+
def get_all_values(user: nil)
|
158
|
+
keys = get_all_keys()
|
159
|
+
all_values = {}
|
160
|
+
for key in keys
|
161
|
+
value = get_value(key, nil, user)
|
162
|
+
if !value.equal?(nil)
|
163
|
+
all_values[key] = value
|
164
|
+
end
|
165
|
+
end
|
166
|
+
return all_values
|
167
|
+
end
|
168
|
+
|
116
169
|
def force_refresh()
|
117
170
|
@_cache_policy.force_refresh()
|
118
171
|
end
|
119
172
|
|
120
173
|
def stop()
|
121
|
-
@_cache_policy.stop()
|
122
|
-
@_config_fetcher.close()
|
174
|
+
@_cache_policy.stop() if @_cache_policy
|
175
|
+
@_config_fetcher.close() if @_config_fetcher
|
176
|
+
@@sdk_keys.delete(@_sdk_key)
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def _get_settings()
|
182
|
+
if !@_override_data_source.nil?
|
183
|
+
behaviour = @_override_data_source.get_behaviour()
|
184
|
+
if behaviour == OverrideBehaviour::LOCAL_ONLY
|
185
|
+
return @_override_data_source.get_overrides()
|
186
|
+
elsif behaviour == OverrideBehaviour::REMOTE_OVER_LOCAL
|
187
|
+
remote_settings = @_cache_policy.get()
|
188
|
+
local_settings = @_override_data_source.get_overrides()
|
189
|
+
result = local_settings.clone()
|
190
|
+
if remote_settings.key?(FEATURE_FLAGS) && local_settings.key?(FEATURE_FLAGS)
|
191
|
+
result[FEATURE_FLAGS] = result[FEATURE_FLAGS].merge(remote_settings[FEATURE_FLAGS])
|
192
|
+
end
|
193
|
+
return result
|
194
|
+
elsif behaviour == OverrideBehaviour::LOCAL_OVER_REMOTE
|
195
|
+
remote_settings = @_cache_policy.get()
|
196
|
+
local_settings = @_override_data_source.get_overrides()
|
197
|
+
result = remote_settings.clone()
|
198
|
+
if remote_settings.key?(FEATURE_FLAGS) && local_settings.key?(FEATURE_FLAGS)
|
199
|
+
result[FEATURE_FLAGS] = result[FEATURE_FLAGS].merge(local_settings[FEATURE_FLAGS])
|
200
|
+
end
|
201
|
+
return result
|
202
|
+
end
|
203
|
+
end
|
204
|
+
return @_cache_policy.get()
|
205
|
+
end
|
206
|
+
|
207
|
+
def _get_cache_key()
|
208
|
+
return Digest::SHA1.hexdigest("ruby_" + CONFIG_FILE_NAME + "_" + @_sdk_key)
|
123
209
|
end
|
124
210
|
|
125
211
|
end
|
@@ -1,13 +1,22 @@
|
|
1
1
|
require 'configcat/interfaces'
|
2
2
|
require 'configcat/version'
|
3
|
+
require 'configcat/datagovernance'
|
4
|
+
require 'configcat/constants'
|
3
5
|
require 'net/http'
|
4
6
|
require 'uri'
|
5
7
|
require 'json'
|
6
8
|
|
7
9
|
module ConfigCat
|
8
|
-
|
10
|
+
BASE_URL_GLOBAL = "https://cdn-global.configcat.com"
|
11
|
+
BASE_URL_EU_ONLY = "https://cdn-eu.configcat.com"
|
9
12
|
BASE_PATH = "configuration-files/"
|
10
|
-
BASE_EXTENSION = "/
|
13
|
+
BASE_EXTENSION = "/" + CONFIG_FILE_NAME + ".json"
|
14
|
+
|
15
|
+
class RedirectMode
|
16
|
+
NO_REDIRECT = 0
|
17
|
+
SHOULD_REDIRECT = 1
|
18
|
+
FORCE_REDIRECT = 2
|
19
|
+
end
|
11
20
|
|
12
21
|
class FetchResponse
|
13
22
|
def initialize(response)
|
@@ -32,34 +41,100 @@ module ConfigCat
|
|
32
41
|
end
|
33
42
|
|
34
43
|
class CacheControlConfigFetcher < ConfigFetcher
|
35
|
-
def initialize(sdk_key, mode, base_url
|
44
|
+
def initialize(sdk_key, mode, base_url:nil, proxy_address:nil, proxy_port:nil, proxy_user:nil, proxy_pass:nil,
|
45
|
+
open_timeout:10, read_timeout:30,
|
46
|
+
data_governance:DataGovernance::GLOBAL)
|
36
47
|
@_sdk_key = sdk_key
|
48
|
+
@_proxy_address = proxy_address
|
49
|
+
@_proxy_port = proxy_port
|
50
|
+
@_proxy_user = proxy_user
|
51
|
+
@_proxy_pass = proxy_pass
|
52
|
+
@_open_timeout = open_timeout
|
53
|
+
@_read_timeout = read_timeout
|
37
54
|
@_etag = ""
|
38
55
|
@_headers = {"User-Agent" => ((("ConfigCat-Ruby/") + mode) + ("-")) + VERSION, "X-ConfigCat-UserAgent" => ((("ConfigCat-Ruby/") + mode) + ("-")) + VERSION, "Content-Type" => "application/json"}
|
39
56
|
if !base_url.equal?(nil)
|
57
|
+
@_base_url_overridden = true
|
40
58
|
@_base_url = base_url.chomp("/")
|
41
59
|
else
|
42
|
-
@
|
60
|
+
@_base_url_overridden = false
|
61
|
+
if data_governance == DataGovernance::EU_ONLY
|
62
|
+
@_base_url = BASE_URL_EU_ONLY
|
63
|
+
else
|
64
|
+
@_base_url = BASE_URL_GLOBAL
|
65
|
+
end
|
43
66
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
@
|
48
|
-
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_open_timeout()
|
70
|
+
return @_open_timeout
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_read_timeout()
|
74
|
+
return @_read_timeout
|
49
75
|
end
|
50
76
|
|
51
77
|
# Returns the FetchResponse object contains configuration json Dictionary
|
52
|
-
def get_configuration_json()
|
78
|
+
def get_configuration_json(retries=0)
|
53
79
|
ConfigCat.logger.debug "Fetching configuration from ConfigCat"
|
54
80
|
uri = URI.parse((((@_base_url + ("/")) + BASE_PATH) + @_sdk_key) + BASE_EXTENSION)
|
55
81
|
headers = @_headers
|
56
82
|
headers["If-None-Match"] = @_etag unless @_etag.empty?
|
83
|
+
_create_http()
|
57
84
|
request = Net::HTTP::Get.new(uri.request_uri, headers)
|
58
85
|
response = @_http.request(request)
|
59
86
|
etag = response["ETag"]
|
60
87
|
@_etag = etag unless etag.nil? || etag.empty?
|
61
88
|
ConfigCat.logger.debug "ConfigCat configuration json fetch response code:#{response.code} Cached:#{response['ETag']}"
|
62
|
-
|
89
|
+
fetch_response = FetchResponse.new(response)
|
90
|
+
|
91
|
+
# If there wasn't a config change, we return the response.
|
92
|
+
if !fetch_response.is_fetched()
|
93
|
+
return fetch_response
|
94
|
+
end
|
95
|
+
|
96
|
+
preferences = fetch_response.json().fetch(PREFERENCES, nil)
|
97
|
+
if preferences === nil
|
98
|
+
return fetch_response
|
99
|
+
end
|
100
|
+
|
101
|
+
base_url = preferences.fetch(BASE_URL, nil)
|
102
|
+
|
103
|
+
# If the base_url is the same as the last called one, just return the response.
|
104
|
+
if base_url.equal?(nil) || @_base_url == base_url
|
105
|
+
return fetch_response
|
106
|
+
end
|
107
|
+
|
108
|
+
redirect = preferences.fetch(REDIRECT, nil)
|
109
|
+
# If the base_url is overridden, and the redirect parameter is not 2 (force),
|
110
|
+
# the SDK should not redirect the calls and it just have to return the response.
|
111
|
+
if @_base_url_overridden && redirect != RedirectMode::FORCE_REDIRECT
|
112
|
+
return fetch_response
|
113
|
+
end
|
114
|
+
|
115
|
+
# The next call should use the base_url provided in the config json
|
116
|
+
@_base_url = base_url
|
117
|
+
|
118
|
+
# If the redirect property == 0 (redirect not needed), return the response
|
119
|
+
if redirect == RedirectMode::NO_REDIRECT
|
120
|
+
# Return the response
|
121
|
+
return fetch_response
|
122
|
+
end
|
123
|
+
|
124
|
+
# Try to download again with the new url
|
125
|
+
|
126
|
+
if redirect == RedirectMode::SHOULD_REDIRECT
|
127
|
+
ConfigCat.logger.warn("Your data_governance parameter at ConfigCatClient initialization is not in sync with your preferences on the ConfigCat Dashboard: https://app.configcat.com/organization/data-governance. Only Organization Admins can set this preference.")
|
128
|
+
end
|
129
|
+
|
130
|
+
# To prevent loops we check if we retried at least 3 times with the new base_url
|
131
|
+
if retries >= 2
|
132
|
+
ConfigCat.logger.error("Redirect loop during config.json fetch. Please contact support@configcat.com.")
|
133
|
+
return fetch_response
|
134
|
+
end
|
135
|
+
|
136
|
+
# Retry the config download with the new base_url
|
137
|
+
return get_configuration_json(retries + 1)
|
63
138
|
end
|
64
139
|
|
65
140
|
def close()
|
@@ -67,5 +142,19 @@ module ConfigCat
|
|
67
142
|
@_http = nil
|
68
143
|
end
|
69
144
|
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def _create_http()
|
149
|
+
uri = URI.parse(@_base_url)
|
150
|
+
use_ssl = true if uri.scheme == 'https'
|
151
|
+
if @_http.equal?(nil) || @_http.address != uri.host || @_http.port != uri.port || @_http.use_ssl? != use_ssl
|
152
|
+
close()
|
153
|
+
@_http = Net::HTTP.new(uri.host, uri.port, @_proxy_address, @_proxy_port, @_proxy_user, @_proxy_pass)
|
154
|
+
@_http.use_ssl = use_ssl
|
155
|
+
@_http.open_timeout = @_open_timeout
|
156
|
+
@_http.read_timeout = @_read_timeout
|
157
|
+
end
|
158
|
+
end
|
70
159
|
end
|
71
160
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ConfigCat
|
2
|
+
CONFIG_FILE_NAME = "config_v5"
|
3
|
+
|
4
|
+
PREFERENCES = "p"
|
5
|
+
BASE_URL = "u"
|
6
|
+
REDIRECT = "r"
|
7
|
+
|
8
|
+
FEATURE_FLAGS = "f"
|
9
|
+
VALUE = "v"
|
10
|
+
COMPARATOR = "t"
|
11
|
+
COMPARISON_ATTRIBUTE = "a"
|
12
|
+
COMPARISON_VALUE = "c"
|
13
|
+
ROLLOUT_PERCENTAGE_ITEMS = "p"
|
14
|
+
PERCENTAGE = "p"
|
15
|
+
ROLLOUT_RULES = "r"
|
16
|
+
VARIATION_ID = "i"
|
17
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module ConfigCat
|
2
|
+
class DataGovernance
|
3
|
+
# Control the location of the config.json files containing your feature flags
|
4
|
+
# and settings within the ConfigCat CDN.
|
5
|
+
# Global: Select this if your feature flags are published to all global CDN nodes.
|
6
|
+
# EuOnly: Select this if your feature flags are published to CDN nodes only in the EU.
|
7
|
+
GLOBAL = 0
|
8
|
+
EU_ONLY = 1
|
9
|
+
end
|
10
|
+
end
|
data/lib/configcat/interfaces.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
require 'configcat/interfaces'
|
2
|
+
require 'configcat/constants'
|
2
3
|
require 'concurrent'
|
3
4
|
|
5
|
+
|
4
6
|
module ConfigCat
|
5
7
|
class LazyLoadingCachePolicy < CachePolicy
|
6
8
|
|
7
|
-
def initialize(config_fetcher, config_cache, cache_time_to_live_seconds=60)
|
9
|
+
def initialize(config_fetcher, config_cache, cache_key, cache_time_to_live_seconds=60)
|
8
10
|
if cache_time_to_live_seconds < 1
|
9
11
|
cache_time_to_live_seconds = 1
|
10
12
|
end
|
11
13
|
@_config_fetcher = config_fetcher
|
12
14
|
@_config_cache = config_cache
|
15
|
+
@_cache_key = cache_key
|
13
16
|
@_cache_time_to_live = cache_time_to_live_seconds
|
14
17
|
@_lock = Concurrent::ReadWriteLock.new()
|
15
18
|
@_last_updated = nil
|
@@ -20,7 +23,7 @@ module ConfigCat
|
|
20
23
|
@_lock.acquire_read_lock()
|
21
24
|
utc_now = Time.now.utc
|
22
25
|
if !@_last_updated.equal?(nil) && (@_last_updated + @_cache_time_to_live > utc_now)
|
23
|
-
config = @_config_cache.get()
|
26
|
+
config = @_config_cache.get(@_cache_key)
|
24
27
|
if !config.equal?(nil)
|
25
28
|
return config
|
26
29
|
end
|
@@ -31,7 +34,7 @@ module ConfigCat
|
|
31
34
|
force_refresh()
|
32
35
|
begin
|
33
36
|
@_lock.acquire_read_lock()
|
34
|
-
config = @_config_cache.get()
|
37
|
+
config = @_config_cache.get(@_cache_key)
|
35
38
|
return config
|
36
39
|
ensure
|
37
40
|
@_lock.release_read_lock()
|
@@ -45,7 +48,7 @@ module ConfigCat
|
|
45
48
|
configuration = configuration_response.json()
|
46
49
|
begin
|
47
50
|
@_lock.acquire_write_lock()
|
48
|
-
@_config_cache.set(configuration)
|
51
|
+
@_config_cache.set(@_cache_key, configuration)
|
49
52
|
@_last_updated = Time.now.utc
|
50
53
|
ensure
|
51
54
|
@_lock.release_write_lock()
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'configcat/overridedatasource'
|
2
|
+
require 'configcat/constants'
|
3
|
+
|
4
|
+
|
5
|
+
module ConfigCat
|
6
|
+
class LocalDictionaryDataSource < OverrideDataSource
|
7
|
+
def initialize(source, override_behaviour)
|
8
|
+
super(override_behaviour)
|
9
|
+
dictionary = {}
|
10
|
+
source.each do |key, value|
|
11
|
+
dictionary[key] = {VALUE => value}
|
12
|
+
end
|
13
|
+
@_settings = {FEATURE_FLAGS => dictionary}
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_overrides()
|
17
|
+
return @_settings
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'configcat/overridedatasource'
|
2
|
+
require 'configcat/constants'
|
3
|
+
|
4
|
+
|
5
|
+
module ConfigCat
|
6
|
+
class LocalFileDataSource < OverrideDataSource
|
7
|
+
def initialize(file_path, override_behaviour)
|
8
|
+
super(override_behaviour)
|
9
|
+
if File.exists?(file_path)
|
10
|
+
ConfigCat.logger.error("The file '%s' does not exists." % file_path)
|
11
|
+
end
|
12
|
+
@_file_path = file_path
|
13
|
+
@_settings = nil
|
14
|
+
@_cached_file_stamp = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_overrides()
|
18
|
+
reload_file_content()
|
19
|
+
return @_settings
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def reload_file_content()
|
25
|
+
begin
|
26
|
+
stamp = File.mtime(@_file_path)
|
27
|
+
if stamp != @_cached_file_stamp
|
28
|
+
@_cached_file_stamp = stamp
|
29
|
+
file = File.read(@_file_path)
|
30
|
+
data = JSON.parse(file)
|
31
|
+
if data.key?("flags")
|
32
|
+
dictionary = {}
|
33
|
+
source = data["flags"]
|
34
|
+
source.each do |key, value|
|
35
|
+
dictionary[key] = {VALUE => value}
|
36
|
+
end
|
37
|
+
@_settings = {FEATURE_FLAGS => dictionary}
|
38
|
+
else
|
39
|
+
@_settings = data
|
40
|
+
end
|
41
|
+
end
|
42
|
+
rescue JSON::ParserError => e
|
43
|
+
ConfigCat.logger.error("Could not decode json from file %s. %s" % [@_file_path, e.to_s])
|
44
|
+
rescue Exception => e
|
45
|
+
ConfigCat.logger.error("Could not read the content of the file %s. %s" % [@_file_path, e.to_s])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,18 +1,20 @@
|
|
1
1
|
require 'configcat/interfaces'
|
2
|
+
require 'configcat/constants'
|
2
3
|
require 'concurrent'
|
3
4
|
|
4
5
|
module ConfigCat
|
5
6
|
class ManualPollingCachePolicy < CachePolicy
|
6
|
-
def initialize(config_fetcher, config_cache)
|
7
|
+
def initialize(config_fetcher, config_cache, cache_key)
|
7
8
|
@_config_fetcher = config_fetcher
|
8
9
|
@_config_cache = config_cache
|
10
|
+
@_cache_key = cache_key
|
9
11
|
@_lock = Concurrent::ReadWriteLock.new()
|
10
12
|
end
|
11
13
|
|
12
14
|
def get()
|
13
15
|
begin
|
14
16
|
@_lock.acquire_read_lock()
|
15
|
-
config = @_config_cache.get()
|
17
|
+
config = @_config_cache.get(@_cache_key)
|
16
18
|
return config
|
17
19
|
ensure
|
18
20
|
@_lock.release_read_lock()
|
@@ -26,7 +28,7 @@ module ConfigCat
|
|
26
28
|
configuration = configuration_response.json()
|
27
29
|
begin
|
28
30
|
@_lock.acquire_write_lock()
|
29
|
-
@_config_cache.set(configuration)
|
31
|
+
@_config_cache.set(@_cache_key, configuration)
|
30
32
|
ensure
|
31
33
|
@_lock.release_write_lock()
|
32
34
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ConfigCat
|
2
|
+
class OverrideBehaviour
|
3
|
+
# When evaluating values, the SDK will not use feature flags & settings from the ConfigCat CDN, but it will use
|
4
|
+
# all feature flags & settings that are loaded from local-override sources.
|
5
|
+
LOCAL_ONLY = 0
|
6
|
+
|
7
|
+
# When evaluating values, the SDK will use all feature flags & settings that are downloaded from the ConfigCat CDN,
|
8
|
+
# plus all feature flags & settings that are loaded from local-override sources. If a feature flag or a setting is
|
9
|
+
# defined both in the fetched and the local-override source then the local-override version will take precedence.
|
10
|
+
LOCAL_OVER_REMOTE = 1
|
11
|
+
|
12
|
+
# When evaluating values, the SDK will use all feature flags & settings that are downloaded from the ConfigCat CDN,
|
13
|
+
# plus all feature flags & settings that are loaded from local-override sources. If a feature flag or a setting is
|
14
|
+
# defined both in the fetched and the local-override source then the fetched version will take precedence.
|
15
|
+
REMOTE_OVER_LOCAL = 2
|
16
|
+
end
|
17
|
+
|
18
|
+
class OverrideDataSource
|
19
|
+
def initialize(override_behaviour)
|
20
|
+
@_override_behaviour = override_behaviour
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_behaviour()
|
24
|
+
return @_override_behaviour
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_overrides()
|
28
|
+
# :returns the override dictionary
|
29
|
+
return {}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'configcat/user'
|
2
|
+
require 'configcat/constants'
|
2
3
|
require 'digest'
|
3
4
|
require 'semantic'
|
4
5
|
|
@@ -6,21 +7,18 @@ module ConfigCat
|
|
6
7
|
class RolloutEvaluator
|
7
8
|
COMPARATOR_TEXTS = ["IS ONE OF", "IS NOT ONE OF", "CONTAINS", "DOES NOT CONTAIN", "IS ONE OF (SemVer)", "IS NOT ONE OF (SemVer)", "< (SemVer)", "<= (SemVer)", "> (SemVer)", ">= (SemVer)", "= (Number)", "<> (Number)", "< (Number)", "<= (Number)", "> (Number)", ">= (Number)"]
|
8
9
|
|
9
|
-
VALUE = "v"
|
10
|
-
COMPARATOR = "t"
|
11
|
-
COMPARISON_ATTRIBUTE = "a"
|
12
|
-
COMPARISON_VALUE = "c"
|
13
|
-
ROLLOUT_PERCENTAGE_ITEMS = "p"
|
14
|
-
PERCENTAGE = "p"
|
15
|
-
ROLLOUT_RULES = "r"
|
16
|
-
VARIATION_ID = "i"
|
17
|
-
|
18
10
|
def self.evaluate(key, user, default_value, default_variation_id, config)
|
19
11
|
ConfigCat.logger.info("Evaluating get_value('%s')." % key)
|
20
12
|
|
21
|
-
|
13
|
+
feature_flags = config.fetch(FEATURE_FLAGS, nil)
|
14
|
+
if feature_flags === nil
|
15
|
+
ConfigCat.logger.error("Evaluating get_value('%s') failed. Value not found for key '%s' Returning default_value: [%s]." % [key, key, default_value.to_s])
|
16
|
+
return default_value, default_variation_id
|
17
|
+
end
|
18
|
+
|
19
|
+
setting_descriptor = feature_flags.fetch(key, nil)
|
22
20
|
if setting_descriptor === nil
|
23
|
-
ConfigCat.logger.error("Evaluating get_value('%s') failed. Value not found for key '%s'. Returning default_value: [%s]. Here are the available keys: %s" % [key, key, default_value.to_s,
|
21
|
+
ConfigCat.logger.error("Evaluating get_value('%s') failed. Value not found for key '%s'. Returning default_value: [%s]. Here are the available keys: %s" % [key, key, default_value.to_s, feature_flags.keys.join(", ")])
|
24
22
|
return default_value, default_variation_id
|
25
23
|
end
|
26
24
|
|
@@ -41,132 +39,142 @@ module ConfigCat
|
|
41
39
|
return return_value, return_variation_id
|
42
40
|
end
|
43
41
|
|
44
|
-
|
42
|
+
log_entries = ["Evaluating get_value('%s')." % key, "User object:\n%s" % user.to_s]
|
45
43
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
44
|
+
begin
|
45
|
+
# Evaluate targeting rules
|
46
|
+
for rollout_rule in rollout_rules
|
47
|
+
comparison_attribute = rollout_rule.fetch(COMPARISON_ATTRIBUTE)
|
48
|
+
comparison_value = rollout_rule.fetch(COMPARISON_VALUE, nil)
|
49
|
+
comparator = rollout_rule.fetch(COMPARATOR, nil)
|
51
50
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
51
|
+
user_value = user.get_attribute(comparison_attribute)
|
52
|
+
if user_value === nil || !user_value
|
53
|
+
log_entries.push(format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value))
|
54
|
+
next
|
55
|
+
end
|
57
56
|
|
58
|
-
|
59
|
-
|
57
|
+
value = rollout_rule.fetch(VALUE, nil)
|
58
|
+
variation_id = rollout_rule.fetch(VARIATION_ID, default_variation_id)
|
60
59
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
return value, variation_id
|
66
|
-
end
|
67
|
-
# IS NOT ONE OF
|
68
|
-
elsif comparator == 1
|
69
|
-
if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s)
|
70
|
-
ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
71
|
-
return value, variation_id
|
72
|
-
end
|
73
|
-
# CONTAINS
|
74
|
-
elsif comparator == 2
|
75
|
-
if user_value.to_s.include?(comparison_value.to_s)
|
76
|
-
ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
77
|
-
return value, variation_id
|
78
|
-
end
|
79
|
-
# DOES NOT CONTAIN
|
80
|
-
elsif comparator == 3
|
81
|
-
if !user_value.to_s.include?(comparison_value.to_s)
|
82
|
-
ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
83
|
-
return value, variation_id
|
84
|
-
end
|
85
|
-
# IS ONE OF, IS NOT ONE OF (Semantic version)
|
86
|
-
elsif (4 <= comparator) && (comparator <= 5)
|
87
|
-
begin
|
88
|
-
match = false
|
89
|
-
user_value_version = Semantic::Version.new(user_value.to_s.strip())
|
90
|
-
((comparison_value.to_s.split(",").map { |x| x.strip() }).reject { |c| c.empty? }).each { |x|
|
91
|
-
version = Semantic::Version.new(x)
|
92
|
-
match = (user_value_version == version) || match
|
93
|
-
}
|
94
|
-
if match && comparator == 4 || !match && comparator == 5
|
95
|
-
ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
60
|
+
# IS ONE OF
|
61
|
+
if comparator == 0
|
62
|
+
if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s)
|
63
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
96
64
|
return value, variation_id
|
97
65
|
end
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
# LESS THAN, LESS THAN OR EQUALS TO, GREATER THAN, GREATER THAN OR EQUALS TO (Semantic version)
|
103
|
-
elsif (6 <= comparator) && (comparator <= 9)
|
104
|
-
begin
|
105
|
-
user_value_version = Semantic::Version.new(user_value.to_s.strip())
|
106
|
-
comparison_value_version = Semantic::Version.new(comparison_value.to_s.strip())
|
107
|
-
if (comparator == 6 && user_value_version < comparison_value_version) ||
|
108
|
-
(comparator == 7 && user_value_version <= comparison_value_version) ||
|
109
|
-
(comparator == 8 && user_value_version > comparison_value_version) ||
|
110
|
-
(comparator == 9 && user_value_version >= comparison_value_version)
|
111
|
-
ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
66
|
+
# IS NOT ONE OF
|
67
|
+
elsif comparator == 1
|
68
|
+
if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s)
|
69
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
112
70
|
return value, variation_id
|
113
71
|
end
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
if (
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
72
|
+
# CONTAINS
|
73
|
+
elsif comparator == 2
|
74
|
+
if user_value.to_s.include?(comparison_value.to_s)
|
75
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
76
|
+
return value, variation_id
|
77
|
+
end
|
78
|
+
# DOES NOT CONTAIN
|
79
|
+
elsif comparator == 3
|
80
|
+
if !user_value.to_s.include?(comparison_value.to_s)
|
81
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
82
|
+
return value, variation_id
|
83
|
+
end
|
84
|
+
# IS ONE OF, IS NOT ONE OF (Semantic version)
|
85
|
+
elsif (4 <= comparator) && (comparator <= 5)
|
86
|
+
begin
|
87
|
+
match = false
|
88
|
+
user_value_version = Semantic::Version.new(user_value.to_s.strip())
|
89
|
+
((comparison_value.to_s.split(",").map { |x| x.strip() }).reject { |c| c.empty? }).each { |x|
|
90
|
+
version = Semantic::Version.new(x)
|
91
|
+
match = (user_value_version == version) || match
|
92
|
+
}
|
93
|
+
if match && comparator == 4 || !match && comparator == 5
|
94
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
95
|
+
return value, variation_id
|
96
|
+
end
|
97
|
+
rescue ArgumentError => e
|
98
|
+
message = format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s)
|
99
|
+
ConfigCat.logger.warn(message)
|
100
|
+
log_entries.push(message)
|
101
|
+
next
|
102
|
+
end
|
103
|
+
# LESS THAN, LESS THAN OR EQUALS TO, GREATER THAN, GREATER THAN OR EQUALS TO (Semantic version)
|
104
|
+
elsif (6 <= comparator) && (comparator <= 9)
|
105
|
+
begin
|
106
|
+
user_value_version = Semantic::Version.new(user_value.to_s.strip())
|
107
|
+
comparison_value_version = Semantic::Version.new(comparison_value.to_s.strip())
|
108
|
+
if (comparator == 6 && user_value_version < comparison_value_version) ||
|
109
|
+
(comparator == 7 && user_value_version <= comparison_value_version) ||
|
110
|
+
(comparator == 8 && user_value_version > comparison_value_version) ||
|
111
|
+
(comparator == 9 && user_value_version >= comparison_value_version)
|
112
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
113
|
+
return value, variation_id
|
114
|
+
end
|
115
|
+
rescue ArgumentError => e
|
116
|
+
message = format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s)
|
117
|
+
ConfigCat.logger.warn(message)
|
118
|
+
log_entries.push(message)
|
119
|
+
next
|
120
|
+
end
|
121
|
+
elsif (10 <= comparator) && (comparator <= 15)
|
122
|
+
begin
|
123
|
+
user_value_float = Float(user_value.to_s.gsub(",", "."))
|
124
|
+
comparison_value_float = Float(comparison_value.to_s.gsub(",", "."))
|
125
|
+
if (comparator == 10 && user_value_float == comparison_value_float) ||
|
126
|
+
(comparator == 11 && user_value_float != comparison_value_float) ||
|
127
|
+
(comparator == 12 && user_value_float < comparison_value_float) ||
|
128
|
+
(comparator == 13 && user_value_float <= comparison_value_float) ||
|
129
|
+
(comparator == 14 && user_value_float > comparison_value_float) ||
|
130
|
+
(comparator == 15 && user_value_float >= comparison_value_float)
|
131
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
132
|
+
return value, variation_id
|
133
|
+
end
|
134
|
+
rescue Exception => e
|
135
|
+
message = format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s)
|
136
|
+
ConfigCat.logger.warn(message)
|
137
|
+
log_entries.push(message)
|
138
|
+
next
|
139
|
+
end
|
140
|
+
# IS ONE OF (Sensitive)
|
141
|
+
elsif comparator == 16
|
142
|
+
if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s)
|
143
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
144
|
+
return value, variation_id
|
145
|
+
end
|
146
|
+
# IS NOT ONE OF (Sensitive)
|
147
|
+
elsif comparator == 17
|
148
|
+
if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s)
|
149
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
129
150
|
return value, variation_id
|
130
151
|
end
|
131
|
-
rescue Exception => e
|
132
|
-
ConfigCat.logger.warn(format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s))
|
133
|
-
next
|
134
|
-
end
|
135
|
-
# IS ONE OF (Sensitive)
|
136
|
-
elsif comparator == 16
|
137
|
-
if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s)
|
138
|
-
ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
139
|
-
return value, variation_id
|
140
|
-
end
|
141
|
-
# IS NOT ONE OF (Sensitive)
|
142
|
-
elsif comparator == 17
|
143
|
-
if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s)
|
144
|
-
ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
145
|
-
return value, variation_id
|
146
152
|
end
|
153
|
+
log_entries.push(format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value))
|
147
154
|
end
|
148
|
-
ConfigCat.logger.info(format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value))
|
149
|
-
end
|
150
155
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
156
|
+
if rollout_percentage_items.size > 0
|
157
|
+
user_key = user.get_identifier()
|
158
|
+
hash_candidate = ("%s%s" % [key, user_key]).encode("utf-8")
|
159
|
+
hash_val = Digest::SHA1.hexdigest(hash_candidate)[0...7].to_i(base=16) % 100
|
160
|
+
bucket = 0
|
161
|
+
for rollout_percentage_item in rollout_percentage_items || []
|
162
|
+
bucket += rollout_percentage_item.fetch(PERCENTAGE, 0)
|
163
|
+
if hash_val < bucket
|
164
|
+
percentage_value = rollout_percentage_item.fetch(VALUE, nil)
|
165
|
+
variation_id = rollout_percentage_item.fetch(VARIATION_ID, default_variation_id)
|
166
|
+
log_entries.push("Evaluating %% options. Returning %s" % percentage_value)
|
167
|
+
return percentage_value, variation_id
|
168
|
+
end
|
163
169
|
end
|
164
170
|
end
|
171
|
+
return_value = setting_descriptor.fetch(VALUE, default_value)
|
172
|
+
return_variation_id = setting_descriptor.fetch(VARIATION_ID, default_variation_id)
|
173
|
+
log_entries.push("Returning %s" % return_value)
|
174
|
+
return return_value, return_variation_id
|
175
|
+
ensure
|
176
|
+
ConfigCat.logger.info(log_entries.join("\n"))
|
165
177
|
end
|
166
|
-
return_value = setting_descriptor.fetch(VALUE, default_value)
|
167
|
-
return_variation_id = setting_descriptor.fetch(VARIATION_ID, default_variation_id)
|
168
|
-
ConfigCat.logger.info("Returning %s" % return_value)
|
169
|
-
return return_value, return_variation_id
|
170
178
|
end
|
171
179
|
|
172
180
|
private
|
data/lib/configcat/user.rb
CHANGED
@@ -32,6 +32,25 @@ module ConfigCat
|
|
32
32
|
end
|
33
33
|
return nil
|
34
34
|
end
|
35
|
+
|
36
|
+
def to_s()
|
37
|
+
r = %Q({\n "Identifier": "#{@__identifier}")
|
38
|
+
if !@__data["Email"].equal?(nil)
|
39
|
+
r += %Q(,\n "Email": "#{@__data["Email"]}")
|
40
|
+
end
|
41
|
+
if !@__data["Country"].equal?(nil)
|
42
|
+
r += %Q(,\n "Country": "#{@__data["Country"]}")
|
43
|
+
end
|
44
|
+
if !@__custom.equal?(nil)
|
45
|
+
r += %Q(,\n "Custom": {)
|
46
|
+
for customField in @__custom
|
47
|
+
r += %Q(\n "#{customField}": "#{@__custom[customField]}",)
|
48
|
+
end
|
49
|
+
r += "\n }"
|
50
|
+
end
|
51
|
+
r += "\n}"
|
52
|
+
return r
|
53
|
+
end
|
35
54
|
end
|
36
55
|
|
37
56
|
end
|
data/lib/configcat/version.rb
CHANGED
data/lib/configcat.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'configcat/interfaces'
|
2
|
+
require 'configcat/localdictionarydatasource'
|
3
|
+
require 'configcat/localfiledatasource'
|
2
4
|
require 'configcat/configcatclient'
|
3
5
|
require 'configcat/user'
|
4
6
|
require 'logger'
|
@@ -10,13 +12,17 @@ module ConfigCat
|
|
10
12
|
attr_accessor :logger
|
11
13
|
end
|
12
14
|
|
13
|
-
def ConfigCat.create_client(sdk_key)
|
15
|
+
def ConfigCat.create_client(sdk_key, data_governance: DataGovernance::GLOBAL)
|
14
16
|
#
|
15
17
|
# Create an instance of ConfigCatClient and setup Auto Poll mode with default options
|
16
18
|
#
|
17
19
|
# :param sdk_key: ConfigCat SDK Key to access your configuration.
|
20
|
+
# :param data_governance:
|
21
|
+
# Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard:
|
22
|
+
# https://app.configcat.com/organization/data-governance
|
23
|
+
# (Only Organization Admins have access)
|
18
24
|
#
|
19
|
-
return create_client_with_auto_poll(sdk_key)
|
25
|
+
return create_client_with_auto_poll(sdk_key, data_governance: data_governance)
|
20
26
|
end
|
21
27
|
|
22
28
|
def ConfigCat.create_client_with_auto_poll(sdk_key,
|
@@ -25,10 +31,14 @@ module ConfigCat
|
|
25
31
|
on_configuration_changed_callback: nil,
|
26
32
|
config_cache_class: nil,
|
27
33
|
base_url: nil,
|
28
|
-
proxy_address:nil,
|
29
|
-
proxy_port:nil,
|
30
|
-
proxy_user:nil,
|
31
|
-
proxy_pass:nil
|
34
|
+
proxy_address: nil,
|
35
|
+
proxy_port: nil,
|
36
|
+
proxy_user: nil,
|
37
|
+
proxy_pass: nil,
|
38
|
+
open_timeout: 10,
|
39
|
+
read_timeout: 30,
|
40
|
+
flag_overrides: nil,
|
41
|
+
data_governance: DataGovernance::GLOBAL)
|
32
42
|
#
|
33
43
|
# Create an instance of ConfigCatClient and setup Auto Poll mode with custom options
|
34
44
|
#
|
@@ -43,6 +53,13 @@ module ConfigCat
|
|
43
53
|
# :param proxy_port: Proxy port
|
44
54
|
# :param proxy_user: username for proxy authentication
|
45
55
|
# :param proxy_pass: password for proxy authentication
|
56
|
+
# :param open_timeout: The number of seconds to wait for the server to make the initial connection. Default: 10 seconds.
|
57
|
+
# :param read_timeout: The number of seconds to wait for the server to respond before giving up. Default: 30 seconds.
|
58
|
+
# :param flag_overrides: An OverrideDataSource implementation used to override feature flags & settings.
|
59
|
+
# :param data_governance:
|
60
|
+
# Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard:
|
61
|
+
# https://app.configcat.com/organization/data-governance
|
62
|
+
# (Only Organization Admins have access)
|
46
63
|
#
|
47
64
|
if sdk_key === nil
|
48
65
|
raise ConfigCatClientException, "SDK Key is required."
|
@@ -63,17 +80,25 @@ module ConfigCat
|
|
63
80
|
proxy_address: proxy_address,
|
64
81
|
proxy_port: proxy_port,
|
65
82
|
proxy_user: proxy_user,
|
66
|
-
proxy_pass: proxy_pass
|
83
|
+
proxy_pass: proxy_pass,
|
84
|
+
open_timeout: open_timeout,
|
85
|
+
read_timeout: read_timeout,
|
86
|
+
flag_overrides: flag_overrides,
|
87
|
+
data_governance: data_governance)
|
67
88
|
end
|
68
89
|
|
69
90
|
def ConfigCat.create_client_with_lazy_load(sdk_key,
|
70
91
|
cache_time_to_live_seconds: 60,
|
71
92
|
config_cache_class: nil,
|
72
93
|
base_url: nil,
|
73
|
-
proxy_address:nil,
|
74
|
-
proxy_port:nil,
|
75
|
-
proxy_user:nil,
|
76
|
-
proxy_pass:nil
|
94
|
+
proxy_address: nil,
|
95
|
+
proxy_port: nil,
|
96
|
+
proxy_user: nil,
|
97
|
+
proxy_pass: nil,
|
98
|
+
open_timeout: 10,
|
99
|
+
read_timeout: 30,
|
100
|
+
flag_overrides: nil,
|
101
|
+
data_governance: DataGovernance::GLOBAL)
|
77
102
|
#
|
78
103
|
# Create an instance of ConfigCatClient and setup Lazy Load mode with custom options
|
79
104
|
#
|
@@ -86,6 +111,13 @@ module ConfigCat
|
|
86
111
|
# :param proxy_port: Proxy port
|
87
112
|
# :param proxy_user: username for proxy authentication
|
88
113
|
# :param proxy_pass: password for proxy authentication
|
114
|
+
# :param open_timeout: The number of seconds to wait for the server to make the initial connection. Default: 10 seconds.
|
115
|
+
# :param read_timeout: The number of seconds to wait for the server to respond before giving up. Default: 30 seconds.
|
116
|
+
# :param flag_overrides: An OverrideDataSource implementation used to override feature flags & settings.
|
117
|
+
# :param data_governance:
|
118
|
+
# Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard:
|
119
|
+
# https://app.configcat.com/organization/data-governance
|
120
|
+
# (Only Organization Admins have access)
|
89
121
|
#
|
90
122
|
if sdk_key === nil
|
91
123
|
raise ConfigCatClientException, "SDK Key is required."
|
@@ -103,16 +135,24 @@ module ConfigCat
|
|
103
135
|
proxy_address: proxy_address,
|
104
136
|
proxy_port: proxy_port,
|
105
137
|
proxy_user: proxy_user,
|
106
|
-
proxy_pass: proxy_pass
|
138
|
+
proxy_pass: proxy_pass,
|
139
|
+
open_timeout: open_timeout,
|
140
|
+
read_timeout: read_timeout,
|
141
|
+
flag_overrides: flag_overrides,
|
142
|
+
data_governance: data_governance)
|
107
143
|
end
|
108
144
|
|
109
145
|
def ConfigCat.create_client_with_manual_poll(sdk_key,
|
110
146
|
config_cache_class: nil,
|
111
147
|
base_url: nil,
|
112
|
-
proxy_address:nil,
|
113
|
-
proxy_port:nil,
|
114
|
-
proxy_user:nil,
|
115
|
-
proxy_pass:nil
|
148
|
+
proxy_address: nil,
|
149
|
+
proxy_port: nil,
|
150
|
+
proxy_user: nil,
|
151
|
+
proxy_pass: nil,
|
152
|
+
open_timeout: 10,
|
153
|
+
read_timeout: 30,
|
154
|
+
flag_overrides: nil,
|
155
|
+
data_governance: DataGovernance::GLOBAL)
|
116
156
|
#
|
117
157
|
# Create an instance of ConfigCatClient and setup Manual Poll mode with custom options
|
118
158
|
#
|
@@ -124,6 +164,13 @@ module ConfigCat
|
|
124
164
|
# :param proxy_port: Proxy port
|
125
165
|
# :param proxy_user: username for proxy authentication
|
126
166
|
# :param proxy_pass: password for proxy authentication
|
167
|
+
# :param open_timeout: The number of seconds to wait for the server to make the initial connection. Default: 10 seconds.
|
168
|
+
# :param read_timeout: The number of seconds to wait for the server to respond before giving up. Default: 30 seconds.
|
169
|
+
# :param flag_overrides: An OverrideDataSource implementation used to override feature flags & settings.
|
170
|
+
# :param data_governance:
|
171
|
+
# Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard:
|
172
|
+
# https://app.configcat.com/organization/data-governance
|
173
|
+
# (Only Organization Admins have access)
|
127
174
|
#
|
128
175
|
if sdk_key === nil
|
129
176
|
raise ConfigCatClientException, "SDK Key is required."
|
@@ -138,7 +185,11 @@ module ConfigCat
|
|
138
185
|
proxy_address: proxy_address,
|
139
186
|
proxy_port: proxy_port,
|
140
187
|
proxy_user: proxy_user,
|
141
|
-
proxy_pass: proxy_pass
|
188
|
+
proxy_pass: proxy_pass,
|
189
|
+
open_timeout: open_timeout,
|
190
|
+
read_timeout: read_timeout,
|
191
|
+
flag_overrides: flag_overrides,
|
192
|
+
data_governance: data_governance)
|
142
193
|
end
|
143
194
|
|
144
195
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: configcat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ConfigCat
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-05-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -67,19 +67,19 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '12.3'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: codecov
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '0.
|
75
|
+
version: '0.5'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '0.
|
82
|
+
version: '0.5'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: webmock
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -110,9 +110,14 @@ files:
|
|
110
110
|
- lib/configcat/configcache.rb
|
111
111
|
- lib/configcat/configcatclient.rb
|
112
112
|
- lib/configcat/configfetcher.rb
|
113
|
+
- lib/configcat/constants.rb
|
114
|
+
- lib/configcat/datagovernance.rb
|
113
115
|
- lib/configcat/interfaces.rb
|
114
116
|
- lib/configcat/lazyloadingcachepolicy.rb
|
117
|
+
- lib/configcat/localdictionarydatasource.rb
|
118
|
+
- lib/configcat/localfiledatasource.rb
|
115
119
|
- lib/configcat/manualpollingcachepolicy.rb
|
120
|
+
- lib/configcat/overridedatasource.rb
|
116
121
|
- lib/configcat/rolloutevaluator.rb
|
117
122
|
- lib/configcat/user.rb
|
118
123
|
- lib/configcat/version.rb
|
@@ -135,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
135
140
|
- !ruby/object:Gem::Version
|
136
141
|
version: '0'
|
137
142
|
requirements: []
|
138
|
-
rubygems_version: 3.0.
|
143
|
+
rubygems_version: 3.0.3.1
|
139
144
|
signing_key:
|
140
145
|
specification_version: 4
|
141
146
|
summary: ConfigCat SDK for Ruby.
|