From c0848bca6d910db73f93fe9e118332d4e6ffec98 Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Wed, 8 Oct 2014 16:15:05 -0700 Subject: [PATCH] Allow bin/config to affect database configuration and migrate between local and database configuration Summary: Fixes T4018. Basically hits the bullet points in that task description except the "ideally" one. Test Plan: ran bin/config migrate and saw sensible output. ``` ~> ./bin/config migrate Migrating file-based config to more modern config... Skipping config of source type PhabricatorConfigDatabaseSource... Skipping config of source type PhabricatorConfigLocalSource... Skipping config of source type PhabricatorConfigDefaultSource... Done. Migrated 0 keys. ``` Reviewers: epriestley Reviewed By: epriestley Subscribers: hach-que, epriestley, Korvin Maniphest Tasks: T4018 Differential Revision: https://secure.phabricator.com/D10490 --- src/__phutil_library_map__.php | 2 + .../PhabricatorConfigIgnoreController.php | 5 +- .../config/editor/PhabricatorConfigEditor.php | 30 +++++++- ...bricatorConfigManagementDeleteWorkflow.php | 47 +++++++++--- ...PhabricatorConfigManagementGetWorkflow.php | 55 ++++++++++++-- ...ricatorConfigManagementMigrateWorkflow.php | 75 +++++++++++++++++++ ...PhabricatorConfigManagementSetWorkflow.php | 71 +++++++++++++----- .../PhabricatorApplicationEditController.php | 3 +- ...bricatorApplicationUninstallController.php | 5 +- 9 files changed, 251 insertions(+), 42 deletions(-) create mode 100644 src/applications/config/management/PhabricatorConfigManagementMigrateWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8604e87930..7fbb210ed3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1380,6 +1380,7 @@ phutil_register_library_map(array( 'PhabricatorConfigManagementDeleteWorkflow' => 'applications/config/management/PhabricatorConfigManagementDeleteWorkflow.php', 'PhabricatorConfigManagementGetWorkflow' => 'applications/config/management/PhabricatorConfigManagementGetWorkflow.php', 'PhabricatorConfigManagementListWorkflow' => 'applications/config/management/PhabricatorConfigManagementListWorkflow.php', + 'PhabricatorConfigManagementMigrateWorkflow' => 'applications/config/management/PhabricatorConfigManagementMigrateWorkflow.php', 'PhabricatorConfigManagementSetWorkflow' => 'applications/config/management/PhabricatorConfigManagementSetWorkflow.php', 'PhabricatorConfigManagementWorkflow' => 'applications/config/management/PhabricatorConfigManagementWorkflow.php', 'PhabricatorConfigOption' => 'applications/config/option/PhabricatorConfigOption.php', @@ -4342,6 +4343,7 @@ phutil_register_library_map(array( 'PhabricatorConfigManagementDeleteWorkflow' => 'PhabricatorConfigManagementWorkflow', 'PhabricatorConfigManagementGetWorkflow' => 'PhabricatorConfigManagementWorkflow', 'PhabricatorConfigManagementListWorkflow' => 'PhabricatorConfigManagementWorkflow', + 'PhabricatorConfigManagementMigrateWorkflow' => 'PhabricatorConfigManagementWorkflow', 'PhabricatorConfigManagementSetWorkflow' => 'PhabricatorConfigManagementWorkflow', 'PhabricatorConfigManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorConfigOption' => array( diff --git a/src/applications/config/controller/PhabricatorConfigIgnoreController.php b/src/applications/config/controller/PhabricatorConfigIgnoreController.php index 3261482aa6..a39685a6b5 100644 --- a/src/applications/config/controller/PhabricatorConfigIgnoreController.php +++ b/src/applications/config/controller/PhabricatorConfigIgnoreController.php @@ -59,7 +59,10 @@ final class PhabricatorConfigIgnoreController } PhabricatorConfigEditor::storeNewValue( - $config_entry, $list, $this->getRequest()); + $this->getRequest()->getUser(), + $config_entry, + $list, + PhabricatorContentSource::newFromRequest($this->getRequest())); } } diff --git a/src/applications/config/editor/PhabricatorConfigEditor.php b/src/applications/config/editor/PhabricatorConfigEditor.php index 5b0febb902..dc1b7b8c56 100644 --- a/src/applications/config/editor/PhabricatorConfigEditor.php +++ b/src/applications/config/editor/PhabricatorConfigEditor.php @@ -113,9 +113,10 @@ final class PhabricatorConfigEditor } public static function storeNewValue( + PhabricatorUser $user, PhabricatorConfigEntry $config_entry, $value, - AphrontRequest $request) { + PhabricatorContentSource $source) { $xaction = id(new PhabricatorConfigTransaction()) ->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT) @@ -126,9 +127,30 @@ final class PhabricatorConfigEditor )); $editor = id(new PhabricatorConfigEditor()) - ->setActor($request->getUser()) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request); + ->setActor($user) + ->setContinueOnNoEffect(true) + ->setContentSource($source); + + $editor->applyTransactions($config_entry, array($xaction)); + } + + public static function deleteConfig( + PhabricatorUser $user, + PhabricatorConfigEntry $config_entry, + PhabricatorContentSource $source) { + + $xaction = id(new PhabricatorConfigTransaction()) + ->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT) + ->setNewValue( + array( + 'deleted' => true, + 'value' => null, + )); + + $editor = id(new PhabricatorConfigEditor()) + ->setActor($user) + ->setContinueOnNoEffect(true) + ->setContentSource($source); $editor->applyTransactions($config_entry, array($xaction)); } diff --git a/src/applications/config/management/PhabricatorConfigManagementDeleteWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementDeleteWorkflow.php index 00dbec1180..d89d4b7eb3 100644 --- a/src/applications/config/management/PhabricatorConfigManagementDeleteWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementDeleteWorkflow.php @@ -7,9 +7,14 @@ final class PhabricatorConfigManagementDeleteWorkflow $this ->setName('delete') ->setExamples('**delete** __key__') - ->setSynopsis('Delete a local configuration value.') + ->setSynopsis(pht('Delete a local configuration value.')) ->setArguments( array( + array( + 'name' => 'database', + 'help' => pht('Delete configuration in the database instead of '. + 'in local configuration.'), + ), array( 'name' => 'args', 'wildcard' => true, @@ -22,28 +27,50 @@ final class PhabricatorConfigManagementDeleteWorkflow $argv = $args->getArg('args'); if (count($argv) == 0) { - throw new PhutilArgumentUsageException( - 'Specify a configuration key to delete.'); + throw new PhutilArgumentUsageException(pht( + 'Specify a configuration key to delete.')); } $key = $argv[0]; if (count($argv) > 1) { - throw new PhutilArgumentUsageException( - 'Too many arguments: expected one key.'); + throw new PhutilArgumentUsageException(pht( + 'Too many arguments: expected one key.')); } - $config = new PhabricatorConfigLocalSource(); + + $use_database = $args->getArg('database'); + if ($use_database) { + $config = new PhabricatorConfigDatabaseSource('default'); + $config_type = 'database'; + } else { + $config = new PhabricatorConfigLocalSource(); + $config_type = 'local'; + } $values = $config->getKeys(array($key)); if (!$values) { - throw new PhutilArgumentUsageException( - "Configuration key '{$key}' is not set in local configuration!"); + throw new PhutilArgumentUsageException(pht( + "Configuration key '%s' is not set in %s configuration!", + $key, + $config_type)); } - $config->deleteKeys(array($key)); + if ($use_database) { + $config_entry = id(new PhabricatorConfigOption()) + ->loadOneWhere( + 'namespace = %s and key = %s', + 'default', + $key); + PhabricatorConfigEditor::deleteConfig( + $this->getViewer(), + $config_entry, + PhabricatorContentSource::newConsoleSource()); + } else { + $config->deleteKeys(array($key)); + } $console->writeOut( - pht("Deleted '%s' from local configuration.", $key)."\n"); + pht("Deleted '%s' from %s configuration.", $key, $config_type)."\n"); } } diff --git a/src/applications/config/management/PhabricatorConfigManagementGetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementGetWorkflow.php index 09f0b95838..9ccb26af0d 100644 --- a/src/applications/config/management/PhabricatorConfigManagementGetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementGetWorkflow.php @@ -40,15 +40,60 @@ final class PhabricatorConfigManagementGetWorkflow "keys."); } + $values = array(); $config = new PhabricatorConfigLocalSource(); - $values = $config->getKeys(array($key)); + $local_value = $config->getKeys(array($key)); + if (empty($local_value)) { + $values['local'] = array( + 'key' => $key, + 'value' => null, + 'status' => 'unset', + 'errorInfo' => null, + ); + } else { + $values['local'] = array( + 'key' => $key, + 'value' => reset($local_value), + 'status' => 'set', + 'errorInfo' => null, + ); + } + + $database_config = new PhabricatorConfigDatabaseSource('default'); + try { + $database_value = $database_config->getKeys(array($key)); + if (empty($database_value)) { + $values['database'] = array( + 'key' => $key, + 'value' => null, + 'status' => 'unset', + 'errorInfo' => null, + ); + } else { + $values['database'] = array( + 'key' => $key, + 'value' => reset($database_value), + 'status' => 'set', + 'errorInfo' => null, + ); + } + } catch (Exception $e) { + $values['database'] = array( + 'key' => $key, + 'value' => null, + 'status' => 'error', + 'errorInfo' => pht('Database source is not configured properly'), + ); + } $result = array(); - foreach ($values as $key => $value) { + foreach ($values as $source => $value) { $result[] = array( - 'key' => $key, - 'source' => 'local', - 'value' => $value, + 'key' => $value['key'], + 'source' => $source, + 'value' => $value['value'], + 'status' => $value['status'], + 'errorInfo' => $value['errorInfo'], ); } $result = array( diff --git a/src/applications/config/management/PhabricatorConfigManagementMigrateWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementMigrateWorkflow.php new file mode 100644 index 0000000000..0dcf6e8ee8 --- /dev/null +++ b/src/applications/config/management/PhabricatorConfigManagementMigrateWorkflow.php @@ -0,0 +1,75 @@ +setName('migrate') + ->setExamples('**migrate**') + ->setSynopsis(pht( + 'Migrate file-based configuration to more modern storage.')); + } + + public function execute(PhutilArgumentParser $args) { + $console = PhutilConsole::getConsole(); + $key_count = 0; + + $options = PhabricatorApplicationConfigOptions::loadAllOptions(); + $local_config = new PhabricatorConfigLocalSource(); + $database_config = new PhabricatorConfigDatabaseSource('default'); + $config_sources = PhabricatorEnv::getConfigSourceStack()->getStack(); + $console->writeOut( + pht('Migrating file-based config to more modern config...')."\n"); + foreach ($config_sources as $config_source) { + if (!($config_source instanceof PhabricatorConfigFileSource)) { + $console->writeOut( + pht('Skipping config of source type %s...', + get_class($config_source))."\n"); + continue; + } + $console->writeOut(pht('Migrating file source...')."\n"); + $all_keys = $config_source->getAllKeys(); + foreach ($all_keys as $key => $value) { + $option = idx($options, $key); + if (!$option) { + $console->writeOut(pht('Skipping obsolete option: %s', $key)."\n"); + continue; + } + $in_local = $local_config->getKeys(array($option->getKey())); + if ($in_local) { + $console->writeOut(pht( + 'Skipping option "%s"; already in local config.', $key)."\n"); + continue; + } + $is_locked = $option->getLocked(); + if ($is_locked) { + $local_config->setKeys(array($option->getKey() => $value)); + $key_count++; + $console->writeOut(pht( + 'Migrated option "%s" from file to local config.', $key)."\n"); + } else { + $in_database = $database_config->getKeys(array($option->getKey())); + if ($in_database) { + $console->writeOut(pht( + 'Skipping option "%s"; already in database config.', $key)."\n"); + continue; + } else { + PhabricatorConfigEditor::deleteConfig( + $this->getViewer(), + $option, + PhabricatorContentSource::newFromConsole()); + $key_count++; + $console->writeOut(pht( + 'Migrated option "%s" from file to local config.', $key)."\n"); + } + } + } + } + + $console->writeOut(pht( + 'Done. Migrated %d keys.', $key_count)."\n"); + return 0; + } + +} diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index 46464c6929..5c4fc2765c 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -7,9 +7,14 @@ final class PhabricatorConfigManagementSetWorkflow $this ->setName('set') ->setExamples('**set** __key__ __value__') - ->setSynopsis('Set a local configuration value.') + ->setSynopsis(pht('Set a local configuration value.')) ->setArguments( array( + array( + 'name' => 'database', + 'help' => pht('Update configuration in the database instead of '. + 'in local configuration.'), + ), array( 'name' => 'args', 'wildcard' => true, @@ -21,29 +26,31 @@ final class PhabricatorConfigManagementSetWorkflow $console = PhutilConsole::getConsole(); $argv = $args->getArg('args'); if (count($argv) == 0) { - throw new PhutilArgumentUsageException( - 'Specify a configuration key and a value to set it to.'); + throw new PhutilArgumentUsageException(pht( + 'Specify a configuration key and a value to set it to.')); } $key = $argv[0]; if (count($argv) == 1) { - throw new PhutilArgumentUsageException( - "Specify a value to set the key '{$key}' to."); + throw new PhutilArgumentUsageException(pht( + "Specify a value to set the key '%s' to.", + $key)); } $value = $argv[1]; if (count($argv) > 2) { - throw new PhutilArgumentUsageException( - 'Too many arguments: expected one key and one value.'); + throw new PhutilArgumentUsageException(pht( + 'Too many arguments: expected one key and one value.')); } $options = PhabricatorApplicationConfigOptions::loadAllOptions(); if (empty($options[$key])) { - throw new PhutilArgumentUsageException( - "No such configuration key '{$key}'! Use `config list` to list all ". - "keys."); + throw new PhutilArgumentUsageException(pht( + "No such configuration key '%s'! Use `config list` to list all ". + "keys.", + $key)); } $option = $options[$key]; @@ -57,8 +64,10 @@ final class PhabricatorConfigManagementSetWorkflow break; case 'int': if (!ctype_digit($value)) { - throw new PhutilArgumentUsageException( - "Config key '{$key}' is of type '{$type}'. Specify an integer."); + throw new PhutilArgumentUsageException(pht( + "Config key '%s' is of type '%s'. Specify an integer.", + $key, + $type)); } $value = (int)$value; break; @@ -68,19 +77,30 @@ final class PhabricatorConfigManagementSetWorkflow } else if ($value == 'false') { $value = false; } else { - throw new PhutilArgumentUsageException( - "Config key '{$key}' is of type '{$type}'. ". - "Specify 'true' or 'false'."); + throw new PhutilArgumentUsageException(pht( + "Config key '%s' is of type '%s'. ". + "Specify 'true' or 'false'.", + $key, + $type)); } break; default: $value = json_decode($value, true); if (!is_array($value)) { - throw new PhutilArgumentUsageException( - "Config key '{$key}' is of type '{$type}'. Specify it in JSON."); + throw new PhutilArgumentUsageException(pht( + "Config key '%s' is of type '%s'. Specify it in JSON.", + $key, + $type)); } break; } + $use_database = $args->getArg('database'); + if ($option->getLocked() && $use_database) { + throw new PhutilArgumentUsageException(pht( + "Config key '%s' is locked and can only be set in local ". + 'configuration.', + $key)); + } try { $option->getGroup()->validateOption($option, $value); @@ -89,11 +109,22 @@ final class PhabricatorConfigManagementSetWorkflow throw new PhutilArgumentUsageException($validation->getMessage()); } - $config = new PhabricatorConfigLocalSource(); - $config->setKeys(array($key => $value)); + if ($use_database) { + $config_type = 'database'; + PhabricatorConfigEditor::storeNewValue( + $this->getViewer(), + id(new PhabricatorConfigEntry()) + ->loadOneWhere('namespace = %s AND key = %s', 'default', $key), + $value, + PhabricatorContentSource::newConsoleSource()); + } else { + $config_type = 'local'; + id(new PhabricatorConfigLocalSource()) + ->setKeys(array($key => $value)); + } $console->writeOut( - pht("Set '%s' in local configuration.", $key)."\n"); + pht("Set '%s' in %s configuration.", $key, $config_type)."\n"); } } diff --git a/src/applications/meta/controller/PhabricatorApplicationEditController.php b/src/applications/meta/controller/PhabricatorApplicationEditController.php index 8f7419b14b..c56d0a83e9 100644 --- a/src/applications/meta/controller/PhabricatorApplicationEditController.php +++ b/src/applications/meta/controller/PhabricatorApplicationEditController.php @@ -104,9 +104,10 @@ final class PhabricatorApplicationEditController PhabricatorPolicyCapability::CAN_EDIT); PhabricatorConfigEditor::storeNewValue( + $user, $config_entry, $value, - $this->getRequest()); + PhabricatorContentSource::newFromRequest($this->getRequest())); } return id(new AphrontRedirectResponse())->setURI($view_uri); diff --git a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php index b8f9ce413c..883d1d6ba0 100644 --- a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php +++ b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php @@ -90,7 +90,10 @@ final class PhabricatorApplicationUninstallController } PhabricatorConfigEditor::storeNewValue( - $config_entry, $list, $this->getRequest()); + $this->getRequest()->getUser(), + $config_entry, + $list, + PhabricatorContentSource::newFromRequest($this->getRequest())); } }