rox-rollout 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +45 -0
- data/.gitignore +8 -0
- data/Gemfile +6 -0
- data/LICENSE +50 -0
- data/README_DEVELOP.md +19 -0
- data/Rakefile +16 -0
- data/_archive/.document +5 -0
- data/_archive/.rspec +1 -0
- data/_archive/Gemfile +15 -0
- data/_archive/Gemfile.lock +87 -0
- data/_archive/README.md +32 -0
- data/_archive/README.rdoc +19 -0
- data/_archive/Rakefile +50 -0
- data/_archive/lib/expr_function_definition.rb +52 -0
- data/_archive/lib/function_definition.rb +48 -0
- data/_archive/lib/function_token.rb +12 -0
- data/_archive/lib/object_extends.rb +12 -0
- data/_archive/lib/ruby_interpreter.rb +292 -0
- data/_archive/lib/stack.rb +48 -0
- data/_archive/lib/string_extends.rb +14 -0
- data/_archive/spec/ruby_interpreter_spec.rb +203 -0
- data/_archive/spec/spec_helper.rb +30 -0
- data/_archive/spec/stack_spec.rb +77 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/e2e/container.rb +38 -0
- data/e2e/custom_props.rb +55 -0
- data/e2e/rox_e2e_test.rb +159 -0
- data/e2e/test_vars.rb +24 -0
- data/lib/rox.rb +5 -0
- data/lib/rox/core/client/buid.rb +82 -0
- data/lib/rox/core/client/device_properties.rb +45 -0
- data/lib/rox/core/client/internal_flags.rb +20 -0
- data/lib/rox/core/client/sdk_settings.rb +5 -0
- data/lib/rox/core/configuration/configuration.rb +5 -0
- data/lib/rox/core/configuration/configuration_fetched_args.rb +23 -0
- data/lib/rox/core/configuration/configuration_fetched_invoker.rb +37 -0
- data/lib/rox/core/configuration/configuration_parser.rb +85 -0
- data/lib/rox/core/configuration/fetcher_error.rb +13 -0
- data/lib/rox/core/configuration/fetcher_status.rb +10 -0
- data/lib/rox/core/configuration/models/experiment_model.rb +5 -0
- data/lib/rox/core/configuration/models/target_group_model.rb +5 -0
- data/lib/rox/core/consts/build.rb +8 -0
- data/lib/rox/core/consts/environment.rb +42 -0
- data/lib/rox/core/consts/property_type.rb +29 -0
- data/lib/rox/core/context/merged_context.rb +16 -0
- data/lib/rox/core/core.rb +131 -0
- data/lib/rox/core/entities/flag.rb +26 -0
- data/lib/rox/core/entities/flag_setter.rb +39 -0
- data/lib/rox/core/entities/variant.rb +56 -0
- data/lib/rox/core/impression/impression_args.rb +5 -0
- data/lib/rox/core/impression/impression_invoker.rb +41 -0
- data/lib/rox/core/impression/models/experiment.rb +14 -0
- data/lib/rox/core/impression/models/reporting_value.rb +5 -0
- data/lib/rox/core/logging/logging.rb +17 -0
- data/lib/rox/core/logging/no_op_logger.rb +11 -0
- data/lib/rox/core/network/configuration_fetch_result.rb +5 -0
- data/lib/rox/core/network/configuration_fetcher.rb +38 -0
- data/lib/rox/core/network/configuration_fetcher_base.rb +25 -0
- data/lib/rox/core/network/configuration_fetcher_roxy.rb +29 -0
- data/lib/rox/core/network/configuration_source.rb +9 -0
- data/lib/rox/core/network/request.rb +46 -0
- data/lib/rox/core/network/request_configuration_builder.rb +48 -0
- data/lib/rox/core/network/request_data.rb +5 -0
- data/lib/rox/core/network/response.rb +16 -0
- data/lib/rox/core/properties/custom_property.rb +18 -0
- data/lib/rox/core/properties/custom_property_type.rb +18 -0
- data/lib/rox/core/properties/device_property.rb +11 -0
- data/lib/rox/core/register/registerer.rb +35 -0
- data/lib/rox/core/reporting/error_reporter.rb +152 -0
- data/lib/rox/core/repositories/custom_property_repository.rb +61 -0
- data/lib/rox/core/repositories/experiment_repository.rb +21 -0
- data/lib/rox/core/repositories/flag_repository.rb +49 -0
- data/lib/rox/core/repositories/roxx/experiments_extensions.rb +82 -0
- data/lib/rox/core/repositories/roxx/properties_extensions.rb +26 -0
- data/lib/rox/core/repositories/target_group_repository.rb +17 -0
- data/lib/rox/core/roxx/core_stack.rb +22 -0
- data/lib/rox/core/roxx/evaluation_result.rb +28 -0
- data/lib/rox/core/roxx/node.rb +11 -0
- data/lib/rox/core/roxx/parser.rb +143 -0
- data/lib/rox/core/roxx/regular_expression_extensions.rb +33 -0
- data/lib/rox/core/roxx/string_tokenizer.rb +68 -0
- data/lib/rox/core/roxx/symbols.rb +14 -0
- data/lib/rox/core/roxx/token_type.rb +30 -0
- data/lib/rox/core/roxx/tokenized_expression.rb +119 -0
- data/lib/rox/core/roxx/value_compare_extensions.rb +137 -0
- data/lib/rox/core/security/signature_verifier.rb +18 -0
- data/lib/rox/core/utils/periodic_task.rb +12 -0
- data/lib/rox/core/utils/type_utils.rb +9 -0
- data/lib/rox/server/client/sdk_settings.rb +5 -0
- data/lib/rox/server/client/server_properties.rb +20 -0
- data/lib/rox/server/flags/rox_flag.rb +19 -0
- data/lib/rox/server/flags/rox_variant.rb +8 -0
- data/lib/rox/server/logging/server_logger.rb +35 -0
- data/lib/rox/server/rox_options.rb +27 -0
- data/lib/rox/server/rox_server.rb +83 -0
- data/lib/rox/version.rb +3 -0
- data/rox.gemspec +29 -0
- metadata +184 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rox/core/entities/variant'
|
2
|
+
|
3
|
+
module Rox
|
4
|
+
module Core
|
5
|
+
class Flag < Variant
|
6
|
+
FLAG_TRUE_VALUE = 'true'.freeze
|
7
|
+
FLAG_FALSE_VALUE = 'false'.freeze
|
8
|
+
|
9
|
+
def initialize(default_value = false)
|
10
|
+
super(default_value ? Flag::FLAG_TRUE_VALUE : Flag::FLAG_FALSE_VALUE, [Flag::FLAG_FALSE_VALUE, Flag::FLAG_TRUE_VALUE])
|
11
|
+
end
|
12
|
+
|
13
|
+
def enabled?(context)
|
14
|
+
value(context) == Flag::FLAG_TRUE_VALUE
|
15
|
+
end
|
16
|
+
|
17
|
+
def enabled(context)
|
18
|
+
yield if enabled?(context)
|
19
|
+
end
|
20
|
+
|
21
|
+
def disabled(context)
|
22
|
+
yield unless enabled?(context)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Rox
|
2
|
+
module Core
|
3
|
+
class FlagSetter
|
4
|
+
def initialize(flag_repository, parser, experiment_repository, impression_invoker)
|
5
|
+
@flag_repository = flag_repository
|
6
|
+
@parser = parser
|
7
|
+
@experiment_repository = experiment_repository
|
8
|
+
@impression_invoker = impression_invoker
|
9
|
+
|
10
|
+
@flag_repository.register_flag_added_handler do |variant|
|
11
|
+
exp = @experiment_repository.experiment_by_flag(variant.name)
|
12
|
+
set_flag_data(variant, exp)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def set_experiments
|
17
|
+
flags_with_condition = []
|
18
|
+
|
19
|
+
@experiment_repository.all_experiments.each do |exp|
|
20
|
+
exp.flags.each do |flag_name|
|
21
|
+
flag = @flag_repository.flag(flag_name)
|
22
|
+
unless flag.nil?
|
23
|
+
set_flag_data(flag, exp)
|
24
|
+
flags_with_condition << flag_name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
@flag_repository.all_flags.each do |flag|
|
30
|
+
set_flag_data(flag) unless flags_with_condition.include?(flag.name)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_flag_data(variant, experiment = nil)
|
35
|
+
variant.set_for_evaluation(@parser, experiment, @impression_invoker)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rox/core/context/merged_context'
|
2
|
+
require 'rox/core/impression/models/experiment'
|
3
|
+
require 'rox/core/impression/models/reporting_value'
|
4
|
+
|
5
|
+
module Rox
|
6
|
+
module Core
|
7
|
+
class Variant
|
8
|
+
attr_accessor :default_value, :options, :name, :context, :condition, :parser, :impression_invoker, :client_experiment
|
9
|
+
|
10
|
+
def initialize(default_value, options)
|
11
|
+
@default_value = default_value
|
12
|
+
@options = options.clone
|
13
|
+
@options << default_value unless options.include?(default_value)
|
14
|
+
|
15
|
+
@condition = nil
|
16
|
+
@parser = nil
|
17
|
+
@context = nil
|
18
|
+
@impression_invoker = nil
|
19
|
+
@client_experiment = nil
|
20
|
+
@name = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_for_evaluation(parser, experiment, impression_invoker)
|
24
|
+
if experiment.nil?
|
25
|
+
@client_experiment = nil
|
26
|
+
@condition = ''
|
27
|
+
else
|
28
|
+
@client_experiment = Experiment.new(experiment)
|
29
|
+
@condition = experiment.condition
|
30
|
+
end
|
31
|
+
|
32
|
+
@parser = parser
|
33
|
+
@impression_invoker = impression_invoker
|
34
|
+
end
|
35
|
+
|
36
|
+
def value(context = nil)
|
37
|
+
return_value = @default_value
|
38
|
+
merged_context = MergedContext.new(@context, context)
|
39
|
+
|
40
|
+
if !@parser.nil? && !@condition.nil? && !@condition.empty?
|
41
|
+
evaluation_result = @parser.evaluate_expression(@condition, merged_context)
|
42
|
+
unless evaluation_result.nil?
|
43
|
+
value = evaluation_result.string_value
|
44
|
+
if !value.nil? && !value.empty?
|
45
|
+
return_value = value if @options.include?(value)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
@impression_invoker.invoke(ReportingValue.new(@name, return_value), @client_experiment, merged_context) if @impression_invoker != nil
|
51
|
+
|
52
|
+
return_value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rox/core/impression/impression_args'
|
2
|
+
|
3
|
+
module Rox
|
4
|
+
module Core
|
5
|
+
class ImpressionInvoker
|
6
|
+
def initialize(internal_flags, custom_property_repository, device_properties, analytics_client, is_roxy)
|
7
|
+
@internal_flags = internal_flags
|
8
|
+
@custom_property_repository = custom_property_repository
|
9
|
+
@device_properties = device_properties
|
10
|
+
@analytics_client = analytics_client
|
11
|
+
@is_roxy = is_roxy
|
12
|
+
|
13
|
+
@impression_handlers = []
|
14
|
+
@mutex = Mutex.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def invoke(reporting_value, client_experiment, context)
|
18
|
+
# TODO: Implement analytics logic
|
19
|
+
|
20
|
+
raise_impression_event(ImpressionArgs.new(reporting_value, client_experiment, context))
|
21
|
+
end
|
22
|
+
|
23
|
+
def register_impression_handler(&block)
|
24
|
+
@mutex.synchronize do
|
25
|
+
@impression_handlers << block
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def raise_impression_event(args)
|
30
|
+
handlers = []
|
31
|
+
@mutex.synchronize do
|
32
|
+
handlers = @impression_handlers.clone
|
33
|
+
end
|
34
|
+
|
35
|
+
handlers.each do |handler|
|
36
|
+
handler.call(args)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Rox
|
2
|
+
module Core
|
3
|
+
class Experiment
|
4
|
+
attr_accessor :name, :identifier, :is_archived, :labels
|
5
|
+
|
6
|
+
def initialize(experiment)
|
7
|
+
@name = experiment.name
|
8
|
+
@identifier = experiment.id
|
9
|
+
@is_archived = experiment.is_archived
|
10
|
+
@labels = experiment.labels
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rox/core/network/configuration_source'
|
2
|
+
require 'rox/core/network/configuration_fetch_result'
|
3
|
+
require 'rox/core/network/configuration_fetcher_base'
|
4
|
+
|
5
|
+
module Rox
|
6
|
+
module Core
|
7
|
+
class ConfigurationFetcher < ConfigurationFetcherBase
|
8
|
+
def fetch
|
9
|
+
source = ConfigurationSource::CDN
|
10
|
+
begin
|
11
|
+
fetch_result = fetch_from_cdn
|
12
|
+
return ConfigurationFetchResult.new(fetch_result.text, source) if fetch_result.success_status_code?
|
13
|
+
|
14
|
+
if [403, 404].include?(fetch_result.status_code)
|
15
|
+
write_fetch_error_to_log_and_invoke_fetch_handler(source, fetch_result, false, ConfigurationSource::API)
|
16
|
+
source = ConfigurationSource::API
|
17
|
+
fetch_result = fetch_from_api
|
18
|
+
return ConfigurationFetchResult.new(fetch_result.text, source) if fetch_result.success_status_code?
|
19
|
+
end
|
20
|
+
|
21
|
+
write_fetch_error_to_log_and_invoke_fetch_handler(source, fetch_result)
|
22
|
+
rescue StandardError => ex
|
23
|
+
write_fetch_exception_to_log_and_invoke_fetch_handler(source, ex)
|
24
|
+
end
|
25
|
+
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def fetch_from_cdn
|
30
|
+
@request.send_get(@request_configuration_builder.build_for_cdn)
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch_from_api
|
34
|
+
@request.send_get(@request_configuration_builder.build_for_api)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rox/core/configuration/fetcher_error'
|
2
|
+
require 'rox/core/logging/logging'
|
3
|
+
|
4
|
+
module Rox
|
5
|
+
module Core
|
6
|
+
class ConfigurationFetcherBase
|
7
|
+
def initialize(request_configuration_builder, request, configuration_fetched_invoker)
|
8
|
+
@request_configuration_builder = request_configuration_builder
|
9
|
+
@request = request
|
10
|
+
@configuration_fetched_invoker = configuration_fetched_invoker
|
11
|
+
end
|
12
|
+
|
13
|
+
def write_fetch_error_to_log_and_invoke_fetch_handler(source, response, raise_configuration_handler = true, next_source = nil)
|
14
|
+
retry_msg = next_source.nil? ? '' : "Trying from #{next_source}. "
|
15
|
+
Logging.logger.debug("Failed to fetch from #{source}. #{retry_msg}http error code: #{response.status_code}")
|
16
|
+
@configuration_fetched_invoker.invoke_error(FetcherError::NETWORK_ERROR) if raise_configuration_handler
|
17
|
+
end
|
18
|
+
|
19
|
+
def write_fetch_exception_to_log_and_invoke_fetch_handler(source, ex)
|
20
|
+
Logging.logger.error("Failed to fetch configuration. Source: #{source}. Ex: #{ex}")
|
21
|
+
@configuration_fetched_invoker.invoke_error(FetcherError::NETWORK_ERROR)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rox/core/network/configuration_source'
|
2
|
+
require 'rox/core/network/configuration_fetch_result'
|
3
|
+
require 'rox/core/network/configuration_fetcher_base'
|
4
|
+
|
5
|
+
module Rox
|
6
|
+
module Core
|
7
|
+
class ConfigurationFetcherRoxy < ConfigurationFetcherBase
|
8
|
+
def fetch
|
9
|
+
source = ConfigurationSource::ROXY
|
10
|
+
begin
|
11
|
+
fetch_roxy = fetch_from_roxy
|
12
|
+
if fetch_roxy.success_status_code?
|
13
|
+
return ConfigurationFetchResult.new(fetch_roxy.text, source)
|
14
|
+
else
|
15
|
+
write_fetch_error_to_log_and_invoke_fetch_handler(source, fetch_roxy)
|
16
|
+
end
|
17
|
+
rescue StandardError => ex
|
18
|
+
write_fetch_exception_to_log_and_invoke_fetch_handler(source, ex)
|
19
|
+
end
|
20
|
+
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch_from_roxy
|
25
|
+
@request.send_get(@request_configuration_builder.build_for_roxy)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
require 'rox/core/network/response'
|
4
|
+
|
5
|
+
module Rox
|
6
|
+
module Core
|
7
|
+
class Request
|
8
|
+
def send_get(request_data)
|
9
|
+
uri = URI(request_data.url)
|
10
|
+
uri.query = URI.encode_www_form(request_data.query_params)
|
11
|
+
req = Net::HTTP::Get.new(uri)
|
12
|
+
|
13
|
+
send(req, uri, nil)
|
14
|
+
end
|
15
|
+
|
16
|
+
def send_post(url, payload)
|
17
|
+
uri = URI(url)
|
18
|
+
req = Net::HTTP::Post.new(uri)
|
19
|
+
|
20
|
+
send(req, uri, payload)
|
21
|
+
end
|
22
|
+
|
23
|
+
def send(request, uri, payload)
|
24
|
+
request['Accept-Encoding'] = 'gzip'
|
25
|
+
|
26
|
+
unless payload.nil?
|
27
|
+
request['Content-Type'] = 'application/json'
|
28
|
+
request.body = JSON.dump(payload)
|
29
|
+
end
|
30
|
+
|
31
|
+
resp = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') do |http|
|
32
|
+
http.request(request)
|
33
|
+
end
|
34
|
+
|
35
|
+
resp_body = resp.body
|
36
|
+
if resp['Content-Encoding'].eql?('gzip')
|
37
|
+
sio = StringIO.new(resp.body)
|
38
|
+
gz = Zlib::GzipReader.new(sio)
|
39
|
+
resp_body = gz.read
|
40
|
+
end
|
41
|
+
|
42
|
+
Response.new(resp.code.to_i, resp_body)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rox/core/network/request_data'
|
2
|
+
require 'rox/core/consts/environment'
|
3
|
+
require 'rox/core/consts/property_type'
|
4
|
+
|
5
|
+
module Rox
|
6
|
+
module Core
|
7
|
+
class RequestConfigurationBuilder
|
8
|
+
def initialize(sdk_settings, buid, device_properties, roxy_url)
|
9
|
+
@sdk_settings = sdk_settings
|
10
|
+
@buid = buid
|
11
|
+
@device_properties = device_properties
|
12
|
+
@roxy_url = roxy_url
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_for_roxy
|
16
|
+
roxy_endpoint = URI.join(@roxy_url, Rox::Core::Environment.roxy_internal_path).to_s
|
17
|
+
build_request_with_full_params(roxy_endpoint)
|
18
|
+
end
|
19
|
+
|
20
|
+
def build_for_cdn
|
21
|
+
RequestData.new("#{Rox::Core::Environment.cdn_path}/#{@buid.value}",
|
22
|
+
Rox::Core::PropertyType::DISTINCT_ID.name => @device_properties.distinct_id)
|
23
|
+
end
|
24
|
+
|
25
|
+
def build_for_api
|
26
|
+
build_request_with_full_params(Rox::Core::Environment.api_path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_request_with_full_params(url)
|
30
|
+
query_params = {}
|
31
|
+
|
32
|
+
@buid.query_string_parts.each do |key, value|
|
33
|
+
query_params[key] = value unless query_params.include?(key)
|
34
|
+
end
|
35
|
+
|
36
|
+
@device_properties.all_properties.each do |key, value|
|
37
|
+
query_params[key] = value unless query_params.include?(key)
|
38
|
+
end
|
39
|
+
|
40
|
+
cdn_data = build_for_cdn
|
41
|
+
query_params[Rox::Core::PropertyType::CACHE_MISS_URL.name] = cdn_data.url
|
42
|
+
query_params['devModeSecret'] = @sdk_settings.dev_mode_secret
|
43
|
+
|
44
|
+
RequestData.new(url, query_params)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|