Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2893562
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
32 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
index 170e4c8a5c..447bd53704 100644
--- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
+++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
@@ -1,605 +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();
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/plan/HarbormasterBuildPlanBehavior.php b/src/applications/harbormaster/plan/HarbormasterBuildPlanBehavior.php
index 63fc263fcb..690619f56e 100644
--- a/src/applications/harbormaster/plan/HarbormasterBuildPlanBehavior.php
+++ b/src/applications/harbormaster/plan/HarbormasterBuildPlanBehavior.php
@@ -1,373 +1,378 @@
<?php
final class HarbormasterBuildPlanBehavior
extends Phobject {
private $key;
private $name;
private $options;
private $defaultKey;
private $editInstructions;
const BEHAVIOR_RUNNABLE = 'runnable';
const RUNNABLE_IF_VIEWABLE = 'view';
const RUNNABLE_IF_EDITABLE = 'edit';
const BEHAVIOR_RESTARTABLE = 'restartable';
const RESTARTABLE_ALWAYS = 'always';
const RESTARTABLE_NEVER = 'never';
const BEHAVIOR_DRAFTS = 'hold-drafts';
const DRAFTS_ALWAYS = 'always';
const DRAFTS_IF_BUILDING = 'building';
const DRAFTS_NEVER = 'never';
+ const BEHAVIOR_BUILDABLE = 'buildable';
+ const BUILDABLE_ALWAYS = 'always';
+ const BUILDABLE_IF_BUILDING = 'building';
+ const BUILDABLE_NEVER = 'never';
+
public function setKey($key) {
$this->key = $key;
return $this;
}
public function getKey() {
return $this->key;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setEditInstructions($edit_instructions) {
$this->editInstructions = $edit_instructions;
return $this;
}
public function getEditInstructions() {
return $this->editInstructions;
}
public function getOptionMap() {
return mpull($this->options, 'getName', 'getKey');
}
public function setOptions(array $options) {
assert_instances_of($options, 'HarbormasterBuildPlanBehaviorOption');
$key_map = array();
$default = null;
foreach ($options as $option) {
$key = $option->getKey();
if (isset($key_map[$key])) {
throw new Exception(
pht(
'Multiple behavior options (for behavior "%s") have the same '.
'key ("%s"). Each option must have a unique key.',
$this->getKey(),
$key));
}
$key_map[$key] = true;
if ($option->getIsDefault()) {
if ($default === null) {
$default = $key;
} else {
throw new Exception(
pht(
'Multiple behavior options (for behavior "%s") are marked as '.
'default options ("%s" and "%s"). Exactly one option must be '.
'marked as the default option.',
$this->getKey(),
$default,
$key));
}
}
}
if ($default === null) {
throw new Exception(
pht(
'No behavior option is marked as the default option (for '.
'behavior "%s"). Exactly one option must be marked as the '.
'default option.',
$this->getKey()));
}
$this->options = mpull($options, null, 'getKey');
$this->defaultKey = $default;
return $this;
}
public function getOptions() {
return $this->options;
}
public function getPlanOption(HarbormasterBuildPlan $plan) {
$behavior_key = $this->getKey();
$storage_key = self::getStorageKeyForBehaviorKey($behavior_key);
$plan_value = $plan->getPlanProperty($storage_key);
if (isset($this->options[$plan_value])) {
return $this->options[$plan_value];
}
return idx($this->options, $this->defaultKey);
}
public static function getTransactionMetadataKey() {
return 'behavior-key';
}
public static function getStorageKeyForBehaviorKey($behavior_key) {
return sprintf('behavior.%s', $behavior_key);
}
public static function getBehavior($key) {
$behaviors = self::newPlanBehaviors();
if (!isset($behaviors[$key])) {
throw new Exception(
pht(
'No build plan behavior with key "%s" exists.',
$key));
}
return $behaviors[$key];
}
public static function newPlanBehaviors() {
$draft_options = array(
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::DRAFTS_ALWAYS)
->setIcon('fa-check-circle-o green')
->setName(pht('Always'))
->setIsDefault(true)
->setDescription(
pht(
'Revisions are not sent for review until the build completes, '.
'and are returned to the author for updates if the build fails.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::DRAFTS_IF_BUILDING)
->setIcon('fa-pause-circle-o yellow')
->setName(pht('If Building'))
->setDescription(
pht(
'Revisions are not sent for review until the build completes, '.
'but they will be sent for review even if it fails.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::DRAFTS_NEVER)
->setIcon('fa-circle-o red')
->setName(pht('Never'))
->setDescription(
pht(
'Revisions are sent for review regardless of the status of the '.
'build.')),
);
$land_options = array(
id(new HarbormasterBuildPlanBehaviorOption())
->setKey('always')
->setIcon('fa-check-circle-o green')
->setName(pht('Always'))
->setIsDefault(true)
->setDescription(
pht(
'"arc land" warns if the build is still running or has '.
'failed.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey('building')
->setIcon('fa-pause-circle-o yellow')
->setName(pht('If Building'))
->setDescription(
pht(
'"arc land" warns if the build is still running, but ignores '.
'the build if it has failed.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey('complete')
->setIcon('fa-dot-circle-o yellow')
->setName(pht('If Complete'))
->setDescription(
pht(
'"arc land" warns if the build has failed, but ignores the '.
'build if it is still running.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey('never')
->setIcon('fa-circle-o red')
->setName(pht('Never'))
->setDescription(
pht(
'"arc land" never warns that the build is still running or '.
'has failed.')),
);
$aggregate_options = array(
id(new HarbormasterBuildPlanBehaviorOption())
- ->setKey('always')
+ ->setKey(self::BUILDABLE_ALWAYS)
->setIcon('fa-check-circle-o green')
->setName(pht('Always'))
->setIsDefault(true)
->setDescription(
pht(
'The buildable waits for the build, and fails if the '.
'build fails.')),
id(new HarbormasterBuildPlanBehaviorOption())
- ->setKey('building')
+ ->setKey(self::BUILDABLE_IF_BUILDING)
->setIcon('fa-pause-circle-o yellow')
->setName(pht('If Building'))
->setDescription(
pht(
'The buildable waits for the build, but does not fail '.
'if the build fails.')),
id(new HarbormasterBuildPlanBehaviorOption())
- ->setKey('never')
+ ->setKey(self::BUILDABLE_NEVER)
->setIcon('fa-circle-o red')
->setName(pht('Never'))
->setDescription(
pht(
'The buildable does not wait for the build.')),
);
$restart_options = array(
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::RESTARTABLE_ALWAYS)
->setIcon('fa-repeat green')
->setName(pht('Always'))
->setIsDefault(true)
->setDescription(
pht('The build may be restarted.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::RESTARTABLE_NEVER)
->setIcon('fa-times red')
->setName(pht('Never'))
->setDescription(
pht('The build may not be restarted.')),
);
$run_options = array(
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::RUNNABLE_IF_EDITABLE)
->setIcon('fa-pencil green')
->setName(pht('If Editable'))
->setIsDefault(true)
->setDescription(
pht('Only users who can edit the plan can run it manually.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::RUNNABLE_IF_VIEWABLE)
->setIcon('fa-exclamation-triangle yellow')
->setName(pht('If Viewable'))
->setDescription(
pht(
'Any user who can view the plan can run it manually.')),
);
$behaviors = array(
id(new self())
->setKey(self::BEHAVIOR_DRAFTS)
->setName(pht('Hold Drafts'))
->setEditInstructions(
pht(
'When users create revisions in Differential, the default '.
'behavior is to hold them in the "Draft" state until all builds '.
'pass. Once builds pass, the revisions promote and are sent for '.
'review, which notifies reviewers.'.
"\n\n".
'The general intent of this workflow is to make sure reviewers '.
'are only spending time on review once changes survive automated '.
'tests. If a change does not pass tests, it usually is not '.
'really ready for review.'.
"\n\n".
'If you want to promote revisions out of "Draft" before builds '.
'pass, or promote revisions even when builds fail, you can '.
'change the promotion behavior. This may be useful if you have '.
'very long-running builds, or some builds which are not very '.
'important.'.
"\n\n".
'Users may always use "Request Review" to promote a "Draft" '.
'revision, even if builds have failed or are still in progress.'))
->setOptions($draft_options),
id(new self())
->setKey('arc-land')
->setName(pht('Warn When Landing'))
->setEditInstructions(
pht(
'When a user attempts to `arc land` a revision and that revision '.
'has ongoing or failed builds, the default behavior of `arc` is '.
'to warn them about those builds and give them a chance to '.
'reconsider: they may want to wait for ongoing builds to '.
'complete, or fix failed builds before landing the change.'.
"\n\n".
'If you do not want to warn users about this build, you can '.
'change the warning behavior. This may be useful if the build '.
'takes a long time to run (so you do not expect users to wait '.
'for it) or the outcome is not important.'.
"\n\n".
'This warning is only advisory. Users may always elect to ignore '.
'this warning and continue, even if builds have failed.'))
->setOptions($land_options),
id(new self())
- ->setKey('buildable')
+ ->setKey(self::BEHAVIOR_BUILDABLE)
->setEditInstructions(
pht(
'The overall state of a buildable (like a commit or revision) is '.
'normally the aggregation of the individual states of all builds '.
'that have run against it.'.
"\n\n".
'Buildables are "building" until all builds pass (which changes '.
'them to "pass"), or any build fails (which changes them to '.
'"fail").'.
"\n\n".
'You can change this behavior if you do not want to wait for this '.
'build, or do not care if it fails.'))
->setName(pht('Affects Buildable'))
->setOptions($aggregate_options),
id(new self())
->setKey(self::BEHAVIOR_RESTARTABLE)
->setEditInstructions(
pht(
'Usually, builds may be restarted. This may be useful if you '.
'suspect a build has failed for environmental or circumstantial '.
'reasons unrelated to the actual code, and want to give it '.
'another chance at glory.'.
"\n\n".
'If you want to prevent a build from being restarted, you can '.
'change the behavior here. This may be useful to prevent '.
'accidents where a build with a dangerous side effect (like '.
'deployment) is restarted improperly.'))
->setName(pht('Restartable'))
->setOptions($restart_options),
id(new self())
->setKey(self::BEHAVIOR_RUNNABLE)
->setEditInstructions(
pht(
'To run a build manually, you normally must have permission to '.
'edit the related build plan. If you would prefer that anyone who '.
'can see the build plan be able to run and restart the build, you '.
'can change the behavior here.'.
"\n\n".
'Note that this controls access to all build management actions: '.
'"Run Plan Manually", "Restart", "Abort", "Pause", and "Resume".'.
"\n\n".
'WARNING: This may be unsafe, particularly if the build has '.
'side effects like deployment.'.
"\n\n".
'If you weaken this policy, an attacker with control of an '.
'account that has "Can View" permission but not "Can Edit" '.
'permission can manually run this build against any old version '.
'of the code, including versions with known security issues.'.
"\n\n".
'If running the build has a side effect like deploying code, '.
'they can force deployment of a vulnerable version and then '.
'escalate into an attack against the deployed service.'))
->setName(pht('Runnable'))
->setOptions($run_options),
);
return mpull($behaviors, null, 'getKey');
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 18:38 (1 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1127504
Default Alt Text
(32 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment