cloner 0.13.0 → 0.14.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/.env +5 -0
- data/.ruby-version +1 -1
- data/CLAUDE.md +58 -0
- data/README.md +166 -0
- data/compose.yml +15 -0
- data/lib/cloner/ar.rb +7 -7
- data/lib/cloner/docker_compose.rb +283 -0
- data/lib/cloner/internal.rb +1 -0
- data/lib/cloner/mongodb.rb +32 -2
- data/lib/cloner/mysql.rb +65 -15
- data/lib/cloner/postgres.rb +63 -15
- data/lib/cloner/version.rb +1 -1
- data/lib/cloner.rb +1 -0
- data/lib/generators/cloner_generator.rb +18 -4
- data/lib/generators/templates/cloner_docker_compose.thor.erb +154 -0
- data/test/dl_compose_test.thor +158 -0
- data/test/test_docker_compose.rb +213 -0
- data/test/tmp/dump/cloner.bak +1 -0
- metadata +15 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e2bcb2230fc707f0e164ad4a7e6462d35cc96889905940ebe882eba396530516
|
4
|
+
data.tar.gz: 8824290ff01994056936a731d1a5e35fa188abc20188b47f9b7dee23b6173b63
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a42dd7db0a91b1fa6bf2d2e6421914b823f982ddd89cdd36c76130bbebbb4f555afc667abc345fead697e26465599bf6ba5ab7dd104ffc4121f3911c4c9a6b1
|
7
|
+
data.tar.gz: eb889b20bd8a91b6cab12d367c17615ba4123b429cdf9703a7315ca7b7a8ec195e7fa69fcddb024b4a552bda9e9c58ff5e98ec0a9f5c8a798e4a6d8e09f8928a
|
data/.env
ADDED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.4.5
|
data/CLAUDE.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# CLAUDE.md
|
2
|
+
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
4
|
+
|
5
|
+
## About Cloner
|
6
|
+
|
7
|
+
Cloner is a Ruby gem that simplifies cloning production databases and files to local development or staging environments. It supports MongoDB (via Mongoid), PostgreSQL, and MySQL databases, and uses rsync for file synchronization.
|
8
|
+
|
9
|
+
## Key Commands
|
10
|
+
|
11
|
+
```bash
|
12
|
+
# Install dependencies
|
13
|
+
bundle install
|
14
|
+
|
15
|
+
# Generate cloner template (basic)
|
16
|
+
bundle exec rails generate cloner
|
17
|
+
|
18
|
+
# Generate extended template with more options
|
19
|
+
bundle exec rails generate cloner -e
|
20
|
+
|
21
|
+
# Run cloning (after generating template)
|
22
|
+
thor dl
|
23
|
+
|
24
|
+
# Clone only database (skip files)
|
25
|
+
thor dl -F
|
26
|
+
|
27
|
+
# Clone only files (skip database)
|
28
|
+
thor dl -D
|
29
|
+
```
|
30
|
+
|
31
|
+
## Architecture Overview
|
32
|
+
|
33
|
+
The gem is organized as a modular Thor-based CLI tool:
|
34
|
+
|
35
|
+
1. **Core Module Structure** (`lib/cloner/`):
|
36
|
+
- `base.rb`: Thor command base class that users extend
|
37
|
+
- `internal.rb`: Core functionality shared across all adapters
|
38
|
+
- Database adapters: `mongodb.rb`, `mysql.rb`, `postgres.rb`, `ar.rb`
|
39
|
+
- `rsync.rb`: File synchronization logic
|
40
|
+
- `ssh.rb`: SSH connection handling
|
41
|
+
|
42
|
+
2. **Rails Integration**:
|
43
|
+
- Generator in `lib/generators/cloner_generator.rb` creates `lib/tasks/dl.thor`
|
44
|
+
- Templates in `lib/generators/templates/` provide base and extended configurations
|
45
|
+
|
46
|
+
3. **Key Design Patterns**:
|
47
|
+
- Uses Thor for CLI interface
|
48
|
+
- Modular adapters for different database types
|
49
|
+
- Override methods for customization (e.g., `ssh_host`, `ssh_user`, `remote_dump_path`)
|
50
|
+
- Leverages native database tools (pg_dump, mysqldump, mongodump)
|
51
|
+
|
52
|
+
## Development Notes
|
53
|
+
|
54
|
+
- The gem version is defined in `lib/cloner/version.rb`
|
55
|
+
- Uses `fileutils` for file operations (ensure compatibility)
|
56
|
+
- Supports Rails 6+ multi-database configurations
|
57
|
+
- SSH connections use net-ssh gem
|
58
|
+
- Database detection automatically chooses appropriate adapter based on Rails configuration
|
data/README.md
CHANGED
@@ -107,8 +107,174 @@ All functions from cloner/internal.rb can be overriden, for example:
|
|
107
107
|
{}
|
108
108
|
end
|
109
109
|
|
110
|
+
## Docker Compose Support
|
111
|
+
|
112
|
+
Cloner now supports Docker Compose for both local and remote database operations. This is useful when your databases run inside Docker containers.
|
113
|
+
|
114
|
+
### Generating Docker Compose Template
|
115
|
+
|
116
|
+
To generate a template with Docker Compose examples:
|
117
|
+
|
118
|
+
```
|
119
|
+
bundle exec rails generate cloner -d
|
120
|
+
```
|
121
|
+
|
122
|
+
### Configuration
|
123
|
+
|
124
|
+
Add these methods to your `dl.thor` file to enable Docker Compose:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
# For remote Docker Compose
|
128
|
+
def remote_docker_compose?
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
def remote_docker_compose_service
|
133
|
+
'db' # Your database service name in compose.yml
|
134
|
+
end
|
135
|
+
|
136
|
+
def remote_docker_compose_path
|
137
|
+
remote_app_path # Path where compose.yml is located
|
138
|
+
end
|
139
|
+
|
140
|
+
# Optional: Override compose file name (default is 'compose.yml')
|
141
|
+
def remote_docker_compose_file
|
142
|
+
'docker-compose.yml' # Use if your file is named differently
|
143
|
+
end
|
144
|
+
|
145
|
+
# For local Docker Compose
|
146
|
+
def local_docker_compose?
|
147
|
+
true
|
148
|
+
end
|
149
|
+
|
150
|
+
def local_docker_compose_service
|
151
|
+
'db' # Your local database service name
|
152
|
+
end
|
153
|
+
|
154
|
+
def local_docker_compose_path
|
155
|
+
Rails.root.to_s # Path where your local compose.yml is located
|
156
|
+
end
|
157
|
+
|
158
|
+
# Optional: Override compose file name for local (default is 'compose.yml')
|
159
|
+
def local_docker_compose_file
|
160
|
+
'docker-compose.yml' # Use if your file is named differently
|
161
|
+
end
|
162
|
+
|
163
|
+
# Optional: Override local database config (reads from local .env by default)
|
164
|
+
def local_db_config
|
165
|
+
{
|
166
|
+
adapter: 'postgresql',
|
167
|
+
host: read_local_env('DB_HOST') || 'localhost',
|
168
|
+
port: read_local_env('DB_PORT') || '5432',
|
169
|
+
database: read_local_env('DB_NAME'),
|
170
|
+
username: read_local_env('DB_USER'),
|
171
|
+
password: read_local_env('DB_PASSWORD')
|
172
|
+
}.stringify_keys
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
### PostgreSQL with Docker Compose Example
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
class Dl < Cloner::Base
|
180
|
+
no_commands do
|
181
|
+
def ssh_host
|
182
|
+
'production.example.com'
|
183
|
+
end
|
184
|
+
|
185
|
+
def ssh_user
|
186
|
+
'deploy'
|
187
|
+
end
|
188
|
+
|
189
|
+
def remote_app_path
|
190
|
+
'/home/deploy/myapp'
|
191
|
+
end
|
192
|
+
|
193
|
+
def remote_dump_path
|
194
|
+
"#{remote_app_path}/tmp_dump"
|
195
|
+
end
|
196
|
+
|
197
|
+
# Enable Docker Compose for remote
|
198
|
+
def remote_docker_compose?
|
199
|
+
true
|
200
|
+
end
|
201
|
+
|
202
|
+
def remote_docker_compose_service
|
203
|
+
'postgres'
|
204
|
+
end
|
205
|
+
|
206
|
+
# Override to read credentials from .env file
|
207
|
+
def read_ar_r_conf
|
208
|
+
# Read from remote .env file
|
209
|
+
env_content = ""
|
210
|
+
do_ssh do |ssh|
|
211
|
+
env_content = ssh.exec!("cat #{e remote_app_path}/.env")
|
212
|
+
end
|
213
|
+
|
214
|
+
# Parse .env content
|
215
|
+
env_vars = {}
|
216
|
+
env_content.each_line do |line|
|
217
|
+
next if line.strip.empty? || line.strip.start_with?('#')
|
218
|
+
key, value = line.strip.split('=', 2)
|
219
|
+
next unless key && value
|
220
|
+
value = value.gsub(/^["']|["']$/, '')
|
221
|
+
env_vars[key] = value
|
222
|
+
end
|
223
|
+
|
224
|
+
{
|
225
|
+
adapter: "postgresql",
|
226
|
+
host: env_vars['DB_HOST'] || 'postgres',
|
227
|
+
database: env_vars['DB_NAME'],
|
228
|
+
username: env_vars['DB_USER'],
|
229
|
+
password: env_vars['DB_PASSWORD']
|
230
|
+
}.stringify_keys
|
231
|
+
end
|
232
|
+
|
233
|
+
# Enable Docker Compose for local
|
234
|
+
def local_docker_compose?
|
235
|
+
true
|
236
|
+
end
|
237
|
+
|
238
|
+
def local_docker_compose_service
|
239
|
+
'db'
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
desc "download", "clone DB from production"
|
244
|
+
def download
|
245
|
+
load_env
|
246
|
+
clone_db
|
247
|
+
end
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
251
|
+
### How It Works
|
252
|
+
|
253
|
+
When Docker Compose is enabled:
|
254
|
+
|
255
|
+
1. **Remote operations**: Database dump commands are wrapped with `docker compose exec` on the remote server
|
256
|
+
2. **Local operations**: Database restore commands pipe data into `docker compose exec -T` locally
|
257
|
+
3. **Automatic command wrapping**: The gem automatically detects and wraps database commands appropriately
|
258
|
+
|
259
|
+
### Supported Databases
|
260
|
+
|
261
|
+
Docker Compose support is available for:
|
262
|
+
- PostgreSQL
|
263
|
+
- MySQL
|
264
|
+
|
110
265
|
## Changelog
|
111
266
|
|
267
|
+
### 0.14.0
|
268
|
+
|
269
|
+
- Add Docker Compose support for local and remote database operations
|
270
|
+
- Add Docker Compose generator template with `-d` option
|
271
|
+
- Support automatic command wrapping for PostgreSQL and MySQL when using Docker Compose
|
272
|
+
- Add helper methods for Docker Compose configuration
|
273
|
+
- Default compose file name is now 'compose.yml' (configurable via docker_compose_file methods)
|
274
|
+
- Add `local_db_config` and `remote_db_config` methods for customizing database configurations
|
275
|
+
- Support reading from .env files for both local and remote environments
|
276
|
+
- Replace direct usage of `ar_conf` and `ar_r_conf` with configurable `local_db_config` and `remote_db_config`
|
277
|
+
|
112
278
|
### 0.10.0
|
113
279
|
|
114
280
|
- Support rails 6 multi database activerecord apps via option
|
data/compose.yml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
version: '3.8'
|
2
|
+
|
3
|
+
services:
|
4
|
+
testdb:
|
5
|
+
restart: unless-stopped
|
6
|
+
image: postgres:17.4-alpine
|
7
|
+
container_name: testdb
|
8
|
+
environment:
|
9
|
+
- POSTGRES_USER=testuser
|
10
|
+
- POSTGRES_PASSWORD=testpass
|
11
|
+
- POSTGRES_DB=test_development
|
12
|
+
ports:
|
13
|
+
- "5432:5432"
|
14
|
+
volumes:
|
15
|
+
- ./db:/var/lib/postgresql/data
|
data/lib/cloner/ar.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Cloner::Ar
|
2
2
|
def read_ar_conf
|
3
3
|
@conf ||= begin
|
4
|
-
YAML.load(ERB.new(File.read(Rails.root.join('config', 'database.yml'))).result)[env_database]
|
4
|
+
YAML.load(ERB.new(File.read(Rails.root.join('config', 'database.yml'))).result, aliases: true)[env_database]
|
5
5
|
end
|
6
6
|
end
|
7
7
|
def ar_conf
|
@@ -26,7 +26,7 @@ module Cloner::Ar
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def ar_to
|
29
|
-
|
29
|
+
local_db_config['database']
|
30
30
|
end
|
31
31
|
|
32
32
|
def read_ar_r_conf
|
@@ -35,7 +35,7 @@ module Cloner::Ar
|
|
35
35
|
ret = ssh_exec!(ssh, "cat #{e(remote_app_path + '/config/database.yml')}")
|
36
36
|
check_ssh_err(ret)
|
37
37
|
begin
|
38
|
-
res = YAML.load(ERB.new(ret[0]).result)[env_from]
|
38
|
+
res = YAML.load(ERB.new(ret[0]).result, aliases: true)[env_from]
|
39
39
|
raise 'no data' if res.blank?
|
40
40
|
#res['host'] ||= '127.0.0.1'
|
41
41
|
rescue Exception => e
|
@@ -65,19 +65,19 @@ module Cloner::Ar
|
|
65
65
|
end
|
66
66
|
|
67
67
|
def run_clone_ar
|
68
|
-
if
|
69
|
-
puts "Error: ActiveRecord adapter mismatch: local #{
|
68
|
+
if local_db_config["adapter"] != remote_db_config["adapter"]
|
69
|
+
puts "Error: ActiveRecord adapter mismatch: local #{local_db_config["adapter"]}, remote #{remote_db_config["adapter"]}"
|
70
70
|
puts "it is not possible to convert from one database to another via this tool."
|
71
71
|
exit
|
72
72
|
end
|
73
73
|
|
74
|
-
case
|
74
|
+
case local_db_config["adapter"]
|
75
75
|
when 'postgresql'
|
76
76
|
clone_pg
|
77
77
|
when 'mysql2'
|
78
78
|
clone_my
|
79
79
|
else
|
80
|
-
puts "unknown activerecord adapter: #{
|
80
|
+
puts "unknown activerecord adapter: #{local_db_config["adapter"]}"
|
81
81
|
puts "currently supported adapters: mysql2, postgresql"
|
82
82
|
exit
|
83
83
|
end
|
@@ -0,0 +1,283 @@
|
|
1
|
+
require 'active_support/core_ext/hash'
|
2
|
+
|
3
|
+
module Cloner::DockerCompose
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
# Docker Compose configuration methods
|
7
|
+
def use_docker_compose?
|
8
|
+
false
|
9
|
+
end
|
10
|
+
|
11
|
+
def local_docker_compose?
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
def remote_docker_compose?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def docker_compose_file
|
20
|
+
"compose.yml"
|
21
|
+
end
|
22
|
+
|
23
|
+
def local_docker_compose_file
|
24
|
+
docker_compose_file
|
25
|
+
end
|
26
|
+
|
27
|
+
def remote_docker_compose_file
|
28
|
+
docker_compose_file
|
29
|
+
end
|
30
|
+
|
31
|
+
def docker_compose_service
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def local_docker_compose_service
|
36
|
+
docker_compose_service
|
37
|
+
end
|
38
|
+
|
39
|
+
def remote_docker_compose_service
|
40
|
+
docker_compose_service
|
41
|
+
end
|
42
|
+
|
43
|
+
def docker_compose_path
|
44
|
+
"."
|
45
|
+
end
|
46
|
+
|
47
|
+
def local_docker_compose_path
|
48
|
+
docker_compose_path
|
49
|
+
end
|
50
|
+
|
51
|
+
def remote_docker_compose_path
|
52
|
+
remote_app_path
|
53
|
+
end
|
54
|
+
|
55
|
+
def docker_compose_exec(service, command, opts = {})
|
56
|
+
compose_file = opts[:compose_file] || docker_compose_file
|
57
|
+
compose_path = opts[:compose_path] || docker_compose_path
|
58
|
+
no_tty = opts[:no_tty] != false ? "--no-TTY" : ""
|
59
|
+
env_vars = opts[:env] || {}
|
60
|
+
|
61
|
+
env_str = env_vars.map { |k, v| "--env #{e k}=#{e v}" }.join(" ")
|
62
|
+
|
63
|
+
"cd #{e compose_path} && docker compose -f #{e compose_file} exec #{no_tty} #{env_str} #{e service} #{command}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def local_docker_compose_exec(service, command, opts = {})
|
67
|
+
opts[:compose_file] ||= local_docker_compose_file
|
68
|
+
opts[:compose_path] ||= local_docker_compose_path
|
69
|
+
docker_compose_exec(service, command, opts)
|
70
|
+
end
|
71
|
+
|
72
|
+
def remote_docker_compose_exec(service, command, opts = {})
|
73
|
+
opts[:compose_file] ||= remote_docker_compose_file
|
74
|
+
opts[:compose_path] ||= remote_docker_compose_path
|
75
|
+
docker_compose_exec(service, command, opts)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Helper to wrap commands with docker compose when needed
|
79
|
+
def wrap_command(command, local: true)
|
80
|
+
if local && local_docker_compose?
|
81
|
+
service = local_docker_compose_service
|
82
|
+
return local_docker_compose_exec(service, command) if service
|
83
|
+
elsif !local && remote_docker_compose?
|
84
|
+
service = remote_docker_compose_service
|
85
|
+
return remote_docker_compose_exec(service, command) if service
|
86
|
+
end
|
87
|
+
command
|
88
|
+
end
|
89
|
+
|
90
|
+
# Helper to read and parse remote .env file
|
91
|
+
def remote_env_content
|
92
|
+
@remote_env_content ||= begin
|
93
|
+
content = ""
|
94
|
+
do_ssh do |ssh|
|
95
|
+
# Use docker compose path, not app path
|
96
|
+
env_path = "#{remote_docker_compose_path}/.env"
|
97
|
+
# First check if file exists
|
98
|
+
check_cmd = "test -f #{e env_path} && echo 'EXISTS' || echo 'NOT_EXISTS'"
|
99
|
+
check_ret = ssh_exec!(ssh, check_cmd)
|
100
|
+
|
101
|
+
if check_ret && check_ret[0] && check_ret[0].strip == 'NOT_EXISTS'
|
102
|
+
puts "ERROR: .env file not found at #{env_path}"
|
103
|
+
puts "Try running: ssh #{ssh_user}@#{ssh_host} 'ls -la #{remote_docker_compose_path}/'"
|
104
|
+
exit 1
|
105
|
+
end
|
106
|
+
|
107
|
+
# Then read the file
|
108
|
+
read_cmd = "cat #{e env_path} 2>&1"
|
109
|
+
ret = ssh_exec!(ssh, read_cmd)
|
110
|
+
|
111
|
+
# ssh_exec! returns [stdout, stderr, exit_code, ...]
|
112
|
+
content = ret[0] if ret && ret[0]
|
113
|
+
|
114
|
+
if content.to_s.strip == 'FILE NOT FOUND'
|
115
|
+
puts "ERROR: .env file not found at #{env_path}"
|
116
|
+
puts "Try running: ssh #{ssh_user}@#{ssh_host} 'ls -la #{remote_docker_compose_path}/'"
|
117
|
+
exit 1
|
118
|
+
end
|
119
|
+
end
|
120
|
+
content || ""
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Parse .env content into a hash
|
125
|
+
def remote_env_vars
|
126
|
+
@remote_env_vars ||= begin
|
127
|
+
vars = {}
|
128
|
+
content = remote_env_content
|
129
|
+
content.each_line.with_index do |line, idx|
|
130
|
+
original_line = line
|
131
|
+
line = line.strip
|
132
|
+
next if line.empty? || line.start_with?('#')
|
133
|
+
|
134
|
+
# Handle KEY=VALUE format
|
135
|
+
if match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/)
|
136
|
+
key = match[1]
|
137
|
+
value = match[2]
|
138
|
+
|
139
|
+
# Remove surrounding quotes if present
|
140
|
+
value = value.gsub(/^["']|["']$/, '') if value
|
141
|
+
|
142
|
+
vars[key] = value
|
143
|
+
else
|
144
|
+
end
|
145
|
+
end
|
146
|
+
vars
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Helper to read a specific env var from remote .env file
|
151
|
+
def read_remote_env(key = nil)
|
152
|
+
if key.nil?
|
153
|
+
# Return all env vars
|
154
|
+
remote_env_vars
|
155
|
+
else
|
156
|
+
# Return specific key
|
157
|
+
value = remote_env_vars[key]
|
158
|
+
value
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Helper to read and parse local .env file
|
163
|
+
def local_env_content
|
164
|
+
@local_env_content ||= begin
|
165
|
+
# Use docker compose path for .env file
|
166
|
+
env_path = File.join(local_docker_compose_path, '.env')
|
167
|
+
if File.exist?(env_path)
|
168
|
+
content = File.read(env_path)
|
169
|
+
content
|
170
|
+
else
|
171
|
+
""
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Parse local .env content into a hash
|
177
|
+
def local_env_vars
|
178
|
+
@local_env_vars ||= begin
|
179
|
+
vars = {}
|
180
|
+
local_env_content.each_line do |line|
|
181
|
+
line = line.strip
|
182
|
+
next if line.empty? || line.start_with?('#')
|
183
|
+
|
184
|
+
# Handle KEY=VALUE format
|
185
|
+
if match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/)
|
186
|
+
key = match[1]
|
187
|
+
value = match[2]
|
188
|
+
|
189
|
+
# Remove surrounding quotes if present
|
190
|
+
value = value.gsub(/^["']|["']$/, '') if value
|
191
|
+
|
192
|
+
vars[key] = value
|
193
|
+
end
|
194
|
+
end
|
195
|
+
vars
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Helper to read a specific env var from local .env file
|
200
|
+
def read_local_env(key = nil)
|
201
|
+
if key.nil?
|
202
|
+
# Return all env vars
|
203
|
+
local_env_vars
|
204
|
+
else
|
205
|
+
# Return specific key
|
206
|
+
value = local_env_vars[key]
|
207
|
+
value
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# Default local database config for Docker Compose
|
212
|
+
# Override this method to customize your database configuration
|
213
|
+
def local_db_config
|
214
|
+
if local_docker_compose?
|
215
|
+
# When using Docker Compose, read from .env file
|
216
|
+
config = {
|
217
|
+
adapter: 'postgresql',
|
218
|
+
host: read_local_env('DB_HOST') || 'localhost',
|
219
|
+
port: read_local_env('DB_PORT') || '5432',
|
220
|
+
database: read_local_env('DB_NAME'),
|
221
|
+
username: read_local_env('DB_USER'),
|
222
|
+
password: read_local_env('DB_PASSWORD') || ''
|
223
|
+
}.stringify_keys
|
224
|
+
|
225
|
+
# Validate required fields
|
226
|
+
if config['database'].nil? || config['database'].empty?
|
227
|
+
puts "Error: DB_NAME not found in local .env file at #{local_docker_compose_path}/.env"
|
228
|
+
puts "Available env vars: #{local_env_vars.keys.join(', ')}"
|
229
|
+
puts "Local .env content (first 10 lines):"
|
230
|
+
puts local_env_content.lines.first(10).join
|
231
|
+
exit 1
|
232
|
+
end
|
233
|
+
|
234
|
+
if config['username'].nil? || config['username'].empty?
|
235
|
+
puts "Error: DB_USER not found in local .env file at #{local_docker_compose_path}/.env"
|
236
|
+
puts "Available env vars: #{local_env_vars.keys.join(', ')}"
|
237
|
+
exit 1
|
238
|
+
end
|
239
|
+
|
240
|
+
config
|
241
|
+
else
|
242
|
+
# Fall back to reading from database.yml
|
243
|
+
ar_conf
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Default remote database config for Docker Compose
|
248
|
+
# Override this method to customize your database configuration
|
249
|
+
def remote_db_config
|
250
|
+
if remote_docker_compose?
|
251
|
+
# When using Docker Compose, read from .env file
|
252
|
+
config = {
|
253
|
+
adapter: 'postgresql',
|
254
|
+
host: read_remote_env('DB_HOST') || 'db',
|
255
|
+
port: read_remote_env('DB_PORT') || '5432',
|
256
|
+
database: read_remote_env('DB_NAME'),
|
257
|
+
username: read_remote_env('DB_USER'),
|
258
|
+
password: read_remote_env('DB_PASSWORD') || ''
|
259
|
+
}.stringify_keys
|
260
|
+
|
261
|
+
|
262
|
+
# Validate required fields
|
263
|
+
if config['database'].nil? || config['database'].empty?
|
264
|
+
puts "Error: DB_NAME not found in remote .env file at #{remote_docker_compose_path}/.env"
|
265
|
+
puts "Available env vars: #{remote_env_vars.keys.join(', ')}"
|
266
|
+
puts "Remote .env content (first 10 lines):"
|
267
|
+
puts remote_env_content.lines.first(10).join
|
268
|
+
exit 1
|
269
|
+
end
|
270
|
+
|
271
|
+
if config['username'].nil? || config['username'].empty?
|
272
|
+
puts "Error: DB_USER not found in remote .env file at #{remote_docker_compose_path}/.env"
|
273
|
+
puts "Available env vars: #{remote_env_vars.keys.join(', ')}"
|
274
|
+
exit 1
|
275
|
+
end
|
276
|
+
|
277
|
+
config
|
278
|
+
else
|
279
|
+
# Fall back to reading from database.yml
|
280
|
+
ar_r_conf
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
data/lib/cloner/internal.rb
CHANGED
data/lib/cloner/mongodb.rb
CHANGED
@@ -53,6 +53,36 @@ module Cloner::MongoDB
|
|
53
53
|
def mongodb_dump_extra
|
54
54
|
""
|
55
55
|
end
|
56
|
+
|
57
|
+
def mongodb_bin_path(util)
|
58
|
+
util
|
59
|
+
end
|
60
|
+
|
61
|
+
def mongodb_local_bin_path(util)
|
62
|
+
if local_docker_compose? && local_docker_compose_service
|
63
|
+
# Build docker compose exec command
|
64
|
+
compose_cmd = local_docker_compose_exec(
|
65
|
+
local_docker_compose_service,
|
66
|
+
util,
|
67
|
+
no_tty: true
|
68
|
+
)
|
69
|
+
return compose_cmd
|
70
|
+
end
|
71
|
+
mongodb_bin_path(util)
|
72
|
+
end
|
73
|
+
|
74
|
+
def mongodb_remote_bin_path(util)
|
75
|
+
if remote_docker_compose? && remote_docker_compose_service
|
76
|
+
# Build docker compose exec command for remote
|
77
|
+
compose_cmd = remote_docker_compose_exec(
|
78
|
+
remote_docker_compose_service,
|
79
|
+
util,
|
80
|
+
no_tty: true
|
81
|
+
)
|
82
|
+
return compose_cmd
|
83
|
+
end
|
84
|
+
mongodb_bin_path(util)
|
85
|
+
end
|
56
86
|
|
57
87
|
def mongodb_dump_remote
|
58
88
|
puts "backup remote DB via ssh"
|
@@ -65,7 +95,7 @@ module Cloner::MongoDB
|
|
65
95
|
else
|
66
96
|
username, password = mongodb_r_conf['username'], mongodb_r_conf['password']
|
67
97
|
end
|
68
|
-
dump = "mongodump -u #{e username} -p #{e password} -d #{e mongodb_r_conf['database']} --authenticationDatabase #{e mongodb_r_conf['database']} -o #{e remote_dump_path} #{mongodb_dump_extra}"
|
98
|
+
dump = "#{mongodb_remote_bin_path 'mongodump'} -u #{e username} -p #{e password} -d #{e mongodb_r_conf['database']} --authenticationDatabase #{e mongodb_r_conf['database']} -o #{e remote_dump_path} #{mongodb_dump_extra}"
|
69
99
|
puts dump if verbose?
|
70
100
|
ret = ssh_exec!(ssh, dump)
|
71
101
|
check_ssh_err(ret)
|
@@ -74,7 +104,7 @@ module Cloner::MongoDB
|
|
74
104
|
|
75
105
|
def mongodb_dump_restore
|
76
106
|
puts "restoring DB"
|
77
|
-
restore = "mongorestore --drop -d #{e mongodb_to} #{mongodb_local_auth} #{e mongodb_path}"
|
107
|
+
restore = "#{mongodb_local_bin_path 'mongorestore'} --drop -d #{e mongodb_to} #{mongodb_local_auth} #{e mongodb_path}"
|
78
108
|
puts restore if verbose?
|
79
109
|
pipe = IO.popen(restore)
|
80
110
|
while (line = pipe.gets)
|