console_kit 1.1.0 → 1.3.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.
@@ -34,7 +34,7 @@ module ConsoleKit
34
34
  return if silent
35
35
 
36
36
  formatted = (type == :header ? "\n--- #{text} ---" : text)
37
- print_with(type, formatted, timestamp, { newline: newline })
37
+ print_with(type, formatted, timestamp: timestamp, newline: newline)
38
38
  end
39
39
  end
40
40
 
@@ -51,19 +51,33 @@ module ConsoleKit
51
51
  puts text
52
52
  end
53
53
 
54
- # Backtrace prints always with timestamp, no param
55
54
  def print_backtrace(exception)
56
55
  return if silent
57
56
 
58
- exception&.backtrace&.each { |line| print_with(:trace, " #{line}", true, { newline: true }) }
57
+ exception&.backtrace&.each do |line|
58
+ print_with(:trace, " #{line}", timestamp: true)
59
+ end
59
60
  end
60
61
 
61
62
  private
62
63
 
63
- def print_with(type, text, timestamp, opts = {})
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
+ def build_formatted_message(type, text, timestamp)
64
72
  meta = TYPES.fetch(type)
65
73
  message = build_message(text, meta[:symbol], timestamp)
66
- emit(message, meta[:color], opts.fetch(:newline, true))
74
+ colorize(message, meta[:color])
75
+ end
76
+
77
+ def colorize(message, color)
78
+ return message unless ConsoleKit.configuration.pretty_output && color
79
+
80
+ "\e[#{color}m#{message}\e[0m"
67
81
  end
68
82
 
69
83
  def build_message(text, symbol, timestamp)
@@ -73,12 +87,6 @@ module ConsoleKit
73
87
  def prefix_for(value) = value ? yield(value) : ''
74
88
  def timestamp_prefix(timestamp) = prefix_for(timestamp) { Time.current.strftime('[%Y-%m-%d %H:%M:%S] ') }
75
89
  def symbol_prefix(symbol) = prefix_for(symbol) { |sym| "#{sym} " }
76
-
77
- def emit(message, color, newline)
78
- writer = newline ? :puts : :print
79
- formatted = ConsoleKit.configuration.pretty_output && color ? "\e[#{color}m#{message}\e[0m" : message
80
- send(writer, formatted)
81
- end
82
90
  end
83
91
  end
84
92
  end
@@ -18,8 +18,8 @@ module ConsoleKit
18
18
 
19
19
  def apply_irb_prompt
20
20
  conf = IRB.conf
21
- conf[:PROMPT] ||= {}
22
- conf[:PROMPT][:CONSOLE_KIT] = {
21
+ prompt = conf[:PROMPT] ||= {}
22
+ prompt[:CONSOLE_KIT] = {
23
23
  PROMPT_I: "#{tenant_label} %N(%m):%03n> ",
24
24
  PROMPT_S: "#{tenant_label} %N(%m):%03n%l ",
25
25
  PROMPT_C: "#{tenant_label} %N(%m):%03n* ",
@@ -29,14 +29,21 @@ module ConsoleKit
29
29
  end
30
30
 
31
31
  def apply_pry_prompt
32
- Pry.config.prompt = Pry::Prompt.new(
33
- 'console_kit',
34
- 'ConsoleKit tenant prompt',
35
- [
36
- proc { |obj, nest_level, _pry_instance| "#{Prompt.send(:tenant_label)} (#{obj}):#{nest_level}> " },
37
- proc { |obj, nest_level, _pry_instance| "#{Prompt.send(:tenant_label)} (#{obj}):#{nest_level}* " }
38
- ]
39
- )
32
+ procs = pry_prompt_procs(tenant_label)
33
+ Pry.config.prompt = build_pry_prompt(procs)
34
+ end
35
+
36
+ def pry_prompt_procs(label)
37
+ [
38
+ proc { |obj, nest, _| "#{label} (#{obj}):#{nest}> " },
39
+ proc { |obj, nest, _| "#{label} (#{obj}):#{nest}* " }
40
+ ]
41
+ end
42
+
43
+ def build_pry_prompt(procs)
44
+ return procs unless defined?(Pry::Prompt)
45
+
46
+ Pry::Prompt.try(:new, 'console_kit', 'ConsoleKit tenant prompt', procs) || procs
40
47
  end
41
48
  end
42
49
  end
@@ -6,10 +6,10 @@ module ConsoleKit
6
6
  console do
7
7
  ConsoleKit::Setup.setup
8
8
  ConsoleKit::Prompt.apply
9
- if defined?(Pry)
10
- TOPLEVEL_BINDING.receiver.extend(ConsoleKit::ConsoleHelpers)
11
- elsif defined?(IRB::ExtendCommandBundle)
9
+ if defined?(IRB::ExtendCommandBundle) && !defined?(Pry)
12
10
  IRB::ExtendCommandBundle.include(ConsoleKit::ConsoleHelpers)
11
+ else
12
+ TOPLEVEL_BINDING.receiver.extend(ConsoleKit::ConsoleHelpers)
13
13
  end
14
14
  end
15
15
 
@@ -3,135 +3,25 @@
3
3
  require_relative 'tenant_selector'
4
4
  require_relative 'tenant_configurator'
5
5
  require_relative 'output'
6
+ require_relative 'setup_ui'
7
+ require_relative 'tenant_orchestrator'
6
8
 
7
9
  # Core Logic for initial Setup
8
10
  module ConsoleKit
9
11
  # Does the initial setup
10
12
  module Setup
11
13
  class << self
12
- ENVIRONMENT_WARNINGS = {
13
- 'production' => -> { Output.print_error('WARNING: You are connected to a PRODUCTION environment!') },
14
- 'staging' => -> { Output.print_warning('You are connected to a staging environment.') }
15
- }.freeze
16
-
17
14
  def current_tenant = Thread.current[:console_kit_current_tenant]
18
15
 
19
16
  def current_tenant=(val)
20
17
  Thread.current[:console_kit_current_tenant] = val
21
18
  end
22
19
 
23
- def setup = run_setup
20
+ def setup = TenantOrchestrator.run
24
21
  def tenant_setup_successful? = !current_tenant.to_s.empty?
25
-
26
- def reapply
27
- return unless tenant_setup_successful?
28
-
29
- Output.silence { TenantConfigurator.configure_tenant(current_tenant) }
30
- end
31
-
32
- def reset_current_tenant
33
- return warn_no_tenants unless tenants?
34
-
35
- key = select_tenant_key
36
- return cancel_switch if key == :abort || key.blank?
37
-
38
- clear_current_tenant
39
- return skip_tenant_message if %i[exit none].include?(key)
40
-
41
- configure(key)
42
- end
43
-
44
- private
45
-
46
- def run_setup
47
- return if tenant_setup_successful?
48
-
49
- ConsoleKit.configuration.validate!
50
- select_and_configure
51
- rescue StandardError => e
52
- handle_error(e)
53
- end
54
-
55
- def select_and_configure
56
- key = select_tenant_key
57
- return handle_selection_result(key) if %i[exit abort none].include?(key) || key.blank?
58
-
59
- configure(key)
60
- end
61
-
62
- def handle_selection_result(key)
63
- exit_on_key if %i[exit abort].include?(key)
64
-
65
- case key
66
- when :none
67
- Output.print_info('No tenant selected. Loading without tenant configuration.')
68
- when nil, ''
69
- Output.print_error('Tenant selection failed. Loading without tenant configuration.')
70
- end
71
- end
72
-
73
- def exit_on_key
74
- Output.print_info('Exiting console...')
75
- Kernel.exit
76
- end
77
-
78
- def configure(key)
79
- TenantConfigurator.configure_tenant(key)
80
- return unless TenantConfigurator.configuration_success
81
-
82
- self.current_tenant = key
83
- Prompt.apply
84
- print_tenant_banner(key)
85
- end
86
-
87
- def print_tenant_banner(key)
88
- constants = ConsoleKit.configuration.tenants[key]&.[](:constants) || {}
89
- env = constants[:environment]&.to_s&.downcase
90
- Output.print_success("Tenant initialized: #{key}")
91
- print_environment_warning(env) if env
92
- print_active_connections
93
- end
94
-
95
- def print_environment_warning(env) = ENVIRONMENT_WARNINGS[env]&.call
96
-
97
- def print_active_connections
98
- names = active_connection_names
99
- Output.print_info("Active connections: #{names.join(', ')}") unless names.empty?
100
- end
101
-
102
- def active_connection_names
103
- ctx = context_class
104
- return [] unless ctx
105
-
106
- handlers = ConsoleKit::Connections::ConnectionManager.available_handlers(ctx)
107
- handlers.map { |h| h.class.name.demodulize.delete_suffix('ConnectionHandler') }
108
- end
109
-
110
- def tenants = ConsoleKit.configuration.tenants
111
- def context_class = ConsoleKit.configuration.context_class
112
- def tenants? = tenants&.any?
113
- def no_tenants? = !tenants?
114
- def select_tenant_key = auto_select? ? tenants.keys.first : TenantSelector.select
115
- def auto_select? = single_tenant? || non_interactive?
116
- def single_tenant? = tenants.size == 1
117
- def non_interactive? = !$stdin.tty?
118
- def warn_no_tenants = Output.print_warning('Cannot reset tenant: No tenants configured.')
119
- def warn_reset = Output.print_warning("Resetting tenant: #{current_tenant}")
120
- def cancel_switch = Output.print_warning('Tenant switch cancelled.')
121
- def skip_tenant_message = Output.print_info('No tenant selected. Loading without tenant configuration.')
122
-
123
- def clear_current_tenant
124
- if current_tenant
125
- warn_reset
126
- TenantConfigurator.clear
127
- end
128
- self.current_tenant = nil
129
- end
130
-
131
- def handle_error(error)
132
- Output.print_error("Error setting up tenant: #{error.message}")
133
- Output.print_backtrace(error)
134
- end
22
+ def reapply = TenantOrchestrator.reapply
23
+ def reset_current_tenant = TenantOrchestrator.reset
24
+ def auto_select? = TenantOrchestrator.auto_select?
135
25
  end
136
26
  end
137
27
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConsoleKit
4
+ # UI helpers for Setup
5
+ module SetupUI
6
+ ENVIRONMENT_WARNINGS = {
7
+ 'production' => -> { Output.print_error('!!! CAUTION: YOU ARE IN PRODUCTION ENVIRONMENT !!!') },
8
+ 'staging' => -> { Output.print_warning('CAUTION: You are in staging environment.') }
9
+ }.freeze
10
+
11
+ class << self
12
+ def print_tenant_banner(key, config)
13
+ Output.print_success("Tenant initialized: #{key}")
14
+ print_env_warning(key, config)
15
+ print_active_connections
16
+ ConsoleKit::Connections::Dashboard.display if config.show_dashboard
17
+ end
18
+
19
+ private
20
+
21
+ def print_env_warning(key, config)
22
+ constants = config.tenants[key]&.[](:constants) || {}
23
+ env = constants[:environment]&.to_s&.downcase
24
+ ENVIRONMENT_WARNINGS[env]&.call if env
25
+ end
26
+
27
+ def print_active_connections
28
+ ctx = ConsoleKit.configuration.context_class
29
+ active = Connections::ConnectionManager.available_handlers(ctx).map do |handler|
30
+ handler.class.name.demodulize.delete_suffix('ConnectionHandler')
31
+ end
32
+
33
+ Output.print_info("Active connections: #{active.join(', ')}") if active.any?
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConsoleKit
4
+ module TenantConfigurator
5
+ # Encapsulates context and attributes to resolve DataClump smells
6
+ class ContextWrapper
7
+ HANDLER_ATTRIBUTES = {
8
+ Connections::SqlConnectionHandler => :tenant_shard,
9
+ Connections::MongoConnectionHandler => :tenant_mongo_db,
10
+ Connections::RedisConnectionHandler => :tenant_redis_db,
11
+ Connections::ElasticsearchConnectionHandler => :tenant_elasticsearch_prefix
12
+ }.freeze
13
+
14
+ attr_reader :ctx, :attributes
15
+
16
+ class << self
17
+ def for_context(ctx)
18
+ new(ctx, detect_attributes(ctx))
19
+ end
20
+
21
+ private
22
+
23
+ def detect_attributes(ctx)
24
+ methods = ctx.public_methods
25
+ partner_attrs(methods) + handler_attrs(methods)
26
+ end
27
+
28
+ def partner_attrs(methods)
29
+ methods.include?(:partner_identifier=) ? [:partner_identifier] : []
30
+ end
31
+
32
+ def handler_attrs(methods)
33
+ HANDLER_ATTRIBUTES.each_with_object([]) do |(handler, attr), list|
34
+ next unless methods.include?(:"#{attr}=")
35
+ next unless handler_available?(handler)
36
+
37
+ list << attr
38
+ end
39
+ end
40
+
41
+ def handler_available?(handler_class)
42
+ handler_class.new(nil).available?
43
+ rescue NotImplementedError, StandardError
44
+ false
45
+ end
46
+ end
47
+
48
+ def initialize(ctx, attributes)
49
+ @ctx = ctx
50
+ @attributes = attributes
51
+ end
52
+
53
+ def any_set?
54
+ attributes.any? { |attr| ctx.public_send(attr).present? }
55
+ end
56
+
57
+ def reset
58
+ attributes.each { |attr| ctx.public_send("#{attr}=", nil) }
59
+ end
60
+
61
+ def assign(constant, mapping)
62
+ attributes.map do |attr|
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
68
+ end
69
+
70
+ private
71
+
72
+ def safe_read(attr)
73
+ ctx.public_send(attr)
74
+ rescue StandardError
75
+ nil
76
+ end
77
+ end
78
+ end
79
+ end
@@ -2,53 +2,71 @@
2
2
 
3
3
  require_relative 'output'
4
4
  require_relative 'connections/connection_manager'
5
+ require_relative 'connections/dashboard'
6
+ require_relative 'tenant_configurator/context_wrapper'
5
7
 
6
8
  module ConsoleKit
7
9
  # For tenant configuration
8
10
  module TenantConfigurator
9
- class << self
10
- HANDLER_ATTRIBUTES = {
11
- Connections::SqlConnectionHandler => :tenant_shard,
12
- Connections::MongoConnectionHandler => :tenant_mongo_db,
13
- Connections::RedisConnectionHandler => :tenant_redis_db,
14
- Connections::ElasticsearchConnectionHandler => :tenant_elasticsearch_prefix
15
- }.freeze
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
16
18
 
19
+ class << self
17
20
  def configuration_success = Thread.current[:console_kit_configuration_success]
18
21
 
19
22
  def configuration_success=(val)
20
23
  Thread.current[:console_kit_configuration_success] = val
21
24
  end
22
25
 
26
+ def current_tenant_key = Thread.current[:console_kit_current_tenant_key]
27
+
28
+ def current_tenant_key=(val)
29
+ Thread.current[:console_kit_current_tenant_key] = val
30
+ end
31
+
23
32
  def configure_tenant(key)
24
- constants = ConsoleKit.configuration.tenants[key]&.[](:constants)
25
- return missing_config_error(key) unless constants
33
+ return true if key == current_tenant_key && configuration_success
26
34
 
27
- perform_configuration(key, constants)
35
+ attempt_configuration(key)
28
36
  rescue StandardError => e
29
- handle_error(e, key)
37
+ handle_error?(e, key)
30
38
  end
31
39
 
32
40
  def clear
33
41
  ctx = ConsoleKit.configuration.context_class
34
42
  return unless ctx
35
43
 
36
- reset_tenant(ctx)
37
- Output.print_info('Tenant context has been cleared.')
44
+ perform_clear(ContextWrapper.for_context(ctx))
38
45
  end
39
46
 
40
47
  private
41
48
 
42
- def reset_tenant(ctx)
43
- self.configuration_success = false
44
- reset_context_attributes(ctx)
45
- setup_connections(ctx)
49
+ def attempt_configuration(key)
50
+ constants = ConsoleKit.configuration.tenants[key]&.[](:constants)
51
+ return missing_config_error?(key) unless constants
52
+
53
+ execute_configuration(key, constants)
54
+ configuration_success
46
55
  end
47
56
 
48
- def reset_context_attributes(ctx)
49
- available_context_attributes(ctx).each do |attr|
50
- ctx.public_send("#{attr}=", nil)
51
- end
57
+ def perform_clear(wrapper)
58
+ return unless configuration_success || wrapper.any_set?
59
+
60
+ reset_tenant(wrapper)
61
+ Output.print_info('Tenant context has been cleared.')
62
+ true
63
+ end
64
+
65
+ def reset_tenant(wrapper)
66
+ self.configuration_success = false
67
+ self.current_tenant_key = nil
68
+ wrapper.reset
69
+ setup_connections(wrapper.ctx)
52
70
  end
53
71
 
54
72
  def validate_constants!(constants)
@@ -56,64 +74,54 @@ module ConsoleKit
56
74
  raise Error, "Tenant constants missing keys: #{missing.join(', ')}" unless missing.empty?
57
75
  end
58
76
 
59
- def missing_config_error(key)
77
+ def missing_config_error?(key)
60
78
  self.configuration_success = false
61
79
  Output.print_error("No configuration found for tenant: #{key}")
80
+ false
62
81
  end
63
82
 
64
- def perform_configuration(key, constants)
83
+ def execute_configuration(key, constants)
65
84
  validate_constants!(constants)
66
85
  apply_context(constants)
67
- configure_success(key)
68
- end
69
-
70
- def handler_available?(handler_class)
71
- handler_class.new(nil).available?
72
- rescue NotImplementedError, StandardError
73
- false
74
- end
75
-
76
- def available_context_attributes(ctx)
77
- attributes = %i[partner_identifier]
78
- HANDLER_ATTRIBUTES.each do |handler, attr|
79
- attributes << attr if handler_available?(handler) && ctx.respond_to?("#{attr}=")
80
- end
81
- attributes.select { |attr| ctx.respond_to?("#{attr}=") }
86
+ mark_success(key)
82
87
  end
83
88
 
84
89
  def apply_context(constant)
85
- ctx = ConsoleKit.configuration.context_class
86
- assign_context_attributes(ctx, constant)
87
- setup_connections(ctx)
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)
88
95
  end
89
96
 
90
- def assign_context_attributes(ctx, constant)
91
- attribute_to_constant = {
92
- partner_identifier: :partner_code,
93
- tenant_shard: :shard,
94
- tenant_mongo_db: :mongo_db,
95
- tenant_redis_db: :redis_db,
96
- tenant_elasticsearch_prefix: :elasticsearch_prefix
97
- }
98
-
99
- available_context_attributes(ctx).each do |attr|
100
- ctx.public_send("#{attr}=", constant[attribute_to_constant[attr]])
101
- end
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?
102
101
  end
103
102
 
104
103
  def setup_connections(context)
105
- ConsoleKit::Connections::ConnectionManager.available_handlers(context).each(&:connect)
104
+ Connections::ConnectionManager.available_handlers(context).each(&:connect)
106
105
  end
107
106
 
108
- def configure_success(key)
107
+ def mark_success(key)
109
108
  Output.print_success("Tenant set to: #{key}")
110
109
  self.configuration_success = true
110
+ self.current_tenant_key = key
111
111
  end
112
112
 
113
- def handle_error(error, key)
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
+ )
118
+ end
119
+
120
+ def handle_error?(error, key)
114
121
  self.configuration_success = false
115
122
  Output.print_error("Failed to configure tenant '#{key}': #{error.message}")
116
123
  Output.print_backtrace(error)
124
+ false
117
125
  end
118
126
  end
119
127
  end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConsoleKit
4
+ # Orchestrates tenant lifecycle, selection, and configuration
5
+ class TenantOrchestrator
6
+ class << self
7
+ def auto_select? = (tenants.size == 1) || !$stdin.tty?
8
+
9
+ def current_tenant = Setup.current_tenant
10
+
11
+ def current_tenant=(val)
12
+ Setup.current_tenant = val
13
+ end
14
+
15
+ def reapply
16
+ return unless tenant_setup_successful?
17
+
18
+ Output.silence do
19
+ TenantConfigurator.current_tenant_key = nil
20
+ TenantConfigurator.configure_tenant(current_tenant)
21
+ end
22
+ end
23
+
24
+ def reset
25
+ return warn_no_tenants unless tenants?
26
+
27
+ perform_reset
28
+ end
29
+
30
+ def run
31
+ return if tenant_setup_successful?
32
+
33
+ perform_setup
34
+ rescue StandardError => e
35
+ handle_error(e)
36
+ end
37
+
38
+ def tenants = ConsoleKit.configuration.tenants
39
+ def tenants? = tenants&.any?
40
+ 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
+
45
+ private
46
+
47
+ def tenant_setup_successful? = !current_tenant.to_s.empty?
48
+
49
+ def perform_reset
50
+ key = select_tenant_key
51
+ return cancel_switch if key == :abort || key.blank?
52
+ return already_on_tenant?(key) if key == current_tenant
53
+
54
+ clear_current_tenant
55
+ return skip_tenant_message if %i[exit none].include?(key)
56
+
57
+ configure(key)
58
+ end
59
+
60
+ def perform_setup
61
+ ConsoleKit.configuration.validate!
62
+ key = select_tenant_key
63
+ return handle_selection_result(key) if %i[exit abort none].include?(key) || key.blank?
64
+
65
+ configure(key)
66
+ end
67
+
68
+ def handle_selection_result(key)
69
+ exit_on_key if %i[exit abort].include?(key)
70
+
71
+ skip_tenant_message if key == :none
72
+ Output.print_error('Tenant selection failed. Loading without tenant configuration.') if key.blank?
73
+ end
74
+
75
+ def exit_on_key
76
+ Output.print_info('Exiting console...')
77
+ Kernel.exit
78
+ end
79
+
80
+ def configure(key)
81
+ TenantConfigurator.configure_tenant(key)
82
+ return unless TenantConfigurator.configuration_success
83
+
84
+ Setup.current_tenant = key
85
+ Prompt.apply
86
+ SetupUI.print_tenant_banner(key, ConsoleKit.configuration)
87
+ end
88
+
89
+ def already_on_tenant?(key)
90
+ Output.print_info("Already using tenant: #{key}. No changes made.")
91
+ true
92
+ end
93
+
94
+ def clear_current_tenant
95
+ if current_tenant
96
+ Output.print_warning("Resetting tenant: #{current_tenant}")
97
+ TenantConfigurator.clear
98
+ end
99
+ self.current_tenant = nil
100
+ end
101
+
102
+ def handle_error(error)
103
+ Output.print_error("Error setting up tenant: #{error.message}")
104
+ Output.print_backtrace(error)
105
+ end
106
+ end
107
+ end
108
+ end