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,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Sbmt
|
6
|
+
module Strangler
|
7
|
+
module WorkModes
|
8
|
+
class Mirror < Base
|
9
|
+
include Dry::Monads::Result::Mixin
|
10
|
+
|
11
|
+
def call
|
12
|
+
mirror_task = Concurrent::Promises.future { mirror_call }
|
13
|
+
proxy_task = Concurrent::Promises.future { http_request(http_params) }
|
14
|
+
|
15
|
+
mirror_call_result = mirror_task.value!
|
16
|
+
origin_response = proxy_task.value!
|
17
|
+
|
18
|
+
begin
|
19
|
+
track_mirror_call(mirror_call_result.success?)
|
20
|
+
if mirror_call_result.success?
|
21
|
+
mirror_result = mirror_call_result.value!
|
22
|
+
origin_result = copy_of_origin_result(origin_response)
|
23
|
+
compare_call_result = compare_call(origin_result, mirror_result)
|
24
|
+
track_compare_call(compare_call_result.success?)
|
25
|
+
track_compare_call_result(compare_call_result.value!) if compare_call_result.success?
|
26
|
+
end
|
27
|
+
rescue => err
|
28
|
+
handle_error(err)
|
29
|
+
end
|
30
|
+
|
31
|
+
render_origin_response(origin_response)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
delegate :http_params, :http_request, :render_origin_response, to: :rails_controller
|
37
|
+
delegate :track_mirror_call, :track_compare_call, :track_compare_call_result, to: :metric_tracker
|
38
|
+
|
39
|
+
def copy_of_origin_result(origin_response)
|
40
|
+
if origin_response.success?
|
41
|
+
origin_response.value!.deep_dup
|
42
|
+
else
|
43
|
+
origin_response.failure.deep_dup
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def mirror_call
|
48
|
+
value = strangler_action.mirror.call(rails_controller)
|
49
|
+
Success(value)
|
50
|
+
rescue => err
|
51
|
+
handle_error(err)
|
52
|
+
Failure(nil)
|
53
|
+
end
|
54
|
+
|
55
|
+
MATCH_ERROR = :error
|
56
|
+
|
57
|
+
def compare_call(origin_result, mirror_result)
|
58
|
+
cmp = strangler_action.compare.call(origin_result, mirror_result)
|
59
|
+
raise "Strangler action compare lambda must return a boolean value instead of #{cmp}!" unless cmp.in?([true, false])
|
60
|
+
Success(cmp)
|
61
|
+
rescue => err
|
62
|
+
handle_error(err)
|
63
|
+
Failure(nil)
|
64
|
+
end
|
65
|
+
|
66
|
+
def handle_error(err)
|
67
|
+
Sbmt::Strangler.error_tracker.error(err)
|
68
|
+
Sbmt::Strangler.logger.error(err)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Sbmt
|
6
|
+
module Strangler
|
7
|
+
module WorkModes
|
8
|
+
class Proxy < Base
|
9
|
+
def call
|
10
|
+
origin_response = http_request(http_params)
|
11
|
+
render_origin_response(origin_response)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
delegate :http_params, :http_request, :render_origin_response, to: :rails_controller
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Sbmt
|
6
|
+
module Strangler
|
7
|
+
module WorkModes
|
8
|
+
class Replace < Base
|
9
|
+
include Dry::Monads::Result::Mixin
|
10
|
+
|
11
|
+
def call
|
12
|
+
mirror_call_result = mirror_call
|
13
|
+
track_mirror_call(mirror_call_result.success?)
|
14
|
+
|
15
|
+
unless mirror_call_result.success?
|
16
|
+
render(
|
17
|
+
json: {error: "Mirror lambda call failed!"},
|
18
|
+
status: :internal_server_error
|
19
|
+
) # TODO: Возможно стоит сделать фолбэк на проксирование?
|
20
|
+
return
|
21
|
+
end
|
22
|
+
|
23
|
+
render_call_result = render_call(mirror_call_result.value!)
|
24
|
+
track_render_call(render_call_result.success?)
|
25
|
+
|
26
|
+
unless render_call_result.success?
|
27
|
+
render(
|
28
|
+
json: {error: "Render lambda call failed!"},
|
29
|
+
status: :internal_server_error
|
30
|
+
) # TODO: Возможно стоит сделать фолбэк на проксирование?
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
render render_call_result.value!
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
delegate :render, to: :rails_controller
|
40
|
+
delegate :track_mirror_call, :track_render_call, to: :metric_tracker
|
41
|
+
|
42
|
+
def mirror_call
|
43
|
+
value = strangler_action.mirror.call(rails_controller)
|
44
|
+
Success(value)
|
45
|
+
rescue => err
|
46
|
+
handle_error(err)
|
47
|
+
Failure(nil)
|
48
|
+
end
|
49
|
+
|
50
|
+
def render_call(mirror_result)
|
51
|
+
value = strangler_action.render.call(mirror_result)
|
52
|
+
Success(value)
|
53
|
+
rescue => err
|
54
|
+
handle_error(err)
|
55
|
+
Failure(nil)
|
56
|
+
end
|
57
|
+
|
58
|
+
def handle_error(err)
|
59
|
+
Sbmt::Strangler.error_tracker.error(err)
|
60
|
+
Sbmt::Strangler.logger.error(err)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails"
|
4
|
+
require "dry-monads"
|
5
|
+
require "yabeda"
|
6
|
+
require "faraday"
|
7
|
+
require "faraday/net_http_persistent"
|
8
|
+
require "flipper"
|
9
|
+
require "concurrent"
|
10
|
+
require "securerandom"
|
11
|
+
require "oj"
|
12
|
+
|
13
|
+
begin
|
14
|
+
require "sentry-rails"
|
15
|
+
rescue LoadError
|
16
|
+
# optional dependency
|
17
|
+
end
|
18
|
+
|
19
|
+
begin
|
20
|
+
require "opentelemetry-instrumentation-concurrent_ruby"
|
21
|
+
rescue LoadError
|
22
|
+
Warning.warn <<~WARN if Object.const_defined?(:Opentelemetry)
|
23
|
+
WARNING! It looks like you're using OpenTelemetry but you didn't install an instrumentation for the concurrent-ruby gem.
|
24
|
+
sbmt-strangler runs your code using concurrent-ruby futures in mirror mode, so this is very adviced to install
|
25
|
+
the instrumentation (opentelemetry-instrumentation-concurrent_ruby gem) to get traces in mirror mode!
|
26
|
+
WARN
|
27
|
+
end
|
28
|
+
|
29
|
+
require_relative "strangler/configurable"
|
30
|
+
require_relative "strangler/http"
|
31
|
+
require_relative "strangler/action"
|
32
|
+
require_relative "strangler/controller"
|
33
|
+
require_relative "strangler/configuration"
|
34
|
+
require_relative "strangler/mixin"
|
35
|
+
require_relative "strangler/builder"
|
36
|
+
require_relative "strangler/action_invoker"
|
37
|
+
require_relative "strangler/const_definer"
|
38
|
+
require_relative "strangler/errors"
|
39
|
+
require_relative "strangler/error_tracker"
|
40
|
+
require_relative "strangler/logger"
|
41
|
+
require_relative "strangler/flipper"
|
42
|
+
require_relative "strangler/feature_flags"
|
43
|
+
require_relative "strangler/metric_tracker"
|
44
|
+
require_relative "strangler/work_modes/proxy"
|
45
|
+
require_relative "strangler/work_modes/mirror"
|
46
|
+
require_relative "strangler/work_modes/replace"
|
47
|
+
|
48
|
+
require_relative "strangler/engine"
|
49
|
+
|
50
|
+
module Sbmt
|
51
|
+
module Strangler
|
52
|
+
module_function
|
53
|
+
|
54
|
+
# Public: Configure strangler.
|
55
|
+
#
|
56
|
+
# Sbmt::Strangler.configure do |config|
|
57
|
+
# config.controller(...) do |controller|
|
58
|
+
# controller.action(...) do {...}
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# Yields Sbmt::Strangler::Configuration instance.
|
63
|
+
def configure
|
64
|
+
yield configuration if block_given?
|
65
|
+
end
|
66
|
+
|
67
|
+
# Public: Returns Sbmt::Strangler::Configuration instance.
|
68
|
+
def configuration
|
69
|
+
@configuration ||= Configuration.new
|
70
|
+
end
|
71
|
+
|
72
|
+
def action_controller_base_class
|
73
|
+
@action_controller_base_class ||= configuration.action_controller_base_class.constantize
|
74
|
+
end
|
75
|
+
|
76
|
+
def error_tracker
|
77
|
+
@error_tracker ||= configuration.error_tracker.constantize
|
78
|
+
end
|
79
|
+
|
80
|
+
def logger
|
81
|
+
@logger ||= Sbmt::Strangler::Logger.new
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/sbmt/strangler/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "sbmt-strangler"
|
7
|
+
spec.version = Sbmt::Strangler::VERSION
|
8
|
+
spec.authors = ["sbermarket team"]
|
9
|
+
|
10
|
+
spec.summary = "Utility for strangler pattern"
|
11
|
+
spec.description = spec.summary
|
12
|
+
spec.homepage = "https://github.com/SberMarket-Tech/sbmt-strangler"
|
13
|
+
spec.required_ruby_version = ">= 3.1.0"
|
14
|
+
|
15
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/-/blob/master/CHANGELOG.md"
|
20
|
+
spec.metadata["rubygems_mfa_required"] = "false" # rubocop:disable Gemspec/RequireMFA
|
21
|
+
|
22
|
+
spec.files = Dir.chdir(__dir__) do
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
24
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_dependency "faraday", "> 2.0"
|
32
|
+
spec.add_dependency "faraday-net_http_persistent", "~> 2.0"
|
33
|
+
spec.add_dependency "net-http-persistent", ">= 4.0.1"
|
34
|
+
spec.add_dependency "rails", ">= 6.1", "< 8"
|
35
|
+
spec.add_dependency "yabeda", ">= 0.11"
|
36
|
+
spec.add_dependency "flipper", ">= 1.2.2"
|
37
|
+
spec.add_dependency "concurrent-ruby", ">= 1.2.3"
|
38
|
+
spec.add_dependency "oj"
|
39
|
+
spec.add_dependency "dry-monads"
|
40
|
+
spec.add_dependency "dry-struct"
|
41
|
+
|
42
|
+
spec.add_development_dependency "appraisal"
|
43
|
+
spec.add_development_dependency "bundler"
|
44
|
+
spec.add_development_dependency "combustion"
|
45
|
+
spec.add_development_dependency "rake"
|
46
|
+
spec.add_development_dependency "rspec"
|
47
|
+
spec.add_development_dependency "rspec-rails"
|
48
|
+
spec.add_development_dependency "rspec_junit_formatter"
|
49
|
+
spec.add_development_dependency "rswag-specs"
|
50
|
+
spec.add_development_dependency "rubocop"
|
51
|
+
spec.add_development_dependency "rubocop-rails"
|
52
|
+
spec.add_development_dependency "rubocop-rspec"
|
53
|
+
spec.add_development_dependency "rubocop-performance"
|
54
|
+
spec.add_development_dependency "vcr"
|
55
|
+
spec.add_development_dependency "standard", ">= 1.7"
|
56
|
+
spec.add_development_dependency "zeitwerk"
|
57
|
+
spec.add_development_dependency "sentry-rails", "> 5.2.0"
|
58
|
+
spec.add_development_dependency "debug"
|
59
|
+
end
|