archive.drush.inc

  1. 8.0.x commands/core/archive.drush.inc
  2. 6.x commands/core/archive.drush.inc
  3. 7.x commands/core/archive.drush.inc
  4. 4.x commands/core/archive.drush.inc
  5. 5.x commands/core/archive.drush.inc
  6. master commands/core/archive.drush.inc

An early implementation of Site Archive dump/restore. See http://groups.drupal.org/site-archive-format.

Functions

Namesort descending Description
archive_drush_command
drush_archive_dump Command callback. Generate site archive file.
drush_archive_restore Command callback. Restore web site(s) from a site archive file.

File

commands/core/archive.drush.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * An early implementation of Site Archive dump/restore. See
  5. * http://groups.drupal.org/site-archive-format.
  6. */
  7. function archive_drush_command() {
  8. $items['archive-dump'] = array(
  9. 'description' => 'Backup your code, files, and database into a single file.',
  10. 'arguments' => array(
  11. 'targets' => 'Optional. Site specifications, delimited by commas. Typically, list subdirectory name(s) under /sites.',
  12. ),
  13. 'options' => array(
  14. 'description' => 'Describe the archive contents.',
  15. 'tags' => 'Add tags to the archive manifest. Delimit multiple by commas.',
  16. 'destination' => 'The full path and filename in which the archive should be stored. If omitted, it will be saved to the drush-backups directory and a filename will be generated.',
  17. 'overwrite' => 'Do not fail if the destination file exists; overwrite it instead.',
  18. 'generator' => 'The generator name to store in the MANIFEST file. The default is "Drush archive-dump".',
  19. 'generatorversion' => 'The generator version number to store in the MANIFEST file. The default is ' . DRUSH_VERSION . '.',
  20. 'pipe' => 'Only print the destination of the archive. Useful for scripts that don\'t pass --destination.',
  21. 'preserve-symlinks' => 'Preserve symbolic links.',
  22. ),
  23. 'examples' => array(
  24. 'drush archive-dump default,example.com,foo.com' => 'Write an archive containing 3 sites in it.',
  25. 'drush archive-dump @sites' => 'Save archive containing all sites in a multi-site.',
  26. 'drush archive-dump default --destination=/backups/mysite.tar' => 'Save archive to custom location.',
  27. ),
  28. 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE,
  29. 'aliases' => array('ard', 'archive-backup', 'arb'),
  30. );
  31. $items['archive-restore'] = array(
  32. 'description' => 'Expand a site archive into a Drupal web site.',
  33. 'arguments' => array(
  34. 'file' => 'The site archive file that should be expanded.',
  35. 'site name' => 'Optional. Which site within the archive you want to restore. Defaults to all.',
  36. ),
  37. 'options' => array(
  38. 'destination' => 'Specify where the Drupal site should be expanded. Defaults to the current working directory.',
  39. 'db-prefix' => 'An optional table prefix to use during restore.',
  40. 'db-url' => 'A Drupal 6 style database URL indicating the target for database restore. If not provided, the archived settings.php is used.',
  41. 'db-su' => 'Account to use when creating the new database. Optional.',
  42. 'db-su-pw' => 'Password for the "db-su" account. Optional.',
  43. 'overwrite' => 'Allow drush to overwrite any files in the destinion.',
  44. ),
  45. 'examples' => array(
  46. 'drush archive-restore ./example.tar.gz' => 'Restore the files and databases for all sites in the archive.',
  47. 'drush archive-restore ./example.tar.gz example.com' => 'Restore the files and database for example.com site.',
  48. 'drush archive-restore ./example.tar.gz --destination=/var/www/example.com/docroot' => 'Restore archive to a custom location.',
  49. 'drush archive-restore ./example.tar.gz --db-url=mysql://root:pass@127.0.0.1/dbname' => 'Restore archive to a new database (and customize settings.php to point there.).',
  50. ),
  51. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  52. 'aliases' => array('arr'),
  53. );
  54. return $items;
  55. }
  56. /**
  57. * Command callback. Generate site archive file.
  58. */
  59. function drush_archive_dump($sites_subdirs = '@self') {
  60. $aliases = drush_sitealias_resolve_sitespecs(explode(',', $sites_subdirs));
  61. foreach ($aliases as $key => $alias) {
  62. if (($db_record = sitealias_get_databases_from_record($alias))) {
  63. $full[$key] = $alias += $db_record;
  64. }
  65. else {
  66. drush_log(dt('DB connections not found for !alias', array('!alias' => $alias)), 'error');
  67. return;
  68. }
  69. }
  70. // The user can specify a destination filepath or not. That filepath might
  71. // end with .gz, .tgz, or something else. At the end of this command we will
  72. // gzip a file, and we want it to end up with the user-specified name (if
  73. // any), but gzip renames files and refuses to compress files ending with
  74. // .gz and .tgz, making our lives difficult. Solution:
  75. //
  76. // 1. Create a unique temporary base name to which gzip WILL append .gz.
  77. // 2. If no destination is provided, set $dest_dir to a backup directory and
  78. // $final_destination to be the unique name in that dir.
  79. // 3. If a destination is provided, set $dest_dir to that directory and
  80. // $final_destination to the exact name given.
  81. // 4. Set $destination, the actual working file we will build up, to the
  82. // unqiue name in $dest_dir.
  83. // 5. After gzip'ing $destination, rename $destination.gz to
  84. // $final_destination.
  85. //
  86. // Sheesh.
  87. // Create the unique temporary name.
  88. $date = gmdate('Ymd_his');
  89. $first = current($full);
  90. $prefix = count($sites_subdirs) > 1 ? 'multiple_sites' : $first['default']['default']['database'];
  91. $temp_dest_name = "$prefix.$date.tar";
  92. $final_destination = drush_get_option('destination');
  93. if (!$final_destination) {
  94. // No destination provided.
  95. drush_include_engine('version_control', 'backup');
  96. $backup = new drush_pm_version_control_backup();
  97. // TODO: this standard drush pattern leads to a slightly obtuse directory structure.
  98. $dest_dir = $backup->prepare_backup_dir('archive-dump');
  99. if (empty($dest_dir)) {
  100. $dest_dir = drush_tempdir();
  101. }
  102. $final_destination = "$dest_dir/$temp_dest_name.gz";
  103. }
  104. else {
  105. // Use the supplied --destination. If it is relative, resolve it
  106. // relative to the directory in which drush was invoked.
  107. $command_cwd = getcwd();
  108. drush_op('chdir', drush_get_context('DRUSH_OLDCWD', getcwd()));
  109. // This doesn't perform realpath on the basename, but that's okay. This is
  110. // not path-based security. We just use it for checking for perms later.
  111. $dest_dir = realpath(dirname($final_destination));
  112. $final_destination = $dest_dir . '/' . basename($final_destination);
  113. drush_op('chdir', $command_cwd);
  114. }
  115. // $dest_dir is either the backup directory or specified directory. Set our
  116. // working file.
  117. $destination = "$dest_dir/$temp_dest_name";
  118. // Validate the FINAL destination. It should be a file that does not exist
  119. // (unless --overwrite) in a writable directory (and a writable file if
  120. // it exists). We check all this up front to avoid failing after a long
  121. // dump process.
  122. $overwrite = drush_get_option('overwrite');
  123. $dest_dir = dirname($final_destination);
  124. $dt_args = array('!file' => $final_destination, '!dir' => $dest_dir);
  125. if (is_dir($final_destination)) {
  126. drush_set_error('DRUSH_ARCHIVE_DEST_IS_DIR', dt('destination !file must be a file, not a directory.', $dt_args));
  127. return;
  128. }
  129. else if (file_exists($final_destination)) {
  130. if (!$overwrite) {
  131. drush_set_error('DRUSH_ARCHIVE_DEST_EXISTS', dt('destination !file exists; specify --overwrite to overwrite.', $dt_args));
  132. return;
  133. }
  134. else if (!is_writable($final_destination)) {
  135. drush_set_error('DRUSH_ARCHIVE_DEST_FILE_NOT_WRITEABLE', dt('destination !file is not writable.', $dt_args));
  136. return;
  137. }
  138. }
  139. else if (!is_writable(dirname($final_destination))) {
  140. drush_set_error('DRUSH_ARCHIVE_DEST_DIR_NOT_WRITEABLE', dt('destination directory !dir is not writable.', $dt_args));
  141. return;
  142. }
  143. $docroot_path = realpath(drush_get_context('DRUSH_DRUPAL_ROOT'));
  144. $docroot = basename($docroot_path);
  145. $workdir = dirname($docroot_path);
  146. $dereference = (drush_get_option('preserve-symlinks', FALSE)) ? '' : '--dereference ';
  147. // Archive Drupal core, excluding sites dir.
  148. drush_shell_cd_and_exec($workdir, "tar --exclude '{$docroot}/sites' {$dereference}-cf %s %s", $destination, $docroot);
  149. // Add sites/all to the same archive.
  150. drush_shell_cd_and_exec($workdir, "tar {$dereference}-rf %s %s", $destination, "{$docroot}/sites/all");
  151. // Dump the default database for each site and add to the archive.
  152. foreach ($full as $key => $alias) {
  153. if ($db = $alias['databases']['default']['default']) {
  154. $tmp = drush_tempdir();
  155. // Use a subdirectory name matching the docroot name.
  156. drush_mkdir("{$tmp}/{$docroot}");
  157. // Ensure uniqueness by prefixing key if needed.
  158. $dbname = $db['database'];
  159. $result_file = count($full) == 1 ? "$tmp/$dbname.sql" : str_replace('@', '', "$tmp/$key-$dbname.sql");
  160. $all_dbs[$key] = array(
  161. 'file' => $result_file,
  162. 'driver' => $db['driver'],
  163. );
  164. drush_set_option('result-file', $result_file);
  165. $table_selection = drush_sql_get_table_selection();
  166. list($dump_exec, $dump_file) = drush_sql_build_dump_command($table_selection, $db);
  167. drush_shell_exec($dump_exec);
  168. drush_shell_cd_and_exec($tmp, 'tar --dereference -rf %s %s', $destination, basename($result_file));
  169. }
  170. }
  171. // Build a manifest file AND add sites/$subdir to archive as we go.
  172. $platform = array(
  173. 'datestamp' => time(),
  174. 'formatversion' => '1.0',
  175. 'generator' => drush_get_option('generator', 'Drush archive-dump'),
  176. 'generatorversion' => drush_get_option('generatorversion', DRUSH_VERSION),
  177. 'description' => drush_get_option('description', ''),
  178. 'tags' => drush_get_option('tags', ''),
  179. );
  180. $contents = drush_export_ini(array('Global' => $platform));
  181. $i=0;
  182. foreach ($full as $key => $alias) {
  183. $status = drush_invoke_sitealias_args($alias, 'core-status', array(), array());
  184. // Add the site specific directory to archive.
  185. if (!empty($status['object']['%paths']['%site'])) {
  186. drush_shell_cd_and_exec($workdir, "tar --dereference -rf %s %s", $destination, "{$docroot}/sites/" . basename($status['object']['%paths']['%site']));
  187. }
  188. $site = array(
  189. 'docroot' => DRUPAL_ROOT,
  190. 'sitedir' => @$status['object']['%paths']['%site'],
  191. 'files-public' => @$status['object']['%paths']['%files'],
  192. 'files-private' => @$status['object']['%paths']['%private'],
  193. );
  194. // Add info for each DB connection (usually only 1);
  195. foreach ($all_dbs as $dbkey => $db) {
  196. $site["database-default-file"] = basename($db['file']);
  197. $site["database-default-driver"] = $db['driver'];
  198. }
  199. // The section title is the sites subdirectory name.
  200. $info[basename($site['sitedir'])] = $site;
  201. $contents .= "\n" . drush_export_ini($info);
  202. unset($info);
  203. $i++;
  204. }
  205. file_put_contents("{$tmp}/MANIFEST.ini", $contents);
  206. // Add manifest to archive.
  207. drush_shell_cd_and_exec($tmp, 'tar --dereference -rf %s %s', $destination, "MANIFEST.ini");
  208. // Compress the archive
  209. drush_shell_exec("gzip --no-name -f %s", $destination);
  210. // gzip appends .gz unless the name already ends in .gz, .tgz, or .taz.
  211. if ("{$destination}.gz" != $final_destination) {
  212. drush_move_dir("{$destination}.gz", $final_destination, $overwrite);
  213. }
  214. drush_log(dt('Archive saved to !dest', array('!dest' => $final_destination)), 'ok');
  215. drush_print_pipe($final_destination);
  216. return $final_destination;
  217. }
  218. /**
  219. * Command callback. Restore web site(s) from a site archive file.
  220. */
  221. function drush_archive_restore($file, $site_id = NULL) {
  222. $tmp = drush_tempdir();
  223. if (!$files = drush_tarball_extract($file, $tmp)) {
  224. return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_EXTRACT', dt('Unable to extract site archive tarball to !tmp.', array('!tmp' => $tmp)));
  225. }
  226. $manifest = $tmp . '/MANIFEST.ini';
  227. if (file_exists($manifest)) {
  228. if (!$ini = parse_ini_file($manifest, TRUE)) {
  229. return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_PARSE_MANIFEST', dt('Unable to parse MANIFEST.ini in the archive.'));
  230. }
  231. }
  232. else {
  233. // No manifest. Try to find docroot and DB dump file.
  234. $db_file = drush_scan_directory($tmp, '/\.sql$/', array('.', '..', 'CVS'), 0, 0);
  235. $directories = glob($tmp . '/*' , GLOB_ONLYDIR);
  236. $ini = array(
  237. 'Global' => array(),
  238. 'default' => array(
  239. 'docroot' => reset($directories),
  240. 'sitedir' => 'sites/default',
  241. 'database-default-file' => key($db_file),
  242. ),
  243. );
  244. }
  245. // Grab the first site in the Manifest and move docroot to destination.
  246. $ini_tmp = $ini;
  247. unset($ini_tmp['Global']);
  248. $first = array_shift($ini_tmp);
  249. $docroot = basename($first['docroot']);
  250. $destination = drush_get_option('destination', realpath('.') . "/$docroot");
  251. if (!drush_move_dir("$tmp/$docroot", $destination, drush_get_option('overwrite'))) {
  252. return drush_set_error('DRUSH_ARCHIVE_UNABLE _RESTORE_FILES', dt('Unable to restore files to !dest', array('!dest' => $destination)));
  253. }
  254. // Loop over sites and restore databases and append to settings.php.
  255. foreach ($ini as $section => $site) {
  256. if ($section != 'Global' && (is_null($site_id) || $section == $site_id) && !empty($site['database-default-file'])) {
  257. // Restore database
  258. $sql_file = $tmp . '/' . $site['database-default-file'];
  259. if ($db_url = drush_get_option('db-url')) {
  260. if (empty($site_id) && count($ini) >= 3) {
  261. // TODO: Use drushrc to provide multiple db-urls for multi-restore?
  262. return drush_set_error('DRUSH_ARCHIVE_UNABLE_MULTI_RESTORE', dt('You must specify a site to restore when the archive contains multiple sites and a db-url is provided.'));
  263. }
  264. $db_spec = drush_convert_db_from_db_url($db_url);
  265. }
  266. else {
  267. $site_specification = $destination . '#' . $section;
  268. if ($return = drush_invoke_sitealias_args($site_specification, 'sql-conf', array(), array('all' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE))) {
  269. $databases = $return['object'];
  270. $db_spec = $databases['default']['default'];
  271. }
  272. else {
  273. return drush_set_error('DRUSH_ARCHIVE_UNABLE_DISCOVER_DB', dt('Unable to get database details from db-url option or settings.php', array('!key' => $key)));
  274. }
  275. }
  276. drush_sql_empty_db($db_spec);
  277. _drush_sql_query(NULL, $db_spec, $sql_file);
  278. // Append new DB info to settings.php.
  279. if ($db_url) {
  280. $settingsfile = $destination . '/' . $site['sitedir'] . '/settings.php';
  281. chmod($settingsfile, 0755); // Need to do something here or else we can't write.
  282. file_put_contents($settingsfile, "\n// Appended by drush archive-restore command.\n", FILE_APPEND);
  283. if (drush_drupal_major_version($destination) >= 7) {
  284. file_put_contents($settingsfile, "\n" . '$databases = ' . var_export(drush_sitealias_convert_db_from_db_url($db_url), TRUE) . ";\n", FILE_APPEND);
  285. }
  286. else {
  287. file_put_contents($settingsfile, "\n" . '$db_url = \'' . $db_url . "';\n", FILE_APPEND);
  288. }
  289. drush_log(dt('Drush appended the new database configuration at settings.php. Optionally remove the old configuration manually.'), 'ok');
  290. }
  291. }
  292. }
  293. drush_log(dt('Archive restored to !dest', array('!dest' => $destination)), 'ok');
  294. return $destination;
  295. }