prefab-cloud-ruby 1.8.1 → 1.8.3
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/.github/workflows/ruby.yml +1 -1
- data/CHANGELOG.md +8 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +5 -1
- data/Rakefile +8 -6
- data/VERSION +1 -1
- data/dev/allocation_stats +3 -3
- data/dev/console +2 -17
- data/dev/script_setup.rb +18 -0
- data/lib/prefab/config_client.rb +2 -2
- data/lib/prefab/config_resolver.rb +4 -0
- data/lib/prefab/criteria_evaluator.rb +4 -2
- data/lib/prefab/duration.rb +4 -0
- data/lib/prefab/javascript_stub.rb +99 -0
- data/lib/prefab/prefab.rb +10 -0
- data/lib/prefab/sse_config_client.rb +38 -18
- data/lib/prefab-cloud-ruby.rb +1 -0
- data/prefab-cloud-ruby.gemspec +10 -5
- data/test/test_javascript_stub.rb +133 -0
- data/test/test_sse_config_client.rb +155 -11
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ddab427174fc8ee9580364b7f55becb58806d98b94968ee3d903ce67e9b01268
|
4
|
+
data.tar.gz: 181cdf62f18a7c9b09846746597273230f6b13100b302ef4531693f0a3d07085
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 784b5d8e0229ec1e953d16a2442561a1cc67e18fc4f398f4878ac4672256892a0d6c314b06b9cd835a8309d184d47a2280d348b8afd396f977c6c57248d7c2b9
|
7
|
+
data.tar.gz: '0865454d8eab0548aff23e3caa338d037babc8d9a49165424e6b8822d6a5f5b5ba67e8395752bebedb9666ff5cd3f1eac46f4d4df66abc1c8f2274770e4d634f'
|
data/.github/workflows/ruby.yml
CHANGED
@@ -40,7 +40,7 @@ jobs:
|
|
40
40
|
- name: Install dependencies
|
41
41
|
run: bundle install --without development --jobs 4 --retry 3
|
42
42
|
- name: Run tests
|
43
|
-
run: bundle exec rake
|
43
|
+
run: bundle exec rake --trace
|
44
44
|
env:
|
45
45
|
PREFAB_INTEGRATION_TEST_API_KEY: ${{ secrets.PREFAB_INTEGRATION_TEST_API_KEY }}
|
46
46
|
PREFAB_INTEGRATION_TEST_ENCRYPTION_KEY: ${{ secrets.PREFAB_INTEGRATION_TEST_ENCRYPTION_KEY }}
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
@@ -9,9 +9,10 @@ gem 'uuid'
|
|
9
9
|
|
10
10
|
gem 'activesupport', '>= 4'
|
11
11
|
|
12
|
-
gem 'semantic_logger', require: "semantic_logger/sync"
|
12
|
+
gem 'semantic_logger', '!= 4.16.0', require: "semantic_logger/sync"
|
13
13
|
|
14
14
|
group :development do
|
15
|
+
gem 'allocation_stats'
|
15
16
|
gem 'benchmark-ips'
|
16
17
|
gem 'bundler'
|
17
18
|
gem 'juwelier', '~> 2.4.9'
|
@@ -24,4 +25,5 @@ group :test do
|
|
24
25
|
gem 'minitest-focus'
|
25
26
|
gem 'minitest-reporters'
|
26
27
|
gem 'timecop'
|
28
|
+
gem 'webrick'
|
27
29
|
end
|
data/Gemfile.lock
CHANGED
@@ -13,6 +13,7 @@ GEM
|
|
13
13
|
tzinfo (~> 2.0)
|
14
14
|
addressable (2.8.6)
|
15
15
|
public_suffix (>= 2.0.2, < 6.0)
|
16
|
+
allocation_stats (0.1.5)
|
16
17
|
ansi (1.5.0)
|
17
18
|
base64 (0.2.0)
|
18
19
|
benchmark-ips (2.13.0)
|
@@ -151,12 +152,14 @@ GEM
|
|
151
152
|
concurrent-ruby (~> 1.0)
|
152
153
|
uuid (2.3.9)
|
153
154
|
macaddr (~> 1.0)
|
155
|
+
webrick (1.8.1)
|
154
156
|
|
155
157
|
PLATFORMS
|
156
158
|
ruby
|
157
159
|
|
158
160
|
DEPENDENCIES
|
159
161
|
activesupport (>= 4)
|
162
|
+
allocation_stats
|
160
163
|
benchmark-ips
|
161
164
|
bundler
|
162
165
|
concurrent-ruby (~> 1.0, >= 1.0.5)
|
@@ -169,10 +172,11 @@ DEPENDENCIES
|
|
169
172
|
minitest-focus
|
170
173
|
minitest-reporters
|
171
174
|
rdoc
|
172
|
-
semantic_logger
|
175
|
+
semantic_logger (!= 4.16.0)
|
173
176
|
simplecov
|
174
177
|
timecop
|
175
178
|
uuid
|
179
|
+
webrick
|
176
180
|
|
177
181
|
BUNDLED WITH
|
178
182
|
2.3.5
|
data/Rakefile
CHANGED
@@ -11,6 +11,14 @@ rescue Bundler::BundlerError => e
|
|
11
11
|
end
|
12
12
|
|
13
13
|
require 'rake'
|
14
|
+
|
15
|
+
require 'rake/testtask'
|
16
|
+
Rake::TestTask.new(:test) do |test|
|
17
|
+
test.libs << 'lib' << 'test'
|
18
|
+
test.pattern = 'test/**/test_*.rb'
|
19
|
+
test.verbose = true
|
20
|
+
end
|
21
|
+
|
14
22
|
task default: :test
|
15
23
|
|
16
24
|
unless ENV['CI']
|
@@ -28,12 +36,6 @@ unless ENV['CI']
|
|
28
36
|
# dependencies defined in Gemfile
|
29
37
|
end
|
30
38
|
Juwelier::RubygemsDotOrgTasks.new
|
31
|
-
require 'rake/testtask'
|
32
|
-
Rake::TestTask.new(:test) do |test|
|
33
|
-
test.libs << 'lib' << 'test'
|
34
|
-
test.pattern = 'test/**/test_*.rb'
|
35
|
-
test.verbose = true
|
36
|
-
end
|
37
39
|
|
38
40
|
desc 'Code coverage detail'
|
39
41
|
task :simplecov do
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.8.
|
1
|
+
1.8.3
|
data/dev/allocation_stats
CHANGED
@@ -20,7 +20,7 @@ require 'prefab-cloud-ruby'
|
|
20
20
|
|
21
21
|
$prefab = Prefab::Client.new(collect_logger_counts: false, collect_evaluation_summaries: false,
|
22
22
|
context_upload_mode: :none)
|
23
|
-
$prefab.get('
|
23
|
+
$prefab.get('a.live.integer')
|
24
24
|
|
25
25
|
puts '-' * 80
|
26
26
|
|
@@ -50,11 +50,11 @@ def measure(description)
|
|
50
50
|
end
|
51
51
|
|
52
52
|
measure "no-JIT context (#{$runs} runs)" do
|
53
|
-
$prefab.get('
|
53
|
+
$prefab.get('a.live.integer')
|
54
54
|
end
|
55
55
|
|
56
56
|
puts "\n\n"
|
57
57
|
|
58
58
|
measure "with JIT context (#{$runs} runs)" do
|
59
|
-
$prefab.get('
|
59
|
+
$prefab.get('a.live.integer', { a: { b: "c" } })
|
60
60
|
end
|
data/dev/console
CHANGED
@@ -1,23 +1,8 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
1
|
+
#!/usr/bin/env bundle exec ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'irb'
|
5
|
-
|
6
|
-
|
7
|
-
gemspec = Dir.glob(File.expand_path("../../*.gemspec", __FILE__)).first
|
8
|
-
spec = Gem::Specification.load(gemspec)
|
9
|
-
|
10
|
-
# Add the require paths to the $LOAD_PATH
|
11
|
-
spec.require_paths.each do |path|
|
12
|
-
full_path = File.expand_path("../" + path, __dir__)
|
13
|
-
$LOAD_PATH.unshift(full_path) unless $LOAD_PATH.include?(full_path)
|
14
|
-
end
|
15
|
-
|
16
|
-
spec.require_paths.each do |path|
|
17
|
-
require "./lib/prefab-cloud-ruby"
|
18
|
-
end
|
19
|
-
|
20
|
-
SemanticLogger.add_appender(io: $stdout)
|
5
|
+
require_relative "./script_setup"
|
21
6
|
|
22
7
|
if !ENV['PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL']
|
23
8
|
puts "run with PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL=debug (or trace) for more output"
|
data/dev/script_setup.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
|
5
|
+
gemspec = Dir.glob(File.expand_path("../../*.gemspec", __FILE__)).first
|
6
|
+
spec = Gem::Specification.load(gemspec)
|
7
|
+
|
8
|
+
# Add the require paths to the $LOAD_PATH
|
9
|
+
spec.require_paths.each do |path|
|
10
|
+
full_path = File.expand_path("../" + path, __dir__)
|
11
|
+
$LOAD_PATH.unshift(full_path) unless $LOAD_PATH.include?(full_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
spec.require_paths.each do |path|
|
15
|
+
require "./lib/prefab-cloud-ruby"
|
16
|
+
end
|
17
|
+
|
18
|
+
SemanticLogger.add_appender(io: $stdout)
|
data/lib/prefab/config_client.rb
CHANGED
@@ -46,9 +46,9 @@ module Prefab
|
|
46
46
|
stream_lock = Concurrent::ReadWriteLock.new
|
47
47
|
@sse_config_client = Prefab::SSEConfigClient.new(@options, @config_loader)
|
48
48
|
|
49
|
-
@sse_config_client.start do |configs|
|
49
|
+
@sse_config_client.start do |configs, _event, source|
|
50
50
|
stream_lock.with_write_lock do
|
51
|
-
load_configs(configs,
|
51
|
+
load_configs(configs, source)
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
@@ -21,8 +21,10 @@ module Prefab
|
|
21
21
|
|
22
22
|
def evaluate(properties)
|
23
23
|
rtn = evaluate_for_env(@project_env_id, properties) ||
|
24
|
-
|
25
|
-
LOG.trace
|
24
|
+
evaluate_for_env(0, properties)
|
25
|
+
LOG.trace {
|
26
|
+
"Eval Key #{@config.key} Result #{rtn&.reportable_value} with #{properties.to_h}"
|
27
|
+
} unless @config.config_type == :LOG_LEVEL
|
26
28
|
rtn
|
27
29
|
end
|
28
30
|
|
data/lib/prefab/duration.rb
CHANGED
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class JavaScriptStub
|
5
|
+
LOG = Prefab::InternalLogger.new(self)
|
6
|
+
|
7
|
+
def initialize(client = nil)
|
8
|
+
@client = client || Prefab.instance
|
9
|
+
end
|
10
|
+
|
11
|
+
# Generate the JavaScript snippet to bootstrap the client SDK. This will
|
12
|
+
# include the configuration values that are permitted to be sent to the
|
13
|
+
# client SDK.
|
14
|
+
#
|
15
|
+
# If the context provided to the client SDK is not the same as the context
|
16
|
+
# used to generate the configuration values, the client SDK will still
|
17
|
+
# generate a fetch to get the correct values for the context.
|
18
|
+
#
|
19
|
+
# Any keys that could not be resolved will be logged as a warning to the
|
20
|
+
# console.
|
21
|
+
def bootstrap(context)
|
22
|
+
configs, warnings = data(context)
|
23
|
+
<<~JS
|
24
|
+
window._prefabBootstrap = {
|
25
|
+
configs: #{JSON.dump(configs)},
|
26
|
+
context: #{JSON.dump(context)}
|
27
|
+
}
|
28
|
+
#{log_warnings(warnings)}
|
29
|
+
JS
|
30
|
+
end
|
31
|
+
|
32
|
+
# Generate the JavaScript snippet to *replace* the client SDK. Use this to
|
33
|
+
# get `prefab.get` and `prefab.isEnabled` functions on the window object.
|
34
|
+
#
|
35
|
+
# Only use this if you are not using the client SDK and do not need
|
36
|
+
# client-side context.
|
37
|
+
#
|
38
|
+
# Any keys that could not be resolved will be logged as a warning to the
|
39
|
+
# console.
|
40
|
+
def generate_stub(context)
|
41
|
+
configs, warnings = data(context)
|
42
|
+
<<~JS
|
43
|
+
window.prefab = window.prefab || {};
|
44
|
+
window.prefab.config = #{JSON.dump(configs)};
|
45
|
+
window.prefab.get = function(key) {
|
46
|
+
return window.prefab.config[key];
|
47
|
+
};
|
48
|
+
window.prefab.isEnabled = function(key) {
|
49
|
+
return window.prefab.config[key] === true;
|
50
|
+
};
|
51
|
+
#{log_warnings(warnings)}
|
52
|
+
JS
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def underlying_value(value)
|
58
|
+
v = Prefab::ConfigValueUnwrapper.new(value, @client.resolver).unwrap
|
59
|
+
case v
|
60
|
+
when Google::Protobuf::RepeatedField
|
61
|
+
v.to_a
|
62
|
+
when Prefab::Duration
|
63
|
+
v.as_json
|
64
|
+
else
|
65
|
+
v
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def log_warnings(warnings)
|
70
|
+
return '' if warnings.empty?
|
71
|
+
|
72
|
+
<<~JS
|
73
|
+
console.warn('The following keys could not be resolved:', #{JSON.dump(@warnings)});
|
74
|
+
JS
|
75
|
+
end
|
76
|
+
|
77
|
+
def data(context)
|
78
|
+
permitted = {}
|
79
|
+
warnings = []
|
80
|
+
resolver_keys = @client.resolver.keys
|
81
|
+
|
82
|
+
resolver_keys.each do |key|
|
83
|
+
begin
|
84
|
+
config = @client.resolver.raw(key)
|
85
|
+
|
86
|
+
if config.config_type == :FEATURE_FLAG || config.send_to_client_sdk
|
87
|
+
permitted[key] = underlying_value(@client.resolver.get(key, context).value)
|
88
|
+
end
|
89
|
+
rescue StandardError => e
|
90
|
+
LOG.warn("Could not resolve key #{key}: #{e}")
|
91
|
+
|
92
|
+
warnings << key
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
[permitted, warnings]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/prefab/prefab.rb
CHANGED
@@ -78,6 +78,16 @@ module Prefab
|
|
78
78
|
@singleton.is_ff?(key)
|
79
79
|
end
|
80
80
|
|
81
|
+
def self.bootstrap_javascript(context)
|
82
|
+
ensure_initialized
|
83
|
+
Prefab::JavaScriptStub.new(@singleton).bootstrap(context)
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.generate_javascript_stub(context)
|
87
|
+
ensure_initialized
|
88
|
+
Prefab::JavaScriptStub.new(@singleton).generate_stub(context)
|
89
|
+
end
|
90
|
+
|
81
91
|
private
|
82
92
|
|
83
93
|
def self.ensure_initialized(key = nil)
|
@@ -2,15 +2,33 @@
|
|
2
2
|
|
3
3
|
module Prefab
|
4
4
|
class SSEConfigClient
|
5
|
-
|
6
|
-
|
5
|
+
class Options
|
6
|
+
attr_reader :sse_read_timeout, :seconds_between_new_connection,
|
7
|
+
:sse_default_reconnect_time, :sleep_delay_for_new_connection_check,
|
8
|
+
:errors_to_close_connection
|
9
|
+
|
10
|
+
def initialize(sse_read_timeout: 300,
|
11
|
+
seconds_between_new_connection: 5,
|
12
|
+
sleep_delay_for_new_connection_check: 1,
|
13
|
+
sse_default_reconnect_time: SSE::Client::DEFAULT_RECONNECT_TIME,
|
14
|
+
errors_to_close_connection: [HTTP::ConnectionError])
|
15
|
+
@sse_read_timeout = sse_read_timeout
|
16
|
+
@seconds_between_new_connection = seconds_between_new_connection
|
17
|
+
@sse_default_reconnect_time = sse_default_reconnect_time
|
18
|
+
@sleep_delay_for_new_connection_check = sleep_delay_for_new_connection_check
|
19
|
+
@errors_to_close_connection = errors_to_close_connection
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
7
23
|
AUTH_USER = 'authuser'
|
8
24
|
LOG = Prefab::InternalLogger.new(self)
|
9
25
|
|
10
|
-
def initialize(options,
|
11
|
-
@
|
26
|
+
def initialize(prefab_options, config_loader, options = nil, logger = nil)
|
27
|
+
@prefab_options = prefab_options
|
28
|
+
@options = options || Options.new
|
12
29
|
@config_loader = config_loader
|
13
30
|
@connected = false
|
31
|
+
@logger = logger || LOG
|
14
32
|
end
|
15
33
|
|
16
34
|
def close
|
@@ -19,8 +37,8 @@ module Prefab
|
|
19
37
|
end
|
20
38
|
|
21
39
|
def start(&load_configs)
|
22
|
-
if @
|
23
|
-
|
40
|
+
if @prefab_options.sse_sources.empty?
|
41
|
+
@logger.debug 'No SSE sources configured'
|
24
42
|
return
|
25
43
|
end
|
26
44
|
|
@@ -30,13 +48,14 @@ module Prefab
|
|
30
48
|
|
31
49
|
@retry_thread = Thread.new do
|
32
50
|
loop do
|
33
|
-
sleep
|
51
|
+
sleep @options.sleep_delay_for_new_connection_check
|
34
52
|
|
35
53
|
if @client.closed?
|
36
|
-
closed_count +=
|
54
|
+
closed_count += @options.sleep_delay_for_new_connection_check
|
37
55
|
|
38
|
-
if closed_count >
|
56
|
+
if closed_count > @options.seconds_between_new_connection
|
39
57
|
closed_count = 0
|
58
|
+
@logger.debug 'Reconnecting SSE client'
|
40
59
|
@client = connect(&load_configs)
|
41
60
|
end
|
42
61
|
end
|
@@ -46,22 +65,23 @@ module Prefab
|
|
46
65
|
|
47
66
|
def connect(&load_configs)
|
48
67
|
url = "#{source}/api/v1/sse/config"
|
49
|
-
|
68
|
+
@logger.debug "SSE Streaming Connect to #{url} start_at #{@config_loader.highwater_mark}"
|
50
69
|
|
51
70
|
SSE::Client.new(url,
|
52
71
|
headers: headers,
|
53
|
-
read_timeout:
|
72
|
+
read_timeout: @options.sse_read_timeout,
|
73
|
+
reconnect_time: @options.sse_default_reconnect_time,
|
54
74
|
logger: Prefab::InternalLogger.new(SSE::Client)) do |client|
|
55
75
|
client.on_event do |event|
|
56
76
|
configs = PrefabProto::Configs.decode(Base64.decode64(event.data))
|
57
|
-
load_configs.call(configs, :sse)
|
77
|
+
load_configs.call(configs, event, :sse)
|
58
78
|
end
|
59
79
|
|
60
80
|
client.on_error do |error|
|
61
|
-
|
81
|
+
@logger.error "SSE Streaming Error: #{error.inspect} for url #{url}"
|
62
82
|
|
63
|
-
if error.is_a?(
|
64
|
-
|
83
|
+
if @options.errors_to_close_connection.any? { |klass| error.is_a?(klass) }
|
84
|
+
@logger.debug "Closing SSE connection for url #{url}"
|
65
85
|
client.close
|
66
86
|
end
|
67
87
|
end
|
@@ -69,7 +89,7 @@ module Prefab
|
|
69
89
|
end
|
70
90
|
|
71
91
|
def headers
|
72
|
-
auth = "#{AUTH_USER}:#{@
|
92
|
+
auth = "#{AUTH_USER}:#{@prefab_options.api_key}"
|
73
93
|
auth_string = Base64.strict_encode64(auth)
|
74
94
|
return {
|
75
95
|
'x-prefab-start-at-id' => @config_loader.highwater_mark,
|
@@ -82,11 +102,11 @@ module Prefab
|
|
82
102
|
def source
|
83
103
|
@source_index = @source_index.nil? ? 0 : @source_index + 1
|
84
104
|
|
85
|
-
if @source_index >= @
|
105
|
+
if @source_index >= @prefab_options.sse_sources.size
|
86
106
|
@source_index = 0
|
87
107
|
end
|
88
108
|
|
89
|
-
return @
|
109
|
+
return @prefab_options.sse_sources[@source_index]
|
90
110
|
end
|
91
111
|
end
|
92
112
|
end
|
data/lib/prefab-cloud-ruby.rb
CHANGED
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.8.
|
5
|
+
# stub: prefab-cloud-ruby 1.8.3 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "prefab-cloud-ruby".freeze
|
9
|
-
s.version = "1.8.
|
9
|
+
s.version = "1.8.3"
|
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-09-
|
14
|
+
s.date = "2024-09-16"
|
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 = [
|
@@ -37,6 +37,7 @@ Gem::Specification.new do |s|
|
|
37
37
|
"dev/allocation_stats",
|
38
38
|
"dev/benchmark",
|
39
39
|
"dev/console",
|
40
|
+
"dev/script_setup.rb",
|
40
41
|
"lib/prefab-cloud-ruby.rb",
|
41
42
|
"lib/prefab/client.rb",
|
42
43
|
"lib/prefab/config_client.rb",
|
@@ -65,6 +66,7 @@ Gem::Specification.new do |s|
|
|
65
66
|
"lib/prefab/feature_flag_client.rb",
|
66
67
|
"lib/prefab/http_connection.rb",
|
67
68
|
"lib/prefab/internal_logger.rb",
|
69
|
+
"lib/prefab/javascript_stub.rb",
|
68
70
|
"lib/prefab/local_config_parser.rb",
|
69
71
|
"lib/prefab/log_path_aggregator.rb",
|
70
72
|
"lib/prefab/logger_client.rb",
|
@@ -107,6 +109,7 @@ Gem::Specification.new do |s|
|
|
107
109
|
"test/test_helper.rb",
|
108
110
|
"test/test_integration.rb",
|
109
111
|
"test/test_internal_logger.rb",
|
112
|
+
"test/test_javascript_stub.rb",
|
110
113
|
"test/test_local_config_parser.rb",
|
111
114
|
"test/test_log_path_aggregator.rb",
|
112
115
|
"test/test_logger.rb",
|
@@ -134,7 +137,8 @@ Gem::Specification.new do |s|
|
|
134
137
|
s.add_runtime_dependency(%q<ld-eventsource>.freeze, [">= 0"])
|
135
138
|
s.add_runtime_dependency(%q<uuid>.freeze, [">= 0"])
|
136
139
|
s.add_runtime_dependency(%q<activesupport>.freeze, [">= 4"])
|
137
|
-
s.add_runtime_dependency(%q<semantic_logger>.freeze, ["
|
140
|
+
s.add_runtime_dependency(%q<semantic_logger>.freeze, ["!= 4.16.0"])
|
141
|
+
s.add_development_dependency(%q<allocation_stats>.freeze, [">= 0"])
|
138
142
|
s.add_development_dependency(%q<benchmark-ips>.freeze, [">= 0"])
|
139
143
|
s.add_development_dependency(%q<bundler>.freeze, [">= 0"])
|
140
144
|
s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.4.9"])
|
@@ -148,7 +152,8 @@ Gem::Specification.new do |s|
|
|
148
152
|
s.add_dependency(%q<ld-eventsource>.freeze, [">= 0"])
|
149
153
|
s.add_dependency(%q<uuid>.freeze, [">= 0"])
|
150
154
|
s.add_dependency(%q<activesupport>.freeze, [">= 4"])
|
151
|
-
s.add_dependency(%q<semantic_logger>.freeze, ["
|
155
|
+
s.add_dependency(%q<semantic_logger>.freeze, ["!= 4.16.0"])
|
156
|
+
s.add_dependency(%q<allocation_stats>.freeze, [">= 0"])
|
152
157
|
s.add_dependency(%q<benchmark-ips>.freeze, [">= 0"])
|
153
158
|
s.add_dependency(%q<bundler>.freeze, [">= 0"])
|
154
159
|
s.add_dependency(%q<juwelier>.freeze, ["~> 2.4.9"])
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class JavascriptStubTest < Minitest::Test
|
6
|
+
PROJECT_ENV_ID = 1
|
7
|
+
DEFAULT_VALUE = 'default_value'
|
8
|
+
DEFAULT_VALUE_CONFIG = PrefabProto::ConfigValue.new(string: DEFAULT_VALUE)
|
9
|
+
TRUE_CONFIG = PrefabProto::ConfigValue.new(bool: true)
|
10
|
+
FALSE_CONFIG = PrefabProto::ConfigValue.new(bool: false)
|
11
|
+
DEFAULT_ROW = PrefabProto::ConfigRow.new(
|
12
|
+
values: [
|
13
|
+
PrefabProto::ConditionalValue.new(value: DEFAULT_VALUE_CONFIG)
|
14
|
+
]
|
15
|
+
)
|
16
|
+
|
17
|
+
def setup
|
18
|
+
super
|
19
|
+
|
20
|
+
log_level = PrefabProto::Config.new(
|
21
|
+
id: 999,
|
22
|
+
key: 'log-level',
|
23
|
+
config_type: PrefabProto::ConfigType::LOG_LEVEL,
|
24
|
+
rows: [
|
25
|
+
PrefabProto::ConfigRow.new(
|
26
|
+
values: [
|
27
|
+
PrefabProto::ConditionalValue.new(
|
28
|
+
criteria: [],
|
29
|
+
value: PrefabProto::ConfigValue.new(log_level: PrefabProto::LogLevel::INFO)
|
30
|
+
)
|
31
|
+
]
|
32
|
+
)
|
33
|
+
]
|
34
|
+
)
|
35
|
+
|
36
|
+
config_for_sdk = PrefabProto::Config.new(
|
37
|
+
id: 123,
|
38
|
+
key: 'basic-config',
|
39
|
+
config_type: PrefabProto::ConfigType::CONFIG,
|
40
|
+
rows: [DEFAULT_ROW],
|
41
|
+
send_to_client_sdk: true
|
42
|
+
)
|
43
|
+
|
44
|
+
config_not_for_sdk = PrefabProto::Config.new(
|
45
|
+
id: 787,
|
46
|
+
key: 'non-sdk-basic-config',
|
47
|
+
config_type: PrefabProto::ConfigType::CONFIG,
|
48
|
+
rows: [DEFAULT_ROW]
|
49
|
+
)
|
50
|
+
|
51
|
+
ff = PrefabProto::Config.new(
|
52
|
+
id: 456,
|
53
|
+
key: 'feature-flag',
|
54
|
+
config_type: PrefabProto::ConfigType::FEATURE_FLAG,
|
55
|
+
rows: [
|
56
|
+
PrefabProto::ConfigRow.new(
|
57
|
+
values: [
|
58
|
+
PrefabProto::ConditionalValue.new(
|
59
|
+
value: TRUE_CONFIG,
|
60
|
+
criteria: [
|
61
|
+
PrefabProto::Criterion.new(
|
62
|
+
operator: PrefabProto::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
|
63
|
+
value_to_match: string_list(['hotmail.com', 'gmail.com']),
|
64
|
+
property_name: 'user.email'
|
65
|
+
)
|
66
|
+
]
|
67
|
+
),
|
68
|
+
PrefabProto::ConditionalValue.new(value: FALSE_CONFIG)
|
69
|
+
]
|
70
|
+
)
|
71
|
+
]
|
72
|
+
)
|
73
|
+
|
74
|
+
@client = new_client(
|
75
|
+
config: [log_level, config_for_sdk, config_not_for_sdk, ff],
|
76
|
+
project_env_id: PROJECT_ENV_ID,
|
77
|
+
collect_evaluation_summaries: true,
|
78
|
+
prefab_config_override_dir: '/tmp',
|
79
|
+
prefab_config_classpath_dir: '/tmp',
|
80
|
+
context_upload_mode: :periodic_example,
|
81
|
+
allow_telemetry_in_local_mode: true
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_bootstrap
|
86
|
+
result = Prefab::JavaScriptStub.new(@client).bootstrap({})
|
87
|
+
|
88
|
+
assert_equal %(
|
89
|
+
window._prefabBootstrap = {
|
90
|
+
configs: {"basic-config":"default_value","feature-flag":false},
|
91
|
+
context: {}
|
92
|
+
}
|
93
|
+
).strip, result.strip
|
94
|
+
|
95
|
+
result = Prefab::JavaScriptStub.new(@client).bootstrap({ user: { email: 'gmail.com' } })
|
96
|
+
|
97
|
+
assert_equal %(
|
98
|
+
window._prefabBootstrap = {
|
99
|
+
configs: {"basic-config":"default_value","feature-flag":true},
|
100
|
+
context: {"user":{"email":"gmail.com"}}
|
101
|
+
}
|
102
|
+
).strip, result.strip
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_generate_stub
|
106
|
+
result = Prefab::JavaScriptStub.new(@client).generate_stub({})
|
107
|
+
|
108
|
+
assert_equal %(
|
109
|
+
window.prefab = window.prefab || {};
|
110
|
+
window.prefab.config = {"basic-config":"default_value","feature-flag":false};
|
111
|
+
window.prefab.get = function(key) {
|
112
|
+
return window.prefab.config[key];
|
113
|
+
};
|
114
|
+
window.prefab.isEnabled = function(key) {
|
115
|
+
return window.prefab.config[key] === true;
|
116
|
+
};
|
117
|
+
).strip, result.strip
|
118
|
+
|
119
|
+
result = Prefab::JavaScriptStub.new(@client).generate_stub({ user: { email: 'gmail.com' } })
|
120
|
+
|
121
|
+
assert_equal %(
|
122
|
+
window.prefab = window.prefab || {};
|
123
|
+
window.prefab.config = {"basic-config":"default_value","feature-flag":true};
|
124
|
+
window.prefab.get = function(key) {
|
125
|
+
return window.prefab.config[key];
|
126
|
+
};
|
127
|
+
window.prefab.isEnabled = function(key) {
|
128
|
+
return window.prefab.config[key] === true;
|
129
|
+
};
|
130
|
+
|
131
|
+
).strip, result.strip
|
132
|
+
end
|
133
|
+
end
|
@@ -1,12 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
4
|
+
require 'webrick'
|
2
5
|
|
3
6
|
class TestSSEConfigClient < Minitest::Test
|
4
7
|
def test_client
|
5
8
|
sources = [
|
6
|
-
|
9
|
+
'https://api.staging-prefab.cloud/'
|
7
10
|
]
|
8
11
|
|
9
|
-
options = Prefab::Options.new(sources: sources, api_key: ENV
|
12
|
+
options = Prefab::Options.new(sources: sources, api_key: ENV.fetch('PREFAB_INTEGRATION_TEST_API_KEY', nil))
|
10
13
|
|
11
14
|
config_loader = OpenStruct.new(highwater_mark: 4)
|
12
15
|
|
@@ -17,7 +20,7 @@ class TestSSEConfigClient < Minitest::Test
|
|
17
20
|
result = nil
|
18
21
|
|
19
22
|
# fake our load_configs block
|
20
|
-
client.start do |c, source|
|
23
|
+
client.start do |c, _event, source|
|
21
24
|
result = c
|
22
25
|
assert_equal :sse, source
|
23
26
|
end
|
@@ -31,35 +34,176 @@ class TestSSEConfigClient < Minitest::Test
|
|
31
34
|
|
32
35
|
def test_failing_over
|
33
36
|
sources = [
|
34
|
-
|
35
|
-
|
37
|
+
'https://does.not.exist.staging-prefab.cloud/',
|
38
|
+
'https://api.staging-prefab.cloud/'
|
36
39
|
]
|
37
40
|
|
38
|
-
|
41
|
+
prefab_options = Prefab::Options.new(sources: sources, api_key: ENV.fetch('PREFAB_INTEGRATION_TEST_API_KEY', nil))
|
39
42
|
|
40
43
|
config_loader = OpenStruct.new(highwater_mark: 4)
|
41
44
|
|
42
|
-
|
45
|
+
sse_options = Prefab::SSEConfigClient::Options.new(seconds_between_new_connection: 0.01, sleep_delay_for_new_connection_check: 0.01)
|
46
|
+
|
47
|
+
client = Prefab::SSEConfigClient.new(prefab_options, config_loader, sse_options)
|
43
48
|
|
44
49
|
assert_equal 4, client.headers['x-prefab-start-at-id']
|
45
50
|
|
46
51
|
result = nil
|
47
52
|
|
48
53
|
# fake our load_configs block
|
49
|
-
client.start do |c, source|
|
54
|
+
client.start do |c, _event, source|
|
50
55
|
result = c
|
51
56
|
assert_equal :sse, source
|
52
57
|
end
|
53
58
|
|
54
|
-
wait_for -> { !result.nil? }
|
59
|
+
wait_for -> { !result.nil? }
|
55
60
|
|
56
61
|
assert result.configs.size > 30
|
57
62
|
ensure
|
58
63
|
client.close
|
59
64
|
|
60
65
|
assert_logged [
|
61
|
-
|
62
|
-
/HTTP::ConnectionError
|
66
|
+
%r{failed to connect: .*https://does.not.exist},
|
67
|
+
/HTTP::ConnectionError/
|
63
68
|
]
|
64
69
|
end
|
70
|
+
|
71
|
+
def test_recovering_from_disconnection
|
72
|
+
server, = start_webrick_server(4567, DisconnectingEndpoint)
|
73
|
+
|
74
|
+
config_loader = OpenStruct.new(highwater_mark: 4)
|
75
|
+
|
76
|
+
prefab_options = OpenStruct.new(sse_sources: ['http://localhost:4567'], api_key: 'test')
|
77
|
+
last_event_id = nil
|
78
|
+
client = nil
|
79
|
+
|
80
|
+
begin
|
81
|
+
Thread.new do
|
82
|
+
server.start
|
83
|
+
end
|
84
|
+
|
85
|
+
sse_options = Prefab::SSEConfigClient::Options.new(
|
86
|
+
sse_default_reconnect_time: 0.1
|
87
|
+
)
|
88
|
+
client = Prefab::SSEConfigClient.new(prefab_options, config_loader, sse_options)
|
89
|
+
|
90
|
+
client.start do |_configs, event, _source|
|
91
|
+
last_event_id = event.id.to_i
|
92
|
+
end
|
93
|
+
|
94
|
+
wait_for -> { last_event_id && last_event_id > 1 }
|
95
|
+
ensure
|
96
|
+
client.close
|
97
|
+
server.stop
|
98
|
+
|
99
|
+
refute_nil last_event_id, 'Expected to have received an event'
|
100
|
+
assert last_event_id > 1, 'Expected to have received multiple events (indicating a retry)'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_recovering_from_an_error
|
105
|
+
log_output = StringIO.new
|
106
|
+
logger = Logger.new(log_output)
|
107
|
+
|
108
|
+
server, = start_webrick_server(4568, ErroringEndpoint)
|
109
|
+
|
110
|
+
config_loader = OpenStruct.new(highwater_mark: 4)
|
111
|
+
|
112
|
+
prefab_options = OpenStruct.new(sse_sources: ['http://localhost:4568'], api_key: 'test')
|
113
|
+
last_event_id = nil
|
114
|
+
client = nil
|
115
|
+
|
116
|
+
begin
|
117
|
+
Thread.new do
|
118
|
+
server.start
|
119
|
+
end
|
120
|
+
|
121
|
+
sse_options = Prefab::SSEConfigClient::Options.new(
|
122
|
+
sse_default_reconnect_time: 0.1,
|
123
|
+
seconds_between_new_connection: 0.1,
|
124
|
+
sleep_delay_for_new_connection_check: 0.1,
|
125
|
+
errors_to_close_connection: [SSE::Errors::HTTPStatusError]
|
126
|
+
)
|
127
|
+
client = Prefab::SSEConfigClient.new(prefab_options, config_loader, sse_options, logger)
|
128
|
+
|
129
|
+
client.start do |_configs, event, _source|
|
130
|
+
last_event_id = event.id.to_i
|
131
|
+
end
|
132
|
+
|
133
|
+
wait_for -> { last_event_id && last_event_id > 2 }
|
134
|
+
ensure
|
135
|
+
server.stop
|
136
|
+
client.close
|
137
|
+
|
138
|
+
refute_nil last_event_id, 'Expected to have received an event'
|
139
|
+
assert last_event_id > 2, 'Expected to have received multiple events (indicating a reconnect)'
|
140
|
+
end
|
141
|
+
|
142
|
+
log_lines = log_output.string.split("\n")
|
143
|
+
|
144
|
+
assert_match(/SSE Streaming Connect/, log_lines[0])
|
145
|
+
assert_match(/SSE Streaming Error/, log_lines[1], 'Expected to have logged an error. If this starts failing after an ld-eventsource upgrade, you might need to tweak NUMBER_OF_FAILURES below')
|
146
|
+
assert_match(/Closing SSE connection/, log_lines[2])
|
147
|
+
assert_match(/Reconnecting SSE client/, log_lines[3])
|
148
|
+
assert_match(/SSE Streaming Connect/, log_lines[4])
|
149
|
+
end
|
150
|
+
|
151
|
+
def start_webrick_server(port, endpoint_class)
|
152
|
+
log_string = StringIO.new
|
153
|
+
logger = WEBrick::Log.new(log_string)
|
154
|
+
server = WEBrick::HTTPServer.new(Port: port, Logger: logger, AccessLog: [])
|
155
|
+
server.mount '/api/v1/sse/config', endpoint_class
|
156
|
+
|
157
|
+
[server, log_string]
|
158
|
+
end
|
159
|
+
|
160
|
+
module SharedEndpointLogic
|
161
|
+
def event_id
|
162
|
+
@@event_id ||= 0
|
163
|
+
@@event_id += 1
|
164
|
+
end
|
165
|
+
|
166
|
+
def setup_response(response)
|
167
|
+
response.status = 200
|
168
|
+
response['Content-Type'] = 'text/event-stream'
|
169
|
+
response['Cache-Control'] = 'no-cache'
|
170
|
+
response['Connection'] = 'keep-alive'
|
171
|
+
|
172
|
+
response.chunked = false
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
class DisconnectingEndpoint < WEBrick::HTTPServlet::AbstractServlet
|
177
|
+
include SharedEndpointLogic
|
178
|
+
|
179
|
+
def do_GET(_request, response)
|
180
|
+
setup_response(response)
|
181
|
+
|
182
|
+
output = response.body
|
183
|
+
|
184
|
+
output << "id: #{event_id}\n"
|
185
|
+
output << "event: message\n"
|
186
|
+
output << "data: CmYIu8fh4YaO0x4QZBo0bG9nLWxldmVsLmNsb3VkLnByZWZhYi5zZXJ2ZXIubG9nZ2luZy5FdmVudFByb2Nlc3NvciIfCAESG2phbWVzLmtlYmluZ2VyQHByZWZhYi5jbG91ZDgGSAkSDQhkELvH4eGGjtMeGGU=\n\n"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class ErroringEndpoint < WEBrick::HTTPServlet::AbstractServlet
|
191
|
+
include SharedEndpointLogic
|
192
|
+
NUMBER_OF_FAILURES = 5
|
193
|
+
|
194
|
+
def do_GET(_request, response)
|
195
|
+
setup_response(response)
|
196
|
+
|
197
|
+
output = response.body
|
198
|
+
|
199
|
+
output << "id: #{event_id}\n"
|
200
|
+
|
201
|
+
if event_id < NUMBER_OF_FAILURES
|
202
|
+
raise 'ErroringEndpoint' # This manifests as an SSE::Errors::HTTPStatusError
|
203
|
+
end
|
204
|
+
|
205
|
+
output << "event: message\n"
|
206
|
+
output << "data: CmYIu8fh4YaO0x4QZBo0bG9nLWxldmVsLmNsb3VkLnByZWZhYi5zZXJ2ZXIubG9nZ2luZy5FdmVudFByb2Nlc3NvciIfCAESG2phbWVzLmtlYmluZ2VyQHByZWZhYi5jbG91ZDgGSAkSDQhkELvH4eGGjtMeGGU=\n\n"
|
207
|
+
end
|
208
|
+
end
|
65
209
|
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.8.
|
4
|
+
version: 1.8.3
|
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-09-
|
11
|
+
date: 2024-09-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -116,12 +116,26 @@ dependencies:
|
|
116
116
|
version: '4'
|
117
117
|
- !ruby/object:Gem::Dependency
|
118
118
|
name: semantic_logger
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "!="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: 4.16.0
|
124
|
+
type: :runtime
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "!="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 4.16.0
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: allocation_stats
|
119
133
|
requirement: !ruby/object:Gem::Requirement
|
120
134
|
requirements:
|
121
135
|
- - ">="
|
122
136
|
- !ruby/object:Gem::Version
|
123
137
|
version: '0'
|
124
|
-
type: :
|
138
|
+
type: :development
|
125
139
|
prerelease: false
|
126
140
|
version_requirements: !ruby/object:Gem::Requirement
|
127
141
|
requirements:
|
@@ -224,6 +238,7 @@ files:
|
|
224
238
|
- dev/allocation_stats
|
225
239
|
- dev/benchmark
|
226
240
|
- dev/console
|
241
|
+
- dev/script_setup.rb
|
227
242
|
- lib/prefab-cloud-ruby.rb
|
228
243
|
- lib/prefab/client.rb
|
229
244
|
- lib/prefab/config_client.rb
|
@@ -252,6 +267,7 @@ files:
|
|
252
267
|
- lib/prefab/feature_flag_client.rb
|
253
268
|
- lib/prefab/http_connection.rb
|
254
269
|
- lib/prefab/internal_logger.rb
|
270
|
+
- lib/prefab/javascript_stub.rb
|
255
271
|
- lib/prefab/local_config_parser.rb
|
256
272
|
- lib/prefab/log_path_aggregator.rb
|
257
273
|
- lib/prefab/logger_client.rb
|
@@ -294,6 +310,7 @@ files:
|
|
294
310
|
- test/test_helper.rb
|
295
311
|
- test/test_integration.rb
|
296
312
|
- test/test_internal_logger.rb
|
313
|
+
- test/test_javascript_stub.rb
|
297
314
|
- test/test_local_config_parser.rb
|
298
315
|
- test/test_log_path_aggregator.rb
|
299
316
|
- test/test_logger.rb
|