-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathSqlSyncCommands.php
193 lines (179 loc) · 9.62 KB
/
SqlSyncCommands.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
<?php
namespace Drush\Commands\sql;
use Consolidation\AnnotatedCommand\CommandData;
use Drush\Commands\DrushCommands;
use Drush\Drush;
use Drush\Exceptions\UserAbortException;
use Drush\SiteAlias\AliasRecord;
use Drush\SiteAlias\SiteAliasManagerAwareInterface;
use Drush\SiteAlias\SiteAliasManagerAwareTrait;
use Symfony\Component\Config\Definition\Exception\Exception;
use Webmozart\PathUtil\Path;
use Robo\Contract\ConfigAwareInterface;
use Robo\Common\ConfigAwareTrait;
use Drush\Config\ConfigLocator;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
class SqlSyncCommands extends DrushCommands implements SiteAliasManagerAwareInterface, ConfigAwareInterface
{
use SiteAliasManagerAwareTrait;
use ConfigAwareTrait;
/**
* Copy DB data from a source site to a target site. Transfers data via rsync.
*
* @command sql:sync
* @aliases sql-sync
* @param $source A site-alias or the name of a subdirectory within /sites whose database you want to copy from.
* @param $target A site-alias or the name of a subdirectory within /sites whose database you want to replace.
* @optionset_table_selection
* @option no-dump Do not dump the sql database; always use an existing dump file.
* @option no-sync Do not rsync the database dump file from source to target.
* @option runner Where to run the rsync command; defaults to the local site. Can also be 'source' or 'target'.
* @option create-db Create a new database before importing the database dump on the target machine.
* @option db-su Account to use when creating a new database (e.g. root).
* @option db-su-pw Password for the db-su account.
* @option source-dump The path for retrieving the sql-dump on source machine.
* @option target-dump The path for storing the sql-dump on target machine.
* @usage drush sql:sync @source @target
* Copy the database from the site with the alias 'source' to the site with the alias 'target'.
* @usage drush sql:sync #prod #dev
* Copy the database from the site in /sites/prod to the site in /sites/dev (multisite installation).
* @topics docs:aliases,docs:policy,docs:example-sync-via-http
*/
public function sqlsync($source, $target, $options = ['no-dump' => false, 'no-sync' => false, 'runner' => self::REQ, 'create-db' => false, 'db-su' => self::REQ, 'db-su-pw' => self::REQ, 'target-dump' => self::REQ, 'source-dump' => self::OPT])
{
$manager = $this->siteAliasManager();
$sourceRecord = $manager->get($source);
$targetRecord = $manager->get($target);
$backend_options = [];
$global_options = Drush::redispatchOptions() + ['strict' => 0];
// Create target DB if needed.
if ($options['create-db']) {
$this->logger()->notice(dt('Starting to create database on target.'));
$return = drush_invoke_process($target, 'sql-create', array(), $global_options, $backend_options);
if ($return['error_status']) {
throw new \Exception(dt('sql-create failed.'));
}
}
// Perform sql-dump on source unless told otherwise.
$dump_options = $global_options + array(
'gzip' => true,
'result-file' => $options['source-dump'] ?: true,
);
if (!$options['no-dump']) {
$this->logger()->notice(dt('Starting to dump database on source.'));
$return = drush_invoke_process($sourceRecord, 'sql-dump', array(), $dump_options, $backend_options);
if ($return['error_status']) {
throw new \Exception(dt('sql-dump failed.'));
} elseif (Drush::simulate()) {
$source_dump_path = '/simulated/path/to/dump.tgz';
} else {
$source_dump_path = $return['object'];
if (!is_string($source_dump_path)) {
throw new \Exception(dt('The Drush sql-dump command did not report the path to the dump file produced. Try upgrading the version of Drush you are using on the source machine.'));
}
}
} else {
$source_dump_path = $options['source-dump'];
}
$do_rsync = !$options['no-sync'];
// Determine path/to/dump on target.
if ($options['target-dump']) {
$target_dump_path = $options['target-dump'];
$backend_options['interactive'] = false; // @temporary: See https://github.com/drush-ops/drush/pull/555
} elseif (!$sourceRecord->isRemote() && !$targetRecord->isRemote()) {
$target_dump_path = $source_dump_path;
$do_rsync = false;
} else {
$tmp = '/tmp'; // Our fallback plan.
$this->logger()->notice(dt('Starting to discover temporary files directory on target.'));
$return = drush_invoke_process($target, 'core-status', array(), array(), array('integrate' => false, 'override-simulated' => true));
if (!$return['error_status'] && isset($return['object']['drush-temp'])) {
$tmp = $return['object']['drush-temp'];
}
$target_dump_path = Path::join($tmp, basename($source_dump_path));
$backend_options['interactive'] = false; // No need to prompt as target is a tmp file.
}
if ($do_rsync) {
$rsync_options = [];
if (!$options['no-dump']) {
// Cleanup if this command created the dump file.
$rsync_options[] = '--remove-source-files';
}
if (!$runner = $options['runner']) {
$runner = $sourceRecord->isRemote() && $targetRecord->isRemote() ? $target : '@self';
}
if ($runner == 'source') {
$runner = $source;
}
if (($runner == 'target') || ($runner == 'destination')) {
$runner = $target;
}
// Since core-rsync is a strict-handling command and drush_invoke_process() puts options at end, we can't send along cli options to rsync.
// Alternatively, add options like --ssh-options to a site alias (usually on the machine that initiates the sql-sync).
$return = drush_invoke_process($runner, 'core-rsync', array_merge(["$source:$source_dump_path", "$target:$target_dump_path", '--'], $rsync_options), [], $backend_options);
$this->logger()->notice(dt('Copying dump file from source to target.'));
if ($return['error_status']) {
throw new \Exception(dt('core-rsync failed.'));
}
}
// Import file into target.
$this->logger()->notice(dt('Starting to import dump file onto target database.'));
$query_options = $global_options + array(
'file' => $target_dump_path,
'file-delete' => true,
);
$return = drush_invoke_process($target, 'sql-query', array(), $query_options, $backend_options);
if ($return['error_status']) {
throw new Exception('Failed to rsync the database dump from source to target.');
}
}
/**
* @hook validate sql-sync
*/
public function validate(CommandData $commandData)
{
$source = $commandData->input()->getArgument('source');
$target = $commandData->input()->getArgument('target');
// Get target info for confirmation prompt.
$manager = $this->siteAliasManager();
if (!$sourceRecord = $manager->get($source)) {
throw new \Exception(dt('Error: no alias record could be found for source !source', array('!source' => $source)));
}
if (!$targetRecord = $manager->get($target)) {
throw new \Exception(dt('Error: no alias record could be found for target !target', array('!target' => $target)));
}
if (!$source_db_name = $this->databaseName($sourceRecord)) {
throw new \Exception(dt('Error: no database record could be found for source !source', array('!source' => $source)));
}
if (!$target_db_name = $this->databaseName($targetRecord)) {
throw new \Exception(dt('Error: no database record could be found for target !target', array('!target' => $target)));
}
$txt_source = ($sourceRecord->remoteHost() ? $sourceRecord->remoteHost() . '/' : '') . $source_db_name;
$txt_target = ($targetRecord->remoteHost() ? $targetRecord->remoteHost() . '/' : '') . $target_db_name;
if ($commandData->input()->getOption('no-dump') && !$commandData->input()->getOption('source-dump')) {
throw new \Exception(dt('The --source-dump option must be supplied when --no-dump is specified.'));
}
if ($commandData->input()->getOption('no-sync') && !$commandData->input()->getOption('target-dump')) {
throw new \Exception(dt('The --target-dump option must be supplied when --no-sync is specified.'));
}
if (!Drush::simulate()) {
$this->output()->writeln(dt("You will destroy data in !target and replace with data from !source.", array(
'!source' => $txt_source,
'!target' => $txt_target
)));
if (!$this->io()->confirm(dt('Do you really want to continue?'))) {
throw new UserAbortException();
}
}
}
public function databaseName(AliasRecord $record)
{
if ($record->isRemote() && preg_match('#\.simulated$#', $record->remoteHost())) {
return 'simulated_db';
}
$values = drush_invoke_process($record, "core-status", array(), array(), array('integrate' => false, 'override-simulated' => true));
if (is_array($values) && ($values['error_status'] == 0)) {
return $values['object']['db-name'];
}
}
}