sqlsync.drush.inc

  1. 8.0.x commands/sql/sqlsync.drush.inc
  2. 7.x commands/sql/sqlsync.drush.inc
  3. master commands/sql/sqlsync.drush.inc

Functions

File

commands/sql/sqlsync.drush.inc
View source
  1. <?php
  2. use Drush\Log\LogLevel;
  3. /*
  4. * Notes:
  5. * - Drush is required on source and dest (except with --no-dump)
  6. * - Source and Dest can both be remote. Convenient.
  7. * - No special handling for multiple targets. Use --no-dump.
  8. * - We could move the sanitize confirmations earlier if desired.
  9. * - A bit tricky to know where to put your drushrc/alias customizations. Can be local, $source, or $destination. Just add docs?
  10. * - No longer optimizing for efficient rsync. We could add this back if desired.
  11. * -- Always use gzip during sql-dump. Don't use --ordered-dump.
  12. * -- No more 24-hour freshness check for a team of devs.
  13. * - Can we now simplify anything in sitealias.inc or backend.inc?
  14. */
  15. /**
  16. * Implementation of hook_drush_command().
  17. */
  18. function sqlsync_drush_command() {
  19. $items['sql-sync'] = array(
  20. 'description' => 'Copies the database contents from a source site to a target site. Transfers the database dump via rsync.',
  21. 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  22. 'drush dependencies' => array('sql', 'core'), // core-rsync.
  23. 'package' => 'sql',
  24. 'examples' => array(
  25. 'drush sql-sync @source @target' => 'Copy the database from the site with the alias "source" to the site with the alias "target".',
  26. 'drush sql-sync prod dev' => 'Copy the database from the site in /sites/prod to the site in /sites/dev (multisite installation).',
  27. ),
  28. 'arguments' => array(
  29. 'source' => 'A site-alias or the name of a subdirectory within /sites whose database you want to copy from.',
  30. 'target' => 'A site-alias or the name of a subdirectory within /sites whose database you want to replace.',
  31. ),
  32. 'required-arguments' => TRUE,
  33. 'options' => array(
  34. 'skip-tables-key' => 'A key in the $skip_tables array. See example.drushrc.php. Optional.',
  35. 'skip-tables-list' => 'A comma-separated list of tables to exclude completely. Optional.',
  36. 'structure-tables-key' => 'A key in the $structure_tables array. See example.drushrc.php. Optional.',
  37. 'structure-tables-list' => 'A comma-separated list of tables to include for structure, but not data. Optional.',
  38. 'tables-key' => 'A key in the $tables array. Optional.',
  39. 'tables-list' => 'A comma-separated list of tables to transfer. Optional.',
  40. // 'cache' => 'Skip dump if result file exists and is less than "cache" hours old. Optional; default is 24 hours.',
  41. // 'no-cache' => 'Do not cache the sql-dump file.',
  42. 'no-dump' => 'Do not dump the sql database; always use an existing dump file.',
  43. 'no-sync' => 'Do not rsync the database dump file from source to target.',
  44. 'source-db-url' => 'Database specification for source system to dump from.',
  45. 'source-remote-port' => 'Override sql database port number in source-db-url. Optional.',
  46. 'source-remote-host' => 'Remote machine to run sql-dump file on. Optional; default is local machine.',
  47. 'source-dump' => array(
  48. 'description' => 'The destination for the dump file, or the path to the dump file when --no-dump is specified.',
  49. 'example-value' => '/dumpdir/db.sql',
  50. ),
  51. 'source-database' => 'A key in the $db_url (D6) or $databases (D7+) array which provides the data.',
  52. 'source-target' => array(
  53. 'description' => 'A key within the SOURCE database identifying a particular server in the database group.',
  54. 'example-value' => 'key',
  55. // Gets unhidden in help_alter(). We only want to show to D7+ users but have to
  56. // declare it here since this command does not bootstrap fully.
  57. 'hidden' => TRUE,
  58. ),
  59. 'target-db-url' => '',
  60. 'target-remote-port' => '',
  61. 'target-remote-host' => '',
  62. 'target-dump' => array(
  63. 'description' => 'A path for saving the dump file on target. Mandatory when using --no-sync.',
  64. 'example-value' => '/dumpdir/db.sql.gz',
  65. ),
  66. 'target-database' => 'A key in the $db_url (D6) or $databases (D7+) array which shall receive the data.',
  67. 'target-target' => array(
  68. 'description' => 'Oy. A key within the TARGET database identifying a particular server in the database group.',
  69. 'example-value' => 'key',
  70. // Gets unhidden in help_alter(). We only want to show to D7+ users but have to
  71. // declare it here since this command does not bootstrap fully.
  72. 'hidden' => TRUE,
  73. ),
  74. 'create-db' => 'Create a new database before importing the database dump on the target machine.',
  75. 'db-su' => array(
  76. 'description' => 'Account to use when creating a new database. Optional.',
  77. 'example-value' => 'root',
  78. ),
  79. 'db-su-pw' => array(
  80. 'description' => 'Password for the "db-su" account. Optional.',
  81. 'example-value' => 'pass',
  82. ),
  83. // 'no-ordered-dump' => 'Do not pass --ordered-dump to sql-dump. sql-sync orders the dumpfile by default in order to increase the efficiency of rsync.',
  84. 'sanitize' => 'Obscure email addresses and reset passwords in the user table post-sync.',
  85. ),
  86. 'sub-options' => array(
  87. 'sanitize' => drupal_sanitize_options() + array(
  88. 'confirm-sanitizations' => 'Prompt yes/no after importing the database, but before running the sanitizations',
  89. ),
  90. ),
  91. 'topics' => array('docs-aliases', 'docs-policy', 'docs-example-sync-via-http', 'docs-example-sync-extension'),
  92. );
  93. return $items;
  94. }
  95. /**
  96. * Implements hook_drush_help_alter().
  97. */
  98. function sqlsync_drush_help_alter(&$command) {
  99. // Drupal 7+ only options.
  100. if (drush_drupal_major_version() >= 7) {
  101. if ($command['command'] == 'sql-sync') {
  102. unset($command['options']['source-target']['hidden'], $command['options']['target-target']['hidden']);
  103. }
  104. }
  105. }
  106. /*
  107. * Implements COMMAND hook init.
  108. */
  109. function drush_sql_sync_init($source, $destination) {
  110. // Try to get @self defined when --uri was not provided.
  111. drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
  112. // Preflight destination in case it defines the alias used by the source
  113. _drush_sitealias_get_record($destination);
  114. // After preflight, get source and destination settings
  115. $source_settings = drush_sitealias_get_record($source);
  116. $destination_settings = drush_sitealias_get_record($destination);
  117. // Apply command-specific options.
  118. drush_sitealias_command_default_options($source_settings, 'source-');
  119. drush_sitealias_command_default_options($destination_settings, 'target-');
  120. }
  121. /*
  122. * A command validate callback.
  123. */
  124. function drush_sqlsync_sql_sync_validate($source, $destination) {
  125. // Get destination info for confirmation prompt.
  126. $source_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($source), 'source-');
  127. $destination_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($destination), 'target-');
  128. $source_db_url = drush_sitealias_get_db_spec($source_settings, FALSE, 'source-');
  129. $target_db_url = drush_sitealias_get_db_spec($destination_settings, FALSE, 'target-');
  130. $txt_source = (isset($source_db_url['remote-host']) ? $source_db_url['remote-host'] . '/' : '') . $source_db_url['database'];
  131. $txt_destination = (isset($target_db_url['remote-host']) ? $target_db_url['remote-host'] . '/' : '') . $target_db_url['database'];
  132. // Validate.
  133. if (empty($source_db_url)) {
  134. if (empty($source_settings)) {
  135. return drush_set_error('DRUSH_ALIAS_NOT_FOUND', dt('Error: no alias record could be found for source !source', array('!source' => $source)));
  136. }
  137. return drush_set_error('DRUSH_DATABASE_NOT_FOUND', dt('Error: no database record could be found for source !source', array('!source' => $source)));
  138. }
  139. if (empty($target_db_url)) {
  140. if (empty($destination_settings)) {
  141. return drush_set_error('DRUSH_ALIAS_NOT_FOUND', dt('Error: no alias record could be found for target !destination', array('!destination' => $destination)));
  142. }
  143. return drush_set_error('DRUSH_DATABASE_NOT_FOUND', dt('Error: no database record could be found for target !destination', array('!destination' => $destination)));
  144. }
  145. if (isset($source_db_url['remote-host']) && isset($target_db_url['remote-host']) && ($source_db_url['remote-host'] == $target_db_url['remote-host']) && ($source_db_url['database'] == $target_db_url['database']) && !drush_get_context('DRUSH_SIMULATE')) {
  146. return drush_set_error('DRUSH_SAME_DATABASE', dt('Source and target databases are the same; please sync to a different target.'));
  147. }
  148. if (drush_get_option('no-dump') && !drush_get_option('source-dump')) {
  149. return drush_set_error('DRUSH_SOURCE_DUMP_MISSING', dt('The --source-dump option must be supplied when --no-dump is specified.'));
  150. }
  151. if (drush_get_option('no-sync') && !drush_get_option('target-dump')) {
  152. return drush_set_error('DRUSH_TARGET_DUMP_MISSING', dt('The --target-dump option must be supplied when --no-sync is specified.'));
  153. }
  154. if (!drush_get_context('DRUSH_SIMULATE')) {
  155. drush_print(dt("You will destroy data in !target and replace with data from !source.", array('!source' => $txt_source, '!target' => $txt_destination)));
  156. // @todo Move sanitization prompts to here. They currently show much later.
  157. if (!drush_confirm(dt('Do you really want to continue?'))) {
  158. return drush_user_abort();
  159. }
  160. }
  161. }
  162. /*
  163. * A command callback.
  164. */
  165. function drush_sqlsync_sql_sync($source, $destination) {
  166. $source_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($source), 'source-');
  167. $destination_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($destination), 'target-');
  168. $source_is_local = !array_key_exists('remote-host', $source_settings) || drush_is_local_host($source_settings);
  169. $destination_is_local = !array_key_exists('remote-host', $destination_settings) || drush_is_local_host($destination_settings);
  170. // These options are passed along to subcommands like sql-create, sql-dump, sql-query, sql-sanitize, ...
  171. $source_options = drush_get_merged_prefixed_options('source-');
  172. $target_options = drush_get_merged_prefixed_options('target-');
  173. $backend_options = array();
  174. // @todo drush_redispatch_get_options() assumes you will execute same command. Not good.
  175. $global_options = drush_redispatch_get_options() + array(
  176. 'strict' => 0,
  177. );
  178. // We do not want to include root or uri here. If the user
  179. // provided -r or -l, their key has already been remapped to
  180. // 'root' or 'uri' by the time we get here.
  181. unset($global_options['root']);
  182. unset($global_options['uri']);
  183. if (drush_get_context('DRUSH_SIMULATE')) {
  184. $backend_options['backend-simulate'] = TRUE;
  185. }
  186. // Create destination DB if needed.
  187. if (drush_get_option('create-db')) {
  188. drush_log(dt('Starting to create database on Destination.'), LogLevel::OK);
  189. $return = drush_invoke_process($destination, 'sql-create', array(), $global_options + $target_options, $backend_options);
  190. if ($return['error_status']) {
  191. return drush_set_error('DRUSH_SQL_CREATE_FAILED', dt('sql-create failed.'));
  192. }
  193. }
  194. // Perform sql-dump on source unless told othrwise.
  195. $options = $global_options + $source_options + array(
  196. 'gzip' => TRUE,
  197. 'result-file' => drush_get_option('source-dump', TRUE),
  198. // 'structure-tables-list' => 'cache*', // Do we want to default to this?
  199. );
  200. if (!drush_get_option('no-dump')) {
  201. drush_log(dt('Starting to dump database on Source.'), LogLevel::OK);
  202. $return = drush_invoke_process($source, 'sql-dump', array(), $options, $backend_options);
  203. if ($return['error_status']) {
  204. return drush_set_error('DRUSH_SQL_DUMP_FAILED', dt('sql-dump failed.'));
  205. }
  206. else {
  207. $source_dump_path = $return['object'];
  208. if (!is_string($source_dump_path)) {
  209. return drush_set_error('DRUSH_SQL_DUMP_FILE_NOT_REPORTED', 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.'));
  210. }
  211. }
  212. }
  213. else {
  214. $source_dump_path = drush_get_option('source-dump');
  215. }
  216. $do_rsync = !drush_get_option('no-sync');
  217. // Determine path/to/dump on destination.
  218. if (drush_get_option('target-dump')) {
  219. $destination_dump_path = drush_get_option('target-dump');
  220. $rsync_options['yes'] = TRUE; // @temporary: See https://github.com/drush-ops/drush/pull/555
  221. }
  222. elseif ($source_is_local && $destination_is_local) {
  223. $destination_dump_path = $source_dump_path;
  224. $do_rsync = false;
  225. }
  226. else {
  227. $tmp = '/tmp'; // Our fallback plan.
  228. drush_log(dt('Starting to discover temporary files directory on Destination.'), LogLevel::OK);
  229. $return = drush_invoke_process($destination, 'php-eval', array('return drush_find_tmp();'), array(), array('integrate' => FALSE, 'override-simulated' => TRUE));
  230. if (!$return['error_status']) {
  231. $tmp = $return['object'];
  232. }
  233. $destination_dump_path = $tmp . '/' . basename($source_dump_path);
  234. $rsync_options['yes'] = TRUE; // No need to prompt as destination is a tmp file.
  235. }
  236. if ($do_rsync) {
  237. if (!drush_get_option('no-dump')) {
  238. // Cleanup if this command created the dump file.
  239. $rsync_options['remove-source-files'] = TRUE;
  240. }
  241. // Try run to rsync locally so that aliases always resolve. https://github.com/drush-ops/drush/issues/668
  242. if (drush_sitealias_is_remote_site($source) === FALSE) {
  243. $runner = $source;
  244. }
  245. elseif (drush_sitealias_is_remote_site($destination) === FALSE) {
  246. $runner = $destination;
  247. }
  248. else {
  249. // Both are remote. Arbitrarily run rsync on destination. Aliases must be defined there (for now).
  250. // @todo Add an option for choosing runner? Resolve aliases before rsync?
  251. $runner = $destination;
  252. }
  253. $return = drush_invoke_process($runner, 'core-rsync', array("$source:$source_dump_path", "$destination:$destination_dump_path"), $rsync_options);
  254. drush_log(dt('Copying dump file from Source to Destination.'), LogLevel::OK);
  255. if ($return['error_status']) {
  256. return drush_set_error('DRUSH_RSYNC_FAILED', dt('core-rsync failed.'));
  257. }
  258. }
  259. // Import file into destination.
  260. drush_log(dt('Starting to import dump file onto Destination database.'), LogLevel::OK);
  261. $options = $global_options + $target_options + array(
  262. 'file' => $destination_dump_path,
  263. 'file-delete' => TRUE,
  264. );
  265. $return = drush_invoke_process($destination, 'sql-query', array(), $options, $backend_options);
  266. if ($return['error_status']) {
  267. // An error was already logged.
  268. return FALSE;
  269. }
  270. // Run Sanitize if needed.
  271. $options = $global_options + $target_options;
  272. if (drush_get_option('sanitize')) {
  273. drush_log(dt('Starting to sanitize target database on Destination.'), LogLevel::OK);
  274. $return = drush_invoke_process($destination, 'sql-sanitize', array(), $options, $backend_options);
  275. if ($return['error_status']) {
  276. return drush_set_error('DRUSH_SQL_SANITIZE_FAILED', dt('sql-sanitize failed.'));
  277. }
  278. }
  279. }