Page MenuHomePhorge

No OneTemporary

diff --git a/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php
index fc83b7ab42..a7ee75b2a1 100644
--- a/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php
+++ b/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php
@@ -1,273 +1,690 @@
<?php
final class HarbormasterSendMessageConduitAPIMethod
extends HarbormasterConduitAPIMethod {
public function getAPIMethodName() {
return 'harbormaster.sendmessage';
}
public function getMethodSummary() {
return pht(
- 'Send a message about the status of a build target to Harbormaster, '.
- 'notifying the application of build results in an external system.');
+ 'Modify running builds, and report build results.');
}
public function getMethodDescription() {
+ return pht(<<<EOREMARKUP
+Pause, abort, restart, and report results for builds.
+EOREMARKUP
+ );
+ }
+
+ protected function newDocumentationPages(PhabricatorUser $viewer) {
+ $pages = array();
+
+ $pages[] = $this->newSendingDocumentationBoxPage($viewer);
+ $pages[] = $this->newBuildsDocumentationBoxPage($viewer);
+ $pages[] = $this->newCommandsDocumentationBoxPage($viewer);
+ $pages[] = $this->newTargetsDocumentationBoxPage($viewer);
+ $pages[] = $this->newUnitDocumentationBoxPage($viewer);
+ $pages[] = $this->newLintDocumentationBoxPage($viewer);
+
+ return $pages;
+ }
+
+ private function newSendingDocumentationBoxPage(PhabricatorUser $viewer) {
+ $title = pht('Sending Messages');
+ $content = pht(<<<EOREMARKUP
+Harbormaster build objects work somewhat differently from objects in many other
+applications. Most application objects can be edited directly using synchronous
+APIs (like `maniphest.edit`, `differential.revision.edit`, and so on).
+
+However, builds require long-running background processing and Habormaster
+objects have a more complex lifecycle than most other application objects and
+may spend significant periods of time locked by daemon processes during build
+execition. A synchronous edit might need to wait an arbitrarily long amount of
+time for this lock to become available so the edit could be applied.
+
+Additionally, some edits may also require an arbitrarily long amount of time to
+//complete//. For example, aborting a build may execute cleanup steps which
+take minutes (or even hours) to complete.
+
+Since a synchronous API could not guarantee it could return results to the
+caller in a reasonable amount of time, the edit API for Harbormaster build
+objects is asynchronous: to update a Harbormaster build or build target, use
+this API (`harbormaster.sendmessage`) to send it a message describing an edit
+you would like to effect or additional information you want to provide.
+The message will be processed by the daemons once the build or target reaches
+a suitable state to receive messages.
+
+Select an object to send a message to using the `receiver` parameter. This
+API method can send messages to multiple types of objects:
+
+<table>
+ <tr>
+ <th>Object Type</th>
+ <th>PHID Example</th>
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td>Harbormaster Buildable</td>
+ <td>`PHID-HMBB-...`</td>
+ <td>%s</td>
+ </tr>
+ <tr>
+ <td>Harbormaster Build</td>
+ <td>`PHID-HMBD-...`</td>
+ <td>%s</td>
+ </tr>
+ <tr>
+ <td>Harbormaster Build Target</td>
+ <td>`PHID-HMBT-...`</td>
+ <td>%s</td>
+ </tr>
+</table>
+
+See below for specifics on sending messages to different object types.
+EOREMARKUP
+ ,
+ pht(
+ 'Buildables may receive control commands like "abort" and "restart". '.
+ 'Sending a control command to a Buildable is the same as sending it '.
+ 'to each Build for the Buildable.'),
+ pht(
+ 'Builds may receive control commands like "pause", "resume", "abort", '.
+ 'and "restart".'),
+ pht(
+ 'Build Targets may receive build status and result messages, like '.
+ '"pass" or "fail".'));
+
+ $content = $this->newRemarkupDocumentationView($content);
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('sending')
+ ->setIconIcon('fa-envelope-o');
+ }
+
+ private function newBuildsDocumentationBoxPage(PhabricatorUser $viewer) {
+ $title = pht('Updating Builds');
+
+ $content = pht(<<<EOREMARKUP
+You can use this method (`harbormaster.sendmessage`) to send control commands
+to Buildables and Builds.
+
+Specify the Build or Buildable to receive the control command by providing its
+PHID in the `receiver` parameter.
+
+Sending a control command to a Buildable has the same effect as sending it to
+each Build for the Buildable. For example, sending a "Pause" message to a
+Buildable will pause all builds for the Buildable (or at least attempt to).
+
+When sending control commands, the `unit` and `lint` parameters of this API
+method must be omitted. You can not report lint or unit results directly to
+a Build or Buildable, and can not report them alongside a control command.
+
+More broadly, you can not report build results directly to a Build or
+Buildable. Instead, report results to a Build Target.
+
+See below for a list of control commands.
+
+EOREMARKUP
+ );
+
+ $content = $this->newRemarkupDocumentationView($content);
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('builds')
+ ->setIconIcon('fa-cubes');
+ }
+
+ private function newCommandsDocumentationBoxPage(PhabricatorUser $viewer) {
+ $messages = HarbormasterBuildMessageTransaction::getAllMessages();
+
+ $rows = array();
+
+ $rows[] = '<tr>';
+ $rows[] = '<th>'.pht('Message Type').'</th>';
+ $rows[] = '<th>'.pht('Description').'</th>';
+ $rows[] = '</tr>';
+
+ foreach ($messages as $message) {
+ $row = array();
+
+ $row[] = sprintf(
+ '<td>`%s`</td>',
+ $message->getHarbormasterBuildMessageType());
+
+ $row[] = sprintf(
+ '<td>%s</td>',
+ $message->getHarbormasterBuildMessageDescription());
+
+ $rows[] = sprintf(
+ '<tr>%s</tr>',
+ implode("\n", $row));
+ }
+
+ $message_table = sprintf(
+ '<table>%s</table>',
+ implode("\n", $rows));
+
+ $title = pht('Control Commands');
+
+ $content = pht(<<<EOREMARKUP
+You can use this method to send control commands to Buildables and Builds.
+
+This table summarizes which object types may receive control commands:
+
+<table>
+ <tr>
+ <th>Object Type</th>
+ <th>PHID Example</th>
+ <th />
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td>Harbormaster Buildable</td>
+ <td>`PHID-HMBB-...`</td>
+ <td>{icon check color=green}</td>
+ <td>Buildables may receive control commands.</td>
+ </tr>
+ <tr>
+ <td>Harbormaster Build</td>
+ <td>`PHID-HMBD-...`</td>
+ <td>{icon check color=green}</td>
+ <td>Builds may receive control commands.</td>
+ </tr>
+ <tr>
+ <td>Harbormaster Build Target</td>
+ <td>`PHID-HMBT-...`</td>
+ <td>{icon times color=red}</td>
+ <td>You may **NOT** send control commands to build targets.</td>
+ </tr>
+</table>
+
+You can send these commands:
+
+%s
+
+To send a command message, specify the PHID of the object you would like to
+receive the message using the `receiver` parameter, and specify the message
+type using the `type` parameter.
+
+EOREMARKUP
+ ,
+ $message_table);
+
+ $content = $this->newRemarkupDocumentationView($content);
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('commands')
+ ->setIconIcon('fa-exclamation-triangle');
+ }
+
+ private function newTargetsDocumentationBoxPage(PhabricatorUser $viewer) {
$messages = HarbormasterMessageType::getAllMessages();
- $head_type = pht('Constant');
- $head_desc = pht('Description');
- $head_key = pht('Key');
$head_type = pht('Type');
- $head_name = pht('Name');
+ $head_desc = pht('Description');
$rows = array();
$rows[] = "| {$head_type} | {$head_desc} |";
$rows[] = '|--------------|--------------|';
foreach ($messages as $message) {
$description = HarbormasterMessageType::getMessageDescription($message);
$rows[] = "| `{$message}` | {$description} |";
}
$message_table = implode("\n", $rows);
+ $content = pht(<<<EOREMARKUP
+If you run external builds, you can use this method to publish build results
+back into Harbormaster after the external system finishes work (or as it makes
+progress).
+
+To report build status or results, you must send a message to the appropriate
+Build Target. This table summarizes which object types may receive build status
+and result messages:
+
+<table>
+ <tr>
+ <th>Object Type</th>
+ <th>PHID Example</th>
+ <th />
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td>Harbormaster Buildable</td>
+ <td>`PHID-HMBB-...`</td>
+ <td>{icon times color=red}</td>
+ <td>Buildables may **NOT** receive status or result messages.</td>
+ </tr>
+ <tr>
+ <td>Harbormaster Build</td>
+ <td>`PHID-HMBD-...`</td>
+ <td>{icon times color=red}</td>
+ <td>Builds may **NOT** receive status or result messages.</td>
+ </tr>
+ <tr>
+ <td>Harbormaster Build Target</td>
+ <td>`PHID-HMBT-...`</td>
+ <td>{icon check color=green}</td>
+ <td>Report build status and results to Build Targets.</td>
+ </tr>
+</table>
+
+The simplest way to use this method to report build results is to call it once
+after the build finishes with a `pass` or `fail` message. This will record the
+build result, and continue the next step in the build if the build was waiting
+for a result.
+
+When you send a status message about a build target, you can optionally include
+detailed `lint` or `unit` results alongside the message. See below for details.
+
+If you want to report intermediate results but a build hasn't completed yet,
+you can use the `work` message. This message doesn't have any direct effects,
+but allows you to send additional data to update the progress of the build
+target. The target will continue waiting for a completion message, but the UI
+will update to show the progress which has been made.
+
+When sending a message to a build target to report the status or results of
+a build, your message must include a `type` which describes the overall state
+of the build. For example, use `pass` to tell Harbormaster that a build target
+completed successfully.
+
+Supported message types are:
+
+%s
+
+EOREMARKUP
+ ,
+ $message_table);
+
+ $title = pht('Updating Build Targets');
+
+ $content = $this->newRemarkupDocumentationView($content);
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('targets')
+ ->setIconIcon('fa-bullseye');
+ }
+
+ private function newUnitDocumentationBoxPage(PhabricatorUser $viewer) {
+ $head_key = pht('Key');
+ $head_desc = pht('Description');
+ $head_name = pht('Name');
+ $head_type = pht('Type');
+
$rows = array();
$rows[] = "| {$head_key} | {$head_type} | {$head_desc} |";
$rows[] = '|-------------|--------------|--------------|';
$unit_spec = HarbormasterBuildUnitMessage::getParameterSpec();
foreach ($unit_spec as $key => $parameter) {
$type = idx($parameter, 'type');
$type = str_replace('|', ' '.pht('or').' ', $type);
$description = idx($parameter, 'description');
$rows[] = "| `{$key}` | //{$type}// | {$description} |";
}
$unit_table = implode("\n", $rows);
$rows = array();
$rows[] = "| {$head_key} | {$head_name} | {$head_desc} |";
$rows[] = '|-------------|--------------|--------------|';
$results = ArcanistUnitTestResult::getAllResultCodes();
foreach ($results as $result_code) {
$name = ArcanistUnitTestResult::getResultCodeName($result_code);
$description = ArcanistUnitTestResult::getResultCodeDescription(
$result_code);
$rows[] = "| `{$result_code}` | **{$name}** | {$description} |";
}
$result_table = implode("\n", $rows);
+ $valid_unit = array(
+ array(
+ 'name' => 'PassingTest',
+ 'result' => ArcanistUnitTestResult::RESULT_PASS,
+ ),
+ array(
+ 'name' => 'FailingTest',
+ 'result' => ArcanistUnitTestResult::RESULT_FAIL,
+ ),
+ );
+
+ $json = new PhutilJSON();
+ $valid_unit = $json->encodeAsList($valid_unit);
+
+
+ $title = pht('Reporting Unit Results');
+
+ $content = pht(<<<EOREMARKUP
+You can report test results when updating the state of a build target. The
+simplest way to do this is to report all the results alongside a `pass` or
+`fail` message, but you can also send a `work` message to report intermediate
+results.
+
+
+To provide unit test results, pass a list of results in the `unit`
+parameter. Each result should be a dictionary with these keys:
+
+%s
+
+The `result` parameter recognizes these test results:
+
+%s
+
+This is a simple, valid value for the `unit` parameter. It reports one passing
+test and one failing test:
+
+```lang=json
+%s
+```
+EOREMARKUP
+ ,
+ $unit_table,
+ $result_table,
+ $valid_unit);
+
+ $content = $this->newRemarkupDocumentationView($content);
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('unit');
+ }
+
+ private function newLintDocumentationBoxPage(PhabricatorUser $viewer) {
+
+ $head_key = pht('Key');
+ $head_desc = pht('Description');
+ $head_name = pht('Name');
+ $head_type = pht('Type');
+
$rows = array();
$rows[] = "| {$head_key} | {$head_type} | {$head_desc} |";
$rows[] = '|-------------|--------------|--------------|';
$lint_spec = HarbormasterBuildLintMessage::getParameterSpec();
foreach ($lint_spec as $key => $parameter) {
$type = idx($parameter, 'type');
$type = str_replace('|', ' '.pht('or').' ', $type);
$description = idx($parameter, 'description');
$rows[] = "| `{$key}` | //{$type}// | {$description} |";
}
$lint_table = implode("\n", $rows);
$rows = array();
$rows[] = "| {$head_key} | {$head_name} |";
$rows[] = '|-------------|--------------|';
$severities = ArcanistLintSeverity::getLintSeverities();
foreach ($severities as $key => $name) {
$rows[] = "| `{$key}` | **{$name}** |";
}
$severity_table = implode("\n", $rows);
- $valid_unit = array(
- array(
- 'name' => 'PassingTest',
- 'result' => ArcanistUnitTestResult::RESULT_PASS,
- ),
- array(
- 'name' => 'FailingTest',
- 'result' => ArcanistUnitTestResult::RESULT_FAIL,
- ),
- );
-
$valid_lint = array(
array(
'name' => pht('Syntax Error'),
'code' => 'EXAMPLE1',
'severity' => ArcanistLintSeverity::SEVERITY_ERROR,
'path' => 'path/to/example.c',
'line' => 17,
'char' => 3,
),
array(
'name' => pht('Not A Haiku'),
'code' => 'EXAMPLE2',
'severity' => ArcanistLintSeverity::SEVERITY_ERROR,
'path' => 'path/to/source.cpp',
'line' => 23,
'char' => 1,
'description' => pht(
'This function definition is not a haiku.'),
),
);
$json = new PhutilJSON();
- $valid_unit = $json->encodeAsList($valid_unit);
$valid_lint = $json->encodeAsList($valid_lint);
- return pht(
- "Send a message about the status of a build target to Harbormaster, ".
- "notifying the application of build results in an external system.".
- "\n\n".
- "Sending Messages\n".
- "================\n".
- "If you run external builds, you can use this method to publish build ".
- "results back into Harbormaster after the external system finishes work ".
- "or as it makes progress.".
- "\n\n".
- "The simplest way to use this method is to call it once after the ".
- "build finishes with a `pass` or `fail` message. This will record the ".
- "build result, and continue the next step in the build if the build was ".
- "waiting for a result.".
- "\n\n".
- "When you send a status message about a build target, you can ".
- "optionally include detailed `lint` or `unit` results alongside the ".
- "message. See below for details.".
- "\n\n".
- "If you want to report intermediate results but a build hasn't ".
- "completed yet, you can use the `work` message. This message doesn't ".
- "have any direct effects, but allows you to send additional data to ".
- "update the progress of the build target. The target will continue ".
- "waiting for a completion message, but the UI will update to show the ".
- "progress which has been made.".
- "\n\n".
- "Message Types\n".
- "=============\n".
- "When you send Harbormaster a message, you must include a `type`, ".
- "which describes the overall state of the build. For example, use ".
- "`pass` to tell Harbormaster that a build completed successfully.".
- "\n\n".
- "Supported message types are:".
- "\n\n".
- "%s".
- "\n\n".
- "Unit Results\n".
- "============\n".
- "You can report test results alongside a message. The simplest way to ".
- "do this is to report all the results alongside a `pass` or `fail` ".
- "message, but you can also send a `work` message to report intermediate ".
- "results.\n\n".
- "To provide unit test results, pass a list of results in the `unit` ".
- "parameter. Each result should be a dictionary with these keys:".
- "\n\n".
- "%s".
- "\n\n".
- "The `result` parameter recognizes these test results:".
- "\n\n".
- "%s".
- "\n\n".
- "This is a simple, valid value for the `unit` parameter. It reports ".
- "one passing test and one failing test:\n\n".
- "\n\n".
- "```lang=json\n".
- "%s".
- "```".
- "\n\n".
- "Lint Results\n".
- "============\n".
- "Like unit test results, you can report lint results alongside a ".
- "message. The `lint` parameter should contain results as a list of ".
- "dictionaries with these keys:".
- "\n\n".
- "%s".
- "\n\n".
- "The `severity` parameter recognizes these severity levels:".
- "\n\n".
- "%s".
- "\n\n".
- "This is a simple, valid value for the `lint` parameter. It reports one ".
- "error and one warning:".
- "\n\n".
- "```lang=json\n".
- "%s".
- "```".
- "\n\n",
- $message_table,
- $unit_table,
- $result_table,
- $valid_unit,
+ $title = pht('Reporting Lint Results');
+ $content = pht(<<<EOREMARKUP
+Like unit test results, you can report lint results when updating the state
+of a build target. The `lint` parameter should contain results as a list of
+dictionaries with these keys:
+
+%s
+
+The `severity` parameter recognizes these severity levels:
+
+%s
+
+This is a simple, valid value for the `lint` parameter. It reports one error
+and one warning:
+
+```lang=json
+%s
+```
+
+EOREMARKUP
+ ,
$lint_table,
$severity_table,
$valid_lint);
+
+ $content = $this->newRemarkupDocumentationView($content);
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('lint');
}
protected function defineParamTypes() {
$messages = HarbormasterMessageType::getAllMessages();
+
+ $more_messages = HarbormasterBuildMessageTransaction::getAllMessages();
+ $more_messages = mpull($more_messages, 'getHarbormasterBuildMessageType');
+
+ $messages = array_merge($messages, $more_messages);
+ $messages = array_unique($messages);
+
+ sort($messages);
+
$type_const = $this->formatStringConstants($messages);
return array(
- 'buildTargetPHID' => 'required phid',
+ 'receiver' => 'required string|phid',
'type' => 'required '.$type_const,
'unit' => 'optional list<wild>',
'lint' => 'optional list<wild>',
+ 'buildTargetPHID' => 'deprecated optional phid',
);
}
protected function defineReturnType() {
return 'void';
}
protected function execute(ConduitAPIRequest $request) {
- $viewer = $request->getUser();
+ $viewer = $request->getViewer();
+
+ $receiver_name = $request->getValue('receiver');
$build_target_phid = $request->getValue('buildTargetPHID');
+ if ($build_target_phid !== null) {
+ if ($receiver_name === null) {
+ $receiver_name = $build_target_phid;
+ } else {
+ throw new Exception(
+ pht(
+ 'Call specifies both "receiver" and "buildTargetPHID". '.
+ 'When using the modern "receiver" parameter, omit the '.
+ 'deprecated "buildTargetPHID" parameter.'));
+ }
+ }
+
+ if (!strlen($receiver_name)) {
+ throw new Exception(
+ pht(
+ 'Call omits required "receiver" parameter. Specify the PHID '.
+ 'of the object you want to send a message to.'));
+ }
+
$message_type = $request->getValue('type');
+ if (!strlen($message_type)) {
+ throw new Exception(
+ pht(
+ 'Call omits required "type" parameter. Specify the type of '.
+ 'message you want to send.'));
+ }
- $build_target = id(new HarbormasterBuildTargetQuery())
+ $receiver_object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
- ->withPHIDs(array($build_target_phid))
+ ->withNames(array($receiver_name))
->executeOne();
- if (!$build_target) {
- throw new Exception(pht('No such build target!'));
+ if (!$receiver_object) {
+ throw new Exception(
+ pht(
+ 'Unable to load object "%s" to receive message.',
+ $receiver_name));
+ }
+
+ $is_target = ($receiver_object instanceof HarbormasterBuildTarget);
+ if ($is_target) {
+ return $this->sendToTarget($request, $message_type, $receiver_object);
+ }
+
+ if ($request->getValue('unit') !== null) {
+ throw new Exception(
+ pht(
+ 'Call includes "unit" parameter. This parameter must be omitted '.
+ 'when the receiver is not a Build Target.'));
+ }
+
+ if ($request->getValue('lint') !== null) {
+ throw new Exception(
+ pht(
+ 'Call includes "lint" parameter. This parameter must be omitted '.
+ 'when the receiver is not a Build Target.'));
+ }
+
+ $is_build = ($receiver_object instanceof HarbormasterBuild);
+ if ($is_build) {
+ return $this->sendToBuild($request, $message_type, $receiver_object);
+ }
+
+ $is_buildable = ($receiver_object instanceof HarbormasterBuildable);
+ if ($is_buildable) {
+ return $this->sendToBuildable($request, $message_type, $receiver_object);
}
+ throw new Exception(
+ pht(
+ 'Receiver object (of class "%s") is not a valid receiver.',
+ get_class($receiver_object)));
+ }
+
+ private function sendToTarget(
+ ConduitAPIRequest $request,
+ $message_type,
+ HarbormasterBuildTarget $build_target) {
+ $viewer = $request->getViewer();
+
$save = array();
$lint_messages = $request->getValue('lint', array());
foreach ($lint_messages as $lint) {
$save[] = HarbormasterBuildLintMessage::newFromDictionary(
$build_target,
$lint);
}
$unit_messages = $request->getValue('unit', array());
foreach ($unit_messages as $unit) {
$save[] = HarbormasterBuildUnitMessage::newFromDictionary(
$build_target,
$unit);
}
$save[] = HarbormasterBuildMessage::initializeNewMessage($viewer)
->setReceiverPHID($build_target->getPHID())
->setType($message_type);
$build_target->openTransaction();
foreach ($save as $object) {
$object->save();
}
$build_target->saveTransaction();
// If the build has completely paused because all steps are blocked on
// waiting targets, this will resume it.
$build = $build_target->getBuild();
PhabricatorWorker::scheduleTask(
'HarbormasterBuildWorker',
array(
'buildID' => $build->getID(),
),
array(
'objectPHID' => $build->getPHID(),
));
return null;
}
+ private function sendToBuild(
+ ConduitAPIRequest $request,
+ $message_type,
+ HarbormasterBuild $build) {
+ $viewer = $request->getViewer();
+
+ $xaction =
+ HarbormasterBuildMessageTransaction::getTransactionObjectForMessageType(
+ $message_type);
+ if (!$xaction) {
+ throw new Exception(
+ pht(
+ 'Message type "%s" is not supported.',
+ $message_type));
+ }
+
+ // NOTE: This is a slightly weaker check than we perform in the web UI.
+ // We allow API callers to send a "pause" message to a pausing build,
+ // for example, even though the message will have no effect.
+ $xaction->assertCanApplyMessage($viewer, $build);
+
+ $build->sendMessage($viewer, $xaction->getHarbormasterBuildMessageType());
+ }
+
+ private function sendToBuildable(
+ ConduitAPIRequest $request,
+ $message_type,
+ HarbormasterBuildable $buildable) {
+ $viewer = $request->getViewer();
+
+ $xaction =
+ HarbormasterBuildMessageTransaction::getTransactionObjectForMessageType(
+ $message_type);
+ if (!$xaction) {
+ throw new Exception(
+ pht(
+ 'Message type "%s" is not supported.',
+ $message_type));
+ }
+
+ // Reload the Buildable to load Builds.
+ $buildable = id(new HarbormasterBuildableQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($buildable->getID()))
+ ->needBuilds(true)
+ ->executeOne();
+
+ $can_send = array();
+ foreach ($buildable->getBuilds() as $build) {
+ if ($xaction->canApplyMessage($viewer, $build)) {
+ $can_send[] = $build;
+ }
+ }
+
+ // NOTE: This doesn't actually apply a transaction to the Buildable,
+ // but that transaction is purely informational and should probably be
+ // implemented as a Message.
+
+ foreach ($can_send as $build) {
+ $build->sendMessage($viewer, $xaction->getHarbormasterBuildMessageType());
+ }
+ }
+
}
diff --git a/src/applications/harbormaster/exception/HarbormasterRestartException.php b/src/applications/harbormaster/exception/HarbormasterRestartException.php
index 0d9484093f..6516b52272 100644
--- a/src/applications/harbormaster/exception/HarbormasterRestartException.php
+++ b/src/applications/harbormaster/exception/HarbormasterRestartException.php
@@ -1,42 +1,46 @@
<?php
final class HarbormasterRestartException extends Exception {
private $title;
private $body = array();
public function __construct($title, $body = null) {
$this->setTitle($title);
$this->appendParagraph($body);
- parent::__construct($title);
+ parent::__construct(
+ pht(
+ '%s: %s',
+ $title,
+ $body));
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
public function appendParagraph($description) {
$this->body[] = $description;
return $this;
}
public function getBody() {
return $this->body;
}
public function newDisplayString() {
$title = $this->getTitle();
$body = $this->getBody();
$body = implode("\n\n", $body);
return pht('%s: %s', $title, $body);
}
}
diff --git a/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageAbortTransaction.php b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageAbortTransaction.php
index 2d5a6162a0..eb47cc365c 100644
--- a/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageAbortTransaction.php
+++ b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageAbortTransaction.php
@@ -1,113 +1,117 @@
<?php
final class HarbormasterBuildMessageAbortTransaction
extends HarbormasterBuildMessageTransaction {
const TRANSACTIONTYPE = 'message/abort';
const MESSAGETYPE = 'abort';
public function getHarbormasterBuildMessageName() {
return pht('Abort Build');
}
public function getHarbormasterBuildableMessageName() {
return pht('Abort Builds');
}
public function newConfirmPromptTitle() {
return pht('Really abort build?');
}
public function getHarbormasterBuildableMessageEffect() {
return pht('Build will abort.');
}
public function newConfirmPromptBody() {
return pht(
'Progress on this build will be discarded. Really abort build?');
}
+ public function getHarbormasterBuildMessageDescription() {
+ return pht('Abort the build, discarding progress.');
+ }
+
public function newBuildableConfirmPromptTitle(
array $builds,
array $sendable) {
return pht(
'Really abort %s build(s)?',
phutil_count($builds));
}
public function newBuildableConfirmPromptBody(
array $builds,
array $sendable) {
if (count($sendable) === count($builds)) {
return pht(
'If you abort all builds, work will halt immediately. Work '.
'will be discarded, and builds must be completely restarted.');
} else {
return pht(
'You can only abort some builds. Work will halt immediately on '.
'builds you can abort. Progress will be discarded, and builds must '.
'be completely restarted if you want them to complete.');
}
}
public function getTitle() {
return pht(
'%s aborted this build.',
$this->renderAuthor());
}
public function getIcon() {
return 'fa-exclamation-triangle';
}
public function getColor() {
return 'red';
}
public function applyInternalEffects($object, $value) {
$actor = $this->getActor();
$build = $object;
$build->setBuildStatus(HarbormasterBuildStatus::STATUS_ABORTED);
}
public function applyExternalEffects($object, $value) {
$actor = $this->getActor();
$build = $object;
$build->releaseAllArtifacts($actor);
}
protected function newCanApplyMessageAssertion(
PhabricatorUser $viewer,
HarbormasterBuild $build) {
if ($build->isAutobuild()) {
throw new HarbormasterRestartException(
pht('Unable to Abort Build'),
pht(
'You can not abort a build that uses an autoplan.'));
}
if ($build->isComplete()) {
throw new HarbormasterRestartException(
pht('Unable to Abort Build'),
pht(
'You can not abort this biuld because it is already complete.'));
}
}
protected function newCanSendMessageAssertion(
PhabricatorUser $viewer,
HarbormasterBuild $build) {
if ($build->isAborting()) {
throw new HarbormasterRestartException(
pht('Unable to Abort Build'),
pht(
'You can not abort this build because it is already aborting.'));
}
}
}
diff --git a/src/applications/harbormaster/xaction/build/HarbormasterBuildMessagePauseTransaction.php b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessagePauseTransaction.php
index 8501984314..b960632e0e 100644
--- a/src/applications/harbormaster/xaction/build/HarbormasterBuildMessagePauseTransaction.php
+++ b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessagePauseTransaction.php
@@ -1,121 +1,126 @@
<?php
final class HarbormasterBuildMessagePauseTransaction
extends HarbormasterBuildMessageTransaction {
const TRANSACTIONTYPE = 'message/pause';
const MESSAGETYPE = 'pause';
public function getHarbormasterBuildMessageName() {
return pht('Pause Build');
}
public function getHarbormasterBuildableMessageName() {
return pht('Pause Builds');
}
public function newConfirmPromptTitle() {
return pht('Really pause build?');
}
public function getHarbormasterBuildableMessageEffect() {
return pht('Build will pause.');
}
public function newConfirmPromptBody() {
return pht(
'If you pause this build, work will halt once the current steps '.
'complete. You can resume the build later.');
}
+
+ public function getHarbormasterBuildMessageDescription() {
+ return pht('Pause the build.');
+ }
+
public function newBuildableConfirmPromptTitle(
array $builds,
array $sendable) {
return pht(
'Really pause %s build(s)?',
phutil_count($builds));
}
public function newBuildableConfirmPromptBody(
array $builds,
array $sendable) {
if (count($sendable) === count($builds)) {
return pht(
'If you pause all builds, work will halt once the current steps '.
'complete. You can resume the builds later.');
} else {
return pht(
'You can only pause some builds. Once the current steps complete, '.
'work will halt on builds you can pause. You can resume the builds '.
'later.');
}
}
public function getTitle() {
return pht(
'%s paused this build.',
$this->renderAuthor());
}
public function getIcon() {
return 'fa-pause';
}
public function getColor() {
return 'red';
}
public function applyInternalEffects($object, $value) {
$actor = $this->getActor();
$build = $object;
$build->setBuildStatus(HarbormasterBuildStatus::STATUS_PAUSED);
}
protected function newCanApplyMessageAssertion(
PhabricatorUser $viewer,
HarbormasterBuild $build) {
if ($build->isAutobuild()) {
throw new HarbormasterRestartException(
pht('Unable to Pause Build'),
pht('You can not pause a build that uses an autoplan.'));
}
if ($build->isPaused()) {
throw new HarbormasterRestartException(
pht('Unable to Pause Build'),
pht('You can not pause this build because it is already paused.'));
}
if ($build->isComplete()) {
throw new HarbormasterRestartException(
pht('Unable to Pause Build'),
pht('You can not pause this build because it has already completed.'));
}
}
protected function newCanSendMessageAssertion(
PhabricatorUser $viewer,
HarbormasterBuild $build) {
if ($build->isPausing()) {
throw new HarbormasterRestartException(
pht('Unable to Pause Build'),
pht('You can not pause this build because it is already pausing.'));
}
if ($build->isRestarting()) {
throw new HarbormasterRestartException(
pht('Unable to Pause Build'),
pht('You can not pause this build because it is already restarting.'));
}
if ($build->isAborting()) {
throw new HarbormasterRestartException(
pht('Unable to Pause Build'),
pht('You can not pause this build because it is already aborting.'));
}
}
}
diff --git a/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageRestartTransaction.php b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageRestartTransaction.php
index 4d62be31bb..d6af3acf26 100644
--- a/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageRestartTransaction.php
+++ b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageRestartTransaction.php
@@ -1,166 +1,171 @@
<?php
final class HarbormasterBuildMessageRestartTransaction
extends HarbormasterBuildMessageTransaction {
const TRANSACTIONTYPE = 'message/restart';
const MESSAGETYPE = 'restart';
public function getHarbormasterBuildMessageName() {
return pht('Restart Build');
}
public function getHarbormasterBuildableMessageName() {
return pht('Restart Builds');
}
public function getHarbormasterBuildableMessageEffect() {
return pht('Build will restart.');
}
public function newConfirmPromptTitle() {
return pht('Really restart build?');
}
public function newConfirmPromptBody() {
return pht(
'Progress on this build will be discarded and the build will restart. '.
'Side effects of the build will occur again. Really restart build?');
}
+
+ public function getHarbormasterBuildMessageDescription() {
+ return pht('Restart the build, discarding all progress.');
+ }
+
public function newBuildableConfirmPromptTitle(
array $builds,
array $sendable) {
return pht(
'Really restart %s build(s)?',
phutil_count($builds));
}
public function newBuildableConfirmPromptBody(
array $builds,
array $sendable) {
if (count($sendable) === count($builds)) {
return pht(
'All builds will restart.');
} else {
return pht(
'You can only restart some builds.');
}
}
public function newBuildableConfirmPromptWarnings(
array $builds,
array $sendable) {
$building = false;
foreach ($sendable as $build) {
if ($build->isBuilding()) {
$building = true;
break;
}
}
$warnings = array();
if ($building) {
$warnings[] = pht(
'Progress on running builds will be discarded.');
}
if ($sendable) {
$warnings[] = pht(
'When a build is restarted, side effects associated with '.
'the build may occur again.');
}
return $warnings;
}
public function getTitle() {
return pht(
'%s restarted this build.',
$this->renderAuthor());
}
public function getIcon() {
return 'fa-repeat';
}
public function applyInternalEffects($object, $value) {
$actor = $this->getActor();
$build = $object;
$build->restartBuild($actor);
$build->setBuildStatus(HarbormasterBuildStatus::STATUS_BUILDING);
}
protected function newCanApplyMessageAssertion(
PhabricatorUser $viewer,
HarbormasterBuild $build) {
if ($build->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 = $build->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.'));
}
}
}
protected function newCanSendMessageAssertion(
PhabricatorUser $viewer,
HarbormasterBuild $build) {
if ($build->isRestarting()) {
throw new HarbormasterRestartException(
pht('Already Restarting'),
pht(
'This build is already restarting. You can not reissue a restart '.
'command to a restarting build.'));
}
}
}
diff --git a/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageResumeTransaction.php b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageResumeTransaction.php
index ce7f8fc964..a4eb704d79 100644
--- a/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageResumeTransaction.php
+++ b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageResumeTransaction.php
@@ -1,115 +1,119 @@
<?php
final class HarbormasterBuildMessageResumeTransaction
extends HarbormasterBuildMessageTransaction {
const TRANSACTIONTYPE = 'message/resume';
const MESSAGETYPE = 'resume';
public function getHarbormasterBuildMessageName() {
return pht('Resume Build');
}
public function getHarbormasterBuildableMessageName() {
return pht('Resume Builds');
}
public function getHarbormasterBuildableMessageEffect() {
return pht('Build will resume.');
}
public function newConfirmPromptTitle() {
return pht('Really resume build?');
}
public function newConfirmPromptBody() {
return pht(
'Work will continue on the build. Really resume?');
}
+ public function getHarbormasterBuildMessageDescription() {
+ return pht('Resume work on a previously paused build.');
+ }
+
public function newBuildableConfirmPromptTitle(
array $builds,
array $sendable) {
return pht(
'Really resume %s build(s)?',
phutil_count($builds));
}
public function newBuildableConfirmPromptBody(
array $builds,
array $sendable) {
if (count($sendable) === count($builds)) {
return pht(
'Work will continue on all builds. Really resume?');
} else {
return pht(
'You can only resume some builds. Work will continue on builds '.
'you have permission to resume.');
}
}
public function getTitle() {
return pht(
'%s resumed this build.',
$this->renderAuthor());
}
public function getIcon() {
return 'fa-play';
}
public function applyInternalEffects($object, $value) {
$actor = $this->getActor();
$build = $object;
$build->setBuildStatus(HarbormasterBuildStatus::STATUS_BUILDING);
}
protected function newCanApplyMessageAssertion(
PhabricatorUser $viewer,
HarbormasterBuild $build) {
if ($build->isAutobuild()) {
throw new HarbormasterRestartException(
pht('Unable to Resume Build'),
pht(
'You can not resume a build that uses an autoplan.'));
}
- if (!$build->isPaused()) {
+ if (!$build->isPaused() && !$build->isPausing()) {
throw new HarbormasterRestartException(
pht('Unable to Resume Build'),
pht(
'You can not resume this build because it is not paused. You can '.
'only resume a paused build.'));
}
}
protected function newCanSendMessageAssertion(
PhabricatorUser $viewer,
HarbormasterBuild $build) {
if ($build->isResuming()) {
throw new HarbormasterRestartException(
pht('Unable to Resume Build'),
pht(
'You can not resume this build beacuse it is already resuming.'));
}
if ($build->isRestarting()) {
throw new HarbormasterRestartException(
pht('Unable to Resume Build'),
pht('You can not resume this build because it is already restarting.'));
}
if ($build->isAborting()) {
throw new HarbormasterRestartException(
pht('Unable to Resume Build'),
pht('You can not resume this build because it is already aborting.'));
}
}
}
diff --git a/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageTransaction.php b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageTransaction.php
index bfe4209812..723098d20b 100644
--- a/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageTransaction.php
+++ b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageTransaction.php
@@ -1,151 +1,158 @@
<?php
abstract class HarbormasterBuildMessageTransaction
extends HarbormasterBuildTransactionType {
final public function getHarbormasterBuildMessageType() {
return $this->getPhobjectClassConstant('MESSAGETYPE');
}
abstract public function getHarbormasterBuildMessageName();
+ abstract public function getHarbormasterBuildMessageDescription();
abstract public function getHarbormasterBuildableMessageName();
abstract public function getHarbormasterBuildableMessageEffect();
abstract public function newConfirmPromptTitle();
abstract public function newConfirmPromptBody();
abstract public function newBuildableConfirmPromptTitle(
array $builds,
array $sendable);
abstract public function newBuildableConfirmPromptBody(
array $builds,
array $sendable);
public function newBuildableConfirmPromptWarnings(
array $builds,
array $sendable) {
return array();
}
final public function generateOldValue($object) {
return null;
}
final public function getTransactionTypeForConduit($xaction) {
return 'message';
}
final public function getFieldValuesForConduit($xaction, $data) {
return array(
'type' => $xaction->getNewValue(),
);
}
- final public static function getTransactionObjectForMessageType(
- $message_type) {
+ final public static function getAllMessages() {
$message_xactions = id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->execute();
+ return $message_xactions;
+ }
+
+ final public static function getTransactionObjectForMessageType(
+ $message_type) {
+ $message_xactions = self::getAllMessages();
+
foreach ($message_xactions as $message_xaction) {
$xaction_type = $message_xaction->getHarbormasterBuildMessageType();
if ($xaction_type === $message_type) {
return $message_xaction;
}
}
return null;
}
final public static function getTransactionTypeForMessageType($message_type) {
$message_xaction = self::getTransactionObjectForMessageType($message_type);
if ($message_xaction) {
return $message_xaction->getTransactionTypeConstant();
}
return null;
}
final public function getTransactionHasEffect($object, $old, $new) {
return $this->canApplyMessage($this->getActor(), $object);
}
final public function canApplyMessage(
PhabricatorUser $viewer,
HarbormasterBuild $build) {
try {
$this->assertCanApplyMessage($viewer, $build);
return true;
} catch (HarbormasterRestartException $ex) {
return false;
}
}
final public function canSendMessage(
PhabricatorUser $viewer,
HarbormasterBuild $build) {
try {
$this->assertCanSendMessage($viewer, $build);
return true;
} catch (HarbormasterRestartException $ex) {
return false;
}
}
final public function assertCanApplyMessage(
PhabricatorUser $viewer,
HarbormasterBuild $build) {
$this->newCanApplyMessageAssertion($viewer, $build);
}
final public function assertCanSendMessage(
PhabricatorUser $viewer,
HarbormasterBuild $build) {
$plan = $build->getBuildPlan();
// See T13526. Users without permission to access the build plan can
// currently end up here with no "BuildPlan" object.
if (!$plan) {
throw new HarbormasterRestartException(
pht('No Build Plan Permission'),
pht(
'You can not issue this command because you do not have '.
'permission to access the build plan for this build.'));
}
// Issuing these commands requires that you be able to edit the build, to
// prevent enemy engineers from sabotaging your builds. See T9614.
if (!$plan->canRunWithoutEditCapability()) {
try {
PhabricatorPolicyFilter::requireCapability(
$viewer,
$plan,
PhabricatorPolicyCapability::CAN_EDIT);
} catch (PhabricatorPolicyException $ex) {
throw new HarbormasterRestartException(
pht('Insufficent Build Plan Permission'),
pht(
'The build plan for this build is configured to prevent '.
'users who can not edit it from issuing commands to the '.
'build, and you do not have permission to edit the build '.
'plan.'));
}
}
$this->newCanSendMessageAssertion($viewer, $build);
$this->assertCanApplyMessage($viewer, $build);
}
abstract protected function newCanSendMessageAssertion(
PhabricatorUser $viewer,
HarbormasterBuild $build);
abstract protected function newCanApplyMessageAssertion(
PhabricatorUser $viewer,
HarbormasterBuild $build);
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 18:57 (1 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1127616
Default Alt Text
(48 KB)

Event Timeline