console_kit 1.4.0 → 1.5.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 +4 -4
- data/lib/console_kit/configuration.rb +15 -21
- data/lib/console_kit/configuration_validator.rb +179 -0
- data/lib/console_kit/connections/base_connection_handler.rb +139 -25
- data/lib/console_kit/connections/connection_manager.rb +42 -8
- data/lib/console_kit/connections/dashboard.rb +3 -11
- data/lib/console_kit/connections/diagnostic_helpers.rb +24 -5
- data/lib/console_kit/connections/elasticsearch_connection_handler.rb +87 -32
- data/lib/console_kit/connections/elasticsearch_prefix_registry.rb +70 -0
- data/lib/console_kit/connections/mongo_connection_handler.rb +51 -46
- data/lib/console_kit/connections/mongoid_support.rb +56 -0
- data/lib/console_kit/connections/redis_client_adapter.rb +92 -0
- data/lib/console_kit/connections/redis_connection_handler.rb +97 -41
- data/lib/console_kit/connections/sql_connection_handler.rb +66 -30
- data/lib/console_kit/connections/sql_strategy.rb +248 -0
- data/lib/console_kit/connections/table_renderer.rb +30 -20
- data/lib/console_kit/console_helpers.rb +17 -31
- data/lib/console_kit/diagnostics.rb +143 -0
- data/lib/console_kit/errors.rb +93 -0
- data/lib/console_kit/instrumentation.rb +57 -0
- data/lib/console_kit/output.rb +5 -14
- data/lib/console_kit/prompt.rb +17 -29
- data/lib/console_kit/railtie.rb +2 -3
- data/lib/console_kit/setup.rb +66 -13
- data/lib/console_kit/setup_ui.rb +1 -4
- data/lib/console_kit/tenant_configurator/context_wrapper.rb +51 -23
- data/lib/console_kit/tenant_configurator.rb +40 -78
- data/lib/console_kit/tenant_orchestrator.rb +31 -27
- data/lib/console_kit/tenant_plan.rb +68 -0
- data/lib/console_kit/tenant_rollback.rb +77 -0
- data/lib/console_kit/tenant_selector.rb +3 -6
- data/lib/console_kit/tenant_state.rb +73 -0
- data/lib/console_kit/tenant_switch.rb +124 -0
- data/lib/console_kit/version.rb +1 -1
- data/lib/console_kit.rb +51 -24
- data/lib/generators/console_kit/install_generator.rb +1 -12
- data/lib/generators/console_kit/templates/console_kit.rb +4 -0
- metadata +15 -4
- data/lib/console_kit/connections/table_formatter.rb +0 -37
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'connections/diagnostic_helpers'
|
|
4
|
+
|
|
5
|
+
module ConsoleKit
|
|
6
|
+
PROGRAMMING_ERRORS = [NameError, ArgumentError, TypeError].freeze
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def programming_error?(error) = PROGRAMMING_ERRORS.any? { |klass| error.is_a?(klass) }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
|
|
14
|
+
class ConfigurationError < Error; end
|
|
15
|
+
|
|
16
|
+
class TenantNotFoundError < ConfigurationError; end
|
|
17
|
+
|
|
18
|
+
class UnsupportedBackendError < Error; end
|
|
19
|
+
|
|
20
|
+
class ConnectionError < Error
|
|
21
|
+
attr_reader :backend, :tenant, :operation
|
|
22
|
+
|
|
23
|
+
def initialize(message = nil, backend: nil, tenant: nil, operation: nil)
|
|
24
|
+
@backend = backend
|
|
25
|
+
@tenant = tenant
|
|
26
|
+
@operation = operation
|
|
27
|
+
super(message || "#{backend} #{operation} failed for tenant #{tenant.inspect}")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class ConnectionVerificationError < ConnectionError
|
|
32
|
+
attr_reader :expected, :actual
|
|
33
|
+
|
|
34
|
+
def initialize(message = nil, expected: nil, actual: nil, **opts)
|
|
35
|
+
@expected = expected
|
|
36
|
+
@actual = actual
|
|
37
|
+
super(message || default_message(opts[:backend]), operation: :verify, **opts)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def default_message(backend)
|
|
43
|
+
"#{backend} verification failed. Expected #{expected.inspect}, got #{actual.inspect}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class RollbackError < Error
|
|
48
|
+
attr_reader :failures
|
|
49
|
+
|
|
50
|
+
def initialize(failures)
|
|
51
|
+
@failures = failures
|
|
52
|
+
super("Rollback failed for: #{failures.map { |failure| failure[:backend] }.join(', ')}")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class TenantSwitchError < Error
|
|
57
|
+
attr_reader :from_tenant, :to_tenant, :backend, :original_error, :rollback_failures
|
|
58
|
+
|
|
59
|
+
def initialize(from_tenant:, to_tenant:, original_error:, backend: nil, rollback_failures: [])
|
|
60
|
+
@from_tenant = from_tenant
|
|
61
|
+
@to_tenant = to_tenant
|
|
62
|
+
@backend = backend
|
|
63
|
+
@original_error = original_error
|
|
64
|
+
@rollback_failures = rollback_failures
|
|
65
|
+
super(build_message)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def rollback_succeeded? = rollback_failures.empty?
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def build_message
|
|
73
|
+
[headline, scrub(original_error.message), rollback_summary].compact.join("\n")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def scrub(message) = Connections::DiagnosticHelpers.scrub(message)
|
|
77
|
+
|
|
78
|
+
def headline
|
|
79
|
+
scope = backend ? " (#{backend})" : nil
|
|
80
|
+
"Failed to switch tenant from #{from_tenant.inspect} to #{to_tenant.inspect}#{scope}:"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def rollback_summary
|
|
84
|
+
return "\nPrevious tenant state was restored successfully." if rollback_succeeded?
|
|
85
|
+
|
|
86
|
+
"\nWARNING: rollback did not fully succeed:\n#{formatted_failures}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def formatted_failures
|
|
90
|
+
rollback_failures.map { |failure| " - #{failure[:backend]}: #{scrub(failure[:error].message)}" }.join("\n")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'connections/diagnostic_helpers'
|
|
4
|
+
|
|
5
|
+
module ConsoleKit
|
|
6
|
+
module Instrumentation
|
|
7
|
+
class << self
|
|
8
|
+
def subscribe(&block)
|
|
9
|
+
mutex.synchronize { subscribers << block }
|
|
10
|
+
block
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def clear!
|
|
14
|
+
mutex.synchronize do
|
|
15
|
+
subscribers.clear
|
|
16
|
+
counters.clear
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def counters = @counters ||= Hash.new(0)
|
|
21
|
+
|
|
22
|
+
def counts = mutex.synchronize { counters.dup }
|
|
23
|
+
|
|
24
|
+
def increment(name)
|
|
25
|
+
mutex.synchronize { counters[name] += 1 }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def instrument(name, payload = {})
|
|
29
|
+
start = Connections::DiagnosticHelpers.clock_time
|
|
30
|
+
yield.tap { publish(name, start, payload.merge(status: :ok)) }
|
|
31
|
+
rescue StandardError, NotImplementedError => e
|
|
32
|
+
publish(name, start, payload.merge(error: e.class.name, status: :error))
|
|
33
|
+
raise
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def publish(name, start, payload)
|
|
39
|
+
increment(name)
|
|
40
|
+
duration_ms = elapsed_ms(start)
|
|
41
|
+
each_subscriber { |sub| sub.call(name, duration_ms, payload) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def each_subscriber
|
|
45
|
+
mutex.synchronize { subscribers.dup }.each do |sub|
|
|
46
|
+
yield(sub)
|
|
47
|
+
rescue StandardError
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def elapsed_ms(start) = ((Connections::DiagnosticHelpers.clock_time - start) * 1000).round(2)
|
|
53
|
+
def subscribers = @subscribers ||= []
|
|
54
|
+
def mutex = @mutex ||= Mutex.new
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/console_kit/output.rb
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ConsoleKit
|
|
4
|
-
# Handles Console outputs
|
|
5
4
|
module Output
|
|
6
5
|
PREFIX = '[ConsoleKit]'
|
|
7
6
|
TYPES = {
|
|
8
7
|
error: { symbol: '[✗]', color: '1;31' },
|
|
9
8
|
success: { symbol: '[✓]', color: '1;32' },
|
|
10
9
|
warning: { symbol: '[!]', color: '1;33' },
|
|
11
|
-
prompt: { symbol: nil,
|
|
12
|
-
header: { symbol: nil,
|
|
10
|
+
prompt: { symbol: nil, color: '1;36' },
|
|
11
|
+
header: { symbol: nil, color: '1;34' },
|
|
13
12
|
trace: { symbol: nil, color: '0;90' },
|
|
14
13
|
info: { symbol: nil, color: nil }
|
|
15
14
|
}.freeze
|
|
@@ -34,7 +33,8 @@ module ConsoleKit
|
|
|
34
33
|
return if silent
|
|
35
34
|
|
|
36
35
|
formatted = (type == :header ? "\n--- #{text} ---" : text)
|
|
37
|
-
|
|
36
|
+
message = build_formatted_message(type, formatted, timestamp)
|
|
37
|
+
newline ? puts(message) : print(message)
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
@@ -54,20 +54,11 @@ module ConsoleKit
|
|
|
54
54
|
def print_backtrace(exception)
|
|
55
55
|
return if silent
|
|
56
56
|
|
|
57
|
-
exception&.backtrace&.each
|
|
58
|
-
print_with(:trace, " #{line}", timestamp: true)
|
|
59
|
-
end
|
|
57
|
+
exception&.backtrace&.each { |line| print_trace(" #{line}", timestamp: true) }
|
|
60
58
|
end
|
|
61
59
|
|
|
62
60
|
private
|
|
63
61
|
|
|
64
|
-
def print_with(type, text, options = {})
|
|
65
|
-
opts = options.is_a?(Hash) ? options : { timestamp: options }
|
|
66
|
-
message = build_formatted_message(type, text, opts[:timestamp])
|
|
67
|
-
|
|
68
|
-
opts.fetch(:newline, true) ? puts(message) : print(message)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
62
|
def build_formatted_message(type, text, timestamp)
|
|
72
63
|
meta = TYPES.fetch(type)
|
|
73
64
|
message = build_message(text, meta[:symbol], timestamp)
|
data/lib/console_kit/prompt.rb
CHANGED
|
@@ -1,46 +1,34 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ConsoleKit
|
|
4
|
-
# Sets the console prompt to show the current tenant
|
|
5
4
|
module Prompt
|
|
5
|
+
module IrbLabel
|
|
6
|
+
def prompt_i = Prompt.labelled(super)
|
|
7
|
+
def prompt_s = Prompt.labelled(super)
|
|
8
|
+
def prompt_c = Prompt.labelled(super)
|
|
9
|
+
end
|
|
10
|
+
|
|
6
11
|
class << self
|
|
7
12
|
def apply
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
require 'irb' unless defined?(Pry)
|
|
14
|
+
IRB::Context.prepend(IrbLabel) if defined?(IRB::Context) && !IRB::Context.include?(IrbLabel)
|
|
15
|
+
Pry.config.prompt = pry_prompt if defined?(Pry)
|
|
10
16
|
end
|
|
11
17
|
|
|
12
|
-
|
|
18
|
+
def labelled(prompt) = prompt && "#{label.gsub('%', '%%')} #{prompt}"
|
|
13
19
|
|
|
14
|
-
def
|
|
15
|
-
tenant =
|
|
20
|
+
def label
|
|
21
|
+
tenant = StateStore.tenant_key
|
|
16
22
|
tenant ? "[#{tenant}]" : '[no-tenant]'
|
|
17
23
|
end
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
conf = IRB.conf
|
|
21
|
-
prompt = conf[:PROMPT] ||= {}
|
|
22
|
-
prompt[:CONSOLE_KIT] = {
|
|
23
|
-
PROMPT_I: "#{tenant_label} %N(%m):%03n> ",
|
|
24
|
-
PROMPT_S: "#{tenant_label} %N(%m):%03n%l ",
|
|
25
|
-
PROMPT_C: "#{tenant_label} %N(%m):%03n* ",
|
|
26
|
-
RETURN: "=> %s\n"
|
|
27
|
-
}
|
|
28
|
-
conf[:PROMPT_MODE] = :CONSOLE_KIT
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def apply_pry_prompt
|
|
32
|
-
procs = pry_prompt_procs(tenant_label)
|
|
33
|
-
Pry.config.prompt = build_pry_prompt(procs)
|
|
34
|
-
end
|
|
25
|
+
private
|
|
35
26
|
|
|
36
|
-
def
|
|
37
|
-
[
|
|
38
|
-
proc { |obj, nest, _| "#{label} (#{obj}):#{nest}> " },
|
|
39
|
-
proc { |obj, nest, _| "#{label} (#{obj}):#{nest}* " }
|
|
27
|
+
def pry_prompt
|
|
28
|
+
procs = [
|
|
29
|
+
proc { |obj, nest, _| "#{ConsoleKit::Prompt.label} (#{obj}):#{nest}> " },
|
|
30
|
+
proc { |obj, nest, _| "#{ConsoleKit::Prompt.label} (#{obj}):#{nest}* " }
|
|
40
31
|
]
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def build_pry_prompt(procs)
|
|
44
32
|
return procs unless defined?(Pry::Prompt)
|
|
45
33
|
|
|
46
34
|
Pry::Prompt.try(:new, 'console_kit', 'ConsoleKit tenant prompt', procs) || procs
|
data/lib/console_kit/railtie.rb
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ConsoleKit
|
|
4
|
-
# Railtie for integrating ConsoleKit with Rails console.
|
|
5
4
|
class Railtie < Rails::Railtie
|
|
6
5
|
console do
|
|
7
|
-
ConsoleKit::
|
|
6
|
+
ConsoleKit::TenantOrchestrator.run
|
|
8
7
|
ConsoleKit::Prompt.apply
|
|
9
8
|
if defined?(IRB::ExtendCommandBundle) && !defined?(Pry)
|
|
10
9
|
IRB::ExtendCommandBundle.include(ConsoleKit::ConsoleHelpers)
|
|
@@ -13,6 +12,6 @@ module ConsoleKit
|
|
|
13
12
|
end
|
|
14
13
|
end
|
|
15
14
|
|
|
16
|
-
config.to_prepare { ConsoleKit::
|
|
15
|
+
config.to_prepare { ConsoleKit::TenantOrchestrator.reapply if defined?(Rails::Console) }
|
|
17
16
|
end
|
|
18
17
|
end
|
data/lib/console_kit/setup.rb
CHANGED
|
@@ -1,27 +1,80 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative '
|
|
4
|
-
require_relative 'tenant_configurator'
|
|
5
|
-
require_relative 'output'
|
|
6
|
-
require_relative 'setup_ui'
|
|
3
|
+
require_relative 'configuration'
|
|
7
4
|
require_relative 'tenant_orchestrator'
|
|
8
5
|
|
|
9
|
-
# Core Logic for initial Setup
|
|
10
6
|
module ConsoleKit
|
|
11
|
-
|
|
7
|
+
module Deprecation
|
|
8
|
+
class << self
|
|
9
|
+
def call(name, replacement)
|
|
10
|
+
warned = (@warned ||= {})
|
|
11
|
+
unless warned[name]
|
|
12
|
+
warned[name] = true
|
|
13
|
+
Kernel.warn("ConsoleKit: #{name} is deprecated and will be removed in 2.0. Use #{replacement} instead.")
|
|
14
|
+
end
|
|
15
|
+
yield
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
12
20
|
module Setup
|
|
13
21
|
class << self
|
|
14
|
-
def
|
|
22
|
+
def setup = Deprecation.call('Setup.setup', 'TenantOrchestrator.run') { TenantOrchestrator.run }
|
|
23
|
+
|
|
24
|
+
def current_tenant
|
|
25
|
+
Deprecation.call('Setup.current_tenant', 'ConsoleKit.current_tenant') { ConsoleKit.current_tenant }
|
|
26
|
+
end
|
|
15
27
|
|
|
16
28
|
def current_tenant=(val)
|
|
17
|
-
|
|
29
|
+
Deprecation.call('Setup.current_tenant=', 'ConsoleKit.switch_tenant') do
|
|
30
|
+
TenantOrchestrator.current_tenant = val
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def tenant_setup_successful?
|
|
35
|
+
Deprecation.call('Setup.tenant_setup_successful?', 'ConsoleKit.current_tenant') do
|
|
36
|
+
!ConsoleKit.current_tenant.to_s.empty?
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def reapply = Deprecation.call('Setup.reapply', 'TenantOrchestrator.reapply') { TenantOrchestrator.reapply }
|
|
41
|
+
|
|
42
|
+
def reset_current_tenant
|
|
43
|
+
Deprecation.call('Setup.reset_current_tenant', 'ConsoleKit.reset_current_tenant') do
|
|
44
|
+
ConsoleKit.reset_current_tenant
|
|
45
|
+
end
|
|
18
46
|
end
|
|
19
47
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
48
|
+
def auto_select?
|
|
49
|
+
Deprecation.call('Setup.auto_select?', 'TenantOrchestrator.auto_select?') { TenantOrchestrator.auto_select? }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class << self
|
|
55
|
+
%i[pretty_output context_class show_dashboard].each do |name|
|
|
56
|
+
define_method(name) do
|
|
57
|
+
Deprecation.call("ConsoleKit.#{name}", "ConsoleKit.configuration.#{name}") { configuration.public_send(name) }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
%i[pretty_output tenants context_class show_dashboard].each do |name|
|
|
62
|
+
define_method(:"#{name}=") do |val|
|
|
63
|
+
Deprecation.call("ConsoleKit.#{name}=", "ConsoleKit.configuration.#{name}=") do
|
|
64
|
+
configuration.public_send(:"#{name}=", val)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class Configuration
|
|
71
|
+
def validate
|
|
72
|
+
Deprecation.call('Configuration#validate', 'Configuration#validate!') do
|
|
73
|
+
validate!
|
|
74
|
+
true
|
|
75
|
+
rescue Error
|
|
76
|
+
false
|
|
77
|
+
end
|
|
25
78
|
end
|
|
26
79
|
end
|
|
27
80
|
end
|
data/lib/console_kit/setup_ui.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ConsoleKit
|
|
4
|
-
# UI helpers for Setup
|
|
5
4
|
module SetupUI
|
|
6
5
|
ENVIRONMENT_WARNINGS = {
|
|
7
6
|
'production' => -> { Output.print_error('!!! CAUTION: YOU ARE IN PRODUCTION ENVIRONMENT !!!') },
|
|
@@ -26,9 +25,7 @@ module ConsoleKit
|
|
|
26
25
|
|
|
27
26
|
def print_active_connections
|
|
28
27
|
ctx = ConsoleKit.configuration.context_class
|
|
29
|
-
active = Connections::ConnectionManager.available_handlers(ctx).map
|
|
30
|
-
handler.class.name.demodulize.delete_suffix('ConnectionHandler')
|
|
31
|
-
end
|
|
28
|
+
active = Connections::ConnectionManager.available_handlers(ctx).map(&:display_name)
|
|
32
29
|
|
|
33
30
|
Output.print_info("Active connections: #{active.join(', ')}") if active.any?
|
|
34
31
|
end
|
|
@@ -2,14 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module ConsoleKit
|
|
4
4
|
module TenantConfigurator
|
|
5
|
-
# Encapsulates context and attributes to resolve DataClump smells
|
|
6
5
|
class ContextWrapper
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Connections::MongoConnectionHandler => :tenant_mongo_db,
|
|
10
|
-
Connections::RedisConnectionHandler => :tenant_redis_db,
|
|
11
|
-
Connections::ElasticsearchConnectionHandler => :tenant_elasticsearch_prefix
|
|
12
|
-
}.freeze
|
|
6
|
+
UNREADABLE = :'#<console_kit unreadable>'
|
|
7
|
+
UNREADABLE_MESSAGE = 'Previous value of %s could not be read, so it was left as the new tenant set it.'
|
|
13
8
|
|
|
14
9
|
attr_reader :ctx, :attributes
|
|
15
10
|
|
|
@@ -22,15 +17,13 @@ module ConsoleKit
|
|
|
22
17
|
|
|
23
18
|
def detect_attributes(ctx)
|
|
24
19
|
methods = ctx.public_methods
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def partner_attrs(methods)
|
|
29
|
-
methods.include?(:partner_identifier=) ? [:partner_identifier] : []
|
|
20
|
+
partner = methods.include?(:partner_identifier=) ? [:partner_identifier] : []
|
|
21
|
+
partner + handler_attrs(methods)
|
|
30
22
|
end
|
|
31
23
|
|
|
32
24
|
def handler_attrs(methods)
|
|
33
|
-
|
|
25
|
+
Connections::BaseConnectionHandler.registry.each_with_object([]) do |handler, list|
|
|
26
|
+
attr = handler.context_attribute
|
|
34
27
|
next unless methods.include?(:"#{attr}=")
|
|
35
28
|
next unless handler_available?(handler)
|
|
36
29
|
|
|
@@ -54,25 +47,60 @@ module ConsoleKit
|
|
|
54
47
|
attributes.any? { |attr| ctx.public_send(attr).present? }
|
|
55
48
|
end
|
|
56
49
|
|
|
57
|
-
def
|
|
58
|
-
|
|
50
|
+
def current_values = attributes.to_h { |attr| [attr, safe_read(attr)] }
|
|
51
|
+
|
|
52
|
+
def restore(values)
|
|
53
|
+
failures = values.filter_map { |attr, value| restore_attribute(attr, value) }
|
|
54
|
+
raise_restore_failure(failures) if failures.any?
|
|
55
|
+
|
|
56
|
+
values
|
|
59
57
|
end
|
|
60
58
|
|
|
61
59
|
def assign(constant, mapping)
|
|
62
|
-
attributes.
|
|
63
|
-
existing = safe_read(attr)
|
|
64
|
-
new_value = constant[mapping[attr]]
|
|
65
|
-
ctx.public_send("#{attr}=", new_value)
|
|
66
|
-
[attr, existing, new_value]
|
|
67
|
-
end
|
|
60
|
+
attributes.to_h { |attr| [attr, write_attribute(attr, constant[mapping[attr]])] }
|
|
68
61
|
end
|
|
69
62
|
|
|
70
63
|
private
|
|
71
64
|
|
|
65
|
+
def restore_attribute(attr, value)
|
|
66
|
+
return [attr, Error.new(format(UNREADABLE_MESSAGE, attr))] if value == UNREADABLE
|
|
67
|
+
|
|
68
|
+
ctx.public_send(:"#{attr}=", value)
|
|
69
|
+
nil
|
|
70
|
+
rescue StandardError, NotImplementedError => e
|
|
71
|
+
[attr, e]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def write_attribute(attr, new_value)
|
|
75
|
+
existing = safe_read(attr)
|
|
76
|
+
ctx.public_send(:"#{attr}=", new_value)
|
|
77
|
+
warn_case_mismatch(attr, existing, new_value)
|
|
78
|
+
new_value
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def raise_restore_failure(failures)
|
|
82
|
+
detail = failures.map { |attr, error| "#{attr} (#{error.class})" }.join(', ')
|
|
83
|
+
raise Error, "Could not restore context attributes: #{detail}. " \
|
|
84
|
+
'Those attributes are still set to the tenant the switch failed to reach.'
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def warn_case_mismatch(attr, existing, configured)
|
|
88
|
+
return unless existing.is_a?(String) && configured.is_a?(String) &&
|
|
89
|
+
existing != configured && existing.casecmp(configured).zero?
|
|
90
|
+
|
|
91
|
+
Output.print_warning(
|
|
92
|
+
"#{attr} case mismatch: context had '#{existing}', config set '#{configured}'. " \
|
|
93
|
+
'Check your ConsoleKit tenant configuration.'
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
72
97
|
def safe_read(attr)
|
|
73
98
|
ctx.public_send(attr)
|
|
74
|
-
rescue StandardError
|
|
75
|
-
|
|
99
|
+
rescue StandardError, NotImplementedError => e
|
|
100
|
+
Output.print_warning(
|
|
101
|
+
"Could not read context attribute #{attr}: #{e.class}. Rollback will not be able to restore it."
|
|
102
|
+
)
|
|
103
|
+
UNREADABLE
|
|
76
104
|
end
|
|
77
105
|
end
|
|
78
106
|
end
|
|
@@ -1,127 +1,89 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'output'
|
|
4
|
+
require_relative 'errors'
|
|
5
|
+
require_relative 'tenant_state'
|
|
4
6
|
require_relative 'connections/connection_manager'
|
|
5
7
|
require_relative 'connections/dashboard'
|
|
6
8
|
require_relative 'tenant_configurator/context_wrapper'
|
|
9
|
+
require_relative 'tenant_switch'
|
|
7
10
|
|
|
8
11
|
module ConsoleKit
|
|
9
|
-
# For tenant configuration
|
|
10
12
|
module TenantConfigurator
|
|
11
|
-
CONTEXT_MAPPING = {
|
|
12
|
-
partner_identifier: :partner_code,
|
|
13
|
-
tenant_shard: :shard,
|
|
14
|
-
tenant_mongo_db: :mongo_db,
|
|
15
|
-
tenant_redis_db: :redis_db,
|
|
16
|
-
tenant_elasticsearch_prefix: :elasticsearch_prefix
|
|
17
|
-
}.freeze
|
|
18
|
-
|
|
19
13
|
class << self
|
|
20
|
-
def
|
|
14
|
+
def context_mapping
|
|
15
|
+
{ partner_identifier: :partner_code }.merge(backend_context_mapping)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def configuration_success? = StateStore.configured?
|
|
19
|
+
alias configuration_success configuration_success?
|
|
21
20
|
|
|
22
21
|
def configuration_success=(val)
|
|
23
|
-
|
|
22
|
+
StateStore.clear! unless val
|
|
24
23
|
end
|
|
25
24
|
|
|
26
|
-
def current_tenant_key =
|
|
25
|
+
def current_tenant_key = StateStore.tenant_key
|
|
27
26
|
|
|
28
27
|
def current_tenant_key=(val)
|
|
29
|
-
|
|
28
|
+
StateStore.clear! if val.nil?
|
|
30
29
|
end
|
|
31
30
|
|
|
32
31
|
def configure_tenant(key)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
switch_unless_current(key)
|
|
33
|
+
true
|
|
34
|
+
rescue StandardError, NotImplementedError => e
|
|
35
|
+
report_failure(e, key)
|
|
36
|
+
false
|
|
38
37
|
end
|
|
39
38
|
|
|
40
39
|
def clear
|
|
41
40
|
ctx = ConsoleKit.configuration.context_class
|
|
42
41
|
return unless ctx
|
|
43
42
|
|
|
44
|
-
perform_clear(ContextWrapper.for_context(ctx))
|
|
43
|
+
perform_clear(ctx, ContextWrapper.for_context(ctx))
|
|
45
44
|
end
|
|
46
45
|
|
|
47
46
|
private
|
|
48
47
|
|
|
49
|
-
def
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
def backend_context_mapping
|
|
49
|
+
Connections::BaseConnectionHandler.registry.to_h { |handler| [handler.context_attribute, handler.constants_key] }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def switch_unless_current(key)
|
|
53
|
+
return if key == current_tenant_key && configuration_success
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
configuration_success
|
|
55
|
+
TenantSwitch.call(key)
|
|
55
56
|
end
|
|
56
57
|
|
|
57
|
-
def perform_clear(wrapper)
|
|
58
|
+
def perform_clear(ctx, wrapper)
|
|
58
59
|
return unless configuration_success || wrapper.any_set?
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
TenantSwitch.clear(context: ctx)
|
|
61
62
|
Output.print_info('Tenant context has been cleared.')
|
|
62
63
|
true
|
|
63
64
|
end
|
|
64
65
|
|
|
65
|
-
def
|
|
66
|
-
|
|
67
|
-
self.current_tenant_key = nil
|
|
68
|
-
wrapper.reset
|
|
69
|
-
setup_connections(wrapper.ctx)
|
|
70
|
-
end
|
|
66
|
+
def report_failure(error, key)
|
|
67
|
+
return print_missing_config(key) if tenant_missing?(error)
|
|
71
68
|
|
|
72
|
-
|
|
73
|
-
missing = %i[shard partner_code] - constants.keys
|
|
74
|
-
raise Error, "Tenant constants missing keys: #{missing.join(', ')}" unless missing.empty?
|
|
69
|
+
print_error_details(error, key)
|
|
75
70
|
end
|
|
76
71
|
|
|
77
|
-
def
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
false
|
|
72
|
+
def tenant_missing?(error)
|
|
73
|
+
error.is_a?(TenantNotFoundError) ||
|
|
74
|
+
(error.is_a?(TenantSwitchError) && error.original_error.is_a?(TenantNotFoundError))
|
|
81
75
|
end
|
|
82
76
|
|
|
83
|
-
def
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
mark_success(key)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def apply_context(constant)
|
|
90
|
-
wrapper = ContextWrapper.for_context(ConsoleKit.configuration.context_class)
|
|
91
|
-
wrapper.assign(constant, CONTEXT_MAPPING).each do |attr, existing, configured|
|
|
92
|
-
warn_case_mismatch(attr, existing, configured) if case_mismatch?(existing, configured)
|
|
93
|
-
end
|
|
94
|
-
setup_connections(wrapper.ctx)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def case_mismatch?(existing, new_value)
|
|
98
|
-
existing.is_a?(String) && new_value.is_a?(String) &&
|
|
99
|
-
existing != new_value &&
|
|
100
|
-
existing.casecmp(new_value).zero?
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def setup_connections(context)
|
|
104
|
-
Connections::ConnectionManager.available_handlers(context).each(&:connect)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def mark_success(key)
|
|
108
|
-
Output.print_success("Tenant set to: #{key}")
|
|
109
|
-
self.configuration_success = true
|
|
110
|
-
self.current_tenant_key = key
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def warn_case_mismatch(attr, existing, configured)
|
|
114
|
-
Output.print_warning(
|
|
115
|
-
"#{attr} case mismatch: context had '#{existing}', config set '#{configured}'. " \
|
|
116
|
-
'Check your ConsoleKit tenant configuration.'
|
|
117
|
-
)
|
|
77
|
+
def print_missing_config(key)
|
|
78
|
+
Output.print_error("No configuration found for tenant: #{key}")
|
|
79
|
+
nil
|
|
118
80
|
end
|
|
119
81
|
|
|
120
|
-
def
|
|
121
|
-
|
|
122
|
-
Output.print_error("Failed to configure tenant '#{key}': #{
|
|
82
|
+
def print_error_details(error, key)
|
|
83
|
+
message = Connections::DiagnosticHelpers.scrub(error.message)
|
|
84
|
+
Output.print_error("Failed to configure tenant '#{key}': #{message}")
|
|
123
85
|
Output.print_backtrace(error)
|
|
124
|
-
|
|
86
|
+
nil
|
|
125
87
|
end
|
|
126
88
|
end
|
|
127
89
|
end
|