prefab-cloud-ruby 0 → 0.0.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 (101) hide show
  1. checksums.yaml +5 -5
  2. data/.ruby-version +1 -0
  3. data/Gemfile +9 -22
  4. data/Gemfile.lock +88 -160
  5. data/LICENSE.txt +1 -1
  6. data/Rakefile +14 -14
  7. data/VERSION +1 -1
  8. data/lib/prefab/auth_interceptor.rb +25 -0
  9. data/lib/prefab/client.rb +34 -139
  10. data/lib/prefab/config_client.rb +23 -275
  11. data/lib/prefab/config_loader.rb +27 -60
  12. data/lib/prefab/config_resolver.rb +40 -53
  13. data/lib/prefab/noop_cache.rb +13 -0
  14. data/lib/prefab/noop_stats.rb +8 -0
  15. data/lib/prefab/prefab_pb.rb +39 -0
  16. data/lib/prefab/prefab_services_pb.rb +37 -0
  17. data/lib/prefab/ratelimit_client.rb +58 -0
  18. data/lib/prefab/ratelimit_pb.rb +125 -0
  19. data/lib/prefab/store.rb +29 -0
  20. data/lib/prefab_client.rb +35 -0
  21. metadata +36 -198
  22. data/.envrc.sample +0 -3
  23. data/.github/workflows/ruby.yml +0 -46
  24. data/.gitmodules +0 -3
  25. data/.rubocop.yml +0 -13
  26. data/.tool-versions +0 -1
  27. data/CHANGELOG.md +0 -169
  28. data/CODEOWNERS +0 -1
  29. data/README.md +0 -94
  30. data/bin/console +0 -21
  31. data/compile_protos.sh +0 -18
  32. data/lib/prefab/config_client_presenter.rb +0 -18
  33. data/lib/prefab/config_value_unwrapper.rb +0 -115
  34. data/lib/prefab/config_value_wrapper.rb +0 -18
  35. data/lib/prefab/context.rb +0 -179
  36. data/lib/prefab/context_shape.rb +0 -20
  37. data/lib/prefab/context_shape_aggregator.rb +0 -65
  38. data/lib/prefab/criteria_evaluator.rb +0 -136
  39. data/lib/prefab/encryption.rb +0 -65
  40. data/lib/prefab/error.rb +0 -6
  41. data/lib/prefab/errors/env_var_parse_error.rb +0 -11
  42. data/lib/prefab/errors/initialization_timeout_error.rb +0 -13
  43. data/lib/prefab/errors/invalid_api_key_error.rb +0 -19
  44. data/lib/prefab/errors/missing_default_error.rb +0 -13
  45. data/lib/prefab/errors/missing_env_var_error.rb +0 -11
  46. data/lib/prefab/errors/uninitialized_error.rb +0 -13
  47. data/lib/prefab/evaluation.rb +0 -52
  48. data/lib/prefab/evaluation_summary_aggregator.rb +0 -87
  49. data/lib/prefab/example_contexts_aggregator.rb +0 -78
  50. data/lib/prefab/exponential_backoff.rb +0 -21
  51. data/lib/prefab/feature_flag_client.rb +0 -42
  52. data/lib/prefab/http_connection.rb +0 -41
  53. data/lib/prefab/internal_logger.rb +0 -16
  54. data/lib/prefab/local_config_parser.rb +0 -151
  55. data/lib/prefab/log_path_aggregator.rb +0 -69
  56. data/lib/prefab/logger_client.rb +0 -264
  57. data/lib/prefab/murmer3.rb +0 -50
  58. data/lib/prefab/options.rb +0 -208
  59. data/lib/prefab/periodic_sync.rb +0 -69
  60. data/lib/prefab/prefab.rb +0 -56
  61. data/lib/prefab/rate_limit_cache.rb +0 -41
  62. data/lib/prefab/resolved_config_presenter.rb +0 -86
  63. data/lib/prefab/time_helpers.rb +0 -7
  64. data/lib/prefab/weighted_value_resolver.rb +0 -42
  65. data/lib/prefab/yaml_config_parser.rb +0 -34
  66. data/lib/prefab-cloud-ruby.rb +0 -57
  67. data/lib/prefab_pb.rb +0 -93
  68. data/prefab-cloud-ruby.gemspec +0 -155
  69. data/test/.prefab.default.config.yaml +0 -2
  70. data/test/.prefab.unit_tests.config.yaml +0 -28
  71. data/test/integration_test.rb +0 -150
  72. data/test/integration_test_helpers.rb +0 -151
  73. data/test/support/common_helpers.rb +0 -180
  74. data/test/support/mock_base_client.rb +0 -42
  75. data/test/support/mock_config_client.rb +0 -19
  76. data/test/support/mock_config_loader.rb +0 -1
  77. data/test/test_client.rb +0 -444
  78. data/test/test_config_client.rb +0 -109
  79. data/test/test_config_loader.rb +0 -117
  80. data/test/test_config_resolver.rb +0 -430
  81. data/test/test_config_value_unwrapper.rb +0 -224
  82. data/test/test_config_value_wrapper.rb +0 -42
  83. data/test/test_context.rb +0 -203
  84. data/test/test_context_shape.rb +0 -50
  85. data/test/test_context_shape_aggregator.rb +0 -147
  86. data/test/test_criteria_evaluator.rb +0 -726
  87. data/test/test_encryption.rb +0 -16
  88. data/test/test_evaluation_summary_aggregator.rb +0 -162
  89. data/test/test_example_contexts_aggregator.rb +0 -238
  90. data/test/test_exponential_backoff.rb +0 -18
  91. data/test/test_feature_flag_client.rb +0 -48
  92. data/test/test_helper.rb +0 -17
  93. data/test/test_integration.rb +0 -58
  94. data/test/test_local_config_parser.rb +0 -147
  95. data/test/test_log_path_aggregator.rb +0 -62
  96. data/test/test_logger.rb +0 -621
  97. data/test/test_logger_initialization.rb +0 -12
  98. data/test/test_options.rb +0 -75
  99. data/test/test_prefab.rb +0 -12
  100. data/test/test_rate_limit_cache.rb +0 -44
  101. data/test/test_weighted_value_resolver.rb +0 -71
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Prefab
4
- class InternalLogger < StaticLogger
5
- INTERNAL_PREFIX = 'cloud.prefab.client'
6
-
7
- def initialize(path)
8
- if path.is_a?(Class)
9
- path_string = path.name.split('::').last.downcase
10
- else
11
- path_string = path
12
- end
13
- super("#{INTERNAL_PREFIX}.#{path_string}")
14
- end
15
- end
16
- end
@@ -1,151 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Prefab
4
- class LocalConfigParser
5
- class << self
6
- def parse(key, value, config, file)
7
- if value.instance_of?(Hash)
8
- if value['feature_flag']
9
- config[key] = feature_flag_config(file, key, value)
10
- elsif value['type'] == 'provided'
11
- config[key] = provided_config(file, key, value)
12
- elsif value['decrypt_with'] || value['confidential']
13
- config[key] = complex_string(file, key, value)
14
- else
15
- value.each do |nest_key, nest_value|
16
- nested_key = "#{key}.#{nest_key}"
17
- nested_key = key if nest_key == '_'
18
- parse(nested_key, nest_value, config, file)
19
- end
20
- end
21
- else
22
- config[key] = {
23
- source: file,
24
- match: 'default',
25
- config: PrefabProto::Config.new(
26
- config_type: :CONFIG,
27
- key: key,
28
- rows: [
29
- PrefabProto::ConfigRow.new(values: [
30
- PrefabProto::ConditionalValue.new(value: value_from(key, value))
31
- ])
32
- ]
33
- )
34
- }
35
- end
36
-
37
- config
38
- end
39
-
40
- def value_from(key, raw)
41
- case raw
42
- when String
43
- if key.to_s.start_with? Prefab::LoggerClient::BASE_KEY
44
- prefab_log_level_resolve = PrefabProto::LogLevel.resolve(raw.upcase.to_sym) || PrefabProto::LogLevel::NOT_SET_LOG_LEVEL
45
- { log_level: prefab_log_level_resolve }
46
- else
47
- { string: raw }
48
- end
49
- when Integer
50
- { int: raw }
51
- when TrueClass, FalseClass
52
- { bool: raw }
53
- when Float
54
- { double: raw }
55
- end
56
- end
57
-
58
- def feature_flag_config(file, key, value)
59
- criterion = (parse_criterion(value['criterion']) if value['criterion'])
60
-
61
- variant = PrefabProto::ConfigValue.new(value_from(key, value['value']))
62
-
63
- row = PrefabProto::ConfigRow.new(
64
- values: [
65
- PrefabProto::ConditionalValue.new(
66
- criteria: [criterion].compact,
67
- value: variant
68
- )
69
- ]
70
- )
71
-
72
- raise Prefab::Error, "Feature flag config `#{key}` #{file} must have a `value`" unless value.key?('value')
73
-
74
- {
75
- source: file,
76
- match: key,
77
- config: PrefabProto::Config.new(
78
- config_type: :FEATURE_FLAG,
79
- key: key,
80
- allowable_values: [variant],
81
- rows: [row]
82
- )
83
- }
84
- end
85
-
86
- def provided_config(file, key, value_hash)
87
- value = PrefabProto::ConfigValue.new(provided: PrefabProto::Provided.new(
88
- source: :ENV_VAR,
89
- lookup: value_hash["lookup"],
90
- ),
91
- confidential: value_hash["confidential"],
92
- )
93
-
94
- row = PrefabProto::ConfigRow.new(
95
- values: [
96
- PrefabProto::ConditionalValue.new(
97
- value: value
98
- )
99
- ]
100
- )
101
-
102
- {
103
- source: file,
104
- match: value.provided.lookup,
105
- config: PrefabProto::Config.new(
106
- config_type: :CONFIG,
107
- key: key,
108
- rows: [row]
109
- )
110
- }
111
- end
112
-
113
- def complex_string(file, key, value_hash)
114
- value = PrefabProto::ConfigValue.new(
115
- string: value_hash["value"],
116
- confidential: value_hash["confidential"],
117
- decrypt_with: value_hash["decrypt_with"],
118
- )
119
-
120
- row = PrefabProto::ConfigRow.new(
121
- values: [
122
- PrefabProto::ConditionalValue.new(
123
- value: value
124
- )
125
- ]
126
- )
127
-
128
- {
129
- source: file,
130
- config: PrefabProto::Config.new(
131
- config_type: :CONFIG,
132
- key: key,
133
- rows: [row]
134
- )
135
- }
136
- end
137
-
138
- def parse_criterion(criterion)
139
- PrefabProto::Criterion.new(operator: criterion['operator'],
140
- property_name: criterion['property'],
141
- value_to_match: parse_value_to_match(criterion['values']))
142
- end
143
-
144
- def parse_value_to_match(values)
145
- raise "Can't handle #{values}" unless values.instance_of?(Array)
146
-
147
- PrefabProto::ConfigValue.new(string_list: PrefabProto::StringList.new(values: values))
148
- end
149
- end
150
- end
151
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'periodic_sync'
4
-
5
- module Prefab
6
- class LogPathAggregator
7
- LOG = Prefab::InternalLogger.new(LogPathAggregator)
8
-
9
- include Prefab::PeriodicSync
10
-
11
- INCREMENT = ->(count) { (count || 0) + 1 }
12
-
13
- SEVERITY_KEY = {
14
- ::Logger::DEBUG => 'debugs',
15
- ::Logger::INFO => 'infos',
16
- ::Logger::WARN => 'warns',
17
- ::Logger::ERROR => 'errors',
18
- ::Logger::FATAL => 'fatals'
19
- }.freeze
20
-
21
- attr_reader :data
22
-
23
- def initialize(client:, max_paths:, sync_interval:)
24
- @max_paths = max_paths
25
- @client = client
26
- @name = 'log_path_aggregator'
27
-
28
- @data = Concurrent::Map.new
29
-
30
- @last_data_sent = nil
31
- @last_request = nil
32
-
33
- start_periodic_sync(sync_interval)
34
- end
35
-
36
- def push(path, severity)
37
- return if @data.size >= @max_paths
38
-
39
- @data.compute([path, severity], &INCREMENT)
40
- end
41
-
42
- private
43
-
44
- def flush(to_ship, start_at_was)
45
- pool.post do
46
- LOG.debug "Uploading stats for #{to_ship.size} paths"
47
-
48
- aggregate = Hash.new { |h, k| h[k] = PrefabProto::Logger.new }
49
-
50
- to_ship.each do |(path, severity), count|
51
- aggregate[path][SEVERITY_KEY[severity]] = count
52
- aggregate[path]['logger_name'] = path
53
- end
54
-
55
- loggers = PrefabProto::Loggers.new(
56
- loggers: aggregate.values,
57
- start_at: start_at_was,
58
- end_at: Prefab::TimeHelpers.now_in_ms,
59
- instance_hash: @client.instance_hash,
60
- namespace: @client.namespace
61
- )
62
-
63
- result = post('/api/v1/known-loggers', loggers)
64
-
65
- LOG.debug "Uploaded #{to_ship.size} paths: #{result.status}"
66
- end
67
- end
68
- end
69
- end
@@ -1,264 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Prefab
4
- class LoggerClient < ::Logger
5
- SEP = '.'
6
- BASE_KEY = 'log-level'
7
- UNKNOWN_PATH = 'unknown.'
8
- LOG_TAGS = 'log.tags'
9
- REQ_TAGS = 'req.tags'
10
-
11
- LOG_LEVEL_LOOKUPS = {
12
- PrefabProto::LogLevel::NOT_SET_LOG_LEVEL => ::Logger::DEBUG,
13
- PrefabProto::LogLevel::TRACE => ::Logger::DEBUG,
14
- PrefabProto::LogLevel::DEBUG => ::Logger::DEBUG,
15
- PrefabProto::LogLevel::INFO => ::Logger::INFO,
16
- PrefabProto::LogLevel::WARN => ::Logger::WARN,
17
- PrefabProto::LogLevel::ERROR => ::Logger::ERROR,
18
- PrefabProto::LogLevel::FATAL => ::Logger::FATAL
19
- }.freeze
20
-
21
- def self.instance
22
- @@shared_instance ||= LoggerClient.new($stdout)
23
- end
24
-
25
- def initialize(logdev, log_path_aggregator: nil, formatter: Options::DEFAULT_LOG_FORMATTER, prefix: nil)
26
- super(logdev)
27
- self.formatter = Prefab::Logging::FormatterBase.new(formatter_proc: formatter, logger_client: self)
28
- @config_client = BootstrappingConfigClient.new
29
- @silences = Concurrent::Map.new(initial_capacity: 2)
30
- @recurse_check = Concurrent::Map.new(initial_capacity: 2)
31
- @prefix = "#{prefix}#{prefix && '.'}"
32
-
33
- @context_keys_map = Concurrent::Map.new(initial_capacity: 4)
34
-
35
- @log_path_aggregator = log_path_aggregator
36
- @@shared_instance = self
37
- end
38
-
39
- def add_context_keys(*keys)
40
- context_keys.merge(keys)
41
- end
42
-
43
- def with_context_keys(*keys)
44
- context_keys.merge(keys)
45
- yield
46
- ensure
47
- context_keys.subtract(keys)
48
- end
49
-
50
- def internal_logger(path = nil)
51
- InternalLogger.new(path, self)
52
- end
53
-
54
- def context_keys
55
- @context_keys_map.fetch_or_store(local_log_id, Concurrent::Set.new)
56
- end
57
-
58
- # InternalLoggers Will Call This
59
- def add_internal(severity, message, progname, loc, log_context = {}, &block)
60
- path_loc = get_loc_path(loc)
61
- path = @prefix + path_loc
62
-
63
- log(message, path, progname, severity, log_context, &block)
64
- end
65
-
66
- def log_internal(severity, message, path, log_context = {}, &block)
67
- return if @recurse_check[local_log_id]
68
- @recurse_check[local_log_id] = true
69
- begin
70
- log(message, path, nil, severity, log_context, &block)
71
- ensure
72
- @recurse_check[local_log_id] = false
73
- end
74
- end
75
-
76
- def log(message, path, progname, severity, log_context = {})
77
- severity ||= ::Logger::UNKNOWN
78
-
79
- return true if !should_log? severity, path
80
- return true if @logdev.nil? || @silences[local_log_id]
81
-
82
- progname = @progname if progname.nil?
83
-
84
- if message.nil?
85
- if block_given?
86
- message = yield
87
- else
88
- message = progname
89
- progname = @progname
90
- end
91
- end
92
-
93
- @logdev.write(
94
- format_message(format_severity(severity), Time.now, progname, message, path, stringify_keys(log_context.merge(fetch_context_for_context_keys)))
95
- )
96
- true
97
- end
98
-
99
- def should_log?(severity, path)
100
- @log_path_aggregator&.push(path, severity)
101
- severity >= level_of(path)
102
- end
103
-
104
- def debug(progname = nil, **log_context, &block)
105
- add_internal(DEBUG, nil, progname, caller_locations(1, 1)[0], log_context, &block)
106
- end
107
-
108
- def info(progname = nil, **log_context, &block)
109
- add_internal(INFO, nil, progname, caller_locations(1, 1)[0], log_context, &block)
110
- end
111
-
112
- def warn(progname = nil, **log_context, &block)
113
- add_internal(WARN, nil, progname, caller_locations(1, 1)[0], log_context, &block)
114
- end
115
-
116
- def error(progname = nil, **log_context, &block)
117
- add_internal(ERROR, nil, progname, caller_locations(1, 1)[0], log_context, &block)
118
- end
119
-
120
- def fatal(progname = nil, **log_context, &block)
121
- add_internal(FATAL, nil, progname, caller_locations(1, 1)[0], log_context, &block)
122
- end
123
-
124
- def debug?
125
- true
126
- end
127
-
128
- def info?
129
- true
130
- end
131
-
132
- def warn?
133
- true
134
- end
135
-
136
- def error?
137
- true
138
- end
139
-
140
- def fatal?
141
- true
142
- end
143
-
144
- def level
145
- DEBUG
146
- end
147
-
148
- def tagged(*tags)
149
- to_add = tags.flatten.compact
150
- if block_given?
151
- new_log_tags = current_tags
152
- new_log_tags += to_add unless to_add.empty?
153
- Prefab::Context.with_merged_context({ "log" => { "tags" => new_log_tags } }) do
154
- with_context_keys LOG_TAGS do
155
- yield self
156
- end
157
- end
158
- else
159
- new_log_tags = Prefab::Context.current.get(REQ_TAGS) || []
160
- new_log_tags += to_add unless to_add.empty?
161
- add_context_keys REQ_TAGS
162
- Prefab::Context.current.set("req", {"tags": new_log_tags})
163
- self
164
- end
165
- end
166
-
167
- def current_tags
168
- Prefab::Context.current.get(LOG_TAGS) || []
169
- end
170
-
171
- def flush
172
- Prefab::Context.current.set("req", {"tags": nil})
173
- super if defined?(super)
174
- end
175
-
176
- def config_client=(config_client)
177
- @config_client = config_client
178
- end
179
-
180
- def local_log_id
181
- Thread.current.__id__
182
- end
183
-
184
- def silence
185
- @silences[local_log_id] = true
186
- yield self
187
- ensure
188
- @silences[local_log_id] = false
189
- end
190
-
191
- private
192
-
193
- NO_DEFAULT = nil
194
-
195
- def stringify_keys(hash)
196
- Hash[hash.map { |k, v| [k.to_s, v] }]
197
- end
198
-
199
- def fetch_context_for_context_keys
200
- context = Prefab::Context.current.to_h
201
- Hash[context_keys.map do |key|
202
- [key, context.dig(*key.split("."))]
203
- end]
204
- end
205
-
206
- # Find the closest match to 'log_level.path' in config
207
- def level_of(path)
208
- closest_log_level_match = nil
209
-
210
- path.split(SEP).each_with_object([BASE_KEY]) do |n, memo|
211
- memo << n
212
- val = @config_client.get(memo.join(SEP), NO_DEFAULT)
213
- closest_log_level_match = val unless val.nil?
214
- end
215
-
216
- if closest_log_level_match.nil?
217
- # get the top-level setting or default to WARN
218
- closest_log_level_match = @config_client.get(BASE_KEY, :WARN)
219
- end
220
-
221
- closest_log_level_match_int = PrefabProto::LogLevel.resolve(closest_log_level_match)
222
- LOG_LEVEL_LOOKUPS[closest_log_level_match_int]
223
- end
224
-
225
- def get_loc_path(loc)
226
- loc_path = loc.absolute_path || loc.to_s
227
- get_path(loc_path, loc.base_label)
228
- end
229
-
230
- # sanitize & clean the path of the caller so the key
231
- # looks like log_level.app.models.user
232
- def get_path(absolute_path, base_label)
233
- path = (absolute_path || UNKNOWN_PATH).dup
234
- path.slice! Dir.pwd
235
- path.gsub!(%r{(.*)?(?=/lib)}im, '') # replace everything before first lib
236
-
237
- path = path.gsub('/', SEP).gsub(/.rb.*/, '') + SEP + base_label
238
- path.slice! '.lib'
239
- path.slice! SEP
240
- path
241
- end
242
-
243
- def format_message(severity, datetime, progname, msg, path = nil, log_context = {})
244
- formatter = (@formatter || @default_formatter)
245
- compact_context = log_context.reject{ |_, v| v.nil? || ((v.is_a? Array) && v.empty?) }
246
- @formatter.call_proc(
247
- severity: severity,
248
- datetime: datetime,
249
- progname: progname,
250
- path: path,
251
- message: msg,
252
- log_context: compact_context
253
- )
254
- end
255
- end
256
-
257
- # StubConfigClient to be used while config client initializes
258
- # since it may log
259
- class BootstrappingConfigClient
260
- def get(_key, default = nil, _properties = {})
261
- ENV['PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL'] ? ENV['PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL'].upcase.to_sym : default
262
- end
263
- end
264
- end
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Murmur3
4
- ## MurmurHash3 was written by Austin Appleby, and is placed in the public
5
- ## domain. The author hereby disclaims copyright to this source code.
6
-
7
- MASK32 = 0xffffffff
8
-
9
- def self.murmur3_32_rotl(x, r)
10
- ((x << r) | (x >> (32 - r))) & MASK32
11
- end
12
-
13
- def self.murmur3_32_fmix(h)
14
- h &= MASK32
15
- h ^= h >> 16
16
- h = (h * 0x85ebca6b) & MASK32
17
- h ^= h >> 13
18
- h = (h * 0xc2b2ae35) & MASK32
19
- h ^ (h >> 16)
20
- end
21
-
22
- def self.murmur3_32__mmix(k1)
23
- k1 = (k1 * 0xcc9e2d51) & MASK32
24
- k1 = murmur3_32_rotl(k1, 15)
25
- (k1 * 0x1b873593) & MASK32
26
- end
27
-
28
- def self.murmur3_32(str, seed = 0)
29
- h1 = seed
30
- numbers = str.unpack('V*C*')
31
- tailn = str.length % 4
32
- tail = numbers.slice!(numbers.size - tailn, tailn)
33
- for k1 in numbers
34
- h1 ^= murmur3_32__mmix(k1)
35
- h1 = murmur3_32_rotl(h1, 13)
36
- h1 = (h1 * 5 + 0xe6546b64) & MASK32
37
- end
38
-
39
- unless tail.empty?
40
- k1 = 0
41
- tail.reverse_each do |c1|
42
- k1 = (k1 << 8) | c1
43
- end
44
- h1 ^= murmur3_32__mmix(k1)
45
- end
46
-
47
- h1 ^= str.length
48
- murmur3_32_fmix(h1)
49
- end
50
- end