prefab-cloud-ruby 0 → 0.0.1
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 +5 -5
- data/.ruby-version +1 -0
- data/Gemfile +9 -22
- data/Gemfile.lock +88 -160
- data/LICENSE.txt +1 -1
- data/Rakefile +14 -14
- data/VERSION +1 -1
- data/lib/prefab/auth_interceptor.rb +25 -0
- data/lib/prefab/client.rb +34 -139
- data/lib/prefab/config_client.rb +23 -275
- data/lib/prefab/config_loader.rb +27 -60
- data/lib/prefab/config_resolver.rb +40 -53
- data/lib/prefab/noop_cache.rb +13 -0
- data/lib/prefab/noop_stats.rb +8 -0
- data/lib/prefab/prefab_pb.rb +39 -0
- data/lib/prefab/prefab_services_pb.rb +37 -0
- data/lib/prefab/ratelimit_client.rb +58 -0
- data/lib/prefab/ratelimit_pb.rb +125 -0
- data/lib/prefab/store.rb +29 -0
- data/lib/prefab_client.rb +35 -0
- metadata +36 -198
- data/.envrc.sample +0 -3
- data/.github/workflows/ruby.yml +0 -46
- data/.gitmodules +0 -3
- data/.rubocop.yml +0 -13
- data/.tool-versions +0 -1
- data/CHANGELOG.md +0 -169
- data/CODEOWNERS +0 -1
- data/README.md +0 -94
- data/bin/console +0 -21
- data/compile_protos.sh +0 -18
- data/lib/prefab/config_client_presenter.rb +0 -18
- data/lib/prefab/config_value_unwrapper.rb +0 -115
- data/lib/prefab/config_value_wrapper.rb +0 -18
- data/lib/prefab/context.rb +0 -179
- data/lib/prefab/context_shape.rb +0 -20
- data/lib/prefab/context_shape_aggregator.rb +0 -65
- data/lib/prefab/criteria_evaluator.rb +0 -136
- data/lib/prefab/encryption.rb +0 -65
- data/lib/prefab/error.rb +0 -6
- data/lib/prefab/errors/env_var_parse_error.rb +0 -11
- data/lib/prefab/errors/initialization_timeout_error.rb +0 -13
- data/lib/prefab/errors/invalid_api_key_error.rb +0 -19
- data/lib/prefab/errors/missing_default_error.rb +0 -13
- data/lib/prefab/errors/missing_env_var_error.rb +0 -11
- data/lib/prefab/errors/uninitialized_error.rb +0 -13
- data/lib/prefab/evaluation.rb +0 -52
- data/lib/prefab/evaluation_summary_aggregator.rb +0 -87
- data/lib/prefab/example_contexts_aggregator.rb +0 -78
- data/lib/prefab/exponential_backoff.rb +0 -21
- data/lib/prefab/feature_flag_client.rb +0 -42
- data/lib/prefab/http_connection.rb +0 -41
- data/lib/prefab/internal_logger.rb +0 -16
- data/lib/prefab/local_config_parser.rb +0 -151
- data/lib/prefab/log_path_aggregator.rb +0 -69
- data/lib/prefab/logger_client.rb +0 -264
- data/lib/prefab/murmer3.rb +0 -50
- data/lib/prefab/options.rb +0 -208
- data/lib/prefab/periodic_sync.rb +0 -69
- data/lib/prefab/prefab.rb +0 -56
- data/lib/prefab/rate_limit_cache.rb +0 -41
- data/lib/prefab/resolved_config_presenter.rb +0 -86
- data/lib/prefab/time_helpers.rb +0 -7
- data/lib/prefab/weighted_value_resolver.rb +0 -42
- data/lib/prefab/yaml_config_parser.rb +0 -34
- data/lib/prefab-cloud-ruby.rb +0 -57
- data/lib/prefab_pb.rb +0 -93
- data/prefab-cloud-ruby.gemspec +0 -155
- data/test/.prefab.default.config.yaml +0 -2
- data/test/.prefab.unit_tests.config.yaml +0 -28
- data/test/integration_test.rb +0 -150
- data/test/integration_test_helpers.rb +0 -151
- data/test/support/common_helpers.rb +0 -180
- data/test/support/mock_base_client.rb +0 -42
- data/test/support/mock_config_client.rb +0 -19
- data/test/support/mock_config_loader.rb +0 -1
- data/test/test_client.rb +0 -444
- data/test/test_config_client.rb +0 -109
- data/test/test_config_loader.rb +0 -117
- data/test/test_config_resolver.rb +0 -430
- data/test/test_config_value_unwrapper.rb +0 -224
- data/test/test_config_value_wrapper.rb +0 -42
- data/test/test_context.rb +0 -203
- data/test/test_context_shape.rb +0 -50
- data/test/test_context_shape_aggregator.rb +0 -147
- data/test/test_criteria_evaluator.rb +0 -726
- data/test/test_encryption.rb +0 -16
- data/test/test_evaluation_summary_aggregator.rb +0 -162
- data/test/test_example_contexts_aggregator.rb +0 -238
- data/test/test_exponential_backoff.rb +0 -18
- data/test/test_feature_flag_client.rb +0 -48
- data/test/test_helper.rb +0 -17
- data/test/test_integration.rb +0 -58
- data/test/test_local_config_parser.rb +0 -147
- data/test/test_log_path_aggregator.rb +0 -62
- data/test/test_logger.rb +0 -621
- data/test/test_logger_initialization.rb +0 -12
- data/test/test_options.rb +0 -75
- data/test/test_prefab.rb +0 -12
- data/test/test_rate_limit_cache.rb +0 -44
- data/test/test_weighted_value_resolver.rb +0 -71
@@ -1,65 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'periodic_sync'
|
4
|
-
|
5
|
-
module Prefab
|
6
|
-
class ContextShapeAggregator
|
7
|
-
include Prefab::PeriodicSync
|
8
|
-
|
9
|
-
LOG = Prefab::InternalLogger.new(ContextShapeAggregator)
|
10
|
-
|
11
|
-
attr_reader :data
|
12
|
-
|
13
|
-
def initialize(client:, max_shapes:, sync_interval:)
|
14
|
-
@max_shapes = max_shapes
|
15
|
-
@client = client
|
16
|
-
@name = 'context_shape_aggregator'
|
17
|
-
|
18
|
-
@data = Concurrent::Set.new
|
19
|
-
|
20
|
-
start_periodic_sync(sync_interval)
|
21
|
-
end
|
22
|
-
|
23
|
-
def push(context)
|
24
|
-
return if @data.size >= @max_shapes
|
25
|
-
|
26
|
-
context.contexts.each_pair do |name, name_context|
|
27
|
-
name_context.to_h.each_pair do |key, value|
|
28
|
-
@data.add [name, key, Prefab::ContextShape.field_type_number(value)]
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def prepare_data
|
34
|
-
duped = @data.dup
|
35
|
-
@data.clear
|
36
|
-
|
37
|
-
duped.inject({}) do |acc, (name, key, type)|
|
38
|
-
acc[name] ||= {}
|
39
|
-
acc[name][key] = type
|
40
|
-
acc
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def flush(to_ship, _)
|
47
|
-
pool.post do
|
48
|
-
LOG.debug "Uploading context shapes for #{to_ship.values.size}"
|
49
|
-
|
50
|
-
shapes = PrefabProto::ContextShapes.new(
|
51
|
-
shapes: to_ship.map do |name, shape|
|
52
|
-
PrefabProto::ContextShape.new(
|
53
|
-
name: name,
|
54
|
-
field_types: shape
|
55
|
-
)
|
56
|
-
end
|
57
|
-
)
|
58
|
-
|
59
|
-
result = post('/api/v1/context-shapes', shapes)
|
60
|
-
|
61
|
-
LOG.debug "Uploaded #{to_ship.values.size} shapes: #{result.status}"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
@@ -1,136 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# rubocop:disable Naming/MethodName
|
4
|
-
# We're intentionally keeping the UPCASED method names to match the protobuf
|
5
|
-
# and avoid wasting CPU cycles lowercasing things
|
6
|
-
module Prefab
|
7
|
-
# This class evaluates a config's criteria. `evaluate` returns the value of
|
8
|
-
# the first match based on the provided properties.
|
9
|
-
class CriteriaEvaluator
|
10
|
-
LOG = Prefab::InternalLogger.new(CriteriaEvaluator)
|
11
|
-
NAMESPACE_KEY = 'NAMESPACE'
|
12
|
-
NO_MATCHING_ROWS = [].freeze
|
13
|
-
|
14
|
-
def initialize(config, project_env_id:, resolver:, namespace:, base_client:)
|
15
|
-
@config = config
|
16
|
-
@project_env_id = project_env_id
|
17
|
-
@resolver = resolver
|
18
|
-
@namespace = namespace
|
19
|
-
@base_client = base_client
|
20
|
-
end
|
21
|
-
|
22
|
-
def evaluate(properties)
|
23
|
-
rtn = evaluate_for_env(@project_env_id, properties) ||
|
24
|
-
evaluate_for_env(0, properties)
|
25
|
-
LOG.debug "Eval Key #{@config.key} Result #{rtn&.reportable_value} with #{properties.to_h}" unless @config.config_type == :LOG_LEVEL
|
26
|
-
rtn
|
27
|
-
end
|
28
|
-
|
29
|
-
def all_criteria_match?(conditional_value, props)
|
30
|
-
conditional_value.criteria.all? do |criterion|
|
31
|
-
public_send(criterion.operator, criterion, props)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def IN_SEG(criterion, properties)
|
36
|
-
in_segment?(criterion, properties)
|
37
|
-
end
|
38
|
-
|
39
|
-
def NOT_IN_SEG(criterion, properties)
|
40
|
-
!in_segment?(criterion, properties)
|
41
|
-
end
|
42
|
-
|
43
|
-
def ALWAYS_TRUE(_criterion, _properties)
|
44
|
-
true
|
45
|
-
end
|
46
|
-
|
47
|
-
def PROP_IS_ONE_OF(criterion, properties)
|
48
|
-
matches?(criterion, value_from_properties(criterion, properties), properties)
|
49
|
-
end
|
50
|
-
|
51
|
-
def PROP_IS_NOT_ONE_OF(criterion, properties)
|
52
|
-
!matches?(criterion, value_from_properties(criterion, properties), properties)
|
53
|
-
end
|
54
|
-
|
55
|
-
def PROP_ENDS_WITH_ONE_OF(criterion, properties)
|
56
|
-
prop_ends_with_one_of?(criterion, value_from_properties(criterion, properties))
|
57
|
-
end
|
58
|
-
|
59
|
-
def PROP_DOES_NOT_END_WITH_ONE_OF(criterion, properties)
|
60
|
-
!prop_ends_with_one_of?(criterion, value_from_properties(criterion, properties))
|
61
|
-
end
|
62
|
-
|
63
|
-
def HIERARCHICAL_MATCH(criterion, properties)
|
64
|
-
value = value_from_properties(criterion, properties)
|
65
|
-
value&.start_with?(criterion.value_to_match.string)
|
66
|
-
end
|
67
|
-
|
68
|
-
def IN_INT_RANGE(criterion, properties)
|
69
|
-
value = if criterion.property_name == 'prefab.current-time'
|
70
|
-
Time.now.utc.to_i * 1000
|
71
|
-
else
|
72
|
-
value_from_properties(criterion, properties)
|
73
|
-
end
|
74
|
-
|
75
|
-
value && value >= criterion.value_to_match.int_range.start && value < criterion.value_to_match.int_range.end
|
76
|
-
end
|
77
|
-
|
78
|
-
def value_from_properties(criterion, properties)
|
79
|
-
criterion.property_name == NAMESPACE_KEY ? @namespace : properties.get(criterion.property_name)
|
80
|
-
end
|
81
|
-
|
82
|
-
private
|
83
|
-
|
84
|
-
def evaluate_for_env(env_id, properties)
|
85
|
-
@config.rows.each_with_index do |row, index|
|
86
|
-
next unless row.project_env_id == env_id
|
87
|
-
|
88
|
-
row.values.each_with_index do |conditional_value, value_index|
|
89
|
-
next unless all_criteria_match?(conditional_value, properties)
|
90
|
-
|
91
|
-
return Prefab::Evaluation.new(
|
92
|
-
config: @config,
|
93
|
-
value: conditional_value.value,
|
94
|
-
value_index: value_index,
|
95
|
-
config_row_index: index,
|
96
|
-
context: properties,
|
97
|
-
resolver: @resolver
|
98
|
-
)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
nil
|
103
|
-
end
|
104
|
-
|
105
|
-
def in_segment?(criterion, properties)
|
106
|
-
segment = @resolver.get(criterion.value_to_match.string, properties)
|
107
|
-
|
108
|
-
@base_client.log.info("Segment #{criterion.value_to_match.string} not found") unless segment
|
109
|
-
|
110
|
-
segment&.report_and_return(@base_client.evaluation_summary_aggregator)
|
111
|
-
end
|
112
|
-
|
113
|
-
def matches?(criterion, value, properties)
|
114
|
-
criterion_value_or_values = Prefab::ConfigValueUnwrapper.deepest_value(criterion.value_to_match, @config.key,
|
115
|
-
properties, @resolver).unwrap
|
116
|
-
|
117
|
-
case criterion_value_or_values
|
118
|
-
when Google::Protobuf::RepeatedField
|
119
|
-
# we to_s the value from properties for comparison because the
|
120
|
-
# criterion_value_or_values is a list of strings
|
121
|
-
criterion_value_or_values.include?(value.to_s)
|
122
|
-
else
|
123
|
-
criterion_value_or_values == value
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def prop_ends_with_one_of?(criterion, value)
|
128
|
-
return false unless value
|
129
|
-
|
130
|
-
criterion.value_to_match.string_list.values.any? do |ending|
|
131
|
-
value.end_with?(ending)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
# rubocop:enable Naming/MethodName
|
data/lib/prefab/encryption.rb
DELETED
@@ -1,65 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Prefab
|
4
|
-
class Encryption
|
5
|
-
CIPHER_TYPE = "aes-256-gcm" # 32/12
|
6
|
-
SEPARATOR = "--"
|
7
|
-
|
8
|
-
# Hexadecimal format ensures that generated keys are representable with
|
9
|
-
# plain text
|
10
|
-
#
|
11
|
-
# To convert back to the original string with the desired length:
|
12
|
-
# [ value ].pack("H*")
|
13
|
-
def self.generate_new_hex_key
|
14
|
-
generate_random_key.unpack("H*")[0]
|
15
|
-
end
|
16
|
-
|
17
|
-
def initialize(key_string_hex)
|
18
|
-
@key = [key_string_hex].pack("H*")
|
19
|
-
end
|
20
|
-
|
21
|
-
def encrypt(clear_text)
|
22
|
-
cipher = OpenSSL::Cipher.new(CIPHER_TYPE)
|
23
|
-
cipher.encrypt
|
24
|
-
iv = cipher.random_iv
|
25
|
-
|
26
|
-
# load them into the cipher
|
27
|
-
cipher.key = @key
|
28
|
-
cipher.iv = iv
|
29
|
-
cipher.auth_data = ""
|
30
|
-
|
31
|
-
# encrypt the message
|
32
|
-
encrypted = cipher.update(clear_text)
|
33
|
-
encrypted << cipher.final
|
34
|
-
tag = cipher.auth_tag
|
35
|
-
|
36
|
-
# pack and join
|
37
|
-
[encrypted, iv, tag].map { |p| p.unpack("H*")[0] }.join(SEPARATOR)
|
38
|
-
end
|
39
|
-
|
40
|
-
def decrypt(encrypted_string)
|
41
|
-
unpacked_parts = encrypted_string.split(SEPARATOR).map { |p| [p].pack("H*") }
|
42
|
-
|
43
|
-
cipher = OpenSSL::Cipher.new(CIPHER_TYPE)
|
44
|
-
cipher.decrypt
|
45
|
-
cipher.key = @key
|
46
|
-
cipher.iv = unpacked_parts[1]
|
47
|
-
cipher.auth_tag = unpacked_parts[2]
|
48
|
-
|
49
|
-
# and decrypt it
|
50
|
-
decrypted = cipher.update(unpacked_parts[0])
|
51
|
-
decrypted << cipher.final
|
52
|
-
decrypted
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
def self.generate_random_key
|
58
|
-
SecureRandom.random_bytes(key_length)
|
59
|
-
end
|
60
|
-
|
61
|
-
def self.key_length
|
62
|
-
OpenSSL::Cipher.new(CIPHER_TYPE).key_len
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
data/lib/prefab/error.rb
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Prefab
|
4
|
-
module Errors
|
5
|
-
class EnvVarParseError < Prefab::Error
|
6
|
-
def initialize(env_var, config, env_var_name)
|
7
|
-
super("Evaluating #{config.key} couldn't coerce #{env_var_name} of #{env_var} to #{config.value_type}")
|
8
|
-
end
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Prefab
|
4
|
-
module Errors
|
5
|
-
class InitializationTimeoutError < Prefab::Error
|
6
|
-
def initialize(timeout_sec, key)
|
7
|
-
message = "Prefab couldn't initialize in #{timeout_sec} second timeout. Trying to fetch key `#{key}`."
|
8
|
-
|
9
|
-
super(message)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Prefab
|
4
|
-
module Errors
|
5
|
-
class InvalidApiKeyError < Prefab::Error
|
6
|
-
def initialize(key)
|
7
|
-
if key.nil? || key.empty?
|
8
|
-
message = 'No API key. Set PREFAB_API_KEY env var or use PREFAB_DATASOURCES=LOCAL_ONLY'
|
9
|
-
|
10
|
-
super(message)
|
11
|
-
else
|
12
|
-
message = "Your API key format is invalid. Expecting something like 123-development-yourapikey-SDK. You provided `#{key}`"
|
13
|
-
|
14
|
-
super(message)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Prefab
|
4
|
-
module Errors
|
5
|
-
class MissingDefaultError < Prefab::Error
|
6
|
-
def initialize(key)
|
7
|
-
message = "No value found for key '#{key}' and no default was provided.\n\nIf you'd prefer returning `nil` rather than raising when this occurs, modify the `on_no_default` value you provide in your Prefab::Options."
|
8
|
-
|
9
|
-
super(message)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
data/lib/prefab/evaluation.rb
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Prefab
|
4
|
-
# Records the result of evaluating a config's criteria and forensics for reporting
|
5
|
-
class Evaluation
|
6
|
-
attr_reader :value
|
7
|
-
|
8
|
-
def initialize(config:, value:, value_index:, config_row_index:, context:, resolver:)
|
9
|
-
@config = config
|
10
|
-
@value = value
|
11
|
-
@value_index = value_index
|
12
|
-
@config_row_index = config_row_index
|
13
|
-
@context = context
|
14
|
-
@resolver = resolver
|
15
|
-
end
|
16
|
-
|
17
|
-
def unwrapped_value
|
18
|
-
deepest_value.unwrap
|
19
|
-
end
|
20
|
-
|
21
|
-
def reportable_value
|
22
|
-
deepest_value.reportable_value
|
23
|
-
end
|
24
|
-
|
25
|
-
def report_and_return(evaluation_summary_aggregator)
|
26
|
-
report(evaluation_summary_aggregator)
|
27
|
-
|
28
|
-
unwrapped_value
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
def report(evaluation_summary_aggregator)
|
34
|
-
return if @config.config_type == :LOG_LEVEL
|
35
|
-
evaluation_summary_aggregator&.record(
|
36
|
-
config_key: @config.key,
|
37
|
-
config_type: @config.config_type,
|
38
|
-
counter: {
|
39
|
-
config_id: @config.id,
|
40
|
-
config_row_index: @config_row_index,
|
41
|
-
conditional_value_index: @value_index,
|
42
|
-
selected_value: deepest_value.reportable_wrapped_value,
|
43
|
-
weighted_value_index: deepest_value.weighted_value_index,
|
44
|
-
selected_index: nil # TODO
|
45
|
-
})
|
46
|
-
end
|
47
|
-
|
48
|
-
def deepest_value
|
49
|
-
@deepest_value ||= Prefab::ConfigValueUnwrapper.deepest_value(@value, @config, @context, @resolver)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
@@ -1,87 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'periodic_sync'
|
4
|
-
|
5
|
-
module Prefab
|
6
|
-
# This class aggregates the number of times each config is evaluated, and
|
7
|
-
# details about how the config is evaluated This data is reported to the
|
8
|
-
# server at a regular interval defined by `sync_interval`.
|
9
|
-
class EvaluationSummaryAggregator
|
10
|
-
include Prefab::PeriodicSync
|
11
|
-
|
12
|
-
LOG = Prefab::InternalLogger.new(EvaluationSummaryAggregator)
|
13
|
-
|
14
|
-
attr_reader :data
|
15
|
-
|
16
|
-
def initialize(client:, max_keys:, sync_interval:)
|
17
|
-
@client = client
|
18
|
-
@max_keys = max_keys
|
19
|
-
@name = 'evaluation_summary_aggregator'
|
20
|
-
|
21
|
-
@data = Concurrent::Hash.new
|
22
|
-
|
23
|
-
start_periodic_sync(sync_interval)
|
24
|
-
end
|
25
|
-
|
26
|
-
def record(config_key:, config_type:, counter:)
|
27
|
-
return if @data.size >= @max_keys
|
28
|
-
|
29
|
-
key = [config_key, config_type]
|
30
|
-
@data[key] ||= Concurrent::Hash.new
|
31
|
-
|
32
|
-
@data[key][counter] ||= 0
|
33
|
-
@data[key][counter] += 1
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def counter_proto(counter, count)
|
39
|
-
PrefabProto::ConfigEvaluationCounter.new(
|
40
|
-
config_id: counter[:config_id],
|
41
|
-
selected_index: counter[:selected_index],
|
42
|
-
config_row_index: counter[:config_row_index],
|
43
|
-
conditional_value_index: counter[:conditional_value_index],
|
44
|
-
weighted_value_index: counter[:weighted_value_index],
|
45
|
-
selected_value: counter[:selected_value],
|
46
|
-
count: count
|
47
|
-
)
|
48
|
-
end
|
49
|
-
|
50
|
-
def flush(to_ship, start_at_was)
|
51
|
-
pool.post do
|
52
|
-
LOG.debug "Flushing #{to_ship.size} summaries"
|
53
|
-
|
54
|
-
summaries_proto = PrefabProto::ConfigEvaluationSummaries.new(
|
55
|
-
start: start_at_was,
|
56
|
-
end: Prefab::TimeHelpers.now_in_ms,
|
57
|
-
summaries: summaries(to_ship)
|
58
|
-
)
|
59
|
-
|
60
|
-
result = post('/api/v1/telemetry', events(summaries_proto))
|
61
|
-
|
62
|
-
LOG.debug "Uploaded #{to_ship.size} summaries: #{result.status}"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def events(summaries)
|
67
|
-
event = PrefabProto::TelemetryEvent.new(summaries: summaries)
|
68
|
-
|
69
|
-
PrefabProto::TelemetryEvents.new(
|
70
|
-
instance_hash: @client.instance_hash,
|
71
|
-
events: [event]
|
72
|
-
)
|
73
|
-
end
|
74
|
-
|
75
|
-
def summaries(data)
|
76
|
-
data.map do |(config_key, config_type), counters|
|
77
|
-
counter_protos = counters.map { |counter, count| counter_proto(counter, count) }
|
78
|
-
|
79
|
-
PrefabProto::ConfigEvaluationSummary.new(
|
80
|
-
key: config_key,
|
81
|
-
type: config_type,
|
82
|
-
counters: counter_protos
|
83
|
-
)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
@@ -1,78 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'periodic_sync'
|
4
|
-
|
5
|
-
module Prefab
|
6
|
-
# This class aggregates example contexts. It dedupes based on the
|
7
|
-
# concatenation of the keys of the contexts.
|
8
|
-
#
|
9
|
-
# It shouldn't send the same context more than once per hour.
|
10
|
-
class ExampleContextsAggregator
|
11
|
-
include Prefab::PeriodicSync
|
12
|
-
|
13
|
-
LOG = Prefab::InternalLogger.new(ExampleContextsAggregator)
|
14
|
-
|
15
|
-
attr_reader :data, :cache
|
16
|
-
|
17
|
-
ONE_HOUR = 60 * 60
|
18
|
-
|
19
|
-
def initialize(client:, max_contexts:, sync_interval:)
|
20
|
-
@client = client
|
21
|
-
@max_contexts = max_contexts
|
22
|
-
@name = 'example_contexts_aggregator'
|
23
|
-
|
24
|
-
@data = Concurrent::Array.new
|
25
|
-
@cache = Prefab::RateLimitCache.new(ONE_HOUR)
|
26
|
-
|
27
|
-
start_periodic_sync(sync_interval)
|
28
|
-
end
|
29
|
-
|
30
|
-
def record(contexts)
|
31
|
-
key = contexts.grouped_key
|
32
|
-
|
33
|
-
return unless @data.size < @max_contexts && !@cache.fresh?(key)
|
34
|
-
|
35
|
-
@cache.set(key)
|
36
|
-
|
37
|
-
@data.push(contexts)
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def on_prepare_data
|
43
|
-
@cache.prune
|
44
|
-
end
|
45
|
-
|
46
|
-
def flush(to_ship, _)
|
47
|
-
pool.post do
|
48
|
-
LOG.debug "Flushing #{to_ship.size} examples"
|
49
|
-
|
50
|
-
result = post('/api/v1/telemetry', events(to_ship))
|
51
|
-
|
52
|
-
LOG.debug "Uploaded #{to_ship.size} examples: #{result.status}"
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def example_contexts(to_ship)
|
57
|
-
to_ship.map do |contexts|
|
58
|
-
PrefabProto::ExampleContext.new(
|
59
|
-
timestamp: contexts.seen_at * 1000,
|
60
|
-
contextSet: contexts.slim_proto
|
61
|
-
)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def events(to_ship)
|
66
|
-
event = PrefabProto::TelemetryEvent.new(
|
67
|
-
example_contexts: PrefabProto::ExampleContexts.new(
|
68
|
-
examples: example_contexts(to_ship)
|
69
|
-
)
|
70
|
-
)
|
71
|
-
|
72
|
-
PrefabProto::TelemetryEvents.new(
|
73
|
-
instance_hash: @client.instance_hash,
|
74
|
-
events: [event]
|
75
|
-
)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Prefab
|
4
|
-
# This class implements exponential backoff with a maximum delay.
|
5
|
-
#
|
6
|
-
# This is the default sync interval for aggregators.
|
7
|
-
class ExponentialBackoff
|
8
|
-
def initialize(max_delay:, initial_delay: 2, multiplier: 2)
|
9
|
-
@initial_delay = initial_delay
|
10
|
-
@max_delay = max_delay
|
11
|
-
@multiplier = multiplier
|
12
|
-
@delay = initial_delay
|
13
|
-
end
|
14
|
-
|
15
|
-
def call
|
16
|
-
delay = @delay
|
17
|
-
@delay = [@delay * @multiplier, @max_delay].min
|
18
|
-
delay
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,42 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Prefab
|
4
|
-
class FeatureFlagClient
|
5
|
-
def initialize(base_client)
|
6
|
-
@base_client = base_client
|
7
|
-
end
|
8
|
-
|
9
|
-
def feature_is_on?(feature_name)
|
10
|
-
feature_is_on_for?(feature_name, {})
|
11
|
-
end
|
12
|
-
|
13
|
-
def feature_is_on_for?(feature_name, properties)
|
14
|
-
variant = @base_client.config_client.get(feature_name, false, properties)
|
15
|
-
|
16
|
-
is_on?(variant)
|
17
|
-
end
|
18
|
-
|
19
|
-
def get(feature_name, properties, default: false)
|
20
|
-
value = _get(feature_name, properties)
|
21
|
-
|
22
|
-
value.nil? ? default : value
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def _get(feature_name, properties)
|
28
|
-
@base_client.config_client.get(feature_name, nil, properties)
|
29
|
-
end
|
30
|
-
|
31
|
-
def is_on?(variant)
|
32
|
-
return false if variant.nil?
|
33
|
-
|
34
|
-
return variant if variant == !!variant
|
35
|
-
|
36
|
-
variant.bool
|
37
|
-
rescue StandardError
|
38
|
-
@base_client.log.info("is_on? methods only work for boolean feature flags variants. This feature flags variant is '#{variant}'. Returning false")
|
39
|
-
false
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Prefab
|
4
|
-
class HttpConnection
|
5
|
-
AUTH_USER = 'authuser'
|
6
|
-
PROTO_HEADERS = {
|
7
|
-
'Content-Type' => 'application/x-protobuf',
|
8
|
-
'Accept' => 'application/x-protobuf',
|
9
|
-
'X-PrefabCloud-Client-Version' => "prefab-cloud-ruby-#{Prefab::VERSION}"
|
10
|
-
}.freeze
|
11
|
-
|
12
|
-
def initialize(api_root, api_key)
|
13
|
-
@api_root = api_root
|
14
|
-
@api_key = api_key
|
15
|
-
end
|
16
|
-
|
17
|
-
def get(path)
|
18
|
-
connection(PROTO_HEADERS).get(path)
|
19
|
-
end
|
20
|
-
|
21
|
-
def post(path, body)
|
22
|
-
connection(PROTO_HEADERS).post(path, body.to_proto)
|
23
|
-
end
|
24
|
-
|
25
|
-
def connection(headers = {})
|
26
|
-
if Faraday::VERSION[0].to_i >= 2
|
27
|
-
Faraday.new(@api_root) do |conn|
|
28
|
-
conn.request :authorization, :basic, AUTH_USER, @api_key
|
29
|
-
|
30
|
-
conn.headers.merge!(headers)
|
31
|
-
end
|
32
|
-
else
|
33
|
-
Faraday.new(@api_root) do |conn|
|
34
|
-
conn.request :basic_auth, AUTH_USER, @api_key
|
35
|
-
|
36
|
-
conn.headers.merge!(headers)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|