prefab-cloud-ruby 0.5.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: 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