rox-rollout 0.1.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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +45 -0
  3. data/.gitignore +8 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE +50 -0
  6. data/README_DEVELOP.md +19 -0
  7. data/Rakefile +16 -0
  8. data/_archive/.document +5 -0
  9. data/_archive/.rspec +1 -0
  10. data/_archive/Gemfile +15 -0
  11. data/_archive/Gemfile.lock +87 -0
  12. data/_archive/README.md +32 -0
  13. data/_archive/README.rdoc +19 -0
  14. data/_archive/Rakefile +50 -0
  15. data/_archive/lib/expr_function_definition.rb +52 -0
  16. data/_archive/lib/function_definition.rb +48 -0
  17. data/_archive/lib/function_token.rb +12 -0
  18. data/_archive/lib/object_extends.rb +12 -0
  19. data/_archive/lib/ruby_interpreter.rb +292 -0
  20. data/_archive/lib/stack.rb +48 -0
  21. data/_archive/lib/string_extends.rb +14 -0
  22. data/_archive/spec/ruby_interpreter_spec.rb +203 -0
  23. data/_archive/spec/spec_helper.rb +30 -0
  24. data/_archive/spec/stack_spec.rb +77 -0
  25. data/bin/console +14 -0
  26. data/bin/setup +8 -0
  27. data/e2e/container.rb +38 -0
  28. data/e2e/custom_props.rb +55 -0
  29. data/e2e/rox_e2e_test.rb +159 -0
  30. data/e2e/test_vars.rb +24 -0
  31. data/lib/rox.rb +5 -0
  32. data/lib/rox/core/client/buid.rb +82 -0
  33. data/lib/rox/core/client/device_properties.rb +45 -0
  34. data/lib/rox/core/client/internal_flags.rb +20 -0
  35. data/lib/rox/core/client/sdk_settings.rb +5 -0
  36. data/lib/rox/core/configuration/configuration.rb +5 -0
  37. data/lib/rox/core/configuration/configuration_fetched_args.rb +23 -0
  38. data/lib/rox/core/configuration/configuration_fetched_invoker.rb +37 -0
  39. data/lib/rox/core/configuration/configuration_parser.rb +85 -0
  40. data/lib/rox/core/configuration/fetcher_error.rb +13 -0
  41. data/lib/rox/core/configuration/fetcher_status.rb +10 -0
  42. data/lib/rox/core/configuration/models/experiment_model.rb +5 -0
  43. data/lib/rox/core/configuration/models/target_group_model.rb +5 -0
  44. data/lib/rox/core/consts/build.rb +8 -0
  45. data/lib/rox/core/consts/environment.rb +42 -0
  46. data/lib/rox/core/consts/property_type.rb +29 -0
  47. data/lib/rox/core/context/merged_context.rb +16 -0
  48. data/lib/rox/core/core.rb +131 -0
  49. data/lib/rox/core/entities/flag.rb +26 -0
  50. data/lib/rox/core/entities/flag_setter.rb +39 -0
  51. data/lib/rox/core/entities/variant.rb +56 -0
  52. data/lib/rox/core/impression/impression_args.rb +5 -0
  53. data/lib/rox/core/impression/impression_invoker.rb +41 -0
  54. data/lib/rox/core/impression/models/experiment.rb +14 -0
  55. data/lib/rox/core/impression/models/reporting_value.rb +5 -0
  56. data/lib/rox/core/logging/logging.rb +17 -0
  57. data/lib/rox/core/logging/no_op_logger.rb +11 -0
  58. data/lib/rox/core/network/configuration_fetch_result.rb +5 -0
  59. data/lib/rox/core/network/configuration_fetcher.rb +38 -0
  60. data/lib/rox/core/network/configuration_fetcher_base.rb +25 -0
  61. data/lib/rox/core/network/configuration_fetcher_roxy.rb +29 -0
  62. data/lib/rox/core/network/configuration_source.rb +9 -0
  63. data/lib/rox/core/network/request.rb +46 -0
  64. data/lib/rox/core/network/request_configuration_builder.rb +48 -0
  65. data/lib/rox/core/network/request_data.rb +5 -0
  66. data/lib/rox/core/network/response.rb +16 -0
  67. data/lib/rox/core/properties/custom_property.rb +18 -0
  68. data/lib/rox/core/properties/custom_property_type.rb +18 -0
  69. data/lib/rox/core/properties/device_property.rb +11 -0
  70. data/lib/rox/core/register/registerer.rb +35 -0
  71. data/lib/rox/core/reporting/error_reporter.rb +152 -0
  72. data/lib/rox/core/repositories/custom_property_repository.rb +61 -0
  73. data/lib/rox/core/repositories/experiment_repository.rb +21 -0
  74. data/lib/rox/core/repositories/flag_repository.rb +49 -0
  75. data/lib/rox/core/repositories/roxx/experiments_extensions.rb +82 -0
  76. data/lib/rox/core/repositories/roxx/properties_extensions.rb +26 -0
  77. data/lib/rox/core/repositories/target_group_repository.rb +17 -0
  78. data/lib/rox/core/roxx/core_stack.rb +22 -0
  79. data/lib/rox/core/roxx/evaluation_result.rb +28 -0
  80. data/lib/rox/core/roxx/node.rb +11 -0
  81. data/lib/rox/core/roxx/parser.rb +143 -0
  82. data/lib/rox/core/roxx/regular_expression_extensions.rb +33 -0
  83. data/lib/rox/core/roxx/string_tokenizer.rb +68 -0
  84. data/lib/rox/core/roxx/symbols.rb +14 -0
  85. data/lib/rox/core/roxx/token_type.rb +30 -0
  86. data/lib/rox/core/roxx/tokenized_expression.rb +119 -0
  87. data/lib/rox/core/roxx/value_compare_extensions.rb +137 -0
  88. data/lib/rox/core/security/signature_verifier.rb +18 -0
  89. data/lib/rox/core/utils/periodic_task.rb +12 -0
  90. data/lib/rox/core/utils/type_utils.rb +9 -0
  91. data/lib/rox/server/client/sdk_settings.rb +5 -0
  92. data/lib/rox/server/client/server_properties.rb +20 -0
  93. data/lib/rox/server/flags/rox_flag.rb +19 -0
  94. data/lib/rox/server/flags/rox_variant.rb +8 -0
  95. data/lib/rox/server/logging/server_logger.rb +35 -0
  96. data/lib/rox/server/rox_options.rb +27 -0
  97. data/lib/rox/server/rox_server.rb +83 -0
  98. data/lib/rox/version.rb +3 -0
  99. data/rox.gemspec +29 -0
  100. 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,5 @@
1
+ module Rox
2
+ module Core
3
+ ImpressionArgs = Struct.new(:reporting_value, :experiment, :context)
4
+ end
5
+ 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,5 @@
1
+ module Rox
2
+ module Core
3
+ ReportingValue = Struct.new(:name, :value)
4
+ end
5
+ end
@@ -0,0 +1,17 @@
1
+ require 'rox/core/logging/no_op_logger'
2
+
3
+ module Rox
4
+ module Core
5
+ class Logging
6
+ @logger = nil
7
+
8
+ def self.logger=(logger)
9
+ @logger = logger
10
+ end
11
+
12
+ def self.logger
13
+ @logger || NoOpLogger.new
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module Rox
2
+ module Core
3
+ class NoOpLogger
4
+ def debug(message, ex = nil); end
5
+
6
+ def error(message, ex = nil); end
7
+
8
+ def warn(message, ex = nil); end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module Rox
2
+ module Core
3
+ ConfigurationFetchResult = Struct.new(:data, :source)
4
+ end
5
+ 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,9 @@
1
+ module Rox
2
+ module Core
3
+ module ConfigurationSource
4
+ CDN = 'CDN'.freeze
5
+ API = 'API'.freeze
6
+ ROXY = 'Roxy'.freeze
7
+ end
8
+ end
9
+ 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
@@ -0,0 +1,5 @@
1
+ module Rox
2
+ module Core
3
+ RequestData = Struct.new(:url, :query_params)
4
+ end
5
+ end