@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at recaptime-dev/main 690 lines 21 kB view raw
1<?php 2 3final class HarbormasterSendMessageConduitAPIMethod 4 extends HarbormasterConduitAPIMethod { 5 6 public function getAPIMethodName() { 7 return 'harbormaster.sendmessage'; 8 } 9 10 public function getMethodSummary() { 11 return pht( 12 'Modify running builds, and report build results.'); 13 } 14 15 public function getMethodDescription() { 16 return pht(<<<EOREMARKUP 17Pause, abort, restart, and report results for builds. 18EOREMARKUP 19 ); 20 } 21 22 protected function newDocumentationPages(PhabricatorUser $viewer) { 23 $pages = array(); 24 25 $pages[] = $this->newSendingDocumentationBoxPage($viewer); 26 $pages[] = $this->newBuildsDocumentationBoxPage($viewer); 27 $pages[] = $this->newCommandsDocumentationBoxPage($viewer); 28 $pages[] = $this->newTargetsDocumentationBoxPage($viewer); 29 $pages[] = $this->newUnitDocumentationBoxPage($viewer); 30 $pages[] = $this->newLintDocumentationBoxPage($viewer); 31 32 return $pages; 33 } 34 35 private function newSendingDocumentationBoxPage(PhabricatorUser $viewer) { 36 $title = pht('Sending Messages'); 37 $content = pht(<<<EOREMARKUP 38Harbormaster build objects work somewhat differently from objects in many other 39applications. Most application objects can be edited directly using synchronous 40APIs (like `maniphest.edit`, `differential.revision.edit`, and so on). 41 42However, builds require long-running background processing and Habormaster 43objects have a more complex lifecycle than most other application objects and 44may spend significant periods of time locked by daemon processes during build 45execution. A synchronous edit might need to wait an arbitrarily long amount of 46time for this lock to become available so the edit could be applied. 47 48Additionally, some edits may also require an arbitrarily long amount of time to 49//complete//. For example, aborting a build may execute cleanup steps which 50take minutes (or even hours) to complete. 51 52Since a synchronous API could not guarantee it could return results to the 53caller in a reasonable amount of time, the edit API for Harbormaster build 54objects is asynchronous: to update a Harbormaster build or build target, use 55this API (`harbormaster.sendmessage`) to send it a message describing an edit 56you would like to effect or additional information you want to provide. 57The message will be processed by the daemons once the build or target reaches 58a suitable state to receive messages. 59 60Select an object to send a message to using the `receiver` parameter. This 61API method can send messages to multiple types of objects: 62 63<table> 64 <tr> 65 <th>Object Type</th> 66 <th>PHID Example</th> 67 <th>Description</th> 68 </tr> 69 <tr> 70 <td>Harbormaster Buildable</td> 71 <td>`PHID-HMBB-...`</td> 72 <td>%s</td> 73 </tr> 74 <tr> 75 <td>Harbormaster Build</td> 76 <td>`PHID-HMBD-...`</td> 77 <td>%s</td> 78 </tr> 79 <tr> 80 <td>Harbormaster Build Target</td> 81 <td>`PHID-HMBT-...`</td> 82 <td>%s</td> 83 </tr> 84</table> 85 86See below for specifics on sending messages to different object types. 87EOREMARKUP 88 , 89 pht( 90 'Buildables may receive control commands like "abort" and "restart". '. 91 'Sending a control command to a Buildable is the same as sending it '. 92 'to each Build for the Buildable.'), 93 pht( 94 'Builds may receive control commands like "pause", "resume", "abort", '. 95 'and "restart".'), 96 pht( 97 'Build Targets may receive build status and result messages, like '. 98 '"pass" or "fail".')); 99 100 $content = $this->newRemarkupDocumentationView($content); 101 102 return $this->newDocumentationBoxPage($viewer, $title, $content) 103 ->setAnchor('sending') 104 ->setIconIcon('fa-envelope-o'); 105 } 106 107 private function newBuildsDocumentationBoxPage(PhabricatorUser $viewer) { 108 $title = pht('Updating Builds'); 109 110 $content = pht(<<<EOREMARKUP 111You can use this method (`harbormaster.sendmessage`) to send control commands 112to Buildables and Builds. 113 114Specify the Build or Buildable to receive the control command by providing its 115PHID in the `receiver` parameter. 116 117Sending a control command to a Buildable has the same effect as sending it to 118each Build for the Buildable. For example, sending a "Pause" message to a 119Buildable will pause all builds for the Buildable (or at least attempt to). 120 121When sending control commands, the `unit` and `lint` parameters of this API 122method must be omitted. You can not report lint or unit results directly to 123a Build or Buildable, and can not report them alongside a control command. 124 125More broadly, you can not report build results directly to a Build or 126Buildable. Instead, report results to a Build Target. 127 128See below for a list of control commands. 129 130EOREMARKUP 131 ); 132 133 $content = $this->newRemarkupDocumentationView($content); 134 135 return $this->newDocumentationBoxPage($viewer, $title, $content) 136 ->setAnchor('builds') 137 ->setIconIcon('fa-cubes'); 138 } 139 140 private function newCommandsDocumentationBoxPage(PhabricatorUser $viewer) { 141 $messages = HarbormasterBuildMessageTransaction::getAllMessages(); 142 143 $rows = array(); 144 145 $rows[] = '<tr>'; 146 $rows[] = '<th>'.pht('Message Type').'</th>'; 147 $rows[] = '<th>'.pht('Description').'</th>'; 148 $rows[] = '</tr>'; 149 150 foreach ($messages as $message) { 151 $row = array(); 152 153 $row[] = sprintf( 154 '<td>`%s`</td>', 155 $message->getHarbormasterBuildMessageType()); 156 157 $row[] = sprintf( 158 '<td>%s</td>', 159 $message->getHarbormasterBuildMessageDescription()); 160 161 $rows[] = sprintf( 162 '<tr>%s</tr>', 163 implode("\n", $row)); 164 } 165 166 $message_table = sprintf( 167 '<table>%s</table>', 168 implode("\n", $rows)); 169 170 $title = pht('Control Commands'); 171 172 $content = pht(<<<EOREMARKUP 173You can use this method to send control commands to Buildables and Builds. 174 175This table summarizes which object types may receive control commands: 176 177<table> 178 <tr> 179 <th>Object Type</th> 180 <th>PHID Example</th> 181 <th /> 182 <th>Description</th> 183 </tr> 184 <tr> 185 <td>Harbormaster Buildable</td> 186 <td>`PHID-HMBB-...`</td> 187 <td>{icon check color=green}</td> 188 <td>Buildables may receive control commands.</td> 189 </tr> 190 <tr> 191 <td>Harbormaster Build</td> 192 <td>`PHID-HMBD-...`</td> 193 <td>{icon check color=green}</td> 194 <td>Builds may receive control commands.</td> 195 </tr> 196 <tr> 197 <td>Harbormaster Build Target</td> 198 <td>`PHID-HMBT-...`</td> 199 <td>{icon times color=red}</td> 200 <td>You may **NOT** send control commands to build targets.</td> 201 </tr> 202</table> 203 204You can send these commands: 205 206%s 207 208To send a command message, specify the PHID of the object you would like to 209receive the message using the `receiver` parameter, and specify the message 210type using the `type` parameter. 211 212EOREMARKUP 213 , 214 $message_table); 215 216 $content = $this->newRemarkupDocumentationView($content); 217 218 return $this->newDocumentationBoxPage($viewer, $title, $content) 219 ->setAnchor('commands') 220 ->setIconIcon('fa-exclamation-triangle'); 221 } 222 223 private function newTargetsDocumentationBoxPage(PhabricatorUser $viewer) { 224 $messages = HarbormasterMessageType::getAllMessages(); 225 226 $head_type = pht('Type'); 227 $head_desc = pht('Description'); 228 229 $rows = array(); 230 $rows[] = "| {$head_type} | {$head_desc} |"; 231 $rows[] = '|--------------|--------------|'; 232 foreach ($messages as $message) { 233 $description = HarbormasterMessageType::getMessageDescription($message); 234 $rows[] = "| `{$message}` | {$description} |"; 235 } 236 $message_table = implode("\n", $rows); 237 238 $content = pht(<<<EOREMARKUP 239If you run external builds, you can use this method to publish build results 240back into Harbormaster after the external system finishes work (or as it makes 241progress). 242 243To report build status or results, you must send a message to the appropriate 244Build Target. This table summarizes which object types may receive build status 245and result messages: 246 247<table> 248 <tr> 249 <th>Object Type</th> 250 <th>PHID Example</th> 251 <th /> 252 <th>Description</th> 253 </tr> 254 <tr> 255 <td>Harbormaster Buildable</td> 256 <td>`PHID-HMBB-...`</td> 257 <td>{icon times color=red}</td> 258 <td>Buildables may **NOT** receive status or result messages.</td> 259 </tr> 260 <tr> 261 <td>Harbormaster Build</td> 262 <td>`PHID-HMBD-...`</td> 263 <td>{icon times color=red}</td> 264 <td>Builds may **NOT** receive status or result messages.</td> 265 </tr> 266 <tr> 267 <td>Harbormaster Build Target</td> 268 <td>`PHID-HMBT-...`</td> 269 <td>{icon check color=green}</td> 270 <td>Report build status and results to Build Targets.</td> 271 </tr> 272</table> 273 274The simplest way to use this method to report build results is to call it once 275after the build finishes with a `pass` or `fail` message. This will record the 276build result, and continue the next step in the build if the build was waiting 277for a result. 278 279When you send a status message about a build target, you can optionally include 280detailed `lint` or `unit` results alongside the message. See below for details. 281 282If you want to report intermediate results but a build hasn't completed yet, 283you can use the `work` message. This message doesn't have any direct effects, 284but allows you to send additional data to update the progress of the build 285target. The target will continue waiting for a completion message, but the UI 286will update to show the progress which has been made. 287 288When sending a message to a build target to report the status or results of 289a build, your message must include a `type` which describes the overall state 290of the build. For example, use `pass` to tell Harbormaster that a build target 291completed successfully. 292 293Supported message types are: 294 295%s 296 297EOREMARKUP 298 , 299 $message_table); 300 301 $title = pht('Updating Build Targets'); 302 303 $content = $this->newRemarkupDocumentationView($content); 304 305 return $this->newDocumentationBoxPage($viewer, $title, $content) 306 ->setAnchor('targets') 307 ->setIconIcon('fa-bullseye'); 308 } 309 310 private function newUnitDocumentationBoxPage(PhabricatorUser $viewer) { 311 $head_key = pht('Key'); 312 $head_desc = pht('Description'); 313 $head_name = pht('Name'); 314 $head_type = pht('Type'); 315 316 $rows = array(); 317 $rows[] = "| {$head_key} | {$head_type} | {$head_desc} |"; 318 $rows[] = '|-------------|--------------|--------------|'; 319 $unit_spec = HarbormasterBuildUnitMessage::getParameterSpec(); 320 foreach ($unit_spec as $key => $parameter) { 321 $type = idx($parameter, 'type'); 322 $type = str_replace('|', ' '.pht('or').' ', $type); 323 $description = idx($parameter, 'description'); 324 $rows[] = "| `{$key}` | //{$type}// | {$description} |"; 325 } 326 $unit_table = implode("\n", $rows); 327 328 $rows = array(); 329 $rows[] = "| {$head_key} | {$head_name} | {$head_desc} |"; 330 $rows[] = '|-------------|--------------|--------------|'; 331 $results = ArcanistUnitTestResult::getAllResultCodes(); 332 foreach ($results as $result_code) { 333 $name = ArcanistUnitTestResult::getResultCodeName($result_code); 334 $description = ArcanistUnitTestResult::getResultCodeDescription( 335 $result_code); 336 $rows[] = "| `{$result_code}` | **{$name}** | {$description} |"; 337 } 338 $result_table = implode("\n", $rows); 339 340 $valid_unit = array( 341 array( 342 'name' => 'PassingTest', 343 'result' => ArcanistUnitTestResult::RESULT_PASS, 344 ), 345 array( 346 'name' => 'FailingTest', 347 'result' => ArcanistUnitTestResult::RESULT_FAIL, 348 ), 349 ); 350 351 $json = new PhutilJSON(); 352 $valid_unit = $json->encodeAsList($valid_unit); 353 354 355 $title = pht('Reporting Unit Results'); 356 357 $content = pht(<<<EOREMARKUP 358You can report test results when updating the state of a build target. The 359simplest way to do this is to report all the results alongside a `pass` or 360`fail` message, but you can also send a `work` message to report intermediate 361results. 362 363 364To provide unit test results, pass a list of results in the `unit` 365parameter. Each result should be a dictionary with these keys: 366 367%s 368 369The `result` parameter recognizes these test results: 370 371%s 372 373This is a simple, valid value for the `unit` parameter. It reports one passing 374test and one failing test: 375 376```lang=json 377%s 378``` 379EOREMARKUP 380 , 381 $unit_table, 382 $result_table, 383 $valid_unit); 384 385 $content = $this->newRemarkupDocumentationView($content); 386 387 return $this->newDocumentationBoxPage($viewer, $title, $content) 388 ->setAnchor('unit'); 389 } 390 391 private function newLintDocumentationBoxPage(PhabricatorUser $viewer) { 392 393 $head_key = pht('Key'); 394 $head_desc = pht('Description'); 395 $head_name = pht('Name'); 396 $head_type = pht('Type'); 397 398 $rows = array(); 399 $rows[] = "| {$head_key} | {$head_type} | {$head_desc} |"; 400 $rows[] = '|-------------|--------------|--------------|'; 401 $lint_spec = HarbormasterBuildLintMessage::getParameterSpec(); 402 foreach ($lint_spec as $key => $parameter) { 403 $type = idx($parameter, 'type'); 404 $type = str_replace('|', ' '.pht('or').' ', $type); 405 $description = idx($parameter, 'description'); 406 $rows[] = "| `{$key}` | //{$type}// | {$description} |"; 407 } 408 $lint_table = implode("\n", $rows); 409 410 $rows = array(); 411 $rows[] = "| {$head_key} | {$head_name} |"; 412 $rows[] = '|-------------|--------------|'; 413 $severities = ArcanistLintSeverity::getLintSeverities(); 414 foreach ($severities as $key => $name) { 415 $rows[] = "| `{$key}` | **{$name}** |"; 416 } 417 $severity_table = implode("\n", $rows); 418 419 $valid_lint = array( 420 array( 421 'name' => pht('Syntax Error'), 422 'code' => 'EXAMPLE1', 423 'severity' => ArcanistLintSeverity::SEVERITY_ERROR, 424 'path' => 'path/to/example.c', 425 'line' => 17, 426 'char' => 3, 427 ), 428 array( 429 'name' => pht('Not A Haiku'), 430 'code' => 'EXAMPLE2', 431 'severity' => ArcanistLintSeverity::SEVERITY_ERROR, 432 'path' => 'path/to/source.cpp', 433 'line' => 23, 434 'char' => 1, 435 'description' => pht( 436 'This function definition is not a haiku.'), 437 ), 438 ); 439 440 $json = new PhutilJSON(); 441 $valid_lint = $json->encodeAsList($valid_lint); 442 443 $title = pht('Reporting Lint Results'); 444 $content = pht(<<<EOREMARKUP 445Like unit test results, you can report lint results when updating the state 446of a build target. The `lint` parameter should contain results as a list of 447dictionaries with these keys: 448 449%s 450 451The `severity` parameter recognizes these severity levels: 452 453%s 454 455This is a simple, valid value for the `lint` parameter. It reports one error 456and one warning: 457 458```lang=json 459%s 460``` 461 462EOREMARKUP 463 , 464 $lint_table, 465 $severity_table, 466 $valid_lint); 467 468 $content = $this->newRemarkupDocumentationView($content); 469 470 return $this->newDocumentationBoxPage($viewer, $title, $content) 471 ->setAnchor('lint'); 472 } 473 474 protected function defineParamTypes() { 475 $messages = HarbormasterMessageType::getAllMessages(); 476 477 $more_messages = HarbormasterBuildMessageTransaction::getAllMessages(); 478 $more_messages = mpull($more_messages, 'getHarbormasterBuildMessageType'); 479 480 $messages = array_merge($messages, $more_messages); 481 $messages = array_unique($messages); 482 483 sort($messages); 484 485 $type_const = $this->formatStringConstants($messages); 486 487 return array( 488 'receiver' => 'required string|phid', 489 'type' => 'required '.$type_const, 490 'unit' => 'optional list<wild>', 491 'lint' => 'optional list<wild>', 492 'buildTargetPHID' => 'deprecated optional phid', 493 ); 494 } 495 496 protected function defineReturnType() { 497 return 'void'; 498 } 499 500 protected function execute(ConduitAPIRequest $request) { 501 $viewer = $request->getViewer(); 502 503 $receiver_name = $request->getValue('receiver'); 504 505 $build_target_phid = $request->getValue('buildTargetPHID'); 506 if ($build_target_phid !== null) { 507 if ($receiver_name === null) { 508 $receiver_name = $build_target_phid; 509 } else { 510 throw new Exception( 511 pht( 512 'Call specifies both "receiver" and "buildTargetPHID". '. 513 'When using the modern "receiver" parameter, omit the '. 514 'deprecated "buildTargetPHID" parameter.')); 515 } 516 } 517 518 if (!strlen($receiver_name)) { 519 throw new Exception( 520 pht( 521 'Call omits required "receiver" parameter. Specify the PHID '. 522 'of the object you want to send a message to.')); 523 } 524 525 $message_type = $request->getValue('type'); 526 if (!strlen($message_type)) { 527 throw new Exception( 528 pht( 529 'Call omits required "type" parameter. Specify the type of '. 530 'message you want to send.')); 531 } 532 533 $receiver_object = id(new PhabricatorObjectQuery()) 534 ->setViewer($viewer) 535 ->withNames(array($receiver_name)) 536 ->executeOne(); 537 if (!$receiver_object) { 538 throw new Exception( 539 pht( 540 'Unable to load object "%s" to receive message.', 541 $receiver_name)); 542 } 543 544 $is_target = ($receiver_object instanceof HarbormasterBuildTarget); 545 if ($is_target) { 546 return $this->sendToTarget($request, $message_type, $receiver_object); 547 } 548 549 if ($request->getValue('unit') !== null) { 550 throw new Exception( 551 pht( 552 'Call includes "unit" parameter. This parameter must be omitted '. 553 'when the receiver is not a Build Target.')); 554 } 555 556 if ($request->getValue('lint') !== null) { 557 throw new Exception( 558 pht( 559 'Call includes "lint" parameter. This parameter must be omitted '. 560 'when the receiver is not a Build Target.')); 561 } 562 563 $is_build = ($receiver_object instanceof HarbormasterBuild); 564 if ($is_build) { 565 return $this->sendToBuild($request, $message_type, $receiver_object); 566 } 567 568 $is_buildable = ($receiver_object instanceof HarbormasterBuildable); 569 if ($is_buildable) { 570 return $this->sendToBuildable($request, $message_type, $receiver_object); 571 } 572 573 throw new Exception( 574 pht( 575 'Receiver object (of class "%s") is not a valid receiver.', 576 get_class($receiver_object))); 577 } 578 579 private function sendToTarget( 580 ConduitAPIRequest $request, 581 $message_type, 582 HarbormasterBuildTarget $build_target) { 583 $viewer = $request->getViewer(); 584 585 $save = array(); 586 587 $lint_messages = $request->getValue('lint', array()); 588 foreach ($lint_messages as $lint) { 589 $save[] = HarbormasterBuildLintMessage::newFromDictionary( 590 $build_target, 591 $lint); 592 } 593 594 $unit_messages = $request->getValue('unit', array()); 595 foreach ($unit_messages as $unit) { 596 $save[] = HarbormasterBuildUnitMessage::newFromDictionary( 597 $build_target, 598 $unit); 599 } 600 601 $save[] = HarbormasterBuildMessage::initializeNewMessage($viewer) 602 ->setReceiverPHID($build_target->getPHID()) 603 ->setType($message_type); 604 605 $build_target->openTransaction(); 606 foreach ($save as $object) { 607 $object->save(); 608 } 609 $build_target->saveTransaction(); 610 611 // If the build has completely paused because all steps are blocked on 612 // waiting targets, this will resume it. 613 $build = $build_target->getBuild(); 614 615 PhabricatorWorker::scheduleTask( 616 'HarbormasterBuildWorker', 617 array( 618 'buildID' => $build->getID(), 619 ), 620 array( 621 'objectPHID' => $build->getPHID(), 622 )); 623 624 return null; 625 } 626 627 private function sendToBuild( 628 ConduitAPIRequest $request, 629 $message_type, 630 HarbormasterBuild $build) { 631 $viewer = $request->getViewer(); 632 633 $xaction = 634 HarbormasterBuildMessageTransaction::getTransactionObjectForMessageType( 635 $message_type); 636 if (!$xaction) { 637 throw new Exception( 638 pht( 639 'Message type "%s" is not supported.', 640 $message_type)); 641 } 642 643 // NOTE: This is a slightly weaker check than we perform in the web UI. 644 // We allow API callers to send a "pause" message to a pausing build, 645 // for example, even though the message will have no effect. 646 $xaction->assertCanApplyMessage($viewer, $build); 647 648 $build->sendMessage($viewer, $xaction->getHarbormasterBuildMessageType()); 649 } 650 651 private function sendToBuildable( 652 ConduitAPIRequest $request, 653 $message_type, 654 HarbormasterBuildable $buildable) { 655 $viewer = $request->getViewer(); 656 657 $xaction = 658 HarbormasterBuildMessageTransaction::getTransactionObjectForMessageType( 659 $message_type); 660 if (!$xaction) { 661 throw new Exception( 662 pht( 663 'Message type "%s" is not supported.', 664 $message_type)); 665 } 666 667 // Reload the Buildable to load Builds. 668 $buildable = id(new HarbormasterBuildableQuery()) 669 ->setViewer($viewer) 670 ->withIDs(array($buildable->getID())) 671 ->needBuilds(true) 672 ->executeOne(); 673 674 $can_send = array(); 675 foreach ($buildable->getBuilds() as $build) { 676 if ($xaction->canApplyMessage($viewer, $build)) { 677 $can_send[] = $build; 678 } 679 } 680 681 // NOTE: This doesn't actually apply a transaction to the Buildable, 682 // but that transaction is purely informational and should probably be 683 // implemented as a Message. 684 685 foreach ($can_send as $build) { 686 $build->sendMessage($viewer, $xaction->getHarbormasterBuildMessageType()); 687 } 688 } 689 690}