Page MenuHomePhabricator

D8788.diff
No OneTemporary

D8788.diff

diff --git a/src/applications/harbormaster/conduit/ConduitAPI_harbormaster_sendmessage_Method.php b/src/applications/harbormaster/conduit/ConduitAPI_harbormaster_sendmessage_Method.php
--- a/src/applications/harbormaster/conduit/ConduitAPI_harbormaster_sendmessage_Method.php
+++ b/src/applications/harbormaster/conduit/ConduitAPI_harbormaster_sendmessage_Method.php
@@ -43,6 +43,13 @@
->setType($message_type)
->save();
+ // If the build has completely paused because all steps are blocked on
+ // waiting targets, this will resume it.
+ id(new HarbormasterBuildEngine())
+ ->setViewer($viewer)
+ ->setBuild($build_target->getBuild())
+ ->continueBuild();
+
return null;
}
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
--- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
@@ -251,15 +251,27 @@
switch ($target->getTargetStatus()) {
case HarbormasterBuildTarget::STATUS_PENDING:
$icon = 'time-green';
+ $status_name = pht('Pending');
+ break;
+ case HarbormasterBuildTarget::STATUS_BUILDING:
+ $icon = 'right-green';
+ $status_name = pht('Building');
+ break;
+ case HarbormasterBuildTarget::STATUS_WAITING:
+ $icon = 'time-orange';
+ $status_name = pht('Waiting');
break;
case HarbormasterBuildTarget::STATUS_PASSED:
$icon = 'accept-green';
+ $status_name = pht('Passed');
break;
case HarbormasterBuildTarget::STATUS_FAILED:
$icon = 'reject-red';
+ $status_name = pht('Failed');
break;
default:
$icon = 'question';
+ $status_name = pht('Unknown');
break;
}
@@ -272,7 +284,7 @@
$target_list->addItem(
id(new PHUIStatusItemView())
- ->setIcon($icon)
+ ->setIcon($icon, $status_name)
->setTarget(pht('Target %d', $target->getID()))
->setNote($name));
}
diff --git a/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php b/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php
--- a/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php
+++ b/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php
@@ -9,7 +9,41 @@
}
public function createFields($object) {
- $specs = $object->getStepImplementation()->getFieldSpecifications();
+ $impl = $object->getStepImplementation();
+ $specs = $impl->getFieldSpecifications();
+
+ if ($impl->supportsWaitForMessage()) {
+ $specs['builtin.next-steps-header'] = array(
+ 'type' => 'header',
+ 'name' => pht('Next Steps'),
+ );
+
+ $specs['builtin.wait-for-message'] = array(
+ 'type' => 'select',
+ 'name' => pht('When Complete'),
+ 'instructions' => pht(
+ 'After completing this build step Harbormaster can continue the '.
+ 'build normally, or it can pause the build and wait for a message. '.
+ 'If you are using this build step to trigger some work in an '.
+ 'external system, you may want to have Phabricator wait for that '.
+ 'system to perform the work and report results back.'.
+ "\n\n".
+ 'If you select **Continue Build Normally**, the build plan will '.
+ 'proceed once this step finishes.'.
+ "\n\n".
+ 'If you select **Wait For Message**, the build plan will pause '.
+ 'indefinitely once this step finishes. To resume the build, an '.
+ 'external system must call `harbormaster.sendmessage` with the '.
+ 'build target PHID, and either `"pass"` or `"fail"` to indicate '.
+ 'the result for this step. After the result is recorded, the build '.
+ 'plan will resume.'),
+ 'options' => array(
+ '' => pht('Continue Build Normally'),
+ 'wait' => pht('Wait For Message'),
+ ),
+ );
+ }
+
return PhabricatorStandardCustomField::buildStandardFields($this, $specs);
}
diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
--- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
+++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
@@ -127,6 +127,9 @@
->setViewer($this->getViewer())
->withBuildPHIDs(array($build->getPHID()))
->execute();
+
+ $this->updateWaitingTargets($targets);
+
$targets = mgroup($targets, 'getBuildStepPHID');
$steps = id(new HarbormasterBuildStepQuery())
@@ -134,15 +137,35 @@
->withBuildPlanPHIDs(array($build->getBuildPlan()->getPHID()))
->execute();
- // Identify steps which are complete.
+ // Identify steps which are in various states.
+ $queued = array();
+ $underway = array();
+ $waiting = array();
$complete = array();
$failed = array();
- $waiting = 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()) {
@@ -158,25 +181,33 @@
break;
}
}
-
- $is_waiting = false;
} else {
+ $is_queued = true;
+ $is_underway = false;
+ $is_waiting = false;
$is_complete = false;
$is_failed = false;
- $is_waiting = true;
}
- if ($is_complete) {
- $complete[$step->getPHID()] = true;
+ if ($is_queued) {
+ $queued[$step->getPHID()] = true;
}
- if ($is_failed) {
- $failed[$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.
@@ -209,7 +240,7 @@
$dependencies = array();
}
- if (isset($waiting[$step->getPHID()])) {
+ if (isset($queued[$step->getPHID()])) {
$can_run = true;
foreach ($dependencies as $dependency) {
if (empty($complete[$dependency->getPHID()])) {
@@ -226,9 +257,9 @@
$previous_step = $step;
}
- if (!$runnable) {
+ if (!$runnable && !$waiting && !$underway) {
// TODO: This means the build is deadlocked, probably? It should not
- // normally be possible, but we should communicate it more clearly.
+ // normally be possible yet, but we should communicate it more clearly.
$build->setBuildStatus(HarbormasterBuild::STATUS_FAILED);
$build->save();
return;
@@ -243,6 +274,60 @@
$this->queueNewBuildTarget($target);
}
+
+ }
+
+
+ /**
+ * 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()];
+
+ $new_status = null;
+ switch ($message->getType()) {
+ case 'pass':
+ $new_status = HarbormasterBuildTarget::STATUS_PASSED;
+ break;
+ case 'fail':
+ $new_status = HarbormasterBuildTarget::STATUS_FAILED;
+ break;
+ }
+
+ if ($new_status !== null) {
+ $message->setIsConsumed(true);
+ $message->save();
+
+ $target->setTargetStatus($new_status);
+ $target->save();
+ }
+ }
}
}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php b/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php
--- a/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php
@@ -83,7 +83,7 @@
$where[] = qsprintf(
$conn_r,
'isConsumed = %d',
- (int)$this->isConsumed);
+ (int)$this->consumed);
}
$where[] = $this->buildPagingClause($conn_r);
diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
--- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
+++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
@@ -196,4 +196,16 @@
}
}
+ public function supportsWaitForMessage() {
+ return false;
+ }
+
+ public function shouldWaitForMessage(HarbormasterBuildTarget $target) {
+ if (!$this->supportsWaitForMessage()) {
+ return false;
+ }
+
+ return (bool)$target->getDetail('builtin.wait-for-message');
+ }
+
}
diff --git a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php
--- a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php
+++ b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php
@@ -99,4 +99,8 @@
);
}
+ public function supportsWaitForMessage() {
+ return true;
+ }
+
}
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php
--- a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php
@@ -11,6 +11,8 @@
protected $targetStatus;
const STATUS_PENDING = 'target/pending';
+ const STATUS_BUILDING = 'target/building';
+ const STATUS_WAITING = 'target/waiting';
const STATUS_PASSED = 'target/passed';
const STATUS_FAILED = 'target/failed';
@@ -128,6 +130,26 @@
}
+ public function isWaiting() {
+ switch ($this->getTargetStatus()) {
+ case self::STATUS_WAITING:
+ return true;
+ }
+
+ return false;
+ }
+
+ public function isUnderway() {
+ switch ($this->getTargetStatus()) {
+ case self::STATUS_PENDING:
+ case self::STATUS_BUILDING:
+ return true;
+ }
+
+ return false;
+ }
+
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */
diff --git a/src/applications/harbormaster/worker/HarbormasterTargetWorker.php b/src/applications/harbormaster/worker/HarbormasterTargetWorker.php
--- a/src/applications/harbormaster/worker/HarbormasterTargetWorker.php
+++ b/src/applications/harbormaster/worker/HarbormasterTargetWorker.php
@@ -36,9 +36,21 @@
$viewer = $this->getViewer();
try {
+ $status_pending = HarbormasterBuildTarget::STATUS_PENDING;
+ if ($target->getTargetStatus() == $status_pending) {
+ $target->setTargetStatus(HarbormasterBuildTarget::STATUS_BUILDING);
+ $target->save();
+ }
+
$implementation = $target->getImplementation();
$implementation->execute($build, $target);
- $target->setTargetStatus(HarbormasterBuildTarget::STATUS_PASSED);
+
+ $next_status = HarbormasterBuildTarget::STATUS_PASSED;
+ if ($implementation->shouldWaitForMessage($target)) {
+ $next_status = HarbormasterBuildTarget::STATUS_WAITING;
+ }
+
+ $target->setTargetStatus($next_status);
$target->save();
} catch (Exception $ex) {
phlog($ex);

File Metadata

Mime Type
text/plain
Expires
Sun, Jan 19, 2:27 AM (2 h, 33 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7015768
Default Alt Text
D8788.diff (12 KB)

Event Timeline