console_kit 1.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee9784d78350655db23efa1080d49d5e2527e1b6530ef7a1c2772974e57a89ec
4
- data.tar.gz: 2edbd66f104e2a2e549d11991fbbdbd1f77118227dfad56122bb58baefd4ee72
3
+ metadata.gz: 348d99d0b8d74a78d96ffa4e74f7ee2baf25aab659700b5d44929d2e3cd87104
4
+ data.tar.gz: 9c5e7514f498818e2ffe8b0a9376511cbf364e81336a266df7fe02d143a26cf4
5
5
  SHA512:
6
- metadata.gz: 92d30809eba5bb236130e2e472cb2ae8fc34f9ea0bd69b3a5c67334199cc34a9894666797ebe66c52489f8a2c22a403d1f65e097bfafdba89e3d3d9d5ec8d5b9
7
- data.tar.gz: db949d61fdcff82557532b163dd1df0487e6e3fb6c3f0901eb172fa83b6adc87f70b191b59dca6405b76c90fad67c3b2c58aaff21161e441ae295e3dbdea116b
6
+ metadata.gz: 5e56f04db89485e9b0a3a23b667062a8998287a8ab9ad67dc793f3202730ba6f54dcf02f45ab65ff739e3aef23ec7c65574ba32c4aafe7cebc1a6dcff63c907f
7
+ data.tar.gz: 86d6d4457bb2ad62e8ac2ecdd22c3afc0fa72313f4625cf4b304bbd1f0436a67033e0a2c4190d995895b12aba4aef77c68465ffa1fe1dcf6815d93909f355394
data/.DS_Store ADDED
Binary file
data/.reek.yml.new ADDED
File without changes
@@ -39,8 +39,9 @@ module ConsoleKit
39
39
  private
40
40
 
41
41
  def resolve_context_class(val)
42
- val.to_s.constantize
43
- rescue NameError
42
+ klass = val.to_s.safe_constantize
43
+ return klass if klass
44
+
44
45
  raise Error, "ConsoleKit: context_class '#{val}' could not be found. " \
45
46
  'Ensure the class is defined before configuration is accessed.'
46
47
  end
@@ -29,7 +29,6 @@ module ConsoleKit
29
29
  if thread.join(timeout)
30
30
  result_wrapper[:value] || error_diagnostics(handler_name, StandardError.new('Unknown error'))
31
31
  else
32
- thread.kill
33
32
  timeout_diagnostics(handler_name, timeout)
34
33
  end
35
34
  end
@@ -38,29 +37,24 @@ module ConsoleKit
38
37
 
39
38
  def spawn_diagnostic_thread(handler_name)
40
39
  wrapper = { value: nil }
41
- thread = Thread.new { wrapper[:value] = run_diagnostics_safely(handler_name) }
40
+ thread = Thread.new { wrapper[:value] = run_diagnostics_safely(handler_name) { diagnostics } }
42
41
  [thread, wrapper]
43
42
  end
44
43
 
45
44
  def run_diagnostics_safely(name)
46
- diagnostics
45
+ yield
47
46
  rescue StandardError => e
48
47
  error_diagnostics(name, e)
49
48
  end
50
49
 
51
- def context_attribute(name)
52
- @context.respond_to?(name, true) ? @context.send(name) : nil
53
- end
54
-
55
50
  def measure_latency
56
51
  start = clock_time
57
52
  yield
58
53
  ((clock_time - start) * 1000).round(1)
59
54
  end
60
55
 
61
- def unavailable_diagnostics(name)
62
- { name: name, status: :unavailable, latency_ms: nil, details: {} }
63
- end
56
+ def context_attribute(name) = @context.try(name)
57
+ def unavailable_diagnostics(name) = { name: name, status: :unavailable, latency_ms: nil, details: {} }
64
58
  end
65
59
  end
66
60
  end
@@ -6,54 +6,66 @@ module ConsoleKit
6
6
  module Connections
7
7
  # Handles Elasticsearch connections
8
8
  class ElasticsearchConnectionHandler < BaseConnectionHandler
9
+ class << self
10
+ def elasticsearch_available?
11
+ return false unless defined?(Elasticsearch::Model)
12
+
13
+ Elasticsearch::Model.method(:client)
14
+ true
15
+ rescue NameError
16
+ false
17
+ end
18
+
19
+ def apply_prefix(prefix)
20
+ return unless defined?(Elasticsearch::Model)
21
+
22
+ Elasticsearch::Model.try(:index_name_prefix=, prefix)
23
+ end
24
+ end
25
+
9
26
  def connect
10
27
  prefix = context_attribute(:tenant_elasticsearch_prefix).presence
11
28
  Output.print_info(switch_message(prefix))
12
29
  Thread.current[:console_kit_elasticsearch_prefix] = prefix
13
- apply_model_index_prefix(prefix)
30
+ self.class.apply_prefix(prefix)
14
31
  end
15
32
 
16
- def available?
17
- !!(defined?(Elasticsearch::Model) && Elasticsearch::Model.respond_to?(:client))
18
- end
33
+ def available? = self.class.elasticsearch_available?
19
34
 
20
35
  def diagnostics
21
- name = 'Elasticsearch'
22
- return unavailable_diagnostics(name) unless available?
36
+ return unavailable_diagnostics('Elasticsearch') unless available?
23
37
 
24
- client = Elasticsearch::Model.client
25
- latency = measure_latency { client.ping }
26
- build_elasticsearch_diagnostics(client, latency)
38
+ perform_diagnostics
27
39
  rescue StandardError => e
28
- error_diagnostics(name, e)
40
+ error_diagnostics('Elasticsearch', e)
29
41
  end
30
42
 
31
43
  private
32
44
 
33
- def build_elasticsearch_diagnostics(client, latency)
45
+ def perform_diagnostics
46
+ client = Elasticsearch::Model.client
47
+ latency = measure_latency do
48
+ client.ping
49
+ rescue StandardError
50
+ nil
51
+ end
52
+ health = client.cluster.health
53
+ build_elasticsearch_diagnostics(health['cluster_name'], health['status'], latency)
54
+ end
55
+
56
+ def build_elasticsearch_diagnostics(cluster, status, latency)
34
57
  {
35
58
  name: 'Elasticsearch',
36
59
  status: :connected,
37
60
  latency_ms: latency,
38
- details: elasticsearch_details(client.cluster.health)
39
- }
40
- end
41
-
42
- def elasticsearch_details(health)
43
- {
44
- prefix: context_attribute(:tenant_elasticsearch_prefix),
45
- cluster: health['cluster_name'],
46
- health: health['status']
61
+ details: {
62
+ prefix: context_attribute(:tenant_elasticsearch_prefix),
63
+ cluster: cluster,
64
+ health: status
65
+ }
47
66
  }
48
67
  end
49
68
 
50
- def apply_model_index_prefix(prefix)
51
- return unless defined?(Elasticsearch::Model)
52
- return unless Elasticsearch::Model.respond_to?(:index_name_prefix=)
53
-
54
- Elasticsearch::Model.index_name_prefix = prefix
55
- end
56
-
57
69
  def switch_message(prefix)
58
70
  prefix ? "Setting Elasticsearch index prefix: #{prefix}" : 'Resetting Elasticsearch index prefix to default'
59
71
  end
@@ -8,51 +8,67 @@ module ConsoleKit
8
8
  class MongoConnectionHandler < BaseConnectionHandler
9
9
  def connect
10
10
  db = context_attribute(:tenant_mongo_db).presence
11
- Output.print_info(switch_message(db))
12
- Mongoid.override_database(db)
11
+ switch_mongo(db)
13
12
  rescue NoMethodError
14
- Output.print_warning('Mongoid.override_database is not available in this version of Mongoid.')
13
+ Output.print_warning('Mongoid client override is not available in this version of Mongoid.')
15
14
  end
16
15
 
17
16
  def available? = defined?(Mongoid)
18
17
 
19
18
  def diagnostics
20
- name = 'MongoDB'
21
- return unavailable_diagnostics(name) unless available?
19
+ return unavailable_diagnostics('MongoDB') unless available?
22
20
 
23
- db = tenant_database
24
- latency = measure_latency { db.command(ping: 1) }
25
- build_mongo_diagnostics(db, latency)
21
+ perform_diagnostics
26
22
  rescue StandardError => e
27
- error_diagnostics(name, e)
23
+ error_diagnostics('MongoDB', e)
28
24
  end
29
25
 
30
26
  private
31
27
 
32
- def tenant_database
33
- override = context_attribute(:tenant_mongo_db).presence
34
- client = Mongoid.default_client
35
- return client.database unless override
36
-
37
- client.use(override).database
28
+ def perform_diagnostics
29
+ db = tenant_database
30
+ latency = measure_latency { db.command(ping: 1) }
31
+ info = db.command(buildInfo: 1).first
32
+ build_mongo_diagnostics(db.name, info['version'], latency)
38
33
  end
39
34
 
40
- def build_mongo_diagnostics(database, latency)
41
- build_info = database.command(buildInfo: 1).first
35
+ def build_mongo_diagnostics(name, version, latency)
42
36
  {
43
37
  name: 'MongoDB',
44
38
  status: :connected,
45
39
  latency_ms: latency,
46
- details: mongo_details(database.name, build_info['version'])
40
+ details: { database: name, version: version }
47
41
  }
48
42
  end
49
43
 
50
- def mongo_details(name, version)
51
- { database: name, version: version }
44
+ def tenant_database
45
+ override = context_attribute(:tenant_mongo_db).presence
46
+ client = Mongoid.default_client
47
+ (override ? client.use(override) : client).database
48
+ end
49
+
50
+ def switch_mongo(db)
51
+ if db.nil?
52
+ Output.print_info('Resetting MongoDB client to default')
53
+ reset_overrides
54
+ elsif named_client?(db)
55
+ Output.print_info("Switching to MongoDB client: #{db}")
56
+ Mongoid.override_client(db)
57
+ else
58
+ Output.print_info("Switching to MongoDB database: #{db}")
59
+ Mongoid.override_database(db)
60
+ end
61
+ end
62
+
63
+ def reset_overrides
64
+ Mongoid.override_client(nil) if Mongoid.respond_to?(:override_client)
65
+ Mongoid.override_database(nil)
52
66
  end
53
67
 
54
- def switch_message(db)
55
- db ? "Switching to MongoDB client: #{db}" : 'Resetting MongoDB client to default'
68
+ def named_client?(name)
69
+ Mongoid::Config.clients.key?(name.to_s)
70
+ rescue StandardError
71
+ false
56
72
  end
57
73
  end
58
74
  end
@@ -8,68 +8,67 @@ module ConsoleKit
8
8
  class RedisConnectionHandler < BaseConnectionHandler
9
9
  DEFAULT_REDIS_DB = 0
10
10
 
11
+ class << self
12
+ def redis_client = Redis.try(:current)
13
+
14
+ def warn_no_auto_select(db_index)
15
+ Output.print_warning("Redis DB #{db_index} configured but auto-select not supported with RedisClient. " \
16
+ 'Ensure your Redis configuration sets the correct DB.')
17
+ end
18
+ end
19
+
11
20
  def connect
12
- db = context_attribute(:tenant_redis_db) || DEFAULT_REDIS_DB
13
- Output.print_info(switch_message(db))
14
- select_redis_db(db)
21
+ db_index = context_attribute(:tenant_redis_db) || DEFAULT_REDIS_DB
22
+ Output.print_info(switch_message(db_index))
23
+ select_redis_db(db_index)
15
24
  end
16
25
 
17
26
  def available? = defined?(Redis)
18
27
 
19
28
  def diagnostics
20
- name = 'Redis'
21
- return unavailable_diagnostics(name) unless available?
29
+ redis = self.class.redis_client if available?
30
+ return unavailable_diagnostics('Redis') unless redis
22
31
 
23
- redis = fetch_redis_client
24
- return unavailable_diagnostics(name) unless redis
25
-
26
- latency = measure_latency { redis.ping }
27
- build_redis_diagnostics(redis.info, latency)
32
+ perform_diagnostics(redis)
28
33
  rescue StandardError => e
29
- error_diagnostics(name, e)
34
+ error_diagnostics('Redis', e)
30
35
  end
31
36
 
32
37
  private
33
38
 
34
- def fetch_redis_client
35
- Redis.respond_to?(:current) && Redis.current
39
+ def perform_diagnostics(redis)
40
+ latency = measure_latency { redis.ping }
41
+ info = redis.info
42
+ build_redis_diagnostics(info['redis_version'], info['used_memory_human'], latency)
36
43
  end
37
44
 
38
- def build_redis_diagnostics(info, latency)
45
+ def build_redis_diagnostics(version, memory, latency)
39
46
  {
40
47
  name: 'Redis',
41
48
  status: :connected,
42
49
  latency_ms: latency,
43
- details: redis_details(info)
50
+ details: {
51
+ db: context_attribute(:tenant_redis_db) || DEFAULT_REDIS_DB,
52
+ version: version,
53
+ memory: memory
54
+ }
44
55
  }
45
56
  end
46
57
 
47
- def redis_details(info)
48
- {
49
- db: context_attribute(:tenant_redis_db) || DEFAULT_REDIS_DB,
50
- version: info['redis_version'],
51
- memory: info['used_memory_human']
52
- }
53
- end
54
-
55
- def select_redis_db(db)
56
- redis = fetch_redis_client
58
+ def select_redis_db(db_index)
59
+ klass = self.class
60
+ redis = klass.redis_client
57
61
  if redis
58
- redis.select(db)
59
- elsif defined?(RedisClient) && db != DEFAULT_REDIS_DB
60
- warn_about_redis_client(db)
62
+ redis.select(db_index)
63
+ elsif defined?(RedisClient) && db_index != DEFAULT_REDIS_DB
64
+ klass.warn_no_auto_select(db_index)
61
65
  end
62
66
  rescue NoMethodError
63
67
  Output.print_warning('Redis.current is not available (deprecated in Redis v5+).')
64
68
  end
65
69
 
66
- def warn_about_redis_client(db)
67
- Output.print_warning("Redis DB #{db} configured but auto-select not supported with RedisClient. " \
68
- 'Ensure your Redis configuration sets the correct DB.')
69
- end
70
-
71
- def switch_message(db)
72
- db ? "Switching to Redis DB: #{db}" : 'Resetting Redis connection to default'
70
+ def switch_message(db_index)
71
+ db_index ? "Switching to Redis DB: #{db_index}" : 'Resetting Redis connection to default'
73
72
  end
74
73
  end
75
74
  end
@@ -6,6 +6,16 @@ module ConsoleKit
6
6
  module Connections
7
7
  # Handles SQL connections
8
8
  class SqlConnectionHandler < BaseConnectionHandler
9
+ class << self
10
+ def sql_version(conn)
11
+ conn.select_value('SELECT version()')
12
+ rescue StandardError
13
+ nil
14
+ end
15
+
16
+ def base_class_name = ConsoleKit.configuration.sql_base_class
17
+ end
18
+
9
19
  def connect
10
20
  shard = context_attribute(:tenant_shard).presence&.to_sym
11
21
  Output.print_info("#{connection_message(shard)} via #{base_class}")
@@ -13,25 +23,26 @@ module ConsoleKit
13
23
  shard ? base_class.establish_connection(shard) : base_class.establish_connection
14
24
  end
15
25
 
16
- def available? = sql_base_class_name.to_s.safe_constantize.present?
26
+ def available? = self.class.base_class_name.to_s.safe_constantize.present?
17
27
 
18
28
  def diagnostics
19
- name = 'SQL'
20
- return unavailable_diagnostics(name) unless available?
29
+ return unavailable_diagnostics('SQL') unless available?
21
30
 
22
- conn = base_class.connection
23
- latency = measure_latency { conn.execute('SELECT 1') }
24
- build_sql_diagnostics(conn, latency)
31
+ perform_diagnostics
25
32
  rescue StandardError => e
26
- error_diagnostics(name, e)
33
+ error_diagnostics('SQL', e)
27
34
  end
28
35
 
29
36
  private
30
37
 
31
- def disconnect_existing_pool
32
- return unless base_class.respond_to?(:connection_pool)
38
+ def perform_diagnostics
39
+ conn = base_class.connection
40
+ latency = measure_latency { conn.execute('SELECT 1') }
41
+ build_sql_diagnostics(conn, latency)
42
+ end
33
43
 
34
- pool = base_class.connection_pool
44
+ def disconnect_existing_pool
45
+ pool = base_class.try(:connection_pool)
35
46
  pool&.disconnect!
36
47
  end
37
48
 
@@ -43,29 +54,22 @@ module ConsoleKit
43
54
  details: {
44
55
  adapter: conn.adapter_name,
45
56
  pool_size: base_class.connection_pool.size,
46
- version: fetch_sql_version(conn).to_s.truncate(50)
57
+ version: self.class.sql_version(conn).to_s.truncate(50)
47
58
  }
48
59
  }
49
60
  end
50
61
 
51
- def fetch_sql_version(conn)
52
- conn.select_value('SELECT version()')
53
- rescue StandardError
54
- nil
55
- end
56
-
57
62
  def base_class
58
- klass = sql_base_class_name.to_s.safe_constantize
59
- return klass if klass
60
-
61
- raise Error, "ConsoleKit: sql_base_class '#{sql_base_class_name}' could not be found."
63
+ @base_class ||= begin
64
+ name = self.class.base_class_name
65
+ klass = name.to_s.safe_constantize
66
+ klass || raise(Error, "ConsoleKit: sql_base_class '#{name}' could not be found.")
67
+ end
62
68
  end
63
69
 
64
70
  def connection_message(shard)
65
71
  shard ? "Establishing SQL connection to shard: #{shard}" : 'Resetting SQL connection to default'
66
72
  end
67
-
68
- def sql_base_class_name = ConsoleKit.configuration.sql_base_class
69
73
  end
70
74
  end
71
75
  end
@@ -3,24 +3,27 @@
3
3
  module ConsoleKit
4
4
  # Helper methods available in the Rails console
5
5
  module ConsoleHelpers
6
- def switch_tenant = ConsoleKit.reset_current_tenant
6
+ def switch_tenant
7
+ ConsoleKit.reset_current_tenant
8
+ self
9
+ end
7
10
 
8
11
  def tenant_info
9
12
  tenant = ConsoleKit::Setup.current_tenant
10
- unless tenant
11
- ConsoleKit::Output.print_warning('No tenant is currently configured.')
12
- return
13
- end
13
+ return no_tenant_warning unless tenant
14
14
 
15
- constants = ConsoleKit.configuration.tenants[tenant]&.[](:constants) || {}
16
- print_tenant_details(tenant, constants)
15
+ display_tenant_info(tenant)
16
+ nil
17
17
  end
18
18
 
19
- def dashboard = ConsoleKit::Connections::Dashboard.display
19
+ def dashboard
20
+ ConsoleKit::Connections::Dashboard.display
21
+ self
22
+ end
20
23
 
21
24
  def tenants
22
25
  names = ConsoleKit.configuration.tenants&.keys || []
23
- ConsoleKit::Output.print_list(names, header: 'Available Tenants')
26
+ print_available_tenants(names)
24
27
  names
25
28
  end
26
29
 
@@ -31,17 +34,31 @@ module ConsoleKit
31
34
 
32
35
  private
33
36
 
34
- def print_tenant_details(tenant, constants)
35
- ConsoleKit::Output.print_header("Tenant: #{tenant}")
36
- DETAIL_LABELS.each do |label, key|
37
- constants.fetch(key, :missing).then do |val|
38
- case val
39
- when :missing then next
40
- else ConsoleKit::Output.print_info(" #{label.ljust(13)}#{val}")
41
- end
37
+ def no_tenant_warning
38
+ ConsoleKit::Output.print_warning('No tenant is currently configured.')
39
+ self
40
+ end
41
+
42
+ def display_tenant_info(tenant)
43
+ constants = ConsoleKit.configuration.tenants[tenant]&.[](:constants) || {}
44
+ ConsoleHelpers.print_tenant_details(tenant, constants)
45
+ self
46
+ end
47
+
48
+ def print_available_tenants(names)
49
+ ConsoleKit::Output.print_list(names, header: 'Available Tenants')
50
+ self
51
+ end
52
+
53
+ class << self
54
+ def print_tenant_details(tenant, constants)
55
+ ConsoleKit::Output.print_header("Tenant: #{tenant}")
56
+ DETAIL_LABELS.each do |label, key|
57
+ next unless constants.key?(key)
58
+
59
+ ConsoleKit::Output.print_info(" #{label.ljust(13)}#{constants[key]}")
42
60
  end
43
61
  end
44
- nil
45
62
  end
46
63
  end
47
64
  end
@@ -63,12 +63,21 @@ module ConsoleKit
63
63
 
64
64
  def print_with(type, text, options = {})
65
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)
66
72
  meta = TYPES.fetch(type)
67
- message = build_message(text, meta[:symbol], opts[:timestamp])
68
- color = meta[:color]
69
- formatted = ConsoleKit.configuration.pretty_output && color ? "\e[#{color}m#{message}\e[0m" : message
73
+ message = build_message(text, meta[:symbol], timestamp)
74
+ colorize(message, meta[:color])
75
+ end
76
+
77
+ def colorize(message, color)
78
+ return message unless ConsoleKit.configuration.pretty_output && color
70
79
 
71
- opts.fetch(:newline, true) ? puts(formatted) : print(formatted)
80
+ "\e[#{color}m#{message}\e[0m"
72
81
  end
73
82
 
74
83
  def build_message(text, symbol, timestamp)
@@ -41,11 +41,9 @@ module ConsoleKit
41
41
  end
42
42
 
43
43
  def build_pry_prompt(procs)
44
- if defined?(Pry::Prompt) && Pry::Prompt.respond_to?(:new)
45
- Pry::Prompt.new('console_kit', 'ConsoleKit tenant prompt', procs)
46
- else
47
- procs
48
- end
44
+ return procs unless defined?(Pry::Prompt)
45
+
46
+ Pry::Prompt.try(:new, 'console_kit', 'ConsoleKit tenant prompt', procs) || procs
49
47
  end
50
48
  end
51
49
  end
@@ -4,6 +4,7 @@ require_relative 'tenant_selector'
4
4
  require_relative 'tenant_configurator'
5
5
  require_relative 'output'
6
6
  require_relative 'setup_ui'
7
+ require_relative 'tenant_orchestrator'
7
8
 
8
9
  # Core Logic for initial Setup
9
10
  module ConsoleKit
@@ -16,91 +17,11 @@ module ConsoleKit
16
17
  Thread.current[:console_kit_current_tenant] = val
17
18
  end
18
19
 
19
- def setup = run_setup
20
+ def setup = TenantOrchestrator.run
20
21
  def tenant_setup_successful? = !current_tenant.to_s.empty?
21
-
22
- def reapply
23
- return unless tenant_setup_successful?
24
-
25
- Output.silence { TenantConfigurator.configure_tenant(current_tenant) }
26
- end
27
-
28
- def reset_current_tenant
29
- return warn_no_tenants unless tenants?
30
-
31
- perform_tenant_reset
32
- end
33
-
34
- private
35
-
36
- def perform_tenant_reset
37
- key = select_tenant_key
38
- return cancel_switch if key == :abort || key.blank?
39
-
40
- clear_current_tenant
41
- return skip_tenant_message if %i[exit none].include?(key)
42
-
43
- configure(key)
44
- end
45
-
46
- def run_setup
47
- return if tenant_setup_successful?
48
-
49
- config = ConsoleKit.configuration
50
- config.validate!
51
- select_and_configure
52
- rescue StandardError => e
53
- handle_error(e)
54
- end
55
-
56
- def select_and_configure
57
- key = select_tenant_key
58
- return handle_selection_result(key) if %i[exit abort none].include?(key) || key.blank?
59
-
60
- configure(key)
61
- end
62
-
63
- def handle_selection_result(key)
64
- exit_on_key if %i[exit abort].include?(key)
65
-
66
- skip_tenant_message if key == :none
67
- Output.print_error('Tenant selection failed. Loading without tenant configuration.') if key.blank?
68
- end
69
-
70
- def exit_on_key
71
- Output.print_info('Exiting console...')
72
- Kernel.exit
73
- end
74
-
75
- def configure(key)
76
- TenantConfigurator.configure_tenant(key)
77
- return unless TenantConfigurator.configuration_success
78
-
79
- self.current_tenant = key
80
- Prompt.apply
81
- SetupUI.print_tenant_banner(key, ConsoleKit.configuration)
82
- end
83
-
84
- def tenants = ConsoleKit.configuration.tenants
85
- def tenants? = tenants&.any?
86
- def select_tenant_key = auto_select? ? tenants.keys.first : TenantSelector.select
87
- def auto_select? = (tenants.size == 1) || !$stdin.tty?
88
- def warn_no_tenants = Output.print_warning('Cannot reset tenant: No tenants configured.')
89
- def cancel_switch = Output.print_warning('Tenant switch cancelled.')
90
- def skip_tenant_message = Output.print_info('No tenant selected. Loading without tenant configuration.')
91
-
92
- def clear_current_tenant
93
- if current_tenant
94
- Output.print_warning("Resetting tenant: #{current_tenant}")
95
- TenantConfigurator.clear
96
- end
97
- self.current_tenant = nil
98
- end
99
-
100
- def handle_error(error)
101
- Output.print_error("Error setting up tenant: #{error.message}")
102
- Output.print_backtrace(error)
103
- end
22
+ def reapply = TenantOrchestrator.reapply
23
+ def reset_current_tenant = TenantOrchestrator.reset
24
+ def auto_select? = TenantOrchestrator.auto_select?
104
25
  end
105
26
  end
106
27
  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
@@ -3,53 +3,70 @@
3
3
  require_relative 'output'
4
4
  require_relative 'connections/connection_manager'
5
5
  require_relative 'connections/dashboard'
6
+ require_relative 'tenant_configurator/context_wrapper'
6
7
 
7
8
  module ConsoleKit
8
9
  # For tenant configuration
9
10
  module TenantConfigurator
10
- class << self
11
- HANDLER_ATTRIBUTES = {
12
- Connections::SqlConnectionHandler => :tenant_shard,
13
- Connections::MongoConnectionHandler => :tenant_mongo_db,
14
- Connections::RedisConnectionHandler => :tenant_redis_db,
15
- Connections::ElasticsearchConnectionHandler => :tenant_elasticsearch_prefix
16
- }.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
17
18
 
19
+ class << self
18
20
  def configuration_success = Thread.current[:console_kit_configuration_success]
19
21
 
20
22
  def configuration_success=(val)
21
23
  Thread.current[:console_kit_configuration_success] = val
22
24
  end
23
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
+
24
32
  def configure_tenant(key)
25
- constants = ConsoleKit.configuration.tenants[key]&.[](:constants)
26
- return missing_config_error(key) unless constants
33
+ return true if key == current_tenant_key && configuration_success
27
34
 
28
- perform_configuration(key, constants)
35
+ attempt_configuration(key)
29
36
  rescue StandardError => e
30
- handle_error(e, key)
37
+ handle_error?(e, key)
31
38
  end
32
39
 
33
40
  def clear
34
41
  ctx = ConsoleKit.configuration.context_class
35
42
  return unless ctx
36
43
 
37
- reset_tenant(ctx)
38
- Output.print_info('Tenant context has been cleared.')
44
+ perform_clear(ContextWrapper.for_context(ctx))
39
45
  end
40
46
 
41
47
  private
42
48
 
43
- def reset_tenant(ctx)
44
- self.configuration_success = false
45
- reset_context_attributes(ctx)
46
- 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
47
55
  end
48
56
 
49
- def reset_context_attributes(ctx)
50
- available_context_attributes(ctx).each do |attr|
51
- ctx.public_send("#{attr}=", nil)
52
- 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)
53
70
  end
54
71
 
55
72
  def validate_constants!(constants)
@@ -57,67 +74,54 @@ module ConsoleKit
57
74
  raise Error, "Tenant constants missing keys: #{missing.join(', ')}" unless missing.empty?
58
75
  end
59
76
 
60
- def missing_config_error(key)
77
+ def missing_config_error?(key)
61
78
  self.configuration_success = false
62
79
  Output.print_error("No configuration found for tenant: #{key}")
80
+ false
63
81
  end
64
82
 
65
- def perform_configuration(key, constants)
83
+ def execute_configuration(key, constants)
66
84
  validate_constants!(constants)
67
85
  apply_context(constants)
68
- configure_success(key)
69
- end
70
-
71
- def handler_available?(handler_class)
72
- handler_class.new(nil).available?
73
- rescue NotImplementedError, StandardError
74
- false
75
- end
76
-
77
- def available_context_attributes(ctx)
78
- attributes = ctx.respond_to?(:partner_identifier=) ? [:partner_identifier] : []
79
-
80
- HANDLER_ATTRIBUTES.each_with_object(attributes) do |(handler, attr), list|
81
- next unless ctx.respond_to?("#{attr}=")
82
- next unless handler_available?(handler)
83
-
84
- list << attr
85
- end
86
+ mark_success(key)
86
87
  end
87
88
 
88
89
  def apply_context(constant)
89
- ctx = ConsoleKit.configuration.context_class
90
- assign_context_attributes(ctx, constant)
91
- 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)
92
95
  end
93
96
 
94
- def assign_context_attributes(ctx, constant)
95
- attribute_to_constant = {
96
- partner_identifier: :partner_code,
97
- tenant_shard: :shard,
98
- tenant_mongo_db: :mongo_db,
99
- tenant_redis_db: :redis_db,
100
- tenant_elasticsearch_prefix: :elasticsearch_prefix
101
- }
102
-
103
- available_context_attributes(ctx).each do |attr|
104
- ctx.public_send("#{attr}=", constant[attribute_to_constant[attr]])
105
- 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?
106
101
  end
107
102
 
108
103
  def setup_connections(context)
109
- ConsoleKit::Connections::ConnectionManager.available_handlers(context).each(&:connect)
104
+ Connections::ConnectionManager.available_handlers(context).each(&:connect)
110
105
  end
111
106
 
112
- def configure_success(key)
107
+ def mark_success(key)
113
108
  Output.print_success("Tenant set to: #{key}")
114
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
+ )
115
118
  end
116
119
 
117
- def handle_error(error, key)
120
+ def handle_error?(error, key)
118
121
  self.configuration_success = false
119
122
  Output.print_error("Failed to configure tenant '#{key}': #{error.message}")
120
123
  Output.print_backtrace(error)
124
+ false
121
125
  end
122
126
  end
123
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ConsoleKit
4
- VERSION = '1.2.0'
4
+ VERSION = '1.3.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: console_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Soumyadeep Pal
@@ -58,6 +58,8 @@ executables: []
58
58
  extensions: []
59
59
  extra_rdoc_files: []
60
60
  files:
61
+ - ".DS_Store"
62
+ - ".reek.yml.new"
61
63
  - LICENSE.txt
62
64
  - lib/console_kit.rb
63
65
  - lib/console_kit/configuration.rb
@@ -78,6 +80,8 @@ files:
78
80
  - lib/console_kit/setup.rb
79
81
  - lib/console_kit/setup_ui.rb
80
82
  - lib/console_kit/tenant_configurator.rb
83
+ - lib/console_kit/tenant_configurator/context_wrapper.rb
84
+ - lib/console_kit/tenant_orchestrator.rb
81
85
  - lib/console_kit/tenant_selector.rb
82
86
  - lib/console_kit/version.rb
83
87
  - lib/generators/console_kit/install_generator.rb