rox-rollout 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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