1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-09-20 01:08:50 +02:00

Add an "Restartable: If Failed" behavior to Harbormaster build plans

Summary:
Ref T13249. Ref T13258. In some cases, builds are not idempotent and should not be restarted casually.

If the scary part is at the very end (deploy / provision / whatever), it could be okay to restart them if they previously failed.

Also, make the "reasons why you can't restart" and "explanations of why you can't restart" logic a little more cohesive.

Test Plan:
  - Tried to restart builds in various states (failed/not failed, restartable always/if failed/never, already restarted), got appropriate errors or restarts.
  - (I'm not sure the "Autoplan" error is normally reachable, since you can't edit autoplans to configure things to let you try to restart them.)

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13258, T13249

Differential Revision: https://secure.phabricator.com/D20252
This commit is contained in:
epriestley 2019-03-06 09:32:31 -08:00
parent 1d4f6bd444
commit c1bff3b801
7 changed files with 104 additions and 26 deletions

View file

@ -1443,6 +1443,7 @@ phutil_register_library_map(array(
'HarbormasterQueryBuildsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildsConduitAPIMethod.php',
'HarbormasterQueryBuildsSearchEngineAttachment' => 'applications/harbormaster/engineextension/HarbormasterQueryBuildsSearchEngineAttachment.php',
'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php',
'HarbormasterRestartException' => 'applications/harbormaster/exception/HarbormasterRestartException.php',
'HarbormasterRunBuildPlansHeraldAction' => 'applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php',
'HarbormasterSchemaSpec' => 'applications/harbormaster/storage/HarbormasterSchemaSpec.php',
'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php',
@ -7093,6 +7094,7 @@ phutil_register_library_map(array(
'HarbormasterQueryBuildsConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterQueryBuildsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'HarbormasterRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'HarbormasterRestartException' => 'Exception',
'HarbormasterRunBuildPlansHeraldAction' => 'HeraldAction',
'HarbormasterSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'HarbormasterScratchTable' => 'HarbormasterDAO',

View file

@ -52,6 +52,10 @@ final class HarbormasterBuildStatus extends Phobject {
return ($this->key === self::STATUS_PASSED);
}
public function isFailed() {
return ($this->key === self::STATUS_FAILED);
}
/**
* Get a human readable name for a build status constant.

View file

@ -64,19 +64,13 @@ final class HarbormasterBuildActionController
'restart. Side effects of the build will occur again. Really '.
'restart build?');
$submit = pht('Restart Build');
} else if (!$build->getBuildPlan()->canRestartBuildPlan()) {
$title = pht('Not Restartable');
$body = pht(
'The build plan for this build is not restartable, so you '.
'can not restart the build.');
} else {
$title = pht('Unable to Restart Build');
if ($build->isRestarting()) {
$body = pht(
'This build is already restarting. You can not reissue a '.
'restart command to a restarting build.');
} else {
$body = pht('You can not restart this build.');
try {
$build->assertCanRestartBuild();
throw new Exception(pht('Expected to be unable to restart build.'));
} catch (HarbormasterRestartException $ex) {
$title = $ex->getTitle();
$body = $ex->getBody();
}
}
break;

View file

@ -0,0 +1,33 @@
<?php
final class HarbormasterRestartException extends Exception {
private $title;
private $body = array();
public function __construct($title, $body = null) {
$this->setTitle($title);
$this->appendParagraph($body);
parent::__construct($title);
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
public function appendParagraph($description) {
$this->body[] = $description;
return $this;
}
public function getBody() {
return $this->body;
}
}

View file

@ -15,6 +15,7 @@ final class HarbormasterBuildPlanBehavior
const BEHAVIOR_RESTARTABLE = 'restartable';
const RESTARTABLE_ALWAYS = 'always';
const RESTARTABLE_IF_FAILED = 'failed';
const RESTARTABLE_NEVER = 'never';
const BEHAVIOR_DRAFTS = 'hold-drafts';
@ -251,6 +252,12 @@ final class HarbormasterBuildPlanBehavior
->setIsDefault(true)
->setDescription(
pht('The build may be restarted.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::RESTARTABLE_IF_FAILED)
->setIcon('fa-times-circle-o yellow')
->setName(pht('If Failed'))
->setDescription(
pht('The build may be restarted if it has failed.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::RESTARTABLE_NEVER)
->setIcon('fa-times red')

View file

@ -183,6 +183,10 @@ final class HarbormasterBuild extends HarbormasterDAO
return $this->getBuildStatusObject()->isPassed();
}
public function isFailed() {
return $this->getBuildStatusObject()->isFailed();
}
public function getURI() {
$id = $this->getID();
return "/harbormaster/build/{$id}/";
@ -211,16 +215,60 @@ final class HarbormasterBuild extends HarbormasterDAO
}
public function canRestartBuild() {
try {
$this->assertCanRestartBuild();
return true;
} catch (HarbormasterRestartException $ex) {
return false;
}
}
public function assertCanRestartBuild() {
if ($this->isAutobuild()) {
return false;
throw new HarbormasterRestartException(
pht('Can Not Restart Autobuild'),
pht(
'This build can not be restarted because it is an automatic '.
'build.'));
}
$restartable = HarbormasterBuildPlanBehavior::BEHAVIOR_RESTARTABLE;
$plan = $this->getBuildPlan();
if (!$plan->canRestartBuildPlan()) {
return false;
$option = HarbormasterBuildPlanBehavior::getBehavior($restartable)
->getPlanOption($plan);
$option_key = $option->getKey();
$never_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_NEVER;
$is_never = ($option_key === $never_restartable);
if ($is_never) {
throw new HarbormasterRestartException(
pht('Build Plan Prevents Restart'),
pht(
'This build can not be restarted because the build plan is '.
'configured to prevent the build from restarting.'));
}
return !$this->isRestarting();
$failed_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_IF_FAILED;
$is_failed = ($option_key === $failed_restartable);
if ($is_failed) {
if (!$this->isFailed()) {
throw new HarbormasterRestartException(
pht('Only Restartable if Failed'),
pht(
'This build can not be restarted because the build plan is '.
'configured to prevent the build from restarting unless it '.
'has failed, and it has not failed.'));
}
}
if ($this->isRestarting()) {
throw new HarbormasterRestartException(
pht('Already Restarting'),
pht(
'This build is already restarting. You can not reissue a restart '.
'command to a restarting build.'));
}
}
public function canPauseBuild() {

View file

@ -175,16 +175,6 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
$capability);
}
public function canRestartBuildPlan() {
$restartable = HarbormasterBuildPlanBehavior::BEHAVIOR_RESTARTABLE;
$is_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_ALWAYS;
$option = HarbormasterBuildPlanBehavior::getBehavior($restartable)
->getPlanOption($this);
return ($option->getKey() === $is_restartable);
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */