sbmt-strangler 0.9.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 +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
|