@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

Replace "Cancel Build" with "Stop", "Resume" and "Restart"

Summary:
Ref T1049. Currently you can cancel a build, but now that we're tracking a lot more state we can stop, resume, and restart builds.

When the user issues a command against a build, I'm writing it into an auxiliary queue (`HarbormasterBuildCommand`) and then reading them out in the worker. This is mostly to avoid race messes where we try to `save()` the object in multiple places: basically, the BuildEngine is the //only// thing that writes to Build objects, and it holds a lock while it does it.

Test Plan:
- Created a plan which runs "sleep 2" a bunch of times in a row.
- Stopped, resumed, and restarted it.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran, chad

Maniphest Tasks: T1049

Differential Revision: https://secure.phabricator.com/D7892

+386 -128
+18
resources/sql/autopatches/20140104.harbormastercmd.sql
··· 1 + CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildcommand ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + targetPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 5 + command VARCHAR(128) NOT NULL COLLATE utf8_bin, 6 + dateCreated INT UNSIGNED NOT NULL, 7 + dateModified INT UNSIGNED NOT NULL, 8 + KEY `key_target` (targetPHID) 9 + ) ENGINE=InnoDB, COLLATE utf8_general_ci; 10 + 11 + ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_build 12 + DROP cancelRequested; 13 + 14 + ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildtarget 15 + ADD targetStatus VARCHAR(64) NOT NULL COLLATE utf8_bin; 16 + 17 + UPDATE {$NAMESPACE}_harbormaster.harbormaster_buildtarget 18 + SET targetStatus = 'target/pending' WHERE targetStatus = '';
+5 -3
src/__phutil_library_map__.php
··· 704 704 'FileMailReceiver' => 'applications/files/mail/FileMailReceiver.php', 705 705 'FileReplyHandler' => 'applications/files/mail/FileReplyHandler.php', 706 706 'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php', 707 + 'HarbormasterBuildActionController' => 'applications/harbormaster/controller/HarbormasterBuildActionController.php', 707 708 'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php', 708 709 'HarbormasterBuildArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildArtifactQuery.php', 709 - 'HarbormasterBuildCancelController' => 'applications/harbormaster/controller/HarbormasterBuildCancelController.php', 710 + 'HarbormasterBuildCommand' => 'applications/harbormaster/storage/HarbormasterBuildCommand.php', 710 711 'HarbormasterBuildEngine' => 'applications/harbormaster/engine/HarbormasterBuildEngine.php', 711 712 'HarbormasterBuildItem' => 'applications/harbormaster/storage/build/HarbormasterBuildItem.php', 712 713 'HarbormasterBuildItemQuery' => 'applications/harbormaster/query/HarbormasterBuildItemQuery.php', ··· 3155 3156 0 => 'HarbormasterDAO', 3156 3157 1 => 'PhabricatorPolicyInterface', 3157 3158 ), 3159 + 'HarbormasterBuildActionController' => 'HarbormasterController', 3158 3160 'HarbormasterBuildArtifact' => 3159 3161 array( 3160 3162 0 => 'HarbormasterDAO', 3161 3163 1 => 'PhabricatorPolicyInterface', 3162 3164 ), 3163 3165 'HarbormasterBuildArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3164 - 'HarbormasterBuildCancelController' => 'HarbormasterController', 3166 + 'HarbormasterBuildCommand' => 'HarbormasterDAO', 3165 3167 'HarbormasterBuildEngine' => 'Phobject', 3166 3168 'HarbormasterBuildItem' => 'HarbormasterDAO', 3167 3169 'HarbormasterBuildItemQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', ··· 3226 3228 'HarbormasterPHIDTypeBuildStep' => 'PhabricatorPHIDType', 3227 3229 'HarbormasterPHIDTypeBuildTarget' => 'PhabricatorPHIDType', 3228 3230 'HarbormasterPHIDTypeBuildable' => 'PhabricatorPHIDType', 3229 - 'HarbormasterPlanController' => 'PhabricatorController', 3231 + 'HarbormasterPlanController' => 'HarbormasterController', 3230 3232 'HarbormasterPlanDisableController' => 'HarbormasterPlanController', 3231 3233 'HarbormasterPlanEditController' => 'HarbormasterPlanController', 3232 3234 'HarbormasterPlanListController' =>
+2 -1
src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
··· 55 55 ), 56 56 'build/' => array( 57 57 '(?:(?P<id>\d+)/)?' => 'HarbormasterBuildViewController', 58 - 'cancel/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildCancelController', 58 + '(?P<action>stop|resume|restart)/(?:(?P<id>\d+)/)?' 59 + => 'HarbormasterBuildActionController', 59 60 ), 60 61 'plan/' => array( 61 62 '(?:query/(?P<queryKey>[^/]+)/)?'
+146
src/applications/harbormaster/controller/HarbormasterBuildActionController.php
··· 1 + <?php 2 + 3 + final class HarbormasterBuildActionController 4 + extends HarbormasterController { 5 + 6 + private $id; 7 + private $action; 8 + 9 + public function willProcessRequest(array $data) { 10 + $this->id = $data['id']; 11 + $this->action = $data['action']; 12 + } 13 + 14 + public function processRequest() { 15 + $request = $this->getRequest(); 16 + $viewer = $request->getUser(); 17 + $command = $this->action; 18 + 19 + $build = id(new HarbormasterBuildQuery()) 20 + ->setViewer($viewer) 21 + ->withIDs(array($this->id)) 22 + ->executeOne(); 23 + if (!$build) { 24 + return new Aphront404Response(); 25 + } 26 + 27 + switch ($command) { 28 + case HarbormasterBuildCommand::COMMAND_RESTART: 29 + $can_issue = $build->canRestartBuild(); 30 + break; 31 + case HarbormasterBuildCommand::COMMAND_STOP: 32 + $can_issue = $build->canStopBuild(); 33 + break; 34 + case HarbormasterBuildCommand::COMMAND_RESUME: 35 + $can_issue = $build->canResumeBuild(); 36 + break; 37 + default: 38 + return new Aphront400Response(); 39 + } 40 + 41 + $build_uri = $this->getApplicationURI('/build/'.$build->getID().'/'); 42 + 43 + if ($request->isDialogFormPost() && $can_issue) { 44 + 45 + // Issue the new build command. 46 + id(new HarbormasterBuildCommand()) 47 + ->setAuthorPHID($viewer->getPHID()) 48 + ->setTargetPHID($build->getPHID()) 49 + ->setCommand($command) 50 + ->save(); 51 + 52 + // Schedule a build update. We may already have stuff in queue (in which 53 + // case this will just no-op), but we might also be dealing with a 54 + // stopped build, which won't restart unless we deal with this. 55 + PhabricatorWorker::scheduleTask( 56 + 'HarbormasterBuildWorker', 57 + array( 58 + 'buildID' => $build->getID() 59 + )); 60 + 61 + return id(new AphrontRedirectResponse())->setURI($build_uri); 62 + } 63 + 64 + switch ($command) { 65 + case HarbormasterBuildCommand::COMMAND_RESTART: 66 + if ($can_issue) { 67 + $title = pht('Really restart build?'); 68 + $body = pht( 69 + 'Progress on this build will be discarded and the build will '. 70 + 'restart. Side effects of the build will occur again. Really '. 71 + 'restart build?'); 72 + $submit = pht('Restart Build'); 73 + } else { 74 + $title = pht('Unable to Restart Build'); 75 + if ($build->isRestarting()) { 76 + $body = pht( 77 + 'This build is already restarting. You can not reissue a '. 78 + 'restart command to a restarting build.'); 79 + } else { 80 + $body = pht( 81 + 'You can not restart this build.'); 82 + } 83 + } 84 + break; 85 + case HarbormasterBuildCommand::COMMAND_STOP: 86 + if ($can_issue) { 87 + $title = pht('Really stop build?'); 88 + $body = pht( 89 + 'If you stop this build, work will halt once the current steps '. 90 + 'complete. You can resume the build later.'); 91 + $submit = pht('Stop Build'); 92 + } else { 93 + $title = pht('Unable to Stop Build'); 94 + if ($build->isComplete()) { 95 + $body = pht( 96 + 'This build is already complete. You can not stop a completed '. 97 + 'build.'); 98 + } else if ($build->isStopped()) { 99 + $body = pht( 100 + 'This build is already stopped. You can not stop a build which '. 101 + 'has already been stopped.'); 102 + } else if ($build->isStopping()) { 103 + $body = pht( 104 + 'This build is already stopping. You can not reissue a stop '. 105 + 'command to a stopping build.'); 106 + } else { 107 + $body = pht( 108 + 'This build can not be stopped.'); 109 + } 110 + } 111 + break; 112 + case HarbormasterBuildCommand::COMMAND_RESUME: 113 + if ($can_issue) { 114 + $title = pht('Really resume build?'); 115 + $body = pht( 116 + 'Work will continue on the build. Really resume?'); 117 + $submit = pht('Resume Build'); 118 + } else { 119 + $title = pht('Unable to Resume Build'); 120 + if ($build->isResuming()) { 121 + $body = pht( 122 + 'This build is already resuming. You can not reissue a resume '. 123 + 'command to a resuming build.'); 124 + } else if (!$build->isStopped()) { 125 + $body = pht( 126 + 'This build is not stopped. You can only resume a stopped '. 127 + 'build.'); 128 + } 129 + } 130 + break; 131 + } 132 + 133 + $dialog = id(new AphrontDialogView()) 134 + ->setUser($viewer) 135 + ->setTitle($title) 136 + ->appendChild($body) 137 + ->addCancelButton($build_uri); 138 + 139 + if ($can_issue) { 140 + $dialog->addSubmitButton($submit); 141 + } 142 + 143 + return id(new AphrontDialogResponse())->setDialog($dialog); 144 + } 145 + 146 + }
-49
src/applications/harbormaster/controller/HarbormasterBuildCancelController.php
··· 1 - <?php 2 - 3 - final class HarbormasterBuildCancelController 4 - extends HarbormasterController { 5 - 6 - private $id; 7 - 8 - public function willProcessRequest(array $data) { 9 - $this->id = $data['id']; 10 - } 11 - 12 - public function processRequest() { 13 - $request = $this->getRequest(); 14 - $viewer = $request->getUser(); 15 - 16 - $id = $this->id; 17 - 18 - $build = id(new HarbormasterBuildQuery()) 19 - ->setViewer($viewer) 20 - ->withIDs(array($id)) 21 - ->executeOne(); 22 - if ($build === null) { 23 - return new Aphront404Response(); 24 - } 25 - 26 - $build_uri = $this->getApplicationURI('/build/'.$build->getID()); 27 - 28 - if ($request->isDialogFormPost()) { 29 - $build->setCancelRequested(1); 30 - $build->save(); 31 - 32 - return id(new AphrontRedirectResponse())->setURI($build_uri); 33 - } 34 - 35 - $dialog = new AphrontDialogView(); 36 - $dialog->setTitle(pht('Really cancel build?')) 37 - ->setUser($viewer) 38 - ->addSubmitButton(pht('Cancel')) 39 - ->addCancelButton($build_uri, pht('Don\'t Cancel')); 40 - $dialog->appendChild( 41 - phutil_tag( 42 - 'p', 43 - array(), 44 - pht( 45 - 'Really cancel this build?'))); 46 - return id(new AphrontDialogResponse())->setDialog($dialog); 47 - } 48 - 49 - }
+30 -22
src/applications/harbormaster/controller/HarbormasterBuildViewController.php
··· 214 214 ->setObject($build) 215 215 ->setObjectURI("/build/{$id}"); 216 216 217 - $action = 217 + $can_restart = $build->canRestartBuild(); 218 + $can_stop = $build->canStopBuild(); 219 + $can_resume = $build->canResumeBuild(); 220 + 221 + $list->addAction( 218 222 id(new PhabricatorActionView()) 219 - ->setName(pht('Cancel Build')) 220 - ->setIcon('delete'); 221 - switch ($build->getBuildStatus()) { 222 - case HarbormasterBuild::STATUS_PENDING: 223 - case HarbormasterBuild::STATUS_WAITING: 224 - case HarbormasterBuild::STATUS_BUILDING: 225 - $cancel_uri = $this->getApplicationURI('/build/cancel/'.$id.'/'); 226 - $action 227 - ->setHref($cancel_uri) 228 - ->setWorkflow(true); 229 - break; 230 - default: 231 - $action 232 - ->setDisabled(true); 233 - break; 234 - } 235 - $list->addAction($action); 223 + ->setName(pht('Restart Build')) 224 + ->setIcon('backward') 225 + ->setHref($this->getApplicationURI('/build/restart/'.$id.'/')) 226 + ->setDisabled(!$can_restart) 227 + ->setWorkflow(true)); 228 + 229 + $list->addAction( 230 + id(new PhabricatorActionView()) 231 + ->setName(pht('Stop Build')) 232 + ->setIcon('stop') 233 + ->setHref($this->getApplicationURI('/build/stop/'.$id.'/')) 234 + ->setDisabled(!$can_stop) 235 + ->setWorkflow(true)); 236 + 237 + $list->addAction( 238 + id(new PhabricatorActionView()) 239 + ->setName(pht('Resume Build')) 240 + ->setIcon('play') 241 + ->setHref($this->getApplicationURI('/build/resume/'.$id.'/')) 242 + ->setDisabled(!$can_resume) 243 + ->setWorkflow(true)); 236 244 237 245 return $list; 238 246 } ··· 272 280 } 273 281 274 282 private function getStatus(HarbormasterBuild $build) { 275 - if ($build->getCancelRequested()) { 276 - return pht('Cancelling'); 283 + if ($build->isStopping()) { 284 + return pht('Stopping'); 277 285 } 278 286 switch ($build->getBuildStatus()) { 279 287 case HarbormasterBuild::STATUS_INACTIVE: ··· 290 298 return pht('Failed'); 291 299 case HarbormasterBuild::STATUS_ERROR: 292 300 return pht('Unexpected Error'); 293 - case HarbormasterBuild::STATUS_CANCELLED: 294 - return pht('Cancelled'); 301 + case HarbormasterBuild::STATUS_STOPPED: 302 + return pht('Stopped'); 295 303 default: 296 304 return pht('Unknown'); 297 305 }
-4
src/applications/harbormaster/controller/HarbormasterBuildableListController.php
··· 88 88 ->setViewer($user) 89 89 ->addNavigationItems($nav->getMenu()); 90 90 91 - if ($for_app) { 92 - $nav->addFilter('new/', pht('New Build Plan')); 93 - } 94 - 95 91 $nav->addLabel(pht('Build Plans')); 96 92 $nav->addFilter('plan/', pht('Manage Build Plans')); 97 93
+4 -4
src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
··· 38 38 ->setObjectName(pht('Build %d', $build->getID())) 39 39 ->setHeader($build->getName()) 40 40 ->setHref($view_uri); 41 - if ($build->getCancelRequested()) { 41 + if ($build->isStopping()) { 42 42 $item->setBarColor('black'); 43 - $item->addAttribute(pht('Cancelling')); 43 + $item->addAttribute(pht('Stopping')); 44 44 } else { 45 45 switch ($build->getBuildStatus()) { 46 46 case HarbormasterBuild::STATUS_INACTIVE: ··· 71 71 $item->setBarColor('red'); 72 72 $item->addAttribute(pht('Unexpected Error')); 73 73 break; 74 - case HarbormasterBuild::STATUS_CANCELLED: 74 + case HarbormasterBuild::STATUS_STOPPED: 75 75 $item->setBarColor('black'); 76 - $item->addAttribute(pht('Cancelled')); 76 + $item->addAttribute(pht('Stopped')); 77 77 break; 78 78 } 79 79 }
+1 -1
src/applications/harbormaster/controller/HarbormasterPlanController.php
··· 1 1 <?php 2 2 3 - abstract class HarbormasterPlanController extends PhabricatorController { 3 + abstract class HarbormasterPlanController extends HarbormasterController { 4 4 5 5 public function buildApplicationCrumbs() { 6 6 $crumbs = parent::buildApplicationCrumbs();
+4
src/applications/harbormaster/controller/HarbormasterPlanListController.php
··· 57 57 $nav = new AphrontSideNavFilterView(); 58 58 $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); 59 59 60 + if ($for_app) { 61 + $nav->addFilter('new/', pht('New Build Plan')); 62 + } 63 + 60 64 id(new HarbormasterBuildPlanSearchEngine()) 61 65 ->setViewer($user) 62 66 ->addNavigationItems($nav->getMenu());
+42 -8
src/applications/harbormaster/engine/HarbormasterBuildEngine.php
··· 72 72 } 73 73 74 74 private function updateBuild(HarbormasterBuild $build) { 75 - // TODO: Handle cancellation and restarts. 75 + 76 + $should_stop = false; 77 + $should_resume = false; 78 + $should_restart = false; 79 + foreach ($build->getUnprocessedCommands() as $command) { 80 + switch ($command->getCommand()) { 81 + case HarbormasterBuildCommand::COMMAND_STOP: 82 + $should_stop = true; 83 + $should_resume = false; 84 + break; 85 + case HarbormasterBuildCommand::COMMAND_RESUME: 86 + $should_resume = true; 87 + $should_stop = false; 88 + break; 89 + case HarbormasterBuildCommand::COMMAND_RESTART: 90 + $should_restart = true; 91 + $should_resume = true; 92 + $should_stop = false; 93 + break; 94 + } 95 + } 76 96 77 - if ($build->getBuildStatus() == HarbormasterBuild::STATUS_PENDING) { 97 + if (($build->getBuildStatus() == HarbormasterBuild::STATUS_PENDING) || 98 + ($should_restart)) { 78 99 $this->destroyBuildTargets($build); 79 100 $build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING); 80 101 $build->save(); 81 102 } 82 103 104 + if ($should_resume) { 105 + $build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING); 106 + $build->save(); 107 + } 108 + 109 + if ($should_stop && !$build->isComplete()) { 110 + $build->setBuildStatus(HarbormasterBuild::STATUS_STOPPED); 111 + $build->save(); 112 + } 113 + 114 + foreach ($build->getUnprocessedCommands() as $command) { 115 + $command->delete(); 116 + } 117 + $build->attachUnprocessedCommands(array()); 118 + 83 119 if ($build->getBuildStatus() == HarbormasterBuild::STATUS_BUILDING) { 84 - return $this->updateBuildSteps($build); 120 + $this->updateBuildSteps($build); 85 121 } 86 122 } 87 123 ··· 118 154 if ($step_targets) { 119 155 $is_complete = true; 120 156 foreach ($step_targets as $target) { 121 - // TODO: Move this to a top-level "status" field on BuildTarget. 122 - if (!$target->getDetail('__done__')) { 157 + if (!$target->isComplete()) { 123 158 $is_complete = false; 124 159 break; 125 160 } ··· 127 162 128 163 $is_failed = false; 129 164 foreach ($step_targets as $target) { 130 - // TODO: Move this to a top-level "status" field on BuildTarget. 131 - if ($target->getDetail('__failed__')) { 165 + if ($target->isFailed()) { 132 166 $is_failed = true; 133 167 break; 134 168 } ··· 212 246 foreach ($runnable as $runnable_step) { 213 247 $target = HarbormasterBuildTarget::initializeNewBuildTarget( 214 248 $build, 215 - $step, 249 + $runnable_step, 216 250 $build->retrieveVariablesFromBuild()); 217 251 $target->save(); 218 252
+2 -2
src/applications/harbormaster/event/HarbormasterUIEventListener.php
··· 94 94 case HarbormasterBuild::STATUS_ERROR: 95 95 $item->setIcon('minus-red', pht('Unexpected Error')); 96 96 break; 97 - case HarbormasterBuild::STATUS_CANCELLED: 98 - $item->setIcon('minus-dark', pht('Cancelled')); 97 + case HarbormasterBuild::STATUS_STOPPED: 98 + $item->setIcon('minus-dark', pht('Stopped')); 99 99 break; 100 100 default: 101 101 $item->setIcon('question', pht('Unknown'));
+10
src/applications/harbormaster/query/HarbormasterBuildQuery.php
··· 92 92 $build->attachBuildPlan(idx($plans, $plan_phid)); 93 93 } 94 94 95 + $build_phids = mpull($page, 'getPHID'); 96 + $commands = id(new HarbormasterBuildCommand())->loadAllWhere( 97 + 'targetPHID IN (%Ls) ORDER BY id ASC', 98 + $build_phids); 99 + $commands = mgroup($commands, 'getTargetPHID'); 100 + foreach ($page as $build) { 101 + $unprocessed_commands = idx($commands, $build->getPHID(), array()); 102 + $build->attachUnprocessedCommands($unprocessed_commands); 103 + } 104 + 95 105 return $page; 96 106 } 97 107
+1 -1
src/applications/harbormaster/step/WaitForPreviousBuildStepImplementation.php
··· 113 113 ->execute(); 114 114 115 115 foreach ($builds as $build) { 116 - if ($build->isBuilding()) { 116 + if (!$build->isComplete()) { 117 117 $blockers[] = pht('Build %d', $build->getID()); 118 118 } 119 119 }
+13
src/applications/harbormaster/storage/HarbormasterBuildCommand.php
··· 1 + <?php 2 + 3 + final class HarbormasterBuildCommand extends HarbormasterDAO { 4 + 5 + const COMMAND_STOP = 'stop'; 6 + const COMMAND_RESUME = 'resume'; 7 + const COMMAND_RESTART = 'restart'; 8 + 9 + protected $authorPHID; 10 + protected $targetPHID; 11 + protected $command; 12 + 13 + }
+75 -30
src/applications/harbormaster/storage/build/HarbormasterBuild.php
··· 6 6 protected $buildablePHID; 7 7 protected $buildPlanPHID; 8 8 protected $buildStatus; 9 - protected $cancelRequested; 10 9 11 10 private $buildable = self::ATTACHABLE; 12 11 private $buildPlan = self::ATTACHABLE; 12 + private $unprocessedCommands = self::ATTACHABLE; 13 13 14 14 /** 15 15 * Not currently being built. ··· 47 47 const STATUS_ERROR = 'error'; 48 48 49 49 /** 50 - * The build has been cancelled. 50 + * The build has been stopped. 51 51 */ 52 - const STATUS_CANCELLED = 'cancelled'; 52 + const STATUS_STOPPED = 'stopped'; 53 53 54 54 public static function initializeNewBuild(PhabricatorUser $actor) { 55 55 return id(new HarbormasterBuild()) 56 - ->setBuildStatus(self::STATUS_INACTIVE) 57 - ->setCancelRequested(0); 56 + ->setBuildStatus(self::STATUS_INACTIVE); 58 57 } 59 58 60 59 public function getConfiguration() { ··· 97 96 public function isBuilding() { 98 97 return $this->getBuildStatus() === self::STATUS_PENDING || 99 98 $this->getBuildStatus() === self::STATUS_WAITING || 100 - $this->getBuildStatus() === self::STATUS_BUILDING || 101 - $this->getCancelRequested(); 99 + $this->getBuildStatus() === self::STATUS_BUILDING; 102 100 } 103 101 104 102 public function createLog( ··· 106 104 $log_source, 107 105 $log_type) { 108 106 109 - $log = HarbormasterBuildLog::initializeNewBuildLog($build_target); 110 - $log->setLogSource($log_source); 111 - $log->setLogType($log_type); 112 - $log->save(); 107 + $log = HarbormasterBuildLog::initializeNewBuildLog($build_target) 108 + ->setLogSource($log_source) 109 + ->setLogType($log_type) 110 + ->save(); 111 + 113 112 return $log; 114 113 } 115 114 ··· 139 138 return $artifact; 140 139 } 141 140 142 - /** 143 - * Checks for and handles build cancellation. If this method returns 144 - * true, the caller should stop any current operations and return control 145 - * as quickly as possible. 146 - */ 147 - public function checkForCancellation() { 148 - // Here we load a copy of the current build and check whether 149 - // the user requested cancellation. We can't do `reload()` here 150 - // in case there are changes that have not yet been saved. 151 - $copy = id(new HarbormasterBuild())->load($this->getID()); 152 - if ($copy->getCancelRequested()) { 153 - $this->setBuildStatus(HarbormasterBuild::STATUS_CANCELLED); 154 - $this->setCancelRequested(0); 155 - $this->save(); 156 - return true; 157 - } 158 - return false; 159 - } 160 - 161 141 public function retrieveVariablesFromBuild() { 162 142 $results = array( 163 143 'buildable.diff' => null, ··· 210 190 pht('The URI to clone or checkout the repository from.'), 211 191 'step.timestamp' => pht('The current UNIX timestamp.'), 212 192 'build.id' => pht('The ID of the current build.')); 193 + } 194 + 195 + public function isComplete() { 196 + switch ($this->getBuildStatus()) { 197 + case self::STATUS_PASSED: 198 + case self::STATUS_FAILED: 199 + case self::STATUS_ERROR: 200 + case self::STATUS_STOPPED: 201 + return true; 202 + } 203 + 204 + return false; 205 + } 206 + 207 + public function isStopped() { 208 + return ($this->getBuildStatus() == self::STATUS_STOPPED); 209 + } 210 + 211 + 212 + /* -( Build Commands )----------------------------------------------------- */ 213 + 214 + 215 + public function getUnprocessedCommands() { 216 + return $this->assertAttached($this->unprocessedCommands); 217 + } 218 + 219 + public function attachUnprocessedCommands(array $commands) { 220 + $this->unprocessedCommands = $commands; 221 + return $this; 222 + } 223 + 224 + public function hasWaitingCommand($command_name) { 225 + foreach ($this->getUnprocessedCommands() as $command_object) { 226 + if ($command_object->getCommand() == $command_name) { 227 + return true; 228 + } 229 + } 230 + return false; 231 + } 232 + 233 + public function canRestartBuild() { 234 + return !$this->isRestarting(); 235 + } 236 + 237 + public function canStopBuild() { 238 + return !$this->isComplete() && 239 + !$this->isStopped() && 240 + !$this->isStopping(); 241 + } 242 + 243 + public function canResumeBuild() { 244 + return $this->isStopped() && 245 + !$this->isResuming(); 246 + } 247 + 248 + public function isStopping() { 249 + return $this->hasWaitingCommand(HarbormasterBuildCommand::COMMAND_STOP); 250 + } 251 + 252 + public function isResuming() { 253 + return $this->hasWaitingCommand(HarbormasterBuildCommand::COMMAND_RESUME); 254 + } 255 + 256 + public function isRestarting() { 257 + return $this->hasWaitingCommand(HarbormasterBuildCommand::COMMAND_RESTART); 213 258 } 214 259 215 260
+30
src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php
··· 8 8 protected $className; 9 9 protected $details; 10 10 protected $variables; 11 + protected $targetStatus; 12 + 13 + const STATUS_PENDING = 'target/pending'; 14 + const STATUS_PASSED = 'target/passed'; 15 + const STATUS_FAILED = 'target/failed'; 11 16 12 17 private $build = self::ATTACHABLE; 13 18 private $buildStep = self::ATTACHABLE; ··· 21 26 ->setBuildStepPHID($build_step->getPHID()) 22 27 ->setClassName($build_step->getClassName()) 23 28 ->setDetails($build_step->getDetails()) 29 + ->setTargetStatus(self::STATUS_PENDING) 24 30 ->setVariables($variables); 25 31 } 26 32 ··· 93 99 $implementation = newv($class, array()); 94 100 $implementation->loadSettings($this); 95 101 return $implementation; 102 + } 103 + 104 + 105 + /* -( Status )------------------------------------------------------------- */ 106 + 107 + 108 + public function isComplete() { 109 + switch ($this->getTargetStatus()) { 110 + case self::STATUS_PASSED: 111 + case self::STATUS_FAILED: 112 + return true; 113 + } 114 + 115 + return false; 116 + } 117 + 118 + 119 + public function isFailed() { 120 + switch ($this->getTargetStatus()) { 121 + case self::STATUS_FAILED: 122 + return true; 123 + } 124 + 125 + return false; 96 126 } 97 127 98 128
+3 -3
src/applications/harbormaster/worker/HarbormasterTargetWorker.php
··· 38 38 try { 39 39 $implementation = $target->getImplementation(); 40 40 if (!$implementation->validateSettings()) { 41 - $target->setDetail('__failed__', true); 41 + $target->setTargetStatus(HarbormasterBuildTarget::STATUS_FAILED); 42 42 $target->save(); 43 43 } else { 44 44 $implementation->execute($build, $target); 45 - $target->setDetail('__done__', true); 45 + $target->setTargetStatus(HarbormasterBuildTarget::STATUS_PASSED); 46 46 $target->save(); 47 47 } 48 48 } catch (Exception $ex) { 49 - $target->setDetail('__failed__', true); 49 + $target->setTargetStatus(HarbormasterBuildTarget::STATUS_FAILED); 50 50 $target->save(); 51 51 } 52 52