prefab-cloud-ruby 0.13.3 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc875095444727947594dfa96e7a4e59fbab20a14ec486fdd4b804b936aad374
4
- data.tar.gz: 7af3f2155fdb85063c674b4f4bf3365b089e507789adf9eaa0ca5b4e206b9e19
3
+ metadata.gz: 82381e4b656d675f4b98ee11601cde1920637e52eb5b039814247500207b9fa5
4
+ data.tar.gz: 4ff8914774523e745d512d506cc42f20e71a657fd8191d82e7e6ba1878bf04c9
5
5
  SHA512:
6
- metadata.gz: aff23956f0c34934a5702bd51517ac14111a7888ba99948537fb76fbcdf162abf6a032ae8caba5c902e06894b0f3bf5ffcf5a95efce9cf00210b4f333059e233
7
- data.tar.gz: 21717c0f85eecd1972baf9fc1d8fa8b57d225da60eb6cba006b3ac4f288f4fab77c18f5d6a34e49ca8beb8835e22a520b3bc92b058658309d41db5eba9a05c86
6
+ metadata.gz: aac3fc04c779900ca92a6a859430a2fe7776bee90afba632456ff45e3bacb428dda61314d3cbf5a5b04ce61672e4591afb7ae0248b77352b381f52dcbbfb6f24
7
+ data.tar.gz: 5e2008ec802e2f32154bffb34804673187feb618a60edf2aba19666851787e14111e3d6e0fbeeebe8e3192cac56a32f3010b4d46ef507c3d16359886973ee90c
data/Gemfile CHANGED
@@ -8,6 +8,7 @@ gem 'google-protobuf', :platforms => :ruby
8
8
  gem 'googleapis-common-protos-types', :platforms => :ruby
9
9
 
10
10
  group :development do
11
+ gem 'benchmark-ips'
11
12
  gem 'grpc-tools', :platforms => :ruby
12
13
  gem "rdoc", "~> 3.12"
13
14
  gem "bundler"
data/Gemfile.lock CHANGED
@@ -3,6 +3,7 @@ GEM
3
3
  specs:
4
4
  addressable (2.8.0)
5
5
  public_suffix (>= 2.0.2, < 5.0)
6
+ benchmark-ips (2.10.0)
6
7
  builder (3.2.4)
7
8
  concurrent-ruby (1.1.10)
8
9
  daemons (1.4.1)
@@ -109,6 +110,7 @@ PLATFORMS
109
110
  ruby
110
111
 
111
112
  DEPENDENCIES
113
+ benchmark-ips
112
114
  bundler
113
115
  concurrent-ruby (~> 1.0, >= 1.0.5)
114
116
  faraday
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.13.3
1
+ 0.16.0
data/lib/prefab/client.rb CHANGED
@@ -5,7 +5,7 @@ module Prefab
5
5
  BASE_SLEEP_SEC = 0.5
6
6
  NO_DEFAULT_PROVIDED = :no_default_provided
7
7
 
8
- attr_reader :project_id, :shared_cache, :stats, :namespace, :interceptor, :api_key, :prefab_api_url, :options
8
+ attr_reader :shared_cache, :stats, :namespace, :interceptor, :api_key, :prefab_api_url, :options
9
9
 
10
10
  def initialize(options = Prefab::Options.new)
11
11
  @options = options
@@ -15,12 +15,10 @@ module Prefab
15
15
  @stubs = {}
16
16
 
17
17
  if @options.local_only?
18
- @project_id = 0
19
18
  log_internal Logger::INFO, "Prefab Running in Local Mode"
20
19
  else
21
20
  @api_key = @options.api_key
22
- raise Prefab::Errors::InvalidApiKeyError.new(@api_key) if @api_key.nil? || @api_key.empty? || api_key.count("-") != 3
23
- @project_id = @api_key.split("-")[0].to_i # unvalidated, but that's ok. APIs only listen to the actual passwd
21
+ raise Prefab::Errors::InvalidApiKeyError.new(@api_key) if @api_key.nil? || @api_key.empty? || api_key.count("-") < 1
24
22
  @interceptor = Prefab::AuthInterceptor.new(@api_key)
25
23
  @prefab_api_url = @options.prefab_api_url
26
24
  @prefab_grpc_url = @options.prefab_grpc_url
@@ -82,10 +80,6 @@ module Prefab
82
80
  end
83
81
  end
84
82
 
85
- def cache_key(post_fix)
86
- "prefab:#{project_id}:#{post_fix}"
87
- end
88
-
89
83
  def reset!
90
84
  @stubs.clear
91
85
  @_channel = nil
@@ -5,8 +5,8 @@ module Prefab
5
5
 
6
6
  RECONNECT_WAIT = 5
7
7
  DEFAULT_CHECKPOINT_FREQ_SEC = 60
8
- DEFAULT_S3CF_BUCKET = 'http://d2j4ed6ti5snnd.cloudfront.net'
9
8
  SSE_READ_TIMEOUT = 300
9
+ AUTH_USER = "authuser"
10
10
 
11
11
  def initialize(base_client, timeout)
12
12
  @base_client = base_client
@@ -29,8 +29,6 @@ module Prefab
29
29
 
30
30
  @cancellable_interceptor = Prefab::CancellableInterceptor.new(@base_client)
31
31
 
32
- @s3_cloud_front = ENV["PREFAB_S3CF_BUCKET"] || DEFAULT_S3CF_BUCKET
33
-
34
32
  if @options.local_only?
35
33
  finish_init!(:local_only)
36
34
  else
@@ -119,7 +117,6 @@ module Prefab
119
117
  interceptors: [@base_client.interceptor, @cancellable_interceptor])
120
118
  end
121
119
 
122
- # try API first, if not, fallback to s3
123
120
  def load_checkpoint
124
121
  success = load_checkpoint_api_cdn
125
122
 
@@ -134,12 +131,6 @@ module Prefab
134
131
  if success
135
132
  return
136
133
  else
137
- @base_client.log_internal Logger::INFO, "LoadCheckpoint: Fallback to S3"
138
- end
139
-
140
- success = load_checkpoint_from_s3
141
-
142
- if !success
143
134
  @base_client.log_internal Logger::WARN, "No success loading checkpoints"
144
135
  end
145
136
  end
@@ -159,24 +150,19 @@ module Prefab
159
150
 
160
151
  def load_checkpoint_api_cdn
161
152
  key_hash = Murmur3.murmur3_32(@base_client.api_key)
162
- url = "#{@options.url_for_api_cdn}/api/v1/configs/#{@base_client.project_id}/#{key_hash}/0"
153
+ url = "#{@options.url_for_api_cdn}/api/v1/configs/0/#{key_hash}/0"
163
154
  conn = if Faraday::VERSION[0].to_i >= 2
164
155
  Faraday.new(url) do |conn|
165
- conn.request :authorization, :basic, @base_client.project_id, @base_client.api_key
156
+ conn.request :authorization, :basic, AUTH_USER, @base_client.api_key
166
157
  end
167
158
  else
168
159
  Faraday.new(url) do |conn|
169
- conn.request :basic_auth, @base_client.project_id, @base_client.api_key
160
+ conn.request :basic_auth, AUTH_USER, @base_client.api_key
170
161
  end
171
162
  end
172
163
  load_url(conn, :remote_cdn_api)
173
164
  end
174
165
 
175
- def load_checkpoint_from_s3
176
- url = "#{@s3_cloud_front}/#{@base_client.api_key.gsub("|", "/")}"
177
- load_url(Faraday.new(url), :remote_s3)
178
- end
179
-
180
166
  def load_url(conn, source)
181
167
  resp = conn.get('')
182
168
  if resp.status == 200
@@ -193,6 +179,7 @@ module Prefab
193
179
  end
194
180
 
195
181
  def load_configs(configs, source)
182
+ project_id = configs.config_service_pointer.project_id
196
183
  project_env_id = configs.config_service_pointer.project_env_id
197
184
  @config_resolver.project_env_id = project_env_id
198
185
  starting_highwater_mark = @config_loader.highwater_mark
@@ -201,7 +188,7 @@ module Prefab
201
188
  @config_loader.set(config, source)
202
189
  end
203
190
  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}'"
191
+ @base_client.log_internal Logger::INFO, "Found new checkpoint with highwater id #{@config_loader.highwater_mark} from #{source} in project #{project_id} environment: #{project_env_id} and namespace: '#{@namespace}'"
205
192
  else
206
193
  @base_client.log_internal Logger::DEBUG, "Checkpoint with highwater id #{@config_loader.highwater_mark} from #{source}. No changes.", "prefab.config_client.load_configs"
207
194
  end
@@ -240,7 +227,7 @@ module Prefab
240
227
  end
241
228
 
242
229
  def start_sse_streaming_connection_thread(start_at_id)
243
- auth = "#{@base_client.project_id}:#{@base_client.api_key}"
230
+ auth = "#{AUTH_USER}:#{@base_client.api_key}"
244
231
  auth_string = Base64.strict_encode64(auth)
245
232
  headers = {
246
233
  "x-prefab-start-at-id": start_at_id,
@@ -56,57 +56,59 @@ module Prefab
56
56
 
57
57
  def load_classpath_config
58
58
  classpath_dir = @prefab_options.prefab_config_classpath_dir
59
- load_glob(File.join(classpath_dir, ".prefab*config.yaml"))
59
+ rtn = load_glob(File.join(classpath_dir, ".prefab.default.config.yaml"))
60
+ @prefab_options.prefab_envs.each do |env|
61
+ rtn = rtn.merge load_glob(File.join(classpath_dir, ".prefab.#{env}.config.yaml"))
62
+ end
63
+ rtn
60
64
  end
61
65
 
62
66
  def load_local_overrides
63
67
  override_dir = @prefab_options.prefab_config_override_dir
64
- load_glob(File.join(override_dir, ".prefab*config.yaml"))
68
+ rtn = load_glob(File.join(override_dir, ".prefab.overrides.config.yaml"))
69
+ @prefab_options.prefab_envs.each do |env|
70
+ rtn = rtn.merge load_glob(File.join(override_dir, ".prefab.#{env}.config.yaml"))
71
+ end
72
+ rtn
65
73
  end
66
74
 
67
75
  def load_glob(glob)
68
76
  rtn = {}
69
77
  Dir.glob(glob).each do |file|
78
+ @base_client.log_internal Logger::INFO, "Load #{file}"
70
79
  yaml = load(file)
71
80
  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
81
+ load_kv(k, v, rtn, file)
105
82
  end
106
83
  end
107
84
  rtn
108
85
  end
109
86
 
87
+ def load_kv(k, v, rtn, file)
88
+ if v.class == Hash
89
+ if v['feature_flag']
90
+ rtn[k] = feature_flag_config(file, k, v)
91
+ else
92
+ v.each do |nest_k, nest_v|
93
+ nested_key = "#{k}.#{nest_k}"
94
+ nested_key = k if nest_k == "_"
95
+ load_kv(nested_key, nest_v, rtn, file)
96
+ end
97
+ end
98
+ else
99
+ rtn[k] = {
100
+ source: file,
101
+ match: "default",
102
+ config: Prefab::Config.new(
103
+ key: k,
104
+ rows: [
105
+ Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(value_from(v)))
106
+ ]
107
+ )
108
+ }
109
+ end
110
+ end
111
+
110
112
  def load(filename)
111
113
  if File.exist? filename
112
114
  @base_client.log_internal Logger::INFO, "Load #{filename}"
@@ -130,11 +132,11 @@ module Prefab
130
132
  end
131
133
  end
132
134
 
133
- def feature_flag_config(file, k, env_k, env_v)
135
+ def feature_flag_config(file, key, value)
134
136
  criteria = Prefab::Criteria.new(operator: 'ALWAYS_TRUE')
135
137
 
136
- if env_v['criteria']
137
- criteria = Prefab::Criteria.new(criteria_values(env_v['criteria']))
138
+ if value['criteria']
139
+ criteria = Prefab::Criteria.new(criteria_values(value['criteria']))
138
140
  end
139
141
 
140
142
  row = Prefab::ConfigRow.new(
@@ -154,16 +156,16 @@ module Prefab
154
156
  )
155
157
  )
156
158
 
157
- unless env_v.has_key?('value')
158
- raise Prefab::Error, "Feature flag config `#{env_k}` #{file} must have a `value`"
159
+ unless value.has_key?('value')
160
+ raise Prefab::Error, "Feature flag config `#{key}` #{file} must have a `value`"
159
161
  end
160
162
 
161
163
  {
162
164
  source: file,
163
- match: k,
165
+ match: key,
164
166
  config: Prefab::Config.new(
165
- key: env_k,
166
- variants: [Prefab::FeatureFlagVariant.new(value_from(env_v['value']))],
167
+ key: key,
168
+ variants: [Prefab::FeatureFlagVariant.new(value_from(value['value']))],
167
169
  rows: [row]
168
170
  )
169
171
  }
@@ -116,38 +116,45 @@ module Prefab
116
116
  end
117
117
 
118
118
  def get_user_pct(feature, lookup_key)
119
- to_hash = "#{@base_client.project_id}#{feature}#{lookup_key}"
119
+ to_hash = "#{feature}#{lookup_key}"
120
120
  int_value = Murmur3.murmur3_32(to_hash)
121
121
  int_value / MAX_32_FLOAT
122
122
  end
123
123
 
124
- # def criteria_match?(rule, lookup_key, attributes)
125
- #
126
- # end
127
124
  def criteria_match?(criteria, lookup_key, attributes)
128
- if criteria.operator == :ALWAYS_TRUE
129
- return true
130
- elsif criteria.operator == :LOOKUP_KEY_IN
131
- return criteria.values.include?(lookup_key)
132
- elsif criteria.operator == :LOOKUP_KEY_NOT_IN
133
- return !criteria.values.include?(lookup_key)
134
- elsif criteria.operator == :IN_SEG
135
- return segment_matches(criteria.values, lookup_key, attributes).any?
136
- elsif criteria.operator == :NOT_IN_SEG
137
- return segment_matches(criteria.values, lookup_key, attributes).none?
138
- elsif criteria.operator == :PROP_IS_ONE_OF
139
- return criteria.values.include?(attributes[criteria.property]) || criteria.values.include?(attributes[criteria.property.to_sym])
140
- elsif criteria.operator == :PROP_IS_NOT_ONE_OF
141
- return !(criteria.values.include?(attributes[criteria.property]) || criteria.values.include?(attributes[criteria.property.to_sym]))
125
+ case criteria.operator
126
+ when :ALWAYS_TRUE
127
+ true
128
+ when :LOOKUP_KEY_IN
129
+ criteria.values.include?(lookup_key)
130
+ when :LOOKUP_KEY_NOT_IN
131
+ !criteria.values.include?(lookup_key)
132
+ when :IN_SEG
133
+ segment_matches?(criteria.values, lookup_key, attributes)
134
+ when :NOT_IN_SEG
135
+ !segment_matches?(criteria.values, lookup_key, attributes)
136
+ when :PROP_IS_ONE_OF
137
+ criteria.values.include?(attribute_value(attributes, criteria.property))
138
+ when :PROP_IS_NOT_ONE_OF
139
+ !criteria.values.include?(attribute_value(attributes, criteria.property))
140
+ when :PROP_ENDS_WITH_ONE_OF
141
+ criteria.values.any? { |value| attribute_value(attributes, criteria.property)&.end_with?(value) }
142
+ when :PROP_DOES_NOT_END_WITH_ONE_OF
143
+ criteria.values.none? { |value| attribute_value(attributes, criteria.property)&.end_with?(value) }
144
+ else
145
+ @base_client.log.info("Unknown Operator: #{criteria.operator}")
146
+ false
142
147
  end
143
- @base_client.log.info("Unknown Operator")
144
- false
145
148
  end
146
149
 
147
- # evaluate each segment key and return whether each one matches
150
+ def attribute_value(attributes, property)
151
+ attributes[property] || attributes[property.to_sym]
152
+ end
153
+
154
+ # evaluate each segment key and return whether any match
148
155
  # there should be an associated segment available as a standard config obj
149
- def segment_matches(segment_keys, lookup_key, attributes)
150
- segment_keys.map do |segment_key|
156
+ def segment_matches?(segment_keys, lookup_key, attributes)
157
+ segment_keys.any? do |segment_key|
151
158
  segment = @base_client.config_client.get(segment_key)
152
159
  if segment.nil?
153
160
  @base_client.log.info("Missing Segment")
@@ -160,9 +167,9 @@ module Prefab
160
167
 
161
168
  # does a given segment match?
162
169
  def segment_match?(segment, lookup_key, attributes)
163
- segment.criterion.map do |criteria|
170
+ segment.criterion.any? do |criteria|
164
171
  criteria_match?(criteria, lookup_key, attributes)
165
- end.any?
172
+ end
166
173
  end
167
174
  end
168
175
  end
@@ -19,7 +19,7 @@ module Prefab
19
19
  end
20
20
 
21
21
  def add_internal(severity, message = nil, progname = nil, loc, &block)
22
- path = get_path(loc.absolute_path, loc.base_label)
22
+ path = get_loc_path(loc)
23
23
  log_internal(message, path, progname, severity, &block)
24
24
  end
25
25
 
@@ -121,15 +121,19 @@ module Prefab
121
121
  val(closest_log_level_match)
122
122
  end
123
123
 
124
+ def get_loc_path(loc)
125
+ loc_path = loc.absolute_path || loc.to_s
126
+ get_path(loc_path, loc.base_label)
127
+ end
128
+
124
129
  # sanitize & clean the path of the caller so the key
125
130
  # looks like log_level.app.models.user
126
131
  def get_path(absolute_path, base_label)
127
132
  path = (absolute_path || UNKNOWN).dup
128
133
  path.slice! Dir.pwd
134
+ path.gsub!(/(.*)?(?=\/lib)/im, "") # replace everything before first lib
129
135
 
130
- path.gsub!(/.*?(?=\/lib\/)/im, "")
131
-
132
- path = path.gsub("/", SEP).gsub(".rb", "") + SEP + base_label
136
+ path = path.gsub("/", SEP).gsub(/.rb.*/, "") + SEP + base_label
133
137
  path.slice! ".lib"
134
138
  path.slice! SEP
135
139
  path
@@ -14,7 +14,7 @@ module Prefab
14
14
  attr_reader :on_init_failure
15
15
  attr_reader :prefab_config_override_dir
16
16
  attr_reader :prefab_config_classpath_dir
17
- attr_reader :defaults_env
17
+ attr_reader :prefab_envs
18
18
 
19
19
  DEFAULT_LOG_FORMATTER = proc { |severity, datetime, progname, msg|
20
20
  "#{severity.ljust(5)} #{datetime}: #{progname} #{msg}\n"
@@ -50,7 +50,7 @@ module Prefab
50
50
  prefab_datasources: ENV['PREFAB_DATASOURCES'] == "LOCAL_ONLY" ? DATASOURCES::LOCAL_ONLY : DATASOURCES::ALL,
51
51
  prefab_config_override_dir: Dir.home,
52
52
  prefab_config_classpath_dir: ".",
53
- defaults_env: ""
53
+ prefab_envs: ENV['PREFAB_ENVS'].nil? ? [] : ENV['PREFAB_ENVS'].split(",")
54
54
  )
55
55
  # debugger
56
56
  @api_key = api_key
@@ -67,7 +67,7 @@ module Prefab
67
67
  @prefab_datasources = prefab_datasources
68
68
  @prefab_config_classpath_dir = prefab_config_classpath_dir
69
69
  @prefab_config_override_dir = prefab_config_override_dir
70
- @defaults_env = defaults_env
70
+ @prefab_envs = Array(prefab_envs)
71
71
  end
72
72
 
73
73
  def local_only?
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: prefab-cloud-ruby 0.13.3 ruby lib
5
+ # stub: prefab-cloud-ruby 0.16.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "prefab-cloud-ruby".freeze
9
- s.version = "0.13.3"
9
+ s.version = "0.16.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Jeff Dwyer".freeze]
14
- s.date = "2022-08-12"
14
+ s.date = "2022-09-01"
15
15
  s.description = "RateLimits & Config as a service".freeze
16
16
  s.email = "jdwyer@prefab.cloud".freeze
17
17
  s.extra_rdoc_files = [
@@ -54,7 +54,8 @@ Gem::Specification.new do |s|
54
54
  "lib/prefab_services_pb.rb",
55
55
  "prefab-cloud-ruby.gemspec",
56
56
  "run_test_harness_server.sh",
57
- "test/.prefab.test.config.yaml",
57
+ "test/.prefab.default.config.yaml",
58
+ "test/.prefab.unit_tests.config.yaml",
58
59
  "test/harness_server.rb",
59
60
  "test/test_client.rb",
60
61
  "test/test_config_client.rb",
@@ -80,6 +81,7 @@ Gem::Specification.new do |s|
80
81
  s.add_runtime_dependency(%q<grpc>.freeze, [">= 0"])
81
82
  s.add_runtime_dependency(%q<google-protobuf>.freeze, [">= 0"])
82
83
  s.add_runtime_dependency(%q<googleapis-common-protos-types>.freeze, [">= 0"])
84
+ s.add_development_dependency(%q<benchmark-ips>.freeze, [">= 0"])
83
85
  s.add_development_dependency(%q<grpc-tools>.freeze, [">= 0"])
84
86
  s.add_development_dependency(%q<rdoc>.freeze, ["~> 3.12"])
85
87
  s.add_development_dependency(%q<bundler>.freeze, [">= 0"])
@@ -93,6 +95,7 @@ Gem::Specification.new do |s|
93
95
  s.add_dependency(%q<grpc>.freeze, [">= 0"])
94
96
  s.add_dependency(%q<google-protobuf>.freeze, [">= 0"])
95
97
  s.add_dependency(%q<googleapis-common-protos-types>.freeze, [">= 0"])
98
+ s.add_dependency(%q<benchmark-ips>.freeze, [">= 0"])
96
99
  s.add_dependency(%q<grpc-tools>.freeze, [">= 0"])
97
100
  s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
98
101
  s.add_dependency(%q<bundler>.freeze, [">= 0"])
@@ -0,0 +1,2 @@
1
+ sample: default sample value
2
+ sample_bool: true
@@ -0,0 +1,25 @@
1
+ sample_int: 123
2
+ sample_double: 12.12
3
+ sample_bool: true
4
+ false_value: false
5
+ zero_value: 0
6
+ sample_to_override: Foo
7
+ prefab.log_level: debug
8
+ sample: test sample value
9
+ enabled_flag: true
10
+ disabled_flag: false
11
+ flag_with_a_value: { "feature_flag": "true", value: "all-features" }
12
+ in_lookup_key: { "feature_flag": "true", value: true, criteria: { operator: LOOKUP_KEY_IN, values: [ "abc123", "xyz987" ] } }
13
+ just_my_domain: { "feature_flag": "true", value: "new-version", criteria: { operator: PROP_IS_ONE_OF, property: "domain", values: [ "prefab.cloud", "example.com" ] } }
14
+ nested:
15
+ values:
16
+ _: top level
17
+ string: nested value
18
+
19
+ logging:
20
+ app:
21
+ _: error
22
+ controller:
23
+ hello:
24
+ _: warn
25
+ index: info
data/test/test_client.rb CHANGED
@@ -89,7 +89,7 @@ class TestClient < Minitest::Test
89
89
  options = Prefab::Options.new(**{
90
90
  prefab_config_override_dir: "none",
91
91
  prefab_config_classpath_dir: "test",
92
- defaults_env: "unit_tests",
92
+ prefab_envs: ["unit_tests"],
93
93
  prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
94
94
  }.merge(overrides))
95
95
 
@@ -6,7 +6,7 @@ class TestConfigClient < Minitest::Test
6
6
  options = Prefab::Options.new(
7
7
  prefab_config_override_dir: "none",
8
8
  prefab_config_classpath_dir: "test",
9
- defaults_env: "unit_tests",
9
+ prefab_envs: "unit_tests",
10
10
  prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
11
11
  )
12
12
 
@@ -32,6 +32,21 @@ class TestConfigClient < Minitest::Test
32
32
  assert_match(/couldn't initialize in 0.01 second timeout/, err.message)
33
33
  end
34
34
 
35
+ def test_prefab_envs_is_forgiving
36
+ assert_equal ["my_env"], Prefab::Options.new(
37
+ prefab_envs: "my_env",
38
+ ).prefab_envs
39
+
40
+ assert_equal ["my_env", "a_second_env"], Prefab::Options.new(
41
+ prefab_envs: ["my_env", "a_second_env"],
42
+ ).prefab_envs
43
+ end
44
+
45
+ def test_prefab_envs_env_var
46
+ ENV["PREFAB_ENVS"] = "one,two"
47
+ assert_equal ["one", "two"], Prefab::Options.new().prefab_envs
48
+ end
49
+
35
50
  def test_invalid_api_key_error
36
51
  options = Prefab::Options.new(
37
52
  api_key: "",
@@ -6,7 +6,7 @@ class TestConfigLoader < Minitest::Test
6
6
  options = Prefab::Options.new(
7
7
  prefab_config_override_dir: "none",
8
8
  prefab_config_classpath_dir: "test",
9
- defaults_env: "unit_tests"
9
+ prefab_envs: "unit_tests"
10
10
  )
11
11
  @loader = Prefab::ConfigLoader.new(MockBaseClient.new(options))
12
12
  end
@@ -18,11 +18,19 @@ class TestConfigLoader < Minitest::Test
18
18
  should_be :double, 12.12, "sample_double"
19
19
  end
20
20
 
21
- def test_load_in_no_default_env
21
+ def test_nested
22
+ should_be :string, "nested value", "nested.values.string"
23
+ should_be :string, "top level", "nested.values"
24
+ should_be :string, "error", "logging.app"
25
+ should_be :string, "warn", "logging.app.controller.hello"
26
+ should_be :string, "info", "logging.app.controller.hello.index"
27
+ end
28
+
29
+ def test_load_without_unit_test_env
22
30
  options = Prefab::Options.new(
23
31
  prefab_config_override_dir: "none",
24
32
  prefab_config_classpath_dir: "test",
25
- # no defaults_env
33
+ # no prefab_envs
26
34
  )
27
35
  @loader = Prefab::ConfigLoader.new(MockBaseClient.new(options))
28
36
  should_be :string, "default sample value", "sample"
@@ -37,10 +37,10 @@ class TestFeatureFlagClient < Minitest::Test
37
37
  # weights above chosen to be 86% in variant_idx 2. and 14% in variant_idx 1.
38
38
  # since hashes high is 86.32 > 86 it just falls outside the 86% range and gets false
39
39
 
40
- # "1FlagNamehashes high" hashes to 86.322% through dist
40
+ # "FlagNamevery high hash" hashes to 88.8602812% through dist
41
41
  assert_equal false,
42
- evaluate(feature, "hashes high", [], flag, variants)
43
- # "1FlagNamehashes low" hashes to 44.547% through dist
42
+ evaluate(feature, "very high hash", [], flag, variants)
43
+ # "FlagNamehashes low" hashes to 42.7934% through dist
44
44
  assert_equal true,
45
45
  evaluate(feature, "hashes low", [], flag, variants)
46
46
 
@@ -299,6 +299,66 @@ class TestFeatureFlagClient < Minitest::Test
299
299
 
300
300
  end
301
301
 
302
+ def test_prop_ends_with_one_of
303
+ feature = "FlagName"
304
+
305
+ variants = [
306
+ Prefab::FeatureFlagVariant.new(bool: false),
307
+ Prefab::FeatureFlagVariant.new(bool: true)
308
+ ]
309
+ flag = Prefab::FeatureFlag.new(
310
+ active: true,
311
+ inactive_variant_idx: 1,
312
+ rules: [
313
+ Prefab::Rule.new(
314
+ criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::PROP_ENDS_WITH_ONE_OF,
315
+ property: "email",
316
+ values: ["@example.com"]),
317
+ variant_weights: [
318
+ Prefab::VariantWeight.new(weight: 100, variant_idx: 2)
319
+ ]
320
+ )
321
+ ]
322
+ )
323
+
324
+ assert_equal false, evaluate(feature, "user:0", {}, flag, variants)
325
+ assert_equal true, evaluate(feature, "user:0", {email: "test@example.com"}, flag, variants)
326
+ assert_equal true, evaluate(feature, "user:0", {"email" => "test@example.com"}, flag, variants)
327
+ end
328
+
329
+ def test_prop_does_not_end_with_one_of
330
+ feature = "FlagName"
331
+
332
+ variants = [
333
+ Prefab::FeatureFlagVariant.new(bool: false),
334
+ Prefab::FeatureFlagVariant.new(bool: true)
335
+ ]
336
+ flag = Prefab::FeatureFlag.new(
337
+ active: true,
338
+ inactive_variant_idx: 1,
339
+ rules: [
340
+ Prefab::Rule.new(
341
+ criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::PROP_DOES_NOT_END_WITH_ONE_OF,
342
+ property: "email",
343
+ values: ["@example.com"]),
344
+ variant_weights: [
345
+ Prefab::VariantWeight.new(weight: 100, variant_idx: 2)
346
+ ]
347
+ ),
348
+ Prefab::Rule.new(
349
+ criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::ALWAYS_TRUE),
350
+ variant_weights: [
351
+ Prefab::VariantWeight.new(weight: 100, variant_idx: 1)
352
+ ]
353
+ )
354
+ ],
355
+ )
356
+
357
+ assert_equal true, evaluate(feature, "user:0", {}, flag, variants)
358
+ assert_equal false, evaluate(feature, "user:0", {email: "test@example.com"}, flag, variants)
359
+ assert_equal false, evaluate(feature, "user:0", {"email" => "test@example.com"}, flag, variants)
360
+ end
361
+
302
362
  def evaluate(feature_name, lookup_key, attributes, flag, variants)
303
363
  variant = @client.get_variant(feature_name, lookup_key, attributes, flag, variants)
304
364
  @client.value_of_variant(variant)
data/test/test_helper.rb CHANGED
@@ -81,7 +81,7 @@ def new_client(overrides = {})
81
81
  options = Prefab::Options.new(**{
82
82
  prefab_config_override_dir: "none",
83
83
  prefab_config_classpath_dir: "test",
84
- defaults_env: "unit_tests",
84
+ prefab_envs: ["unit_tests"],
85
85
  prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
86
86
  }.merge(overrides))
87
87
 
data/test/test_logger.rb CHANGED
@@ -4,6 +4,7 @@ require 'test_helper'
4
4
  class TestCLogger < Minitest::Test
5
5
  def setup
6
6
  Prefab::LoggerClient.send(:public, :get_path)
7
+ Prefab::LoggerClient.send(:public, :get_loc_path)
7
8
  Prefab::LoggerClient.send(:public, :level_of)
8
9
  @logger = Prefab::LoggerClient.new($stdout)
9
10
  end
@@ -16,6 +17,27 @@ class TestCLogger < Minitest::Test
16
17
  assert_equal "active_support.log_subscriber.info",
17
18
  @logger.get_path("/Users/jdwyah/.rvm/gems/ruby-2.3.3@forcerank/gems/activesupport-4.1.16/lib/active_support/log_subscriber.rb",
18
19
  "info")
20
+ assert_equal "active_support.log_subscriber.info",
21
+ @logger.get_path("/Users/jeffdwyer/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/activesupport-7.0.2.4/lib/active_support/log_subscriber.rb:130:in `info'",
22
+ "info")
23
+ end
24
+
25
+ def test_loc_resolution
26
+ backtrace_location = Struct.new(:absolute_path, :base_label, :string) do
27
+ def to_s
28
+ string
29
+ end
30
+ end # https://ruby-doc.org/core-3.0.0/Thread/Backtrace/Location.html
31
+
32
+ # verify that even if the Thread::Backtrace::Location does not have an absolute_location, we do our best
33
+ assert_equal "active_support.log_subscriber.info",
34
+ @logger.get_loc_path(backtrace_location.new(nil,
35
+ "info",
36
+ "/Users/jeffdwyer/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/activesupport-7.0.2.4/lib/active_support/log_subscriber.rb:130:in `info'"))
37
+ assert_equal "test_l.info",
38
+ @logger.get_loc_path(backtrace_location.new("/Users/jdwyah/Documents/workspace/RateLimitInc/prefab-cloud-ruby/lib/test_l.rb",
39
+ "info",
40
+ "/Users/jeffdwyer/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/activesupport-7.0.2.4/lib/active_support/log_subscriber.rb:130:in `info'"))
19
41
  end
20
42
 
21
43
  def test_level_of
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prefab-cloud-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.3
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Dwyer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-12 00:00:00.000000000 Z
11
+ date: 2022-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -100,6 +100,20 @@ dependencies:
100
100
  - - ">="
101
101
  - !ruby/object:Gem::Version
102
102
  version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: benchmark-ips
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
103
117
  - !ruby/object:Gem::Dependency
104
118
  name: grpc-tools
105
119
  requirement: !ruby/object:Gem::Requirement
@@ -227,7 +241,8 @@ files:
227
241
  - lib/prefab_services_pb.rb
228
242
  - prefab-cloud-ruby.gemspec
229
243
  - run_test_harness_server.sh
230
- - test/.prefab.test.config.yaml
244
+ - test/.prefab.default.config.yaml
245
+ - test/.prefab.unit_tests.config.yaml
231
246
  - test/harness_server.rb
232
247
  - test/test_client.rb
233
248
  - test/test_config_client.rb
@@ -1,32 +0,0 @@
1
- sample: default sample value
2
- sample_int: 123
3
- sample_double: 12.12
4
- sample_bool: true
5
- false_value: false
6
- zero_value: 0
7
- sample_to_override: Foo
8
- prefab.log_level: debug
9
- unit_tests:
10
- sample: test sample value
11
- enabled_flag: true
12
- disabled_flag: false
13
- flag_with_a_value:
14
- feature_flag: true
15
- value: "all-features"
16
- in_lookup_key:
17
- feature_flag: true
18
- value: true
19
- criteria:
20
- operator: LOOKUP_KEY_IN
21
- values:
22
- - abc123
23
- - xyz987
24
- just_my_domain:
25
- feature_flag: true
26
- value: new-version
27
- criteria:
28
- operator: PROP_IS_ONE_OF
29
- property: domain
30
- values: ["prefab.cloud", "example.com"]
31
- ignored_env:
32
- sample: ignored value