prefab-cloud-ruby 0.23.2 → 0.23.4
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 +4 -4
- data/Gemfile +0 -2
- data/Gemfile.lock +0 -6
- data/README.md +1 -3
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/prefab/client.rb +5 -79
- data/lib/prefab/config_client.rb +7 -40
- data/lib/prefab/exponential_backoff.rb +16 -0
- data/lib/prefab/http_connection.rb +37 -0
- data/lib/prefab/log_path_collector.rb +8 -4
- data/lib/prefab/options.rb +1 -5
- data/lib/prefab-cloud-ruby.rb +2 -4
- data/prefab-cloud-ruby.gemspec +7 -11
- data/test/integration_test.rb +1 -2
- data/test/test_client.rb +0 -7
- data/test/test_exponential_backoff.rb +18 -0
- data/test/test_log_path_collector.rb +22 -17
- metadata +6 -34
- data/lib/prefab/auth_interceptor.rb +0 -34
- data/lib/prefab/cancellable_interceptor.rb +0 -49
- data/lib/prefab/ratelimit_client.rb +0 -78
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74e19eed1d40cad8e3ea9b99a8755c90864a97b4a00e08d2df6a9fe5226f5ad0
|
4
|
+
data.tar.gz: 4ad94565a39139d01795007077128d8f997c05e6a8b785e019ca8b66308df5c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a10679fe049175ebaf01f12194f91bea1a98ce226bf0e0f3e87aea3ab299c888fb7cebfe67b942e77dc46367c9b05cc5acaf995870e1d6ee1f2b8aa744f2fa3
|
7
|
+
data.tar.gz: c6e506da285ae5f7913f81891e7f32a1b5365f1071bc90323f317aa2523fddb0bd41c2d8a53decf2afec000e234d535713e198f3ac8b4c98259c7904f03ce7ad
|
data/Gemfile
CHANGED
@@ -4,14 +4,12 @@ gem 'concurrent-ruby', '~> 1.0', '>= 1.0.5'
|
|
4
4
|
gem 'faraday'
|
5
5
|
gem 'googleapis-common-protos-types', platforms: :ruby
|
6
6
|
gem 'google-protobuf', platforms: :ruby
|
7
|
-
gem 'grpc', platforms: :ruby
|
8
7
|
gem 'ld-eventsource'
|
9
8
|
gem 'uuid'
|
10
9
|
|
11
10
|
group :development do
|
12
11
|
gem 'benchmark-ips'
|
13
12
|
gem 'bundler'
|
14
|
-
gem 'grpc-tools', platforms: :ruby
|
15
13
|
gem 'juwelier', '~> 2.4.9'
|
16
14
|
gem 'rdoc'
|
17
15
|
gem 'simplecov', '>= 0'
|
data/Gemfile.lock
CHANGED
@@ -33,10 +33,6 @@ GEM
|
|
33
33
|
google-protobuf (3.22.2)
|
34
34
|
googleapis-common-protos-types (1.5.0)
|
35
35
|
google-protobuf (~> 3.14)
|
36
|
-
grpc (1.53.0)
|
37
|
-
google-protobuf (~> 3.21)
|
38
|
-
googleapis-common-protos-types (~> 1.0)
|
39
|
-
grpc-tools (1.43.1)
|
40
36
|
hashie (3.6.0)
|
41
37
|
highline (2.0.3)
|
42
38
|
http (5.0.1)
|
@@ -124,8 +120,6 @@ DEPENDENCIES
|
|
124
120
|
faraday
|
125
121
|
google-protobuf
|
126
122
|
googleapis-common-protos-types
|
127
|
-
grpc
|
128
|
-
grpc-tools
|
129
123
|
juwelier (~> 2.4.9)
|
130
124
|
ld-eventsource
|
131
125
|
minitest
|
data/README.md
CHANGED
@@ -28,9 +28,7 @@ See full documentation https://docs.prefab.cloud/docs/ruby-sdk/ruby
|
|
28
28
|
|
29
29
|
## Important note about Forking and realtime updates
|
30
30
|
|
31
|
-
Many ruby web servers fork.
|
32
|
-
|
33
|
-
If you're using Puma or Unicorn, you can do the following.
|
31
|
+
Many ruby web servers fork. When the process is forked, the current realtime update stream is disconnected. If you're using Puma or Unicorn, do the following.
|
34
32
|
|
35
33
|
```ruby
|
36
34
|
#config/initializers/prefab.rb
|
data/Rakefile
CHANGED
@@ -17,7 +17,7 @@ Juwelier::Tasks.new do |gem|
|
|
17
17
|
gem.homepage = 'http://github.com/prefab-cloud/prefab-cloud-ruby'
|
18
18
|
gem.license = 'MIT'
|
19
19
|
gem.summary = %(Prefab Ruby Infrastructure)
|
20
|
-
gem.description = %(
|
20
|
+
gem.description = %(Feature Flags, Live Config, and Dynamic Log Levels as a service)
|
21
21
|
gem.email = 'jdwyer@prefab.cloud'
|
22
22
|
gem.authors = ['Jeff Dwyer']
|
23
23
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.23.
|
1
|
+
0.23.4
|
data/lib/prefab/client.rb
CHANGED
@@ -31,14 +31,8 @@ module Prefab
|
|
31
31
|
@api_key = @options.api_key
|
32
32
|
raise Prefab::Errors::InvalidApiKeyError, @api_key if @api_key.nil? || @api_key.empty? || api_key.count('-') < 1
|
33
33
|
|
34
|
-
@interceptor = Prefab::AuthInterceptor.new(@api_key)
|
35
34
|
@prefab_api_url = @options.prefab_api_url
|
36
|
-
|
37
|
-
log_internal ::Logger::INFO,
|
38
|
-
"Prefab Connecting to: #{@prefab_api_url} and #{@prefab_grpc_url} Secure: #{http_secure?}"
|
39
|
-
at_exit do
|
40
|
-
channel.destroy
|
41
|
-
end
|
35
|
+
log_internal ::Logger::INFO, "Prefab Connecting to: #{@prefab_api_url}"
|
42
36
|
end
|
43
37
|
# start config client
|
44
38
|
config_client
|
@@ -54,19 +48,10 @@ module Prefab
|
|
54
48
|
Thread.current[:prefab_log_properties] = {}
|
55
49
|
end
|
56
50
|
|
57
|
-
def channel
|
58
|
-
credentials = http_secure? ? creds : :this_channel_is_insecure
|
59
|
-
@_channel ||= GRPC::Core::Channel.new(@prefab_grpc_url, nil, credentials)
|
60
|
-
end
|
61
|
-
|
62
51
|
def config_client(timeout: 5.0)
|
63
52
|
@config_client ||= Prefab::ConfigClient.new(self, timeout)
|
64
53
|
end
|
65
54
|
|
66
|
-
def ratelimit_client(timeout: 5.0)
|
67
|
-
@ratelimit_client ||= Prefab::RateLimitClient.new(self, timeout)
|
68
|
-
end
|
69
|
-
|
70
55
|
def feature_flag_client
|
71
56
|
@feature_flag_client ||= Prefab::FeatureFlagClient.new(self)
|
72
57
|
end
|
@@ -97,39 +82,6 @@ module Prefab
|
|
97
82
|
log.log_internal msg, path, nil, level
|
98
83
|
end
|
99
84
|
|
100
|
-
def request(service, method, req_options: {}, params: {})
|
101
|
-
# Future-proofing since we previously bumped into a conflict with a service with a `send` method
|
102
|
-
raise ArgumentError, 'Cannot call public_send on an grpc service in Ruby' if method.to_s == 'public_send'
|
103
|
-
|
104
|
-
opts = { timeout: 10 }.merge(req_options)
|
105
|
-
|
106
|
-
attempts = 0
|
107
|
-
start_time = Time.now
|
108
|
-
|
109
|
-
begin
|
110
|
-
attempts += 1
|
111
|
-
|
112
|
-
stub_for(service, opts[:timeout]).public_send(method, *params)
|
113
|
-
rescue StandardError => e
|
114
|
-
log_internal ::Logger::WARN, e
|
115
|
-
|
116
|
-
raise e if Time.now - start_time > opts[:timeout]
|
117
|
-
|
118
|
-
sleep_seconds = [BASE_SLEEP_SEC * (2**(attempts - 1)), MAX_SLEEP_SEC].min
|
119
|
-
sleep_seconds *= (0.5 * (1 + rand))
|
120
|
-
sleep_seconds = [BASE_SLEEP_SEC, sleep_seconds].max
|
121
|
-
log_internal ::Logger::INFO, "Sleep #{sleep_seconds} and Reset #{service} #{method}"
|
122
|
-
sleep sleep_seconds
|
123
|
-
reset!
|
124
|
-
retry
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def reset!
|
129
|
-
@stubs.clear
|
130
|
-
@_channel = nil
|
131
|
-
end
|
132
|
-
|
133
85
|
def enabled?(feature_name, lookup_key = nil, attributes = {})
|
134
86
|
feature_flag_client.feature_is_on_for?(feature_name, lookup_key, attributes: attributes)
|
135
87
|
end
|
@@ -142,6 +94,10 @@ module Prefab
|
|
142
94
|
end
|
143
95
|
end
|
144
96
|
|
97
|
+
def post(path, body)
|
98
|
+
Prefab::HttpConnection.new(@options.prefab_api_url, @api_key).post(path, body)
|
99
|
+
end
|
100
|
+
|
145
101
|
private
|
146
102
|
|
147
103
|
def is_ff?(key)
|
@@ -149,35 +105,5 @@ module Prefab
|
|
149
105
|
|
150
106
|
raw && raw.allowable_values.any?
|
151
107
|
end
|
152
|
-
|
153
|
-
def http_secure?
|
154
|
-
ENV['PREFAB_CLOUD_HTTP'] != 'true'
|
155
|
-
end
|
156
|
-
|
157
|
-
def stub_for(service, timeout)
|
158
|
-
@stubs["#{service}_#{timeout}"] ||= service::Stub.new(nil,
|
159
|
-
nil,
|
160
|
-
timeout: timeout,
|
161
|
-
channel_override: channel,
|
162
|
-
interceptors: [@interceptor])
|
163
|
-
end
|
164
|
-
|
165
|
-
def creds
|
166
|
-
GRPC::Core::ChannelCredentials.new(ssl_certs)
|
167
|
-
end
|
168
|
-
|
169
|
-
def ssl_certs
|
170
|
-
ssl_certs = ''
|
171
|
-
Dir["#{OpenSSL::X509::DEFAULT_CERT_DIR}/*.pem"].each do |cert|
|
172
|
-
ssl_certs += File.open(cert).read
|
173
|
-
end
|
174
|
-
if OpenSSL::X509::DEFAULT_CERT_FILE && File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE)
|
175
|
-
ssl_certs += File.open(OpenSSL::X509::DEFAULT_CERT_FILE).read
|
176
|
-
end
|
177
|
-
ssl_certs
|
178
|
-
rescue StandardError => e
|
179
|
-
log.warn("Issue loading SSL certs #{e.message}")
|
180
|
-
ssl_certs
|
181
|
-
end
|
182
108
|
end
|
183
109
|
end
|
data/lib/prefab/config_client.rb
CHANGED
@@ -26,8 +26,6 @@ module Prefab
|
|
26
26
|
@base_client.log_internal ::Logger::DEBUG, 'Initialize ConfigClient: AcquiredWriteLock'
|
27
27
|
@initialized_future = Concurrent::Future.execute { @initialization_lock.acquire_read_lock }
|
28
28
|
|
29
|
-
@cancellable_interceptor = Prefab::CancellableInterceptor.new(@base_client)
|
30
|
-
|
31
29
|
if @options.local_only?
|
32
30
|
finish_init!(:local_only)
|
33
31
|
else
|
@@ -58,11 +56,6 @@ module Prefab
|
|
58
56
|
@config_resolver.update
|
59
57
|
end
|
60
58
|
|
61
|
-
def reset
|
62
|
-
@base_client.reset!
|
63
|
-
@_stub = nil
|
64
|
-
end
|
65
|
-
|
66
59
|
def to_s
|
67
60
|
@config_resolver.to_s
|
68
61
|
end
|
@@ -108,54 +101,28 @@ module Prefab
|
|
108
101
|
@config_resolver.get(key, lookup_key, properties)
|
109
102
|
end
|
110
103
|
|
111
|
-
def stub
|
112
|
-
@_stub = Prefab::ConfigService::Stub.new(nil,
|
113
|
-
nil,
|
114
|
-
channel_override: @base_client.channel,
|
115
|
-
interceptors: [@base_client.interceptor, @cancellable_interceptor])
|
116
|
-
end
|
117
|
-
|
118
104
|
def load_checkpoint
|
119
105
|
success = load_checkpoint_api_cdn
|
120
106
|
|
121
107
|
return if success
|
122
108
|
|
123
|
-
|
124
|
-
|
125
|
-
success = load_checkpoint_from_grpc_api
|
109
|
+
success = load_checkpoint_api
|
126
110
|
|
127
111
|
return if success
|
128
112
|
|
129
113
|
@base_client.log_internal ::Logger::WARN, 'No success loading checkpoints'
|
130
114
|
end
|
131
115
|
|
132
|
-
def load_checkpoint_from_grpc_api
|
133
|
-
config_req = Prefab::ConfigServicePointer.new(start_at_id: @config_loader.highwater_mark)
|
134
|
-
|
135
|
-
resp = stub.get_all_config(config_req)
|
136
|
-
load_configs(resp, :remote_api_grpc)
|
137
|
-
true
|
138
|
-
rescue GRPC::Unauthenticated
|
139
|
-
@base_client.log_internal ::Logger::WARN, 'Unauthenticated'
|
140
|
-
rescue StandardError => e
|
141
|
-
@base_client.log_internal ::Logger::WARN, "Unexpected grpc_api problem loading checkpoint #{e}"
|
142
|
-
false
|
143
|
-
end
|
144
|
-
|
145
116
|
def load_checkpoint_api_cdn
|
146
|
-
|
147
|
-
conn = if Faraday::VERSION[0].to_i >= 2
|
148
|
-
Faraday.new(url) do |conn|
|
149
|
-
conn.request :authorization, :basic, AUTH_USER, @base_client.api_key
|
150
|
-
end
|
151
|
-
else
|
152
|
-
Faraday.new(url) do |conn|
|
153
|
-
conn.request :basic_auth, AUTH_USER, @base_client.api_key
|
154
|
-
end
|
155
|
-
end
|
117
|
+
conn = Prefab::HttpConnection.new("#{@options.url_for_api_cdn}/api/v1/configs/0", @base_client.api_key)
|
156
118
|
load_url(conn, :remote_cdn_api)
|
157
119
|
end
|
158
120
|
|
121
|
+
def load_checkpoint_api
|
122
|
+
conn = Prefab::HttpConnection.new("#{@options.prefab_api_url}/api/v1/configs/0", @base_client.api_key)
|
123
|
+
load_url(conn, :remote_api)
|
124
|
+
end
|
125
|
+
|
159
126
|
def load_url(conn, source)
|
160
127
|
resp = conn.get('')
|
161
128
|
if resp.status == 200
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Prefab
|
2
|
+
class ExponentialBackoff
|
3
|
+
def initialize(max_delay:, initial_delay: 2, multiplier: 2)
|
4
|
+
@initial_delay = initial_delay
|
5
|
+
@max_delay = max_delay
|
6
|
+
@multiplier = multiplier
|
7
|
+
@delay = initial_delay
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
delay = @delay
|
12
|
+
@delay = [@delay * @multiplier, @max_delay].min
|
13
|
+
delay
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class HttpConnection
|
5
|
+
AUTH_USER = 'authuser'
|
6
|
+
PROTO_HEADERS = { 'Content-Type' => 'application/x-protobuf', 'Accept' => 'application/x-protobuf' }.freeze
|
7
|
+
|
8
|
+
def initialize(api_root, api_key)
|
9
|
+
@api_root = api_root
|
10
|
+
@api_key = api_key
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(path)
|
14
|
+
connection.get(path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def post(path, body)
|
18
|
+
connection(PROTO_HEADERS).post(path, body.to_proto)
|
19
|
+
end
|
20
|
+
|
21
|
+
def connection(headers = {})
|
22
|
+
if Faraday::VERSION[0].to_i >= 2
|
23
|
+
Faraday.new(@api_root) do |conn|
|
24
|
+
conn.request :authorization, :basic, AUTH_USER, @api_key
|
25
|
+
|
26
|
+
conn.headers.merge!(headers)
|
27
|
+
end
|
28
|
+
else
|
29
|
+
Faraday.new(@api_root) do |conn|
|
30
|
+
conn.request :basic_auth, AUTH_USER, @api_key
|
31
|
+
|
32
|
+
conn.headers.merge!(headers)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -14,9 +14,13 @@ module Prefab
|
|
14
14
|
|
15
15
|
def initialize(client:, max_paths:, sync_interval:)
|
16
16
|
@max_paths = max_paths
|
17
|
-
@sync_interval = sync_interval
|
18
17
|
@client = client
|
19
18
|
@start_at = now
|
19
|
+
@sync_interval = if sync_interval.is_a?(Numeric)
|
20
|
+
proc { sync_interval }
|
21
|
+
else
|
22
|
+
sync_interval || ExponentialBackoff.new(initial_delay: 8, max_delay: 60 * 10)
|
23
|
+
end
|
20
24
|
|
21
25
|
@pool = Concurrent::ThreadPoolExecutor.new(
|
22
26
|
fallback_policy: :discard,
|
@@ -72,16 +76,16 @@ module Prefab
|
|
72
76
|
namespace: @client.namespace
|
73
77
|
)
|
74
78
|
|
75
|
-
@client.
|
79
|
+
@client.post('/api/v1/known-loggers', loggers)
|
76
80
|
end
|
77
81
|
end
|
78
82
|
|
79
83
|
def start_periodic_sync
|
80
84
|
Thread.new do
|
81
|
-
log_internal "Initialized log path collector instance_hash=#{@client.instance_hash} max_paths=#{@max_paths}
|
85
|
+
log_internal "Initialized log path collector instance_hash=#{@client.instance_hash} max_paths=#{@max_paths}"
|
82
86
|
|
83
87
|
loop do
|
84
|
-
sleep @sync_interval
|
88
|
+
sleep @sync_interval.call
|
85
89
|
sync
|
86
90
|
end
|
87
91
|
end
|
data/lib/prefab/options.rb
CHANGED
@@ -10,7 +10,6 @@ module Prefab
|
|
10
10
|
attr_reader :shared_cache
|
11
11
|
attr_reader :namespace
|
12
12
|
attr_reader :prefab_api_url
|
13
|
-
attr_reader :prefab_grpc_url
|
14
13
|
attr_reader :on_no_default
|
15
14
|
attr_reader :initialization_timeout_sec
|
16
15
|
attr_reader :on_init_failure
|
@@ -39,7 +38,6 @@ module Prefab
|
|
39
38
|
end
|
40
39
|
|
41
40
|
DEFAULT_MAX_PATHS = 1_000
|
42
|
-
DEFAULT_SYNC_INTERVAL = 60
|
43
41
|
|
44
42
|
private def init(
|
45
43
|
api_key: ENV['PREFAB_API_KEY'],
|
@@ -50,7 +48,6 @@ module Prefab
|
|
50
48
|
log_formatter: DEFAULT_LOG_FORMATTER,
|
51
49
|
log_prefix: nil,
|
52
50
|
prefab_api_url: ENV['PREFAB_API_URL'] || 'https://api.prefab.cloud',
|
53
|
-
prefab_grpc_url: ENV['PREFAB_GRPC_URL'] || 'grpc.prefab.cloud:443',
|
54
51
|
on_no_default: ON_NO_DEFAULT::RAISE, # options :raise, :warn_and_return_nil,
|
55
52
|
initialization_timeout_sec: 10, # how long to wait before on_init_failure
|
56
53
|
on_init_failure: ON_INITIALIZATION_FAILURE::RAISE, # options :unlock_and_continue, :lock_and_keep_trying, :raise
|
@@ -62,7 +59,7 @@ module Prefab
|
|
62
59
|
prefab_envs: ENV['PREFAB_ENVS'].nil? ? [] : ENV['PREFAB_ENVS'].split(','),
|
63
60
|
collect_logs: true,
|
64
61
|
collect_max_paths: DEFAULT_MAX_PATHS,
|
65
|
-
collect_sync_interval:
|
62
|
+
collect_sync_interval: nil
|
66
63
|
)
|
67
64
|
@api_key = api_key
|
68
65
|
@logdev = logdev
|
@@ -72,7 +69,6 @@ module Prefab
|
|
72
69
|
@log_formatter = log_formatter
|
73
70
|
@log_prefix = log_prefix
|
74
71
|
@prefab_api_url = remove_trailing_slash(prefab_api_url)
|
75
|
-
@prefab_grpc_url = prefab_grpc_url
|
76
72
|
@on_no_default = on_no_default
|
77
73
|
@initialization_timeout_sec = initialization_timeout_sec
|
78
74
|
@on_init_failure = on_init_failure
|
data/lib/prefab-cloud-ruby.rb
CHANGED
@@ -8,10 +8,10 @@ require 'openssl'
|
|
8
8
|
require 'ld-eventsource'
|
9
9
|
require 'prefab_pb'
|
10
10
|
require 'prefab/error'
|
11
|
+
require 'prefab/exponential_backoff'
|
11
12
|
require 'prefab/errors/initialization_timeout_error'
|
12
13
|
require 'prefab/errors/invalid_api_key_error'
|
13
14
|
require 'prefab/errors/missing_default_error'
|
14
|
-
require 'prefab_services_pb'
|
15
15
|
require 'prefab/options'
|
16
16
|
require 'prefab/internal_logger'
|
17
17
|
require 'prefab/sse_logger'
|
@@ -22,13 +22,11 @@ require 'prefab/config_loader'
|
|
22
22
|
require 'prefab/local_config_parser'
|
23
23
|
require 'prefab/yaml_config_parser'
|
24
24
|
require 'prefab/config_resolver'
|
25
|
+
require 'prefab/http_connection'
|
25
26
|
require 'prefab/client'
|
26
|
-
require 'prefab/ratelimit_client'
|
27
27
|
require 'prefab/config_client'
|
28
28
|
require 'prefab/feature_flag_client'
|
29
29
|
require 'prefab/logger_client'
|
30
|
-
require 'prefab/auth_interceptor'
|
31
|
-
require 'prefab/cancellable_interceptor'
|
32
30
|
require 'prefab/noop_cache'
|
33
31
|
require 'prefab/noop_stats'
|
34
32
|
require 'prefab/murmer3'
|
data/prefab-cloud-ruby.gemspec
CHANGED
@@ -2,17 +2,17 @@
|
|
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.23.
|
5
|
+
# stub: prefab-cloud-ruby 0.23.4 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "prefab-cloud-ruby".freeze
|
9
|
-
s.version = "0.23.
|
9
|
+
s.version = "0.23.4"
|
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 = "2023-04-
|
15
|
-
s.description = "
|
14
|
+
s.date = "2023-04-12"
|
15
|
+
s.description = "Feature Flags, Live Config, and Dynamic Log Levels as a service".freeze
|
16
16
|
s.email = "jdwyer@prefab.cloud".freeze
|
17
17
|
s.extra_rdoc_files = [
|
18
18
|
"LICENSE.txt",
|
@@ -33,8 +33,6 @@ Gem::Specification.new do |s|
|
|
33
33
|
"VERSION",
|
34
34
|
"compile_protos.sh",
|
35
35
|
"lib/prefab-cloud-ruby.rb",
|
36
|
-
"lib/prefab/auth_interceptor.rb",
|
37
|
-
"lib/prefab/cancellable_interceptor.rb",
|
38
36
|
"lib/prefab/client.rb",
|
39
37
|
"lib/prefab/config_client.rb",
|
40
38
|
"lib/prefab/config_loader.rb",
|
@@ -45,7 +43,9 @@ Gem::Specification.new do |s|
|
|
45
43
|
"lib/prefab/errors/initialization_timeout_error.rb",
|
46
44
|
"lib/prefab/errors/invalid_api_key_error.rb",
|
47
45
|
"lib/prefab/errors/missing_default_error.rb",
|
46
|
+
"lib/prefab/exponential_backoff.rb",
|
48
47
|
"lib/prefab/feature_flag_client.rb",
|
48
|
+
"lib/prefab/http_connection.rb",
|
49
49
|
"lib/prefab/internal_logger.rb",
|
50
50
|
"lib/prefab/local_config_parser.rb",
|
51
51
|
"lib/prefab/log_path_collector.rb",
|
@@ -54,7 +54,6 @@ Gem::Specification.new do |s|
|
|
54
54
|
"lib/prefab/noop_cache.rb",
|
55
55
|
"lib/prefab/noop_stats.rb",
|
56
56
|
"lib/prefab/options.rb",
|
57
|
-
"lib/prefab/ratelimit_client.rb",
|
58
57
|
"lib/prefab/sse_logger.rb",
|
59
58
|
"lib/prefab/weighted_value_resolver.rb",
|
60
59
|
"lib/prefab/yaml_config_parser.rb",
|
@@ -71,6 +70,7 @@ Gem::Specification.new do |s|
|
|
71
70
|
"test/test_config_resolver.rb",
|
72
71
|
"test/test_config_value_unwrapper.rb",
|
73
72
|
"test/test_criteria_evaluator.rb",
|
73
|
+
"test/test_exponential_backoff.rb",
|
74
74
|
"test/test_feature_flag_client.rb",
|
75
75
|
"test/test_helper.rb",
|
76
76
|
"test/test_integration.rb",
|
@@ -94,12 +94,10 @@ Gem::Specification.new do |s|
|
|
94
94
|
s.add_runtime_dependency(%q<faraday>.freeze, [">= 0"])
|
95
95
|
s.add_runtime_dependency(%q<googleapis-common-protos-types>.freeze, [">= 0"])
|
96
96
|
s.add_runtime_dependency(%q<google-protobuf>.freeze, [">= 0"])
|
97
|
-
s.add_runtime_dependency(%q<grpc>.freeze, [">= 0"])
|
98
97
|
s.add_runtime_dependency(%q<ld-eventsource>.freeze, [">= 0"])
|
99
98
|
s.add_runtime_dependency(%q<uuid>.freeze, [">= 0"])
|
100
99
|
s.add_development_dependency(%q<benchmark-ips>.freeze, [">= 0"])
|
101
100
|
s.add_development_dependency(%q<bundler>.freeze, [">= 0"])
|
102
|
-
s.add_development_dependency(%q<grpc-tools>.freeze, [">= 0"])
|
103
101
|
s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.4.9"])
|
104
102
|
s.add_development_dependency(%q<rdoc>.freeze, [">= 0"])
|
105
103
|
s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
|
@@ -108,12 +106,10 @@ Gem::Specification.new do |s|
|
|
108
106
|
s.add_dependency(%q<faraday>.freeze, [">= 0"])
|
109
107
|
s.add_dependency(%q<googleapis-common-protos-types>.freeze, [">= 0"])
|
110
108
|
s.add_dependency(%q<google-protobuf>.freeze, [">= 0"])
|
111
|
-
s.add_dependency(%q<grpc>.freeze, [">= 0"])
|
112
109
|
s.add_dependency(%q<ld-eventsource>.freeze, [">= 0"])
|
113
110
|
s.add_dependency(%q<uuid>.freeze, [">= 0"])
|
114
111
|
s.add_dependency(%q<benchmark-ips>.freeze, [">= 0"])
|
115
112
|
s.add_dependency(%q<bundler>.freeze, [">= 0"])
|
116
|
-
s.add_dependency(%q<grpc-tools>.freeze, [">= 0"])
|
117
113
|
s.add_dependency(%q<juwelier>.freeze, ["~> 2.4.9"])
|
118
114
|
s.add_dependency(%q<rdoc>.freeze, [">= 0"])
|
119
115
|
s.add_dependency(%q<simplecov>.freeze, [">= 0"])
|
data/test/integration_test.rb
CHANGED
@@ -91,8 +91,7 @@ class IntegrationTest
|
|
91
91
|
prefab_envs: ['unit_tests'],
|
92
92
|
prefab_datasources: Prefab::Options::DATASOURCES::ALL,
|
93
93
|
api_key: ENV['PREFAB_INTEGRATION_TEST_API_KEY'],
|
94
|
-
prefab_api_url: 'https://api.staging-prefab.cloud'
|
95
|
-
prefab_grpc_url: 'grpc.staging-prefab.cloud:443'
|
94
|
+
prefab_api_url: 'https://api.staging-prefab.cloud'
|
96
95
|
}.merge(@client_overrides))
|
97
96
|
end
|
98
97
|
end
|
data/test/test_client.rb
CHANGED
@@ -77,13 +77,6 @@ class TestClient < Minitest::Test
|
|
77
77
|
assert_equal 'all-features', @client.get('flag_with_a_value')
|
78
78
|
end
|
79
79
|
|
80
|
-
def test_ssl_certs
|
81
|
-
certs = @client.send(:ssl_certs).split('-----BEGIN CERTIFICATE-----')
|
82
|
-
|
83
|
-
# This is a smoke test to make sure multiple certs are loaded
|
84
|
-
assert certs.length > 1
|
85
|
-
end
|
86
|
-
|
87
80
|
def test_initialization_with_an_options_object
|
88
81
|
options_hash = {
|
89
82
|
namespace: 'test-namespace',
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestExponentialBackoff < Minitest::Test
|
6
|
+
def test_backoff
|
7
|
+
backoff = Prefab::ExponentialBackoff.new(max_delay: 120)
|
8
|
+
|
9
|
+
assert_equal 2, backoff.call
|
10
|
+
assert_equal 4, backoff.call
|
11
|
+
assert_equal 8, backoff.call
|
12
|
+
assert_equal 16, backoff.call
|
13
|
+
assert_equal 32, backoff.call
|
14
|
+
assert_equal 64, backoff.call
|
15
|
+
assert_equal 120, backoff.call
|
16
|
+
assert_equal 120, backoff.call
|
17
|
+
end
|
18
|
+
end
|
@@ -4,6 +4,9 @@ require 'test_helper'
|
|
4
4
|
require 'timecop'
|
5
5
|
|
6
6
|
class TestLogPathCollector < Minitest::Test
|
7
|
+
MAX_WAIT = 2
|
8
|
+
SLEEP_TIME = 0.01
|
9
|
+
|
7
10
|
def test_sync
|
8
11
|
Timecop.freeze do
|
9
12
|
client = new_client(namespace: 'this.is.a.namespace')
|
@@ -13,29 +16,32 @@ class TestLogPathCollector < Minitest::Test
|
|
13
16
|
|
14
17
|
requests = []
|
15
18
|
|
16
|
-
client.define_singleton_method(:
|
19
|
+
client.define_singleton_method(:post) do |*params|
|
17
20
|
requests.push(params)
|
18
21
|
end
|
19
22
|
|
20
23
|
client.log_path_collector.send(:sync)
|
21
24
|
|
22
25
|
# let the flush thread run
|
23
|
-
|
26
|
+
|
27
|
+
wait_time = 0
|
28
|
+
while requests.length == 0
|
29
|
+
wait_time += SLEEP_TIME
|
30
|
+
sleep SLEEP_TIME
|
31
|
+
|
32
|
+
raise "Waited #{MAX_WAIT} seconds for the flush thread to run, but it never did" if wait_time > MAX_WAIT
|
33
|
+
end
|
24
34
|
|
25
35
|
assert_equal requests, [[
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
instance_hash: client.instance_hash,
|
36
|
-
namespace: 'this.is.a.namespace'
|
37
|
-
)
|
38
|
-
}
|
36
|
+
'/api/v1/known-loggers',
|
37
|
+
Prefab::Loggers.new(
|
38
|
+
loggers: [Prefab::Logger.new(logger_name: 'test.test_log_path_collector.test_sync',
|
39
|
+
infos: 2, errors: 3)],
|
40
|
+
start_at: (Time.now.utc.to_f * 1000).to_i,
|
41
|
+
end_at: (Time.now.utc.to_f * 1000).to_i,
|
42
|
+
instance_hash: client.instance_hash,
|
43
|
+
namespace: 'this.is.a.namespace'
|
44
|
+
)
|
39
45
|
]]
|
40
46
|
end
|
41
47
|
end
|
@@ -47,8 +53,7 @@ class TestLogPathCollector < Minitest::Test
|
|
47
53
|
prefab_config_override_dir: 'none',
|
48
54
|
prefab_config_classpath_dir: 'test',
|
49
55
|
prefab_envs: ['unit_tests'],
|
50
|
-
api_key: '123-development-yourapikey-SDK'
|
51
|
-
collect_sync_interval: 1000 # we'll trigger sync manually in our test
|
56
|
+
api_key: '123-development-yourapikey-SDK'
|
52
57
|
}.merge(overrides))
|
53
58
|
|
54
59
|
Prefab::Client.new(options)
|
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.23.
|
4
|
+
version: 0.23.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Dwyer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-04-
|
11
|
+
date: 2023-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -72,20 +72,6 @@ dependencies:
|
|
72
72
|
- - ">="
|
73
73
|
- !ruby/object:Gem::Version
|
74
74
|
version: '0'
|
75
|
-
- !ruby/object:Gem::Dependency
|
76
|
-
name: grpc
|
77
|
-
requirement: !ruby/object:Gem::Requirement
|
78
|
-
requirements:
|
79
|
-
- - ">="
|
80
|
-
- !ruby/object:Gem::Version
|
81
|
-
version: '0'
|
82
|
-
type: :runtime
|
83
|
-
prerelease: false
|
84
|
-
version_requirements: !ruby/object:Gem::Requirement
|
85
|
-
requirements:
|
86
|
-
- - ">="
|
87
|
-
- !ruby/object:Gem::Version
|
88
|
-
version: '0'
|
89
75
|
- !ruby/object:Gem::Dependency
|
90
76
|
name: ld-eventsource
|
91
77
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,20 +128,6 @@ dependencies:
|
|
142
128
|
- - ">="
|
143
129
|
- !ruby/object:Gem::Version
|
144
130
|
version: '0'
|
145
|
-
- !ruby/object:Gem::Dependency
|
146
|
-
name: grpc-tools
|
147
|
-
requirement: !ruby/object:Gem::Requirement
|
148
|
-
requirements:
|
149
|
-
- - ">="
|
150
|
-
- !ruby/object:Gem::Version
|
151
|
-
version: '0'
|
152
|
-
type: :development
|
153
|
-
prerelease: false
|
154
|
-
version_requirements: !ruby/object:Gem::Requirement
|
155
|
-
requirements:
|
156
|
-
- - ">="
|
157
|
-
- !ruby/object:Gem::Version
|
158
|
-
version: '0'
|
159
131
|
- !ruby/object:Gem::Dependency
|
160
132
|
name: juwelier
|
161
133
|
requirement: !ruby/object:Gem::Requirement
|
@@ -198,7 +170,7 @@ dependencies:
|
|
198
170
|
- - ">="
|
199
171
|
- !ruby/object:Gem::Version
|
200
172
|
version: '0'
|
201
|
-
description:
|
173
|
+
description: Feature Flags, Live Config, and Dynamic Log Levels as a service
|
202
174
|
email: jdwyer@prefab.cloud
|
203
175
|
executables: []
|
204
176
|
extensions: []
|
@@ -220,8 +192,6 @@ files:
|
|
220
192
|
- VERSION
|
221
193
|
- compile_protos.sh
|
222
194
|
- lib/prefab-cloud-ruby.rb
|
223
|
-
- lib/prefab/auth_interceptor.rb
|
224
|
-
- lib/prefab/cancellable_interceptor.rb
|
225
195
|
- lib/prefab/client.rb
|
226
196
|
- lib/prefab/config_client.rb
|
227
197
|
- lib/prefab/config_loader.rb
|
@@ -232,7 +202,9 @@ files:
|
|
232
202
|
- lib/prefab/errors/initialization_timeout_error.rb
|
233
203
|
- lib/prefab/errors/invalid_api_key_error.rb
|
234
204
|
- lib/prefab/errors/missing_default_error.rb
|
205
|
+
- lib/prefab/exponential_backoff.rb
|
235
206
|
- lib/prefab/feature_flag_client.rb
|
207
|
+
- lib/prefab/http_connection.rb
|
236
208
|
- lib/prefab/internal_logger.rb
|
237
209
|
- lib/prefab/local_config_parser.rb
|
238
210
|
- lib/prefab/log_path_collector.rb
|
@@ -241,7 +213,6 @@ files:
|
|
241
213
|
- lib/prefab/noop_cache.rb
|
242
214
|
- lib/prefab/noop_stats.rb
|
243
215
|
- lib/prefab/options.rb
|
244
|
-
- lib/prefab/ratelimit_client.rb
|
245
216
|
- lib/prefab/sse_logger.rb
|
246
217
|
- lib/prefab/weighted_value_resolver.rb
|
247
218
|
- lib/prefab/yaml_config_parser.rb
|
@@ -258,6 +229,7 @@ files:
|
|
258
229
|
- test/test_config_resolver.rb
|
259
230
|
- test/test_config_value_unwrapper.rb
|
260
231
|
- test/test_criteria_evaluator.rb
|
232
|
+
- test/test_exponential_backoff.rb
|
261
233
|
- test/test_feature_flag_client.rb
|
262
234
|
- test/test_helper.rb
|
263
235
|
- test/test_integration.rb
|
@@ -1,34 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Prefab
|
4
|
-
class AuthInterceptor < GRPC::ClientInterceptor
|
5
|
-
VERSION = File.exist?('VERSION') ? File.read('VERSION').chomp : ''
|
6
|
-
CLIENT = "prefab-cloud-ruby.#{VERSION}".freeze
|
7
|
-
|
8
|
-
def initialize(api_key)
|
9
|
-
@api_key = api_key
|
10
|
-
end
|
11
|
-
|
12
|
-
def request_response(request:, call:, method:, metadata:, &block)
|
13
|
-
shared(metadata, &block)
|
14
|
-
end
|
15
|
-
|
16
|
-
def client_streamer(requests:, call:, method:, metadata:, &block)
|
17
|
-
shared(metadata, &block)
|
18
|
-
end
|
19
|
-
|
20
|
-
def server_streamer(request:, call:, method:, metadata:, &block)
|
21
|
-
shared(metadata, &block)
|
22
|
-
end
|
23
|
-
|
24
|
-
def bidi_streamer(requests:, call:, method:, metadata:, &block)
|
25
|
-
shared(metadata, &block)
|
26
|
-
end
|
27
|
-
|
28
|
-
def shared(metadata)
|
29
|
-
metadata['auth'] = @api_key
|
30
|
-
metadata['client'] = CLIENT
|
31
|
-
yield
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1,49 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Prefab
|
4
|
-
class CancellableInterceptor < GRPC::ClientInterceptor
|
5
|
-
WAIT_SEC = 3
|
6
|
-
|
7
|
-
def initialize(base_client)
|
8
|
-
@base_client = base_client
|
9
|
-
end
|
10
|
-
|
11
|
-
def cancel
|
12
|
-
@call.instance_variable_get('@wrapped').instance_variable_get('@call').cancel
|
13
|
-
i = 0
|
14
|
-
while i < WAIT_SEC
|
15
|
-
if @call.instance_variable_get('@wrapped').cancelled?
|
16
|
-
@base_client.log_internal ::Logger::DEBUG, 'Cancelled streaming.'
|
17
|
-
return
|
18
|
-
else
|
19
|
-
@base_client.log_internal ::Logger::DEBUG, 'Unable to cancel streaming. Trying again'
|
20
|
-
@call.instance_variable_get('@wrapped').instance_variable_get('@call').cancel
|
21
|
-
i += 1
|
22
|
-
sleep(1)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
@base_client.log_internal ::Logger::INFO, 'Unable to cancel streaming.'
|
26
|
-
end
|
27
|
-
|
28
|
-
def request_response(request:, call:, method:, metadata:, &block)
|
29
|
-
shared(call, &block)
|
30
|
-
end
|
31
|
-
|
32
|
-
def client_streamer(requests:, call:, method:, metadata:, &block)
|
33
|
-
shared(call, &block)
|
34
|
-
end
|
35
|
-
|
36
|
-
def server_streamer(request:, call:, method:, metadata:, &block)
|
37
|
-
shared(call, &block)
|
38
|
-
end
|
39
|
-
|
40
|
-
def bidi_streamer(requests:, call:, method:, metadata:, &block)
|
41
|
-
shared(call, &block)
|
42
|
-
end
|
43
|
-
|
44
|
-
def shared(call)
|
45
|
-
@call = call
|
46
|
-
yield
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
@@ -1,78 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Prefab
|
4
|
-
class RateLimitClient
|
5
|
-
def initialize(base_client, timeout)
|
6
|
-
@timeout = timeout
|
7
|
-
@base_client = base_client
|
8
|
-
end
|
9
|
-
|
10
|
-
def pass?(group)
|
11
|
-
result = acquire([group], 1)
|
12
|
-
result.passed
|
13
|
-
end
|
14
|
-
|
15
|
-
def acquire(groups, acquire_amount, allow_partial_response: false, on_error: :log_and_pass)
|
16
|
-
expiry_cache_key = "prefab.ratelimit.expiry:#{groups.join('.')}"
|
17
|
-
expiry = @base_client.shared_cache.read(expiry_cache_key)
|
18
|
-
if !expiry.nil? && Integer(expiry) > Time.now.utc.to_f * 1000
|
19
|
-
@base_client.stats.increment('prefab.ratelimit.limitcheck.expirycache.hit', tags: [])
|
20
|
-
return Prefab::LimitResponse.new(passed: false, amount: 0)
|
21
|
-
end
|
22
|
-
|
23
|
-
req = Prefab::LimitRequest.new(
|
24
|
-
account_id: @base_client.account_id,
|
25
|
-
acquire_amount: acquire_amount,
|
26
|
-
groups: groups,
|
27
|
-
allow_partial_response: allow_partial_response
|
28
|
-
)
|
29
|
-
|
30
|
-
result = @base_client.request Prefab::RateLimitService, :limit_check, req_options: { timeout: @timeout },
|
31
|
-
params: req
|
32
|
-
|
33
|
-
reset = result.limit_reset_at
|
34
|
-
@base_client.shared_cache.write(expiry_cache_key, reset) unless reset < 1 # protobuf default int to 0
|
35
|
-
|
36
|
-
@base_client.stats.increment('prefab.ratelimit.limitcheck',
|
37
|
-
tags: ["policy_group:#{result.policy_group}", "pass:#{result.passed}"])
|
38
|
-
|
39
|
-
result
|
40
|
-
rescue StandardError => e
|
41
|
-
handle_error(e, on_error, groups)
|
42
|
-
end
|
43
|
-
|
44
|
-
def upsert(key, policy_name, limit, burst: nil, safety_level: nil)
|
45
|
-
burst = limit if burst.nil?
|
46
|
-
limit_definition = Prefab::LimitDefinition.new(
|
47
|
-
account_id: @base_client.account_id,
|
48
|
-
policy_name: Object.const_get("Prefab::LimitResponse::LimitPolicyNames::#{policy_name}"),
|
49
|
-
limit: limit,
|
50
|
-
burst: burst
|
51
|
-
)
|
52
|
-
limit_definition.safety_level = safety_level unless safety_level.nil?
|
53
|
-
config_value = Prefab::ConfigValue.new(limit_definition: limit_definition)
|
54
|
-
config_delta = Prefab::ConfigClient.value_to_delta(key, config_value)
|
55
|
-
upsert_req = Prefab::UpsertRequest.new(config_delta: config_delta)
|
56
|
-
|
57
|
-
@base_client.request Prefab::ConfigService, :upsert, req_options: { timeout: @timeout }, params: upsert_req
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
def handle_error(e, on_error, groups)
|
63
|
-
@base_client.stats.increment('prefab.ratelimit.error', tags: ['type:limit'])
|
64
|
-
|
65
|
-
message = "ratelimit for #{groups} error: #{e.message}"
|
66
|
-
case on_error
|
67
|
-
when :log_and_pass
|
68
|
-
@base_client.log.warn(message)
|
69
|
-
Prefab::LimitResponse.new(passed: true, amount: 0)
|
70
|
-
when :log_and_hit
|
71
|
-
@base_client.log.warn(message)
|
72
|
-
Prefab::LimitResponse.new(passed: false, amount: 0)
|
73
|
-
when :throw
|
74
|
-
raise e
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|