ekylibre-multi_tenancy 0.2.0
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/ekylibre-multi_tenancy.gemspec +32 -0
- data/lib/ekylibre-multi_tenancy.rb +16 -0
- data/lib/ekylibre/multi_tenancy/console_helper.rb +27 -0
- data/lib/ekylibre/multi_tenancy/middleware/container_aware_rack_middleware.rb +105 -0
- data/lib/ekylibre/multi_tenancy/middleware/sidekiq_middleware.rb +45 -0
- data/lib/ekylibre/multi_tenancy/middleware/sidekiq_middleware/container_aware_client_middleware.rb +19 -0
- data/lib/ekylibre/multi_tenancy/middleware/sidekiq_middleware/container_aware_server_middleware.rb +30 -0
- data/lib/ekylibre/multi_tenancy/multi_tenancy_plugin.rb +48 -0
- data/lib/ekylibre/multi_tenancy/rails/railtie.rb +29 -0
- data/lib/ekylibre/multi_tenancy/tenant.rb +22 -0
- data/lib/ekylibre/multi_tenancy/tenant_repository.rb +79 -0
- data/lib/ekylibre/multi_tenancy/tenant_stack.rb +32 -0
- data/lib/ekylibre/multi_tenancy/tenant_switcher.rb +81 -0
- data/lib/ekylibre/multi_tenancy/testing/mock_apartment.rb +15 -0
- data/lib/ekylibre/multi_tenancy/version.rb +7 -0
- metadata +198 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e5b1cf84d71e00f4d04863e55a50295ea7d4858ce81eff7e032920c27e06fa18
|
4
|
+
data.tar.gz: c51f6ae7c6d84d0f3f48e0051cb45f35b828174e86e277daea45f230b9db9623
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b6ca1863661aaa5236367e5eee3c5d9cff6cf73a95d37adb88604842e51858f403b5ce762db4c1e552d69ab107cc1afa05177f187a15fbb4cfc81605036202c2
|
7
|
+
data.tar.gz: c6671183e662add0049036eb4a448e794960f71244e7a0998f2174a56efa150d2cc69cb08c78821dcf7b08119186627e84cf36b5ae387b435fbbe4c9e6b33149
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/ekylibre/multi_tenancy/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'ekylibre-multi_tenancy'
|
7
|
+
spec.version = Ekylibre::MultiTenancy::VERSION
|
8
|
+
spec.authors = ['Ekylibre developers']
|
9
|
+
spec.email = ['dev@ekylibre.com']
|
10
|
+
|
11
|
+
spec.summary = 'Apartment integration as a plugin for Ekylibre'
|
12
|
+
spec.required_ruby_version = '>= 2.6.0'
|
13
|
+
spec.homepage = 'https://www.ekylibre.com'
|
14
|
+
spec.license = 'AGPL-3.0-only'
|
15
|
+
|
16
|
+
spec.files = Dir.glob(%w[lib/**/* *.gemspec])
|
17
|
+
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
# Needed to fix a bundler problem where is tries to install version 3.2.14 even though the plugin system requires railties > 4
|
21
|
+
spec.add_dependency 'activerecord', '> 4'
|
22
|
+
spec.add_dependency 'apartment', '~> 2.2.0'
|
23
|
+
spec.add_dependency 'ekylibre-plugin_system', '~> 0.5.1'
|
24
|
+
spec.add_dependency 'public_suffix', '~> 4.0.6'
|
25
|
+
spec.add_dependency 'zeitwerk', '~> 2.4.0'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bundler', '>= 1.17'
|
28
|
+
spec.add_development_dependency 'minitest', '~> 5.14'
|
29
|
+
spec.add_development_dependency 'minitest-reporters', '~> 1.4'
|
30
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
31
|
+
spec.add_development_dependency 'rubocop', '~> 1.3.0'
|
32
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'apartment'
|
4
|
+
require 'ekylibre-plugin_system'
|
5
|
+
require 'zeitwerk'
|
6
|
+
|
7
|
+
module Ekylibre # :nodoc:
|
8
|
+
end
|
9
|
+
|
10
|
+
loader = Zeitwerk::Loader.for_gem
|
11
|
+
loader.ignore(__FILE__)
|
12
|
+
loader.ignore("#{__dir__}/multi_tenancy/rails/**/*.rb")
|
13
|
+
loader.ignore("#{__dir__}/multi_tenancy/testing/**/*.rb")
|
14
|
+
loader.setup
|
15
|
+
|
16
|
+
require_relative 'ekylibre/multi_tenancy/rails/railtie' if defined?(::Rails)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ekylibre
|
4
|
+
module MultiTenancy
|
5
|
+
# Helper methods to switch tenants in the Rails console
|
6
|
+
module ConsoleHelper
|
7
|
+
# @param [String] name
|
8
|
+
def switch_tenant!(name)
|
9
|
+
container.get(TenantSwitcher)
|
10
|
+
.switch!(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def leave_tenant!
|
14
|
+
container.get(TenantSwitcher)
|
15
|
+
.leave!
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [String] name
|
19
|
+
def switch_tenant(name, &block)
|
20
|
+
return if block.nil?
|
21
|
+
|
22
|
+
container.get(TenantSwitcher)
|
23
|
+
.switch(name) { block.call }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack/request'
|
4
|
+
|
5
|
+
# Heavily inspired from Apartment::Elevators::Generic but with the ability to
|
6
|
+
module Ekylibre
|
7
|
+
module MultiTenancy
|
8
|
+
module Middleware
|
9
|
+
# Provides a rack based tenant switching solution based on request
|
10
|
+
#
|
11
|
+
class ContainerAwareRackMiddleware
|
12
|
+
class << self
|
13
|
+
attr_writer :excluded_subdomains
|
14
|
+
|
15
|
+
def excluded_subdomains
|
16
|
+
@excluded_subdomains ||= []
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(app)
|
21
|
+
@app = app
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(env)
|
25
|
+
request = Rack::Request.new(env)
|
26
|
+
container = env.fetch('container').dup
|
27
|
+
|
28
|
+
PluginSystem::GlobalContainer.replace_with(container) do
|
29
|
+
env['container'] = container
|
30
|
+
|
31
|
+
database = extract_tenant(request)
|
32
|
+
|
33
|
+
begin
|
34
|
+
if database
|
35
|
+
container
|
36
|
+
.get(TenantSwitcher)
|
37
|
+
.switch(database) { @app.call(env) }
|
38
|
+
else
|
39
|
+
@app.call(env)
|
40
|
+
end
|
41
|
+
rescue ::Apartment::TenantNotFound
|
42
|
+
Rails.logger.error "Apartment Tenant not found: #{subdomain(request.host)}"
|
43
|
+
return [404, { 'Content-Type' => 'text/html' }, [File.read(Rails.root.join('public', '404.html'))]]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def extract_tenant(request)
|
49
|
+
if (tenant = ENV['tenant'])
|
50
|
+
tenant
|
51
|
+
elsif ::Rails.env.test?
|
52
|
+
'test'
|
53
|
+
elsif ENV['ELEVATOR'] == 'header'
|
54
|
+
request.env['HTTP_X_TENANT']
|
55
|
+
else
|
56
|
+
extract_subdomain(request)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def extract_subdomain(request)
|
63
|
+
request_subdomain = subdomain(request.host)
|
64
|
+
|
65
|
+
# If the domain acquired is set to be excluded, set the tenant to whatever is currently
|
66
|
+
# next in line in the schema search path.
|
67
|
+
tenant = if self.class.excluded_subdomains.include?(request_subdomain)
|
68
|
+
nil
|
69
|
+
else
|
70
|
+
request_subdomain
|
71
|
+
end
|
72
|
+
|
73
|
+
tenant.presence
|
74
|
+
end
|
75
|
+
|
76
|
+
# *Almost* a direct ripoff of ActionDispatch::Request subdomain methods
|
77
|
+
|
78
|
+
# Only care about the first subdomain for the database name
|
79
|
+
def subdomain(host)
|
80
|
+
subdomains(host).first
|
81
|
+
end
|
82
|
+
|
83
|
+
def subdomains(host)
|
84
|
+
host_valid?(host) ? parse_host(host) : []
|
85
|
+
end
|
86
|
+
|
87
|
+
def host_valid?(host)
|
88
|
+
!ip_host?(host) && domain_valid?(host)
|
89
|
+
end
|
90
|
+
|
91
|
+
def ip_host?(host)
|
92
|
+
!/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/.match(host).nil?
|
93
|
+
end
|
94
|
+
|
95
|
+
def domain_valid?(host)
|
96
|
+
PublicSuffix.valid?(host, ignore_private: true)
|
97
|
+
end
|
98
|
+
|
99
|
+
def parse_host(host)
|
100
|
+
(PublicSuffix.parse(host, ignore_private: true).trd || '').split('.')
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Heavily inspired from apartment-sidekiq
|
4
|
+
module Ekylibre
|
5
|
+
module MultiTenancy
|
6
|
+
module Middleware
|
7
|
+
# Module containing Client ans Server Sidekiq middlewares for the MultiTenancy plugin
|
8
|
+
module SidekiqMiddleware
|
9
|
+
class << self
|
10
|
+
def setup
|
11
|
+
::Sidekiq.configure_client do |config|
|
12
|
+
configure_client(config)
|
13
|
+
end
|
14
|
+
|
15
|
+
::Sidekiq.configure_server do |config|
|
16
|
+
configure_client(config)
|
17
|
+
|
18
|
+
configure_server(config)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def configure_client(config)
|
25
|
+
config.client_middleware do |chain|
|
26
|
+
chain.insert_after(
|
27
|
+
Ekylibre::PluginSystem::Middleware::SidekiqMiddleware::ClientMiddleware,
|
28
|
+
ContainerAwareClientMiddleware
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def configure_server(config)
|
34
|
+
config.server_middleware do |chain|
|
35
|
+
chain.insert_after(
|
36
|
+
Ekylibre::PluginSystem::Middleware::SidekiqMiddleware::ServerMiddleware,
|
37
|
+
ContainerAwareServerMiddleware
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/ekylibre/multi_tenancy/middleware/sidekiq_middleware/container_aware_client_middleware.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Heavily inspired from apartment-sidekiq
|
4
|
+
module Ekylibre
|
5
|
+
module MultiTenancy
|
6
|
+
module Middleware
|
7
|
+
module SidekiqMiddleware
|
8
|
+
# Client Sidekiq middleware to add the name of the current tenant to the job context
|
9
|
+
class ContainerAwareClientMiddleware
|
10
|
+
def call(worker_class, item, queue, redis_pool = nil)
|
11
|
+
item['tenant'] ||= item.fetch('container').get(TenantStack).current&.name
|
12
|
+
|
13
|
+
yield
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/ekylibre/multi_tenancy/middleware/sidekiq_middleware/container_aware_server_middleware.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Heavily inspired from apartment-sidekiq
|
4
|
+
module Ekylibre
|
5
|
+
module MultiTenancy
|
6
|
+
module Middleware
|
7
|
+
module SidekiqMiddleware
|
8
|
+
# Server Sidekiq middleware switching to the correct tenant (if any) based on the job context
|
9
|
+
class ContainerAwareServerMiddleware
|
10
|
+
def call(worker_class, item, queue)
|
11
|
+
tenant = item.fetch('tenant', nil)
|
12
|
+
container = item.fetch('container').dup
|
13
|
+
|
14
|
+
PluginSystem::GlobalContainer.replace_with(container) do
|
15
|
+
item['container'] = container
|
16
|
+
|
17
|
+
if tenant.nil?
|
18
|
+
yield
|
19
|
+
else
|
20
|
+
container
|
21
|
+
.get(TenantSwitcher)
|
22
|
+
.switch(tenant) { yield }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
using Corindon::DependencyInjection::Injectable
|
4
|
+
|
5
|
+
module Ekylibre
|
6
|
+
module MultiTenancy
|
7
|
+
# Plugin adding multi-tenancy to Ekylibre
|
8
|
+
class MultiTenancyPlugin < Ekylibre::PluginSystem::Plugin
|
9
|
+
injectable do
|
10
|
+
args(
|
11
|
+
app: Ekylibre::PluginSystem::Parameters::RAILS_APPLICATION,
|
12
|
+
engine: param(Ekylibre::MultiTenancy::Rails::Railtie),
|
13
|
+
)
|
14
|
+
tag 'ekylibre.system.plugin'
|
15
|
+
end
|
16
|
+
|
17
|
+
APARTMENT = make_parameter('apartment')
|
18
|
+
PRIVATE_ROOT = make_parameter('private_root')
|
19
|
+
TENANTS_FILE = make_parameter('tenants_file')
|
20
|
+
|
21
|
+
def initialize(app:, engine:)
|
22
|
+
super(engine: engine)
|
23
|
+
|
24
|
+
@app = app
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [Corindon::DependencyInjection::Container] container
|
28
|
+
def boot(container)
|
29
|
+
container.set_parameter(APARTMENT.key, ::Apartment::Tenant)
|
30
|
+
container.set_parameter(PRIVATE_ROOT.key, app.root.join('private'))
|
31
|
+
container.set_parameter(TENANTS_FILE.key, app.root.join('config', 'tenants.yml'))
|
32
|
+
|
33
|
+
container.add_definition(TenantRepository)
|
34
|
+
container.add_definition(TenantStack)
|
35
|
+
container.add_definition(TenantSwitcher)
|
36
|
+
end
|
37
|
+
|
38
|
+
def version
|
39
|
+
MultiTenancy::VERSION
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# @return [::Rails::Application]
|
45
|
+
attr_reader :app
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ekylibre
|
4
|
+
module MultiTenancy
|
5
|
+
module Rails
|
6
|
+
# Railtie to integrate the MultiTenancy Plugin into a Rails application
|
7
|
+
class Railtie < ::Rails::Railtie
|
8
|
+
extend PluginSystem::PluginRegistration
|
9
|
+
|
10
|
+
register_plugin(MultiTenancyPlugin)
|
11
|
+
|
12
|
+
initializer(:register_middleware, after: :engines_blank_point) do |app|
|
13
|
+
app.config.middleware.insert_after(
|
14
|
+
Ekylibre::PluginSystem::Middleware::RackMiddleware,
|
15
|
+
Ekylibre::MultiTenancy::Middleware::ContainerAwareRackMiddleware
|
16
|
+
)
|
17
|
+
|
18
|
+
Middleware::SidekiqMiddleware.setup if defined?(::Sidekiq)
|
19
|
+
end
|
20
|
+
|
21
|
+
# rubocop:disable Lint/Debugger
|
22
|
+
console do
|
23
|
+
::Rails::ConsoleMethods.send :include, ConsoleHelper
|
24
|
+
end
|
25
|
+
# rubocop:enable Lint/Debugger
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
using Corindon::DependencyInjection::Injectable
|
4
|
+
|
5
|
+
module Ekylibre
|
6
|
+
module MultiTenancy
|
7
|
+
# Represents a tenant in the MultiTenancy plugin
|
8
|
+
class Tenant
|
9
|
+
# @return [String]
|
10
|
+
attr_reader :name
|
11
|
+
# @return [Pathname]
|
12
|
+
attr_reader :private_directory
|
13
|
+
|
14
|
+
# @param [String] name
|
15
|
+
# @param [Pathname] private_directory
|
16
|
+
def initialize(name:, private_directory:)
|
17
|
+
@name = name
|
18
|
+
@private_directory = private_directory
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
using Corindon::DependencyInjection::Injectable
|
4
|
+
|
5
|
+
module Ekylibre
|
6
|
+
module MultiTenancy
|
7
|
+
class TenantRepository
|
8
|
+
injectable(
|
9
|
+
private_root: MultiTenancyPlugin::PRIVATE_ROOT,
|
10
|
+
tenants_file: MultiTenancyPlugin::TENANTS_FILE,
|
11
|
+
)
|
12
|
+
|
13
|
+
# @param [Pathname] private_root
|
14
|
+
def initialize(private_root:, tenants_file:)
|
15
|
+
@private_root = private_root
|
16
|
+
@tenants_file = tenants_file
|
17
|
+
@write_mutex = Mutex.new
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param [String] name
|
21
|
+
# @return [Tenant, nil]
|
22
|
+
def get(name)
|
23
|
+
if has?(name)
|
24
|
+
Tenant.new(name: name, private_directory: private_root.join(name))
|
25
|
+
else
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete(name)
|
31
|
+
tenants.delete(name)
|
32
|
+
|
33
|
+
write
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param [String] name
|
37
|
+
# @return [Boolean]
|
38
|
+
def has?(name)
|
39
|
+
list.include?(name)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Array<String>]
|
43
|
+
def list
|
44
|
+
tenants.fetch(::Rails.env.to_s, [])
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# @return [Hash{String => Array<String>}]
|
50
|
+
def load
|
51
|
+
if tenants_file.exist?
|
52
|
+
YAML.load_file(tenants_file) || {}
|
53
|
+
else
|
54
|
+
{}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def write
|
59
|
+
write_mutex.synchronize do
|
60
|
+
tenants_file.write(tenants.to_yaml)
|
61
|
+
end
|
62
|
+
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Hash{String => Array<String>}]
|
67
|
+
def tenants
|
68
|
+
@tenants ||= load
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Pathname]
|
72
|
+
attr_reader :private_root
|
73
|
+
# @return [Pathname]
|
74
|
+
attr_reader :tenants_file
|
75
|
+
# @return [Mutex]
|
76
|
+
attr_reader :write_mutex
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ekylibre
|
4
|
+
module MultiTenancy
|
5
|
+
# Stack allowing to track which is the current tenant across multiple nested contexts
|
6
|
+
class TenantStack
|
7
|
+
def initialize
|
8
|
+
@stack = []
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [Tenant, nil]
|
12
|
+
def current
|
13
|
+
@stack.last
|
14
|
+
end
|
15
|
+
|
16
|
+
def pop
|
17
|
+
@stack.pop
|
18
|
+
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [Tenant] tenant
|
23
|
+
def push(tenant)
|
24
|
+
@stack << tenant
|
25
|
+
end
|
26
|
+
|
27
|
+
def size
|
28
|
+
@stack.size
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
using Corindon::DependencyInjection::Injectable
|
4
|
+
|
5
|
+
module Ekylibre
|
6
|
+
module MultiTenancy
|
7
|
+
# Service allowing to switch between tenants and keeping track of what need to be cleaned through the use of a TenantStack
|
8
|
+
class TenantSwitcher
|
9
|
+
injectable(
|
10
|
+
apartment: MultiTenancyPlugin::APARTMENT,
|
11
|
+
tenant_stack: TenantStack,
|
12
|
+
tenant_repository: TenantRepository,
|
13
|
+
)
|
14
|
+
|
15
|
+
# @param [#switch, #switch!] apartment
|
16
|
+
# @param [TenantStack] tenant_stack
|
17
|
+
# @param [TenantRepository] tenant_repository
|
18
|
+
def initialize(apartment:, tenant_stack:, tenant_repository:)
|
19
|
+
@apartment = apartment
|
20
|
+
@tenant_stack = tenant_stack
|
21
|
+
@tenant_repository = tenant_repository
|
22
|
+
@bang_stack = []
|
23
|
+
@bang_switched = false
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [String, #to_s] name
|
27
|
+
def switch(name, &block)
|
28
|
+
name = name.to_s
|
29
|
+
|
30
|
+
tenant = tenant_repository.get(name)
|
31
|
+
return if tenant.nil?
|
32
|
+
|
33
|
+
begin
|
34
|
+
@bang_stack << @bang_switched
|
35
|
+
@bang_switched = false
|
36
|
+
tenant_stack.push(tenant)
|
37
|
+
|
38
|
+
apartment.switch(name, &block)
|
39
|
+
ensure
|
40
|
+
# Make sure we pop the stack twice if we bang_switched
|
41
|
+
leave! if @bang_switched
|
42
|
+
@bang_switched = @bang_stack.pop
|
43
|
+
tenant_stack.pop
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param [String, #to_s] name
|
48
|
+
def switch!(name)
|
49
|
+
name = name.to_s
|
50
|
+
tenant = tenant_repository.get(name)
|
51
|
+
return if tenant.nil?
|
52
|
+
|
53
|
+
# Some housekeeping to keep the tenant_stack up-to-date
|
54
|
+
tenant_stack.pop if @bang_switched
|
55
|
+
@bang_switched = true
|
56
|
+
tenant_stack.push(tenant)
|
57
|
+
|
58
|
+
apartment.switch!(name)
|
59
|
+
end
|
60
|
+
|
61
|
+
def leave!
|
62
|
+
if @bang_switched
|
63
|
+
@bang_switched = false
|
64
|
+
tenant_stack.pop
|
65
|
+
apartment.switch!(tenant_stack.current)
|
66
|
+
else
|
67
|
+
raise StandardError.new('Cannot leave! if not after switch!')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# @return [#switch, #switch!]
|
74
|
+
attr_reader :apartment
|
75
|
+
# @return [TenantRepository]
|
76
|
+
attr_reader :tenant_repository
|
77
|
+
# @return [TenantStack]
|
78
|
+
attr_reader :tenant_stack
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
metadata
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ekylibre-multi_tenancy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ekylibre developers
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-02-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: apartment
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.2.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.2.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ekylibre-plugin_system
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.5.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.5.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: public_suffix
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 4.0.6
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 4.0.6
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: zeitwerk
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 2.4.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 2.4.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.17'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.17'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: minitest
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '5.14'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '5.14'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: minitest-reporters
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.4'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1.4'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rake
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '13.0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '13.0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rubocop
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 1.3.0
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 1.3.0
|
153
|
+
description:
|
154
|
+
email:
|
155
|
+
- dev@ekylibre.com
|
156
|
+
executables: []
|
157
|
+
extensions: []
|
158
|
+
extra_rdoc_files: []
|
159
|
+
files:
|
160
|
+
- ekylibre-multi_tenancy.gemspec
|
161
|
+
- lib/ekylibre-multi_tenancy.rb
|
162
|
+
- lib/ekylibre/multi_tenancy/console_helper.rb
|
163
|
+
- lib/ekylibre/multi_tenancy/middleware/container_aware_rack_middleware.rb
|
164
|
+
- lib/ekylibre/multi_tenancy/middleware/sidekiq_middleware.rb
|
165
|
+
- lib/ekylibre/multi_tenancy/middleware/sidekiq_middleware/container_aware_client_middleware.rb
|
166
|
+
- lib/ekylibre/multi_tenancy/middleware/sidekiq_middleware/container_aware_server_middleware.rb
|
167
|
+
- lib/ekylibre/multi_tenancy/multi_tenancy_plugin.rb
|
168
|
+
- lib/ekylibre/multi_tenancy/rails/railtie.rb
|
169
|
+
- lib/ekylibre/multi_tenancy/tenant.rb
|
170
|
+
- lib/ekylibre/multi_tenancy/tenant_repository.rb
|
171
|
+
- lib/ekylibre/multi_tenancy/tenant_stack.rb
|
172
|
+
- lib/ekylibre/multi_tenancy/tenant_switcher.rb
|
173
|
+
- lib/ekylibre/multi_tenancy/testing/mock_apartment.rb
|
174
|
+
- lib/ekylibre/multi_tenancy/version.rb
|
175
|
+
homepage: https://www.ekylibre.com
|
176
|
+
licenses:
|
177
|
+
- AGPL-3.0-only
|
178
|
+
metadata: {}
|
179
|
+
post_install_message:
|
180
|
+
rdoc_options: []
|
181
|
+
require_paths:
|
182
|
+
- lib
|
183
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: 2.6.0
|
188
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
189
|
+
requirements:
|
190
|
+
- - ">="
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: '0'
|
193
|
+
requirements: []
|
194
|
+
rubygems_version: 3.0.3
|
195
|
+
signing_key:
|
196
|
+
specification_version: 4
|
197
|
+
summary: Apartment integration as a plugin for Ekylibre
|
198
|
+
test_files: []
|