1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace DigiFactory\PullProductionData; |
4
|
|
|
|
5
|
|
|
use Illuminate\Console\Command; |
6
|
|
|
use Illuminate\Support\Facades\DB; |
7
|
|
|
use Illuminate\Support\Facades\File; |
8
|
|
|
use Symfony\Component\Process\Process; |
9
|
|
|
|
10
|
|
|
class PullProductionDataCommand extends Command |
11
|
|
|
{ |
12
|
|
|
protected $signature = 'pull-production-data {--D|no-database : Whether the database should not be synced} {--S|no-storage-folder : Whether the storage folder should not be synced}'; |
13
|
|
|
protected $description = 'Pull your production storage folder and database to your local environment'; |
14
|
|
|
|
15
|
|
|
protected $user; |
16
|
|
|
protected $host; |
17
|
|
|
protected $port; |
18
|
|
|
protected $path; |
19
|
|
|
|
20
|
|
|
protected $productionDatabaseName; |
21
|
|
|
protected $productionDatabaseUser; |
22
|
|
|
protected $productionDatabasePassword; |
23
|
|
|
|
24
|
|
|
public function __construct() |
25
|
|
|
{ |
26
|
|
|
parent::__construct(); |
27
|
|
|
|
28
|
|
|
$deployServer = config('pull-production-data.deploy_server'); |
29
|
|
|
$this->path = config('pull-production-data.deploy_path'); |
30
|
|
|
|
31
|
|
|
preg_match('/(.*)@([^\s]+)(?:\s-p)?([0-9]*)/', $deployServer, $matches); |
32
|
|
|
|
33
|
|
|
if (count($matches) === 4) { |
34
|
|
|
$this->user = $matches[1]; |
35
|
|
|
$this->host = $matches[2]; |
36
|
|
|
$this->port = $matches[3] ? (int)$matches[3] : 22; |
37
|
|
|
} |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Execute the console command. |
42
|
|
|
* |
43
|
|
|
* @return mixed |
44
|
|
|
*/ |
45
|
|
|
public function handle() |
46
|
|
|
{ |
47
|
|
|
if (!$this->user || !$this->host || !$this->path) { |
48
|
|
|
$this->error('Make sure DEPLOY_SERVER and DEPLOY_PATH are set in your .env file!'); |
49
|
|
|
|
50
|
|
|
return; |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
if (!$this->confirm("Is it alright to sync production data from {$this->user} on {$this->host}?", false)) { |
54
|
|
|
$this->error('Aborted!'); |
55
|
|
|
|
56
|
|
|
return; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
$this->syncDatabase(); |
60
|
|
|
$this->syncStorageFolder(); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
public function syncDatabase() |
64
|
|
|
{ |
65
|
|
|
if ($this->option('no-database')) { |
66
|
|
|
$this->line('Skipping database...'); |
67
|
|
|
|
68
|
|
|
return; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
$this->fetchProductionDatabaseCredentials(); |
72
|
|
|
$this->fetchProductionDatabaseBackup(); |
73
|
|
|
$this->importDatabaseBackup(); |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
public function syncStorageFolder() |
77
|
|
|
{ |
78
|
|
|
if ($this->option('no-storage-folder')) { |
79
|
|
|
$this->line('Skipping storage folder...'); |
80
|
|
|
|
81
|
|
|
return; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
$this->info('Removing current storage folder...'); |
85
|
|
|
|
86
|
|
|
File::deleteDirectory(storage_path().'/app'); |
87
|
|
|
|
88
|
|
|
$this->info('Storage folder removed!'); |
89
|
|
|
|
90
|
|
|
$source = sprintf('%s@%s:%s', $this->user, $this->host, $this->path.'/storage/app'); |
91
|
|
|
$destination = storage_path(); |
92
|
|
|
|
93
|
|
|
$process = new Process(['scp', '-r', '-P'.$this->port, $source, $destination]); |
94
|
|
|
$process->setTimeout(config('pull-production-data.timeout')); |
95
|
|
|
|
96
|
|
|
$this->info(sprintf('Syncing data from [%s] to [%s]...', $source, $destination)); |
97
|
|
|
|
98
|
|
|
$process->run(); |
99
|
|
|
|
100
|
|
|
$this->info('Data synced!'); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
private function fetchProductionDatabaseCredentials() |
104
|
|
|
{ |
105
|
|
|
$this->info('Fetching production database credentials...'); |
106
|
|
|
|
107
|
|
|
$process = new Process(['ssh', "{$this->user}@{$this->host}", "-p{$this->port}", 'cat public_html/.env']); |
108
|
|
|
$process->run(); |
109
|
|
|
|
110
|
|
|
$env = $process->getOutput(); |
111
|
|
|
|
112
|
|
|
preg_match('/(?:DB_DATABASE=)(.*)/', $env, $matches); |
113
|
|
|
|
114
|
|
|
$this->productionDatabaseName = $matches[1]; |
115
|
|
|
|
116
|
|
|
preg_match('/(?:DB_USERNAME=)(.*)/', $env, $matches); |
117
|
|
|
|
118
|
|
|
$this->productionDatabaseUser = $matches[1]; |
119
|
|
|
|
120
|
|
|
preg_match('/(?:DB_PASSWORD=)(.*)/', $env, $matches); |
121
|
|
|
|
122
|
|
|
$this->productionDatabasePassword = $matches[1]; |
123
|
|
|
|
124
|
|
|
$this->info('Credentials fetched...'); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
private function fetchProductionDatabaseBackup() |
128
|
|
|
{ |
129
|
|
|
// Create backup |
130
|
|
|
$this->info('Creating production database backup...'); |
131
|
|
|
|
132
|
|
|
$command = sprintf('mysqldump -u%s -p%s %s > %s/database.sql', $this->productionDatabaseUser, $this->productionDatabasePassword, $this->productionDatabaseName, $this->path); |
133
|
|
|
|
134
|
|
|
$process = new Process(['ssh', "{$this->user}@{$this->host}", "-p{$this->port}", $command]); |
135
|
|
|
$process->run(); |
136
|
|
|
|
137
|
|
|
$this->info('Backup created!'); |
138
|
|
|
|
139
|
|
|
// Download backup |
140
|
|
|
$this->info('Downloading production database backup...'); |
141
|
|
|
|
142
|
|
|
$source = sprintf('%s@%s:%s', $this->user, $this->host, $this->path.'/database.sql'); |
143
|
|
|
$destination = base_path().'/database.sql'; |
144
|
|
|
|
145
|
|
|
$process = new Process(['scp', '-P'.$this->port, $source, $destination]); |
146
|
|
|
$process->run(); |
147
|
|
|
|
148
|
|
|
$this->info('Backup downloaded!'); |
149
|
|
|
|
150
|
|
|
// Remove backup from production machine |
151
|
|
|
$this->info('Remove database backup from production machine...'); |
152
|
|
|
|
153
|
|
|
$command = sprintf('rm %s/database.sql', $this->path); |
154
|
|
|
|
155
|
|
|
$process = new Process(['ssh', "{$this->user}@{$this->host}", "-p{$this->port}", $command]); |
156
|
|
|
$process->run(); |
157
|
|
|
|
158
|
|
|
$this->info('Database removed!'); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
private function importDatabaseBackup() |
162
|
|
|
{ |
163
|
|
|
// Remove all tables |
164
|
|
|
$this->info('Remove all local tables...'); |
165
|
|
|
|
166
|
|
|
DB::getSchemaBuilder()->dropAllTables(); |
167
|
|
|
|
168
|
|
|
$this->info('Tables removed!'); |
169
|
|
|
|
170
|
|
|
// Import database backup |
171
|
|
|
$this->info('Importing database backup...'); |
172
|
|
|
|
173
|
|
|
DB::unprepared(file_get_contents(base_path().'/database.sql')); |
174
|
|
|
|
175
|
|
|
$this->info('Database import ready!'); |
176
|
|
|
|
177
|
|
|
// Remove database backup |
178
|
|
|
$this->info('Deleting database backup...'); |
179
|
|
|
|
180
|
|
|
unlink(base_path().'/database.sql'); |
181
|
|
|
|
182
|
|
|
$this->info('Database backup deleted!'); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
public function info($string, $verbosity = null) |
186
|
|
|
{ |
187
|
|
|
parent::info('['.date('Y-m-d H:i:s').'] '.$string, $verbosity); |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
|