Path: blob/master/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php
12256 views
<?php12final class HarbormasterSendMessageConduitAPIMethod3extends HarbormasterConduitAPIMethod {45public function getAPIMethodName() {6return 'harbormaster.sendmessage';7}89public function getMethodSummary() {10return pht(11'Modify running builds, and report build results.');12}1314public function getMethodDescription() {15return pht(<<<EOREMARKUP16Pause, abort, restart, and report results for builds.17EOREMARKUP18);19}2021protected function newDocumentationPages(PhabricatorUser $viewer) {22$pages = array();2324$pages[] = $this->newSendingDocumentationBoxPage($viewer);25$pages[] = $this->newBuildsDocumentationBoxPage($viewer);26$pages[] = $this->newCommandsDocumentationBoxPage($viewer);27$pages[] = $this->newTargetsDocumentationBoxPage($viewer);28$pages[] = $this->newUnitDocumentationBoxPage($viewer);29$pages[] = $this->newLintDocumentationBoxPage($viewer);3031return $pages;32}3334private function newSendingDocumentationBoxPage(PhabricatorUser $viewer) {35$title = pht('Sending Messages');36$content = pht(<<<EOREMARKUP37Harbormaster build objects work somewhat differently from objects in many other38applications. Most application objects can be edited directly using synchronous39APIs (like `maniphest.edit`, `differential.revision.edit`, and so on).4041However, builds require long-running background processing and Habormaster42objects have a more complex lifecycle than most other application objects and43may spend significant periods of time locked by daemon processes during build44execition. A synchronous edit might need to wait an arbitrarily long amount of45time for this lock to become available so the edit could be applied.4647Additionally, some edits may also require an arbitrarily long amount of time to48//complete//. For example, aborting a build may execute cleanup steps which49take minutes (or even hours) to complete.5051Since a synchronous API could not guarantee it could return results to the52caller in a reasonable amount of time, the edit API for Harbormaster build53objects is asynchronous: to update a Harbormaster build or build target, use54this API (`harbormaster.sendmessage`) to send it a message describing an edit55you would like to effect or additional information you want to provide.56The message will be processed by the daemons once the build or target reaches57a suitable state to receive messages.5859Select an object to send a message to using the `receiver` parameter. This60API method can send messages to multiple types of objects:6162<table>63<tr>64<th>Object Type</th>65<th>PHID Example</th>66<th>Description</th>67</tr>68<tr>69<td>Harbormaster Buildable</td>70<td>`PHID-HMBB-...`</td>71<td>%s</td>72</tr>73<tr>74<td>Harbormaster Build</td>75<td>`PHID-HMBD-...`</td>76<td>%s</td>77</tr>78<tr>79<td>Harbormaster Build Target</td>80<td>`PHID-HMBT-...`</td>81<td>%s</td>82</tr>83</table>8485See below for specifics on sending messages to different object types.86EOREMARKUP87,88pht(89'Buildables may receive control commands like "abort" and "restart". '.90'Sending a control command to a Buildable is the same as sending it '.91'to each Build for the Buildable.'),92pht(93'Builds may receive control commands like "pause", "resume", "abort", '.94'and "restart".'),95pht(96'Build Targets may receive build status and result messages, like '.97'"pass" or "fail".'));9899$content = $this->newRemarkupDocumentationView($content);100101return $this->newDocumentationBoxPage($viewer, $title, $content)102->setAnchor('sending')103->setIconIcon('fa-envelope-o');104}105106private function newBuildsDocumentationBoxPage(PhabricatorUser $viewer) {107$title = pht('Updating Builds');108109$content = pht(<<<EOREMARKUP110You can use this method (`harbormaster.sendmessage`) to send control commands111to Buildables and Builds.112113Specify the Build or Buildable to receive the control command by providing its114PHID in the `receiver` parameter.115116Sending a control command to a Buildable has the same effect as sending it to117each Build for the Buildable. For example, sending a "Pause" message to a118Buildable will pause all builds for the Buildable (or at least attempt to).119120When sending control commands, the `unit` and `lint` parameters of this API121method must be omitted. You can not report lint or unit results directly to122a Build or Buildable, and can not report them alongside a control command.123124More broadly, you can not report build results directly to a Build or125Buildable. Instead, report results to a Build Target.126127See below for a list of control commands.128129EOREMARKUP130);131132$content = $this->newRemarkupDocumentationView($content);133134return $this->newDocumentationBoxPage($viewer, $title, $content)135->setAnchor('builds')136->setIconIcon('fa-cubes');137}138139private function newCommandsDocumentationBoxPage(PhabricatorUser $viewer) {140$messages = HarbormasterBuildMessageTransaction::getAllMessages();141142$rows = array();143144$rows[] = '<tr>';145$rows[] = '<th>'.pht('Message Type').'</th>';146$rows[] = '<th>'.pht('Description').'</th>';147$rows[] = '</tr>';148149foreach ($messages as $message) {150$row = array();151152$row[] = sprintf(153'<td>`%s`</td>',154$message->getHarbormasterBuildMessageType());155156$row[] = sprintf(157'<td>%s</td>',158$message->getHarbormasterBuildMessageDescription());159160$rows[] = sprintf(161'<tr>%s</tr>',162implode("\n", $row));163}164165$message_table = sprintf(166'<table>%s</table>',167implode("\n", $rows));168169$title = pht('Control Commands');170171$content = pht(<<<EOREMARKUP172You can use this method to send control commands to Buildables and Builds.173174This table summarizes which object types may receive control commands:175176<table>177<tr>178<th>Object Type</th>179<th>PHID Example</th>180<th />181<th>Description</th>182</tr>183<tr>184<td>Harbormaster Buildable</td>185<td>`PHID-HMBB-...`</td>186<td>{icon check color=green}</td>187<td>Buildables may receive control commands.</td>188</tr>189<tr>190<td>Harbormaster Build</td>191<td>`PHID-HMBD-...`</td>192<td>{icon check color=green}</td>193<td>Builds may receive control commands.</td>194</tr>195<tr>196<td>Harbormaster Build Target</td>197<td>`PHID-HMBT-...`</td>198<td>{icon times color=red}</td>199<td>You may **NOT** send control commands to build targets.</td>200</tr>201</table>202203You can send these commands:204205%s206207To send a command message, specify the PHID of the object you would like to208receive the message using the `receiver` parameter, and specify the message209type using the `type` parameter.210211EOREMARKUP212,213$message_table);214215$content = $this->newRemarkupDocumentationView($content);216217return $this->newDocumentationBoxPage($viewer, $title, $content)218->setAnchor('commands')219->setIconIcon('fa-exclamation-triangle');220}221222private function newTargetsDocumentationBoxPage(PhabricatorUser $viewer) {223$messages = HarbormasterMessageType::getAllMessages();224225$head_type = pht('Type');226$head_desc = pht('Description');227228$rows = array();229$rows[] = "| {$head_type} | {$head_desc} |";230$rows[] = '|--------------|--------------|';231foreach ($messages as $message) {232$description = HarbormasterMessageType::getMessageDescription($message);233$rows[] = "| `{$message}` | {$description} |";234}235$message_table = implode("\n", $rows);236237$content = pht(<<<EOREMARKUP238If you run external builds, you can use this method to publish build results239back into Harbormaster after the external system finishes work (or as it makes240progress).241242To report build status or results, you must send a message to the appropriate243Build Target. This table summarizes which object types may receive build status244and result messages:245246<table>247<tr>248<th>Object Type</th>249<th>PHID Example</th>250<th />251<th>Description</th>252</tr>253<tr>254<td>Harbormaster Buildable</td>255<td>`PHID-HMBB-...`</td>256<td>{icon times color=red}</td>257<td>Buildables may **NOT** receive status or result messages.</td>258</tr>259<tr>260<td>Harbormaster Build</td>261<td>`PHID-HMBD-...`</td>262<td>{icon times color=red}</td>263<td>Builds may **NOT** receive status or result messages.</td>264</tr>265<tr>266<td>Harbormaster Build Target</td>267<td>`PHID-HMBT-...`</td>268<td>{icon check color=green}</td>269<td>Report build status and results to Build Targets.</td>270</tr>271</table>272273The simplest way to use this method to report build results is to call it once274after the build finishes with a `pass` or `fail` message. This will record the275build result, and continue the next step in the build if the build was waiting276for a result.277278When you send a status message about a build target, you can optionally include279detailed `lint` or `unit` results alongside the message. See below for details.280281If you want to report intermediate results but a build hasn't completed yet,282you can use the `work` message. This message doesn't have any direct effects,283but allows you to send additional data to update the progress of the build284target. The target will continue waiting for a completion message, but the UI285will update to show the progress which has been made.286287When sending a message to a build target to report the status or results of288a build, your message must include a `type` which describes the overall state289of the build. For example, use `pass` to tell Harbormaster that a build target290completed successfully.291292Supported message types are:293294%s295296EOREMARKUP297,298$message_table);299300$title = pht('Updating Build Targets');301302$content = $this->newRemarkupDocumentationView($content);303304return $this->newDocumentationBoxPage($viewer, $title, $content)305->setAnchor('targets')306->setIconIcon('fa-bullseye');307}308309private function newUnitDocumentationBoxPage(PhabricatorUser $viewer) {310$head_key = pht('Key');311$head_desc = pht('Description');312$head_name = pht('Name');313$head_type = pht('Type');314315$rows = array();316$rows[] = "| {$head_key} | {$head_type} | {$head_desc} |";317$rows[] = '|-------------|--------------|--------------|';318$unit_spec = HarbormasterBuildUnitMessage::getParameterSpec();319foreach ($unit_spec as $key => $parameter) {320$type = idx($parameter, 'type');321$type = str_replace('|', ' '.pht('or').' ', $type);322$description = idx($parameter, 'description');323$rows[] = "| `{$key}` | //{$type}// | {$description} |";324}325$unit_table = implode("\n", $rows);326327$rows = array();328$rows[] = "| {$head_key} | {$head_name} | {$head_desc} |";329$rows[] = '|-------------|--------------|--------------|';330$results = ArcanistUnitTestResult::getAllResultCodes();331foreach ($results as $result_code) {332$name = ArcanistUnitTestResult::getResultCodeName($result_code);333$description = ArcanistUnitTestResult::getResultCodeDescription(334$result_code);335$rows[] = "| `{$result_code}` | **{$name}** | {$description} |";336}337$result_table = implode("\n", $rows);338339$valid_unit = array(340array(341'name' => 'PassingTest',342'result' => ArcanistUnitTestResult::RESULT_PASS,343),344array(345'name' => 'FailingTest',346'result' => ArcanistUnitTestResult::RESULT_FAIL,347),348);349350$json = new PhutilJSON();351$valid_unit = $json->encodeAsList($valid_unit);352353354$title = pht('Reporting Unit Results');355356$content = pht(<<<EOREMARKUP357You can report test results when updating the state of a build target. The358simplest way to do this is to report all the results alongside a `pass` or359`fail` message, but you can also send a `work` message to report intermediate360results.361362363To provide unit test results, pass a list of results in the `unit`364parameter. Each result should be a dictionary with these keys:365366%s367368The `result` parameter recognizes these test results:369370%s371372This is a simple, valid value for the `unit` parameter. It reports one passing373test and one failing test:374375```lang=json376%s377```378EOREMARKUP379,380$unit_table,381$result_table,382$valid_unit);383384$content = $this->newRemarkupDocumentationView($content);385386return $this->newDocumentationBoxPage($viewer, $title, $content)387->setAnchor('unit');388}389390private function newLintDocumentationBoxPage(PhabricatorUser $viewer) {391392$head_key = pht('Key');393$head_desc = pht('Description');394$head_name = pht('Name');395$head_type = pht('Type');396397$rows = array();398$rows[] = "| {$head_key} | {$head_type} | {$head_desc} |";399$rows[] = '|-------------|--------------|--------------|';400$lint_spec = HarbormasterBuildLintMessage::getParameterSpec();401foreach ($lint_spec as $key => $parameter) {402$type = idx($parameter, 'type');403$type = str_replace('|', ' '.pht('or').' ', $type);404$description = idx($parameter, 'description');405$rows[] = "| `{$key}` | //{$type}// | {$description} |";406}407$lint_table = implode("\n", $rows);408409$rows = array();410$rows[] = "| {$head_key} | {$head_name} |";411$rows[] = '|-------------|--------------|';412$severities = ArcanistLintSeverity::getLintSeverities();413foreach ($severities as $key => $name) {414$rows[] = "| `{$key}` | **{$name}** |";415}416$severity_table = implode("\n", $rows);417418$valid_lint = array(419array(420'name' => pht('Syntax Error'),421'code' => 'EXAMPLE1',422'severity' => ArcanistLintSeverity::SEVERITY_ERROR,423'path' => 'path/to/example.c',424'line' => 17,425'char' => 3,426),427array(428'name' => pht('Not A Haiku'),429'code' => 'EXAMPLE2',430'severity' => ArcanistLintSeverity::SEVERITY_ERROR,431'path' => 'path/to/source.cpp',432'line' => 23,433'char' => 1,434'description' => pht(435'This function definition is not a haiku.'),436),437);438439$json = new PhutilJSON();440$valid_lint = $json->encodeAsList($valid_lint);441442$title = pht('Reporting Lint Results');443$content = pht(<<<EOREMARKUP444Like unit test results, you can report lint results when updating the state445of a build target. The `lint` parameter should contain results as a list of446dictionaries with these keys:447448%s449450The `severity` parameter recognizes these severity levels:451452%s453454This is a simple, valid value for the `lint` parameter. It reports one error455and one warning:456457```lang=json458%s459```460461EOREMARKUP462,463$lint_table,464$severity_table,465$valid_lint);466467$content = $this->newRemarkupDocumentationView($content);468469return $this->newDocumentationBoxPage($viewer, $title, $content)470->setAnchor('lint');471}472473protected function defineParamTypes() {474$messages = HarbormasterMessageType::getAllMessages();475476$more_messages = HarbormasterBuildMessageTransaction::getAllMessages();477$more_messages = mpull($more_messages, 'getHarbormasterBuildMessageType');478479$messages = array_merge($messages, $more_messages);480$messages = array_unique($messages);481482sort($messages);483484$type_const = $this->formatStringConstants($messages);485486return array(487'receiver' => 'required string|phid',488'type' => 'required '.$type_const,489'unit' => 'optional list<wild>',490'lint' => 'optional list<wild>',491'buildTargetPHID' => 'deprecated optional phid',492);493}494495protected function defineReturnType() {496return 'void';497}498499protected function execute(ConduitAPIRequest $request) {500$viewer = $request->getViewer();501502$receiver_name = $request->getValue('receiver');503504$build_target_phid = $request->getValue('buildTargetPHID');505if ($build_target_phid !== null) {506if ($receiver_name === null) {507$receiver_name = $build_target_phid;508} else {509throw new Exception(510pht(511'Call specifies both "receiver" and "buildTargetPHID". '.512'When using the modern "receiver" parameter, omit the '.513'deprecated "buildTargetPHID" parameter.'));514}515}516517if ($receiver_name === null || !strlen($receiver_name)) {518throw new Exception(519pht(520'Call omits required "receiver" parameter. Specify the PHID '.521'of the object you want to send a message to.'));522}523524$message_type = $request->getValue('type');525if ($message_type === null || !strlen($message_type)) {526throw new Exception(527pht(528'Call omits required "type" parameter. Specify the type of '.529'message you want to send.'));530}531532$receiver_object = id(new PhabricatorObjectQuery())533->setViewer($viewer)534->withNames(array($receiver_name))535->executeOne();536if (!$receiver_object) {537throw new Exception(538pht(539'Unable to load object "%s" to receive message.',540$receiver_name));541}542543$is_target = ($receiver_object instanceof HarbormasterBuildTarget);544if ($is_target) {545return $this->sendToTarget($request, $message_type, $receiver_object);546}547548if ($request->getValue('unit') !== null) {549throw new Exception(550pht(551'Call includes "unit" parameter. This parameter must be omitted '.552'when the receiver is not a Build Target.'));553}554555if ($request->getValue('lint') !== null) {556throw new Exception(557pht(558'Call includes "lint" parameter. This parameter must be omitted '.559'when the receiver is not a Build Target.'));560}561562$is_build = ($receiver_object instanceof HarbormasterBuild);563if ($is_build) {564return $this->sendToBuild($request, $message_type, $receiver_object);565}566567$is_buildable = ($receiver_object instanceof HarbormasterBuildable);568if ($is_buildable) {569return $this->sendToBuildable($request, $message_type, $receiver_object);570}571572throw new Exception(573pht(574'Receiver object (of class "%s") is not a valid receiver.',575get_class($receiver_object)));576}577578private function sendToTarget(579ConduitAPIRequest $request,580$message_type,581HarbormasterBuildTarget $build_target) {582$viewer = $request->getViewer();583584$save = array();585586$lint_messages = $request->getValue('lint', array());587foreach ($lint_messages as $lint) {588$save[] = HarbormasterBuildLintMessage::newFromDictionary(589$build_target,590$lint);591}592593$unit_messages = $request->getValue('unit', array());594foreach ($unit_messages as $unit) {595$save[] = HarbormasterBuildUnitMessage::newFromDictionary(596$build_target,597$unit);598}599600$save[] = HarbormasterBuildMessage::initializeNewMessage($viewer)601->setReceiverPHID($build_target->getPHID())602->setType($message_type);603604$build_target->openTransaction();605foreach ($save as $object) {606$object->save();607}608$build_target->saveTransaction();609610// If the build has completely paused because all steps are blocked on611// waiting targets, this will resume it.612$build = $build_target->getBuild();613614PhabricatorWorker::scheduleTask(615'HarbormasterBuildWorker',616array(617'buildID' => $build->getID(),618),619array(620'objectPHID' => $build->getPHID(),621));622623return null;624}625626private function sendToBuild(627ConduitAPIRequest $request,628$message_type,629HarbormasterBuild $build) {630$viewer = $request->getViewer();631632$xaction =633HarbormasterBuildMessageTransaction::getTransactionObjectForMessageType(634$message_type);635if (!$xaction) {636throw new Exception(637pht(638'Message type "%s" is not supported.',639$message_type));640}641642// NOTE: This is a slightly weaker check than we perform in the web UI.643// We allow API callers to send a "pause" message to a pausing build,644// for example, even though the message will have no effect.645$xaction->assertCanApplyMessage($viewer, $build);646647$build->sendMessage($viewer, $xaction->getHarbormasterBuildMessageType());648}649650private function sendToBuildable(651ConduitAPIRequest $request,652$message_type,653HarbormasterBuildable $buildable) {654$viewer = $request->getViewer();655656$xaction =657HarbormasterBuildMessageTransaction::getTransactionObjectForMessageType(658$message_type);659if (!$xaction) {660throw new Exception(661pht(662'Message type "%s" is not supported.',663$message_type));664}665666// Reload the Buildable to load Builds.667$buildable = id(new HarbormasterBuildableQuery())668->setViewer($viewer)669->withIDs(array($buildable->getID()))670->needBuilds(true)671->executeOne();672673$can_send = array();674foreach ($buildable->getBuilds() as $build) {675if ($xaction->canApplyMessage($viewer, $build)) {676$can_send[] = $build;677}678}679680// NOTE: This doesn't actually apply a transaction to the Buildable,681// but that transaction is purely informational and should probably be682// implemented as a Message.683684foreach ($can_send as $build) {685$build->sendMessage($viewer, $xaction->getHarbormasterBuildMessageType());686}687}688689}690691692