prefab-cloud-ruby 1.7.2 → 1.8.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 +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +3 -3
- data/VERSION +1 -1
- data/lib/prefab/client.rb +6 -5
- data/lib/prefab/config_client.rb +30 -42
- data/lib/prefab/context_shape_aggregator.rb +14 -8
- data/lib/prefab/criteria_evaluator.rb +5 -3
- data/lib/prefab/log_path_aggregator.rb +8 -7
- data/lib/prefab/options.rb +30 -8
- data/lib/prefab/periodic_sync.rb +4 -0
- data/lib/prefab/sse_config_client.rb +91 -0
- data/lib/prefab-cloud-ruby.rb +1 -0
- data/prefab-cloud-ruby.gemspec +5 -3
- data/test/integration_test.rb +16 -3
- data/test/integration_test_helpers.rb +3 -7
- data/test/support/common_helpers.rb +4 -1
- data/test/test_context_shape_aggregator.rb +26 -20
- data/test/test_example_contexts_aggregator.rb +0 -2
- data/test/test_integration.rb +13 -2
- data/test/test_log_path_aggregator.rb +21 -9
- data/test/test_options.rb +19 -6
- data/test/test_sse_config_client.rb +65 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9cc77b24b0cabb61c5ffb820116e8cf1a5122ceb4fae6e0d56245be4ab165eb9
|
4
|
+
data.tar.gz: 11e541dfe23555733292de53b1d3193291b81dd412cea70e6126b0c042f52f12
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 795e04559f5dab867e30ba1ce58a622c4c651b7214a1dcfadde0d8fcac1825c88699b019fc36a1a88cd56a94dc89fb9564d773262b4a0e4d07247c5bc60a1cd4
|
7
|
+
data.tar.gz: 2e4718e509231db718a65e18d766a7d481b72e344c9d73d766a17fbe91619a30694799b622a0fdbc419f440c93a449abb1fdc00dfb58ea75c0c5387f190df23e
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -101,7 +101,7 @@ GEM
|
|
101
101
|
rake (~> 13.0)
|
102
102
|
macaddr (1.7.2)
|
103
103
|
systemu (~> 2.6.5)
|
104
|
-
mini_portile2 (2.8.
|
104
|
+
mini_portile2 (2.8.7)
|
105
105
|
minitest (5.22.3)
|
106
106
|
minitest-focus (1.4.0)
|
107
107
|
minitest (>= 4, < 6)
|
@@ -114,7 +114,7 @@ GEM
|
|
114
114
|
multi_xml (0.6.0)
|
115
115
|
multipart-post (2.4.0)
|
116
116
|
mutex_m (0.2.0)
|
117
|
-
nokogiri (1.16.
|
117
|
+
nokogiri (1.16.7)
|
118
118
|
mini_portile2 (~> 2.8.2)
|
119
119
|
racc (~> 1.4)
|
120
120
|
oauth2 (1.4.11)
|
@@ -126,7 +126,7 @@ GEM
|
|
126
126
|
psych (5.1.2)
|
127
127
|
stringio
|
128
128
|
public_suffix (5.0.4)
|
129
|
-
racc (1.
|
129
|
+
racc (1.8.1)
|
130
130
|
rack (3.0.10)
|
131
131
|
rake (13.1.0)
|
132
132
|
rchardet (1.8.0)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.8.0
|
data/lib/prefab/client.rb
CHANGED
@@ -8,7 +8,7 @@ module Prefab
|
|
8
8
|
MAX_SLEEP_SEC = 10
|
9
9
|
BASE_SLEEP_SEC = 0.5
|
10
10
|
|
11
|
-
attr_reader :namespace, :interceptor, :api_key, :
|
11
|
+
attr_reader :namespace, :interceptor, :api_key, :options, :instance_hash
|
12
12
|
|
13
13
|
def initialize(options = Prefab::Options.new)
|
14
14
|
@options = options.is_a?(Prefab::Options) ? options : Prefab::Options.new(options)
|
@@ -23,9 +23,6 @@ module Prefab
|
|
23
23
|
else
|
24
24
|
@api_key = @options.api_key
|
25
25
|
raise Prefab::Errors::InvalidApiKeyError, @api_key if @api_key.nil? || @api_key.empty? || api_key.count('-') < 1
|
26
|
-
|
27
|
-
@prefab_api_url = @options.prefab_api_url
|
28
|
-
LOG.debug "Prefab Connecting to: #{@prefab_api_url}"
|
29
26
|
end
|
30
27
|
|
31
28
|
context.clear
|
@@ -48,6 +45,10 @@ module Prefab
|
|
48
45
|
@config_client ||= Prefab::ConfigClient.new(self, timeout)
|
49
46
|
end
|
50
47
|
|
48
|
+
def stop
|
49
|
+
@config_client.stop
|
50
|
+
end
|
51
|
+
|
51
52
|
def feature_flag_client
|
52
53
|
@feature_flag_client ||= Prefab::FeatureFlagClient.new(self)
|
53
54
|
end
|
@@ -111,7 +112,7 @@ module Prefab
|
|
111
112
|
end
|
112
113
|
|
113
114
|
def post(path, body)
|
114
|
-
Prefab::HttpConnection.new(@options.
|
115
|
+
Prefab::HttpConnection.new(@options.telemetry_destination, @api_key).post(path, body)
|
115
116
|
end
|
116
117
|
|
117
118
|
def inspect
|
data/lib/prefab/config_client.rb
CHANGED
@@ -3,19 +3,15 @@
|
|
3
3
|
module Prefab
|
4
4
|
class ConfigClient
|
5
5
|
LOG = Prefab::InternalLogger.new(self)
|
6
|
-
RECONNECT_WAIT = 5
|
7
6
|
DEFAULT_CHECKPOINT_FREQ_SEC = 60
|
8
|
-
SSE_READ_TIMEOUT = 300
|
9
7
|
STALE_CACHE_WARN_HOURS = 5
|
10
|
-
|
8
|
+
|
11
9
|
def initialize(base_client, timeout)
|
12
10
|
@base_client = base_client
|
13
11
|
@options = base_client.options
|
14
12
|
LOG.debug 'Initialize ConfigClient'
|
15
13
|
@timeout = timeout
|
16
14
|
|
17
|
-
@stream_lock = Concurrent::ReadWriteLock.new
|
18
|
-
|
19
15
|
@checkpoint_freq_secs = DEFAULT_CHECKPOINT_FREQ_SEC
|
20
16
|
|
21
17
|
@config_loader = Prefab::ConfigLoader.new(@base_client)
|
@@ -34,9 +30,27 @@ module Prefab
|
|
34
30
|
end
|
35
31
|
end
|
36
32
|
|
33
|
+
def stop
|
34
|
+
@sse_config_client&.close
|
35
|
+
end
|
36
|
+
|
37
37
|
def start_streaming
|
38
|
-
|
39
|
-
|
38
|
+
Thread.new do
|
39
|
+
# wait for the config loader to have a highwater mark before we connect the SSE
|
40
|
+
loop do
|
41
|
+
break if @config_loader.highwater_mark > 0
|
42
|
+
|
43
|
+
sleep 0.1
|
44
|
+
end
|
45
|
+
|
46
|
+
stream_lock = Concurrent::ReadWriteLock.new
|
47
|
+
@sse_config_client = Prefab::SSEConfigClient.new(@options, @config_loader)
|
48
|
+
|
49
|
+
@sse_config_client.start do |configs|
|
50
|
+
stream_lock.with_write_lock do
|
51
|
+
load_configs(configs, :sse)
|
52
|
+
end
|
53
|
+
end
|
40
54
|
end
|
41
55
|
end
|
42
56
|
|
@@ -104,29 +118,24 @@ module Prefab
|
|
104
118
|
end
|
105
119
|
|
106
120
|
def load_checkpoint
|
107
|
-
success =
|
108
|
-
|
109
|
-
return if success
|
110
|
-
|
111
|
-
success = load_checkpoint_api
|
112
|
-
|
121
|
+
success = load_source_checkpoint
|
113
122
|
return if success
|
114
123
|
|
115
124
|
success = load_cache
|
116
|
-
|
117
125
|
return if success
|
118
126
|
|
119
127
|
LOG.warn 'No success loading checkpoints'
|
120
128
|
end
|
121
129
|
|
122
|
-
def
|
123
|
-
|
124
|
-
|
125
|
-
|
130
|
+
def load_source_checkpoint
|
131
|
+
@options.config_sources.each do |source|
|
132
|
+
conn = Prefab::HttpConnection.new("#{source}/api/v1/configs/0", @base_client.api_key)
|
133
|
+
result = load_url(conn, :remote_api)
|
126
134
|
|
127
|
-
|
128
|
-
|
129
|
-
|
135
|
+
return true if result
|
136
|
+
end
|
137
|
+
|
138
|
+
return false
|
130
139
|
end
|
131
140
|
|
132
141
|
def load_url(conn, source)
|
@@ -263,26 +272,5 @@ module Prefab
|
|
263
272
|
LOG.info presenter.to_s
|
264
273
|
LOG.debug to_s
|
265
274
|
end
|
266
|
-
|
267
|
-
def start_sse_streaming_connection_thread(start_at_id)
|
268
|
-
auth = "#{AUTH_USER}:#{@base_client.api_key}"
|
269
|
-
auth_string = Base64.strict_encode64(auth)
|
270
|
-
headers = {
|
271
|
-
'x-prefab-start-at-id' => start_at_id,
|
272
|
-
'Authorization' => "Basic #{auth_string}",
|
273
|
-
'X-PrefabCloud-Client-Version' => "prefab-cloud-ruby-#{Prefab::VERSION}"
|
274
|
-
}
|
275
|
-
url = "#{@base_client.prefab_api_url}/api/v1/sse/config"
|
276
|
-
LOG.debug "SSE Streaming Connect to #{url} start_at #{start_at_id}"
|
277
|
-
@streaming_thread = SSE::Client.new(url,
|
278
|
-
headers: headers,
|
279
|
-
read_timeout: SSE_READ_TIMEOUT,
|
280
|
-
logger: Prefab::InternalLogger.new(SSE::Client)) do |client|
|
281
|
-
client.on_event do |event|
|
282
|
-
configs = PrefabProto::Configs.decode(Base64.decode64(event.data))
|
283
|
-
load_configs(configs, :sse)
|
284
|
-
end
|
285
|
-
end
|
286
|
-
end
|
287
275
|
end
|
288
276
|
end
|
@@ -46,16 +46,22 @@ module Prefab
|
|
46
46
|
pool.post do
|
47
47
|
LOG.debug "Uploading context shapes for #{to_ship.values.size}"
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
49
|
+
events = PrefabProto::TelemetryEvents.new(
|
50
|
+
instance_hash: instance_hash,
|
51
|
+
events: [
|
52
|
+
PrefabProto::TelemetryEvent.new(context_shapes:
|
53
|
+
PrefabProto::ContextShapes.new(
|
54
|
+
shapes: to_ship.map do |name, shape|
|
55
|
+
PrefabProto::ContextShape.new(
|
56
|
+
name: name,
|
57
|
+
field_types: shape
|
58
|
+
)
|
59
|
+
end
|
60
|
+
))
|
61
|
+
]
|
56
62
|
)
|
57
63
|
|
58
|
-
result = post('/api/v1/
|
64
|
+
result = post('/api/v1/telemetry', events)
|
59
65
|
|
60
66
|
LOG.debug "Uploaded #{to_ship.values.size} shapes: #{result.status}"
|
61
67
|
end
|
@@ -45,11 +45,13 @@ module Prefab
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def PROP_IS_ONE_OF(criterion, properties)
|
48
|
-
|
48
|
+
Array(value_from_properties(criterion, properties)).any? do |prop|
|
49
|
+
matches?(criterion, prop, properties)
|
50
|
+
end
|
49
51
|
end
|
50
52
|
|
51
53
|
def PROP_IS_NOT_ONE_OF(criterion, properties)
|
52
|
-
!
|
54
|
+
!PROP_IS_ONE_OF(criterion, properties)
|
53
55
|
end
|
54
56
|
|
55
57
|
def PROP_ENDS_WITH_ONE_OF(criterion, properties)
|
@@ -57,7 +59,7 @@ module Prefab
|
|
57
59
|
end
|
58
60
|
|
59
61
|
def PROP_DOES_NOT_END_WITH_ONE_OF(criterion, properties)
|
60
|
-
!
|
62
|
+
!PROP_ENDS_WITH_ONE_OF(criterion, properties)
|
61
63
|
end
|
62
64
|
|
63
65
|
def HIERARCHICAL_MATCH(criterion, properties)
|
@@ -51,15 +51,16 @@ module Prefab
|
|
51
51
|
aggregate[path]['logger_name'] = path
|
52
52
|
end
|
53
53
|
|
54
|
-
loggers = PrefabProto::
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
54
|
+
loggers = PrefabProto::LoggersTelemetryEvent.new(loggers: aggregate.values,
|
55
|
+
start_at: start_at_was,
|
56
|
+
end_at: Prefab::TimeHelpers.now_in_ms)
|
57
|
+
|
58
|
+
events = PrefabProto::TelemetryEvents.new(
|
59
|
+
instance_hash: instance_hash,
|
60
|
+
events: [PrefabProto::TelemetryEvent.new(loggers: loggers)]
|
60
61
|
)
|
61
62
|
|
62
|
-
result = post('/api/v1/
|
63
|
+
result = post('/api/v1/telemetry', events)
|
63
64
|
|
64
65
|
LOG.debug "Uploaded #{to_ship.size} paths: #{result.status}"
|
65
66
|
end
|
data/lib/prefab/options.rb
CHANGED
@@ -5,7 +5,10 @@ module Prefab
|
|
5
5
|
class Options
|
6
6
|
attr_reader :api_key
|
7
7
|
attr_reader :namespace
|
8
|
-
attr_reader :
|
8
|
+
attr_reader :sources
|
9
|
+
attr_reader :sse_sources
|
10
|
+
attr_reader :telemetry_destination
|
11
|
+
attr_reader :config_sources
|
9
12
|
attr_reader :on_no_default
|
10
13
|
attr_reader :initialization_timeout_sec
|
11
14
|
attr_reader :on_init_failure
|
@@ -38,10 +41,16 @@ module Prefab
|
|
38
41
|
DEFAULT_MAX_EXAMPLE_CONTEXTS = 100_000
|
39
42
|
DEFAULT_MAX_EVAL_SUMMARIES = 100_000
|
40
43
|
|
44
|
+
DEFAULT_SOURCES = [
|
45
|
+
"https://belt.prefab.cloud",
|
46
|
+
"https://suspenders.prefab.cloud",
|
47
|
+
].freeze
|
48
|
+
|
41
49
|
private def init(
|
50
|
+
sources: nil,
|
42
51
|
api_key: ENV['PREFAB_API_KEY'],
|
43
52
|
namespace: '',
|
44
|
-
prefab_api_url:
|
53
|
+
prefab_api_url: nil,
|
45
54
|
on_no_default: ON_NO_DEFAULT::RAISE, # options :raise, :warn_and_return_nil,
|
46
55
|
initialization_timeout_sec: 10, # how long to wait before on_init_failure
|
47
56
|
on_init_failure: ON_INITIALIZATION_FAILURE::RAISE,
|
@@ -63,7 +72,6 @@ module Prefab
|
|
63
72
|
)
|
64
73
|
@api_key = api_key
|
65
74
|
@namespace = namespace
|
66
|
-
@prefab_api_url = remove_trailing_slash(prefab_api_url)
|
67
75
|
@on_no_default = on_no_default
|
68
76
|
@initialization_timeout_sec = initialization_timeout_sec
|
69
77
|
@on_init_failure = on_init_failure
|
@@ -88,6 +96,25 @@ module Prefab
|
|
88
96
|
@collect_example_contexts = false
|
89
97
|
@collect_max_example_contexts = 0
|
90
98
|
|
99
|
+
if ENV['PREFAB_API_URL_OVERRIDE'] && ENV['PREFAB_API_URL_OVERRIDE'].length > 0
|
100
|
+
sources = ENV['PREFAB_API_URL_OVERRIDE']
|
101
|
+
end
|
102
|
+
|
103
|
+
@sources = Array(sources || DEFAULT_SOURCES).map {|source| remove_trailing_slash(source) }
|
104
|
+
|
105
|
+
@sse_sources = @sources
|
106
|
+
@config_sources = @sources
|
107
|
+
|
108
|
+
@telemetry_destination = @sources.select do |source|
|
109
|
+
source.start_with?('https://') && (source.include?("belt") || source.include?("suspenders"))
|
110
|
+
end.map do |source|
|
111
|
+
source.sub(/(belt|suspenders)\./, 'telemetry.')
|
112
|
+
end[0]
|
113
|
+
|
114
|
+
if prefab_api_url
|
115
|
+
warn '[DEPRECATION] prefab_api_url is deprecated. Please provide `sources` if you need to override the default sources'
|
116
|
+
end
|
117
|
+
|
91
118
|
case context_upload_mode
|
92
119
|
when :none
|
93
120
|
# do nothing
|
@@ -140,11 +167,6 @@ module Prefab
|
|
140
167
|
@collect_max_evaluation_summaries
|
141
168
|
end
|
142
169
|
|
143
|
-
# https://api.prefab.cloud -> https://api-prefab-cloud.global.ssl.fastly.net
|
144
|
-
def url_for_api_cdn
|
145
|
-
ENV['PREFAB_CDN_URL'] || "#{@prefab_api_url.gsub(/\./, '-')}.global.ssl.fastly.net"
|
146
|
-
end
|
147
|
-
|
148
170
|
def api_key_id
|
149
171
|
@api_key&.split("-")&.first
|
150
172
|
end
|
data/lib/prefab/periodic_sync.rb
CHANGED
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class SSEConfigClient
|
5
|
+
SSE_READ_TIMEOUT = 300
|
6
|
+
SECONDS_BETWEEN_RECONNECT = 5
|
7
|
+
AUTH_USER = 'authuser'
|
8
|
+
LOG = Prefab::InternalLogger.new(self)
|
9
|
+
|
10
|
+
def initialize(options, config_loader)
|
11
|
+
@options = options
|
12
|
+
@config_loader = config_loader
|
13
|
+
@connected = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def close
|
17
|
+
@retry_thread&.kill
|
18
|
+
@client&.close
|
19
|
+
end
|
20
|
+
|
21
|
+
def start(&load_configs)
|
22
|
+
if @options.sse_sources.empty?
|
23
|
+
LOG.debug 'No SSE sources configured'
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
@client = connect(&load_configs)
|
28
|
+
|
29
|
+
closed_count = 0
|
30
|
+
|
31
|
+
@retry_thread = Thread.new do
|
32
|
+
loop do
|
33
|
+
sleep 1
|
34
|
+
|
35
|
+
if @client.closed?
|
36
|
+
closed_count += 1
|
37
|
+
|
38
|
+
if closed_count > SECONDS_BETWEEN_RECONNECT
|
39
|
+
closed_count = 0
|
40
|
+
connect(&load_configs)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def connect(&load_configs)
|
48
|
+
url = "#{source}/api/v1/sse/config"
|
49
|
+
LOG.debug "SSE Streaming Connect to #{url} start_at #{@config_loader.highwater_mark}"
|
50
|
+
|
51
|
+
SSE::Client.new(url,
|
52
|
+
headers: headers,
|
53
|
+
read_timeout: SSE_READ_TIMEOUT,
|
54
|
+
logger: Prefab::InternalLogger.new(SSE::Client)) do |client|
|
55
|
+
client.on_event do |event|
|
56
|
+
configs = PrefabProto::Configs.decode(Base64.decode64(event.data))
|
57
|
+
load_configs.call(configs, :sse)
|
58
|
+
end
|
59
|
+
|
60
|
+
client.on_error do |error|
|
61
|
+
LOG.error "SSE Streaming Error: #{error} for url #{url}"
|
62
|
+
|
63
|
+
if error.is_a?(HTTP::ConnectionError)
|
64
|
+
client.close
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def headers
|
71
|
+
auth = "#{AUTH_USER}:#{@options.api_key}"
|
72
|
+
auth_string = Base64.strict_encode64(auth)
|
73
|
+
return {
|
74
|
+
'x-prefab-start-at-id' => @config_loader.highwater_mark,
|
75
|
+
'Authorization' => "Basic #{auth_string}",
|
76
|
+
'Accept' => 'text/event-stream',
|
77
|
+
'X-PrefabCloud-Client-Version' => "prefab-cloud-ruby-#{Prefab::VERSION}"
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
def source
|
82
|
+
@source_index = @source_index.nil? ? 0 : @source_index + 1
|
83
|
+
|
84
|
+
if @source_index >= @options.sse_sources.size
|
85
|
+
@source_index = 0
|
86
|
+
end
|
87
|
+
|
88
|
+
return @options.sse_sources[@source_index]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/prefab-cloud-ruby.rb
CHANGED
@@ -46,6 +46,7 @@ require 'prefab/context'
|
|
46
46
|
require 'prefab/logger_client'
|
47
47
|
require 'active_support/deprecation'
|
48
48
|
require 'active_support'
|
49
|
+
require 'prefab/sse_config_client'
|
49
50
|
require 'prefab/client'
|
50
51
|
require 'prefab/config_client_presenter'
|
51
52
|
require 'prefab/config_client'
|
data/prefab-cloud-ruby.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: prefab-cloud-ruby 1.
|
5
|
+
# stub: prefab-cloud-ruby 1.8.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "prefab-cloud-ruby".freeze
|
9
|
-
s.version = "1.
|
9
|
+
s.version = "1.8.0"
|
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 = "2024-
|
14
|
+
s.date = "2024-08-22"
|
15
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 = [
|
@@ -74,6 +74,7 @@ Gem::Specification.new do |s|
|
|
74
74
|
"lib/prefab/prefab.rb",
|
75
75
|
"lib/prefab/rate_limit_cache.rb",
|
76
76
|
"lib/prefab/resolved_config_presenter.rb",
|
77
|
+
"lib/prefab/sse_config_client.rb",
|
77
78
|
"lib/prefab/time_helpers.rb",
|
78
79
|
"lib/prefab/weighted_value_resolver.rb",
|
79
80
|
"lib/prefab/yaml_config_parser.rb",
|
@@ -113,6 +114,7 @@ Gem::Specification.new do |s|
|
|
113
114
|
"test/test_options.rb",
|
114
115
|
"test/test_prefab.rb",
|
115
116
|
"test/test_rate_limit_cache.rb",
|
117
|
+
"test/test_sse_config_client.rb",
|
116
118
|
"test/test_weighted_value_resolver.rb"
|
117
119
|
]
|
118
120
|
s.homepage = "http://github.com/prefab-cloud/prefab-cloud-ruby".freeze
|
data/test/integration_test.rb
CHANGED
@@ -20,6 +20,10 @@ class IntegrationTest
|
|
20
20
|
@test_client = capture_telemetry(base_client)
|
21
21
|
end
|
22
22
|
|
23
|
+
def teardown
|
24
|
+
test_client.stop
|
25
|
+
end
|
26
|
+
|
23
27
|
def test_type
|
24
28
|
if @data
|
25
29
|
:telemetry
|
@@ -53,7 +57,11 @@ class IntegrationTest
|
|
53
57
|
def parse_client_overrides(overrides)
|
54
58
|
Hash[
|
55
59
|
(overrides || {}).map do |(k, v)|
|
56
|
-
|
60
|
+
if k.to_s == "prefab_api_url"
|
61
|
+
[:sources, [v]]
|
62
|
+
else
|
63
|
+
[k.to_sym, v]
|
64
|
+
end
|
57
65
|
end
|
58
66
|
]
|
59
67
|
end
|
@@ -127,17 +135,22 @@ class IntegrationTest
|
|
127
135
|
prefab_envs: ['unit_tests'],
|
128
136
|
prefab_datasources: Prefab::Options::DATASOURCES::ALL,
|
129
137
|
api_key: ENV['PREFAB_INTEGRATION_TEST_API_KEY'],
|
130
|
-
|
138
|
+
sources: [
|
139
|
+
'https://belt.staging-prefab.cloud',
|
140
|
+
'https://suspenders.staging-prefab.cloud',
|
141
|
+
],
|
131
142
|
global_context: @global_context || {},
|
132
143
|
}.merge(@client_overrides))
|
133
144
|
end
|
134
145
|
|
135
146
|
def capture_telemetry(client)
|
147
|
+
super_method = client.method(:post)
|
148
|
+
|
136
149
|
client.define_singleton_method(:post) do |url, data|
|
137
150
|
client.instance_variable_set(:@last_data_sent, data)
|
138
151
|
client.instance_variable_set(:@last_post_endpoint, url)
|
139
152
|
|
140
|
-
result =
|
153
|
+
result = super_method.call(url, data)
|
141
154
|
|
142
155
|
client.instance_variable_set(:@last_post_result, result)
|
143
156
|
|
@@ -2,16 +2,12 @@
|
|
2
2
|
|
3
3
|
module IntegrationTestHelpers
|
4
4
|
SUBMODULE_PATH = 'test/prefab-cloud-integration-test-data'
|
5
|
-
RAISE_IF_NO_TESTS_FOUND = ENV['PREFAB_INTEGRATION_TEST_RAISE'] == 'true'
|
6
5
|
|
7
6
|
def self.find_integration_tests
|
8
7
|
files = find_test_files
|
9
8
|
|
10
9
|
if files.none?
|
11
|
-
|
12
|
-
raise message if RAISE_IF_NO_TESTS_FOUND
|
13
|
-
|
14
|
-
puts message
|
10
|
+
raise "No integration tests found"
|
15
11
|
end
|
16
12
|
|
17
13
|
files
|
@@ -44,7 +40,7 @@ module IntegrationTestHelpers
|
|
44
40
|
end
|
45
41
|
end
|
46
42
|
|
47
|
-
[aggregator, ->(data) { data.loggers }, expected_loggers.values]
|
43
|
+
[aggregator, ->(data) { data.events[0].loggers.loggers }, expected_loggers.values]
|
48
44
|
when "context_shape"
|
49
45
|
aggregator = it.test_client.context_shape_aggregator
|
50
46
|
|
@@ -59,7 +55,7 @@ module IntegrationTestHelpers
|
|
59
55
|
)
|
60
56
|
end
|
61
57
|
|
62
|
-
[aggregator, ->(data) { data.shapes }, expected]
|
58
|
+
[aggregator, ->(data) { data.events[0].context_shapes.shapes }, expected]
|
63
59
|
when "evaluation_summary"
|
64
60
|
aggregator = it.test_client.evaluation_summary_aggregator
|
65
61
|
|
@@ -47,11 +47,14 @@ module CommonHelpers
|
|
47
47
|
ENV[key] = old_value
|
48
48
|
end
|
49
49
|
|
50
|
+
EFFECTIVELY_NEVER = 99_999 # we sync manually
|
51
|
+
|
50
52
|
DEFAULT_NEW_CLIENT_OPTIONS = {
|
51
53
|
prefab_config_override_dir: 'none',
|
52
54
|
prefab_config_classpath_dir: 'test',
|
53
55
|
prefab_envs: ['unit_tests'],
|
54
|
-
prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
|
56
|
+
prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY,
|
57
|
+
collect_sync_interval: EFFECTIVELY_NEVER,
|
55
58
|
}.freeze
|
56
59
|
|
57
60
|
def new_client(overrides = {})
|
@@ -98,30 +98,36 @@ class TestContextShapeAggregator < Minitest::Test
|
|
98
98
|
|
99
99
|
assert_equal [
|
100
100
|
[
|
101
|
-
'/api/v1/
|
102
|
-
PrefabProto::
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
101
|
+
'/api/v1/telemetry',
|
102
|
+
PrefabProto::TelemetryEvents.new(
|
103
|
+
instance_hash: client.instance_hash,
|
104
|
+
events: [
|
105
|
+
PrefabProto::TelemetryEvent.new(context_shapes:
|
106
|
+
|
107
|
+
PrefabProto::ContextShapes.new(shapes: [
|
108
|
+
PrefabProto::ContextShape.new(
|
109
|
+
name: 'user', field_types: {
|
110
|
+
'age' => 4, 'dob' => 2, 'email' => 2, 'name' => 2
|
111
|
+
}
|
112
|
+
),
|
113
|
+
PrefabProto::ContextShape.new(
|
114
|
+
name: 'subscription', field_types: {
|
115
|
+
'plan' => 2, 'free' => 5, 'trial' => 5
|
116
|
+
}
|
117
|
+
),
|
118
|
+
PrefabProto::ContextShape.new(
|
119
|
+
name: 'device', field_types: {
|
120
|
+
'version' => 1, 'os' => 2, 'name' => 2
|
121
|
+
}
|
122
|
+
)
|
123
|
+
]))
|
124
|
+
]
|
125
|
+
)
|
119
126
|
]
|
120
127
|
], requests
|
121
128
|
|
122
|
-
|
123
129
|
assert_logged [
|
124
|
-
|
130
|
+
'No success loading checkpoints',
|
125
131
|
"Couldn't Initialize In 0. Key some.key. Returning what we have"
|
126
132
|
]
|
127
133
|
end
|
@@ -4,8 +4,6 @@ require 'test_helper'
|
|
4
4
|
require 'timecop'
|
5
5
|
|
6
6
|
class TestExampleContextsAggregator < Minitest::Test
|
7
|
-
EFFECTIVELY_NEVER = 99_999 # we sync manually
|
8
|
-
|
9
7
|
def test_record
|
10
8
|
aggregator = Prefab::ExampleContextsAggregator.new(client: MockBaseClient.new, max_contexts: 2,
|
11
9
|
sync_interval: EFFECTIVELY_NEVER)
|
data/test/test_integration.rb
CHANGED
@@ -8,10 +8,19 @@ require 'yaml'
|
|
8
8
|
class TestIntegration < Minitest::Test
|
9
9
|
IntegrationTestHelpers.find_integration_tests.map do |test_file|
|
10
10
|
tests = YAML.load(File.read(test_file))['tests']
|
11
|
+
test_names = []
|
11
12
|
|
12
13
|
tests.each do |test|
|
13
14
|
test['cases'].each do |test_case|
|
14
|
-
|
15
|
+
new_name = "test_#{test['name']}_#{test_case['name']}"
|
16
|
+
|
17
|
+
if test_names.include?(new_name)
|
18
|
+
raise "Duplicate test name: #{new_name}"
|
19
|
+
end
|
20
|
+
|
21
|
+
test_names << new_name
|
22
|
+
|
23
|
+
define_method(:"#{new_name}") do
|
15
24
|
it = IntegrationTest.new(test_case)
|
16
25
|
|
17
26
|
IntegrationTestHelpers.with_block_context_maybe(it.block_context) do
|
@@ -38,7 +47,7 @@ class TestIntegration < Minitest::Test
|
|
38
47
|
|
39
48
|
wait_for -> { it.last_post_result&.status == 200 }
|
40
49
|
|
41
|
-
|
50
|
+
assert_equal "/api/v1/telemetry", it.last_post_endpoint
|
42
51
|
|
43
52
|
actual = get_actual_data[it.last_data_sent]
|
44
53
|
|
@@ -56,6 +65,8 @@ class TestIntegration < Minitest::Test
|
|
56
65
|
"Prefab::ConfigClient -- Couldn't Initialize In 0.01. Key any-key. Returning what we have"
|
57
66
|
]
|
58
67
|
end
|
68
|
+
ensure
|
69
|
+
it.teardown
|
59
70
|
end
|
60
71
|
end
|
61
72
|
end
|
@@ -32,15 +32,27 @@ class TestLogPathAggregator < Minitest::Test
|
|
32
32
|
client.log_path_aggregator.send(:sync)
|
33
33
|
end
|
34
34
|
|
35
|
-
assert_equal
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
35
|
+
assert_equal [
|
36
|
+
[
|
37
|
+
'/api/v1/telemetry',
|
38
|
+
PrefabProto::TelemetryEvents.new(
|
39
|
+
instance_hash: client.instance_hash,
|
40
|
+
events: [
|
41
|
+
PrefabProto::TelemetryEvent.new(loggers:
|
42
|
+
|
43
|
+
PrefabProto::LoggersTelemetryEvent.new(loggers: [
|
44
|
+
PrefabProto::Logger.new(
|
45
|
+
logger_name: 'test.test_log_path_aggregator.test_sync',
|
46
|
+
infos: 2,
|
47
|
+
errors: 3
|
48
|
+
)
|
49
|
+
],
|
50
|
+
start_at: Prefab::TimeHelpers.now_in_ms,
|
51
|
+
end_at: Prefab::TimeHelpers.now_in_ms,))
|
52
|
+
]
|
53
|
+
)
|
54
|
+
]
|
55
|
+
], requests
|
44
56
|
end
|
45
57
|
end
|
46
58
|
|
data/test/test_options.rb
CHANGED
@@ -5,18 +5,31 @@ require 'test_helper'
|
|
5
5
|
class TestOptions < Minitest::Test
|
6
6
|
API_KEY = 'abcdefg'
|
7
7
|
|
8
|
-
def
|
9
|
-
assert_equal
|
8
|
+
def test_api_override_env_var
|
9
|
+
assert_equal Prefab::Options::DEFAULT_SOURCES, Prefab::Options.new.sources
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
# blank doesn't take effect
|
12
|
+
with_env('PREFAB_API_URL_OVERRIDE', '') do
|
13
|
+
assert_equal Prefab::Options::DEFAULT_SOURCES, Prefab::Options.new.sources
|
13
14
|
end
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
# non-blank does take effect
|
17
|
+
with_env('PREFAB_API_URL_OVERRIDE', 'https://override.example.com') do
|
18
|
+
assert_equal ["https://override.example.com"], Prefab::Options.new.sources
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
22
|
+
def test_overriding_sources
|
23
|
+
assert_equal Prefab::Options::DEFAULT_SOURCES, Prefab::Options.new.sources
|
24
|
+
|
25
|
+
# a plain string ends up wrapped in an array
|
26
|
+
source = 'https://example.com'
|
27
|
+
assert_equal [source], Prefab::Options.new(sources: source).sources
|
28
|
+
|
29
|
+
sources = ['https://example.com', 'https://example2.com']
|
30
|
+
assert_equal sources, Prefab::Options.new(sources: sources).sources
|
31
|
+
end
|
32
|
+
|
20
33
|
def test_works_with_named_arguments
|
21
34
|
assert_equal API_KEY, Prefab::Options.new(api_key: API_KEY).api_key
|
22
35
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestSSEConfigClient < Minitest::Test
|
4
|
+
def test_client
|
5
|
+
sources = [
|
6
|
+
"https://api.staging-prefab.cloud/",
|
7
|
+
]
|
8
|
+
|
9
|
+
options = Prefab::Options.new(sources: sources, api_key: ENV['PREFAB_INTEGRATION_TEST_API_KEY'])
|
10
|
+
|
11
|
+
config_loader = OpenStruct.new(highwater_mark: 4)
|
12
|
+
|
13
|
+
client = Prefab::SSEConfigClient.new(options, config_loader)
|
14
|
+
|
15
|
+
assert_equal 4, client.headers['x-prefab-start-at-id']
|
16
|
+
|
17
|
+
result = nil
|
18
|
+
|
19
|
+
# fake our load_configs block
|
20
|
+
client.start do |c, source|
|
21
|
+
result = c
|
22
|
+
assert_equal :sse, source
|
23
|
+
end
|
24
|
+
|
25
|
+
wait_for -> { !result.nil? }
|
26
|
+
|
27
|
+
assert result.configs.size > 30
|
28
|
+
ensure
|
29
|
+
client.close
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_failing_over
|
33
|
+
sources = [
|
34
|
+
"https://does.not.exist.staging-prefab.cloud/",
|
35
|
+
"https://api.staging-prefab.cloud/",
|
36
|
+
]
|
37
|
+
|
38
|
+
options = Prefab::Options.new(sources: sources, api_key: ENV['PREFAB_INTEGRATION_TEST_API_KEY'])
|
39
|
+
|
40
|
+
config_loader = OpenStruct.new(highwater_mark: 4)
|
41
|
+
|
42
|
+
client = Prefab::SSEConfigClient.new(options, config_loader)
|
43
|
+
|
44
|
+
assert_equal 4, client.headers['x-prefab-start-at-id']
|
45
|
+
|
46
|
+
result = nil
|
47
|
+
|
48
|
+
# fake our load_configs block
|
49
|
+
client.start do |c, source|
|
50
|
+
result = c
|
51
|
+
assert_equal :sse, source
|
52
|
+
end
|
53
|
+
|
54
|
+
wait_for -> { !result.nil? }, max_wait: 10
|
55
|
+
|
56
|
+
assert result.configs.size > 30
|
57
|
+
ensure
|
58
|
+
client.close
|
59
|
+
|
60
|
+
assert_logged [
|
61
|
+
/failed to connect: .*https:\/\/does.not.exist/,
|
62
|
+
/HTTP::ConnectionError/,
|
63
|
+
]
|
64
|
+
end
|
65
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prefab-cloud-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Dwyer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -261,6 +261,7 @@ files:
|
|
261
261
|
- lib/prefab/prefab.rb
|
262
262
|
- lib/prefab/rate_limit_cache.rb
|
263
263
|
- lib/prefab/resolved_config_presenter.rb
|
264
|
+
- lib/prefab/sse_config_client.rb
|
264
265
|
- lib/prefab/time_helpers.rb
|
265
266
|
- lib/prefab/weighted_value_resolver.rb
|
266
267
|
- lib/prefab/yaml_config_parser.rb
|
@@ -300,6 +301,7 @@ files:
|
|
300
301
|
- test/test_options.rb
|
301
302
|
- test/test_prefab.rb
|
302
303
|
- test/test_rate_limit_cache.rb
|
304
|
+
- test/test_sse_config_client.rb
|
303
305
|
- test/test_weighted_value_resolver.rb
|
304
306
|
homepage: http://github.com/prefab-cloud/prefab-cloud-ruby
|
305
307
|
licenses:
|