prefab-cloud-ruby 0.0.9 → 0.0.10

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
  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