prefab-cloud-ruby 0.13.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/Gemfile +4 -11
- data/Gemfile.lock +32 -52
- data/README.md +1 -19
- data/Rakefile +0 -1
- data/VERSION +1 -1
- data/compile_protos.sh +0 -3
- data/lib/prefab/auth_interceptor.rb +0 -1
- data/lib/prefab/cancellable_interceptor.rb +0 -1
- data/lib/prefab/client.rb +36 -49
- data/lib/prefab/config_client.rb +73 -145
- data/lib/prefab/config_loader.rb +13 -98
- data/lib/prefab/config_resolver.rb +49 -56
- data/lib/prefab/feature_flag_client.rb +11 -129
- data/lib/prefab/logger_client.rb +8 -10
- data/lib/prefab/murmer3.rb +0 -1
- data/lib/prefab/noop_cache.rb +0 -1
- data/lib/prefab/noop_stats.rb +0 -1
- data/lib/prefab/ratelimit_client.rb +0 -1
- data/lib/prefab-cloud-ruby.rb +0 -10
- data/lib/prefab_pb.rb +132 -214
- data/lib/prefab_services_pb.rb +6 -35
- data/prefab-cloud-ruby.gemspec +11 -30
- data/test/.prefab.test.config.yaml +1 -27
- data/test/test_config_loader.rb +25 -39
- data/test/test_config_resolver.rb +38 -134
- data/test/test_feature_flag_client.rb +35 -277
- data/test/test_helper.rb +4 -70
- data/test/test_logger.rb +29 -23
- metadata +15 -70
- data/.github/workflows/ruby.yml +0 -39
- data/.tool-versions +0 -1
- data/CODEOWNERS +0 -1
- data/lib/prefab/config_helper.rb +0 -29
- data/lib/prefab/error.rb +0 -6
- data/lib/prefab/errors/initialization_timeout_error.rb +0 -13
- data/lib/prefab/errors/invalid_api_key_error.rb +0 -19
- data/lib/prefab/errors/missing_default_error.rb +0 -13
- data/lib/prefab/internal_logger.rb +0 -29
- data/lib/prefab/options.rb +0 -82
- data/run_test_harness_server.sh +0 -8
- data/test/harness_server.rb +0 -64
- data/test/test_client.rb +0 -91
- data/test/test_config_client.rb +0 -56
data/lib/prefab/config_client.rb
CHANGED
@@ -1,48 +1,36 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
1
|
module Prefab
|
3
2
|
class ConfigClient
|
4
|
-
include Prefab::ConfigHelper
|
5
|
-
|
6
3
|
RECONNECT_WAIT = 5
|
7
4
|
DEFAULT_CHECKPOINT_FREQ_SEC = 60
|
8
5
|
DEFAULT_S3CF_BUCKET = 'http://d2j4ed6ti5snnd.cloudfront.net'
|
9
|
-
SSE_READ_TIMEOUT = 300
|
10
6
|
|
11
7
|
def initialize(base_client, timeout)
|
12
8
|
@base_client = base_client
|
13
|
-
@options = base_client.options
|
14
|
-
@base_client.log_internal Logger::DEBUG, "Initialize ConfigClient"
|
15
9
|
@timeout = timeout
|
16
|
-
|
17
|
-
@stream_lock = Concurrent::ReadWriteLock.new
|
10
|
+
@initialization_lock = Concurrent::ReadWriteLock.new
|
18
11
|
|
19
12
|
@checkpoint_freq_secs = DEFAULT_CHECKPOINT_FREQ_SEC
|
20
13
|
|
21
14
|
@config_loader = Prefab::ConfigLoader.new(@base_client)
|
22
15
|
@config_resolver = Prefab::ConfigResolver.new(@base_client, @config_loader)
|
23
16
|
|
24
|
-
@initialization_lock = Concurrent::ReadWriteLock.new
|
25
|
-
@base_client.log_internal Logger::DEBUG, "Initialize ConfigClient: AcquireWriteLock"
|
26
17
|
@initialization_lock.acquire_write_lock
|
27
|
-
@base_client.log_internal Logger::DEBUG, "Initialize ConfigClient: AcquiredWriteLock"
|
28
|
-
@initialized_future = Concurrent::Future.execute { @initialization_lock.acquire_read_lock }
|
29
18
|
|
30
19
|
@cancellable_interceptor = Prefab::CancellableInterceptor.new(@base_client)
|
31
20
|
|
32
21
|
@s3_cloud_front = ENV["PREFAB_S3CF_BUCKET"] || DEFAULT_S3CF_BUCKET
|
33
|
-
|
34
|
-
|
35
|
-
finish_init!(:local_only)
|
36
|
-
else
|
37
|
-
load_checkpoint
|
38
|
-
start_checkpointing_thread
|
39
|
-
start_streaming
|
40
|
-
end
|
22
|
+
load_checkpoint
|
23
|
+
start_checkpointing_thread
|
41
24
|
end
|
42
25
|
|
43
26
|
def start_streaming
|
44
|
-
@
|
45
|
-
|
27
|
+
@streaming = true
|
28
|
+
start_api_connection_thread(@config_loader.highwater_mark)
|
29
|
+
end
|
30
|
+
|
31
|
+
def get(prop)
|
32
|
+
@initialization_lock.with_read_lock do
|
33
|
+
@config_resolver.get(prop)
|
46
34
|
end
|
47
35
|
end
|
48
36
|
|
@@ -55,7 +43,7 @@ module Prefab
|
|
55
43
|
|
56
44
|
@base_client.request Prefab::ConfigService, :upsert, req_options: { timeout: @timeout }, params: upsert_req
|
57
45
|
@base_client.stats.increment("prefab.config.upsert")
|
58
|
-
@config_loader.set(config_delta
|
46
|
+
@config_loader.set(config_delta)
|
59
47
|
@config_loader.rm(previous_key) if previous_key&.present?
|
60
48
|
@config_resolver.update
|
61
49
|
end
|
@@ -70,48 +58,12 @@ module Prefab
|
|
70
58
|
end
|
71
59
|
|
72
60
|
def self.value_to_delta(key, config_value, namespace = nil)
|
73
|
-
Prefab::
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
def get(key, default=Prefab::Client::NO_DEFAULT_PROVIDED)
|
78
|
-
config = _get(key)
|
79
|
-
config ? value_of(config[:value]) : handle_default(key, default)
|
80
|
-
end
|
81
|
-
|
82
|
-
def get_config_obj(key)
|
83
|
-
config = _get(key)
|
84
|
-
config ? config[:config] : nil
|
61
|
+
Prefab::ConfigDelta.new(key: [namespace, key].compact.join(":"),
|
62
|
+
value: config_value)
|
85
63
|
end
|
86
64
|
|
87
65
|
private
|
88
66
|
|
89
|
-
def handle_default(key, default)
|
90
|
-
if default != Prefab::Client::NO_DEFAULT_PROVIDED
|
91
|
-
return default
|
92
|
-
end
|
93
|
-
|
94
|
-
if @options.on_no_default == Prefab::Options::ON_NO_DEFAULT::RAISE
|
95
|
-
raise Prefab::Errors::MissingDefaultError.new(key)
|
96
|
-
end
|
97
|
-
|
98
|
-
nil
|
99
|
-
end
|
100
|
-
|
101
|
-
def _get(key)
|
102
|
-
# wait timeout sec for the initalization to be complete
|
103
|
-
@initialized_future.value(@options.initialization_timeout_sec)
|
104
|
-
if @initialized_future.incomplete?
|
105
|
-
if @options.on_init_failure == Prefab::Options::ON_INITIALIZATION_FAILURE::RETURN
|
106
|
-
@base_client.log_internal Logger::WARN, "Couldn't Initialize In #{@options.initialization_timeout_sec}. Key #{key}. Returning what we have"
|
107
|
-
@initialization_lock.release_write_lock
|
108
|
-
else
|
109
|
-
raise Prefab::Errors::InitializationTimeoutError.new(@options.initialization_timeout_sec, key)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
@config_resolver._get(key)
|
113
|
-
end
|
114
|
-
|
115
67
|
def stub
|
116
68
|
@_stub = Prefab::ConfigService::Stub.new(nil,
|
117
69
|
nil,
|
@@ -119,92 +71,53 @@ module Prefab
|
|
119
71
|
interceptors: [@base_client.interceptor, @cancellable_interceptor])
|
120
72
|
end
|
121
73
|
|
122
|
-
#
|
74
|
+
# Bootstrap out of the cache
|
75
|
+
# returns the high-watermark of what was in the cache
|
123
76
|
def load_checkpoint
|
124
|
-
success =
|
125
|
-
|
126
|
-
if success
|
127
|
-
return
|
128
|
-
else
|
129
|
-
@base_client.log_internal Logger::INFO, "LoadCheckpoint: Fallback to GRPC API"
|
130
|
-
end
|
131
|
-
|
132
|
-
success = load_checkpoint_from_grpc_api
|
133
|
-
|
134
|
-
if success
|
135
|
-
return
|
136
|
-
else
|
137
|
-
@base_client.log_internal Logger::INFO, "LoadCheckpoint: Fallback to S3"
|
138
|
-
end
|
139
|
-
|
140
|
-
success = load_checkpoint_from_s3
|
77
|
+
success = load_checkpoint_from_config
|
141
78
|
|
142
79
|
if !success
|
143
|
-
@base_client.log_internal Logger::
|
80
|
+
@base_client.log_internal Logger::INFO, "Fallback to S3"
|
81
|
+
load_checkpoint_from_s3
|
144
82
|
end
|
145
|
-
end
|
146
83
|
|
147
|
-
|
148
|
-
|
84
|
+
rescue => e
|
85
|
+
@base_client.log_internal Logger::WARN, "Unexpected problem loading checkpoint #{e}"
|
86
|
+
end
|
149
87
|
|
88
|
+
def load_checkpoint_from_config
|
89
|
+
config_req = Prefab::ConfigServicePointer.new(account_id: @base_client.account_id,
|
90
|
+
start_at_id: @config_loader.highwater_mark)
|
150
91
|
resp = stub.get_all_config(config_req)
|
151
|
-
|
92
|
+
load_deltas(resp, :api)
|
93
|
+
resp.deltas.each do |delta|
|
94
|
+
@config_loader.set(delta)
|
95
|
+
end
|
96
|
+
@config_resolver.update
|
97
|
+
finish_init!(:api)
|
152
98
|
true
|
153
|
-
rescue GRPC::Unauthenticated
|
154
|
-
@base_client.log_internal Logger::WARN, "Unauthenticated"
|
155
99
|
rescue => e
|
156
|
-
@base_client.log_internal Logger::WARN, "Unexpected
|
100
|
+
@base_client.log_internal Logger::WARN, "Unexpected problem loading checkpoint #{e}"
|
157
101
|
false
|
158
102
|
end
|
159
103
|
|
160
|
-
def load_checkpoint_api_cdn
|
161
|
-
key_hash = Murmur3.murmur3_32(@base_client.api_key)
|
162
|
-
url = "#{@options.url_for_api_cdn}/api/v1/config/#{@base_client.project_id}/#{key_hash}/0"
|
163
|
-
conn = if Faraday::VERSION[0].to_i >= 2
|
164
|
-
Faraday.new(url) do |conn|
|
165
|
-
conn.request :authorization, :basic, @base_client.project_id, @base_client.api_key
|
166
|
-
end
|
167
|
-
else
|
168
|
-
Faraday.new(url) do |conn|
|
169
|
-
conn.request :basic_auth, @base_client.project_id, @base_client.api_key
|
170
|
-
end
|
171
|
-
end
|
172
|
-
load_url(conn, :remote_cdn_api)
|
173
|
-
end
|
174
|
-
|
175
104
|
def load_checkpoint_from_s3
|
176
105
|
url = "#{@s3_cloud_front}/#{@base_client.api_key.gsub("|", "/")}"
|
177
|
-
|
178
|
-
end
|
179
|
-
|
180
|
-
def load_url(conn, source)
|
181
|
-
resp = conn.get('')
|
106
|
+
resp = Faraday.get url
|
182
107
|
if resp.status == 200
|
183
|
-
|
184
|
-
|
185
|
-
true
|
108
|
+
deltas = Prefab::ConfigDeltas.decode(resp.body)
|
109
|
+
load_deltas(deltas, :s3)
|
186
110
|
else
|
187
|
-
@base_client.log_internal Logger::INFO, "
|
188
|
-
false
|
111
|
+
@base_client.log_internal Logger::INFO, "No S3 checkpoint. Response #{resp.status} Plan may not support this."
|
189
112
|
end
|
190
|
-
rescue => e
|
191
|
-
@base_client.log_internal Logger::WARN, "Unexpected #{source} problem loading checkpoint #{e} #{conn}"
|
192
|
-
false
|
193
113
|
end
|
194
114
|
|
195
|
-
def load_configs(configs, source)
|
196
|
-
project_env_id = configs.config_service_pointer.project_env_id
|
197
|
-
@config_resolver.project_env_id = project_env_id
|
198
|
-
starting_highwater_mark = @config_loader.highwater_mark
|
199
115
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
if @config_loader.highwater_mark > starting_highwater_mark
|
204
|
-
@base_client.log_internal Logger::INFO, "Found new checkpoint with highwater id #{@config_loader.highwater_mark} from #{source} in project #{@base_client.project_id} environment: #{project_env_id} and namespace: '#{@namespace}'"
|
205
|
-
else
|
206
|
-
@base_client.log_internal Logger::DEBUG, "Checkpoint with highwater id #{@config_loader.highwater_mark} from #{source}. No changes.", "prefab.config_client.load_configs"
|
116
|
+
def load_deltas(deltas, source)
|
117
|
+
deltas.deltas.each do |delta|
|
118
|
+
@config_loader.set(delta)
|
207
119
|
end
|
120
|
+
@base_client.log_internal Logger::INFO, "Found checkpoint with highwater id #{@config_loader.highwater_mark} from #{source}"
|
208
121
|
@base_client.stats.increment("prefab.config.checkpoint.load")
|
209
122
|
@config_resolver.update
|
210
123
|
finish_init!(source)
|
@@ -212,7 +125,6 @@ module Prefab
|
|
212
125
|
|
213
126
|
# A thread that checks for a checkpoint
|
214
127
|
def start_checkpointing_thread
|
215
|
-
|
216
128
|
Thread.new do
|
217
129
|
loop do
|
218
130
|
begin
|
@@ -232,31 +144,47 @@ module Prefab
|
|
232
144
|
|
233
145
|
def finish_init!(source)
|
234
146
|
if @initialization_lock.write_locked?
|
235
|
-
@base_client.log_internal Logger::
|
147
|
+
@base_client.log_internal Logger::DEBUG, "Unlocked Config via #{source}"
|
236
148
|
@initialization_lock.release_write_lock
|
237
149
|
@base_client.log.set_config_client(self)
|
238
|
-
@base_client.log_internal Logger::INFO, to_s
|
239
150
|
end
|
240
151
|
end
|
241
152
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
@
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
153
|
+
# Setup a streaming connection to the API
|
154
|
+
# Save new config values into the loader
|
155
|
+
def start_api_connection_thread(start_at_id)
|
156
|
+
config_req = Prefab::ConfigServicePointer.new(account_id: @base_client.account_id,
|
157
|
+
start_at_id: start_at_id)
|
158
|
+
@base_client.log_internal Logger::DEBUG, "start api connection thread #{start_at_id}"
|
159
|
+
@base_client.stats.increment("prefab.config.api.start")
|
160
|
+
|
161
|
+
@api_connection_thread = Thread.new do
|
162
|
+
at_exit do
|
163
|
+
@streaming = false
|
164
|
+
@cancellable_interceptor.cancel
|
165
|
+
end
|
166
|
+
|
167
|
+
while @streaming do
|
168
|
+
begin
|
169
|
+
resp = stub.get_config(config_req)
|
170
|
+
resp.each do |r|
|
171
|
+
r.deltas.each do |delta|
|
172
|
+
@config_loader.set(delta)
|
173
|
+
end
|
174
|
+
@config_resolver.update
|
175
|
+
finish_init!(:streaming)
|
176
|
+
end
|
177
|
+
rescue => e
|
178
|
+
if @streaming
|
179
|
+
level = e.code == 1 ? Logger::DEBUG : Logger::INFO
|
180
|
+
@base_client.log_internal level, ("config client encountered #{e.message} pausing #{RECONNECT_WAIT}")
|
181
|
+
reset
|
182
|
+
sleep(RECONNECT_WAIT)
|
183
|
+
end
|
184
|
+
end
|
258
185
|
end
|
259
186
|
end
|
187
|
+
|
260
188
|
end
|
261
189
|
end
|
262
190
|
end
|
data/lib/prefab/config_loader.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
require 'yaml'
|
4
2
|
module Prefab
|
5
3
|
class ConfigLoader
|
@@ -7,7 +5,6 @@ module Prefab
|
|
7
5
|
|
8
6
|
def initialize(base_client)
|
9
7
|
@base_client = base_client
|
10
|
-
@prefab_options = base_client.options
|
11
8
|
@highwater_mark = 0
|
12
9
|
@classpath_config = load_classpath_config
|
13
10
|
@local_overrides = load_local_overrides
|
@@ -17,27 +14,24 @@ module Prefab
|
|
17
14
|
def calc_config
|
18
15
|
rtn = @classpath_config.clone
|
19
16
|
@api_config.each_key do |k|
|
20
|
-
rtn[k] = @api_config[k]
|
17
|
+
rtn[k] = @api_config[k].value
|
21
18
|
end
|
22
19
|
rtn = rtn.merge(@local_overrides)
|
23
20
|
rtn
|
24
21
|
end
|
25
22
|
|
26
|
-
def set(
|
23
|
+
def set(delta)
|
27
24
|
# don't overwrite newer values
|
28
|
-
if @api_config[
|
25
|
+
if @api_config[delta.key] && @api_config[delta.key].id > delta.id
|
29
26
|
return
|
30
27
|
end
|
31
28
|
|
32
|
-
if
|
33
|
-
@api_config.delete(
|
29
|
+
if delta.value.nil?
|
30
|
+
@api_config.delete(delta.key)
|
34
31
|
else
|
35
|
-
|
36
|
-
@base_client.log_internal Logger::DEBUG, "Replace #{config.key} with value from #{source} #{ @api_config[config.key][:config].id} -> #{config.id}"
|
37
|
-
end
|
38
|
-
@api_config[config.key] = { source: source, config: config }
|
32
|
+
@api_config[delta.key] = delta
|
39
33
|
end
|
40
|
-
@highwater_mark = [
|
34
|
+
@highwater_mark = [delta.id, @highwater_mark].max
|
41
35
|
end
|
42
36
|
|
43
37
|
def rm(key)
|
@@ -45,22 +39,22 @@ module Prefab
|
|
45
39
|
end
|
46
40
|
|
47
41
|
def get_api_deltas
|
48
|
-
|
42
|
+
deltas = Prefab::ConfigDeltas.new
|
49
43
|
@api_config.each_value do |config_value|
|
50
|
-
|
44
|
+
deltas.deltas << config_value
|
51
45
|
end
|
52
|
-
|
46
|
+
deltas
|
53
47
|
end
|
54
48
|
|
55
49
|
private
|
56
50
|
|
57
51
|
def load_classpath_config
|
58
|
-
classpath_dir =
|
52
|
+
classpath_dir = ENV['PREFAB_CONFIG_CLASSPATH_DIR'] || "."
|
59
53
|
load_glob(File.join(classpath_dir, ".prefab*config.yaml"))
|
60
54
|
end
|
61
55
|
|
62
56
|
def load_local_overrides
|
63
|
-
override_dir =
|
57
|
+
override_dir = ENV['PREFAB_CONFIG_OVERRIDE_DIR'] || Dir.home
|
64
58
|
load_glob(File.join(override_dir, ".prefab*config.yaml"))
|
65
59
|
end
|
66
60
|
|
@@ -69,39 +63,7 @@ module Prefab
|
|
69
63
|
Dir.glob(glob).each do |file|
|
70
64
|
yaml = load(file)
|
71
65
|
yaml.each do |k, v|
|
72
|
-
|
73
|
-
v.each do |env_k, env_v|
|
74
|
-
if k == @prefab_options.defaults_env
|
75
|
-
if env_v.class == Hash && env_v['feature_flag']
|
76
|
-
rtn[env_k] = feature_flag_config(file, k, env_k, env_v)
|
77
|
-
else
|
78
|
-
rtn[env_k] = {
|
79
|
-
source: file,
|
80
|
-
match: k,
|
81
|
-
config: Prefab::Config.new(
|
82
|
-
key: env_k,
|
83
|
-
rows: [
|
84
|
-
Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(value_from(env_v)))
|
85
|
-
]
|
86
|
-
)
|
87
|
-
}
|
88
|
-
end
|
89
|
-
else
|
90
|
-
next
|
91
|
-
end
|
92
|
-
end
|
93
|
-
else
|
94
|
-
rtn[k] = {
|
95
|
-
source: file,
|
96
|
-
match: "default",
|
97
|
-
config: Prefab::Config.new(
|
98
|
-
key: k,
|
99
|
-
rows: [
|
100
|
-
Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(value_from(v)))
|
101
|
-
]
|
102
|
-
)
|
103
|
-
}
|
104
|
-
end
|
66
|
+
rtn[k] = Prefab::ConfigValue.new(value_from(v))
|
105
67
|
end
|
106
68
|
end
|
107
69
|
rtn
|
@@ -129,52 +91,5 @@ module Prefab
|
|
129
91
|
{ double: raw }
|
130
92
|
end
|
131
93
|
end
|
132
|
-
|
133
|
-
def feature_flag_config(file, k, env_k, env_v)
|
134
|
-
criteria = Prefab::Criteria.new(operator: 'ALWAYS_TRUE')
|
135
|
-
|
136
|
-
if env_v['criteria']
|
137
|
-
criteria = Prefab::Criteria.new(criteria_values(env_v['criteria']))
|
138
|
-
end
|
139
|
-
|
140
|
-
row = Prefab::ConfigRow.new(
|
141
|
-
value: Prefab::ConfigValue.new(
|
142
|
-
feature_flag: Prefab::FeatureFlag.new(
|
143
|
-
active: true,
|
144
|
-
inactive_variant_idx: -1, # not supported
|
145
|
-
rules: [
|
146
|
-
Prefab::Rule.new(
|
147
|
-
variant_weights: [
|
148
|
-
Prefab::VariantWeight.new(variant_idx: 0, weight: 1000)
|
149
|
-
],
|
150
|
-
criteria: criteria
|
151
|
-
)
|
152
|
-
]
|
153
|
-
)
|
154
|
-
)
|
155
|
-
)
|
156
|
-
|
157
|
-
unless env_v.has_key?('value')
|
158
|
-
raise Prefab::Error, "Feature flag config `#{env_k}` #{file} must have a `value`"
|
159
|
-
end
|
160
|
-
|
161
|
-
{
|
162
|
-
source: file,
|
163
|
-
match: k,
|
164
|
-
config: Prefab::Config.new(
|
165
|
-
key: env_k,
|
166
|
-
variants: [Prefab::FeatureFlagVariant.new(value_from(env_v['value']))],
|
167
|
-
rows: [row]
|
168
|
-
)
|
169
|
-
}
|
170
|
-
end
|
171
|
-
|
172
|
-
def criteria_values(criteria_hash)
|
173
|
-
if RUBY_VERSION < '2.7'
|
174
|
-
criteria_hash.transform_keys(&:to_sym)
|
175
|
-
else
|
176
|
-
criteria_hash
|
177
|
-
end
|
178
|
-
end
|
179
94
|
end
|
180
95
|
end
|
@@ -1,62 +1,59 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
1
|
module Prefab
|
3
2
|
class ConfigResolver
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
attr_accessor :project_env_id # this will be set by the config_client when it gets an API response
|
3
|
+
NAMESPACE_DELIMITER = ".".freeze
|
4
|
+
NAME_KEY_DELIMITER = ":".freeze
|
8
5
|
|
9
6
|
def initialize(base_client, config_loader)
|
10
7
|
@lock = Concurrent::ReadWriteLock.new
|
11
8
|
@local_store = {}
|
12
|
-
@namespace = base_client.
|
9
|
+
@namespace = base_client.namespace
|
13
10
|
@config_loader = config_loader
|
14
|
-
@project_env_id = 0
|
15
11
|
make_local
|
16
12
|
end
|
17
13
|
|
18
14
|
def to_s
|
19
|
-
str = "
|
15
|
+
str = ""
|
20
16
|
@lock.with_read_lock do
|
21
17
|
@local_store.each do |k, v|
|
22
|
-
|
23
|
-
|
24
|
-
elements << "tombstone"
|
25
|
-
else
|
26
|
-
value = v[:value]
|
27
|
-
elements << value_of(value).to_s.slice(0..34).ljust(35)
|
28
|
-
elements << value_of(value).class.to_s.slice(0..6).ljust(7)
|
29
|
-
elements << "Match: #{v[:match]}".slice(0..29).ljust(30)
|
30
|
-
elements << "Source: #{v[:source]}"
|
31
|
-
end
|
32
|
-
str += elements.join(" | ") << "\n"
|
18
|
+
value = v[:value]
|
19
|
+
str << "|#{k}| in #{v[:namespace]} |#{value_of(value)}|#{value_of(value).class}\n"
|
33
20
|
end
|
34
21
|
end
|
35
22
|
str
|
36
23
|
end
|
37
24
|
|
38
25
|
def get(property)
|
39
|
-
config =
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
def get_config(property)
|
44
|
-
config = _get(property)
|
45
|
-
config ? config[:config] : nil
|
46
|
-
end
|
47
|
-
|
48
|
-
def _get(key)
|
49
|
-
@lock.with_read_lock do
|
50
|
-
@local_store[key]
|
26
|
+
config = @lock.with_read_lock do
|
27
|
+
@local_store[property]
|
51
28
|
end
|
29
|
+
config ? value_of(config[:value]) : nil
|
52
30
|
end
|
53
31
|
|
54
32
|
def update
|
55
33
|
make_local
|
56
34
|
end
|
57
35
|
|
36
|
+
def export_api_deltas
|
37
|
+
@config_loader.get_api_deltas
|
38
|
+
end
|
39
|
+
|
58
40
|
private
|
59
41
|
|
42
|
+
def value_of(config_value)
|
43
|
+
case config_value.type
|
44
|
+
when :string
|
45
|
+
config_value.string
|
46
|
+
when :int
|
47
|
+
config_value.int
|
48
|
+
when :double
|
49
|
+
config_value.double
|
50
|
+
when :bool
|
51
|
+
config_value.bool
|
52
|
+
when :feature_flag
|
53
|
+
config_value.feature_flag
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
60
57
|
# Should client a.b.c see key in namespace a.b? yes
|
61
58
|
# Should client a.b.c see key in namespace a.b.c? yes
|
62
59
|
# Should client a.b.c see key in namespace a.b.d? no
|
@@ -64,37 +61,33 @@ module Prefab
|
|
64
61
|
#
|
65
62
|
def starts_with_ns?(key_namespace, client_namespace)
|
66
63
|
zipped = key_namespace.split(NAMESPACE_DELIMITER).zip(client_namespace.split(NAMESPACE_DELIMITER))
|
67
|
-
|
68
|
-
(k.nil? || k.empty?) ||
|
69
|
-
end
|
70
|
-
[mapped.all?, mapped.size]
|
64
|
+
zipped.map do |k, c|
|
65
|
+
(k.nil? || k.empty?) || c == k
|
66
|
+
end.all?
|
71
67
|
end
|
72
68
|
|
73
69
|
def make_local
|
74
70
|
store = {}
|
75
|
-
@config_loader.calc_config.each do |
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
71
|
+
@config_loader.calc_config.each do |prop, value|
|
72
|
+
property = prop
|
73
|
+
key_namespace = ""
|
74
|
+
|
75
|
+
split = prop.split(NAME_KEY_DELIMITER)
|
76
|
+
|
77
|
+
if split.size > 1
|
78
|
+
property = split[1..-1].join(NAME_KEY_DELIMITER)
|
79
|
+
key_namespace = split[0]
|
80
|
+
end
|
81
|
+
|
82
|
+
if starts_with_ns?(key_namespace, @namespace)
|
83
|
+
existing = store[property]
|
84
|
+
if existing.nil?
|
85
|
+
store[property] = { namespace: key_namespace, value: value }
|
86
|
+
elsif existing[:namespace].split(NAMESPACE_DELIMITER).size < key_namespace.split(NAMESPACE_DELIMITER).size
|
87
|
+
store[property] = { namespace: key_namespace, value: value }
|
91
88
|
end
|
92
|
-
end
|
93
|
-
to_store = sortable.sort_by { |h| h[:sortable] }.last
|
94
|
-
to_store[:source] = config_resolver_obj[:source]
|
95
|
-
store[key] = to_store
|
89
|
+
end
|
96
90
|
end
|
97
|
-
|
98
91
|
@lock.with_write_lock do
|
99
92
|
@local_store = store
|
100
93
|
end
|