prefab-cloud-ruby 0.0.9 → 0.0.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bedbb49cbc2b12ed093d5740ddbcfa26398faefe
4
- data.tar.gz: fcd3d2e87c37927243f2ad475e0564d3a99eecc0
3
+ metadata.gz: e4ae024730e476fe81b2d5e1130bb13e0dfee4f6
4
+ data.tar.gz: aa819d0f9c64ffcd8826751d176133fce7a1a57e
5
5
  SHA512:
6
- metadata.gz: b4cf667a4d6495d12a19af12018f50d423b8dd5b32dcbe0b95fcbfbded7ddfa127ae1d804d3fc44db7df5bb4e2a6c3d8c4d25255b9e274b7e4a469b629b5ed08
7
- data.tar.gz: ad6a8ffb65d448f35a7276910cc36825491f4ee467bd7fd02e4486f1ee68fe1d27d28848a2c9c8be3189aa8339662a83b64960a2b7c5a255574634c656cbca00
6
+ metadata.gz: c10adec4b6866caee1045830c4ad45f2f36b20011e3ed8c3d4670331e62f0b65b3b57bd91c0c19dbe9ce4e164787cc5717d622fbe8cbe6cbbca21e03e54bfcf0
7
+ data.tar.gz: e75c1d4c816f403e974e7b1f105d233fa1578e667d4cfaa804daea81e9ba633478cdba059dab2705ddc45721b3ee734edf1dd2d52dbb1990cc16bd4f28ef203b
data/Gemfile CHANGED
@@ -1,10 +1,10 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gem 'grpc', '~> 1.7.3'
3
+ gem 'grpc', '~> 1.8.3'
4
4
  gem 'concurrent-ruby', '~> 1.0', '>= 1.0.5'
5
5
 
6
6
  group :development do
7
- gem 'grpc-tools', '~> 1.8'
7
+ gem 'grpc-tools', '~> 1.8.3'
8
8
  gem "shoulda", ">= 0"
9
9
  gem "rdoc", "~> 3.12"
10
10
  gem "bundler", "~> 1.0"
data/Gemfile.lock CHANGED
@@ -22,7 +22,7 @@ GEM
22
22
  faraday (~> 0.8)
23
23
  hashie (~> 3.5, >= 3.5.2)
24
24
  oauth2 (~> 1.0)
25
- google-protobuf (3.5.0)
25
+ google-protobuf (3.5.1)
26
26
  googleapis-common-protos-types (1.0.1)
27
27
  google-protobuf (~> 3.0)
28
28
  googleauth (0.6.2)
@@ -33,11 +33,11 @@ GEM
33
33
  multi_json (~> 1.11)
34
34
  os (~> 0.9)
35
35
  signet (~> 0.7)
36
- grpc (1.7.3)
36
+ grpc (1.8.3)
37
37
  google-protobuf (~> 3.1)
38
38
  googleapis-common-protos-types (~> 1.0.0)
39
39
  googleauth (>= 0.5.1, < 0.7)
40
- grpc-tools (1.8.0)
40
+ grpc-tools (1.8.3)
41
41
  hashie (3.5.6)
42
42
  highline (1.7.10)
43
43
  i18n (0.9.1)
@@ -105,8 +105,8 @@ PLATFORMS
105
105
  DEPENDENCIES
106
106
  bundler (~> 1.0)
107
107
  concurrent-ruby (~> 1.0, >= 1.0.5)
108
- grpc (~> 1.7.3)
109
- grpc-tools (~> 1.8)
108
+ grpc (~> 1.8.3)
109
+ grpc-tools (~> 1.8.3)
110
110
  juwelier (~> 2.1.0)
111
111
  rdoc (~> 3.12)
112
112
  shoulda
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.9
1
+ 0.0.10
data/lib/prefab/client.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  module Prefab
2
2
  class Client
3
- attr_reader :account_id, :shared_cache, :stats, :namespace, :logger, :creds, :channel, :interceptor
3
+ attr_reader :account_id, :shared_cache, :stats, :namespace, :logger, :creds, :interceptor
4
4
 
5
- def initialize(api_key:,
5
+ def initialize(api_key: ENV['PREFAB_API_KEY'],
6
6
  logger: nil,
7
7
  stats: nil, # receives increment("prefab.limitcheck", {:tags=>["policy_group:page_view", "pass:true"]})
8
8
  shared_cache: nil, # Something that quacks like Rails.cache ideally memcached
@@ -12,6 +12,7 @@ module Prefab
12
12
  @logger = (logger || Logger.new($stdout)).tap do |log|
13
13
  log.progname = "Prefab" if log.respond_to? :progname=
14
14
  end
15
+ @local = local
15
16
  @stats = (stats || NoopStats.new)
16
17
  @shared_cache = (shared_cache || NoopCache.new)
17
18
  @account_id = api_key.split("|")[0].to_i
@@ -20,10 +21,17 @@ module Prefab
20
21
  @interceptor = AuthInterceptor.new(api_key)
21
22
 
22
23
  @creds = GRPC::Core::ChannelCredentials.new(ssl_certs)
23
- @channel = GRPC::Core::Channel.new('api.prefab.cloud:8443', nil, @creds)
24
- if local
25
- @channel = GRPC::Core::Channel.new('localhost:8443', nil, :this_channel_is_insecure)
26
- end
24
+ end
25
+
26
+ def channel
27
+ @_channel ||= @local ?
28
+ GRPC::Core::Channel.new('localhost:8443', nil, :this_channel_is_insecure)
29
+ :
30
+ GRPC::Core::Channel.new('api.prefab.cloud:8443', nil, @creds)
31
+ end
32
+
33
+ def reset_channel!
34
+ @_channel = nil
27
35
  end
28
36
 
29
37
  def config_client(timeout: 5.0)
@@ -1,61 +1,161 @@
1
1
  module Prefab
2
2
  class ConfigClient
3
3
  RECONNECT_WAIT = 5
4
+ CHECKPOINT_FREQ_SEC = 10
5
+ SUSPENDERS_FREQ_SEC = 60
4
6
 
5
7
  def initialize(base_client, timeout)
6
8
  @base_client = base_client
7
9
  @timeout = timeout
8
- @config_resolver = EzConfig::ConfigResolver.new(base_client)
9
- boot_resolver
10
+ @config_loader = Prefab::ConfigLoader.new(base_client)
11
+ @config_resolver = Prefab::ConfigResolver.new(base_client, @config_loader)
12
+ start_at_id = load_checkpoint
13
+ start_api_connection_thread(start_at_id)
14
+ start_checkpointing_thread
15
+ start_suspenders_thread
10
16
  end
11
17
 
12
18
  def get(prop)
13
19
  @config_resolver.get(prop)
14
20
  end
15
21
 
16
- def set(config_delta)
17
- Retry.it method(:stub_with_timout), :upsert, config_delta, @timeout
18
- @config_resolver.set(config_delta)
22
+ def set(key, config_value, namespace = nil)
23
+ raise "key must not contain ':' set namespaces separately" if key.include? ":"
24
+ raise "namespace must not contain ':'" if namespace&.include?(":")
25
+ config_delta = Prefab::ConfigClient.value_to_delta(key, config_value, namespace)
26
+ Retry.it method(:stub_with_timeout), :upsert, config_delta, @timeout, method(:reset)
27
+ @config_loader.set(config_delta)
28
+ end
29
+
30
+ def reset
31
+ @base_client.reset_channel!
32
+ @_stub = nil
33
+ @_stub_with_timeout = nil
19
34
  end
20
35
 
21
36
  def to_s
22
37
  @config_resolver.to_s
23
38
  end
24
39
 
40
+ def self.value_to_delta(key, config_value, namespace = nil)
41
+ Prefab::ConfigDelta.new(key: [namespace, key].compact.join(":"),
42
+ value: config_value)
43
+ end
44
+
25
45
  private
26
46
 
27
47
  def stub
28
- Prefab::ConfigService::Stub.new(nil,
29
- nil,
30
- channel_override: @base_client.channel,
31
- interceptors: [@base_client.interceptor])
48
+ @_stub = Prefab::ConfigService::Stub.new(nil,
49
+ nil,
50
+ channel_override: @base_client.channel,
51
+ interceptors: [@base_client.interceptor])
32
52
  end
33
53
 
34
- def stub_with_timout
35
- Prefab::ConfigService::Stub.new(nil,
36
- nil,
37
- channel_override: @base_client.channel,
38
- timeout: @timeout,
39
- interceptors: [@base_client.interceptor])
54
+ def stub_with_timeout
55
+ @_stub_with_timeout = Prefab::ConfigService::Stub.new(nil,
56
+ nil,
57
+ channel_override: @base_client.channel,
58
+ timeout: @timeout,
59
+ interceptors: [@base_client.interceptor])
40
60
  end
41
61
 
42
- def boot_resolver
43
- config_req = Prefab::ConfigServicePointer.new(account_id: @base_client.account_id,
44
- start_at_id: 0)
62
+ def cache_key
63
+ "prefab:config:checkpoint"
64
+ end
65
+
66
+ # Bootstrap out of the cache
67
+ # returns the high-watermark of what was in the cache
68
+ def load_checkpoint
69
+ checkpoint = @base_client.shared_cache.read(cache_key)
70
+ start_at_id = 0
71
+
72
+ if checkpoint
73
+ deltas = Prefab::ConfigDeltas.decode(checkpoint)
74
+ deltas.deltas.each do |delta|
75
+ @base_client.logger.debug "checkpoint set #{delta.key} #{delta.value.int} #{delta.value.string} #{delta.id} "
76
+ @config_loader.set(delta)
77
+ start_at_id = [delta.id, start_at_id].max
78
+ end
79
+ @base_client.logger.info "Found checkpoint with highwater id #{start_at_id}"
80
+ @config_resolver.update
81
+ else
82
+ @base_client.logger.info "No checkpoint"
83
+ end
84
+
85
+ start_at_id
86
+ end
87
+
88
+ # A thread that saves current state to the cache, "checkpointing" it
89
+ def start_checkpointing_thread
90
+ Thread.new do
91
+ loop do
92
+ begin
93
+ started_at = Time.now
94
+ deltas = @config_resolver.export_api_deltas
95
+ @base_client.logger.debug "==CHECKPOINT==#{deltas.deltas.map {|d| d.id}.max}====="
96
+ @base_client.shared_cache.write(cache_key, Prefab::ConfigDeltas.encode(deltas))
97
+ delta = CHECKPOINT_FREQ_SEC - (Time.now - started_at)
98
+ if delta > 0
99
+ sleep(delta)
100
+ end
101
+ rescue StandardError => exn
102
+ @base_client.logger.info "Issue Checkpointing #{exn.message}"
103
+ end
104
+ end
105
+ end
106
+ end
45
107
 
108
+ # Setup a streaming connection to the API
109
+ # Save new config values into the loader
110
+ def start_api_connection_thread(start_at_id)
111
+ config_req = Prefab::ConfigServicePointer.new(account_id: @base_client.account_id,
112
+ start_at_id: start_at_id)
46
113
  Thread.new do
47
114
  while true do
48
115
  begin
49
116
  resp = stub.get_config(config_req)
50
117
  resp.each do |r|
51
118
  r.deltas.each do |delta|
52
- @config_resolver.set(delta, do_update: false)
119
+ @config_loader.set(delta)
53
120
  end
54
121
  @config_resolver.update
55
122
  end
56
123
  rescue => e
57
- sleep(RECONNECT_WAIT)
58
124
  @base_client.logger.info("config client encountered #{e.message} pausing #{RECONNECT_WAIT}")
125
+ reset
126
+ sleep(RECONNECT_WAIT)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ # Streaming connections don't guarantee all items have been seen
133
+ #
134
+ def start_suspenders_thread
135
+ start_at_suspenders = 0
136
+
137
+ Thread.new do
138
+ loop do
139
+ begin
140
+ started_at = Time.now
141
+ config_req = Prefab::ConfigServicePointer.new(account_id: @base_client.account_id,
142
+ start_at_id: start_at_suspenders)
143
+ resp = stub_with_timeout.get_config(config_req)
144
+ resp.each do |r|
145
+ r.deltas.each do |delta|
146
+ @config_loader.set(delta)
147
+ start_at_suspenders = [start_at_suspenders, delta.id].max
148
+ end
149
+ end
150
+ rescue GRPC::DeadlineExceeded
151
+ # Ignore. This is a streaming endpoint, but we only need a single response
152
+ rescue => e
153
+ @base_client.logger.info "Suspenders encountered an issue #{e.message}"
154
+ end
155
+
156
+ delta = SUSPENDERS_FREQ_SEC - (Time.now - started_at)
157
+ if delta > 0
158
+ sleep(delta)
59
159
  end
60
160
  end
61
161
  end
@@ -1,51 +1,77 @@
1
1
  require 'yaml'
2
- module EzConfig
2
+ module Prefab
3
3
  class ConfigLoader
4
- def initialize(logger)
5
- @logger = logger
6
- load_ez_config
7
- load_project_config
8
- load_local_overrides
4
+ def initialize(base_client)
5
+ @base_client = base_client
6
+ @classpath_config = load_classpath_config
7
+ @local_overrides = load_local_overrides
9
8
  @api_config = Concurrent::Map.new
10
- @immutable_config = @ez_config.merge(@project_config)
11
9
  end
12
10
 
13
11
  def calc_config
14
- rtn = @immutable_config.clone
12
+ rtn = @classpath_config.clone
15
13
  @api_config.each_key do |k|
16
- rtn[k] = @api_config[k]
14
+ rtn[k] = @api_config[k].value
17
15
  end
18
16
  rtn = rtn.merge(@local_overrides)
19
17
  rtn
20
18
  end
21
19
 
22
20
  def set(delta)
23
- @api_config[delta.key] = delta.value
21
+ @api_config[delta.key] = delta
22
+ end
23
+
24
+ def get_api_deltas
25
+ deltas = Prefab::ConfigDeltas.new
26
+ @api_config.each_value do |config_value|
27
+ deltas.deltas << config_value
28
+ end
29
+ deltas
24
30
  end
25
31
 
26
32
  private
27
33
 
28
- def load_ez_config
29
- @ez_config = load(".ezconfig.yaml")
34
+ def load_classpath_config
35
+ classpath_dir = ENV['PREFAB_CONFIG_CLASSPATH_DIR'] || ""
36
+ load_glob(File.join(classpath_dir, ".prefab*config.yaml"))
30
37
  end
31
38
 
32
- def load_project_config
33
- @project_config = load(".projectconfig.yaml")
39
+ def load_local_overrides
40
+ override_dir = ENV['PREFAB_CONFIG_OVERRIDE_DIR'] || Dir.home
41
+ load_glob(File.join(override_dir, ".prefab*config.yaml"))
34
42
  end
35
43
 
36
- def load_local_overrides
37
- @local_overrides = load(".ezconfig.overrides.yaml")
44
+ def load_glob(glob)
45
+ rtn = {}
46
+ Dir.glob(glob).each do |file|
47
+ yaml = load(file)
48
+ yaml.each do |k, v|
49
+ rtn[k] = Prefab::ConfigValue.new(value_from(v))
50
+ end
51
+ end
52
+ rtn
38
53
  end
39
54
 
40
55
  def load(filename)
41
56
  if File.exist? filename
42
-
43
57
  YAML.load_file(filename)
44
58
  else
45
- @logger.info "No file #{filename}"
59
+ @base_client.logger.info "No file #{filename}"
46
60
  {}
47
61
  end
48
62
  end
49
63
 
64
+ def value_from(raw)
65
+ case raw
66
+ when String
67
+ {string: raw}
68
+ when Integer
69
+ {int: raw}
70
+ when TrueClass, FalseClass
71
+ {bool: raw}
72
+ when Float
73
+ {double: raw}
74
+ end
75
+ end
50
76
  end
51
77
  end
@@ -1,12 +1,12 @@
1
- module EzConfig
1
+ module Prefab
2
2
  class ConfigResolver
3
3
 
4
- def initialize(client)
4
+ def initialize(base_client, config_loader)
5
5
  @lock = Concurrent::ReadWriteLock.new
6
6
  @local_store = {}
7
- @namespace = client.namespace
8
- @config_loader = EzConfig::ConfigLoader.new(client.logger)
9
- @logger = client.logger
7
+ @namespace = base_client.namespace
8
+ @config_loader = config_loader
9
+ @logger = base_client.logger
10
10
  make_local
11
11
  end
12
12
 
@@ -15,7 +15,7 @@ module EzConfig
15
15
  @lock.with_read_lock do
16
16
  @local_store.each do |k, v|
17
17
  value = v[:value]
18
- str << "|#{k}| |#{value_of(value)}|\n"
18
+ str << "|#{k}| |#{value_of(value)}|#{value_of(value).class}\n"
19
19
  end
20
20
  end
21
21
  str
@@ -28,15 +28,14 @@ module EzConfig
28
28
  config ? value_of(config[:value]) : nil
29
29
  end
30
30
 
31
- def set(delta, do_update: true)
32
- @config_loader.set(delta)
33
- update if do_update
34
- end
35
-
36
31
  def update
37
32
  make_local
38
33
  end
39
34
 
35
+ def export_api_deltas
36
+ @config_loader.get_api_deltas
37
+ end
38
+
40
39
  private
41
40
 
42
41
  def value_of(config_value)
@@ -66,7 +65,7 @@ module EzConfig
66
65
  namespace = split[0]
67
66
  end
68
67
 
69
- if (namespace == "") || namespace.start_with?(@namespace)
68
+ if (namespace == "") || @namespace.start_with?(namespace)
70
69
  existing = store[property]
71
70
  if existing.nil?
72
71
  store[property] = { namespace: namespace, value: value }
@@ -7,10 +7,7 @@ module Prefab
7
7
  end
8
8
 
9
9
  def upsert(feature_obj)
10
- delta = Prefab::ConfigDelta.new(account_id: @base_client.account_id,
11
- key: feature_config_name(feature_obj.feature),
12
- value: Prefab::ConfigValue.new(feature_flag: feature_obj))
13
- @base_client.config_client.set(delta)
10
+ @base_client.config_client.set(feature_config_name(feature_obj.feature), Prefab::ConfigValue.new(feature_flag: feature_obj))
14
11
  end
15
12
 
16
13
  def feature_is_on?(feature_name)
@@ -21,6 +18,12 @@ module Prefab
21
18
  @base_client.stats.increment("prefab.featureflag.on", tags: ["feature:#{feature_name}"])
22
19
 
23
20
  feature_obj = @base_client.config_client.get(feature_config_name(feature_name))
21
+ return is_on?(feature_name, lookup_key, attributes, feature_obj)
22
+ end
23
+
24
+ private
25
+
26
+ def is_on?(feature_name, lookup_key, attributes, feature_obj)
24
27
  if feature_obj.nil?
25
28
  return false
26
29
  end
@@ -37,11 +40,8 @@ module Prefab
37
40
  return feature_obj.pct > rand()
38
41
  end
39
42
 
40
- private
41
-
42
-
43
43
  def get_user_pct(feature, lookup_key)
44
- int_value = Murmur3.murmur3_32("#{@account_id}#{feature}#{lookup_key}")
44
+ int_value = Murmur3.murmur3_32("#{@base_client.account_id}#{feature}#{lookup_key}")
45
45
  int_value / MAX_32_FLOAT
46
46
  end
47
47
 
@@ -26,7 +26,7 @@ module Prefab
26
26
  allow_partial_response: allow_partial_response
27
27
  )
28
28
 
29
- result = Retry.it(method(:stub), :limit_check, req, @timeout)
29
+ result = Retry.it(method(:stub), :limit_check, req, @timeout, method(:reset))
30
30
 
31
31
  reset = result.limit_reset_at
32
32
  @base_client.shared_cache.write(expiry_cache_key, reset) unless reset < 1 # protobuf default int to 0
@@ -41,12 +41,17 @@ module Prefab
41
41
 
42
42
  private
43
43
 
44
+ def reset
45
+ @base_client.reset_channel!
46
+ @_stub = nil
47
+ end
48
+
44
49
  def stub
45
- Prefab::RateLimitService::Stub.new(nil,
46
- nil,
47
- channel_override: @base_client.channel,
48
- timeout: @timeout,
49
- interceptors: [@base_client.interceptor])
50
+ @_stub ||= Prefab::RateLimitService::Stub.new(nil,
51
+ nil,
52
+ channel_override: @base_client.channel,
53
+ timeout: @timeout,
54
+ interceptors: [@base_client.interceptor])
50
55
  end
51
56
 
52
57
  def handle_error(e, on_error, groups)
data/lib/prefab/retry.rb CHANGED
@@ -5,21 +5,23 @@ class Retry
5
5
 
6
6
  # GRPC Generally handles timeouts for us
7
7
  # but if the connection is broken we want to retry up until the timeout
8
- def self.it(stub_factory, rpc, req, timeout)
8
+ def self.it(stub_factory, rpc, req, timeout, reset)
9
9
  attempts = 0
10
10
  start_time = Time.now
11
11
 
12
12
  begin
13
13
  attempts += 1
14
-
15
14
  return stub_factory.call.send(rpc, req)
16
15
  rescue => exception
17
- raise exception if Time.now - start_time > timeout
16
+ if Time.now - start_time > timeout
17
+ raise exception
18
+ end
18
19
 
19
20
  sleep_seconds = [BASE_SLEEP_SEC * (2 ** (attempts - 1)), MAX_SLEEP_SEC].min
20
21
  sleep_seconds = sleep_seconds * (0.5 * (1 + rand()))
21
22
  sleep_seconds = [BASE_SLEEP_SEC, sleep_seconds].max
22
23
  sleep sleep_seconds
24
+ reset.call
23
25
  retry
24
26
  end
25
27
  end
data/lib/prefab_pb.rb CHANGED
@@ -9,7 +9,6 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
9
9
  optional :start_at_id, :int64, 2
10
10
  end
11
11
  add_message "prefab.ConfigDelta" do
12
- optional :account_id, :int64, 1
13
12
  optional :id, :int64, 2
14
13
  optional :key, :string, 3
15
14
  optional :value, :message, 4, "prefab.ConfigValue"
@@ -62,11 +61,9 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
62
61
  value :MAXIMUM, 2
63
62
  end
64
63
  add_message "prefab.FeatureFlag" do
65
- optional :account_id, :int64, 1
66
64
  optional :feature, :string, 2
67
65
  optional :pct, :double, 3
68
66
  repeated :whitelisted, :string, 4
69
- optional :prior_feature_name, :string, 5
70
67
  end
71
68
  add_message "prefab.LimitDefinition" do
72
69
  optional :group, :string, 1
@@ -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.0.9 ruby lib
5
+ # stub: prefab-cloud-ruby 0.0.10 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "prefab-cloud-ruby".freeze
9
- s.version = "0.0.9"
9
+ s.version = "0.0.10"
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 = "2017-12-22"
14
+ s.date = "2018-01-12"
15
15
  s.description = "RateLimits & Config as a service".freeze
16
16
  s.email = "jdwyer@prefab.cloud".freeze
17
17
  s.extra_rdoc_files = [
@@ -37,10 +37,14 @@ Gem::Specification.new do |s|
37
37
  "lib/prefab/noop_stats.rb",
38
38
  "lib/prefab/ratelimit_client.rb",
39
39
  "lib/prefab/retry.rb",
40
- "lib/prefab/store.rb",
41
40
  "lib/prefab_pb.rb",
42
41
  "lib/prefab_services_pb.rb",
43
- "prefab-cloud-ruby.gemspec"
42
+ "prefab-cloud-ruby.gemspec",
43
+ "test/.prefab.test.config.yaml",
44
+ "test/test_config_loader.rb",
45
+ "test/test_config_resolver.rb",
46
+ "test/test_feature_flag_client.rb",
47
+ "test/test_helper.rb"
44
48
  ]
45
49
  s.homepage = "http://github.com/prefab-cloud/prefab-cloud-ruby".freeze
46
50
  s.licenses = ["MIT".freeze]
@@ -51,18 +55,18 @@ Gem::Specification.new do |s|
51
55
  s.specification_version = 4
52
56
 
53
57
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
54
- s.add_runtime_dependency(%q<grpc>.freeze, ["~> 1.7.3"])
58
+ s.add_runtime_dependency(%q<grpc>.freeze, ["~> 1.8.3"])
55
59
  s.add_runtime_dependency(%q<concurrent-ruby>.freeze, [">= 1.0.5", "~> 1.0"])
56
- s.add_development_dependency(%q<grpc-tools>.freeze, ["~> 1.8"])
60
+ s.add_development_dependency(%q<grpc-tools>.freeze, ["~> 1.8.3"])
57
61
  s.add_development_dependency(%q<shoulda>.freeze, [">= 0"])
58
62
  s.add_development_dependency(%q<rdoc>.freeze, ["~> 3.12"])
59
63
  s.add_development_dependency(%q<bundler>.freeze, ["~> 1.0"])
60
64
  s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
61
65
  s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
62
66
  else
63
- s.add_dependency(%q<grpc>.freeze, ["~> 1.7.3"])
67
+ s.add_dependency(%q<grpc>.freeze, ["~> 1.8.3"])
64
68
  s.add_dependency(%q<concurrent-ruby>.freeze, [">= 1.0.5", "~> 1.0"])
65
- s.add_dependency(%q<grpc-tools>.freeze, ["~> 1.8"])
69
+ s.add_dependency(%q<grpc-tools>.freeze, ["~> 1.8.3"])
66
70
  s.add_dependency(%q<shoulda>.freeze, [">= 0"])
67
71
  s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
68
72
  s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
@@ -70,9 +74,9 @@ Gem::Specification.new do |s|
70
74
  s.add_dependency(%q<simplecov>.freeze, [">= 0"])
71
75
  end
72
76
  else
73
- s.add_dependency(%q<grpc>.freeze, ["~> 1.7.3"])
77
+ s.add_dependency(%q<grpc>.freeze, ["~> 1.8.3"])
74
78
  s.add_dependency(%q<concurrent-ruby>.freeze, [">= 1.0.5", "~> 1.0"])
75
- s.add_dependency(%q<grpc-tools>.freeze, ["~> 1.8"])
79
+ s.add_dependency(%q<grpc-tools>.freeze, ["~> 1.8.3"])
76
80
  s.add_dependency(%q<shoulda>.freeze, [">= 0"])
77
81
  s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
78
82
  s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
@@ -0,0 +1,5 @@
1
+ sample: OneTwoThree
2
+ sample_int: 123
3
+ sample_double: 12.12
4
+ sample_bool: true
5
+ sample_to_override: Foo
@@ -0,0 +1,43 @@
1
+ require 'test_helper'
2
+
3
+ class TestConfigLoader < Minitest::Test
4
+ def setup
5
+ ENV['PREFAB_CONFIG_OVERRIDE_DIR'] = "none"
6
+ ENV['PREFAB_CONFIG_CLASSPATH_DIR'] = "test"
7
+ @loader = Prefab::ConfigLoader.new(MockBaseClient.new)
8
+ end
9
+
10
+ def test_load
11
+ should_be :int, 123, "sample_int"
12
+ should_be :string, "OneTwoThree", "sample"
13
+ should_be :bool, true, "sample_bool"
14
+ should_be :double, 12.12, "sample_double"
15
+ end
16
+
17
+
18
+ def test_api_precedence
19
+ should_be :int, 123, "sample_int"
20
+
21
+ @loader.set(Prefab::ConfigDelta.new(key: "sample_int", value: Prefab::ConfigValue.new(int: 456)))
22
+ should_be :int, 456, "sample_int"
23
+
24
+ end
25
+
26
+ def test_api_deltas
27
+ val = Prefab::ConfigValue.new(int: 456)
28
+ delta = Prefab::ConfigDelta.new(key: "sample_int", value: val)
29
+ @loader.set(delta)
30
+
31
+ deltas = Prefab::ConfigDeltas.new
32
+ deltas.deltas << delta
33
+ assert_equal deltas, @loader.get_api_deltas
34
+ end
35
+
36
+ private
37
+
38
+ def should_be(type, value, key)
39
+ assert_equal type, @loader.calc_config[key].type
40
+ assert_equal value, @loader.calc_config[key].send(type)
41
+ end
42
+
43
+ end
@@ -0,0 +1,44 @@
1
+ require 'test_helper'
2
+
3
+ class TestConfigResolver < Minitest::Test
4
+
5
+ def test_resolution
6
+ @loader = MockConfigLoader.new
7
+
8
+ loaded_values = {
9
+ "projectA:key" => Prefab::ConfigValue.new(string: "valueA"),
10
+ "key" => Prefab::ConfigValue.new(string: "value_none"),
11
+ "projectB:key" => Prefab::ConfigValue.new(string: "valueB"),
12
+ "projectB.subprojectX:key" => Prefab::ConfigValue.new(string: "projectB.subprojectX"),
13
+ "projectB.subprojectY:key" => Prefab::ConfigValue.new(string: "projectB.subprojectY"),
14
+ "projectB:key2" => Prefab::ConfigValue.new(string: "valueB2")
15
+ }
16
+
17
+ @loader.stub :calc_config, loaded_values do
18
+ @resolver = Prefab::ConfigResolver.new(MockBaseClient.new, @loader)
19
+ assert_equal "value_none", @resolver.get("key")
20
+
21
+
22
+ @resolverA = resolver_for_namespace("projectA", @loader)
23
+ assert_equal "valueA", @resolverA.get("key")
24
+
25
+ @resolverB = resolver_for_namespace("projectB", @loader)
26
+ assert_equal "valueB", @resolverB.get("key")
27
+
28
+ @resolverBX = resolver_for_namespace("projectB.subprojectX", @loader)
29
+ assert_equal "projectB.subprojectX", @resolverBX.get("key")
30
+
31
+ @resolverUndefinedSubProject = resolver_for_namespace("projectB.subprojectX:subsubQ", @loader)
32
+ assert_equal "projectB.subprojectX", @resolverBX.get("key")
33
+
34
+ @resolverBX = resolver_for_namespace("projectC", @loader)
35
+ assert_equal "value_none", @resolverBX.get("key")
36
+ assert_nil @resolverBX.get("key_that_doesnt_exist")
37
+ end
38
+ end
39
+
40
+ def resolver_for_namespace(namespace, loader)
41
+ Prefab::ConfigResolver.new(MockBaseClient.new(namespace: namespace), loader)
42
+ end
43
+
44
+ end
@@ -0,0 +1,64 @@
1
+ require 'test_helper'
2
+
3
+ class TestFeatureFlagClient < Minitest::Test
4
+
5
+ def test_pct
6
+ client = Prefab::FeatureFlagClient.new(MockBaseClient.new)
7
+ Prefab::FeatureFlagClient.send(:public, :is_on?)
8
+
9
+ flag = Prefab::FeatureFlag.new(feature: "FlagName", pct: 0.5)
10
+
11
+ assert_equal false,
12
+ client.is_on?(flag.feature, "hashes high", [], flag)
13
+
14
+ assert_equal true,
15
+ client.is_on?(flag.feature, "hashes low", [], flag)
16
+ end
17
+
18
+
19
+ def test_off
20
+ client = Prefab::FeatureFlagClient.new(MockBaseClient.new)
21
+ Prefab::FeatureFlagClient.send(:public, :is_on?)
22
+
23
+ flag = Prefab::FeatureFlag.new(feature: "FlagName", pct: 0)
24
+
25
+ assert_equal false,
26
+ client.is_on?(flag.feature, "hashes high", [], flag)
27
+
28
+ assert_equal false,
29
+ client.is_on?(flag.feature, "hashes low", [], flag)
30
+ end
31
+
32
+
33
+ def test_on
34
+ client = Prefab::FeatureFlagClient.new(MockBaseClient.new)
35
+ Prefab::FeatureFlagClient.send(:public, :is_on?)
36
+
37
+ flag = Prefab::FeatureFlag.new(feature: "FlagName", pct: 1)
38
+
39
+ assert_equal true,
40
+ client.is_on?(flag.feature, "hashes high", [], flag)
41
+
42
+ assert_equal true,
43
+ client.is_on?(flag.feature, "hashes low", [], flag)
44
+ end
45
+
46
+ def test_whitelist
47
+ client = Prefab::FeatureFlagClient.new(MockBaseClient.new)
48
+ Prefab::FeatureFlagClient.send(:public, :is_on?)
49
+
50
+ flag = Prefab::FeatureFlag.new(feature: "FlagName", pct: 0, whitelisted: ["beta", "user:1", "user:3"])
51
+
52
+ assert_equal false,
53
+ client.is_on?(flag.feature, "anything", [], flag)
54
+ assert_equal true,
55
+ client.is_on?(flag.feature, "anything", ["beta"], flag)
56
+ assert_equal true,
57
+ client.is_on?(flag.feature, "anything", ["alpha", "beta"], flag)
58
+ assert_equal true,
59
+ client.is_on?(flag.feature, "anything", ["alpha", "user:1"], flag)
60
+ assert_equal false,
61
+ client.is_on?(flag.feature, "anything", ["alpha", "user:2"], flag)
62
+
63
+ end
64
+ end
@@ -0,0 +1,21 @@
1
+ require 'minitest/autorun'
2
+ require 'prefab-cloud-ruby'
3
+
4
+ class MockBaseClient
5
+ attr_reader :namespace, :logger
6
+
7
+ def initialize(namespace: "")
8
+ @namespace = namespace
9
+ @logger = Logger.new($stdout)
10
+ end
11
+
12
+ def account_id
13
+ 1
14
+ end
15
+ end
16
+
17
+ class MockConfigLoader
18
+ def calc_config
19
+
20
+ end
21
+ end
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.0.9
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Dwyer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-22 00:00:00.000000000 Z
11
+ date: 2018-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: grpc
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.7.3
19
+ version: 1.8.3
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 1.7.3
26
+ version: 1.8.3
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: concurrent-ruby
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -50,14 +50,14 @@ dependencies:
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '1.8'
53
+ version: 1.8.3
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '1.8'
60
+ version: 1.8.3
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: shoulda
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -154,10 +154,14 @@ files:
154
154
  - lib/prefab/noop_stats.rb
155
155
  - lib/prefab/ratelimit_client.rb
156
156
  - lib/prefab/retry.rb
157
- - lib/prefab/store.rb
158
157
  - lib/prefab_pb.rb
159
158
  - lib/prefab_services_pb.rb
160
159
  - prefab-cloud-ruby.gemspec
160
+ - test/.prefab.test.config.yaml
161
+ - test/test_config_loader.rb
162
+ - test/test_config_resolver.rb
163
+ - test/test_feature_flag_client.rb
164
+ - test/test_helper.rb
161
165
  homepage: http://github.com/prefab-cloud/prefab-cloud-ruby
162
166
  licenses:
163
167
  - MIT
data/lib/prefab/store.rb DELETED
@@ -1,29 +0,0 @@
1
- require "concurrent/atomics"
2
-
3
- module EzConfig
4
- class Store
5
- def initialize
6
- @store = Concurrent::Map
7
- @lock = Concurrent::ReadWriteLock.new
8
- end
9
-
10
- def get(key)
11
- @lock.with_read_lock do
12
- @store[key]
13
- end
14
- end
15
-
16
- def all
17
- @lock.with_read_lock do
18
- @store.all
19
- end
20
- end
21
-
22
- def init(fs)
23
- @lock.with_write_lock do
24
- @store.replace(fs)
25
- @initialized.make_true
26
- end
27
- end
28
- end
29
- end