Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
index 447bd53704..60d3040e33 100644
--- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
+++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
@@ -1,629 +1,629 @@
<?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();
+ $build->markUnprocessedMessagesAsProcessed();
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())
->withReceiverPHIDs(array_keys($waiting_targets))
->withConsumed(false)
->execute();
foreach ($messages as $message) {
$target = $waiting_targets[$message->getReceiverPHID()];
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
*/
public 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();
$messages = id(new HarbormasterBuildMessageQuery())
->setViewer($viewer)
->withReceiverPHIDs(array($buildable->getPHID()))
->withConsumed(false)
->execute();
$done_preparing = false;
$update_container = false;
foreach ($messages as $message) {
switch ($message->getType()) {
case HarbormasterMessageType::BUILDABLE_BUILD:
$done_preparing = true;
break;
case HarbormasterMessageType::BUILDABLE_CONTAINER:
$update_container = true;
break;
default:
break;
}
$message
->setIsConsumed(true)
->save();
}
// If we received a "build" command, all builds are scheduled and we can
// move out of "preparing" into "building".
if ($done_preparing) {
if ($buildable->isPreparing()) {
$buildable
->setBuildableStatus(HarbormasterBuildableStatus::STATUS_BUILDING)
->save();
}
}
// If we've been informed that the container for the buildable has
// changed, update it.
if ($update_container) {
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($buildable->getBuildablePHID()))
->executeOne();
if ($object) {
$buildable
->setContainerPHID($object->getHarbormasterContainerPHID())
->save();
}
}
$old = clone $buildable;
// Don't update the buildable status if we're still preparing builds: more
// builds may still be scheduled shortly, so even if every build we know
// about so far has passed, that doesn't mean the buildable has actually
// passed everything it needs to.
if (!$buildable->isPreparing()) {
$behavior_key = HarbormasterBuildPlanBehavior::BEHAVIOR_BUILDABLE;
$behavior = HarbormasterBuildPlanBehavior::getBehavior($behavior_key);
$key_never = HarbormasterBuildPlanBehavior::BUILDABLE_NEVER;
$key_building = HarbormasterBuildPlanBehavior::BUILDABLE_IF_BUILDING;
$all_pass = true;
$any_fail = false;
foreach ($buildable->getBuilds() as $build) {
$plan = $build->getBuildPlan();
$option = $behavior->getPlanOption($plan);
$option_key = $option->getKey();
$is_never = ($option_key === $key_never);
$is_building = ($option_key === $key_building);
// If this build "Never" affects the buildable, ignore it.
if ($is_never) {
continue;
}
// If this build affects the buildable "If Building", but is already
// complete, ignore it.
if ($is_building && $build->isComplete()) {
continue;
}
if (!$build->isPassed()) {
$all_pass = false;
}
if ($build->isComplete() && !$build->isPassed()) {
$any_fail = true;
}
}
if ($any_fail) {
$new_status = HarbormasterBuildableStatus::STATUS_FAILED;
} else if ($all_pass) {
$new_status = HarbormasterBuildableStatus::STATUS_PASSED;
} else {
$new_status = HarbormasterBuildableStatus::STATUS_BUILDING;
}
$did_update = ($old->getBuildableStatus() !== $new_status);
if ($did_update) {
$buildable->setBuildableStatus($new_status);
$buildable->save();
}
}
$lock->unlock();
// Don't publish anything if we're still preparing builds.
if ($buildable->isPreparing()) {
return;
}
$this->publishBuildable($old, $buildable);
}
public function publishBuildable(
HarbormasterBuildable $old,
HarbormasterBuildable $new) {
$viewer = $this->getViewer();
// Publish the buildable. We publish buildables even if they haven't
// changed status in Harbormaster because applications may care about
// different things than Harbormaster does. For example, Differential
// does not care about local lint and unit tests when deciding whether
// a revision should move out of draft or not.
// NOTE: We're publishing both automatic and manual buildables. Buildable
// objects should generally ignore manual buildables, but it's up to them
// to decide.
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($new->getBuildablePHID()))
->executeOne();
if (!$object) {
return;
}
$engine = HarbormasterBuildableEngine::newForObject($object, $viewer);
$daemon_source = PhabricatorContentSource::newForSource(
PhabricatorDaemonContentSource::SOURCECONST);
$harbormaster_phid = id(new PhabricatorHarbormasterApplication())
->getPHID();
$engine
->setActingAsPHID($harbormaster_phid)
->setContentSource($daemon_source)
->publishBuildable($old, $new);
}
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/query/HarbormasterBuildQuery.php b/src/applications/harbormaster/query/HarbormasterBuildQuery.php
index 770ca4413b..3e474bd81d 100644
--- a/src/applications/harbormaster/query/HarbormasterBuildQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildQuery.php
@@ -1,233 +1,233 @@
<?php
final class HarbormasterBuildQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $buildStatuses;
private $buildablePHIDs;
private $buildPlanPHIDs;
private $initiatorPHIDs;
private $needBuildTargets;
private $autobuilds;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withBuildStatuses(array $build_statuses) {
$this->buildStatuses = $build_statuses;
return $this;
}
public function withBuildablePHIDs(array $buildable_phids) {
$this->buildablePHIDs = $buildable_phids;
return $this;
}
public function withBuildPlanPHIDs(array $build_plan_phids) {
$this->buildPlanPHIDs = $build_plan_phids;
return $this;
}
public function withInitiatorPHIDs(array $initiator_phids) {
$this->initiatorPHIDs = $initiator_phids;
return $this;
}
public function withAutobuilds($with_autobuilds) {
$this->autobuilds = $with_autobuilds;
return $this;
}
public function needBuildTargets($need_targets) {
$this->needBuildTargets = $need_targets;
return $this;
}
public function newResultObject() {
return new HarbormasterBuild();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function willFilterPage(array $page) {
$buildables = array();
$buildable_phids = array_filter(mpull($page, 'getBuildablePHID'));
if ($buildable_phids) {
$buildables = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withPHIDs($buildable_phids)
->setParentQuery($this)
->execute();
$buildables = mpull($buildables, null, 'getPHID');
}
foreach ($page as $key => $build) {
$buildable_phid = $build->getBuildablePHID();
if (empty($buildables[$buildable_phid])) {
unset($page[$key]);
continue;
}
$build->attachBuildable($buildables[$buildable_phid]);
}
return $page;
}
protected function didFilterPage(array $page) {
$plans = array();
$plan_phids = array_filter(mpull($page, 'getBuildPlanPHID'));
if ($plan_phids) {
$plans = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withPHIDs($plan_phids)
->setParentQuery($this)
->execute();
$plans = mpull($plans, null, 'getPHID');
}
foreach ($page as $key => $build) {
$plan_phid = $build->getBuildPlanPHID();
$build->attachBuildPlan(idx($plans, $plan_phid));
}
$build_phids = mpull($page, 'getPHID');
- $commands = id(new HarbormasterBuildCommand())->loadAllWhere(
+ $messages = id(new HarbormasterBuildCommand())->loadAllWhere(
'targetPHID IN (%Ls) ORDER BY id ASC',
$build_phids);
- $commands = mgroup($commands, 'getTargetPHID');
+ $messages = mgroup($messages, 'getTargetPHID');
foreach ($page as $build) {
- $unprocessed_commands = idx($commands, $build->getPHID(), array());
- $build->attachUnprocessedCommands($unprocessed_commands);
+ $unprocessed_messages = idx($messages, $build->getPHID(), array());
+ $build->attachUnprocessedMessages($unprocessed_messages);
}
if ($this->needBuildTargets) {
$targets = id(new HarbormasterBuildTargetQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withBuildPHIDs($build_phids)
->execute();
// TODO: Some day, when targets have dependencies, we should toposort
// these. For now, just put them into chronological order.
$targets = array_reverse($targets);
$targets = mgroup($targets, 'getBuildPHID');
foreach ($page as $build) {
$build_targets = idx($targets, $build->getPHID(), array());
foreach ($build_targets as $phid => $target) {
if ($target->getBuildGeneration() !== $build->getBuildGeneration()) {
unset($build_targets[$phid]);
}
}
$build->attachBuildTargets($build_targets);
}
}
return $page;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'b.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'b.phid in (%Ls)',
$this->phids);
}
if ($this->buildStatuses !== null) {
$where[] = qsprintf(
$conn,
'b.buildStatus in (%Ls)',
$this->buildStatuses);
}
if ($this->buildablePHIDs !== null) {
$where[] = qsprintf(
$conn,
'b.buildablePHID IN (%Ls)',
$this->buildablePHIDs);
}
if ($this->buildPlanPHIDs !== null) {
$where[] = qsprintf(
$conn,
'b.buildPlanPHID IN (%Ls)',
$this->buildPlanPHIDs);
}
if ($this->initiatorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'b.initiatorPHID IN (%Ls)',
$this->initiatorPHIDs);
}
if ($this->autobuilds !== null) {
if ($this->autobuilds) {
$where[] = qsprintf(
$conn,
'p.planAutoKey IS NOT NULL');
} else {
$where[] = qsprintf(
$conn,
'p.planAutoKey IS NULL');
}
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinPlanTable()) {
$joins[] = qsprintf(
$conn,
'JOIN %T p ON b.buildPlanPHID = p.phid',
id(new HarbormasterBuildPlan())->getTableName());
}
return $joins;
}
private function shouldJoinPlanTable() {
if ($this->autobuilds !== null) {
return true;
}
return false;
}
public function getQueryApplicationClass() {
return 'PhabricatorHarbormasterApplication';
}
protected function getPrimaryTableAlias() {
return 'b';
}
}
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
index 7af5af092f..292ccd99cf 100644
--- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
@@ -1,602 +1,608 @@
<?php
final class HarbormasterBuild extends HarbormasterDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorConduitResultInterface,
PhabricatorDestructibleInterface {
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;
+ private $unprocessedMessages = 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();
+ $this->deleteUnprocessedMessages();
$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->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,
'buildable.phid' => null,
'buildable.object.phid' => null,
'buildable.container.phid' => null,
'build.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();
$results['buildable.phid'] = $buildable->getPHID();
$results['buildable.object.phid'] = $object->getPHID();
$results['buildable.container.phid'] = $buildable->getContainerPHID();
$results['build.phid'] = $this->getPHID();
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.'),
'buildable.phid' => pht(
'The object PHID of the Harbormaster Buildable being built.'),
'buildable.object.phid' => pht(
'The object PHID of the object (usually a diff or commit) '.
'being built.'),
'buildable.container.phid' => pht(
'The object PHID of the container (usually a revision or repository) '.
'for the object being built.'),
'build.phid' => pht(
'The object PHID of the Harbormaster Build being built.'),
);
foreach ($objects as $object) {
$variables[] = $object->getAvailableBuildVariables();
}
$variables = array_mergev($variables);
return $variables;
}
public function isComplete() {
return $this->getBuildStatusObject()->isComplete();
}
public function isPaused() {
return $this->getBuildStatusObject()->isPaused();
}
public function isPassed() {
return $this->getBuildStatusObject()->isPassed();
}
public function isFailed() {
return $this->getBuildStatusObject()->isFailed();
}
public function getURI() {
$id = $this->getID();
return "/harbormaster/build/{$id}/";
}
protected function getBuildStatusObject() {
$status_key = $this->getBuildStatus();
return HarbormasterBuildStatus::newBuildStatusObject($status_key);
}
public function getObjectName() {
return pht('Build %d', $this->getID());
}
-/* -( Build Commands )----------------------------------------------------- */
+/* -( Build Messages )----------------------------------------------------- */
- private function getUnprocessedCommands() {
- return $this->assertAttached($this->unprocessedCommands);
+ private function getUnprocessedMessages() {
+ return $this->assertAttached($this->unprocessedMessages);
}
- public function attachUnprocessedCommands(array $commands) {
- $this->unprocessedCommands = $commands;
+ public function attachUnprocessedMessages(array $messages) {
+ $this->unprocessedMessages = $messages;
return $this;
}
public function canRestartBuild() {
try {
$this->assertCanRestartBuild();
return true;
} catch (HarbormasterRestartException $ex) {
return false;
}
}
public function assertCanRestartBuild() {
if ($this->isAutobuild()) {
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();
// See T13526. Users who can't see the "BuildPlan" can end up here with
// no object. This is highly questionable.
if (!$plan) {
throw new HarbormasterRestartException(
pht('No Build Plan Permission'),
pht(
'You can not restart this build because you do not have '.
'permission to access the build plan.'));
}
$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.'));
}
$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() {
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) {
+ foreach ($this->getUnprocessedMessages() as $message_object) {
+ $message_type = $message_object->getCommand();
+ switch ($message_type) {
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) {
+ foreach ($this->getUnprocessedMessages() as $message_object) {
+ $message_type = $message_object->getCommand();
+ switch ($message_type) {
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) {
+ foreach ($this->getUnprocessedMessages() as $message_object) {
+ $message_type = $message_object->getCommand();
+ switch ($message_type) {
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) {
+ foreach ($this->getUnprocessedMessages() as $message_object) {
+ $message_type = $message_object->getCommand();
+ switch ($message_type) {
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]);
+ public function markUnprocessedMessagesAsProcessed() {
+ // TODO: See T13072. This is a placeholder until BuildCommand and
+ // BuildMessage merge.
+ return $this->deleteUnprocessedMessages();
+ }
+
+ public function deleteUnprocessedMessages() {
+ foreach ($this->getUnprocessedMessages() as $key => $message_object) {
+ $message_object->delete();
+ unset($this->unprocessedMessages[$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) {
$plan = $this->getBuildPlan();
// See T13526. Users without permission to access the build plan can
// currently end up here with no "BuildPlan" object.
if (!$plan) {
return false;
}
$need_edit = true;
switch ($command) {
case HarbormasterBuildCommand::COMMAND_RESTART:
case HarbormasterBuildCommand::COMMAND_PAUSE:
case HarbormasterBuildCommand::COMMAND_RESUME:
case HarbormasterBuildCommand::COMMAND_ABORT:
if ($plan->canRunWithoutEditCapability()) {
$need_edit = false;
}
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,
$plan,
PhabricatorPolicyCapability::CAN_EDIT);
}
}
- public function sendMessage(PhabricatorUser $viewer, $command) {
+ public function sendMessage(PhabricatorUser $viewer, $message_type) {
// TODO: This should not be an editor transaction, but there are plans to
// merge BuildCommand into BuildMessage which should moot this. As this
// exists today, it can race against BuildEngine.
// This is a bogus content source, but this whole flow should be obsolete
// soon.
$content_source = PhabricatorContentSource::newForSource(
PhabricatorConsoleContentSource::SOURCECONST);
$editor = id(new HarbormasterBuildTransactionEditor())
->setActor($viewer)
->setContentSource($content_source)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$viewer_phid = $viewer->getPHID();
if (!$viewer_phid) {
$acting_phid = id(new PhabricatorHarbormasterApplication())->getPHID();
$editor->setActingAsPHID($acting_phid);
}
$xaction = id(new HarbormasterBuildTransaction())
->setTransactionType(HarbormasterBuildTransaction::TYPE_COMMAND)
- ->setNewValue($command);
+ ->setNewValue($message_type);
$editor->applyTransactions($this, array($xaction));
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new HarbormasterBuildTransactionEditor();
}
public function getApplicationTransactionTemplate() {
return new HarbormasterBuildTransaction();
}
/* -( 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'),
);
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$viewer = $engine->getViewer();
$this->openTransaction();
$targets = id(new HarbormasterBuildTargetQuery())
->setViewer($viewer)
->withBuildPHIDs(array($this->getPHID()))
->execute();
foreach ($targets as $target) {
$engine->destroyObject($target);
}
$messages = id(new HarbormasterBuildMessageQuery())
->setViewer($viewer)
->withReceiverPHIDs(array($this->getPHID()))
->execute();
foreach ($messages as $message) {
$engine->destroyObject($message);
}
$this->delete();
$this->saveTransaction();
}
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Nov 26, 7:38 PM (17 h, 47 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
420134
Default Alt Text
(45 KB)

Event Timeline