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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/Gemfile +4 -11
  4. data/Gemfile.lock +32 -52
  5. data/README.md +1 -19
  6. data/Rakefile +0 -1
  7. data/VERSION +1 -1
  8. data/compile_protos.sh +0 -3
  9. data/lib/prefab/auth_interceptor.rb +0 -1
  10. data/lib/prefab/cancellable_interceptor.rb +0 -1
  11. data/lib/prefab/client.rb +36 -49
  12. data/lib/prefab/config_client.rb +73 -145
  13. data/lib/prefab/config_loader.rb +13 -98
  14. data/lib/prefab/config_resolver.rb +49 -56
  15. data/lib/prefab/feature_flag_client.rb +11 -129
  16. data/lib/prefab/logger_client.rb +8 -10
  17. data/lib/prefab/murmer3.rb +0 -1
  18. data/lib/prefab/noop_cache.rb +0 -1
  19. data/lib/prefab/noop_stats.rb +0 -1
  20. data/lib/prefab/ratelimit_client.rb +0 -1
  21. data/lib/prefab-cloud-ruby.rb +0 -10
  22. data/lib/prefab_pb.rb +132 -214
  23. data/lib/prefab_services_pb.rb +6 -35
  24. data/prefab-cloud-ruby.gemspec +11 -30
  25. data/test/.prefab.test.config.yaml +1 -27
  26. data/test/test_config_loader.rb +25 -39
  27. data/test/test_config_resolver.rb +38 -134
  28. data/test/test_feature_flag_client.rb +35 -277
  29. data/test/test_helper.rb +4 -70
  30. data/test/test_logger.rb +29 -23
  31. metadata +15 -70
  32. data/.github/workflows/ruby.yml +0 -39
  33. data/.tool-versions +0 -1
  34. data/CODEOWNERS +0 -1
  35. data/lib/prefab/config_helper.rb +0 -29
  36. data/lib/prefab/error.rb +0 -6
  37. data/lib/prefab/errors/initialization_timeout_error.rb +0 -13
  38. data/lib/prefab/errors/invalid_api_key_error.rb +0 -19
  39. data/lib/prefab/errors/missing_default_error.rb +0 -13
  40. data/lib/prefab/internal_logger.rb +0 -29
  41. data/lib/prefab/options.rb +0 -82
  42. data/run_test_harness_server.sh +0 -8
  43. data/test/harness_server.rb +0 -64
  44. data/test/test_client.rb +0 -91
  45. data/test/test_config_client.rb +0 -56
@@ -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
- if @options.local_only?
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
- @stream_lock.with_write_lock do
45
- start_sse_streaming_connection_thread(@config_loader.highwater_mark) if @streaming_thread.nil?
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, :upsert)
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::Config.new(key: [namespace, key].compact.join(":"),
74
- rows: [Prefab::ConfigRow.new(value: config_value)])
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
- # try API first, if not, fallback to s3
74
+ # Bootstrap out of the cache
75
+ # returns the high-watermark of what was in the cache
123
76
  def load_checkpoint
124
- success = load_checkpoint_api_cdn
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::WARN, "No success loading checkpoints"
80
+ @base_client.log_internal Logger::INFO, "Fallback to S3"
81
+ load_checkpoint_from_s3
144
82
  end
145
- end
146
83
 
147
- def load_checkpoint_from_grpc_api
148
- config_req = Prefab::ConfigServicePointer.new(start_at_id: @config_loader.highwater_mark)
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
- load_configs(resp, :remote_api_grpc)
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 grpc_api problem loading checkpoint #{e}"
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
- load_url(Faraday.new(url), :remote_s3)
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
- configs = Prefab::Configs.decode(resp.body)
184
- load_configs(configs, source)
185
- true
108
+ deltas = Prefab::ConfigDeltas.decode(resp.body)
109
+ load_deltas(deltas, :s3)
186
110
  else
187
- @base_client.log_internal Logger::INFO, "Checkpoint #{source} failed to load. Response #{resp.status}"
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
- configs.configs.each do |config|
201
- @config_loader.set(config, source)
202
- end
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::INFO, "Unlocked Config via #{source}"
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
- def start_sse_streaming_connection_thread(start_at_id)
243
- auth = "#{@base_client.project_id}:#{@base_client.api_key}"
244
- auth_string = Base64.strict_encode64(auth)
245
- headers = {
246
- "x-prefab-start-at-id": start_at_id,
247
- "Authorization": "Basic #{auth_string}",
248
- }
249
- url = "#{@base_client.prefab_api_url}/api/v1/sse/config"
250
- @base_client.log_internal Logger::INFO, "SSE Streaming Connect to #{url} start_at #{start_at_id}"
251
- @streaming_thread = SSE::Client.new(url,
252
- headers: headers,
253
- read_timeout: SSE_READ_TIMEOUT,
254
- logger: Prefab::InternalLogger.new("prefab.config.sse", @base_client.log)) do |client|
255
- client.on_event do |event|
256
- configs = Prefab::Configs.decode(Base64.decode64(event.data))
257
- load_configs(configs, :sse)
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
@@ -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(config, source)
23
+ def set(delta)
27
24
  # don't overwrite newer values
28
- if @api_config[config.key] && @api_config[config.key][:config].id >= config.id
25
+ if @api_config[delta.key] && @api_config[delta.key].id > delta.id
29
26
  return
30
27
  end
31
28
 
32
- if config.rows.empty?
33
- @api_config.delete(config.key)
29
+ if delta.value.nil?
30
+ @api_config.delete(delta.key)
34
31
  else
35
- if @api_config[config.key]
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 = [config.id, @highwater_mark].max
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
- configs = Prefab::Configs.new
42
+ deltas = Prefab::ConfigDeltas.new
49
43
  @api_config.each_value do |config_value|
50
- configs.configs << config_value[:config]
44
+ deltas.deltas << config_value
51
45
  end
52
- configs
46
+ deltas
53
47
  end
54
48
 
55
49
  private
56
50
 
57
51
  def load_classpath_config
58
- classpath_dir = @prefab_options.prefab_config_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 = @prefab_options.prefab_config_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
- if v.class == Hash
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
- include Prefab::ConfigHelper
5
- NAMESPACE_DELIMITER = "."
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.options.namespace
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 = "\n"
15
+ str = ""
20
16
  @lock.with_read_lock do
21
17
  @local_store.each do |k, v|
22
- elements = [k.slice(0..49).ljust(50)]
23
- if v.nil?
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 = _get(property)
40
- config ? value_of(config[:value]) : nil
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
- mapped = zipped.map do |k, c|
68
- (k.nil? || k.empty?) || k == c
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 |key, config_resolver_obj|
76
- config = config_resolver_obj[:config]
77
- sortable = config.rows.map do |row|
78
- if row.project_env_id != 0
79
- if row.project_env_id == @project_env_id
80
- if !row.namespace.empty?
81
- (starts_with, count) = starts_with_ns?(row.namespace, @namespace)
82
- # rubocop:disable BlockNesting
83
- { sortable: 2 + count, match: "nm:#{row.namespace}", value: row.value, config: config} if starts_with
84
- else
85
- { sortable: 1, match: "env:#{row.project_env_id}", value: row.value, config: config}
86
- end
87
- end
88
- else
89
- match = config_resolver_obj[:match] || "default"
90
- { sortable: 0, match: match, value: row.value, config: config}
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.compact
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