prefab-cloud-ruby 0.23.2 → 0.23.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|