1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-19 05:12:41 +01:00

Provide "--output" flags for "bin/storage renamespace"

Summary:
Ref T6996. Depends on D16407. This does the same stuff as D16407, but for `bin/storage renamespace`. In particular:

  - Support writing directly to a file (so we can get good errors on failure).
  - Support in-process compression.

Also add support for reading out of a `storage dump` subprocess, so we don't have to do a dump-to-disk + renamespace + compress dance and can just stream out of MySQL directly to a compressed file on disk.

This is used in the second stage of instance exports (see T7148).

It would be nice to share more code with `bin/storage dump`, and possibly to just make this a flag for it, although we still do need to do the file-based version when importing (vs exporting). I figured that was better left for another time.

Test Plan:
Ran `bin/storage renamespace --live --output x --from A --to B --compress --overwrite` and similar commands.

Verified that a compressed, renamespaced dump came out of the other end.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T6996

Differential Revision: https://secure.phabricator.com/D16410
This commit is contained in:
epriestley 2016-08-17 07:59:59 -07:00
parent 37b8ec5bb7
commit 2c5b1dc20a

View file

@ -8,15 +8,20 @@ final class PhabricatorStorageManagementRenamespaceWorkflow
->setName('renamespace')
->setExamples(
'**renamespace** [__options__] '.
'--in __dump.sql__ --from __old__ --to __new__ > __out.sql__')
'--input __dump.sql__ --from __old__ --to __new__ > __out.sql__')
->setSynopsis(pht('Change the database namespace of a .sql dump file.'))
->setArguments(
array(
array(
'name' => 'in',
'name' => 'input',
'param' => 'file',
'help' => pht('SQL dumpfile to process.'),
),
array(
'name' => 'live',
'help' => pht(
'Generate a live dump instead of processing a file on disk.'),
),
array(
'name' => 'from',
'param' => 'namespace',
@ -27,6 +32,21 @@ final class PhabricatorStorageManagementRenamespaceWorkflow
'param' => 'namespace',
'help' => pht('Desired database namespace for output.'),
),
array(
'name' => 'output',
'param' => 'file',
'help' => pht('Write output directly to a file on disk.'),
),
array(
'name' => 'compress',
'help' => pht('Emit gzipped output instead of plain text.'),
),
array(
'name' => 'overwrite',
'help' => pht(
'With __--output__, write to disk even if the file already '.
'exists.'),
),
));
}
@ -37,12 +57,13 @@ final class PhabricatorStorageManagementRenamespaceWorkflow
public function didExecute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$in = $args->getArg('in');
if (!strlen($in)) {
$input = $args->getArg('input');
$is_live = $args->getArg('live');
if (!strlen($input) && !$is_live) {
throw new PhutilArgumentUsageException(
pht(
'Specify the dumpfile to read with %s.',
'--in'));
'Specify the dumpfile to read with "--in", or use "--live" to '.
'generate one automatically.'));
}
$from = $args->getArg('from');
@ -61,6 +82,62 @@ final class PhabricatorStorageManagementRenamespaceWorkflow
'--to'));
}
$output_file = $args->getArg('output');
$is_overwrite = $args->getArg('overwrite');
$is_compress = $args->getArg('compress');
if ($is_overwrite) {
if ($output_file === null) {
throw new PhutilArgumentUsageException(
pht(
'The "--overwrite" flag can only be used alongside "--output".'));
}
}
if ($output_file !== null) {
if (Filesystem::pathExists($output_file)) {
if (!$is_overwrite) {
throw new PhutilArgumentUsageException(
pht(
'Output file "%s" already exists. Use "--overwrite" '.
'to overwrite.',
$output_file));
}
}
}
if ($is_live) {
$root = dirname(phutil_get_library_root('phabricator'));
$future = new ExecFuture(
'%R dump',
$root.'/bin/storage');
$lines = new LinesOfALargeExecFuture($future);
} else {
$lines = new LinesOfALargeFile($input);
}
if ($output_file === null) {
$file = fopen('php://stdout', 'wb');
$output_name = pht('stdout');
} else {
if ($is_compress) {
$file = gzopen($output_file, 'wb');
} else {
$file = fopen($output_file, 'wb');
}
$output_name = $output_file;
}
if (!$file) {
throw new Exception(
pht(
'Failed to open output file "%s" for writing.',
$output_name));
}
$patterns = array(
'use' => '@^(USE `)([^_]+)(_.*)$@',
'create' => '@^(CREATE DATABASE /\*.*?\*/ `)([^_]+)(_.*)$@',
@ -68,27 +145,66 @@ final class PhabricatorStorageManagementRenamespaceWorkflow
$found = array_fill_keys(array_keys($patterns), 0);
$matches = null;
foreach (new LinesOfALargeFile($in) as $line) {
try {
$matches = null;
foreach ($lines as $line) {
foreach ($patterns as $key => $pattern) {
if (preg_match($pattern, $line, $matches)) {
$namespace = $matches[2];
if ($namespace != $from) {
throw new Exception(
pht(
'Expected namespace "%s", found "%s": %s.',
$from,
$namespace,
$line));
foreach ($patterns as $key => $pattern) {
if (preg_match($pattern, $line, $matches)) {
$namespace = $matches[2];
if ($namespace != $from) {
throw new Exception(
pht(
'Expected namespace "%s", found "%s": %s.',
$from,
$namespace,
$line));
}
$line = $matches[1].$to.$matches[3];
$found[$key]++;
}
}
$line = $matches[1].$to.$matches[3];
$found[$key]++;
$data = $line."\n";
if ($is_compress) {
$bytes = gzwrite($file, $data);
} else {
$bytes = fwrite($file, $data);
}
if ($bytes !== strlen($data)) {
throw new Exception(
pht(
'Failed to write %d byte(s) to "%s".',
new PhutilNumber(strlen($data)),
$output_name));
}
}
echo $line."\n";
if ($is_compress) {
$ok = gzclose($file);
} else {
$ok = fclose($file);
}
if ($ok !== true) {
throw new Exception(
pht(
'Failed to close file "%s".',
$output_file));
}
} catch (Exception $ex) {
try {
if ($output_file !== null) {
Filesystem::remove($output_file);
}
} catch (Exception $ex) {
// Ignore any exception.
}
throw $ex;
}
// Give the user a chance to catch things if the results are crazy.