diff --git a/commands/core/archive.drush.inc b/commands/core/archive.drush.inc index 6df13c2..bb8cf7b 100644 --- a/commands/core/archive.drush.inc +++ b/commands/core/archive.drush.inc @@ -21,6 +21,7 @@ function archive_drush_command() { 'generatorversion' => 'The generator version number to store in the MANIFEST file. The default is ' . DRUSH_VERSION . '.', 'pipe' => 'Only print the destination of the archive. Useful for scripts that don\'t pass --destination.', 'preserve-symlinks' => 'Preserve symbolic links.', + 'no-core' => 'Exclude Drupal core, so include just site specific stuff.', ), 'examples' => array( 'drush archive-dump default,example.com,foo.com' => 'Write an archive containing 3 sites in it.', @@ -65,6 +66,12 @@ function drush_archive_dump($sites_subdirs = '@self') { $tar = drush_get_tar_executable(); $sites = array(); $aliases = drush_sitealias_resolve_sitespecs(explode(',', $sites_subdirs)); + + $include_platform = TRUE; + if (drush_get_option('no-core', FALSE)) { + $include_platform = FALSE; + } + foreach ($aliases as $key => $alias) { $sites[$key] = $alias; if (($db_record = sitealias_get_databases_from_record($alias))) { @@ -163,14 +170,17 @@ function drush_archive_dump($sites_subdirs = '@self') { $docroot_path = realpath(drush_get_context('DRUSH_DRUPAL_ROOT')); $docroot = basename($docroot_path); $workdir = dirname($docroot_path); - $dereference = (drush_get_option('preserve-symlinks', FALSE)) ? '' : '--dereference '; - // Archive Drupal core, excluding sites dir. - drush_shell_cd_and_exec($workdir, "$tar --exclude \"{$docroot}/sites\" {$dereference}-cf %s %s", $destination, $docroot); - // Add sites/all to the same archive. - drush_shell_cd_and_exec($workdir, "$tar {$dereference}-rf %s %s", $destination, "{$docroot}/sites/all"); - // Add sites/sites.php to the same archive. - if (file_exists("{$docroot}/sites/sites.php")) { - drush_shell_cd_and_exec($workdir, "$tar {$dereference}-rf %s %s", $destination, "{$docroot}/sites/sites.php"); + + if ($include_platform) { + $dereference = (drush_get_option('preserve-symlinks', FALSE)) ? '' : '--dereference '; + // Archive Drupal core, excluding sites dir. + drush_shell_cd_and_exec($workdir, "$tar --exclude \"{$docroot}/sites\" {$dereference}-cf %s %s", $destination, $docroot); + // Add sites/all to the same archive. + drush_shell_cd_and_exec($workdir, "$tar {$dereference}-rf %s %s", $destination, "{$docroot}/sites/all"); + // Add sites/sites.php to the same archive. + if (file_exists("{$docroot}/sites/sites.php")) { + drush_shell_cd_and_exec($workdir, "$tar {$dereference}-rf %s %s", $destination, "{$docroot}/sites/sites.php"); + } } $tmp = drush_tempdir(); @@ -205,6 +215,7 @@ function drush_archive_dump($sites_subdirs = '@self') { 'generatorversion' => drush_get_option('generatorversion', DRUSH_VERSION), 'description' => drush_get_option('description', ''), 'tags' => drush_get_option('tags', ''), + 'archive_format' => ($include_platform ? 'platform' : 'site'), ); $contents = drush_export_ini(array('Global' => $platform)); @@ -285,25 +296,7 @@ function drush_archive_restore($file, $site_id = NULL) { return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_EXTRACT', dt('Unable to extract site archive tarball to !tmp.', array('!tmp' => $tmp))); } - $manifest = $tmp . '/MANIFEST.ini'; - if (file_exists($manifest)) { - if (!$ini = parse_ini_file($manifest, TRUE)) { - return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_PARSE_MANIFEST', dt('Unable to parse MANIFEST.ini in the archive.')); - } - } - else { - // No manifest. Try to find docroot and DB dump file. - $db_file = drush_scan_directory($tmp, '/\.sql$/', array('.', '..', 'CVS'), 0, 0); - $directories = glob($tmp . '/*' , GLOB_ONLYDIR); - $ini = array( - 'Global' => array(), - 'default' => array( - 'docroot' => reset($directories), - 'sitedir' => 'sites/default', - 'database-default-file' => key($db_file), - ), - ); - } + $ini = drush_archive_read_manifest($tmp); // Grab the first site in the Manifest and move docroot to destination. $ini_tmp = $ini; @@ -311,13 +304,27 @@ function drush_archive_restore($file, $site_id = NULL) { $first = array_shift($ini_tmp); $docroot = basename($first['docroot']); $destination = drush_get_option('destination', realpath('.') . "/$docroot"); - if (!drush_move_dir("$tmp/$docroot", $destination, drush_get_option('overwrite'))) { - return drush_set_error('DRUSH_ARCHIVE_UNABLE _RESTORE_FILES', dt('Unable to restore files to !dest', array('!dest' => $destination))); + + if ($ini['Global']['archive_format'] == 'platform') { + // Move the whole platform inplace at once. + if (!drush_move_dir("$tmp/$docroot", $destination, drush_get_option('overwrite'))) { + return drush_set_error('DRUSH_ARCHIVE_UNABLE _RESTORE_FILES', dt('Unable to restore platform to !dest', array('!dest' => $destination))); + } + } else { + // When no platform is included we do this on a per-site basis + } // Loop over sites and restore databases and append to settings.php. foreach ($ini as $section => $site) { if ($section != 'Global' && (is_null($site_id) || $section == $site_id) && !empty($site['database-default-file'])) { + $site_destination = $destination . '/' . $site['sitedir']; + // Restore site, incase not already done above + if ($ini['Global']['archive_format'] == 'site') { + if (!drush_move_dir("$tmp/$docroot/" . $site['sitedir'], $site_destination, drush_get_option('overwrite'))) { + return drush_set_error('DRUSH_ARCHIVE_UNABLE _RESTORE_FILES', dt('Unable to restore site to !dest', array('!dest' => $site_destination))); + } + } // Restore database $sql_file = $tmp . '/' . $site['database-default-file']; @@ -343,7 +350,7 @@ function drush_archive_restore($file, $site_id = NULL) { // Append new DB info to settings.php. if ($db_url) { - $settingsfile = $destination . '/' . $site['sitedir'] . '/settings.php'; + $settingsfile = $site_destination . '/settings.php'; chmod($settingsfile, 0755); // Need to do something here or else we can't write. file_put_contents($settingsfile, "\n// Appended by drush archive-restore command.\n", FILE_APPEND); if (drush_drupal_major_version($destination) >= 7) { @@ -380,3 +387,49 @@ function archive_archive_restore_complete() { ), ); } + +/** + * Read and parse the MANIFEST from an extracted archive + * + * @param string $path The location of theextracted archive + * @return array The manifest data + */ +function drush_archive_read_manifest($path) { + $manifest = $path . '/MANIFEST.ini'; + if (file_exists($manifest)) { + if (!$ini = parse_ini_file($manifest, TRUE)) { + return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_PARSE_MANIFEST', dt('Unable to parse MANIFEST.ini in the archive.')); + } + } + else { + // No manifest. Try to find docroot and DB dump file. + $db_file = drush_scan_directory($path, '/\.sql$/', array('.', '..', 'CVS'), 0, 0); + + if (file_exists($path . '/index.php')) { + $docroot = './'; + } + else { + $directories = glob($path . '/*' , GLOB_ONLYDIR); + $docroot = reset($directories); + } + + $ini = array( + 'Global' => array( + // Very crude detection of a platform... + 'archive_format' => (file_exists($directories . '/modules') ? 'platform' : 'site'), + ), + 'default' => array( + 'docroot' => $docroot, + 'sitedir' => 'sites/default', + 'database-default-file' => key($db_file), + ), + ); + } + + // Format compatibility, for 1.0 this option initially did not exist. + if (!isset( $ini['Global']['archive_format'])) { + $ini['Global']['archive_format'] = 'platform'; + } + + return $ini; +} diff --git a/docs/site-archive-format.html b/docs/site-archive-format.html new file mode 100644 index 0000000..31d8517 --- /dev/null +++ b/docs/site-archive-format.html @@ -0,0 +1,81 @@ +

Site Archive Format specification

+ +version: 1.0 + +

Drush has the commands archive-dump and archive-restore to store a site in an archive file.

+

Discussion: http://groups.drupal.org/site-archive-format

+ +

Tree structure

+ +Inside the archive is a directory structure. + + + + +

MANIFEST.ini format

+ +Example MANIFEST.ini: +
+[Global]
+datestamp = "1317033628"
+formatversion = "1.0"
+generator = "Drush archive-dump"
+generatorversion = "5.0-dev"
+archive_format = "platform"
+
+[dev]
+docroot = "/tmp/drush-sandbox/web"
+sitedir = "sites/dev"
+files-public = "sites/dev/files"
+database-default-file = "./unish_dev.sql"
+database-default-driver = "mysql"
+
+@TODO: List possible properties and their possible values, are there any missing? +
+
+
+ +

Sections

+ + + +

Properties for the "Global" section

+
+
formatversion
The version of the Site Archive Format specification used to generate this archive
+
generator
+
generatorversion
+
archive_format
Site or platform
+
datestamp
The creation date of the archive
+
+
+ + +

Properties for the site specific section

+
+
docroot
+
sitedir
+
files-public
+
database-default-file
Filename holding the data, e.g. ./sitename.sql
+
database-default-driver
E.g. mysql
+
+
+ + diff --git a/tests/archiveDumpTest.php b/tests/archiveDumpTest.php index 8d0d995..97886a1 100644 --- a/tests/archiveDumpTest.php +++ b/tests/archiveDumpTest.php @@ -37,10 +37,96 @@ class archiveDumpCase extends Drush_CommandTestCase { $tar = self::get_tar_executable(); $exec = sprintf("mkdir %s && cd %s && $tar -xzf %s%s%s", $untar_dest, $untar_dest, UNISH_SANDBOX, DIRECTORY_SEPARATOR, $dump_dest); $this->execute($exec); + + if (strpos(UNISH_DB_URL, 'mysql') !== FALSE) { + $this->execute(sprintf('head %s/unish_%s.sql | grep "MySQL dump"', $untar_dest, $uri)); + } + $this->assertFileExists($untar_dest . '/MANIFEST.ini', 'MANIFEST.ini should exist'); + $this->execute('test -d ' . $untar_dest . '/' . $docroot); + + // Cleanup + unish_file_delete_recursive($untar_dest); + unish_file_delete_recursive(UNISH_SANDBOX . '/' . $dump_dest); + } + + /* + * Test dump and extraction with --no-core. + */ + public function testArchiveDumpNoCore() { + $sites = $this->setUpDrupal(1, TRUE); + $site = reset($sites); + $root = $this->webroot(); + $uri = key($sites); + $docroot = basename($root); + + $dump_dest = "dump.tar.gz"; + $options = array( + 'root' => $root, + 'uri' => $uri, + 'yes' => NULL, + 'no-core' => TRUE, + 'destination' => $dump_dest, + ); + $this->drush('archive-dump', array($uri), $options); + $exec = sprintf('file %s/%s', UNISH_SANDBOX, $dump_dest); + $this->execute($exec); + $output = $this->getOutput(); + $expected = UNISH_SANDBOX . "/dump.tar.gz: gzip compressed data, from Unix"; + $this->assertEquals($expected, $output); + + // Untar it, make sure it looks right. + $untar_dest = UNISH_SANDBOX . '/untar'; + $exec = sprintf('mkdir %s && cd %s && tar xzf %s/%s', $untar_dest, $untar_dest, UNISH_SANDBOX, $dump_dest); + $this->execute($exec); + if (strpos(UNISH_DB_URL, 'mysql') !== FALSE) { $this->execute(sprintf('head %s/unish_%s.sql | grep "MySQL dump"', $untar_dest, $uri)); } $this->assertFileExists($untar_dest . '/MANIFEST.ini', 'MANIFEST.ini should exist'); $this->execute('test -d ' . $untar_dest . '/' . $docroot); + $this->assertFileNotExists($untar_dest . '/' . $docroot . '/modules', 'No modules directory should exist with --no-core'); + + // Cleanup + unish_file_delete_recursive($untar_dest); + unish_file_delete_recursive(UNISH_SANDBOX . '/' . $dump_dest); + } + + + /* + * Test dump and extraction with --no-core. + */ + public function testArchiveRestoreNoCore() { + $sites = $this->setUpDrupal(1, TRUE); + $site = reset($sites); + $root = $this->webroot(); + $uri = key($sites); + $docroot = basename($root); + + $dump_dest = "dump.tar.gz"; + $dump_dest_path = UNISH_SANDBOX . '/' . $dump_dest ; + $options = array( + 'root' => $root, + 'uri' => $uri, + 'yes' => NULL, + 'no-core' => TRUE, // <<-- The option being tested here + 'destination' => $dump_dest, + ); + $this->drush('archive-dump', array($uri), $options); + + unish_file_delete_recursive($root . '/sites/dev'); + $this->assertFileNotExists($root . '/sites/dev/settings.php', 'settings.php should have been removed before the restore'); + $options = array( + 'overwrite' => TRUE, + ); + + // Do the restore + $this->drush('archive-restore',array($dump_dest_path), $options); + + // Verify + $this->assertFileExists($root . '/index.php', 'index.php should still exist'); + $this->assertFileExists($root . '/sites/dev/settings.php', 'settings.php should have been recreated by the restore'); + + // Cleanup + unish_file_delete_recursive(UNISH_SANDBOX . '/' . $dump_dest); } }