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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e9bb8754c88d1b03e5d4a32bc244c6e14351d0e875dfd12bbf9011cb08c450ce
|
4
|
+
data.tar.gz: 3ac98da59470beb7f2d023352072bc589867e5af9d2160d07a805529577e7482
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e1d13adacfa0630878515a07ae6619ddd23d2182a1fb17183be233ef7c10e0552c8917404d9728961e2fc14d0aa68131c328245b111465a56370a6d8e3d87086
|
7
|
+
data.tar.gz: 405bf3d3146936259e97aeb52f6d0a5065d5080be4891b9cff97463fde3df32a6d998a5b1b023b630b1e1fc4c35dc353384dfcf09431f9549d7e641cd065ef69
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
2
|
+
|
3
|
+
# We want Exclude directives from different
|
4
|
+
# config files to get merged, not overwritten
|
5
|
+
inherit_mode:
|
6
|
+
merge:
|
7
|
+
- Exclude
|
8
|
+
|
9
|
+
AllCops:
|
10
|
+
TargetRubyVersion: 3.3
|
11
|
+
TargetRailsVersion: 7.1
|
12
|
+
NewCops: enable
|
13
|
+
SuggestExtensions: false
|
14
|
+
|
15
|
+
require:
|
16
|
+
# Performance cops are bundled with Standard
|
17
|
+
- rubocop-performance
|
18
|
+
- rubocop-rails
|
19
|
+
- rubocop-rspec
|
20
|
+
# Standard's config uses custom cops,
|
21
|
+
# so it must be loaded
|
22
|
+
- standard
|
23
|
+
|
24
|
+
inherit_gem:
|
25
|
+
standard: config/base.yml
|
26
|
+
|
27
|
+
# rubocop-rails
|
28
|
+
Rails/SkipsModelValidations:
|
29
|
+
Enabled: false
|
30
|
+
|
31
|
+
# rubocop-rspec
|
32
|
+
RSpec/AnyInstance:
|
33
|
+
Enabled: false
|
34
|
+
|
35
|
+
RSpec/MultipleExpectations:
|
36
|
+
Enabled: false
|
37
|
+
|
38
|
+
RSpec/LetSetup:
|
39
|
+
Enabled: false
|
40
|
+
|
41
|
+
RSpec/StubbedMock:
|
42
|
+
Enabled: false
|
43
|
+
|
44
|
+
RSpec/MessageSpies:
|
45
|
+
Enabled: false
|
46
|
+
|
47
|
+
RSpec/NestedGroups:
|
48
|
+
Max: 10
|
49
|
+
|
50
|
+
RSpec/ExampleLength:
|
51
|
+
Max: 35
|
52
|
+
|
53
|
+
RSpec/MultipleMemoizedHelpers:
|
54
|
+
Max: 15
|
data/.rubocop_todo.yml
ADDED
File without changes
|
data/Appraisals
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# See compatibility table at https://www.fastruby.io/blog/ruby/rails/versions/compatibility-table.html
|
4
|
+
|
5
|
+
versions_map = {
|
6
|
+
"7.0" => %w[3.1],
|
7
|
+
"7.1" => %w[3.2 3.3]
|
8
|
+
}
|
9
|
+
|
10
|
+
current_ruby_version = RUBY_VERSION.split(".").first(2).join(".")
|
11
|
+
|
12
|
+
versions_map.each do |rails_version, ruby_versions|
|
13
|
+
ruby_versions.each do |ruby_version|
|
14
|
+
next if ruby_version != current_ruby_version
|
15
|
+
|
16
|
+
appraise "railties-#{ruby_version}-#{rails_version}" do
|
17
|
+
gem "railties", "~> #{rails_version}.0"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
6
|
+
and this project adheres to [Semantic Versioning](http://semver.org/).
|
7
|
+
|
8
|
+
## [0.9.1] - 2024-07-02
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- Add put request support
|
12
|
+
|
13
|
+
## [0.9.0] - 2024-06-06
|
14
|
+
|
15
|
+
### Added
|
16
|
+
- Add handler for Faraday::ConnectionFailed
|
17
|
+
- Add retry connection support
|
18
|
+
|
19
|
+
## [0.8.0] - 2024-06-05
|
20
|
+
|
21
|
+
### Added
|
22
|
+
- Add `action.render = ->(mirror_result) { ... }` lambda
|
23
|
+
|
24
|
+
## [0.7.0] - 2024-06-03
|
25
|
+
|
26
|
+
### Added
|
27
|
+
- allow to configure timeout per route
|
28
|
+
|
29
|
+
### Changed
|
30
|
+
- rename "proxy_http_verb" to "proxy_http_method"
|
31
|
+
|
32
|
+
## [0.6.0] - 2024-05-30
|
33
|
+
|
34
|
+
### Added
|
35
|
+
- add support for enabling work mode by request header
|
36
|
+
|
37
|
+
## [0.5.0] - 2024-05-22
|
38
|
+
|
39
|
+
### Added
|
40
|
+
- use [net_http_persistent](https://github.com/lostisland/faraday-net_http_persistent) for proxying
|
41
|
+
|
42
|
+
## [0.4.0] - 2024-05-22
|
43
|
+
|
44
|
+
### Added
|
45
|
+
- enable replace mode (it was temporarily commented out in the source code)
|
46
|
+
- in the replace mode a value returned from action.mirror block is passed directly to the #render method
|
47
|
+
- action.flipper_actor lambda can now return an array of actor IDs instead of a single actor ID
|
48
|
+
|
49
|
+
### Changed
|
50
|
+
- automatic feature flags name now has BEM-like formatting: "controller-path__action-name--work-mode" (because of Flipper UI limitations/recomendations)
|
51
|
+
- action.compare is called for failed origin/proxy responses too
|
52
|
+
- the first argument of action.compare block (called origin_result) is a hash of the form {body:, status:, headers:}
|
53
|
+
- metric renamed: sbmt_strangler_compare_result -> sbmt_strangler_compare_call_result
|
54
|
+
|
55
|
+
## [0.3.1] - 2024-04-24
|
56
|
+
|
57
|
+
### Fixed
|
58
|
+
- Create feature flags after the app fully initialized and handle flag creation errors
|
59
|
+
|
60
|
+
## [0.3.0] - 2024-04-22
|
61
|
+
|
62
|
+
### Added
|
63
|
+
- add support for mirror and replace modes
|
64
|
+
- add config options: action.mirror, action.compare, configuration.flipper_actor
|
65
|
+
- add to Mixin: #track_mirror_call, #track_compare_call, #track_compare_result
|
66
|
+
|
67
|
+
### Changed
|
68
|
+
- rename Mixin#render_proxy_response to Mixin#render_origin_response
|
69
|
+
- rename Mixin#track_work_tactic to Mixin#track_work_mode
|
70
|
+
- Sbmt::Strangler::Http::Transport#get|post_request returns response body as String (it skips parsing body as JSON).
|
71
|
+
|
72
|
+
### Fixed
|
73
|
+
- Fix from v0.2.2 ported to v0.3.0
|
74
|
+
|
75
|
+
## [0.2.2] - 2024-04-19
|
76
|
+
|
77
|
+
### Fixed
|
78
|
+
- filter out IDs like 'R608473650' from Faraday metric 'path' tag
|
79
|
+
|
80
|
+
## [0.2.0] - 2024-04-09
|
81
|
+
|
82
|
+
### Added
|
83
|
+
- add metrics for http client request
|
84
|
+
|
85
|
+
### Changed
|
86
|
+
|
87
|
+
### Fixed
|
88
|
+
|
89
|
+
## [0.1.1] - 2024-04-08
|
90
|
+
|
91
|
+
### Added
|
92
|
+
|
93
|
+
### Changed
|
94
|
+
|
95
|
+
### Fixed
|
96
|
+
- ignore inherit constants in Sbmt::Strangler::ConstDefiner
|
97
|
+
|
98
|
+
## [0.1.0] - 2024-04-05
|
99
|
+
|
100
|
+
### Added
|
101
|
+
- initial release with proxy tooling
|
102
|
+
|
103
|
+
### Changed
|
104
|
+
|
105
|
+
### Fixed
|
106
|
+
- ignore inherit constants in Sbmt::Strangler::ConstDefiner
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 SberMarket Tech
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# Sbmt-strangler
|
2
|
+
|
3
|
+
Sbmt-strangler is an implementation of Strangler-pattern for replacing legacy system http endpoints with three stages: proxying, mirroring, replacing.
|
4
|
+
|
5
|
+
## Strangler Pattern
|
6
|
+
|
7
|
+
[The strangler pattern](https://microservices.io/patterns/refactoring/strangler-application.html) is a software design and architectural pattern used in the context of legacy system modernization or application migration. The pattern’s name comes from the way certain plants, like strangler figs, grow around host trees, eventually replacing or “strangling” them entirely. Similarly, the Strangler Pattern aims to replace or evolve an existing system gradually, without causing any significant disruptions to the overall system functionality.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- **Automatic proxying**: with help of DSL, the proxying algorithm is effortlessly implemented. A Rails controller and a proxying client are dynamically created. All you need to provide is the processed URL and the address of the original service (refine Rails route is also necessary).
|
12
|
+
- **Observability** - a comprehensive observability suite, with native support for [OpenTelementry](https://github.com/open-telemetry/opentelemetry-ruby), alongside proxy metrics based on the [Yabeda](https://github.com/yabeda-rb/yabeda) framework.
|
13
|
+
- **Smooth Mode Switching** allows you to safely and smoothly switch between modes using the [Flipper](https://github.com/flippercloud/flipper)-based feature flags.
|
14
|
+
- **Mirroring mode metrics** aid in tracking the accuracy and completeness of transferring business logic to a new service by comparing responses from both the legacy system and the new service.
|
15
|
+
|
16
|
+
# Usage
|
17
|
+
|
18
|
+
## Proxying
|
19
|
+
|
20
|
+
*Proxying* mode is a setup where traffic destined for the legacy system is first routed through the service, only to be fully redirected back to the legacy system. While the service doesn't offer any extra functionalities, it does gain control over the traffic, allowing for future replacements.
|
21
|
+
|
22
|
+
![Proxy-mode](docs/img/01-proxy_mode.png).
|
23
|
+
|
24
|
+
To determine the proxy mode, you need to:
|
25
|
+
|
26
|
+
1. Set the service path in the Rails controller, which will be generated automatically.
|
27
|
+
2. Specify the path to the legacy system and the HTTP verbs used.
|
28
|
+
3. Optionally, define a whitelist of proxied HTTP headers, as business logic can depend on them (original params & headers are available)
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
Sbmt::Strangler.configure do |strangler|
|
32
|
+
strangler.controller("api/hello") do |controller|
|
33
|
+
controller.headers_allowlist = %w[HTTP_API_VERSION HTTP_USER_AGENT HTTP_X_REQUEST_ID]
|
34
|
+
controller.params_tracking_allowlist = %w[name surname]
|
35
|
+
|
36
|
+
controller.action("index") do |action|
|
37
|
+
action.proxy_url = "http://example.com:8080/api/hello"
|
38
|
+
action.proxy_http_method = :post
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
## Mirroring
|
44
|
+
|
45
|
+
*Mirroring* is an approach where requests are simultaneously processed by both the service and the monolith. The outcomes are then compared to calculate an accuracy metric, enabling timely monitoring of the service's endpoint performance. Importantly, the client receives the result from the monolith, ensuring that mirroring does not affect user experience.
|
46
|
+
|
47
|
+
![Mirror-mode](docs/img/02-mirror_mode.png).
|
48
|
+
|
49
|
+
To determine the mirroring mode, it is necessary to describe the business logic of the endpoint which will run in parallel with the request's proxying in the legacy system. Depending on the result, the success/failure metric of the mirroring logic will be logged.
|
50
|
+
|
51
|
+
The mirroring mode involves comparing the results of the response from the legacy system with the results of calculations from the new implementation. To make this comparison, it is necessary to describe the corresponding lambda (compare).
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
Sbmt::Strangler.configure do |strangler|
|
55
|
+
strangler.controller("api/hello") do |controller|
|
56
|
+
controller.headers_allowlist = %w[HTTP_API_VERSION HTTP_USER_AGENT HTTP_X_REQUEST_ID]
|
57
|
+
controller.params_tracking_allowlist = %w[name surname]
|
58
|
+
|
59
|
+
controller.action("index") do |action|
|
60
|
+
action.proxy_url = "http://example.com:8080/api/hello"
|
61
|
+
action.proxy_http_method = :post
|
62
|
+
|
63
|
+
# new implementation of legacy system business logic
|
64
|
+
action.mirror = ->(rails_controller) do
|
65
|
+
name, surname = rails_controller.params.values_at(:name, :surname)
|
66
|
+
{json: {greeting: "Hello, #{name} #{surname}!"}}
|
67
|
+
end
|
68
|
+
|
69
|
+
# compare legacy system response and mirror lambda calculation result
|
70
|
+
action.compare = ->(origin_result, mirror_result) do
|
71
|
+
origin_result[:status] == 200 &&
|
72
|
+
JSON.parse(origin_result[:body]) == mirror_result[:json]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
## Replacement
|
80
|
+
|
81
|
+
*Replacement* refers to the process of substituting the functionality of a legacy system with a new implementation. In this scenario, the legacy system can either be completely removed from the framework or utilized to enhance the response outcomes of the new service.
|
82
|
+
|
83
|
+
![Replace-mode](docs/img/03-replace_mode.png).
|
84
|
+
|
85
|
+
|
86
|
+
To enable replacement mode, use the feature flag. Replacement mode uses mirror lambda for replacement logic.
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
module Metrics
|
6
|
+
module Yabeda
|
7
|
+
HTTP_BUCKETS = [0.01, 0.02, 0.04, 0.1, 0.2, 0.5, 0.8, 1, 1.5, 2, 5, 15, 30, 60].freeze
|
8
|
+
|
9
|
+
::Yabeda.configure do
|
10
|
+
group :sbmt_strangler do
|
11
|
+
histogram :http_request_duration,
|
12
|
+
tags: %i[name method status host path],
|
13
|
+
unit: :seconds,
|
14
|
+
buckets: HTTP_BUCKETS,
|
15
|
+
comment: "HTTP client request duration"
|
16
|
+
|
17
|
+
counter :params_usage,
|
18
|
+
comment: "Parameters usage counter",
|
19
|
+
tags: %i[params controller action]
|
20
|
+
counter :work_mode,
|
21
|
+
comment: "Work mode counter (mode: proxy, mirror, replace)",
|
22
|
+
tags: %i[mode params controller action]
|
23
|
+
counter :mirror_call,
|
24
|
+
comment: "Mirror lambda call counter (success: true, false)",
|
25
|
+
tags: %i[success params controller action]
|
26
|
+
counter :compare_call,
|
27
|
+
comment: "Compare lambda call counter (success: true, false)",
|
28
|
+
tags: %i[success params controller action]
|
29
|
+
counter :compare_call_result,
|
30
|
+
comment: "Compare lambda call result counter (value: true, false)",
|
31
|
+
tags: %i[value params controller action]
|
32
|
+
counter :render_call,
|
33
|
+
comment: "Render lambda call counter (success: true, false)",
|
34
|
+
tags: %i[success params controller action]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/dip.yml
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
version: '7'
|
2
|
+
|
3
|
+
environment:
|
4
|
+
RUBY_VERSION: '3.3'
|
5
|
+
|
6
|
+
compose:
|
7
|
+
files:
|
8
|
+
- docker-compose.yml
|
9
|
+
|
10
|
+
interaction:
|
11
|
+
bash:
|
12
|
+
description: Open the Bash shell in app's container
|
13
|
+
service: ruby
|
14
|
+
command: /bin/bash
|
15
|
+
|
16
|
+
bundle:
|
17
|
+
description: Run Bundler commands
|
18
|
+
service: ruby
|
19
|
+
command: bundle
|
20
|
+
|
21
|
+
rails:
|
22
|
+
description: Run RoR commands
|
23
|
+
service: ruby
|
24
|
+
command: bundle exec rails
|
25
|
+
|
26
|
+
appraisal:
|
27
|
+
description: Run Appraisal commands
|
28
|
+
service: ruby
|
29
|
+
command: bundle exec appraisal
|
30
|
+
|
31
|
+
swaggerize:
|
32
|
+
description: Build swagger docs
|
33
|
+
service: ruby
|
34
|
+
environment:
|
35
|
+
RAILS_ENV: test
|
36
|
+
command: bundle exec rspec --format Rswag::Specs::SwaggerFormatter --order defined spec/requests/
|
37
|
+
|
38
|
+
rspec:
|
39
|
+
description: Run Rspec commands
|
40
|
+
service: ruby
|
41
|
+
command: bundle exec rspec
|
42
|
+
subcommands:
|
43
|
+
all:
|
44
|
+
command: bundle exec appraisal rspec
|
45
|
+
rails-6.1:
|
46
|
+
command: bundle exec appraisal rails-6.1 rspec
|
47
|
+
rails-7.0:
|
48
|
+
command: bundle exec appraisal rails-7.0 rspec
|
49
|
+
rails-7.1:
|
50
|
+
command: bundle exec appraisal rails-7.1 rspec
|
51
|
+
|
52
|
+
rubocop:
|
53
|
+
description: Run Ruby linter
|
54
|
+
service: ruby
|
55
|
+
command: bundle exec rubocop
|
56
|
+
|
57
|
+
setup:
|
58
|
+
description: Install deps
|
59
|
+
service: ruby
|
60
|
+
command: bin/setup
|
61
|
+
|
62
|
+
provision:
|
63
|
+
- dip compose down --volumes
|
64
|
+
- cp -f lefthook-local.dip_example.yml lefthook-local.yml
|
65
|
+
- rm -f Gemfile.lock
|
66
|
+
- rm -f gemfiles/*gemfile*
|
67
|
+
- dip setup
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
services:
|
2
|
+
ruby:
|
3
|
+
image: ${RUBY_DOCKER_IMAGE:-ruby}:${RUBY_VERSION:-3.3}
|
4
|
+
environment:
|
5
|
+
HISTFILE: /app/tmp/.bash_history
|
6
|
+
BUNDLE_PATH: /usr/local/bundle
|
7
|
+
BUNDLE_CONFIG: /app/.bundle/config
|
8
|
+
prometheus_multiproc_dir: ./tmp
|
9
|
+
command: bash
|
10
|
+
working_dir: /app
|
11
|
+
volumes:
|
12
|
+
- .:/app:cached
|
13
|
+
- ${RUBYGEMS_PATH:-..}:/app/vendor/gems:cached
|
14
|
+
- bundler_data:/usr/local/bundle
|
15
|
+
tmpfs:
|
16
|
+
- /tmp
|
17
|
+
|
18
|
+
volumes:
|
19
|
+
bundler_data:
|
Binary file
|
Binary file
|
Binary file
|
data/lefthook.yml
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
class Action
|
6
|
+
extend Sbmt::Strangler::Configurable
|
7
|
+
|
8
|
+
option :params_tracking_allowlist, :headers_allowlist, :flipper_actor, default_from: :controller
|
9
|
+
option :proxy_url
|
10
|
+
option :proxy_http_method, default: :get
|
11
|
+
option :mirror, default: ->(_rails_controller) {}
|
12
|
+
option :compare, default: ->(_origin_result, _mirror_result) { false }
|
13
|
+
option :render, default: ->(mirror_result) { mirror_result }
|
14
|
+
|
15
|
+
attr_reader :name, :controller
|
16
|
+
|
17
|
+
def initialize(name, controller, &)
|
18
|
+
@name = name
|
19
|
+
@controller = controller
|
20
|
+
|
21
|
+
yield(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
def full_name
|
25
|
+
"#{controller.name}##{name}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def http
|
29
|
+
@http ||= ActiveSupport::InheritableOptions.new(controller.http)
|
30
|
+
end
|
31
|
+
|
32
|
+
def http_client
|
33
|
+
@http_client ||= Sbmt::Strangler::Http::Client.new(http_options: http)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
class ActionInvoker
|
6
|
+
attr_reader :rails_controller, :strangler_action, :metric_tracker, :feature_flags
|
7
|
+
|
8
|
+
def initialize(rails_controller:, strangler_action:, metric_tracker:, feature_flags:)
|
9
|
+
@rails_controller = rails_controller
|
10
|
+
@strangler_action = strangler_action
|
11
|
+
@metric_tracker = metric_tracker
|
12
|
+
@feature_flags = feature_flags
|
13
|
+
end
|
14
|
+
|
15
|
+
delegate :track_params_usage, :track_work_mode, :log_unallowed_params, to: :metric_tracker
|
16
|
+
|
17
|
+
def call
|
18
|
+
track_params_usage
|
19
|
+
log_unallowed_params
|
20
|
+
track_work_mode(work_mode_class.name.demodulize.underscore)
|
21
|
+
work_mode_class.new(
|
22
|
+
rails_controller:,
|
23
|
+
strangler_action:,
|
24
|
+
metric_tracker:,
|
25
|
+
feature_flags:
|
26
|
+
).call
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def work_mode_class
|
32
|
+
@work_mode_class ||=
|
33
|
+
if feature_flags.replace?
|
34
|
+
Sbmt::Strangler::WorkModes::Replace
|
35
|
+
elsif feature_flags.mirror?
|
36
|
+
Sbmt::Strangler::WorkModes::Mirror
|
37
|
+
else
|
38
|
+
Sbmt::Strangler::WorkModes::Proxy
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
class Builder
|
6
|
+
attr_reader :configuration
|
7
|
+
|
8
|
+
# @param configuration [Sbmt::Strangler::Configuration]
|
9
|
+
def initialize(configuration)
|
10
|
+
@configuration = configuration
|
11
|
+
end
|
12
|
+
|
13
|
+
def call!
|
14
|
+
build_controllers
|
15
|
+
add_feature_flags
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.call!(configuration = Sbmt::Strangler.configuration)
|
19
|
+
new(configuration).call!
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def build_controllers
|
25
|
+
configuration.controllers.each do |controller|
|
26
|
+
unless Object.const_defined?(controller.class_name)
|
27
|
+
Sbmt::Strangler::ConstDefiner.call!(controller.class_name, Class.new(Sbmt::Strangler.action_controller_base_class))
|
28
|
+
end
|
29
|
+
|
30
|
+
controller.class_name.constantize.class_eval do
|
31
|
+
include Sbmt::Strangler::Mixin
|
32
|
+
|
33
|
+
controller.actions.each do |action|
|
34
|
+
define_method(action.name) do
|
35
|
+
@strangler_action = action
|
36
|
+
|
37
|
+
Sbmt::Strangler::ActionInvoker.new(
|
38
|
+
rails_controller: self,
|
39
|
+
strangler_action: action,
|
40
|
+
metric_tracker: Sbmt::Strangler::MetricTracker.new(self),
|
41
|
+
feature_flags: Sbmt::Strangler::FeatureFlags.new(
|
42
|
+
rails_controller: self,
|
43
|
+
strangler_action: action
|
44
|
+
)
|
45
|
+
).call
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def add_feature_flags
|
53
|
+
configuration.controllers.each do |controller|
|
54
|
+
controller.actions.each do |action|
|
55
|
+
Sbmt::Strangler::FeatureFlags.new(strangler_action: action).add_all!
|
56
|
+
rescue => error
|
57
|
+
Sbmt::Strangler.logger.log_warn(
|
58
|
+
"Unable to add feature flags for action #{action.full_name}: #{error}",
|
59
|
+
error_class: error.class.name
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Strangler
|
5
|
+
module Configurable
|
6
|
+
def option(*hash)
|
7
|
+
case hash
|
8
|
+
in [*attributes, Hash => options]
|
9
|
+
in [*attributes]
|
10
|
+
options = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
attributes.each do |attribute|
|
14
|
+
define_method :"#{attribute}=" do |value|
|
15
|
+
instance_variable_set(:"@#{attribute}", value)
|
16
|
+
end
|
17
|
+
|
18
|
+
define_method attribute.to_s do
|
19
|
+
value = instance_variable_get(:"@#{attribute}")
|
20
|
+
return value if value
|
21
|
+
|
22
|
+
if options[:default_from]
|
23
|
+
value = send(options[:default_from])&.public_send(attribute)
|
24
|
+
end
|
25
|
+
|
26
|
+
value || options[:default]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|