console_kit 0.1.5 → 1.1.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 +4 -4
- data/CHANGELOG.md +43 -4
- data/README.md +70 -17
- data/SECURITY.md +3 -1
- data/lib/console_kit/configuration.rb +33 -6
- data/lib/console_kit/connections/base_connection_handler.rb +12 -4
- data/lib/console_kit/connections/connection_manager.rb +5 -1
- data/lib/console_kit/connections/elasticsearch_connection_handler.rb +31 -0
- data/lib/console_kit/connections/mongo_connection_handler.rb +10 -10
- data/lib/console_kit/connections/redis_connection_handler.rb +37 -0
- data/lib/console_kit/connections/sql_connection_handler.rb +17 -8
- data/lib/console_kit/console_helpers.rb +38 -0
- data/lib/console_kit/output.rb +41 -10
- data/lib/console_kit/prompt.rb +43 -0
- data/lib/console_kit/railtie.rb +11 -1
- data/lib/console_kit/setup.rb +77 -10
- data/lib/console_kit/tenant_configurator.rb +61 -13
- data/lib/console_kit/tenant_selector.rb +41 -15
- data/lib/console_kit/version.rb +1 -1
- data/lib/console_kit.rb +29 -7
- data/lib/generators/console_kit/templates/console_kit.rb +10 -3
- metadata +5 -19
- data/.reek.yml +0 -4
- data/.rspec +0 -3
- data/.rubocop.yml +0 -11
- data/Rakefile +0 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: acd47ead1008fc3bcdc25b0d995b735f69d1e6f5a8d5f8442c67dc5ee86bb9a3
|
|
4
|
+
data.tar.gz: 55bd88158a92c6741eab85bda17d4b61828dd101e94cd5ab02097688f8994302
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9d1214289e7859342493f2930557554e05ad3ccb3200e041d3ade9b2f95a710b6f111eaea9bfc40f0192e98cabd1010f7be6c71469066d2fd702a74d70a33132
|
|
7
|
+
data.tar.gz: 4564b76383af8ab6d60c6ee79644fc78a1be8633520ffe1c74a26a69936ebbacb7db8b20dd84c28c2433f532198c46c1c140ff7d31cd323d613559624fabb713
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,47 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [1.1.0] - 2026-03-14
|
|
10
|
+
### Added
|
|
11
|
+
- **Redis Connection Handler:** Automatic Redis DB selection per tenant via `Redis.current.select`, with graceful fallback for Redis v5+ where `Redis.current` is deprecated.
|
|
12
|
+
- **Elasticsearch Connection Handler:** Sets a per-tenant Elasticsearch index name prefix via thread-local storage and `Elasticsearch::Model.index_name_prefix=` (when available).
|
|
13
|
+
- **Console Helpers:** New `switch_tenant`, `tenant_info`, and `tenants` methods available in the Rails console for quick tenant management.
|
|
14
|
+
- **Custom Console Prompt:** IRB and Pry prompts now display the active tenant name (e.g., `[acme] main:001>`).
|
|
15
|
+
- **Tenant Banner:** On successful tenant initialization, a banner now shows the tenant name, environment safety warnings (production in red, staging in yellow), and a summary of active connections.
|
|
16
|
+
- **Environment Safety Warnings:** Production and staging environments are flagged with color-coded warnings at tenant setup time.
|
|
17
|
+
- New tenant configuration keys: `redis_db`, `elasticsearch_prefix`, and `environment`.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- `TenantConfigurator` now manages `tenant_redis_db` and `tenant_elasticsearch_prefix` context attributes alongside existing ones.
|
|
21
|
+
- Generator template updated with examples for the new configuration keys.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## [1.0.0] - 2026-03-01
|
|
26
|
+
### Added
|
|
27
|
+
- **Global Configuration Persistence:** ConsoleKit settings now persist across the entire session and across multiple threads.
|
|
28
|
+
- **Isolated Tenant Selection:** Each thread maintains its own tenant selection for safety, while sharing the global configuration.
|
|
29
|
+
- **Seamless Rails Reloading:** Full support for Rails `reload!`; your selected tenant and context are now automatically preserved after code reloads.
|
|
30
|
+
- **Reliable Tenant Switching:** Switching or clearing tenants now correctly resets all database connections (SQL and MongoDB) to their default state.
|
|
31
|
+
- **Flexible Tenant Selection:** Users can now select tenants by typing their names (case-insensitive) in addition to index numbers.
|
|
32
|
+
- **Session Control:** Added support for `exit` or `quit` commands directly at the selection prompt to terminate the console session.
|
|
33
|
+
- **Safe Mode:** Added a "Skip" option (0) to load the console without any tenant configuration.
|
|
34
|
+
- **Improved Configuration Validation:** Enhanced startup checks to provide clearer feedback if the configuration or context class is incorrectly defined.
|
|
35
|
+
- **Custom SQL Base Class:** New configuration option to specify a custom base class for SQL connections.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
- **Modernized CLI Interface:** Redesigned the tenant selection menu and prompts for a cleaner, more intuitive user experience.
|
|
39
|
+
- **Enhanced Error Feedback:** Improved messaging for invalid selections and missing configurations.
|
|
40
|
+
- **Optimized Performance:** Refactored internal discovery and configuration logic for better reliability in large applications.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
- Fixed a bug where tenant context was lost after running `reload!` in the Rails console.
|
|
44
|
+
- Fixed an issue where database connections could remain tied to a previous tenant after the context was cleared.
|
|
45
|
+
- Resolved all stability and code quality warnings.
|
|
46
|
+
- Fixed timestamp formatting in console output.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
9
50
|
## [0.1.5] - 2025-10-12
|
|
10
51
|
### Added
|
|
11
52
|
- Minor Bug Fixes
|
|
@@ -58,10 +99,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
58
99
|
- Tenant-specific database configuration.
|
|
59
100
|
- Colorized console output for improved UX.
|
|
60
101
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
## [Unreleased]
|
|
64
|
-
|
|
102
|
+
[1.1.0]: https://github.com/Soumyadeep-ai/console_kit/releases/tag/v1.1.0
|
|
103
|
+
[1.0.0]: https://github.com/Soumyadeep-ai/console_kit/releases/tag/v1.0.0
|
|
65
104
|
[0.1.5]: https://github.com/Soumyadeep-ai/console_kit/releases/tag/v0.1.5
|
|
66
105
|
[0.1.4]: https://github.com/Soumyadeep-ai/console_kit/releases/tag/v0.1.4
|
|
67
106
|
[0.1.3]: https://github.com/Soumyadeep-ai/console_kit/releases/tag/v0.1.3
|
data/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
A simple and flexible multi-tenant console setup toolkit for Rails applications.
|
|
10
10
|
|
|
11
|
-
ConsoleKit helps you manage tenant-specific database connections and context configuration via an easy CLI interface and Rails integration.
|
|
11
|
+
ConsoleKit helps you manage tenant-specific database connections (SQL, MongoDB, Redis, Elasticsearch) and context configuration via an easy CLI interface and Rails integration.
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
@@ -50,10 +50,24 @@ Then, edit config/initializers/console_kit.rb to define your tenants and context
|
|
|
50
50
|
ConsoleKit.configure do |config|
|
|
51
51
|
config.tenants = {
|
|
52
52
|
tenant_one: {
|
|
53
|
-
constants: {
|
|
53
|
+
constants: {
|
|
54
|
+
shard: :tenant_one_db,
|
|
55
|
+
mongo_db: :tenant_one_mongo,
|
|
56
|
+
partner_code: 'partnerA',
|
|
57
|
+
redis_db: 1,
|
|
58
|
+
elasticsearch_prefix: 'tenant_one',
|
|
59
|
+
environment: 'production'
|
|
60
|
+
}
|
|
54
61
|
},
|
|
55
62
|
tenant_two: {
|
|
56
|
-
constants: {
|
|
63
|
+
constants: {
|
|
64
|
+
shard: :tenant_two_db,
|
|
65
|
+
mongo_db: :tenant_two_mongo,
|
|
66
|
+
partner_code: 'partnerB',
|
|
67
|
+
redis_db: 2,
|
|
68
|
+
elasticsearch_prefix: 'tenant_two',
|
|
69
|
+
environment: 'staging'
|
|
70
|
+
}
|
|
57
71
|
}
|
|
58
72
|
}
|
|
59
73
|
|
|
@@ -64,33 +78,72 @@ ConsoleKit.configure do |config|
|
|
|
64
78
|
end
|
|
65
79
|
```
|
|
66
80
|
|
|
81
|
+
## Supported Connections
|
|
82
|
+
|
|
83
|
+
ConsoleKit automatically detects and manages connections for:
|
|
84
|
+
|
|
85
|
+
| Connection | Gem Required | Config Key | Behavior |
|
|
86
|
+
|-----------------|-----------------|--------------------------|------------------------------------------------|
|
|
87
|
+
| SQL (ActiveRecord) | `activerecord` | `shard` | Calls `establish_connection` on your base class |
|
|
88
|
+
| MongoDB | `mongoid` | `mongo_db` | Calls `Mongoid.override_database` |
|
|
89
|
+
| Redis | `redis` | `redis_db` | Calls `Redis.current.select(db)` |
|
|
90
|
+
| Elasticsearch | `elasticsearch` | `elasticsearch_prefix` | Sets `Elasticsearch::Model.index_name_prefix=` |
|
|
91
|
+
|
|
92
|
+
Handlers are only activated when their corresponding gem is loaded.
|
|
93
|
+
|
|
67
94
|
## Console Usage
|
|
68
95
|
|
|
69
|
-
When launching the Rails console, ConsoleKit will prompt you to select a tenant (if tenants are configured).
|
|
70
|
-
You can also manually interact with it:
|
|
96
|
+
When launching the Rails console, ConsoleKit will prompt you to select a tenant (if multiple tenants are configured). On selection, a tenant banner is displayed showing the tenant name, environment safety warnings, and active connections.
|
|
71
97
|
|
|
72
|
-
###
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
98
|
+
### Selection Options:
|
|
99
|
+
- **Number or Name:** Select a tenant by its index or name (case-insensitive).
|
|
100
|
+
- **0 (Skip):** Load the console without any tenant configuration.
|
|
101
|
+
- **exit / quit:** Immediately terminate the console session.
|
|
102
|
+
|
|
103
|
+
### Console Helpers
|
|
104
|
+
|
|
105
|
+
The following helper methods are available in your Rails console:
|
|
77
106
|
|
|
78
|
-
### Reset Current Tenant
|
|
79
107
|
```ruby
|
|
80
|
-
|
|
81
|
-
|
|
108
|
+
# Switch to a different tenant
|
|
109
|
+
switch_tenant
|
|
110
|
+
|
|
111
|
+
# Print details about the current tenant
|
|
112
|
+
tenant_info
|
|
113
|
+
|
|
114
|
+
# List all available tenants
|
|
115
|
+
tenants
|
|
82
116
|
```
|
|
83
117
|
|
|
84
|
-
###
|
|
85
|
-
|
|
86
|
-
ConsoleKit
|
|
118
|
+
### Custom Prompt
|
|
119
|
+
|
|
120
|
+
ConsoleKit automatically sets your IRB/Pry prompt to show the active tenant:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
[tenant_one] main:001>
|
|
87
124
|
```
|
|
88
125
|
|
|
89
|
-
###
|
|
126
|
+
### Other Methods
|
|
127
|
+
|
|
90
128
|
```ruby
|
|
129
|
+
# Get current tenant
|
|
130
|
+
ConsoleKit.current_tenant
|
|
131
|
+
# => :tenant_one
|
|
132
|
+
|
|
133
|
+
# Reset and re-select tenant
|
|
134
|
+
ConsoleKit.reset_current_tenant
|
|
135
|
+
|
|
136
|
+
# Toggle pretty output
|
|
137
|
+
ConsoleKit.enable_pretty_output
|
|
91
138
|
ConsoleKit.disable_pretty_output
|
|
92
139
|
```
|
|
93
140
|
|
|
141
|
+
### Environment Warnings
|
|
142
|
+
|
|
143
|
+
When a tenant has an `environment` key in its constants:
|
|
144
|
+
- **production**: A red warning is displayed at setup time.
|
|
145
|
+
- **staging**: A yellow warning is displayed at setup time.
|
|
146
|
+
|
|
94
147
|
## Development
|
|
95
148
|
|
|
96
149
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/SECURITY.md
CHANGED
|
@@ -8,7 +8,9 @@ Once a new version is released, the previous version is branched and locked, and
|
|
|
8
8
|
|
|
9
9
|
| Version | Supported |
|
|
10
10
|
| ------- | ------------------ |
|
|
11
|
-
|
|
|
11
|
+
| 1.1.0 | :white_check_mark: |
|
|
12
|
+
| 1.0.0 | :x: |
|
|
13
|
+
| 0.1.5 | :x: |
|
|
12
14
|
| 0.1.4 | :x: |
|
|
13
15
|
| 0.1.3 | :x: |
|
|
14
16
|
| 0.1.2 | :x: |
|
|
@@ -3,16 +3,43 @@
|
|
|
3
3
|
module ConsoleKit
|
|
4
4
|
# Stores ConsoleKit configurations such as tenant map and context behavior
|
|
5
5
|
class Configuration
|
|
6
|
-
|
|
6
|
+
attr_accessor :pretty_output, :tenants, :sql_base_class
|
|
7
|
+
attr_writer :context_class
|
|
7
8
|
|
|
8
|
-
def initialize
|
|
9
|
+
def initialize
|
|
9
10
|
@pretty_output = true
|
|
10
|
-
@tenants =
|
|
11
|
-
@context_class =
|
|
11
|
+
@tenants = nil
|
|
12
|
+
@context_class = nil
|
|
13
|
+
@sql_base_class = 'ApplicationRecord'
|
|
12
14
|
end
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
def context_class
|
|
17
|
+
case @context_class
|
|
18
|
+
when String, Symbol then resolve_context_class
|
|
19
|
+
else @context_class
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def validate
|
|
24
|
+
validate!
|
|
25
|
+
true
|
|
26
|
+
rescue Error
|
|
27
|
+
false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def validate!
|
|
31
|
+
raise Error, 'ConsoleKit: `tenants` is not configured.' if @tenants.blank?
|
|
32
|
+
raise Error, 'ConsoleKit: `tenants` must be a Hash.' unless @tenants.is_a?(Hash)
|
|
33
|
+
raise Error, 'ConsoleKit: `context_class` is not configured.' if @context_class.blank?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def resolve_context_class
|
|
39
|
+
@context_class.to_s.constantize
|
|
40
|
+
rescue NameError
|
|
41
|
+
raise Error, "ConsoleKit: context_class '#{@context_class}' could not be found. " \
|
|
42
|
+
'Ensure the class is defined before configuration is accessed.'
|
|
16
43
|
end
|
|
17
44
|
end
|
|
18
45
|
end
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'active_support/core_ext/class/subclasses'
|
|
4
|
+
|
|
3
5
|
module ConsoleKit
|
|
4
6
|
module Connections
|
|
5
7
|
# Parent class for connection handlers
|
|
6
8
|
class BaseConnectionHandler
|
|
9
|
+
class << self
|
|
10
|
+
def registry = descendants
|
|
11
|
+
end
|
|
12
|
+
|
|
7
13
|
attr_reader :context
|
|
8
14
|
|
|
9
15
|
def initialize(context) = @context = context
|
|
16
|
+
def connect = raise NotImplementedError, "#{self.class} must implement #connect"
|
|
17
|
+
def available? = raise NotImplementedError, "#{self.class} must implement #available?"
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
raise NotImplementedError, "#{self.class} must implement #connect"
|
|
13
|
-
end
|
|
19
|
+
private
|
|
14
20
|
|
|
15
|
-
def
|
|
21
|
+
def context_attribute(name)
|
|
22
|
+
@context.respond_to?(name, true) ? @context.send(name) : nil
|
|
23
|
+
end
|
|
16
24
|
end
|
|
17
25
|
end
|
|
18
26
|
end
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'sql_connection_handler'
|
|
4
4
|
require_relative 'mongo_connection_handler'
|
|
5
|
+
require_relative 'redis_connection_handler'
|
|
6
|
+
require_relative 'elasticsearch_connection_handler'
|
|
5
7
|
|
|
6
8
|
module ConsoleKit
|
|
7
9
|
module Connections
|
|
@@ -12,12 +14,14 @@ module ConsoleKit
|
|
|
12
14
|
handler_classes.filter_map do |klass|
|
|
13
15
|
handler = klass.new(context)
|
|
14
16
|
handler if handler.available?
|
|
17
|
+
rescue NotImplementedError
|
|
18
|
+
nil
|
|
15
19
|
end
|
|
16
20
|
end
|
|
17
21
|
|
|
18
22
|
private
|
|
19
23
|
|
|
20
|
-
def handler_classes = BaseConnectionHandler.
|
|
24
|
+
def handler_classes = BaseConnectionHandler.registry
|
|
21
25
|
end
|
|
22
26
|
end
|
|
23
27
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_connection_handler'
|
|
4
|
+
|
|
5
|
+
module ConsoleKit
|
|
6
|
+
module Connections
|
|
7
|
+
# Handles Elasticsearch connections
|
|
8
|
+
class ElasticsearchConnectionHandler < BaseConnectionHandler
|
|
9
|
+
def connect
|
|
10
|
+
prefix = context_attribute(:tenant_elasticsearch_prefix).presence
|
|
11
|
+
Output.print_info(switch_message(prefix))
|
|
12
|
+
Thread.current[:console_kit_elasticsearch_prefix] = prefix
|
|
13
|
+
apply_model_index_prefix(prefix)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def available? = defined?(Elasticsearch)
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def apply_model_index_prefix(prefix)
|
|
21
|
+
return unless defined?(Elasticsearch::Model) && Elasticsearch::Model.respond_to?(:index_name_prefix=)
|
|
22
|
+
|
|
23
|
+
Elasticsearch::Model.index_name_prefix = prefix
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def switch_message(prefix)
|
|
27
|
+
prefix ? "Setting Elasticsearch index prefix: #{prefix}" : 'Resetting Elasticsearch index prefix to default'
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'forwardable'
|
|
4
3
|
require_relative 'base_connection_handler'
|
|
5
4
|
|
|
6
5
|
module ConsoleKit
|
|
7
6
|
module Connections
|
|
8
7
|
# Handles MongoDB connections
|
|
9
8
|
class MongoConnectionHandler < BaseConnectionHandler
|
|
10
|
-
extend Forwardable
|
|
11
|
-
|
|
12
|
-
def_delegator :@context, :tenant_mongo_db
|
|
13
|
-
|
|
14
9
|
def connect
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Mongoid.override_client(tenant_mongo_db)
|
|
10
|
+
db = context_attribute(:tenant_mongo_db).presence
|
|
11
|
+
Output.print_info(switch_message(db))
|
|
12
|
+
Mongoid.override_database(db)
|
|
19
13
|
rescue NoMethodError
|
|
20
|
-
Output.print_warning('Mongoid.
|
|
14
|
+
Output.print_warning('Mongoid.override_database is not available in this version of Mongoid.')
|
|
21
15
|
end
|
|
22
16
|
|
|
23
17
|
def available? = defined?(Mongoid)
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def switch_message(db)
|
|
22
|
+
db ? "Switching to MongoDB client: #{db}" : 'Resetting MongoDB client to default'
|
|
23
|
+
end
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_connection_handler'
|
|
4
|
+
|
|
5
|
+
module ConsoleKit
|
|
6
|
+
module Connections
|
|
7
|
+
# Handles Redis connections
|
|
8
|
+
class RedisConnectionHandler < BaseConnectionHandler
|
|
9
|
+
DEFAULT_REDIS_DB = 0
|
|
10
|
+
|
|
11
|
+
def connect
|
|
12
|
+
db = context_attribute(:tenant_redis_db)
|
|
13
|
+
Output.print_info(switch_message(db))
|
|
14
|
+
select_redis_db(db.nil? ? DEFAULT_REDIS_DB : db)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def available? = defined?(Redis)
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def select_redis_db(db)
|
|
22
|
+
if Redis.respond_to?(:current) && Redis.current
|
|
23
|
+
Redis.current.select(db)
|
|
24
|
+
elsif defined?(RedisClient) && db != DEFAULT_REDIS_DB
|
|
25
|
+
Output.print_warning("Redis DB #{db} configured but auto-select not supported with RedisClient. " \
|
|
26
|
+
'Ensure your Redis configuration sets the correct DB.')
|
|
27
|
+
end
|
|
28
|
+
rescue NoMethodError
|
|
29
|
+
Output.print_warning('Redis.current is not available (deprecated in Redis v5+).')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def switch_message(db)
|
|
33
|
+
db ? "Switching to Redis DB: #{db}" : 'Resetting Redis connection to default'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -1,24 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'forwardable'
|
|
4
3
|
require_relative 'base_connection_handler'
|
|
5
4
|
|
|
6
5
|
module ConsoleKit
|
|
7
6
|
module Connections
|
|
8
7
|
# Handles SQL connections
|
|
9
8
|
class SqlConnectionHandler < BaseConnectionHandler
|
|
10
|
-
|
|
9
|
+
def connect
|
|
10
|
+
shard = context_attribute(:tenant_shard).presence&.to_sym
|
|
11
|
+
Output.print_info("#{connection_message(shard)} via #{base_class}")
|
|
12
|
+
shard ? base_class.establish_connection(shard) : base_class.establish_connection
|
|
13
|
+
end
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
def available? = sql_base_class_name.to_s.safe_constantize.present?
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def base_class
|
|
20
|
+
klass = sql_base_class_name.to_s.safe_constantize
|
|
21
|
+
return klass if klass
|
|
22
|
+
|
|
23
|
+
raise Error, "ConsoleKit: sql_base_class '#{sql_base_class_name}' could not be found."
|
|
24
|
+
end
|
|
16
25
|
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
def connection_message(shard)
|
|
27
|
+
shard ? "Establishing SQL connection to shard: #{shard}" : 'Resetting SQL connection to default'
|
|
19
28
|
end
|
|
20
29
|
|
|
21
|
-
def
|
|
30
|
+
def sql_base_class_name = ConsoleKit.configuration.sql_base_class
|
|
22
31
|
end
|
|
23
32
|
end
|
|
24
33
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConsoleKit
|
|
4
|
+
# Helper methods available in the Rails console
|
|
5
|
+
module ConsoleHelpers
|
|
6
|
+
def switch_tenant = ConsoleKit.reset_current_tenant
|
|
7
|
+
|
|
8
|
+
def tenant_info
|
|
9
|
+
tenant = ConsoleKit::Setup.current_tenant
|
|
10
|
+
unless tenant
|
|
11
|
+
ConsoleKit::Output.print_warning('No tenant is currently configured.')
|
|
12
|
+
return
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
constants = ConsoleKit.configuration.tenants[tenant]&.[](:constants) || {}
|
|
16
|
+
print_tenant_details(tenant, constants)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def tenants
|
|
20
|
+
names = ConsoleKit.configuration.tenants&.keys || []
|
|
21
|
+
ConsoleKit::Output.print_list(names, header: 'Available Tenants')
|
|
22
|
+
names
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def print_tenant_details(tenant, constants)
|
|
28
|
+
ConsoleKit::Output.print_header("Tenant: #{tenant}")
|
|
29
|
+
{
|
|
30
|
+
'Partner' => :partner_code, 'Shard' => :shard, 'Mongo DB' => :mongo_db,
|
|
31
|
+
'Redis DB' => :redis_db, 'ES Prefix' => :elasticsearch_prefix, 'Environment' => :environment
|
|
32
|
+
}.each do |label, key|
|
|
33
|
+
ConsoleKit::Output.print_info(" #{label.ljust(13)}#{constants[key]}") unless constants[key].nil?
|
|
34
|
+
end
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/console_kit/output.rb
CHANGED
|
@@ -15,24 +15,55 @@ module ConsoleKit
|
|
|
15
15
|
}.freeze
|
|
16
16
|
|
|
17
17
|
class << self
|
|
18
|
+
def silent = Thread.current[:console_kit_silent]
|
|
19
|
+
|
|
20
|
+
def silent=(val)
|
|
21
|
+
Thread.current[:console_kit_silent] = val
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def silence
|
|
25
|
+
old_silent = silent
|
|
26
|
+
self.silent = true
|
|
27
|
+
yield
|
|
28
|
+
ensure
|
|
29
|
+
self.silent = old_silent
|
|
30
|
+
end
|
|
31
|
+
|
|
18
32
|
TYPES.each_key do |type|
|
|
19
|
-
define_method("print_#{type}") do |text, timestamp: false|
|
|
20
|
-
|
|
21
|
-
|
|
33
|
+
define_method("print_#{type}") do |text, timestamp: false, newline: (type != :prompt)|
|
|
34
|
+
return if silent
|
|
35
|
+
|
|
36
|
+
formatted = (type == :header ? "\n--- #{text} ---" : text)
|
|
37
|
+
print_with(type, formatted, timestamp, { newline: newline })
|
|
22
38
|
end
|
|
23
39
|
end
|
|
24
40
|
|
|
41
|
+
def print_list(items, header: nil)
|
|
42
|
+
return if silent
|
|
43
|
+
|
|
44
|
+
print_header(header) if header
|
|
45
|
+
items.each { |item| puts " #{item}" }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def print_raw(text)
|
|
49
|
+
return if silent
|
|
50
|
+
|
|
51
|
+
puts text
|
|
52
|
+
end
|
|
53
|
+
|
|
25
54
|
# Backtrace prints always with timestamp, no param
|
|
26
55
|
def print_backtrace(exception)
|
|
27
|
-
|
|
56
|
+
return if silent
|
|
57
|
+
|
|
58
|
+
exception&.backtrace&.each { |line| print_with(:trace, " #{line}", true, { newline: true }) }
|
|
28
59
|
end
|
|
29
60
|
|
|
30
61
|
private
|
|
31
62
|
|
|
32
|
-
def print_with(type, text, timestamp)
|
|
63
|
+
def print_with(type, text, timestamp, opts = {})
|
|
33
64
|
meta = TYPES.fetch(type)
|
|
34
65
|
message = build_message(text, meta[:symbol], timestamp)
|
|
35
|
-
|
|
66
|
+
emit(message, meta[:color], opts.fetch(:newline, true))
|
|
36
67
|
end
|
|
37
68
|
|
|
38
69
|
def build_message(text, symbol, timestamp)
|
|
@@ -43,10 +74,10 @@ module ConsoleKit
|
|
|
43
74
|
def timestamp_prefix(timestamp) = prefix_for(timestamp) { Time.current.strftime('[%Y-%m-%d %H:%M:%S] ') }
|
|
44
75
|
def symbol_prefix(symbol) = prefix_for(symbol) { |sym| "#{sym} " }
|
|
45
76
|
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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)
|
|
50
81
|
end
|
|
51
82
|
end
|
|
52
83
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConsoleKit
|
|
4
|
+
# Sets the console prompt to show the current tenant
|
|
5
|
+
module Prompt
|
|
6
|
+
class << self
|
|
7
|
+
def apply
|
|
8
|
+
apply_irb_prompt if defined?(IRB)
|
|
9
|
+
apply_pry_prompt if defined?(Pry)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def tenant_label
|
|
15
|
+
tenant = ConsoleKit::Setup.current_tenant
|
|
16
|
+
tenant ? "[#{tenant}]" : '[no-tenant]'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def apply_irb_prompt
|
|
20
|
+
conf = IRB.conf
|
|
21
|
+
conf[:PROMPT] ||= {}
|
|
22
|
+
conf[: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
|
+
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
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/console_kit/railtie.rb
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
module ConsoleKit
|
|
4
4
|
# Railtie for integrating ConsoleKit with Rails console.
|
|
5
5
|
class Railtie < Rails::Railtie
|
|
6
|
-
console
|
|
6
|
+
console do
|
|
7
|
+
ConsoleKit::Setup.setup
|
|
8
|
+
ConsoleKit::Prompt.apply
|
|
9
|
+
if defined?(Pry)
|
|
10
|
+
TOPLEVEL_BINDING.receiver.extend(ConsoleKit::ConsoleHelpers)
|
|
11
|
+
elsif defined?(IRB::ExtendCommandBundle)
|
|
12
|
+
IRB::ExtendCommandBundle.include(ConsoleKit::ConsoleHelpers)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
config.to_prepare { ConsoleKit::Setup.reapply if defined?(Rails::Console) }
|
|
7
17
|
end
|
|
8
18
|
end
|
data/lib/console_kit/setup.rb
CHANGED
|
@@ -9,27 +9,44 @@ module ConsoleKit
|
|
|
9
9
|
# Does the initial setup
|
|
10
10
|
module Setup
|
|
11
11
|
class << self
|
|
12
|
-
|
|
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
|
+
def current_tenant = Thread.current[:console_kit_current_tenant]
|
|
18
|
+
|
|
19
|
+
def current_tenant=(val)
|
|
20
|
+
Thread.current[:console_kit_current_tenant] = val
|
|
21
|
+
end
|
|
13
22
|
|
|
14
23
|
def setup = run_setup
|
|
15
|
-
def tenant_setup_successful? =
|
|
24
|
+
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
|
|
16
31
|
|
|
17
32
|
def reset_current_tenant
|
|
18
33
|
return warn_no_tenants unless tenants?
|
|
19
34
|
|
|
20
|
-
|
|
21
|
-
|
|
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)
|
|
22
40
|
|
|
23
|
-
|
|
24
|
-
setup
|
|
41
|
+
configure(key)
|
|
25
42
|
end
|
|
26
43
|
|
|
27
44
|
private
|
|
28
45
|
|
|
29
46
|
def run_setup
|
|
30
47
|
return if tenant_setup_successful?
|
|
31
|
-
return Output.print_error('No tenants configured.') if no_tenants?
|
|
32
48
|
|
|
49
|
+
ConsoleKit.configuration.validate!
|
|
33
50
|
select_and_configure
|
|
34
51
|
rescue StandardError => e
|
|
35
52
|
handle_error(e)
|
|
@@ -37,17 +54,57 @@ module ConsoleKit
|
|
|
37
54
|
|
|
38
55
|
def select_and_configure
|
|
39
56
|
key = select_tenant_key
|
|
40
|
-
return
|
|
57
|
+
return handle_selection_result(key) if %i[exit abort none].include?(key) || key.blank?
|
|
41
58
|
|
|
42
59
|
configure(key)
|
|
43
60
|
end
|
|
44
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
|
+
|
|
45
78
|
def configure(key)
|
|
46
79
|
TenantConfigurator.configure_tenant(key)
|
|
47
80
|
return unless TenantConfigurator.configuration_success
|
|
48
81
|
|
|
49
|
-
|
|
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
|
|
50
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') }
|
|
51
108
|
end
|
|
52
109
|
|
|
53
110
|
def tenants = ConsoleKit.configuration.tenants
|
|
@@ -59,7 +116,17 @@ module ConsoleKit
|
|
|
59
116
|
def single_tenant? = tenants.size == 1
|
|
60
117
|
def non_interactive? = !$stdin.tty?
|
|
61
118
|
def warn_no_tenants = Output.print_warning('Cannot reset tenant: No tenants configured.')
|
|
62
|
-
def warn_reset = Output.print_warning("Resetting tenant: #{
|
|
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
|
|
63
130
|
|
|
64
131
|
def handle_error(error)
|
|
65
132
|
Output.print_error("Error setting up tenant: #{error.message}")
|
|
@@ -7,7 +7,18 @@ module ConsoleKit
|
|
|
7
7
|
# For tenant configuration
|
|
8
8
|
module TenantConfigurator
|
|
9
9
|
class << self
|
|
10
|
-
|
|
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
|
|
16
|
+
|
|
17
|
+
def configuration_success = Thread.current[:console_kit_configuration_success]
|
|
18
|
+
|
|
19
|
+
def configuration_success=(val)
|
|
20
|
+
Thread.current[:console_kit_configuration_success] = val
|
|
21
|
+
end
|
|
11
22
|
|
|
12
23
|
def configure_tenant(key)
|
|
13
24
|
constants = ConsoleKit.configuration.tenants[key]&.[](:constants)
|
|
@@ -19,22 +30,34 @@ module ConsoleKit
|
|
|
19
30
|
end
|
|
20
31
|
|
|
21
32
|
def clear
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
ctx = ConsoleKit.configuration.context_class
|
|
34
|
+
return unless ctx
|
|
35
|
+
|
|
36
|
+
reset_tenant(ctx)
|
|
26
37
|
Output.print_info('Tenant context has been cleared.')
|
|
27
38
|
end
|
|
28
39
|
|
|
29
40
|
private
|
|
30
41
|
|
|
42
|
+
def reset_tenant(ctx)
|
|
43
|
+
self.configuration_success = false
|
|
44
|
+
reset_context_attributes(ctx)
|
|
45
|
+
setup_connections(ctx)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def reset_context_attributes(ctx)
|
|
49
|
+
available_context_attributes(ctx).each do |attr|
|
|
50
|
+
ctx.public_send("#{attr}=", nil)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
31
54
|
def validate_constants!(constants)
|
|
32
55
|
missing = %i[shard partner_code] - constants.keys
|
|
33
|
-
raise "Tenant constants missing keys: #{missing.join(', ')}" unless missing.empty?
|
|
56
|
+
raise Error, "Tenant constants missing keys: #{missing.join(', ')}" unless missing.empty?
|
|
34
57
|
end
|
|
35
58
|
|
|
36
59
|
def missing_config_error(key)
|
|
37
|
-
|
|
60
|
+
self.configuration_success = false
|
|
38
61
|
Output.print_error("No configuration found for tenant: #{key}")
|
|
39
62
|
end
|
|
40
63
|
|
|
@@ -44,26 +67,51 @@ module ConsoleKit
|
|
|
44
67
|
configure_success(key)
|
|
45
68
|
end
|
|
46
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}=") }
|
|
82
|
+
end
|
|
83
|
+
|
|
47
84
|
def apply_context(constant)
|
|
48
85
|
ctx = ConsoleKit.configuration.context_class
|
|
49
|
-
ctx
|
|
50
|
-
ctx.tenant_mongo_db = constant[:mongo_db]
|
|
51
|
-
ctx.partner_identifier = constant[:partner_code]
|
|
52
|
-
|
|
86
|
+
assign_context_attributes(ctx, constant)
|
|
53
87
|
setup_connections(ctx)
|
|
54
88
|
end
|
|
55
89
|
|
|
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
|
|
102
|
+
end
|
|
103
|
+
|
|
56
104
|
def setup_connections(context)
|
|
57
105
|
ConsoleKit::Connections::ConnectionManager.available_handlers(context).each(&:connect)
|
|
58
106
|
end
|
|
59
107
|
|
|
60
108
|
def configure_success(key)
|
|
61
109
|
Output.print_success("Tenant set to: #{key}")
|
|
62
|
-
|
|
110
|
+
self.configuration_success = true
|
|
63
111
|
end
|
|
64
112
|
|
|
65
113
|
def handle_error(error, key)
|
|
66
|
-
|
|
114
|
+
self.configuration_success = false
|
|
67
115
|
Output.print_error("Failed to configure tenant '#{key}': #{error.message}")
|
|
68
116
|
Output.print_backtrace(error)
|
|
69
117
|
end
|
|
@@ -10,35 +10,56 @@ module ConsoleKit
|
|
|
10
10
|
|
|
11
11
|
class << self
|
|
12
12
|
def select
|
|
13
|
-
|
|
13
|
+
RETRY_LIMIT.times do
|
|
14
|
+
result = attempt_selection
|
|
15
|
+
return result unless result == :retry
|
|
16
|
+
end
|
|
17
|
+
nil
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
private
|
|
17
21
|
|
|
18
|
-
def attempt_selection
|
|
19
|
-
return nil if retries_left.zero?
|
|
20
|
-
|
|
22
|
+
def attempt_selection
|
|
21
23
|
print_tenant_selection_menu
|
|
22
24
|
selection = parse_user_selection
|
|
23
|
-
|
|
25
|
+
return :abort if selection == :abort
|
|
26
|
+
return :retry unless selection
|
|
27
|
+
|
|
28
|
+
selection.is_a?(Integer) ? resolve_selection(selection) : selection
|
|
24
29
|
end
|
|
25
30
|
|
|
26
31
|
def print_tenant_selection_menu
|
|
27
32
|
Output.print_header('Multiple tenants detected. Please choose one:')
|
|
28
|
-
Output.
|
|
33
|
+
Output.print_list(menu_items)
|
|
34
|
+
end
|
|
29
35
|
|
|
36
|
+
def menu_items
|
|
37
|
+
items = ['0. Skip (load without tenant configuration)']
|
|
30
38
|
ConsoleKit.tenants.keys.each_with_index do |key, index|
|
|
31
|
-
|
|
39
|
+
items << "#{index + 1}. #{key} (partner: #{tenant_partner(key)})"
|
|
32
40
|
end
|
|
41
|
+
items
|
|
33
42
|
end
|
|
34
43
|
|
|
35
44
|
def tenant_partner(key) = ConsoleKit.tenants.dig(key, :constants, :partner_code) || 'N/A'
|
|
36
45
|
|
|
37
46
|
def parse_user_selection
|
|
38
47
|
input = read_input_with_default
|
|
39
|
-
return
|
|
48
|
+
return :abort if input == :abort
|
|
49
|
+
return :exit if %w[exit quit].include?(input.downcase)
|
|
50
|
+
return find_tenant_by_name(input) unless valid_integer?(input)
|
|
51
|
+
|
|
52
|
+
validate_index_range(input.to_i)
|
|
53
|
+
end
|
|
40
54
|
|
|
41
|
-
|
|
55
|
+
def find_tenant_by_name(input)
|
|
56
|
+
match = ConsoleKit.tenants.keys.find { |k| k.to_s.casecmp(input).zero? }
|
|
57
|
+
return match if match
|
|
58
|
+
|
|
59
|
+
handle_invalid_input("Invalid selection: '#{input}'. Please enter a number or tenant name.")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def validate_index_range(index)
|
|
42
63
|
unless valid_selection_index?(index)
|
|
43
64
|
return handle_invalid_input("Selection must be between 0 and #{max_index}.")
|
|
44
65
|
end
|
|
@@ -47,11 +68,16 @@ module ConsoleKit
|
|
|
47
68
|
end
|
|
48
69
|
|
|
49
70
|
def read_input_with_default
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
71
|
+
Output.print_prompt("Selection (number or name) [#{DEFAULT_SELECTION}]: ")
|
|
72
|
+
raw_input = $stdin.gets
|
|
73
|
+
raw_input ? normalize_input(raw_input) : :abort
|
|
74
|
+
rescue Interrupt
|
|
75
|
+
:abort
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def normalize_input(raw_input)
|
|
79
|
+
input = raw_input.chomp.strip
|
|
80
|
+
input.empty? ? DEFAULT_SELECTION : input
|
|
55
81
|
end
|
|
56
82
|
|
|
57
83
|
def handle_invalid_input(message) = Output.print_warning(message).then { nil }
|
|
@@ -60,7 +86,7 @@ module ConsoleKit
|
|
|
60
86
|
def valid_selection_index?(index) = index.between?(0, max_index)
|
|
61
87
|
|
|
62
88
|
def resolve_selection(index)
|
|
63
|
-
return
|
|
89
|
+
return :none if index.zero?
|
|
64
90
|
|
|
65
91
|
ConsoleKit.tenants.keys[index - 1]
|
|
66
92
|
end
|
data/lib/console_kit/version.rb
CHANGED
data/lib/console_kit.rb
CHANGED
|
@@ -1,29 +1,51 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'active_support/core_ext/object/blank'
|
|
4
|
+
require 'active_support/core_ext/object/inclusion'
|
|
5
|
+
require 'active_support/core_ext/string/inflections'
|
|
6
|
+
|
|
3
7
|
require_relative 'console_kit/version'
|
|
4
8
|
require_relative 'console_kit/configuration'
|
|
5
9
|
require_relative 'console_kit/setup'
|
|
10
|
+
require_relative 'console_kit/console_helpers'
|
|
11
|
+
require_relative 'console_kit/prompt'
|
|
6
12
|
require_relative 'console_kit/railtie' if defined?(Rails::Railtie)
|
|
7
13
|
|
|
8
|
-
# Main module for
|
|
14
|
+
# Main module for ConsoleKit
|
|
9
15
|
module ConsoleKit
|
|
10
16
|
# Base error class for ConsoleKit-related exceptions.
|
|
11
17
|
class Error < StandardError; end
|
|
12
18
|
|
|
13
19
|
class << self
|
|
14
20
|
def configure = yield(configuration)
|
|
21
|
+
def configuration = @configuration ||= Configuration.new
|
|
22
|
+
|
|
23
|
+
def reset_configuration!
|
|
24
|
+
@configuration = nil
|
|
25
|
+
Setup.current_tenant = nil
|
|
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
|
+
end
|
|
15
34
|
|
|
16
|
-
def
|
|
17
|
-
def reset_configuration! = Thread.current[:console_kit_configuration] = nil
|
|
35
|
+
def tenants = configuration.tenants
|
|
18
36
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
37
|
+
def tenants=(val)
|
|
38
|
+
configuration.tenants = val
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def context_class = configuration.context_class
|
|
42
|
+
|
|
43
|
+
def context_class=(val)
|
|
44
|
+
configuration.context_class = val
|
|
22
45
|
end
|
|
23
46
|
|
|
24
47
|
def current_tenant = Setup.current_tenant
|
|
25
48
|
def reset_current_tenant = Setup.reset_current_tenant
|
|
26
|
-
|
|
27
49
|
def enable_pretty_output = configuration.pretty_output = true
|
|
28
50
|
def disable_pretty_output = configuration.pretty_output = false
|
|
29
51
|
end
|
|
@@ -11,20 +11,27 @@ Rails.application.config.after_initialize do
|
|
|
11
11
|
# constants: {
|
|
12
12
|
# shard: :shard_1,
|
|
13
13
|
# mongo_db: 'mongo_db_1',
|
|
14
|
-
# partner_code: 'partner_a'
|
|
14
|
+
# partner_code: 'partner_a',
|
|
15
|
+
# redis_db: 1,
|
|
16
|
+
# elasticsearch_prefix: 'tenant_a',
|
|
17
|
+
# environment: 'production'
|
|
15
18
|
# }
|
|
16
19
|
# },
|
|
17
20
|
# tenant_b: {
|
|
18
21
|
# constants: {
|
|
19
22
|
# shard: :shard_2,
|
|
20
23
|
# mongo_db: 'mongo_db_2',
|
|
21
|
-
# partner_code: 'partner_b'
|
|
24
|
+
# partner_code: 'partner_b',
|
|
25
|
+
# redis_db: 2,
|
|
26
|
+
# elasticsearch_prefix: 'tenant_b',
|
|
27
|
+
# environment: 'staging'
|
|
22
28
|
# }
|
|
23
29
|
# }
|
|
24
30
|
# }
|
|
25
31
|
config.tenants = nil
|
|
26
32
|
|
|
27
|
-
# TODO: Set your context class (e.g., CurrentContext)
|
|
33
|
+
# TODO: Set your context class (e.g., 'CurrentContext')
|
|
34
|
+
# Recommendation: Use a String to ensure the class is correctly re-resolved after `reload!`
|
|
28
35
|
config.context_class = nil
|
|
29
36
|
|
|
30
37
|
# Toggle pretty output on/off (default: true)
|
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:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Soumyadeep Pal
|
|
@@ -9,20 +9,6 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
-
- !ruby/object:Gem::Dependency
|
|
13
|
-
name: mongoid
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - ">="
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0'
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - ">="
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0'
|
|
26
12
|
- !ruby/object:Gem::Dependency
|
|
27
13
|
name: rails
|
|
28
14
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -44,22 +30,22 @@ executables: []
|
|
|
44
30
|
extensions: []
|
|
45
31
|
extra_rdoc_files: []
|
|
46
32
|
files:
|
|
47
|
-
- ".reek.yml"
|
|
48
|
-
- ".rspec"
|
|
49
|
-
- ".rubocop.yml"
|
|
50
33
|
- CHANGELOG.md
|
|
51
34
|
- CODE_OF_CONDUCT.md
|
|
52
35
|
- LICENSE.txt
|
|
53
36
|
- README.md
|
|
54
|
-
- Rakefile
|
|
55
37
|
- SECURITY.md
|
|
56
38
|
- lib/console_kit.rb
|
|
57
39
|
- lib/console_kit/configuration.rb
|
|
58
40
|
- lib/console_kit/connections/base_connection_handler.rb
|
|
59
41
|
- lib/console_kit/connections/connection_manager.rb
|
|
42
|
+
- lib/console_kit/connections/elasticsearch_connection_handler.rb
|
|
60
43
|
- lib/console_kit/connections/mongo_connection_handler.rb
|
|
44
|
+
- lib/console_kit/connections/redis_connection_handler.rb
|
|
61
45
|
- lib/console_kit/connections/sql_connection_handler.rb
|
|
46
|
+
- lib/console_kit/console_helpers.rb
|
|
62
47
|
- lib/console_kit/output.rb
|
|
48
|
+
- lib/console_kit/prompt.rb
|
|
63
49
|
- lib/console_kit/railtie.rb
|
|
64
50
|
- lib/console_kit/setup.rb
|
|
65
51
|
- lib/console_kit/tenant_configurator.rb
|
data/.reek.yml
DELETED
data/.rspec
DELETED
data/.rubocop.yml
DELETED