ekylibre-multi_tenancy 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
@@ -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
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ekylibre
4
+ module MultiTenancy
5
+ module Testing
6
+ class MockApartment
7
+ def switch(*, **)
8
+ yield if block_given?
9
+ end
10
+
11
+ def switch!(*, **) end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ekylibre
4
+ module MultiTenancy
5
+ VERSION = '0.2.0'
6
+ end
7
+ 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: []