sbmt-strangler 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +54 -0
- data/.rubocop_todo.yml +0 -0
- data/Appraisals +20 -0
- data/CHANGELOG.md +106 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +86 -0
- data/Rakefile +12 -0
- data/config/initializers/strangler.rb +5 -0
- data/config/initializers/yabeda.rb +40 -0
- data/dip.yml +67 -0
- data/docker-compose.yml +19 -0
- data/docs/img/01-proxy_mode.png +0 -0
- data/docs/img/02-mirror_mode.png +0 -0
- data/docs/img/03-replace_mode.png +0 -0
- data/lefthook-local.dip_example.yml +4 -0
- data/lefthook.yml +6 -0
- data/lib/sbmt/strangler/action.rb +37 -0
- data/lib/sbmt/strangler/action_invoker.rb +43 -0
- data/lib/sbmt/strangler/builder.rb +66 -0
- data/lib/sbmt/strangler/configurable.rb +32 -0
- data/lib/sbmt/strangler/configuration.rb +25 -0
- data/lib/sbmt/strangler/const_definer.rb +36 -0
- data/lib/sbmt/strangler/controller.rb +30 -0
- data/lib/sbmt/strangler/engine.rb +11 -0
- data/lib/sbmt/strangler/error_tracker.rb +34 -0
- data/lib/sbmt/strangler/errors.rb +7 -0
- data/lib/sbmt/strangler/feature_flags.rb +59 -0
- data/lib/sbmt/strangler/flipper.rb +38 -0
- data/lib/sbmt/strangler/http/client.rb +41 -0
- data/lib/sbmt/strangler/http/transport.rb +89 -0
- data/lib/sbmt/strangler/http.rb +89 -0
- data/lib/sbmt/strangler/logger.rb +48 -0
- data/lib/sbmt/strangler/metric_tracker.rb +69 -0
- data/lib/sbmt/strangler/mixin.rb +56 -0
- data/lib/sbmt/strangler/version.rb +7 -0
- data/lib/sbmt/strangler/work_modes/base.rb +22 -0
- data/lib/sbmt/strangler/work_modes/mirror.rb +73 -0
- data/lib/sbmt/strangler/work_modes/proxy.rb +20 -0
- data/lib/sbmt/strangler/work_modes/replace.rb +65 -0
- data/lib/sbmt/strangler.rb +84 -0
- data/sbmt-strangler.gemspec +59 -0
- metadata +473 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
class Configuration
|
6
|
+
extend Sbmt::Strangler::Configurable
|
7
|
+
|
8
|
+
option :params_tracking_allowlist, :headers_allowlist, default: []
|
9
|
+
option :action_controller_base_class, default: "ActionController::Base"
|
10
|
+
option :error_tracker, default: "Sbmt::Strangler::ErrorTracker"
|
11
|
+
option :flipper_actor, default: ->(_http_params, _headers) {}
|
12
|
+
|
13
|
+
attr_reader :controllers, :http
|
14
|
+
|
15
|
+
def initialize(options = {})
|
16
|
+
@controllers = []
|
17
|
+
@http = ActiveSupport::InheritableOptions.new(Sbmt::Strangler::Http::DEFAULT_HTTP_OPTIONS)
|
18
|
+
end
|
19
|
+
|
20
|
+
def controller(name, &)
|
21
|
+
controllers.push(Sbmt::Strangler::Controller.new(name, self, &))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
class ConstDefiner
|
6
|
+
class << self
|
7
|
+
def call!(name, klass)
|
8
|
+
const_names = name.split("::")
|
9
|
+
class_name = const_names.pop
|
10
|
+
module_name = if const_names.any?
|
11
|
+
define_modules(const_names)
|
12
|
+
else
|
13
|
+
Object
|
14
|
+
end
|
15
|
+
|
16
|
+
module_name.const_set(class_name, klass)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def define_modules(module_names)
|
22
|
+
module_names.reduce(Object) do |parent_module_name, module_name|
|
23
|
+
define_module(module_name, parent_module_name)
|
24
|
+
"#{parent_module_name}::#{module_name}".constantize
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def define_module(module_name, parent_module_name)
|
29
|
+
return if parent_module_name.const_defined?(module_name, false)
|
30
|
+
|
31
|
+
parent_module_name.const_set(module_name, Module.new)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
class Controller
|
6
|
+
extend Sbmt::Strangler::Configurable
|
7
|
+
|
8
|
+
option :params_tracking_allowlist, :headers_allowlist, :flipper_actor, default_from: :configuration
|
9
|
+
|
10
|
+
attr_reader :name, :class_name, :actions, :configuration
|
11
|
+
|
12
|
+
def initialize(name, configuration, &)
|
13
|
+
@name = name
|
14
|
+
@class_name = "#{name.camelize}Controller"
|
15
|
+
@actions = []
|
16
|
+
@configuration = configuration
|
17
|
+
|
18
|
+
yield(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def action(name, &)
|
22
|
+
@actions.push(Sbmt::Strangler::Action.new(name, self, &))
|
23
|
+
end
|
24
|
+
|
25
|
+
def http
|
26
|
+
@http ||= ActiveSupport::InheritableOptions.new(configuration.http)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
class ErrorTracker
|
6
|
+
class << self
|
7
|
+
def error(message, params = {})
|
8
|
+
unless defined?(Sentry)
|
9
|
+
Sbmt::Strangler.logger.log_error(message, params)
|
10
|
+
return
|
11
|
+
end
|
12
|
+
|
13
|
+
logging(:error, message, params)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def logging(level, message, params)
|
19
|
+
params = {message: params} if params.is_a?(String)
|
20
|
+
|
21
|
+
Sentry.with_scope do |scope|
|
22
|
+
scope.set_contexts(contexts: params)
|
23
|
+
|
24
|
+
if message.is_a?(Exception)
|
25
|
+
Sentry.capture_exception(message, level: level)
|
26
|
+
else
|
27
|
+
Sentry.capture_message(message, level: level)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
class FeatureFlags
|
6
|
+
FLAGS = %i[
|
7
|
+
mirror
|
8
|
+
replace
|
9
|
+
]
|
10
|
+
FEATURES_HEADER_NAME = "HTTP_STRANGLER_FEATURES"
|
11
|
+
|
12
|
+
attr_reader :strangler_action, :rails_controller
|
13
|
+
|
14
|
+
def initialize(strangler_action:, rails_controller: nil)
|
15
|
+
@strangler_action = strangler_action
|
16
|
+
@rails_controller = rails_controller
|
17
|
+
end
|
18
|
+
|
19
|
+
FLAGS.each do |flag_name|
|
20
|
+
define_method(:"#{flag_name}?") { feature_enabled?(feature_name(flag_name)) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_all!
|
24
|
+
FLAGS.each { |flag_name| add(feature_name(flag_name)) }
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
delegate :add, :enabled?, :enabled_on_time?, to: "Sbmt::Strangler::Flipper"
|
30
|
+
|
31
|
+
FEATURE_NAME_SANITIZER = -> { _1.to_s.gsub(/[^A-Za-z0-9]+/, "-") }
|
32
|
+
|
33
|
+
def feature_name(flag_name)
|
34
|
+
sanitized_controller_name = FEATURE_NAME_SANITIZER.call(strangler_action.controller.name)
|
35
|
+
sanitized_action_name = FEATURE_NAME_SANITIZER.call(strangler_action.name)
|
36
|
+
sanitized_flag_name = FEATURE_NAME_SANITIZER.call(flag_name)
|
37
|
+
|
38
|
+
"#{sanitized_controller_name}__#{sanitized_action_name}--#{sanitized_flag_name}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def feature_enabled?(feature_name)
|
42
|
+
enabled?(feature_name, flipper_actor) ||
|
43
|
+
enabled_on_time?(feature_name) ||
|
44
|
+
enabled_by_header?(feature_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def flipper_actor
|
48
|
+
@flipper_actor ||= strangler_action.flipper_actor.call(rails_controller.http_params, rails_controller.request.headers)
|
49
|
+
end
|
50
|
+
|
51
|
+
def enabled_by_header?(feature_name)
|
52
|
+
features = rails_controller.request.headers[FEATURES_HEADER_NAME]
|
53
|
+
return false unless features.present? && features.is_a?(String)
|
54
|
+
|
55
|
+
features.split(",").map(&:strip).include?(feature_name)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
module Flipper
|
6
|
+
FLIPPER_ID_STRUCT = Struct.new(:flipper_id)
|
7
|
+
ONTIME_ACTOR_REGEXP = /^ONTIME:(\d{2})-(\d{2})$/
|
8
|
+
|
9
|
+
class << self
|
10
|
+
delegate :add, to: ::Flipper
|
11
|
+
|
12
|
+
def enabled?(feature_name, *actors)
|
13
|
+
raise "feature name is blank" if feature_name.blank?
|
14
|
+
|
15
|
+
actors = Array(actors).flatten.compact
|
16
|
+
::Flipper.enabled?(feature_name, *actors.map { FLIPPER_ID_STRUCT.new(_1) })
|
17
|
+
end
|
18
|
+
|
19
|
+
def enabled_on_time?(feature_name)
|
20
|
+
raise "feature name is blank" if feature_name.blank?
|
21
|
+
|
22
|
+
hours_ranges =
|
23
|
+
::Flipper[feature_name]
|
24
|
+
.actors_value
|
25
|
+
.filter_map { |e|
|
26
|
+
e.match(ONTIME_ACTOR_REGEXP) {
|
27
|
+
$LAST_MATCH_INFO.captures.map(&:to_i)
|
28
|
+
}
|
29
|
+
}
|
30
|
+
.compact
|
31
|
+
|
32
|
+
hour_now = DateTime.now.in_time_zone.hour
|
33
|
+
hours_ranges.any? { |range| (range.first..range.last).cover?(hour_now) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
module Http
|
6
|
+
class Client
|
7
|
+
include Dry::Monads::Result::Mixin
|
8
|
+
|
9
|
+
def initialize(http_options: nil)
|
10
|
+
@http_options = http_options
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(url, http_verb, payload: {}, headers: {})
|
14
|
+
case http_verb.downcase
|
15
|
+
when :get
|
16
|
+
transport.get_request(url, params: payload, headers: prepare_headers(headers))
|
17
|
+
when :post
|
18
|
+
transport.post_request(url, body: payload, headers: prepare_headers(headers))
|
19
|
+
when :put
|
20
|
+
transport.put_request(url, body: payload, headers: prepare_headers(headers))
|
21
|
+
|
22
|
+
else
|
23
|
+
raise "unsupported http verb - #{http_verb}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :http_options
|
30
|
+
|
31
|
+
def transport
|
32
|
+
@transport ||= Sbmt::Strangler::Http::Transport.new(http_options: http_options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def prepare_headers(headers)
|
36
|
+
headers&.transform_keys { |key| key.sub("HTTP_", "").tr("_", "-") }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
module Http
|
6
|
+
class Transport
|
7
|
+
include Dry::Monads::Do
|
8
|
+
include Dry::Monads::Result::Mixin
|
9
|
+
|
10
|
+
def initialize(http_options: nil)
|
11
|
+
@http_options = http_options
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_request(url, params: {}, headers: {})
|
15
|
+
with_error_handling(url) do
|
16
|
+
response = connection.get(url, params, headers)
|
17
|
+
Success(body: response.body, status: response.status, headers: response.headers)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def post_request(url, body: {}, headers: {})
|
22
|
+
with_error_handling(url) do
|
23
|
+
response = connection.post(url, body, headers)
|
24
|
+
Success(body: response.body, status: response.status, headers: response.headers)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def put_request(url, body: {}, headers: {})
|
29
|
+
with_error_handling(url) do
|
30
|
+
response = connection.put(url, body, headers)
|
31
|
+
Success(body: response.body, status: response.status, headers: response.headers)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :http_options
|
38
|
+
|
39
|
+
def connection
|
40
|
+
@connection ||= Faraday.new do |conn|
|
41
|
+
conn.response :raise_error
|
42
|
+
Sbmt::Strangler::Http.configure_faraday(conn, name: "strangler_http_client", http_options: http_options)
|
43
|
+
# Skip JSON parsing because
|
44
|
+
# 1. it speeds up proxy mode and
|
45
|
+
# 2. allows us to duplicate proxy response easily.
|
46
|
+
# conn.response :json
|
47
|
+
conn.request :json
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def with_error_handling(url)
|
52
|
+
retry_count ||= 0
|
53
|
+
|
54
|
+
yield
|
55
|
+
rescue Faraday::ConnectionFailed => error
|
56
|
+
Sbmt::Strangler.logger.error(
|
57
|
+
message: "Sbmt::Strangler::Http::Transport ConnectionFailed",
|
58
|
+
url: url,
|
59
|
+
attempt: retry_count + 1,
|
60
|
+
retries_count: http_options.retries_count
|
61
|
+
)
|
62
|
+
|
63
|
+
retry if (retry_count += 1) && retry_count <= http_options.retries_count
|
64
|
+
|
65
|
+
Failure(status: :bad_gateway)
|
66
|
+
rescue Faraday::UnprocessableEntityError, Faraday::ForbiddenError => error
|
67
|
+
Failure(body: error.response_body, status: error.response_status, headers: error.response_headers)
|
68
|
+
rescue Faraday::TimeoutError
|
69
|
+
Sbmt::Strangler.logger.error(
|
70
|
+
message: "Sbmt::Strangler::Http::Transport TimeoutError",
|
71
|
+
url: url
|
72
|
+
)
|
73
|
+
Failure(status: :gateway_timeout)
|
74
|
+
rescue Faraday::Error => error
|
75
|
+
response = error.response
|
76
|
+
Sbmt::Strangler.logger.error(error.message)
|
77
|
+
Sbmt::Strangler.error_tracker.error(error)
|
78
|
+
return Failure(status: :internal_server_error) unless response
|
79
|
+
|
80
|
+
Failure(body: response[:body], status: response[:status], headers: response[:headers])
|
81
|
+
rescue => error
|
82
|
+
Sbmt::Strangler.logger.error(error.message)
|
83
|
+
Sbmt::Strangler.error_tracker.error(error)
|
84
|
+
Failure(status: :internal_server_error)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "http/client"
|
4
|
+
require_relative "http/transport"
|
5
|
+
|
6
|
+
module Sbmt
|
7
|
+
module Strangler
|
8
|
+
module Http
|
9
|
+
DEFAULT_HTTP_OPTIONS = {
|
10
|
+
keepalive_pool_size: 256,
|
11
|
+
keepalive_idle_timeout: 30,
|
12
|
+
timeout: 5,
|
13
|
+
read_timeout: 5,
|
14
|
+
write_timeout: 5,
|
15
|
+
open_timeout: 1,
|
16
|
+
retries_count: 1
|
17
|
+
}.freeze
|
18
|
+
REQUEST_PATH_FILTER_REGEX = %r{(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})|(/\d+)|(/[A-Z]\d{9,11}(-\d{1})?)}
|
19
|
+
|
20
|
+
# Configures Faraday connection. Sets default options and adds default middlewares into chain.
|
21
|
+
# Accepts an optional block to configure net-http-persistent-adapter
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
#
|
25
|
+
# @conn ||= Faraday.new(@base_url) do |f|
|
26
|
+
# Sbmt::Strangler::Http.configure_faraday(f, name: "http-client") do |http|
|
27
|
+
# http.idle_timeout = 42
|
28
|
+
# end
|
29
|
+
# f.timeout = 5
|
30
|
+
# f.response :json
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# @param [Faraday::Connection] conn
|
34
|
+
# @param [Hash] opts faraday & middlewares options
|
35
|
+
# @option opts [String] :name client name for tracing and instrumentation. Required.
|
36
|
+
# @option opts [Hash] :adapter_opts net_http_persistent adapter options
|
37
|
+
# @option opts [ActiveSupport::InheritableOptions] :http_options timeout options
|
38
|
+
# @option opts [Regexp] :request_path_filter_regex (REQUEST_PATH_FILTER_REGEX) regex for filtering out
|
39
|
+
# variables from http request metric `path` tag. Set to false to add empty value instead.
|
40
|
+
#
|
41
|
+
# @return [Faraday::Connection]
|
42
|
+
def self.configure_faraday(conn, opts = {})
|
43
|
+
raise ConfigurationError, "Faraday client :name must be set" unless opts[:name]
|
44
|
+
|
45
|
+
http_options = opts[:http_options] || Sbmt::Strangler.configuration.http
|
46
|
+
|
47
|
+
conn.options.timeout = http_options.timeout
|
48
|
+
conn.options.read_timeout = http_options.read_timeout
|
49
|
+
conn.options.open_timeout = http_options.open_timeout
|
50
|
+
conn.options.write_timeout = http_options.write_timeout
|
51
|
+
|
52
|
+
configure_faraday_metrics(conn, opts.slice(:name, :request_path_filter_regex))
|
53
|
+
|
54
|
+
adapter_opts = {pool_size: http_options.keepalive_pool_size}.merge(opts[:adapter_opts] || {})
|
55
|
+
conn.adapter :net_http_persistent, adapter_opts do |http|
|
56
|
+
http.idle_timeout = http_options.keepalive_idle_timeout
|
57
|
+
yield http if block_given?
|
58
|
+
end
|
59
|
+
|
60
|
+
conn
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.configure_faraday_metrics(conn, opts = {})
|
64
|
+
@subscribers ||= {}
|
65
|
+
name = opts.fetch(:name)
|
66
|
+
instrument_full_name = ["request.faraday", name].compact.join(".")
|
67
|
+
filter = opts.fetch(:request_path_filter_regex, REQUEST_PATH_FILTER_REGEX)
|
68
|
+
|
69
|
+
conn.request :instrumentation, name: instrument_full_name
|
70
|
+
return if @subscribers[instrument_full_name]
|
71
|
+
|
72
|
+
@subscribers[instrument_full_name] = ActiveSupport::Notifications.subscribe(instrument_full_name) do |*args|
|
73
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
74
|
+
env = event.payload
|
75
|
+
|
76
|
+
tags = {
|
77
|
+
name: name,
|
78
|
+
method: env.method,
|
79
|
+
status: env.status || :error,
|
80
|
+
host: env.url.host,
|
81
|
+
path: filter ? env.url.path.gsub(filter, "/:id") : ""
|
82
|
+
}
|
83
|
+
|
84
|
+
Yabeda.sbmt_strangler.http_request_duration.measure(tags, event.duration.fdiv(1000))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
class Logger
|
6
|
+
delegate :logger, to: :Rails
|
7
|
+
delegate_missing_to :logger
|
8
|
+
|
9
|
+
def log_debug(message, **params)
|
10
|
+
with_tags(**params) do
|
11
|
+
logger.debug(message)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def log_info(message, **params)
|
16
|
+
with_tags(**params) do
|
17
|
+
logger.info(message)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def log_warn(message, **params)
|
22
|
+
with_tags(**params) do
|
23
|
+
logger.warn(message)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def log_error(message, **params)
|
28
|
+
with_tags(**params) do
|
29
|
+
logger.error(message)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def log_success(message, **params)
|
34
|
+
log_info(message, status: "success", **params)
|
35
|
+
end
|
36
|
+
|
37
|
+
def log_failure(message, **params)
|
38
|
+
log_error(message, status: "failure", **params)
|
39
|
+
end
|
40
|
+
|
41
|
+
def with_tags(**params)
|
42
|
+
logger.tagged(**params) do
|
43
|
+
yield
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
class MetricTracker
|
6
|
+
attr_reader :rails_controller
|
7
|
+
|
8
|
+
def initialize(rails_controller)
|
9
|
+
@rails_controller = rails_controller
|
10
|
+
end
|
11
|
+
|
12
|
+
def track_params_usage
|
13
|
+
::Yabeda.sbmt_strangler.params_usage.increment(common_tags)
|
14
|
+
end
|
15
|
+
|
16
|
+
def log_unallowed_params
|
17
|
+
unallowed_params = all_request_params - allowed_request_params
|
18
|
+
Sbmt::Strangler.logger.log_warn(<<~WARN.strip) if unallowed_params.any?
|
19
|
+
Not allowed parameters in #{controller_path}##{action_name}: #{unallowed_params}
|
20
|
+
WARN
|
21
|
+
end
|
22
|
+
|
23
|
+
def track_work_mode(mode)
|
24
|
+
yabeda_tags = common_tags.merge(mode: mode.to_s)
|
25
|
+
::Yabeda.sbmt_strangler.work_mode.increment(yabeda_tags)
|
26
|
+
end
|
27
|
+
|
28
|
+
def track_mirror_call(success)
|
29
|
+
yabeda_tags = common_tags.merge(success: success.to_s)
|
30
|
+
::Yabeda.sbmt_strangler.mirror_call.increment(yabeda_tags)
|
31
|
+
end
|
32
|
+
|
33
|
+
def track_compare_call(success)
|
34
|
+
yabeda_tags = common_tags.merge(success: success.to_s)
|
35
|
+
::Yabeda.sbmt_strangler.compare_call.increment(yabeda_tags)
|
36
|
+
end
|
37
|
+
|
38
|
+
def track_compare_call_result(value)
|
39
|
+
yabeda_tags = common_tags.merge(value: value.to_s)
|
40
|
+
::Yabeda.sbmt_strangler.compare_call_result.increment(yabeda_tags)
|
41
|
+
end
|
42
|
+
|
43
|
+
def track_render_call(success)
|
44
|
+
yabeda_tags = common_tags.merge(success: success.to_s)
|
45
|
+
::Yabeda.sbmt_strangler.render_call.increment(yabeda_tags)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
delegate :http_params, :allowed_params, :controller_path, :action_name, to: :rails_controller
|
51
|
+
|
52
|
+
def common_tags
|
53
|
+
{
|
54
|
+
params: allowed_request_params.join(","),
|
55
|
+
controller: controller_path,
|
56
|
+
action: action_name
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def allowed_request_params
|
61
|
+
@allowed_request_params ||= allowed_params.keys.map(&:to_s).sort.uniq
|
62
|
+
end
|
63
|
+
|
64
|
+
def all_request_params
|
65
|
+
@all_request_params ||= http_params.keys.map(&:to_s).sort.uniq
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
module Mixin
|
6
|
+
attr_reader :strangler_action
|
7
|
+
|
8
|
+
def http_params
|
9
|
+
params.to_unsafe_h.except(:action, :controller, :format)
|
10
|
+
end
|
11
|
+
|
12
|
+
def allowed_params
|
13
|
+
return http_params if strangler_action.params_tracking_allowlist.blank?
|
14
|
+
|
15
|
+
params.permit(*strangler_action.params_tracking_allowlist).to_h
|
16
|
+
end
|
17
|
+
|
18
|
+
def allowed_headers
|
19
|
+
if strangler_action.headers_allowlist.blank?
|
20
|
+
return request.headers.select { |name, _| name.starts_with?("HTTP_") }.to_h
|
21
|
+
end
|
22
|
+
|
23
|
+
request.headers.select { |name, _| name.in?(strangler_action.headers_allowlist) }.to_h
|
24
|
+
end
|
25
|
+
|
26
|
+
def http_request(payload)
|
27
|
+
strangler_action.http_client.call(
|
28
|
+
proxy_url,
|
29
|
+
strangler_action.proxy_http_method,
|
30
|
+
payload: payload,
|
31
|
+
headers: allowed_headers
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def proxy_url
|
36
|
+
case strangler_action.proxy_url
|
37
|
+
in String => url
|
38
|
+
url
|
39
|
+
in Proc => proc
|
40
|
+
proc.call(http_params, request.headers)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def render_origin_response(response)
|
45
|
+
if response.success?
|
46
|
+
body, status = response.value!.values_at(:body, :status)
|
47
|
+
render json: body, status: status
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
body, status = response.failure.values_at(:body, :status)
|
52
|
+
render json: body, status: status
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
module WorkModes
|
6
|
+
class Base
|
7
|
+
attr_reader :rails_controller, :strangler_action, :metric_tracker, :feature_flags
|
8
|
+
|
9
|
+
def initialize(rails_controller:, strangler_action:, metric_tracker:, feature_flags:)
|
10
|
+
@rails_controller = rails_controller
|
11
|
+
@strangler_action = strangler_action
|
12
|
+
@metric_tracker = metric_tracker
|
13
|
+
@feature_flags = feature_flags
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|