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,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
|