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
|
@@ -1,28 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'tenant_state'
|
|
4
|
+
require_relative 'tenant_selector'
|
|
5
|
+
require_relative 'tenant_configurator'
|
|
6
|
+
require_relative 'output'
|
|
7
|
+
require_relative 'setup_ui'
|
|
8
|
+
|
|
3
9
|
module ConsoleKit
|
|
4
|
-
# Orchestrates tenant lifecycle, selection, and configuration
|
|
5
10
|
class TenantOrchestrator
|
|
11
|
+
NO_TENANT_SELECTED = 'No tenant selected. Loading without tenant configuration.'
|
|
12
|
+
|
|
6
13
|
class << self
|
|
7
14
|
def auto_select? = (tenants.size == 1) || !$stdin.tty?
|
|
8
15
|
|
|
9
|
-
def current_tenant =
|
|
16
|
+
def current_tenant = StateStore.tenant_key
|
|
10
17
|
|
|
11
18
|
def current_tenant=(val)
|
|
12
|
-
|
|
19
|
+
return if StateStore.tenant_key == val
|
|
20
|
+
|
|
21
|
+
val.nil? ? StateStore.clear! : StateStore.current = TenantState.new(tenant_key: val)
|
|
13
22
|
end
|
|
14
23
|
|
|
15
24
|
def reapply
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
TenantConfigurator.configure_tenant(current_tenant)
|
|
21
|
-
end
|
|
25
|
+
key = current_tenant
|
|
26
|
+
Output.silence { TenantSwitch.call(key) } if tenant_setup_successful?
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
Output.print_error("Failed to reapply tenant '#{key}': #{scrub(e.message)}")
|
|
22
29
|
end
|
|
23
30
|
|
|
24
31
|
def reset
|
|
25
|
-
return
|
|
32
|
+
return Output.print_warning('Cannot reset tenant: No tenants configured.') unless tenants?
|
|
26
33
|
|
|
27
34
|
perform_reset
|
|
28
35
|
end
|
|
@@ -38,9 +45,6 @@ module ConsoleKit
|
|
|
38
45
|
def tenants = ConsoleKit.configuration.tenants
|
|
39
46
|
def tenants? = tenants&.any?
|
|
40
47
|
def select_tenant_key = auto_select? ? tenants.keys.first : TenantSelector.select
|
|
41
|
-
def warn_no_tenants = Output.print_warning('Cannot reset tenant: No tenants configured.')
|
|
42
|
-
def cancel_switch = Output.print_warning('Tenant switch cancelled.')
|
|
43
|
-
def skip_tenant_message = Output.print_info('No tenant selected. Loading without tenant configuration.')
|
|
44
48
|
|
|
45
49
|
private
|
|
46
50
|
|
|
@@ -48,13 +52,17 @@ module ConsoleKit
|
|
|
48
52
|
|
|
49
53
|
def perform_reset
|
|
50
54
|
key = select_tenant_key
|
|
51
|
-
return
|
|
52
|
-
return
|
|
55
|
+
return Output.print_warning('Tenant switch cancelled.') if key == :abort || key.blank?
|
|
56
|
+
return Output.print_info("Already using tenant: #{key}. No changes made.") if key == current_tenant
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
apply_reset(key)
|
|
59
|
+
end
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
def apply_reset(key)
|
|
62
|
+
return configure(key) unless %i[exit none].include?(key)
|
|
63
|
+
|
|
64
|
+
clear_current_tenant
|
|
65
|
+
Output.print_info(NO_TENANT_SELECTED)
|
|
58
66
|
end
|
|
59
67
|
|
|
60
68
|
def perform_setup
|
|
@@ -68,7 +76,7 @@ module ConsoleKit
|
|
|
68
76
|
def handle_selection_result(key)
|
|
69
77
|
exit_on_key if %i[exit abort].include?(key)
|
|
70
78
|
|
|
71
|
-
|
|
79
|
+
Output.print_info(NO_TENANT_SELECTED) if key == :none
|
|
72
80
|
Output.print_error('Tenant selection failed. Loading without tenant configuration.') if key.blank?
|
|
73
81
|
end
|
|
74
82
|
|
|
@@ -81,16 +89,10 @@ module ConsoleKit
|
|
|
81
89
|
TenantConfigurator.configure_tenant(key)
|
|
82
90
|
return unless TenantConfigurator.configuration_success
|
|
83
91
|
|
|
84
|
-
|
|
85
|
-
Prompt.apply
|
|
92
|
+
self.current_tenant = key
|
|
86
93
|
SetupUI.print_tenant_banner(key, ConsoleKit.configuration)
|
|
87
94
|
end
|
|
88
95
|
|
|
89
|
-
def already_on_tenant?(key)
|
|
90
|
-
Output.print_info("Already using tenant: #{key}. No changes made.")
|
|
91
|
-
true
|
|
92
|
-
end
|
|
93
|
-
|
|
94
96
|
def clear_current_tenant
|
|
95
97
|
if current_tenant
|
|
96
98
|
Output.print_warning("Resetting tenant: #{current_tenant}")
|
|
@@ -100,9 +102,11 @@ module ConsoleKit
|
|
|
100
102
|
end
|
|
101
103
|
|
|
102
104
|
def handle_error(error)
|
|
103
|
-
Output.print_error("Error setting up tenant: #{error.message}")
|
|
105
|
+
Output.print_error("Error setting up tenant: #{scrub(error.message)}")
|
|
104
106
|
Output.print_backtrace(error)
|
|
105
107
|
end
|
|
108
|
+
|
|
109
|
+
def scrub(message) = Connections::DiagnosticHelpers.scrub(message)
|
|
106
110
|
end
|
|
107
111
|
end
|
|
108
112
|
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'errors'
|
|
4
|
+
|
|
5
|
+
module ConsoleKit
|
|
6
|
+
class TenantPlan
|
|
7
|
+
REQUIRED_KEYS = %i[shard partner_code].freeze
|
|
8
|
+
|
|
9
|
+
attr_reader :tenant_key
|
|
10
|
+
|
|
11
|
+
def initialize(tenant_key, constants: nil)
|
|
12
|
+
@tenant_key = tenant_key
|
|
13
|
+
@constants = constants
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def constants = @constants ||= resolve_constants
|
|
17
|
+
|
|
18
|
+
def targets_for(handlers)
|
|
19
|
+
handlers.to_h { |handler| [handler.backend_key, target_for(handler)] }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def target_for(handler) = constants[handler.class.constants_key]
|
|
25
|
+
|
|
26
|
+
def resolve_constants
|
|
27
|
+
return {} if tenant_key.nil?
|
|
28
|
+
|
|
29
|
+
found = lookup_constants
|
|
30
|
+
raise TenantNotFoundError, not_found_message unless found
|
|
31
|
+
|
|
32
|
+
validate!(found)
|
|
33
|
+
found
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def not_found_message
|
|
37
|
+
tenants = ConsoleKit.configuration.tenants
|
|
38
|
+
keys = tenants.is_a?(Hash) ? tenants.keys : []
|
|
39
|
+
known = keys.any? ? "Configured tenants: #{keys.map(&:inspect).join(', ')}." : 'No tenants are configured.'
|
|
40
|
+
"No configuration found for tenant: #{tenant_key.inspect}. #{known}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def lookup_constants
|
|
44
|
+
tenants = ConsoleKit.configuration.tenants
|
|
45
|
+
entry = tenants.is_a?(Hash) ? tenants[tenant_key] : nil
|
|
46
|
+
constants_from(entry) unless entry.nil?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def constants_from(entry)
|
|
50
|
+
raise ConfigurationError, "Tenant #{tenant_key.inspect} configuration must be a Hash, got #{entry.class}." \
|
|
51
|
+
unless entry.is_a?(Hash)
|
|
52
|
+
|
|
53
|
+
entry[:constants]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validate!(constants)
|
|
57
|
+
tenant = tenant_key.inspect
|
|
58
|
+
unless constants.is_a?(Hash)
|
|
59
|
+
raise ConfigurationError, "Tenant #{tenant} `:constants` must be a Hash, got #{constants.class}."
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
missing = REQUIRED_KEYS - constants.keys
|
|
63
|
+
return if missing.empty?
|
|
64
|
+
|
|
65
|
+
raise ConfigurationError, "Tenant #{tenant} constants missing keys: #{missing.join(', ')}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'instrumentation'
|
|
4
|
+
|
|
5
|
+
module ConsoleKit
|
|
6
|
+
class TenantRollback
|
|
7
|
+
EVENT = 'console_kit.rollback'
|
|
8
|
+
MISSING_HANDLER = 'no connection handler is available to restore it, so it may still be serving the tenant ' \
|
|
9
|
+
'that was being unwound'
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def unwind(state, previous)
|
|
13
|
+
failures = restore(state, state.context_object || ConsoleKit.configuration.context_class)
|
|
14
|
+
StateStore.current = previous
|
|
15
|
+
raise RollbackError, failures unless failures.empty?
|
|
16
|
+
|
|
17
|
+
previous
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def restore(state, ctx)
|
|
23
|
+
wrapper = TenantConfigurator::ContextWrapper.new(ctx, state.undo_context.keys)
|
|
24
|
+
live, missing = resolve_handlers(state, ctx)
|
|
25
|
+
new(state.undo, wrapper).call(live, missing)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def resolve_handlers(state, ctx)
|
|
29
|
+
live = Connections::ConnectionManager.available_handlers(ctx)
|
|
30
|
+
.to_h { |handler| [handler.backend_key, handler] }
|
|
31
|
+
present, missing = state.undo_backends.keys.partition { |key| live.key?(key) }
|
|
32
|
+
[live.values_at(*present), missing]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def initialize(undo, context_wrapper)
|
|
37
|
+
@undo = undo
|
|
38
|
+
@context_wrapper = context_wrapper
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def call(handlers, unrestorable = [])
|
|
42
|
+
return [] if undo.nil?
|
|
43
|
+
|
|
44
|
+
Instrumentation.increment(EVENT)
|
|
45
|
+
abandoned(unrestorable) + restore_backends(handlers) + restore_context
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
attr_reader :undo, :context_wrapper
|
|
51
|
+
|
|
52
|
+
def abandoned(keys)
|
|
53
|
+
keys.map { |key| failure(key, UnsupportedBackendError.new("ConsoleKit: #{key} - #{MISSING_HANDLER}.")) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def restore_backends(handlers)
|
|
57
|
+
handlers.reverse.filter_map do |handler|
|
|
58
|
+
handler.restore(undo[:backends][handler.backend_key])
|
|
59
|
+
nil
|
|
60
|
+
rescue StandardError, NotImplementedError => e
|
|
61
|
+
failure(handler.display_name, e)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def restore_context
|
|
66
|
+
context_wrapper.restore(undo[:context])
|
|
67
|
+
[]
|
|
68
|
+
rescue StandardError, NotImplementedError => e
|
|
69
|
+
[failure('context', e)]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def failure(backend, error)
|
|
73
|
+
Instrumentation.increment('console_kit.rollback_failure')
|
|
74
|
+
{ backend: backend, error: error }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require_relative 'output'
|
|
4
4
|
|
|
5
5
|
module ConsoleKit
|
|
6
|
-
# For tenant selection
|
|
7
6
|
module TenantSelector
|
|
8
7
|
RETRY_LIMIT = 3
|
|
9
8
|
DEFAULT_SELECTION = '1'
|
|
@@ -37,12 +36,10 @@ module ConsoleKit
|
|
|
37
36
|
end
|
|
38
37
|
|
|
39
38
|
def menu_items
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
tenants.each_with_index.map do |key, index|
|
|
43
|
-
items << "#{index + 1}. #{key} (partner: #{tenant_partner(key)})"
|
|
39
|
+
numbered = ConsoleKit.tenants.keys.map.with_index(1) do |key, index|
|
|
40
|
+
"#{index}. #{key} (partner: #{tenant_partner(key)})"
|
|
44
41
|
end
|
|
45
|
-
|
|
42
|
+
['0. Skip (load without tenant configuration)'] + numbered
|
|
46
43
|
end
|
|
47
44
|
|
|
48
45
|
def tenant_partner(key) = ConsoleKit.tenants.dig(key, :constants, :partner_code) || 'N/A'
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConsoleKit
|
|
4
|
+
class TenantState
|
|
5
|
+
NO_DROPPED = [].freeze
|
|
6
|
+
EMPTY_UNDO = { context: {}.freeze, backends: {}.freeze, dropped: NO_DROPPED, context_object: nil }.freeze
|
|
7
|
+
|
|
8
|
+
attr_reader :tenant_key, :constants, :undo
|
|
9
|
+
|
|
10
|
+
module OwnCopy
|
|
11
|
+
class << self
|
|
12
|
+
def call(value)
|
|
13
|
+
case value
|
|
14
|
+
when Hash then value.transform_values { |entry| call(entry) }.freeze
|
|
15
|
+
when Array then value.map { |entry| call(entry) }.freeze
|
|
16
|
+
else value
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
def empty = new
|
|
24
|
+
|
|
25
|
+
def undo_bundle(context:, backends:, dropped: NO_DROPPED, context_object: nil)
|
|
26
|
+
{ context: OwnCopy.call(context), backends: OwnCopy.call(backends),
|
|
27
|
+
dropped: OwnCopy.call(dropped), context_object: context_object }.freeze
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initialize(tenant_key: nil, constants: {}, undo: EMPTY_UNDO, configured: false)
|
|
32
|
+
@tenant_key = tenant_key
|
|
33
|
+
@configured = configured
|
|
34
|
+
@constants = OwnCopy.call(constants)
|
|
35
|
+
@undo = undo
|
|
36
|
+
freeze
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def configured? = @configured
|
|
40
|
+
def undo_context = @undo[:context]
|
|
41
|
+
def undo_backends = @undo[:backends]
|
|
42
|
+
|
|
43
|
+
def context_object = @undo[:context_object]
|
|
44
|
+
|
|
45
|
+
def dropped_backends = @undo[:dropped] || NO_DROPPED
|
|
46
|
+
def snapshot_for(backend) = @undo[:backends][backend]
|
|
47
|
+
|
|
48
|
+
def backends_missing_from(live) = @undo[:backends].keys - live
|
|
49
|
+
|
|
50
|
+
def inspect = "#<ConsoleKit::TenantState #{@tenant_key.inspect} backends=#{@undo[:backends].keys.inspect}>"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
module StateStore
|
|
54
|
+
STATE_KEY = :console_kit_state
|
|
55
|
+
|
|
56
|
+
class << self
|
|
57
|
+
def current = stored || TenantState.empty
|
|
58
|
+
|
|
59
|
+
def stored = Thread.current.thread_variable_get(STATE_KEY)
|
|
60
|
+
|
|
61
|
+
def current=(state)
|
|
62
|
+
Thread.current.thread_variable_set(STATE_KEY, state)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def tenant_key = current.tenant_key
|
|
66
|
+
def configured? = current.configured?
|
|
67
|
+
|
|
68
|
+
def clear!
|
|
69
|
+
Thread.current.thread_variable_set(STATE_KEY, nil)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'errors'
|
|
4
|
+
require_relative 'tenant_state'
|
|
5
|
+
require_relative 'instrumentation'
|
|
6
|
+
require_relative 'tenant_rollback'
|
|
7
|
+
require_relative 'tenant_plan'
|
|
8
|
+
require_relative 'connections/connection_manager'
|
|
9
|
+
|
|
10
|
+
module ConsoleKit
|
|
11
|
+
class TenantSwitch
|
|
12
|
+
EVENT = 'console_kit.tenant_switch'
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def call(tenant_key) = new(tenant_key).call
|
|
16
|
+
|
|
17
|
+
def clear(context: nil) = new(nil, context: context).call
|
|
18
|
+
|
|
19
|
+
def verify_current!(dropped = nil)
|
|
20
|
+
state = StateStore.current
|
|
21
|
+
raise ConfigurationError, 'No tenant is currently configured.' unless state.configured?
|
|
22
|
+
|
|
23
|
+
new(state.tenant_key).send(:verify_committed, state, dropped)
|
|
24
|
+
state
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def unwind(state, previous) = TenantRollback.unwind(state, previous)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize(tenant_key, context: nil)
|
|
31
|
+
@tenant_key = tenant_key
|
|
32
|
+
@context = context || ConsoleKit.configuration.context_class
|
|
33
|
+
@failing_handler = nil
|
|
34
|
+
@dropped_backends = []
|
|
35
|
+
@handlers = []
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def call
|
|
39
|
+
Instrumentation.instrument(EVENT, tenant: @tenant_key, from: StateStore.tenant_key) { perform }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
attr_reader :tenant_key, :context
|
|
45
|
+
|
|
46
|
+
def perform
|
|
47
|
+
plan = TenantPlan.new(tenant_key)
|
|
48
|
+
@handlers = Connections::ConnectionManager.available_handlers(context, @dropped_backends)
|
|
49
|
+
targets = plan.targets_for(@handlers)
|
|
50
|
+
prepare_all(targets)
|
|
51
|
+
run_transaction(plan.constants, targets, snapshot_state)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def prepare_all(targets)
|
|
55
|
+
@handlers.each { |handler| handler.prepare(targets[handler.backend_key]) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def snapshot_state
|
|
59
|
+
snapshots = @handlers.to_h { |handler| [handler.backend_key, handler.snapshot] }
|
|
60
|
+
TenantState.undo_bundle(context: context_wrapper.current_values, backends: snapshots,
|
|
61
|
+
dropped: @dropped_backends, context_object: context)
|
|
62
|
+
rescue StandardError, NotImplementedError => e
|
|
63
|
+
raise switch_error(e)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def run_transaction(constants, targets, undo)
|
|
67
|
+
attempted = []
|
|
68
|
+
apply(constants, targets, attempted)
|
|
69
|
+
commit(constants, undo)
|
|
70
|
+
rescue StandardError, NotImplementedError => e
|
|
71
|
+
raise switch_error(e, TenantRollback.new(undo, context_wrapper).call(attempted))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def apply(constants, targets, attempted)
|
|
75
|
+
context_wrapper.assign(constants, TenantConfigurator.context_mapping)
|
|
76
|
+
connect_all(targets, attempted)
|
|
77
|
+
verify_all(attempted, targets)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def connect_all(targets, attempted)
|
|
81
|
+
@handlers.each do |handler|
|
|
82
|
+
attempted << handler
|
|
83
|
+
instrument_backend('console_kit.backend_connect', handler) { handler.connect!(targets[handler.backend_key]) }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def verify_all(handlers, targets)
|
|
88
|
+
handlers.each { |handler| verify_one(handler, targets[handler.backend_key]) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def verify_one(handler, target)
|
|
92
|
+
instrument_backend('console_kit.backend_verify', handler) { handler.verify!(target) }
|
|
93
|
+
rescue ConnectionVerificationError => e
|
|
94
|
+
Instrumentation.increment('console_kit.verification_failure')
|
|
95
|
+
raise e
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def instrument_backend(event, handler, &)
|
|
99
|
+
@failing_handler = handler
|
|
100
|
+
Instrumentation.instrument(event, backend: handler.backend_key, tenant: tenant_key, &)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def commit(constants, undo)
|
|
104
|
+
StateStore.current = TenantState.new(tenant_key: tenant_key, constants: constants, undo: undo,
|
|
105
|
+
configured: !tenant_key.nil?)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def switch_error(error, rollback_failures = [])
|
|
109
|
+
TenantSwitchError.new(
|
|
110
|
+
from_tenant: StateStore.tenant_key, to_tenant: tenant_key, original_error: error,
|
|
111
|
+
backend: error.try(:backend) || @failing_handler&.display_name, rollback_failures: rollback_failures
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def verify_committed(state, dropped = nil)
|
|
116
|
+
handlers = Connections::ConnectionManager.available_handlers(context, dropped)
|
|
117
|
+
dropped&.concat(state.backends_missing_from(handlers.map(&:backend_key)) - dropped)
|
|
118
|
+
verify_all(handlers, TenantPlan.new(state.tenant_key, constants: state.constants).targets_for(handlers))
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def context_wrapper = @context_wrapper ||= TenantConfigurator::ContextWrapper.for_context(context)
|
|
123
|
+
end
|
|
124
|
+
end
|
data/lib/console_kit/version.rb
CHANGED
data/lib/console_kit.rb
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'English'
|
|
4
|
+
require 'logger'
|
|
5
|
+
require 'active_support'
|
|
3
6
|
require 'active_support/core_ext/object/blank'
|
|
4
|
-
require 'active_support/core_ext/object/
|
|
7
|
+
require 'active_support/core_ext/object/try'
|
|
5
8
|
require 'active_support/core_ext/string/inflections'
|
|
9
|
+
require 'active_support/core_ext/time/calculations'
|
|
6
10
|
|
|
7
11
|
require_relative 'console_kit/version'
|
|
12
|
+
require_relative 'console_kit/errors'
|
|
13
|
+
require_relative 'console_kit/tenant_state'
|
|
14
|
+
require_relative 'console_kit/instrumentation'
|
|
8
15
|
require_relative 'console_kit/configuration'
|
|
16
|
+
require_relative 'console_kit/tenant_orchestrator'
|
|
9
17
|
require_relative 'console_kit/setup'
|
|
10
18
|
require_relative 'console_kit/console_helpers'
|
|
11
19
|
require_relative 'console_kit/prompt'
|
|
12
20
|
require_relative 'console_kit/railtie' if defined?(Rails::Railtie)
|
|
13
21
|
|
|
14
|
-
# Main module for ConsoleKit
|
|
15
22
|
module ConsoleKit
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
INCOMPLETE_VERIFICATION = 'ConsoleKit: tenant %<tenant>p is verified only for the backends ConsoleKit could ' \
|
|
24
|
+
'drive. Never switched, verified or rolled back, and possibly still serving another ' \
|
|
25
|
+
'tenant: %<backends>s.'
|
|
18
26
|
|
|
19
27
|
class << self
|
|
20
28
|
def configure = yield(configuration)
|
|
@@ -22,37 +30,56 @@ module ConsoleKit
|
|
|
22
30
|
|
|
23
31
|
def reset_configuration!
|
|
24
32
|
@configuration = nil
|
|
25
|
-
|
|
26
|
-
TenantConfigurator.configuration_success = false if defined?(TenantConfigurator)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def pretty_output = configuration.pretty_output
|
|
30
|
-
|
|
31
|
-
def pretty_output=(val)
|
|
32
|
-
configuration.pretty_output = val
|
|
33
|
+
StateStore.clear!
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
def tenants = configuration.tenants
|
|
36
37
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
end
|
|
38
|
+
def current_tenant = StateStore.tenant_key
|
|
39
|
+
def reset_current_tenant = TenantOrchestrator.reset
|
|
40
40
|
|
|
41
|
-
def
|
|
41
|
+
def switch_tenant(key) = TenantSwitch.call(key)
|
|
42
42
|
|
|
43
|
-
def
|
|
44
|
-
|
|
43
|
+
def verify_tenant!
|
|
44
|
+
dropped_now = []
|
|
45
|
+
state = TenantSwitch.verify_current!(dropped_now)
|
|
46
|
+
report_dropped_backends(state, dropped_now)
|
|
47
|
+
state
|
|
45
48
|
end
|
|
46
49
|
|
|
47
|
-
def
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
def with_tenant(key, &)
|
|
51
|
+
previous = StateStore.current
|
|
52
|
+
state = TenantSwitch.call(key)
|
|
53
|
+
unwind_after(state, previous, &)
|
|
51
54
|
end
|
|
52
55
|
|
|
53
|
-
def current_tenant = Setup.current_tenant
|
|
54
|
-
def reset_current_tenant = Setup.reset_current_tenant
|
|
55
56
|
def enable_pretty_output = configuration.pretty_output = true
|
|
56
57
|
def disable_pretty_output = configuration.pretty_output = false
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def report_dropped_backends(state, dropped_now)
|
|
62
|
+
dropped = state.dropped_backends | dropped_now
|
|
63
|
+
return if dropped.empty?
|
|
64
|
+
|
|
65
|
+
Instrumentation.increment('console_kit.incomplete_verification')
|
|
66
|
+
Output.print_warning(format(INCOMPLETE_VERIFICATION, tenant: state.tenant_key, backends: dropped.join(', ')))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def unwind_after(state, previous)
|
|
70
|
+
completed = false
|
|
71
|
+
yield.tap { completed = true }
|
|
72
|
+
ensure
|
|
73
|
+
unwind_scope(state, previous, completed ? nil : $ERROR_INFO)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def unwind_scope(state, previous, in_flight)
|
|
77
|
+
TenantSwitch.unwind(state, previous)
|
|
78
|
+
rescue RollbackError => e
|
|
79
|
+
raise e unless in_flight
|
|
80
|
+
|
|
81
|
+
Output.print_error("#{e.message}\nThis rollback failure did not replace the in-flight " \
|
|
82
|
+
"#{in_flight.class}: #{in_flight.message}")
|
|
83
|
+
end
|
|
57
84
|
end
|
|
58
85
|
end
|
|
@@ -5,22 +5,11 @@ require 'rails/generators/base'
|
|
|
5
5
|
|
|
6
6
|
module ConsoleKit
|
|
7
7
|
module Generators
|
|
8
|
-
# Generates the required files
|
|
9
8
|
class InstallGenerator < Rails::Generators::Base
|
|
10
9
|
source_root File.expand_path('templates', __dir__)
|
|
11
10
|
|
|
12
|
-
class_option :force, type: :boolean, default: false, desc: 'Overwrite existing files'
|
|
13
|
-
|
|
14
11
|
def copy_initializer
|
|
15
|
-
|
|
16
|
-
initializer_path = Rails.root.join('config', 'initializers', 'console_kit.rb')
|
|
17
|
-
|
|
18
|
-
if File.exist?(initializer_path) && !force
|
|
19
|
-
say_status :skipped, "Initializer already exists: #{initializer_path}", :yellow
|
|
20
|
-
else
|
|
21
|
-
template 'console_kit.rb', 'config/initializers/console_kit.rb', force: force
|
|
22
|
-
say_status :created, "Initializer generated at #{initializer_path}", :green
|
|
23
|
-
end
|
|
12
|
+
template 'console_kit.rb', 'config/initializers/console_kit.rb'
|
|
24
13
|
end
|
|
25
14
|
|
|
26
15
|
def remind_about_customization
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
Rails.application.config.after_initialize do
|
|
7
7
|
ConsoleKit.configure do |config|
|
|
8
8
|
# TODO: Set your tenants source, example:
|
|
9
|
+
# `shard` and `partner_code` are required for every tenant; `mongo_db`, `redis_db` and
|
|
10
|
+
# `elasticsearch_prefix` are optional. Extra keys (like `environment` below) are fine -
|
|
11
|
+
# they're just not read by ConsoleKit - but `config.validate!` will warn about them in
|
|
12
|
+
# case they're actually a typo of one of the keys above.
|
|
9
13
|
# {
|
|
10
14
|
# tenant_a: {
|
|
11
15
|
# constants: {
|