prefab-cloud-ruby 0.5.0 → 0.7.0

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
- SHA1:
3
- metadata.gz: 21650fb9bfd13dc3d1b8e0e2a6c826e97ff8cce9
4
- data.tar.gz: 502213b0d3394afb96427fb54a52113d7672981d
2
+ SHA256:
3
+ metadata.gz: a36d24a94355b3b7a2b3b889734e1f6f6ccafc7dcebfb11ff11d77ba92ea7828
4
+ data.tar.gz: 326b3166d4fc2589ebb2550114b2cc198617620daa70ecfb6b9fe394c9dd79a5
5
5
  SHA512:
6
- metadata.gz: 7364cfd35d713e3144bb6fbcabe89807c92dad4e1a06523d717e23ee2dfa78a227f64b2e9e9668306ed23fca1c250331fc263a52021881a9c3218c5d62585f9b
7
- data.tar.gz: 1546ee4f08a823292765fb322ad922d2b87892276d034fdcaec1ba4cddb29c4100d4f8863c5bbef10960cda0b18eae4e4933e5398170b2394b4f9f2c16c7195c
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', '~> 1.17.1'
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
@@ -1,37 +1,43 @@
1
1
  GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
- activesupport (5.2.0)
4
+ activesupport (5.2.4.5)
5
5
  concurrent-ruby (~> 1.0, >= 1.0.2)
6
6
  i18n (>= 0.7, < 2)
7
7
  minitest (~> 5.1)
8
8
  tzinfo (~> 1.1)
9
- addressable (2.6.0)
10
- public_suffix (>= 2.0.2, < 4.0)
11
- builder (3.2.3)
12
- concurrent-ruby (1.0.5)
9
+ addressable (2.7.0)
10
+ public_suffix (>= 2.0.2, < 5.0)
11
+ builder (3.2.4)
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
- docile (1.3.0)
16
- faraday (0.15.4)
16
+ docile (1.3.5)
17
+ eventmachine (1.2.7)
18
+ faraday (1.3.0)
19
+ faraday-net_http (~> 1.0)
17
20
  multipart-post (>= 1.2, < 3)
18
- git (1.5.0)
19
- github_api (0.18.2)
21
+ ruby2_keywords
22
+ faraday-net_http (1.0.1)
23
+ git (1.8.1)
24
+ rchardet (~> 1.8)
25
+ github_api (0.19.0)
20
26
  addressable (~> 2.4)
21
27
  descendants_tracker (~> 0.0.4)
22
- faraday (~> 0.8)
28
+ faraday (>= 0.8, < 2)
23
29
  hashie (~> 3.5, >= 3.5.2)
24
30
  oauth2 (~> 1.0)
25
- google-protobuf (3.6.1)
26
- googleapis-common-protos-types (1.0.2)
27
- google-protobuf (~> 3.0)
28
- grpc (1.17.1)
29
- google-protobuf (~> 3.1)
30
- googleapis-common-protos-types (~> 1.0.0)
31
- grpc-tools (1.17.1)
31
+ google-protobuf (3.19.3)
32
+ googleapis-common-protos-types (1.3.0)
33
+ google-protobuf (~> 3.14)
34
+ grpc (1.43.1)
35
+ google-protobuf (~> 3.18)
36
+ googleapis-common-protos-types (~> 1.0)
37
+ grpc-tools (1.43.1)
32
38
  hashie (3.6.0)
33
- highline (2.0.1)
34
- i18n (1.0.1)
39
+ highline (2.0.3)
40
+ i18n (1.8.9)
35
41
  concurrent-ruby (~> 1.0)
36
42
  json (1.8.6)
37
43
  juwelier (2.4.9)
@@ -46,57 +52,67 @@ GEM
46
52
  rake
47
53
  rdoc
48
54
  semver2
49
- jwt (2.1.0)
55
+ jwt (2.2.2)
50
56
  kamelcase (0.0.2)
51
57
  semver2 (~> 3)
52
- mini_portile2 (2.4.0)
53
- minitest (5.11.3)
54
- multi_json (1.13.1)
58
+ mini_portile2 (2.7.1)
59
+ minitest (5.14.4)
60
+ multi_json (1.15.0)
55
61
  multi_xml (0.6.0)
56
- multipart-post (2.0.0)
57
- nokogiri (1.10.1)
58
- mini_portile2 (~> 2.4.0)
59
- oauth2 (1.4.1)
60
- faraday (>= 0.8, < 0.16.0)
62
+ multipart-post (2.1.1)
63
+ nokogiri (1.13.1)
64
+ mini_portile2 (~> 2.7.0)
65
+ racc (~> 1.4)
66
+ oauth2 (1.4.7)
67
+ faraday (>= 0.8, < 2.0)
61
68
  jwt (>= 1.0, < 3.0)
62
69
  multi_json (~> 1.3)
63
70
  multi_xml (~> 0.5)
64
71
  rack (>= 1.2, < 3)
65
- psych (3.1.0)
66
- public_suffix (3.0.3)
67
- rack (2.0.6)
68
- rake (12.3.2)
72
+ psych (3.3.1)
73
+ public_suffix (4.0.6)
74
+ racc (1.6.0)
75
+ rack (2.2.3)
76
+ rake (13.0.3)
77
+ rchardet (1.8.0)
69
78
  rdoc (3.12.2)
70
79
  json (~> 1.4)
80
+ ruby2_keywords (0.0.4)
71
81
  semver2 (3.4.2)
72
- shoulda (3.5.0)
73
- shoulda-context (~> 1.0, >= 1.0.1)
74
- shoulda-matchers (>= 1.4.1, < 3.0)
75
- shoulda-context (1.2.2)
76
- shoulda-matchers (2.8.0)
77
- activesupport (>= 3.0.0)
78
- simplecov (0.16.1)
82
+ shoulda (4.0.0)
83
+ shoulda-context (~> 2.0)
84
+ shoulda-matchers (~> 4.0)
85
+ shoulda-context (2.0.0)
86
+ shoulda-matchers (4.5.1)
87
+ activesupport (>= 4.2.0)
88
+ simplecov (0.18.5)
79
89
  docile (~> 1.1)
80
- json (>= 1.8, < 3)
81
- simplecov-html (~> 0.10.0)
82
- simplecov-html (0.10.2)
90
+ simplecov-html (~> 0.11)
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)
83
96
  thread_safe (0.3.6)
84
- tzinfo (1.2.5)
97
+ tzinfo (1.2.9)
85
98
  thread_safe (~> 0.1)
86
99
 
87
100
  PLATFORMS
88
101
  ruby
89
102
 
90
103
  DEPENDENCIES
91
- bundler (~> 1.0)
104
+ bundler
92
105
  concurrent-ruby (~> 1.0, >= 1.0.5)
93
106
  faraday
94
- grpc (~> 1.17.1)
95
- grpc-tools (~> 1.17.1)
107
+ google-protobuf
108
+ googleapis-common-protos-types
109
+ grpc
110
+ grpc-tools
96
111
  juwelier (~> 2.4.9)
97
112
  rdoc (~> 3.12)
98
113
  shoulda
99
114
  simplecov
115
+ thin
100
116
 
101
117
  BUNDLED WITH
102
- 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.5.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
@@ -1,8 +1,26 @@
1
1
  module Prefab
2
2
  class CancellableInterceptor < GRPC::ClientInterceptor
3
+ WAIT_SEC = 3
4
+
5
+ def initialize(base_client)
6
+ @base_client = base_client
7
+ end
3
8
 
4
9
  def cancel
5
10
  @call.instance_variable_get("@wrapped").instance_variable_get("@call").cancel
11
+ i = 0
12
+ while (i < WAIT_SEC) do
13
+ if @call.instance_variable_get("@wrapped").cancelled?
14
+ @base_client.log_internal Logger::DEBUG, "Cancelled streaming."
15
+ return
16
+ else
17
+ @base_client.log_internal Logger::DEBUG, "Unable to cancel streaming. Trying again"
18
+ @call.instance_variable_get("@wrapped").instance_variable_get("@call").cancel
19
+ i += 1
20
+ sleep(1)
21
+ end
22
+ end
23
+ @base_client.log_internal Logger::INFO, "Unable to cancel streaming."
6
24
  end
7
25
 
8
26
  def request_response(request:, call:, method:, metadata:, &block)
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,26 +15,26 @@ 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
- @cancellable_interceptor = Prefab::CancellableInterceptor.new
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
25
29
 
26
30
  def start_streaming
27
- at_exit do
28
- @cancellable_interceptor.cancel
29
- end
30
-
31
+ @streaming = true
31
32
  start_api_connection_thread(@config_loader.highwater_mark)
32
33
  end
33
34
 
34
- def get(prop)
35
+ def get(key)
35
36
  @initialization_lock.with_read_lock do
36
- @config_resolver.get(prop)
37
+ @config_resolver.get(key)
37
38
  end
38
39
  end
39
40
 
@@ -89,9 +90,12 @@ module Prefab
89
90
  end
90
91
 
91
92
  def load_checkpoint_from_config
92
- 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,
93
96
  start_at_id: @config_loader.highwater_mark)
94
97
  resp = stub.get_all_config(config_req)
98
+ @base_client.log_internal Logger::DEBUG, "Got Response #{resp}"
95
99
  load_deltas(resp, :api)
96
100
  resp.deltas.each do |delta|
97
101
  @config_loader.set(delta)
@@ -115,7 +119,6 @@ module Prefab
115
119
  end
116
120
  end
117
121
 
118
-
119
122
  def load_deltas(deltas, source)
120
123
  deltas.deltas.each do |delta|
121
124
  @config_loader.set(delta)
@@ -156,12 +159,18 @@ module Prefab
156
159
  # Setup a streaming connection to the API
157
160
  # Save new config values into the loader
158
161
  def start_api_connection_thread(start_at_id)
159
- config_req = Prefab::ConfigServicePointer.new(account_id: @base_client.account_id,
162
+ config_req = Prefab::ConfigServicePointer.new(project_id: @base_client.project_id,
160
163
  start_at_id: start_at_id)
161
164
  @base_client.log_internal Logger::DEBUG, "start api connection thread #{start_at_id}"
162
165
  @base_client.stats.increment("prefab.config.api.start")
166
+
163
167
  @api_connection_thread = Thread.new do
164
- while true do
168
+ at_exit do
169
+ @streaming = false
170
+ @cancellable_interceptor.cancel
171
+ end
172
+
173
+ while @streaming do
165
174
  begin
166
175
  resp = stub.get_config(config_req)
167
176
  resp.each do |r|
@@ -172,13 +181,16 @@ module Prefab
172
181
  finish_init!(:streaming)
173
182
  end
174
183
  rescue => e
175
- level = e.code == 1 ? Logger::DEBUG : Logger::INFO
176
- @base_client.log_internal level, ("config client encountered #{e.message} pausing #{RECONNECT_WAIT}")
177
- reset
178
- sleep(RECONNECT_WAIT)
184
+ if @streaming
185
+ level = e.code == 1 ? Logger::DEBUG : Logger::INFO
186
+ @base_client.log_internal level, ("config client encountered #{e.message} pausing #{RECONNECT_WAIT}")
187
+ reset
188
+ sleep(RECONNECT_WAIT)
189
+ end
179
190
  end
180
191
  end
181
192
  end
193
+
182
194
  end
183
195
  end
184
196
  end
@@ -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