Improve schema upgrade workflow for unprivileged users
Summary: In a basically reasonable configuration where you connect with a non-privileged user from the web workflow, upgrade_schema.php won't have enough privileges. Allow the user to override the normal auth with -u and -p. Test Plan: Tried to do a schema upgrade with an underprivileged user, got a useful error message instead of garbage. Reviewed By: Girish Reviewers: Girish, davidrecordon, jungejason, tuomaspelkonen, aran CC: aran, epriestley, Girish Differential Revision: 191
This commit is contained in:
@@ -25,14 +25,14 @@ phutil_require_module('phutil', 'console');
|
||||
|
||||
define('SCHEMA_VERSION_TABLE_NAME', 'schema_version');
|
||||
|
||||
if (isset($argv[1]) && !is_numeric($argv[1])) {
|
||||
print
|
||||
"USAGE: ./update_schema.php [first_patch_version]\n\n".
|
||||
"run './update_schema.php 12' to apply all patches starting from ".
|
||||
"version 12.\n".
|
||||
"run './update_schema.php' to apply all patches that are new since\n".
|
||||
"the last time this script was run\n\n";
|
||||
exit(0);
|
||||
$options = getopt('v:u:p:') + array(
|
||||
'v' => null,
|
||||
'u' => null,
|
||||
'p' => null,
|
||||
);
|
||||
|
||||
if ($options['v'] && !is_numeric($options['v'])) {
|
||||
usage();
|
||||
}
|
||||
|
||||
echo phutil_console_wrap(
|
||||
@@ -45,113 +45,141 @@ if (!phutil_console_confirm('Are you ready to continue?')) {
|
||||
}
|
||||
|
||||
// Use always the version from the commandline if it is defined
|
||||
$next_version = isset($argv[1]) ? (int)$argv[1] : null;
|
||||
$next_version = isset($options['v']) ? (int)$options['v'] : null;
|
||||
|
||||
// Dummy class needed for creating our database
|
||||
class DummyUser extends PhabricatorLiskDAO {
|
||||
public function getApplicationName() {
|
||||
return 'user';
|
||||
}
|
||||
if ($options['u']) {
|
||||
$conn_user = $options['u'];
|
||||
$conn_pass = $options['p'];
|
||||
} else {
|
||||
$conn_user = PhabricatorEnv::getEnvConfig('mysql.user');
|
||||
$conn_pass = PhabricatorEnv::getEnvConfig('mysql.pass');
|
||||
}
|
||||
$conn_host = PhabricatorEnv::getEnvConfig('mysql.host');
|
||||
|
||||
// Class needed for setting up the actual SQL connection
|
||||
class PhabricatorSchemaVersion extends PhabricatorLiskDAO {
|
||||
public function getApplicationName() {
|
||||
return 'meta_data';
|
||||
}
|
||||
}
|
||||
$conn = new AphrontMySQLDatabaseConnection(
|
||||
array(
|
||||
'user' => $conn_user,
|
||||
'pass' => $conn_pass,
|
||||
'host' => $conn_host,
|
||||
'database' => null,
|
||||
));
|
||||
|
||||
// Connect to 'phabricator_user' db first to create our db
|
||||
$conn = id(new DummyUser())->establishConnection('w');
|
||||
$create_sql = <<<END
|
||||
CREATE DATABASE IF NOT EXISTS `phabricator_meta_data`;
|
||||
try {
|
||||
|
||||
$create_sql = <<<END
|
||||
CREATE DATABASE IF NOT EXISTS `phabricator_meta_data`;
|
||||
END;
|
||||
queryfx($conn, $create_sql);
|
||||
queryfx($conn, $create_sql);
|
||||
|
||||
// 'phabricator_meta_data' database exists, let's connect to it now
|
||||
$conn = id(new PhabricatorSchemaVersion())->establishConnection('w');
|
||||
$create_sql = <<<END
|
||||
CREATE TABLE IF NOT EXISTS phabricator_meta_data.`schema_version` (
|
||||
`version` INTEGER not null
|
||||
);
|
||||
$create_sql = <<<END
|
||||
CREATE TABLE IF NOT EXISTS phabricator_meta_data.`schema_version` (
|
||||
`version` INTEGER not null
|
||||
);
|
||||
END;
|
||||
queryfx($conn, $create_sql);
|
||||
queryfx($conn, $create_sql);
|
||||
|
||||
// Get the version only if commandline argument wasn't given
|
||||
if ($next_version === null) {
|
||||
$version = queryfx_one(
|
||||
$conn,
|
||||
'SELECT * FROM %T',
|
||||
SCHEMA_VERSION_TABLE_NAME);
|
||||
// Get the version only if commandline argument wasn't given
|
||||
if ($next_version === null) {
|
||||
$version = queryfx_one(
|
||||
$conn,
|
||||
'SELECT * FROM phabricator_meta_data.%T',
|
||||
SCHEMA_VERSION_TABLE_NAME);
|
||||
|
||||
if (!$version) {
|
||||
print "*** No version information in the database ***\n";
|
||||
print "*** Give the first patch version which to ***\n";
|
||||
print "*** apply as the command line argument ***\n";
|
||||
exit(-1);
|
||||
if (!$version) {
|
||||
print "*** No version information in the database ***\n";
|
||||
print "*** Give the first patch version which to ***\n";
|
||||
print "*** apply as the command line argument ***\n";
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
$next_version = $version['version'] + 1;
|
||||
}
|
||||
|
||||
$next_version = $version['version'] + 1;
|
||||
}
|
||||
// Find the patch files
|
||||
$patches_dir = $root.'/resources/sql/patches/';
|
||||
$finder = id(new FileFinder($patches_dir))
|
||||
->withSuffix('sql');
|
||||
$results = $finder->find();
|
||||
|
||||
// Find the patch files
|
||||
$patches_dir = $root.'/resources/sql/patches/';
|
||||
$finder = id(new FileFinder($patches_dir))
|
||||
->withSuffix('sql');
|
||||
$results = $finder->find();
|
||||
|
||||
$patches = array();
|
||||
foreach ($results as $r) {
|
||||
$matches = array();
|
||||
if (preg_match('/(\d+)\..*\.sql$/', $r, $matches)) {
|
||||
$patches[] = array('version' => (int)$matches[1],
|
||||
'file' => $r);
|
||||
} else {
|
||||
print
|
||||
"*** WARNING : File {$r} does not follow the normal naming ".
|
||||
"convention. ***\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Files are in some 'random' order returned by the operating system
|
||||
// We need to apply them in proper order
|
||||
$patches = isort($patches, 'version');
|
||||
|
||||
$patch_applied = false;
|
||||
foreach ($patches as $patch) {
|
||||
if ($patch['version'] < $next_version) {
|
||||
continue;
|
||||
$patches = array();
|
||||
foreach ($results as $r) {
|
||||
$matches = array();
|
||||
if (preg_match('/(\d+)\..*\.sql$/', $r, $matches)) {
|
||||
$patches[] = array('version' => (int)$matches[1],
|
||||
'file' => $r);
|
||||
} else {
|
||||
print
|
||||
"*** WARNING : File {$r} does not follow the normal naming ".
|
||||
"convention. ***\n";
|
||||
}
|
||||
}
|
||||
|
||||
print "Applying patch {$patch['file']}\n";
|
||||
// Files are in some 'random' order returned by the operating system
|
||||
// We need to apply them in proper order
|
||||
$patches = isort($patches, 'version');
|
||||
|
||||
$path = Filesystem::resolvePath($patches_dir.$patch['file']);
|
||||
$patch_applied = false;
|
||||
foreach ($patches as $patch) {
|
||||
if ($patch['version'] < $next_version) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$user = PhabricatorEnv::getEnvConfig('mysql.user');
|
||||
$pass = PhabricatorEnv::getEnvConfig('mysql.pass');
|
||||
$host = PhabricatorEnv::getEnvConfig('mysql.host');
|
||||
print "Applying patch {$patch['file']}\n";
|
||||
|
||||
list($stdout, $stderr) = execx(
|
||||
"mysql --user=%s --password=%s --host=%s < %s",
|
||||
$user, $pass, $host, $path);
|
||||
$path = Filesystem::resolvePath($patches_dir.$patch['file']);
|
||||
|
||||
if ($stderr) {
|
||||
print $stderr;
|
||||
exit(-1);
|
||||
list($stdout, $stderr) = execx(
|
||||
"mysql --user=%s --password=%s --host=%s < %s",
|
||||
$conn_user,
|
||||
$conn_pass,
|
||||
$conn_host,
|
||||
$path);
|
||||
|
||||
if ($stderr) {
|
||||
print $stderr;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// Patch was successful, update the db with the latest applied patch version
|
||||
// 'DELETE' and 'INSERT' instead of update, because the table might be empty
|
||||
queryfx(
|
||||
$conn,
|
||||
'DELETE FROM phabricator_meta_data.%T',
|
||||
SCHEMA_VERSION_TABLE_NAME);
|
||||
queryfx(
|
||||
$conn,
|
||||
'INSERT INTO phabricator_meta_data.%T VALUES (%d)',
|
||||
SCHEMA_VERSION_TABLE_NAME,
|
||||
$patch['version']);
|
||||
|
||||
$patch_applied = true;
|
||||
}
|
||||
|
||||
// Patch was successful, update the db with the latest applied patch version
|
||||
// 'DELETE' and 'INSERT' instead of update, because the table might be empty
|
||||
queryfx($conn, 'DELETE FROM %T', SCHEMA_VERSION_TABLE_NAME);
|
||||
queryfx(
|
||||
$conn,
|
||||
'INSERT INTO %T values (%d)',
|
||||
SCHEMA_VERSION_TABLE_NAME,
|
||||
$patch['version']);
|
||||
if (!$patch_applied) {
|
||||
print "Your database is already up-to-date.\n";
|
||||
}
|
||||
|
||||
$patch_applied = true;
|
||||
} catch (AphrontQueryAccessDeniedException $ex) {
|
||||
echo
|
||||
"ACCESS DENIED\n".
|
||||
"The user '{$conn_user}' does not have sufficient MySQL privileges to\n".
|
||||
"execute the schema upgrade. Use the -u and -p flags to run as a user\n".
|
||||
"with more privileges (e.g., root).".
|
||||
"\n\n".
|
||||
"EXCEPTION:\n".
|
||||
$ex->getMessage().
|
||||
"\n\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!$patch_applied) {
|
||||
print "Your database is already up-to-date.\n";
|
||||
function usage() {
|
||||
echo
|
||||
"usage: upgrade_schema.php [-v version] [-u user -p pass]".
|
||||
"\n\n".
|
||||
"Run 'upgrade_schema.php -v 12' to apply all patches starting from ".
|
||||
"version 12.\n".
|
||||
"Run 'upgrade_schema.php -u root -p hunter2' to override the configured ".
|
||||
"default user.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user