mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-22 21:40:55 +01:00
Provide a simple read-only maintenance mode for repositories
Summary: Ref T13393. While doing a shard migration in the Phacility cluster, we'd like to stop writes to the migrating repository. It's safe to continue serving reads. Add a simple maintenance mode for making repositories completely read-only during maintenance. Test Plan: Put a repository into read-only mode, tried to write via HTTP + SSH. Viewed web UI. Took it back out of maintenance mode. Maniphest Tasks: T13393 Differential Revision: https://secure.phabricator.com/D20748
This commit is contained in:
parent
c6642213d5
commit
3c26e38487
8 changed files with 223 additions and 5 deletions
|
@ -4470,6 +4470,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryIdentityTransaction' => 'applications/repository/storage/PhabricatorRepositoryIdentityTransaction.php',
|
||||
'PhabricatorRepositoryIdentityTransactionQuery' => 'applications/repository/query/PhabricatorRepositoryIdentityTransactionQuery.php',
|
||||
'PhabricatorRepositoryIdentityTransactionType' => 'applications/repository/xaction/PhabricatorRepositoryIdentityTransactionType.php',
|
||||
'PhabricatorRepositoryMaintenanceTransaction' => 'applications/repository/xaction/PhabricatorRepositoryMaintenanceTransaction.php',
|
||||
'PhabricatorRepositoryManagementCacheWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php',
|
||||
'PhabricatorRepositoryManagementClusterizeWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementClusterizeWorkflow.php',
|
||||
'PhabricatorRepositoryManagementDiscoverWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php',
|
||||
|
@ -4478,6 +4479,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryManagementListPathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListPathsWorkflow.php',
|
||||
'PhabricatorRepositoryManagementListWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php',
|
||||
'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementLookupUsersWorkflow.php',
|
||||
'PhabricatorRepositoryManagementMaintenanceWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMaintenanceWorkflow.php',
|
||||
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php',
|
||||
'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkReachableWorkflow.php',
|
||||
'PhabricatorRepositoryManagementMirrorWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php',
|
||||
|
@ -10911,6 +10913,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryIdentityTransaction' => 'PhabricatorModularTransaction',
|
||||
'PhabricatorRepositoryIdentityTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PhabricatorRepositoryIdentityTransactionType' => 'PhabricatorModularTransactionType',
|
||||
'PhabricatorRepositoryMaintenanceTransaction' => 'PhabricatorRepositoryTransactionType',
|
||||
'PhabricatorRepositoryManagementCacheWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementClusterizeWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementDiscoverWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
|
@ -10919,6 +10922,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryManagementListPathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementMaintenanceWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementMirrorWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
|
|
|
@ -145,13 +145,26 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
->setRight(array($this->branchButton, $actions_button, $clone_button))
|
||||
->addClass('diffusion-action-bar');
|
||||
|
||||
$status_view = null;
|
||||
if ($repository->isReadOnly()) {
|
||||
$status_view = id(new PHUIInfoView())
|
||||
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
|
||||
->setErrors(
|
||||
array(
|
||||
phutil_escape_html_newlines(
|
||||
$repository->getReadOnlyMessageForDisplay()),
|
||||
));
|
||||
}
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setFooter(array(
|
||||
$bar,
|
||||
$description,
|
||||
$content,
|
||||
));
|
||||
->setFooter(
|
||||
array(
|
||||
$status_view,
|
||||
$bar,
|
||||
$description,
|
||||
$content,
|
||||
));
|
||||
|
||||
if ($page_has_content) {
|
||||
$view->setTabs($tabs);
|
||||
|
@ -327,6 +340,8 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
|
||||
if (!$repository->isTracked()) {
|
||||
$header->setStatus('fa-ban', 'dark', pht('Inactive'));
|
||||
} else if ($repository->isReadOnly()) {
|
||||
$header->setStatus('fa-wrench', 'indigo', pht('Under Maintenance'));
|
||||
} else if ($repository->isImporting()) {
|
||||
$ratio = $repository->loadImportProgress();
|
||||
$percentage = sprintf('%.2f%%', 100 * $ratio);
|
||||
|
|
|
@ -302,6 +302,12 @@ final class DiffusionServeController extends DiffusionController {
|
|||
}
|
||||
|
||||
if ($is_push) {
|
||||
if ($repository->isReadOnly()) {
|
||||
return new PhabricatorVCSResponse(
|
||||
503,
|
||||
$repository->getReadOnlyMessageForDisplay());
|
||||
}
|
||||
|
||||
$can_write =
|
||||
$repository->canServeProtocol($proto_https, true) ||
|
||||
$repository->canServeProtocol($proto_http, true);
|
||||
|
|
|
@ -255,6 +255,10 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
|
|||
'user account.'));
|
||||
}
|
||||
|
||||
if ($repository->isReadOnly()) {
|
||||
throw new Exception($repository->getReadOnlyMessageForDisplay());
|
||||
}
|
||||
|
||||
$protocol = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH;
|
||||
if ($repository->canServeProtocol($protocol, true)) {
|
||||
$can_push = PhabricatorPolicyFilter::hasCapability(
|
||||
|
|
|
@ -52,6 +52,13 @@ final class PhabricatorRepositoryPullEngine
|
|||
$repository = $this->getRepository();
|
||||
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||
|
||||
if ($repository->isReadOnly()) {
|
||||
$this->skipPull(
|
||||
pht(
|
||||
"Skipping pull on read-only repository.\n\n%s",
|
||||
$repository->getReadOnlyMessageForDisplay()));
|
||||
}
|
||||
|
||||
$is_hg = false;
|
||||
$is_git = false;
|
||||
$is_svn = false;
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorRepositoryManagementMaintenanceWorkflow
|
||||
extends PhabricatorRepositoryManagementWorkflow {
|
||||
|
||||
protected function didConstruct() {
|
||||
$this
|
||||
->setName('maintenance')
|
||||
->setExamples(
|
||||
"**maintenance** --start __message__ __repository__ ...\n".
|
||||
"**maintenance** --stop __repository__")
|
||||
->setSynopsis(
|
||||
pht('Set or clear read-only mode for repository maintenance.'))
|
||||
->setArguments(
|
||||
array(
|
||||
array(
|
||||
'name' => 'start',
|
||||
'param' => 'message',
|
||||
'help' => pht(
|
||||
'Put repositories into maintenance mode.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'stop',
|
||||
'help' => pht(
|
||||
'Take repositories out of maintenance mode, returning them '.
|
||||
'to normal serice.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'repositories',
|
||||
'wildcard' => true,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$repositories = $this->loadRepositories($args, 'repositories');
|
||||
if (!$repositories) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('Specify one or more repositories to act on.'));
|
||||
}
|
||||
|
||||
$message = $args->getArg('start');
|
||||
$is_start = (bool)strlen($message);
|
||||
$is_stop = $args->getArg('stop');
|
||||
|
||||
if (!$is_start && !$is_stop) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Use "--start <message>" to put repositories into maintenance '.
|
||||
'mode, or "--stop" to take them out of maintenance mode.'));
|
||||
}
|
||||
|
||||
if ($is_start && $is_stop) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Specify either "--start" or "--stop", but not both.'));
|
||||
}
|
||||
|
||||
$content_source = $this->newContentSource();
|
||||
$diffusion_phid = id(new PhabricatorDiffusionApplication())->getPHID();
|
||||
|
||||
if ($is_start) {
|
||||
$new_value = $message;
|
||||
} else {
|
||||
$new_value = null;
|
||||
}
|
||||
|
||||
foreach ($repositories as $repository) {
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = $repository->getApplicationTransactionTemplate()
|
||||
->setTransactionType(
|
||||
PhabricatorRepositoryMaintenanceTransaction::TRANSACTIONTYPE)
|
||||
->setNewValue($new_value);
|
||||
|
||||
$repository->getApplicationTransactionEditor()
|
||||
->setActor($viewer)
|
||||
->setActingAsPHID($diffusion_phid)
|
||||
->setContentSource($content_source)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($repository, $xactions);
|
||||
|
||||
if ($is_start) {
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Put repository "%s" into maintenance mode.',
|
||||
$repository->getDisplayName()));
|
||||
} else {
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Took repository "%s" out of maintenance mode.',
|
||||
$repository->getDisplayName()));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -1410,6 +1410,12 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
}
|
||||
}
|
||||
|
||||
if ($write) {
|
||||
if ($this->isReadOnly()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -2266,6 +2272,35 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
return $this->isGit();
|
||||
}
|
||||
|
||||
public function isReadOnly() {
|
||||
return (bool)$this->getDetail('read-only');
|
||||
}
|
||||
|
||||
public function setReadOnly($read_only) {
|
||||
return $this->setDetail('read-only', $read_only);
|
||||
}
|
||||
|
||||
public function getReadOnlyMessage() {
|
||||
return $this->getDetail('read-only-message');
|
||||
}
|
||||
|
||||
public function setReadOnlyMessage($message) {
|
||||
return $this->setDetail('read-only-message', $message);
|
||||
}
|
||||
|
||||
public function getReadOnlyMessageForDisplay() {
|
||||
$parts = array();
|
||||
$parts[] = pht(
|
||||
'This repository is currently in read-only maintenance mode.');
|
||||
|
||||
$message = $this->getReadOnlyMessage();
|
||||
if ($message !== null) {
|
||||
$parts[] = $message;
|
||||
}
|
||||
|
||||
return implode("\n\n", $parts);
|
||||
}
|
||||
|
||||
/* -( Repository URIs )---------------------------------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorRepositoryMaintenanceTransaction
|
||||
extends PhabricatorRepositoryTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'maintenance';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getReadOnlyMessage();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
if ($value === null) {
|
||||
$object
|
||||
->setReadOnly(false)
|
||||
->setReadOnlyMessage(null);
|
||||
} else {
|
||||
$object
|
||||
->setReadOnly(true)
|
||||
->setReadOnlyMessage($value);
|
||||
}
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
if (strlen($old) && !strlen($new)) {
|
||||
return pht(
|
||||
'%s took this repository out of maintenance mode.',
|
||||
$this->renderAuthor());
|
||||
} else if (!strlen($old) && strlen($new)) {
|
||||
return pht(
|
||||
'%s put this repository into maintenance mode.',
|
||||
$this->renderAuthor());
|
||||
} else {
|
||||
return pht(
|
||||
'%s updated the maintenance message for this repository.',
|
||||
$this->renderAuthor());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue