Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php
index b0e3105d78..dc63b5fd95 100644
--- a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php
+++ b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php
@@ -1,169 +1,204 @@
<?php
final class HarbormasterBuildStatus extends Phobject {
- /**
- * Not currently being built.
- */
const STATUS_INACTIVE = 'inactive';
-
- /**
- * Pending pick up by the Harbormaster daemon.
- */
const STATUS_PENDING = 'pending';
-
- /**
- * Current building the buildable.
- */
const STATUS_BUILDING = 'building';
-
- /**
- * The build has passed.
- */
const STATUS_PASSED = 'passed';
-
- /**
- * The build has failed.
- */
const STATUS_FAILED = 'failed';
-
- /**
- * The build has aborted.
- */
const STATUS_ABORTED = 'aborted';
-
- /**
- * The build encountered an unexpected error.
- */
const STATUS_ERROR = 'error';
-
- /**
- * The build has been paused.
- */
const STATUS_PAUSED = 'paused';
-
- /**
- * The build has been deadlocked.
- */
const STATUS_DEADLOCKED = 'deadlocked';
+ private $key;
+ private $properties;
+
+ public function __construct($key, array $properties) {
+ $this->key = $key;
+ $this->properties = $properties;
+ }
+
+ public static function newBuildStatusObject($status) {
+ $spec = self::getBuildStatusSpec($status);
+ return new self($status, $spec);
+ }
+
+ private function getProperty($key) {
+ if (!array_key_exists($key, $this->properties)) {
+ throw new Exception(
+ pht(
+ 'Attempting to access unknown build status property ("%s").',
+ $key));
+ }
+
+ return $this->properties[$key];
+ }
+
+ public function isBuilding() {
+ return $this->getProperty('isBuilding');
+ }
+
+ public function isPaused() {
+ return ($this->key === self::STATUS_PAUSED);
+ }
+
+ public function isComplete() {
+ return $this->getProperty('isComplete');
+ }
+
+ public function isPassed() {
+ return ($this->key === self::STATUS_PASSED);
+ }
+
/**
* Get a human readable name for a build status constant.
*
* @param const Build status constant.
* @return string Human-readable name.
*/
public static function getBuildStatusName($status) {
$spec = self::getBuildStatusSpec($status);
- return idx($spec, 'name', pht('Unknown ("%s")', $status));
+ return $spec['name'];
}
public static function getBuildStatusMap() {
$specs = self::getBuildStatusSpecMap();
return ipull($specs, 'name');
}
public static function getBuildStatusIcon($status) {
$spec = self::getBuildStatusSpec($status);
- return idx($spec, 'icon', 'fa-question-circle');
+ return $spec['icon'];
}
public static function getBuildStatusColor($status) {
$spec = self::getBuildStatusSpec($status);
- return idx($spec, 'color', 'bluegrey');
+ return $spec['color'];
}
public static function getBuildStatusANSIColor($status) {
$spec = self::getBuildStatusSpec($status);
- return idx($spec, 'color.ansi', 'magenta');
+ return $spec['color.ansi'];
}
public static function getWaitingStatusConstants() {
return array(
self::STATUS_INACTIVE,
self::STATUS_PENDING,
);
}
public static function getActiveStatusConstants() {
return array(
self::STATUS_BUILDING,
self::STATUS_PAUSED,
);
}
public static function getCompletedStatusConstants() {
return array(
self::STATUS_PASSED,
self::STATUS_FAILED,
self::STATUS_ABORTED,
self::STATUS_ERROR,
self::STATUS_DEADLOCKED,
);
}
private static function getBuildStatusSpecMap() {
return array(
self::STATUS_INACTIVE => array(
'name' => pht('Inactive'),
'icon' => 'fa-circle-o',
'color' => 'dark',
'color.ansi' => 'yellow',
+ 'isBuilding' => false,
+ 'isComplete' => false,
),
self::STATUS_PENDING => array(
'name' => pht('Pending'),
'icon' => 'fa-circle-o',
'color' => 'blue',
'color.ansi' => 'yellow',
+ 'isBuilding' => true,
+ 'isComplete' => false,
),
self::STATUS_BUILDING => array(
'name' => pht('Building'),
'icon' => 'fa-chevron-circle-right',
'color' => 'blue',
'color.ansi' => 'yellow',
+ 'isBuilding' => true,
+ 'isComplete' => false,
),
self::STATUS_PASSED => array(
'name' => pht('Passed'),
'icon' => 'fa-check-circle',
'color' => 'green',
'color.ansi' => 'green',
+ 'isBuilding' => false,
+ 'isComplete' => true,
),
self::STATUS_FAILED => array(
'name' => pht('Failed'),
'icon' => 'fa-times-circle',
'color' => 'red',
'color.ansi' => 'red',
+ 'isBuilding' => false,
+ 'isComplete' => true,
),
self::STATUS_ABORTED => array(
'name' => pht('Aborted'),
'icon' => 'fa-minus-circle',
'color' => 'red',
'color.ansi' => 'red',
+ 'isBuilding' => false,
+ 'isComplete' => true,
),
self::STATUS_ERROR => array(
'name' => pht('Unexpected Error'),
'icon' => 'fa-minus-circle',
'color' => 'red',
'color.ansi' => 'red',
+ 'isBuilding' => false,
+ 'isComplete' => true,
),
self::STATUS_PAUSED => array(
'name' => pht('Paused'),
'icon' => 'fa-minus-circle',
'color' => 'dark',
'color.ansi' => 'yellow',
+ 'isBuilding' => false,
+ 'isComplete' => false,
),
self::STATUS_DEADLOCKED => array(
'name' => pht('Deadlocked'),
'icon' => 'fa-exclamation-circle',
'color' => 'red',
'color.ansi' => 'red',
+ 'isBuilding' => false,
+ 'isComplete' => true,
),
);
}
private static function getBuildStatusSpec($status) {
- return idx(self::getBuildStatusSpecMap(), $status, array());
+ $map = self::getBuildStatusSpecMap();
+ if (isset($map[$status])) {
+ return $map[$status];
+ }
+
+ return array(
+ 'name' => pht('Unknown ("%s")', $status),
+ 'icon' => 'fa-question-circle',
+ 'color' => 'bluegrey',
+ 'color.ansi' => 'magenta',
+ 'isBuilding' => false,
+ 'isComplete' => false,
+ );
}
}
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
index 6a47dae429..58e5cf8c49 100644
--- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
@@ -1,351 +1,355 @@
<?php
final class HarbormasterBuildableViewController
extends HarbormasterController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$buildable = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->executeOne();
if (!$buildable) {
return new Aphront404Response();
}
$id = $buildable->getID();
// Pull builds and build targets.
$builds = id(new HarbormasterBuildQuery())
->setViewer($viewer)
->withBuildablePHIDs(array($buildable->getPHID()))
->needBuildTargets(true)
->execute();
list($lint, $unit) = $this->renderLintAndUnit($buildable, $builds);
$buildable->attachBuilds($builds);
$object = $buildable->getBuildableObject();
$build_list = $this->buildBuildList($buildable);
$title = pht('Buildable %d', $id);
$header = id(new PHUIHeaderView())
->setHeader($title)
->setUser($viewer)
->setPolicyObject($buildable)
+ ->setStatus(
+ $buildable->getStatusIcon(),
+ $buildable->getStatusColor(),
+ $buildable->getStatusDisplayName())
->setHeaderIcon('fa-recycle');
$timeline = $this->buildTransactionTimeline(
$buildable,
new HarbormasterBuildableTransactionQuery());
$timeline->setShouldTerminate(true);
$curtain = $this->buildCurtainView($buildable);
$properties = $this->buildPropertyList($buildable);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($buildable->getMonogram());
$crumbs->setBorder(true);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(array(
$properties,
$lint,
$unit,
$build_list,
$timeline,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildCurtainView(HarbormasterBuildable $buildable) {
$viewer = $this->getViewer();
$id = $buildable->getID();
$curtain = $this->newCurtainView($buildable);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$buildable,
PhabricatorPolicyCapability::CAN_EDIT);
$can_restart = false;
$can_resume = false;
$can_pause = false;
$can_abort = false;
$command_restart = HarbormasterBuildCommand::COMMAND_RESTART;
$command_resume = HarbormasterBuildCommand::COMMAND_RESUME;
$command_pause = HarbormasterBuildCommand::COMMAND_PAUSE;
$command_abort = HarbormasterBuildCommand::COMMAND_ABORT;
foreach ($buildable->getBuilds() as $build) {
if ($build->canRestartBuild()) {
if ($build->canIssueCommand($viewer, $command_restart)) {
$can_restart = true;
}
}
if ($build->canResumeBuild()) {
if ($build->canIssueCommand($viewer, $command_resume)) {
$can_resume = true;
}
}
if ($build->canPauseBuild()) {
if ($build->canIssueCommand($viewer, $command_pause)) {
$can_pause = true;
}
}
if ($build->canAbortBuild()) {
if ($build->canIssueCommand($viewer, $command_abort)) {
$can_abort = true;
}
}
}
$restart_uri = "buildable/{$id}/restart/";
$pause_uri = "buildable/{$id}/pause/";
$resume_uri = "buildable/{$id}/resume/";
$abort_uri = "buildable/{$id}/abort/";
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-repeat')
->setName(pht('Restart All Builds'))
->setHref($this->getApplicationURI($restart_uri))
->setWorkflow(true)
->setDisabled(!$can_restart || !$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-pause')
->setName(pht('Pause All Builds'))
->setHref($this->getApplicationURI($pause_uri))
->setWorkflow(true)
->setDisabled(!$can_pause || !$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-play')
->setName(pht('Resume All Builds'))
->setHref($this->getApplicationURI($resume_uri))
->setWorkflow(true)
->setDisabled(!$can_resume || !$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-exclamation-triangle')
->setName(pht('Abort All Builds'))
->setHref($this->getApplicationURI($abort_uri))
->setWorkflow(true)
->setDisabled(!$can_abort || !$can_edit));
return $curtain;
}
private function buildPropertyList(HarbormasterBuildable $buildable) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer);
$container_phid = $buildable->getContainerPHID();
$buildable_phid = $buildable->getBuildablePHID();
if ($container_phid) {
$properties->addProperty(
pht('Container'),
$viewer->renderHandle($container_phid));
}
$properties->addProperty(
pht('Buildable'),
$viewer->renderHandle($buildable_phid));
$properties->addProperty(
pht('Origin'),
$buildable->getIsManualBuildable()
? pht('Manual Buildable')
: pht('Automatic Buildable'));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Properties'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($properties);
}
private function buildBuildList(HarbormasterBuildable $buildable) {
$viewer = $this->getRequest()->getUser();
$build_list = id(new PHUIObjectItemListView())
->setUser($viewer);
foreach ($buildable->getBuilds() as $build) {
$view_uri = $this->getApplicationURI('/build/'.$build->getID().'/');
$item = id(new PHUIObjectItemView())
->setObjectName(pht('Build %d', $build->getID()))
->setHeader($build->getName())
->setHref($view_uri);
$status = $build->getBuildStatus();
$status_color = HarbormasterBuildStatus::getBuildStatusColor($status);
$status_name = HarbormasterBuildStatus::getBuildStatusName($status);
$item->setStatusIcon('fa-dot-circle-o '.$status_color, $status_name);
$item->addAttribute($status_name);
if ($build->isRestarting()) {
$item->addIcon('fa-repeat', pht('Restarting'));
} else if ($build->isPausing()) {
$item->addIcon('fa-pause', pht('Pausing'));
} else if ($build->isResuming()) {
$item->addIcon('fa-play', pht('Resuming'));
}
$build_id = $build->getID();
$restart_uri = "build/restart/{$build_id}/buildable/";
$resume_uri = "build/resume/{$build_id}/buildable/";
$pause_uri = "build/pause/{$build_id}/buildable/";
$abort_uri = "build/abort/{$build_id}/buildable/";
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-repeat')
->setName(pht('Restart'))
->setHref($this->getApplicationURI($restart_uri))
->setWorkflow(true)
->setDisabled(!$build->canRestartBuild()));
if ($build->canResumeBuild()) {
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-play')
->setName(pht('Resume'))
->setHref($this->getApplicationURI($resume_uri))
->setWorkflow(true));
} else {
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-pause')
->setName(pht('Pause'))
->setHref($this->getApplicationURI($pause_uri))
->setWorkflow(true)
->setDisabled(!$build->canPauseBuild()));
}
$targets = $build->getBuildTargets();
if ($targets) {
$target_list = id(new PHUIStatusListView());
foreach ($targets as $target) {
$status = $target->getTargetStatus();
$icon = HarbormasterBuildTarget::getBuildTargetStatusIcon($status);
$color = HarbormasterBuildTarget::getBuildTargetStatusColor($status);
$status_name =
HarbormasterBuildTarget::getBuildTargetStatusName($status);
$name = $target->getName();
$target_list->addItem(
id(new PHUIStatusItemView())
->setIcon($icon, $color, $status_name)
->setTarget(pht('Target %d', $target->getID()))
->setNote($name));
}
$target_box = id(new PHUIBoxView())
->addPadding(PHUI::PADDING_SMALL)
->appendChild($target_list);
$item->appendChild($target_box);
}
$build_list->addItem($item);
}
$build_list->setFlush(true);
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Builds'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($build_list);
return $box;
}
private function renderLintAndUnit(
HarbormasterBuildable $buildable,
array $builds) {
$viewer = $this->getViewer();
$targets = array();
foreach ($builds as $build) {
foreach ($build->getBuildTargets() as $target) {
$targets[] = $target;
}
}
if (!$targets) {
return;
}
$target_phids = mpull($targets, 'getPHID');
$lint_data = id(new HarbormasterBuildLintMessage())->loadAllWhere(
'buildTargetPHID IN (%Ls)',
$target_phids);
$unit_data = id(new HarbormasterBuildUnitMessage())->loadAllWhere(
'buildTargetPHID IN (%Ls)',
$target_phids);
if ($lint_data) {
$lint_table = id(new HarbormasterLintPropertyView())
->setUser($viewer)
->setLimit(10)
->setLintMessages($lint_data);
$lint_href = $this->getApplicationURI('lint/'.$buildable->getID().'/');
$lint_header = id(new PHUIHeaderView())
->setHeader(pht('Lint Messages'))
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setHref($lint_href)
->setIcon('fa-list-ul')
->setText('View All'));
$lint = id(new PHUIObjectBoxView())
->setHeader($lint_header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($lint_table);
} else {
$lint = null;
}
if ($unit_data) {
$unit = id(new HarbormasterUnitSummaryView())
->setBuildable($buildable)
->setUnitMessages($unit_data)
->setShowViewAll(true)
->setLimit(5);
} else {
$unit = null;
}
return array($lint, $unit);
}
}
diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
index 0616e77b4e..9c4112e810 100644
--- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
+++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
@@ -1,582 +1,578 @@
<?php
/**
* Moves a build forward by queuing build tasks, canceling or restarting the
* build, or failing it in response to task failures.
*/
final class HarbormasterBuildEngine extends Phobject {
private $build;
private $viewer;
private $newBuildTargets = array();
private $artifactReleaseQueue = array();
private $forceBuildableUpdate;
public function setForceBuildableUpdate($force_buildable_update) {
$this->forceBuildableUpdate = $force_buildable_update;
return $this;
}
public function shouldForceBuildableUpdate() {
return $this->forceBuildableUpdate;
}
public function queueNewBuildTarget(HarbormasterBuildTarget $target) {
$this->newBuildTargets[] = $target;
return $this;
}
public function getNewBuildTargets() {
return $this->newBuildTargets;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setBuild(HarbormasterBuild $build) {
$this->build = $build;
return $this;
}
public function getBuild() {
return $this->build;
}
public function continueBuild() {
$build = $this->getBuild();
$lock_key = 'harbormaster.build:'.$build->getID();
$lock = PhabricatorGlobalLock::newLock($lock_key)->lock(15);
$build->reload();
$old_status = $build->getBuildStatus();
try {
$this->updateBuild($build);
} catch (Exception $ex) {
// If any exception is raised, the build is marked as a failure and the
// exception is re-thrown (this ensures we don't leave builds in an
// inconsistent state).
$build->setBuildStatus(HarbormasterBuildStatus::STATUS_ERROR);
$build->save();
$lock->unlock();
$this->releaseAllArtifacts($build);
throw $ex;
}
$lock->unlock();
// NOTE: We queue new targets after releasing the lock so that in-process
// execution via `bin/harbormaster` does not reenter the locked region.
foreach ($this->getNewBuildTargets() as $target) {
$task = PhabricatorWorker::scheduleTask(
'HarbormasterTargetWorker',
array(
'targetID' => $target->getID(),
),
array(
'objectPHID' => $target->getPHID(),
));
}
// If the build changed status, we might need to update the overall status
// on the buildable.
$new_status = $build->getBuildStatus();
if ($new_status != $old_status || $this->shouldForceBuildableUpdate()) {
$this->updateBuildable($build->getBuildable());
}
$this->releaseQueuedArtifacts();
// If we are no longer building for any reason, release all artifacts.
if (!$build->isBuilding()) {
$this->releaseAllArtifacts($build);
}
}
private function updateBuild(HarbormasterBuild $build) {
if ($build->isAborting()) {
$this->releaseAllArtifacts($build);
$build->setBuildStatus(HarbormasterBuildStatus::STATUS_ABORTED);
$build->save();
}
if (($build->getBuildStatus() == HarbormasterBuildStatus::STATUS_PENDING) ||
($build->isRestarting())) {
$this->restartBuild($build);
$build->setBuildStatus(HarbormasterBuildStatus::STATUS_BUILDING);
$build->save();
}
if ($build->isResuming()) {
$build->setBuildStatus(HarbormasterBuildStatus::STATUS_BUILDING);
$build->save();
}
if ($build->isPausing() && !$build->isComplete()) {
$build->setBuildStatus(HarbormasterBuildStatus::STATUS_PAUSED);
$build->save();
}
$build->deleteUnprocessedCommands();
if ($build->getBuildStatus() == HarbormasterBuildStatus::STATUS_BUILDING) {
$this->updateBuildSteps($build);
}
}
private function restartBuild(HarbormasterBuild $build) {
// We're restarting the build, so release all previous artifacts.
$this->releaseAllArtifacts($build);
// Increment the build generation counter on the build.
$build->setBuildGeneration($build->getBuildGeneration() + 1);
// Currently running targets should periodically check their build
// generation (which won't have changed) against the build's generation.
// If it is different, they will automatically stop what they're doing
// and abort.
// Previously we used to delete targets, logs and artifacts here. Instead
// leave them around so users can view previous generations of this build.
}
private function updateBuildSteps(HarbormasterBuild $build) {
$all_targets = id(new HarbormasterBuildTargetQuery())
->setViewer($this->getViewer())
->withBuildPHIDs(array($build->getPHID()))
->withBuildGenerations(array($build->getBuildGeneration()))
->execute();
$this->updateWaitingTargets($all_targets);
$targets = mgroup($all_targets, 'getBuildStepPHID');
$steps = id(new HarbormasterBuildStepQuery())
->setViewer($this->getViewer())
->withBuildPlanPHIDs(array($build->getBuildPlan()->getPHID()))
->execute();
$steps = mpull($steps, null, 'getPHID');
// Identify steps which are in various states.
$queued = array();
$underway = array();
$waiting = array();
$complete = array();
$failed = array();
foreach ($steps as $step) {
$step_targets = idx($targets, $step->getPHID(), array());
if ($step_targets) {
$is_queued = false;
$is_underway = false;
foreach ($step_targets as $target) {
if ($target->isUnderway()) {
$is_underway = true;
break;
}
}
$is_waiting = false;
foreach ($step_targets as $target) {
if ($target->isWaiting()) {
$is_waiting = true;
break;
}
}
$is_complete = true;
foreach ($step_targets as $target) {
if (!$target->isComplete()) {
$is_complete = false;
break;
}
}
$is_failed = false;
foreach ($step_targets as $target) {
if ($target->isFailed()) {
$is_failed = true;
break;
}
}
} else {
$is_queued = true;
$is_underway = false;
$is_waiting = false;
$is_complete = false;
$is_failed = false;
}
if ($is_queued) {
$queued[$step->getPHID()] = true;
}
if ($is_underway) {
$underway[$step->getPHID()] = true;
}
if ($is_waiting) {
$waiting[$step->getPHID()] = true;
}
if ($is_complete) {
$complete[$step->getPHID()] = true;
}
if ($is_failed) {
$failed[$step->getPHID()] = true;
}
}
// If any step failed, fail the whole build, then bail.
if (count($failed)) {
$build->setBuildStatus(HarbormasterBuildStatus::STATUS_FAILED);
$build->save();
return;
}
// If every step is complete, we're done with this build. Mark it passed
// and bail.
if (count($complete) == count($steps)) {
$build->setBuildStatus(HarbormasterBuildStatus::STATUS_PASSED);
$build->save();
return;
}
// Release any artifacts which are not inputs to any remaining build
// step. We're done with these, so something else is free to use them.
$ongoing_phids = array_keys($queued + $waiting + $underway);
$ongoing_steps = array_select_keys($steps, $ongoing_phids);
$this->releaseUnusedArtifacts($all_targets, $ongoing_steps);
// Identify all the steps which are ready to run (because all their
// dependencies are complete).
$runnable = array();
foreach ($steps as $step) {
$dependencies = $step->getStepImplementation()->getDependencies($step);
if (isset($queued[$step->getPHID()])) {
$can_run = true;
foreach ($dependencies as $dependency) {
if (empty($complete[$dependency])) {
$can_run = false;
break;
}
}
if ($can_run) {
$runnable[] = $step;
}
}
}
if (!$runnable && !$waiting && !$underway) {
// This means the build is deadlocked, and the user has configured
// circular dependencies.
$build->setBuildStatus(HarbormasterBuildStatus::STATUS_DEADLOCKED);
$build->save();
return;
}
foreach ($runnable as $runnable_step) {
$target = HarbormasterBuildTarget::initializeNewBuildTarget(
$build,
$runnable_step,
$build->retrieveVariablesFromBuild());
$target->save();
$this->queueNewBuildTarget($target);
}
}
/**
* Release any artifacts which aren't used by any running or waiting steps.
*
* This releases artifacts as soon as they're no longer used. This can be
* particularly relevant when a build uses multiple hosts since it returns
* hosts to the pool more quickly.
*
* @param list<HarbormasterBuildTarget> Targets in the build.
* @param list<HarbormasterBuildStep> List of running and waiting steps.
* @return void
*/
private function releaseUnusedArtifacts(array $targets, array $steps) {
assert_instances_of($targets, 'HarbormasterBuildTarget');
assert_instances_of($steps, 'HarbormasterBuildStep');
if (!$targets || !$steps) {
return;
}
$target_phids = mpull($targets, 'getPHID');
$artifacts = id(new HarbormasterBuildArtifactQuery())
->setViewer($this->getViewer())
->withBuildTargetPHIDs($target_phids)
->withIsReleased(false)
->execute();
if (!$artifacts) {
return;
}
// Collect all the artifacts that remaining build steps accept as inputs.
$must_keep = array();
foreach ($steps as $step) {
$inputs = $step->getStepImplementation()->getArtifactInputs();
foreach ($inputs as $input) {
$artifact_key = $input['key'];
$must_keep[$artifact_key] = true;
}
}
// Queue unreleased artifacts which no remaining step uses for immediate
// release.
foreach ($artifacts as $artifact) {
$key = $artifact->getArtifactKey();
if (isset($must_keep[$key])) {
continue;
}
$this->artifactReleaseQueue[] = $artifact;
}
}
/**
* Process messages which were sent to these targets, kicking applicable
* targets out of "Waiting" and into either "Passed" or "Failed".
*
* @param list<HarbormasterBuildTarget> List of targets to process.
* @return void
*/
private function updateWaitingTargets(array $targets) {
assert_instances_of($targets, 'HarbormasterBuildTarget');
// We only care about messages for targets which are actually in a waiting
// state.
$waiting_targets = array();
foreach ($targets as $target) {
if ($target->isWaiting()) {
$waiting_targets[$target->getPHID()] = $target;
}
}
if (!$waiting_targets) {
return;
}
$messages = id(new HarbormasterBuildMessageQuery())
->setViewer($this->getViewer())
->withBuildTargetPHIDs(array_keys($waiting_targets))
->withConsumed(false)
->execute();
foreach ($messages as $message) {
$target = $waiting_targets[$message->getBuildTargetPHID()];
switch ($message->getType()) {
case HarbormasterMessageType::MESSAGE_PASS:
$new_status = HarbormasterBuildTarget::STATUS_PASSED;
break;
case HarbormasterMessageType::MESSAGE_FAIL:
$new_status = HarbormasterBuildTarget::STATUS_FAILED;
break;
case HarbormasterMessageType::MESSAGE_WORK:
default:
$new_status = null;
break;
}
if ($new_status !== null) {
$message->setIsConsumed(true);
$message->save();
$target->setTargetStatus($new_status);
if ($target->isComplete()) {
$target->setDateCompleted(PhabricatorTime::getNow());
}
$target->save();
}
}
}
/**
* Update the overall status of the buildable this build is attached to.
*
* After a build changes state (for example, passes or fails) it may affect
* the overall state of the associated buildable. Compute the new aggregate
* state and save it on the buildable.
*
* @param HarbormasterBuild The buildable to update.
* @return void
*/
private function updateBuildable(HarbormasterBuildable $buildable) {
$viewer = $this->getViewer();
$lock_key = 'harbormaster.buildable:'.$buildable->getID();
$lock = PhabricatorGlobalLock::newLock($lock_key)->lock(15);
$buildable = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->withIDs(array($buildable->getID()))
->needBuilds(true)
->executeOne();
$all_pass = true;
$any_fail = false;
foreach ($buildable->getBuilds() as $build) {
- if ($build->getBuildStatus() != HarbormasterBuildStatus::STATUS_PASSED) {
+ if (!$build->isPassed()) {
$all_pass = false;
}
- if (in_array($build->getBuildStatus(), array(
- HarbormasterBuildStatus::STATUS_FAILED,
- HarbormasterBuildStatus::STATUS_ERROR,
- HarbormasterBuildStatus::STATUS_DEADLOCKED,
- ))) {
+ if ($build->isComplete() && !$build->isPassed()) {
$any_fail = true;
}
}
if ($any_fail) {
$new_status = HarbormasterBuildable::STATUS_FAILED;
} else if ($all_pass) {
$new_status = HarbormasterBuildable::STATUS_PASSED;
} else {
$new_status = HarbormasterBuildable::STATUS_BUILDING;
}
$old_status = $buildable->getBuildableStatus();
$did_update = ($old_status != $new_status);
if ($did_update) {
$buildable->setBuildableStatus($new_status);
$buildable->save();
}
$lock->unlock();
// If we changed the buildable status, try to post a transaction to the
// object about it. We can safely do this outside of the locked region.
// NOTE: We only post transactions for automatic buildables, not for
// manual ones: manual builds are test builds, whoever is doing tests
// can look at the results themselves, and other users generally don't
// care about the outcome.
$should_publish = $did_update &&
$new_status != HarbormasterBuildable::STATUS_BUILDING &&
!$buildable->getIsManualBuildable();
if (!$should_publish) {
return;
}
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($buildable->getBuildablePHID()))
->executeOne();
if (!$object) {
return;
}
$publish_phid = $object->getHarbormasterPublishablePHID();
if (!$publish_phid) {
return;
}
if ($publish_phid === $object->getPHID()) {
$publish = $object;
} else {
$publish = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($publish_phid))
->executeOne();
if (!$publish) {
return;
}
}
if (!($publish instanceof PhabricatorApplicationTransactionInterface)) {
return;
}
$template = $publish->getApplicationTransactionTemplate();
if (!$template) {
return;
}
$template
->setTransactionType(PhabricatorTransactions::TYPE_BUILDABLE)
->setMetadataValue(
'harbormaster:buildablePHID',
$buildable->getPHID())
->setOldValue($old_status)
->setNewValue($new_status);
$harbormaster_phid = id(new PhabricatorHarbormasterApplication())
->getPHID();
$daemon_source = PhabricatorContentSource::newForSource(
PhabricatorDaemonContentSource::SOURCECONST);
$editor = $publish->getApplicationTransactionEditor()
->setActor($viewer)
->setActingAsPHID($harbormaster_phid)
->setContentSource($daemon_source)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$editor->applyTransactions(
$publish->getApplicationTransactionObject(),
array($template));
}
private function releaseAllArtifacts(HarbormasterBuild $build) {
$targets = id(new HarbormasterBuildTargetQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuildPHIDs(array($build->getPHID()))
->withBuildGenerations(array($build->getBuildGeneration()))
->execute();
if (count($targets) === 0) {
return;
}
$target_phids = mpull($targets, 'getPHID');
$artifacts = id(new HarbormasterBuildArtifactQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuildTargetPHIDs($target_phids)
->withIsReleased(false)
->execute();
foreach ($artifacts as $artifact) {
$artifact->releaseArtifact();
}
}
private function releaseQueuedArtifacts() {
foreach ($this->artifactReleaseQueue as $key => $artifact) {
$artifact->releaseArtifact();
unset($this->artifactReleaseQueue[$key]);
}
}
}
diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php
index 9761fb287f..89c13b8b41 100644
--- a/src/applications/harbormaster/storage/HarbormasterBuildable.php
+++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php
@@ -1,333 +1,345 @@
<?php
final class HarbormasterBuildable extends HarbormasterDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
HarbormasterBuildableInterface {
protected $buildablePHID;
protected $containerPHID;
protected $buildableStatus;
protected $isManualBuildable;
private $buildableObject = self::ATTACHABLE;
private $containerObject = self::ATTACHABLE;
private $builds = self::ATTACHABLE;
const STATUS_BUILDING = 'building';
const STATUS_PASSED = 'passed';
const STATUS_FAILED = 'failed';
public static function getBuildableStatusName($status) {
$map = self::getBuildStatusMap();
return idx($map, $status, pht('Unknown ("%s")', $status));
}
public static function getBuildStatusMap() {
return array(
self::STATUS_BUILDING => pht('Building'),
self::STATUS_PASSED => pht('Passed'),
self::STATUS_FAILED => pht('Failed'),
);
}
public static function getBuildableStatusIcon($status) {
switch ($status) {
case self::STATUS_BUILDING:
return PHUIStatusItemView::ICON_RIGHT;
case self::STATUS_PASSED:
return PHUIStatusItemView::ICON_ACCEPT;
case self::STATUS_FAILED:
return PHUIStatusItemView::ICON_REJECT;
default:
return PHUIStatusItemView::ICON_QUESTION;
}
}
public static function getBuildableStatusColor($status) {
switch ($status) {
case self::STATUS_BUILDING:
return 'blue';
case self::STATUS_PASSED:
return 'green';
case self::STATUS_FAILED:
return 'red';
default:
return 'bluegrey';
}
}
+ public function getStatusIcon() {
+ return self::getBuildableStatusIcon($this->getBuildableStatus());
+ }
+
+ public function getStatusDisplayName() {
+ return self::getBuildableStatusName($this->getBuildableStatus());
+ }
+
+ public function getStatusColor() {
+ return self::getBuildableStatusColor($this->getBuildableStatus());
+ }
+
public static function initializeNewBuildable(PhabricatorUser $actor) {
return id(new HarbormasterBuildable())
->setIsManualBuildable(0)
->setBuildableStatus(self::STATUS_BUILDING);
}
public function getMonogram() {
return 'B'.$this->getID();
}
public function getURI() {
return '/'.$this->getMonogram();
}
/**
* Returns an existing buildable for the object's PHID or creates a
* new buildable implicitly if needed.
*/
public static function createOrLoadExisting(
PhabricatorUser $actor,
$buildable_object_phid,
$container_object_phid) {
$buildable = id(new HarbormasterBuildableQuery())
->setViewer($actor)
->withBuildablePHIDs(array($buildable_object_phid))
->withManualBuildables(false)
->setLimit(1)
->executeOne();
if ($buildable) {
return $buildable;
}
$buildable = self::initializeNewBuildable($actor)
->setBuildablePHID($buildable_object_phid)
->setContainerPHID($container_object_phid);
$buildable->save();
return $buildable;
}
/**
* Start builds for a given buildable.
*
* @param phid PHID of the object to build.
* @param phid Container PHID for the buildable.
* @param list<HarbormasterBuildRequest> List of builds to perform.
* @return void
*/
public static function applyBuildPlans(
$phid,
$container_phid,
array $requests) {
assert_instances_of($requests, 'HarbormasterBuildRequest');
if (!$requests) {
return;
}
// Skip all of this logic if the Harbormaster application
// isn't currently installed.
$harbormaster_app = 'PhabricatorHarbormasterApplication';
if (!PhabricatorApplication::isClassInstalled($harbormaster_app)) {
return;
}
$viewer = PhabricatorUser::getOmnipotentUser();
$buildable = self::createOrLoadExisting(
$viewer,
$phid,
$container_phid);
$plan_phids = mpull($requests, 'getBuildPlanPHID');
$plans = id(new HarbormasterBuildPlanQuery())
->setViewer($viewer)
->withPHIDs($plan_phids)
->execute();
$plans = mpull($plans, null, 'getPHID');
foreach ($requests as $request) {
$plan_phid = $request->getBuildPlanPHID();
$plan = idx($plans, $plan_phid);
if (!$plan) {
throw new Exception(
pht(
'Failed to load build plan ("%s").',
$plan_phid));
}
if ($plan->isDisabled()) {
// TODO: This should be communicated more clearly -- maybe we should
// create the build but set the status to "disabled" or "derelict".
continue;
}
$parameters = $request->getBuildParameters();
$buildable->applyPlan($plan, $parameters, $request->getInitiatorPHID());
}
}
public function applyPlan(
HarbormasterBuildPlan $plan,
array $parameters,
$initiator_phid) {
$viewer = PhabricatorUser::getOmnipotentUser();
$build = HarbormasterBuild::initializeNewBuild($viewer)
->setBuildablePHID($this->getPHID())
->setBuildPlanPHID($plan->getPHID())
->setBuildParameters($parameters)
->setBuildStatus(HarbormasterBuildStatus::STATUS_PENDING);
if ($initiator_phid) {
$build->setInitiatorPHID($initiator_phid);
}
$auto_key = $plan->getPlanAutoKey();
if ($auto_key) {
$build->setPlanAutoKey($auto_key);
}
$build->save();
PhabricatorWorker::scheduleTask(
'HarbormasterBuildWorker',
array(
'buildID' => $build->getID(),
),
array(
'objectPHID' => $build->getPHID(),
));
return $build;
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'containerPHID' => 'phid?',
'buildableStatus' => 'text32',
'isManualBuildable' => 'bool',
),
self::CONFIG_KEY_SCHEMA => array(
'key_buildable' => array(
'columns' => array('buildablePHID'),
),
'key_container' => array(
'columns' => array('containerPHID'),
),
'key_manual' => array(
'columns' => array('isManualBuildable'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
HarbormasterBuildablePHIDType::TYPECONST);
}
public function attachBuildableObject($buildable_object) {
$this->buildableObject = $buildable_object;
return $this;
}
public function getBuildableObject() {
return $this->assertAttached($this->buildableObject);
}
public function attachContainerObject($container_object) {
$this->containerObject = $container_object;
return $this;
}
public function getContainerObject() {
return $this->assertAttached($this->containerObject);
}
public function attachBuilds(array $builds) {
assert_instances_of($builds, 'HarbormasterBuild');
$this->builds = $builds;
return $this;
}
public function getBuilds() {
return $this->assertAttached($this->builds);
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new HarbormasterBuildableTransactionEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new HarbormasterBuildableTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return $this->getBuildableObject()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getBuildableObject()->hasAutomaticCapability(
$capability,
$viewer);
}
public function describeAutomaticCapability($capability) {
return pht('A buildable inherits policies from the underlying object.');
}
/* -( HarbormasterBuildableInterface )------------------------------------- */
public function getHarbormasterBuildableDisplayPHID() {
return $this->getBuildableObject()->getHarbormasterBuildableDisplayPHID();
}
public function getHarbormasterBuildablePHID() {
// NOTE: This is essentially just for convenience, as it allows you create
// a copy of a buildable by specifying `B123` without bothering to go
// look up the underlying object.
return $this->getBuildablePHID();
}
public function getHarbormasterContainerPHID() {
return $this->getContainerPHID();
}
public function getHarbormasterPublishablePHID() {
return $this->getBuildableObject()->getHarbormasterPublishablePHID();
}
public function getBuildVariables() {
return array();
}
public function getAvailableBuildVariables() {
return array();
}
}
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
index 92d4293913..d43b19d71f 100644
--- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
@@ -1,453 +1,458 @@
<?php
final class HarbormasterBuild extends HarbormasterDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorConduitResultInterface {
protected $buildablePHID;
protected $buildPlanPHID;
protected $buildStatus;
protected $buildGeneration;
protected $buildParameters = array();
protected $initiatorPHID;
protected $planAutoKey;
private $buildable = self::ATTACHABLE;
private $buildPlan = self::ATTACHABLE;
private $buildTargets = self::ATTACHABLE;
private $unprocessedCommands = self::ATTACHABLE;
public static function initializeNewBuild(PhabricatorUser $actor) {
return id(new HarbormasterBuild())
->setBuildStatus(HarbormasterBuildStatus::STATUS_INACTIVE)
->setBuildGeneration(0);
}
public function delete() {
$this->openTransaction();
$this->deleteUnprocessedCommands();
$result = parent::delete();
$this->saveTransaction();
return $result;
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'buildParameters' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'buildStatus' => 'text32',
'buildGeneration' => 'uint32',
'planAutoKey' => 'text32?',
'initiatorPHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_buildable' => array(
'columns' => array('buildablePHID'),
),
'key_plan' => array(
'columns' => array('buildPlanPHID'),
),
'key_status' => array(
'columns' => array('buildStatus'),
),
'key_planautokey' => array(
'columns' => array('buildablePHID', 'planAutoKey'),
'unique' => true,
),
'key_initiator' => array(
'columns' => array('initiatorPHID'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
HarbormasterBuildPHIDType::TYPECONST);
}
public function attachBuildable(HarbormasterBuildable $buildable) {
$this->buildable = $buildable;
return $this;
}
public function getBuildable() {
return $this->assertAttached($this->buildable);
}
public function getName() {
if ($this->getBuildPlan()) {
return $this->getBuildPlan()->getName();
}
return pht('Build');
}
public function attachBuildPlan(
HarbormasterBuildPlan $build_plan = null) {
$this->buildPlan = $build_plan;
return $this;
}
public function getBuildPlan() {
return $this->assertAttached($this->buildPlan);
}
public function getBuildTargets() {
return $this->assertAttached($this->buildTargets);
}
public function attachBuildTargets(array $targets) {
$this->buildTargets = $targets;
return $this;
}
public function isBuilding() {
- return
- $this->getBuildStatus() === HarbormasterBuildStatus::STATUS_PENDING ||
- $this->getBuildStatus() === HarbormasterBuildStatus::STATUS_BUILDING;
+ return $this->getBuildStatusObject()->isBuilding();
}
public function isAutobuild() {
return ($this->getPlanAutoKey() !== null);
}
public function retrieveVariablesFromBuild() {
$results = array(
'buildable.diff' => null,
'buildable.revision' => null,
'buildable.commit' => null,
'repository.callsign' => null,
'repository.phid' => null,
'repository.vcs' => null,
'repository.uri' => null,
'step.timestamp' => null,
'build.id' => null,
'initiator.phid' => null,
);
foreach ($this->getBuildParameters() as $key => $value) {
$results['build/'.$key] = $value;
}
$buildable = $this->getBuildable();
$object = $buildable->getBuildableObject();
$object_variables = $object->getBuildVariables();
$results = $object_variables + $results;
$results['step.timestamp'] = time();
$results['build.id'] = $this->getID();
$results['initiator.phid'] = $this->getInitiatorPHID();
return $results;
}
public static function getAvailableBuildVariables() {
$objects = id(new PhutilClassMapQuery())
->setAncestorClass('HarbormasterBuildableInterface')
->execute();
$variables = array();
$variables[] = array(
'step.timestamp' => pht('The current UNIX timestamp.'),
'build.id' => pht('The ID of the current build.'),
'target.phid' => pht('The PHID of the current build target.'),
'initiator.phid' => pht(
'The PHID of the user or Object that initiated the build, '.
'if applicable.'),
);
foreach ($objects as $object) {
$variables[] = $object->getAvailableBuildVariables();
}
$variables = array_mergev($variables);
return $variables;
}
public function isComplete() {
- return in_array(
- $this->getBuildStatus(),
- HarbormasterBuildStatus::getCompletedStatusConstants());
+ return $this->getBuildStatusObject()->isComplete();
}
public function isPaused() {
- return ($this->getBuildStatus() == HarbormasterBuildStatus::STATUS_PAUSED);
+ return $this->getBuildStatusObject()->isPaused();
+ }
+
+ public function isPassed() {
+ return $this->getBuildStatusObject()->isPassed();
}
public function getURI() {
$id = $this->getID();
return "/harbormaster/build/{$id}/";
}
+ protected function getBuildStatusObject() {
+ $status_key = $this->getBuildStatus();
+ return HarbormasterBuildStatus::newBuildStatusObject($status_key);
+ }
+
/* -( Build Commands )----------------------------------------------------- */
private function getUnprocessedCommands() {
return $this->assertAttached($this->unprocessedCommands);
}
public function attachUnprocessedCommands(array $commands) {
$this->unprocessedCommands = $commands;
return $this;
}
public function canRestartBuild() {
if ($this->isAutobuild()) {
return false;
}
return !$this->isRestarting();
}
public function canPauseBuild() {
if ($this->isAutobuild()) {
return false;
}
return !$this->isComplete() &&
!$this->isPaused() &&
!$this->isPausing();
}
public function canAbortBuild() {
if ($this->isAutobuild()) {
return false;
}
return !$this->isComplete();
}
public function canResumeBuild() {
if ($this->isAutobuild()) {
return false;
}
return $this->isPaused() &&
!$this->isResuming();
}
public function isPausing() {
$is_pausing = false;
foreach ($this->getUnprocessedCommands() as $command_object) {
$command = $command_object->getCommand();
switch ($command) {
case HarbormasterBuildCommand::COMMAND_PAUSE:
$is_pausing = true;
break;
case HarbormasterBuildCommand::COMMAND_RESUME:
case HarbormasterBuildCommand::COMMAND_RESTART:
$is_pausing = false;
break;
case HarbormasterBuildCommand::COMMAND_ABORT:
$is_pausing = true;
break;
}
}
return $is_pausing;
}
public function isResuming() {
$is_resuming = false;
foreach ($this->getUnprocessedCommands() as $command_object) {
$command = $command_object->getCommand();
switch ($command) {
case HarbormasterBuildCommand::COMMAND_RESTART:
case HarbormasterBuildCommand::COMMAND_RESUME:
$is_resuming = true;
break;
case HarbormasterBuildCommand::COMMAND_PAUSE:
$is_resuming = false;
break;
case HarbormasterBuildCommand::COMMAND_ABORT:
$is_resuming = false;
break;
}
}
return $is_resuming;
}
public function isRestarting() {
$is_restarting = false;
foreach ($this->getUnprocessedCommands() as $command_object) {
$command = $command_object->getCommand();
switch ($command) {
case HarbormasterBuildCommand::COMMAND_RESTART:
$is_restarting = true;
break;
}
}
return $is_restarting;
}
public function isAborting() {
$is_aborting = false;
foreach ($this->getUnprocessedCommands() as $command_object) {
$command = $command_object->getCommand();
switch ($command) {
case HarbormasterBuildCommand::COMMAND_ABORT:
$is_aborting = true;
break;
}
}
return $is_aborting;
}
public function deleteUnprocessedCommands() {
foreach ($this->getUnprocessedCommands() as $key => $command_object) {
$command_object->delete();
unset($this->unprocessedCommands[$key]);
}
return $this;
}
public function canIssueCommand(PhabricatorUser $viewer, $command) {
try {
$this->assertCanIssueCommand($viewer, $command);
return true;
} catch (Exception $ex) {
return false;
}
}
public function assertCanIssueCommand(PhabricatorUser $viewer, $command) {
$need_edit = false;
switch ($command) {
case HarbormasterBuildCommand::COMMAND_RESTART:
break;
case HarbormasterBuildCommand::COMMAND_PAUSE:
case HarbormasterBuildCommand::COMMAND_RESUME:
case HarbormasterBuildCommand::COMMAND_ABORT:
$need_edit = true;
break;
default:
throw new Exception(
pht(
'Invalid Harbormaster build command "%s".',
$command));
}
// Issuing these commands requires that you be able to edit the build, to
// prevent enemy engineers from sabotaging your builds. See T9614.
if ($need_edit) {
PhabricatorPolicyFilter::requireCapability(
$viewer,
$this->getBuildPlan(),
PhabricatorPolicyCapability::CAN_EDIT);
}
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new HarbormasterBuildTransactionEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new HarbormasterBuildTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return $this->getBuildable()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getBuildable()->hasAutomaticCapability(
$capability,
$viewer);
}
public function describeAutomaticCapability($capability) {
return pht('A build inherits policies from its buildable.');
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('buildablePHID')
->setType('phid')
->setDescription(pht('PHID of the object this build is building.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('buildPlanPHID')
->setType('phid')
->setDescription(pht('PHID of the build plan being run.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('buildStatus')
->setType('map<string, wild>')
->setDescription(pht('The current status of this build.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('initiatorPHID')
->setType('phid')
->setDescription(pht('The person (or thing) that started this build.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('name')
->setType('string')
->setDescription(pht('The name of this build.')),
);
}
public function getFieldValuesForConduit() {
$status = $this->getBuildStatus();
return array(
'buildablePHID' => $this->getBuildablePHID(),
'buildPlanPHID' => $this->getBuildPlanPHID(),
'buildStatus' => array(
'value' => $status,
'name' => HarbormasterBuildStatus::getBuildStatusName($status),
'color.ansi' =>
HarbormasterBuildStatus::getBuildStatusANSIColor($status),
),
'initiatorPHID' => nonempty($this->getInitiatorPHID(), null),
'name' => $this->getName(),
);
}
public function getConduitSearchAttachments() {
return array(
id(new HarbormasterQueryBuildsSearchEngineAttachment())
->setAttachmentKey('querybuilds'),
);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Apr 29, 3:35 AM (21 h, 23 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
108183
Default Alt Text
(57 KB)

Event Timeline