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