prefab-cloud-ruby 0.6.0 → 0.7.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
- SHA1:
3
- metadata.gz: fa80ee91d95324b503253f95a12deb9b0db91291
4
- data.tar.gz: 0315d992eb2a87073998e1d7db4864a8c3da289c
2
+ SHA256:
3
+ metadata.gz: a36d24a94355b3b7a2b3b889734e1f6f6ccafc7dcebfb11ff11d77ba92ea7828
4
+ data.tar.gz: 326b3166d4fc2589ebb2550114b2cc198617620daa70ecfb6b9fe394c9dd79a5
5
5
  SHA512:
6
- metadata.gz: e43a7a8dafbfd3a9e6c12cd93b57bed2ee8fd738406fdba166c5eddad03154de66d9150285961f8c08da58d0754fde385b8b68e0bb22f9a27de8441f231a429a
7
- data.tar.gz: c3586f0e6b689e0ee5b1fd86e621d79ffab5a2263cf977c004fd7a4618cea403059f075f719a86d5a0b4533a16e398e3acb35dff9ca0930082fb81266ef1e545
6
+ metadata.gz: fb73839190f23e05540688572b8f47c1300ae9181bdecef0f24fdecb9a9e73ace1c88937e7c228601c20ff68405876c21b977e45bd691ba33f59d71dd4811718
7
+ data.tar.gz: 792f04aa032c2f9975444a191ddbb8f727e256d12ec2fc4b3c26819bff4a425deabffb3ff262221e7fe90ed4c1d1422d7a2cb447530264cde494b4e149de7e78
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 3.0.3
data/CODEOWNERS ADDED
@@ -0,0 +1 @@
1
+ * @prefab-cloud/prefabdevs @prefab-cloud/prefabmaintainers @prefab-cloud/prefabadmins
data/Gemfile CHANGED
@@ -2,13 +2,16 @@ source "https://rubygems.org"
2
2
 
3
3
  gem 'concurrent-ruby', '~> 1.0', '>= 1.0.5'
4
4
  gem 'faraday'
5
- gem 'grpc'
5
+ gem 'grpc', :platforms => :ruby
6
+ gem 'google-protobuf', :platforms => :ruby
7
+ gem 'googleapis-common-protos-types', :platforms => :ruby
6
8
 
7
9
  group :development do
8
- gem 'grpc-tools', '~> 1.17.1'
10
+ gem 'grpc-tools', :platforms => :ruby
9
11
  gem "shoulda", ">= 0"
10
12
  gem "rdoc", "~> 3.12"
11
- gem "bundler", "~> 1.0"
13
+ gem "bundler"
12
14
  gem "juwelier", "~> 2.4.9"
13
15
  gem "simplecov", ">= 0"
16
+ gem 'thin'
14
17
  end
data/Gemfile.lock CHANGED
@@ -10,9 +10,11 @@ GEM
10
10
  public_suffix (>= 2.0.2, < 5.0)
11
11
  builder (3.2.4)
12
12
  concurrent-ruby (1.1.8)
13
+ daemons (1.4.1)
13
14
  descendants_tracker (0.0.4)
14
15
  thread_safe (~> 0.3, >= 0.3.1)
15
16
  docile (1.3.5)
17
+ eventmachine (1.2.7)
16
18
  faraday (1.3.0)
17
19
  faraday-net_http (~> 1.0)
18
20
  multipart-post (>= 1.2, < 3)
@@ -26,13 +28,13 @@ GEM
26
28
  faraday (>= 0.8, < 2)
27
29
  hashie (~> 3.5, >= 3.5.2)
28
30
  oauth2 (~> 1.0)
29
- google-protobuf (3.15.6)
30
- googleapis-common-protos-types (1.0.6)
31
- google-protobuf (~> 3.14)
32
- grpc (1.36.0)
31
+ google-protobuf (3.19.3)
32
+ googleapis-common-protos-types (1.3.0)
33
33
  google-protobuf (~> 3.14)
34
+ grpc (1.43.1)
35
+ google-protobuf (~> 3.18)
34
36
  googleapis-common-protos-types (~> 1.0)
35
- grpc-tools (1.17.1)
37
+ grpc-tools (1.43.1)
36
38
  hashie (3.6.0)
37
39
  highline (2.0.3)
38
40
  i18n (1.8.9)
@@ -53,13 +55,14 @@ GEM
53
55
  jwt (2.2.2)
54
56
  kamelcase (0.0.2)
55
57
  semver2 (~> 3)
56
- mini_portile2 (2.4.0)
58
+ mini_portile2 (2.7.1)
57
59
  minitest (5.14.4)
58
60
  multi_json (1.15.0)
59
61
  multi_xml (0.6.0)
60
62
  multipart-post (2.1.1)
61
- nokogiri (1.10.10)
62
- mini_portile2 (~> 2.4.0)
63
+ nokogiri (1.13.1)
64
+ mini_portile2 (~> 2.7.0)
65
+ racc (~> 1.4)
63
66
  oauth2 (1.4.7)
64
67
  faraday (>= 0.8, < 2.0)
65
68
  jwt (>= 1.0, < 3.0)
@@ -68,6 +71,7 @@ GEM
68
71
  rack (>= 1.2, < 3)
69
72
  psych (3.3.1)
70
73
  public_suffix (4.0.6)
74
+ racc (1.6.0)
71
75
  rack (2.2.3)
72
76
  rake (13.0.3)
73
77
  rchardet (1.8.0)
@@ -85,6 +89,10 @@ GEM
85
89
  docile (~> 1.1)
86
90
  simplecov-html (~> 0.11)
87
91
  simplecov-html (0.12.3)
92
+ thin (1.8.1)
93
+ daemons (~> 1.0, >= 1.0.9)
94
+ eventmachine (~> 1.0, >= 1.0.4)
95
+ rack (>= 1, < 3)
88
96
  thread_safe (0.3.6)
89
97
  tzinfo (1.2.9)
90
98
  thread_safe (~> 0.1)
@@ -93,15 +101,18 @@ PLATFORMS
93
101
  ruby
94
102
 
95
103
  DEPENDENCIES
96
- bundler (~> 1.0)
104
+ bundler
97
105
  concurrent-ruby (~> 1.0, >= 1.0.5)
98
106
  faraday
107
+ google-protobuf
108
+ googleapis-common-protos-types
99
109
  grpc
100
- grpc-tools (~> 1.17.1)
110
+ grpc-tools
101
111
  juwelier (~> 2.4.9)
102
112
  rdoc (~> 3.12)
103
113
  shoulda
104
114
  simplecov
115
+ thin
105
116
 
106
117
  BUNDLED WITH
107
- 1.16.0
118
+ 2.3.5
data/README.md CHANGED
@@ -55,5 +55,5 @@ end
55
55
 
56
56
  ## Copyright
57
57
 
58
- Copyright (c) 2018 Jeff Dwyer. See LICENSE.txt for
58
+ Copyright (c) 2022 Jeff Dwyer. See LICENSE.txt for
59
59
  further details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.0
1
+ 0.7.0
data/compile_protos.sh CHANGED
@@ -1,2 +1,5 @@
1
1
  #!/usr/bin/env bash
2
2
  grpc_tools_ruby_protoc -I ../prefab-cloud/ --ruby_out=lib --grpc_out=lib prefab.proto
3
+ # on M1 you need to
4
+ # 1. run in rosetta
5
+ # 2. mv gems/2.6.0/gems/grpc-tools-1.43.1/bin/x86_64-macos x86-macos
data/lib/prefab/client.rb CHANGED
@@ -8,7 +8,7 @@ module Prefab
8
8
  }
9
9
 
10
10
 
11
- attr_reader :account_id, :shared_cache, :stats, :namespace, :interceptor, :api_key
11
+ attr_reader :project_id, :shared_cache, :stats, :namespace, :interceptor, :api_key, :environment
12
12
 
13
13
  def initialize(api_key: ENV['PREFAB_API_KEY'],
14
14
  logdev: nil,
@@ -19,13 +19,15 @@ module Prefab
19
19
  log_formatter: DEFAULT_LOG_FORMATTER
20
20
  )
21
21
  raise "No API key. Set PREFAB_API_KEY env var" if api_key.nil? || api_key.empty?
22
+ raise "PREFAB_API_KEY format invalid. Expecting 123-development-yourapikey" unless api_key.count("-") == 2
22
23
  @logdev = (logdev || $stdout)
23
24
  @log_formatter = log_formatter
24
25
  @local = local
25
26
  @stats = (stats || NoopStats.new)
26
27
  @shared_cache = (shared_cache || NoopCache.new)
27
28
  @api_key = api_key
28
- @account_id = api_key.split("|")[0].to_i
29
+ @project_id = api_key.split("-")[0].to_i
30
+ @environment = api_key.split("-")[1]
29
31
  @namespace = namespace
30
32
  @interceptor = Prefab::AuthInterceptor.new(api_key)
31
33
  @stubs = {}
@@ -37,7 +39,8 @@ module Prefab
37
39
 
38
40
  def channel
39
41
  credentials = ENV["PREFAB_CLOUD_HTTP"] == "true" ? :this_channel_is_insecure : creds
40
- url = ENV["PREFAB_API_URL"] || 'api.prefab.cloud:443'
42
+ url = ENV["PREFAB_API_URL"] || 'grpc.prefab.cloud:443'
43
+ log_internal Logger::DEBUG, "GRPC Channel #{url} #{credentials}"
41
44
  @_channel ||= GRPC::Core::Channel.new(url, nil, credentials)
42
45
  end
43
46
 
@@ -88,7 +91,7 @@ module Prefab
88
91
  end
89
92
 
90
93
  def cache_key(post_fix)
91
- "prefab:#{account_id}:#{post_fix}"
94
+ "prefab:#{project_id}:#{post_fix}"
92
95
  end
93
96
 
94
97
  def reset!
@@ -6,6 +6,7 @@ module Prefab
6
6
 
7
7
  def initialize(base_client, timeout)
8
8
  @base_client = base_client
9
+ @base_client.log_internal Logger::DEBUG, "Initialize ConfigClient"
9
10
  @timeout = timeout
10
11
  @initialization_lock = Concurrent::ReadWriteLock.new
11
12
 
@@ -14,11 +15,14 @@ module Prefab
14
15
  @config_loader = Prefab::ConfigLoader.new(@base_client)
15
16
  @config_resolver = Prefab::ConfigResolver.new(@base_client, @config_loader)
16
17
 
18
+ @base_client.log_internal Logger::DEBUG, "Initialize ConfigClient: AcquireWriteLock"
17
19
  @initialization_lock.acquire_write_lock
20
+ @base_client.log_internal Logger::DEBUG, "Initialize ConfigClient: AcquiredWriteLock"
18
21
 
19
22
  @cancellable_interceptor = Prefab::CancellableInterceptor.new(@base_client)
20
23
 
21
24
  @s3_cloud_front = ENV["PREFAB_S3CF_BUCKET"] || DEFAULT_S3CF_BUCKET
25
+
22
26
  load_checkpoint
23
27
  start_checkpointing_thread
24
28
  end
@@ -28,9 +32,9 @@ module Prefab
28
32
  start_api_connection_thread(@config_loader.highwater_mark)
29
33
  end
30
34
 
31
- def get(prop)
35
+ def get(key)
32
36
  @initialization_lock.with_read_lock do
33
- @config_resolver.get(prop)
37
+ @config_resolver.get(key)
34
38
  end
35
39
  end
36
40
 
@@ -86,9 +90,12 @@ module Prefab
86
90
  end
87
91
 
88
92
  def load_checkpoint_from_config
89
- config_req = Prefab::ConfigServicePointer.new(account_id: @base_client.account_id,
93
+ @base_client.log_internal Logger::DEBUG, "Load Checkpoint From Config"
94
+
95
+ config_req = Prefab::ConfigServicePointer.new(project_id: @base_client.project_id,
90
96
  start_at_id: @config_loader.highwater_mark)
91
97
  resp = stub.get_all_config(config_req)
98
+ @base_client.log_internal Logger::DEBUG, "Got Response #{resp}"
92
99
  load_deltas(resp, :api)
93
100
  resp.deltas.each do |delta|
94
101
  @config_loader.set(delta)
@@ -112,7 +119,6 @@ module Prefab
112
119
  end
113
120
  end
114
121
 
115
-
116
122
  def load_deltas(deltas, source)
117
123
  deltas.deltas.each do |delta|
118
124
  @config_loader.set(delta)
@@ -153,7 +159,7 @@ module Prefab
153
159
  # Setup a streaming connection to the API
154
160
  # Save new config values into the loader
155
161
  def start_api_connection_thread(start_at_id)
156
- config_req = Prefab::ConfigServicePointer.new(account_id: @base_client.account_id,
162
+ config_req = Prefab::ConfigServicePointer.new(project_id: @base_client.project_id,
157
163
  start_at_id: start_at_id)
158
164
  @base_client.log_internal Logger::DEBUG, "start api connection thread #{start_at_id}"
159
165
  @base_client.stats.increment("prefab.config.api.start")
@@ -0,0 +1,20 @@
1
+ module Prefab
2
+ module ConfigHelper
3
+ def value_of(config_value)
4
+ case config_value.type
5
+ when :string
6
+ config_value.string
7
+ when :int
8
+ config_value.int
9
+ when :double
10
+ config_value.double
11
+ when :bool
12
+ config_value.bool
13
+ when :feature_flag
14
+ config_value.feature_flag
15
+ when :segment
16
+ config_value.segment
17
+ end
18
+ end
19
+ end
20
+ end
@@ -14,7 +14,7 @@ module Prefab
14
14
  def calc_config
15
15
  rtn = @classpath_config.clone
16
16
  @api_config.each_key do |k|
17
- rtn[k] = @api_config[k].value
17
+ rtn[k] = @api_config[k]
18
18
  end
19
19
  rtn = rtn.merge(@local_overrides)
20
20
  rtn
@@ -26,7 +26,7 @@ module Prefab
26
26
  return
27
27
  end
28
28
 
29
- if delta.value.nil?
29
+ if delta.default.nil?
30
30
  @api_config.delete(delta.key)
31
31
  else
32
32
  @api_config[delta.key] = delta
@@ -63,7 +63,7 @@ module Prefab
63
63
  Dir.glob(glob).each do |file|
64
64
  yaml = load(file)
65
65
  yaml.each do |k, v|
66
- rtn[k] = Prefab::ConfigValue.new(value_from(v))
66
+ rtn[k] = Prefab::ConfigDelta.new(key: k, default: Prefab::ConfigValue.new(value_from(v)))
67
67
  end
68
68
  end
69
69
  rtn
@@ -1,11 +1,12 @@
1
1
  module Prefab
2
2
  class ConfigResolver
3
+ include Prefab::ConfigHelper
3
4
  NAMESPACE_DELIMITER = ".".freeze
4
- NAME_KEY_DELIMITER = ":".freeze
5
5
 
6
6
  def initialize(base_client, config_loader)
7
7
  @lock = Concurrent::ReadWriteLock.new
8
8
  @local_store = {}
9
+ @environment = base_client.environment
9
10
  @namespace = base_client.namespace
10
11
  @config_loader = config_loader
11
12
  make_local
@@ -16,7 +17,7 @@ module Prefab
16
17
  @lock.with_read_lock do
17
18
  @local_store.each do |k, v|
18
19
  value = v[:value]
19
- str << "|#{k}| in #{v[:namespace]} |#{value_of(value)}|#{value_of(value).class}\n"
20
+ str << "|#{k}| from #{v[:match]} |#{value_of(value)}|#{value_of(value).class}\n"
20
21
  end
21
22
  end
22
23
  str
@@ -39,21 +40,6 @@ module Prefab
39
40
 
40
41
  private
41
42
 
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
-
57
43
  # Should client a.b.c see key in namespace a.b? yes
58
44
  # Should client a.b.c see key in namespace a.b.c? yes
59
45
  # Should client a.b.c see key in namespace a.b.d? no
@@ -61,32 +47,50 @@ module Prefab
61
47
  #
62
48
  def starts_with_ns?(key_namespace, client_namespace)
63
49
  zipped = key_namespace.split(NAMESPACE_DELIMITER).zip(client_namespace.split(NAMESPACE_DELIMITER))
64
- zipped.map do |k, c|
65
- (k.nil? || k.empty?) || c == k
66
- end.all?
50
+ mapped = zipped.map do |k, c|
51
+ (k.nil? || k.empty?) || k == c
52
+ end
53
+ [mapped.all?, mapped.size]
67
54
  end
68
55
 
69
56
  def make_local
70
57
  store = {}
71
- @config_loader.calc_config.each do |prop, value|
72
- property = prop
73
- key_namespace = ""
58
+ @config_loader.calc_config.each do |key, delta|
59
+ # start with the top level default
60
+ to_store = { match: "default", value: delta.default }
61
+ if delta.envs.any?
62
+ env_values = delta.envs.select { |e| e.environment == @environment }
74
63
 
75
- split = prop.split(NAME_KEY_DELIMITER)
64
+ # do we have and env_values that match our env?
65
+ if env_values.any?
66
+ env_value = env_values.first
76
67
 
77
- if split.size > 1
78
- property = split[1..-1].join(NAME_KEY_DELIMITER)
79
- key_namespace = split[0]
80
- end
68
+ # override the top level default with env default
69
+ to_store = { match: "env_default", env: env_value.environment, value: env_value.default }
81
70
 
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 }
71
+ if env_value.namespace_values.any?
72
+ # check all namespace_values for match
73
+ env_value.namespace_values.each do |namespace_value|
74
+ (starts_with, count) = starts_with_ns?(namespace_value.namespace, @namespace)
75
+ if starts_with
76
+ # is this match the best match?
77
+ if count > (to_store[:match_depth_count] || 0)
78
+ to_store = { match: namespace_value.namespace, count: count, value: namespace_value.config_value }
79
+ end
80
+ end
81
+ end
82
+ end
88
83
  end
89
84
  end
85
+
86
+ # feature flags are a funny case
87
+ # we only define the variants in the default in order to be DRY
88
+ # but we want to access them in environments, clone them over
89
+ if to_store[:value].type == :feature_flag
90
+ to_store[:value].feature_flag.variants = delta.default.feature_flag.variants
91
+ end
92
+
93
+ store[key] = to_store
90
94
  end
91
95
  @lock.with_write_lock do
92
96
  @local_store = store
@@ -1,6 +1,8 @@
1
1
  module Prefab
2
2
  class FeatureFlagClient
3
+ include Prefab::ConfigHelper
3
4
  MAX_32_FLOAT = 4294967294.0
5
+ DISTRIBUTION_SPACE = 1000
4
6
 
5
7
  def initialize(base_client)
6
8
  @base_client = base_client
@@ -14,38 +16,124 @@ module Prefab
14
16
  feature_is_on_for?(feature_name, nil)
15
17
  end
16
18
 
17
- def feature_is_on_for?(feature_name, lookup_key, attributes: [])
19
+ def feature_is_on_for?(feature_name, lookup_key, attributes: {})
18
20
  @base_client.stats.increment("prefab.featureflag.on", tags: ["feature:#{feature_name}"])
19
21
 
22
+ return is_on?(get(feature_name, lookup_key, attributes))
23
+ end
24
+
25
+ def get(feature_name, lookup_key, attributes)
20
26
  feature_obj = @base_client.config_client.get(feature_name)
21
- return is_on?(feature_name, lookup_key, attributes, feature_obj)
27
+ evaluate(feature_name, lookup_key, attributes, feature_obj)
28
+ end
29
+
30
+ def evaluate(feature_name, lookup_key, attributes, feature_obj)
31
+ value_of(get_variant(feature_name, lookup_key, attributes, feature_obj))
22
32
  end
23
33
 
24
34
  private
25
35
 
26
- def is_on?(feature_name, lookup_key, attributes, feature_obj)
27
- if feature_obj.nil?
36
+ def is_on?(variant)
37
+ if variant.nil?
28
38
  return false
29
39
  end
40
+ variant.bool
41
+ end
42
+
43
+ def get_variant(feature_name, lookup_key, attributes, feature_obj)
44
+ if !feature_obj.active
45
+ return get_variant_obj(feature_obj, feature_obj.inactive_variant_idx)
46
+ end
47
+
48
+ variant_distribution = feature_obj.default
49
+
50
+ # if user_targets.match
51
+ feature_obj.user_targets.each do |target|
52
+ if (target.identifiers.include? lookup_key)
53
+ return get_variant_obj(feature_obj, target.variant_idx)
54
+ end
55
+ end
30
56
 
31
- attributes << lookup_key if lookup_key
32
- if (attributes & feature_obj.whitelisted).size > 0
33
- return true
57
+ # if rules.match
58
+ feature_obj.rules.each do |rule|
59
+ if criteria_match?(rule, lookup_key, attributes)
60
+ variant_distribution = rule.distribution
61
+ end
34
62
  end
35
63
 
36
- if lookup_key
37
- return get_user_pct(feature_name, lookup_key) < feature_obj.pct
64
+ if variant_distribution.type == :variant_idx
65
+ variant_idx = variant_distribution.variant_idx
66
+ else
67
+ percent_through_distribution = rand()
68
+ if lookup_key
69
+ percent_through_distribution = get_user_pct(feature_name, lookup_key)
70
+ end
71
+ distribution_bucket = DISTRIBUTION_SPACE * percent_through_distribution
72
+
73
+ variant_idx = get_variant_idx_from_weights(variant_distribution.variant_weights.weights, distribution_bucket, feature_name)
38
74
  end
39
75
 
40
- return feature_obj.pct > rand()
76
+ return get_variant_obj(feature_obj, variant_idx)
77
+ end
78
+
79
+ def get_variant_obj(feature_obj, idx)
80
+ return feature_obj.variants[idx] if feature_obj.variants.length >= idx
81
+ nil
82
+ end
83
+
84
+ def get_variant_idx_from_weights(variant_weights, bucket, feature_name)
85
+ sum = 0
86
+ variant_weights.each do |variant_weight|
87
+ if bucket < sum + variant_weight.weight
88
+ return variant_weight.variant_idx
89
+ else
90
+ sum += variant_weight.weight
91
+ end
92
+ end
93
+ # variants didn't add up to 100%
94
+ @base_client.log.info("Variants of #{feature_name} did not add to 100%")
95
+ return variant_weights.last.variant
41
96
  end
42
97
 
43
98
  def get_user_pct(feature, lookup_key)
44
- to_hash = "#{@base_client.account_id}#{feature}#{lookup_key}"
99
+ to_hash = "#{@base_client.project_id}#{feature}#{lookup_key}"
45
100
  int_value = Murmur3.murmur3_32(to_hash)
46
101
  int_value / MAX_32_FLOAT
47
102
  end
48
103
 
104
+ def criteria_match?(rule, lookup_key, attributes)
105
+ if rule.criteria.operator == :IN
106
+ return rule.criteria.values.include?(lookup_key)
107
+ elsif rule.criteria.operator == :NOT_IN
108
+ return !rule.criteria.values.include?(lookup_key)
109
+ elsif rule.criteria.operator == :IN_SEG
110
+ return segment_matches(rule.criteria.values, lookup_key, attributes).any?
111
+ elsif rule.criteria.operator == :NOT_IN_SEG
112
+ return segment_matches(rule.criteria.values, lookup_key, attributes).none?
113
+ end
114
+ @base_client.log.info("Unknown Operator")
115
+ false
116
+ end
117
+
118
+ # evaluate each segment key and return whether each one matches
119
+ # there should be an associated segment available as a standard config obj
120
+ def segment_matches(segment_keys, lookup_key, attributes)
121
+ segment_keys.map do |segment_key|
122
+ segment = @base_client.config_client.get(segment_key)
123
+ if segment.nil?
124
+ @base_client.log.info("Missing Segment")
125
+ false
126
+ else
127
+ segment_match?(segment, lookup_key, attributes)
128
+ end
129
+ end
130
+ end
131
+
132
+ def segment_match?(segment, lookup_key, attributes)
133
+ includes = segment.includes.include?(lookup_key)
134
+ excludes = segment.excludes.include?(lookup_key)
135
+ includes && !excludes
136
+ end
49
137
  end
50
138
  end
51
139
 
@@ -4,6 +4,7 @@ require 'faraday'
4
4
  require 'openssl'
5
5
  require 'prefab_pb'
6
6
  require 'prefab_services_pb'
7
+ require 'prefab/config_helper'
7
8
  require 'prefab/config_loader'
8
9
  require 'prefab/config_resolver'
9
10
  require 'prefab/client'