Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php
12256 views
1
<?php
2
3
final 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
17
Pause, abort, restart, and report results for builds.
18
EOREMARKUP
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
38
Harbormaster build objects work somewhat differently from objects in many other
39
applications. Most application objects can be edited directly using synchronous
40
APIs (like `maniphest.edit`, `differential.revision.edit`, and so on).
41
42
However, builds require long-running background processing and Habormaster
43
objects have a more complex lifecycle than most other application objects and
44
may spend significant periods of time locked by daemon processes during build
45
execition. A synchronous edit might need to wait an arbitrarily long amount of
46
time for this lock to become available so the edit could be applied.
47
48
Additionally, some edits may also require an arbitrarily long amount of time to
49
//complete//. For example, aborting a build may execute cleanup steps which
50
take minutes (or even hours) to complete.
51
52
Since a synchronous API could not guarantee it could return results to the
53
caller in a reasonable amount of time, the edit API for Harbormaster build
54
objects is asynchronous: to update a Harbormaster build or build target, use
55
this API (`harbormaster.sendmessage`) to send it a message describing an edit
56
you would like to effect or additional information you want to provide.
57
The message will be processed by the daemons once the build or target reaches
58
a suitable state to receive messages.
59
60
Select an object to send a message to using the `receiver` parameter. This
61
API 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
86
See below for specifics on sending messages to different object types.
87
EOREMARKUP
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
111
You can use this method (`harbormaster.sendmessage`) to send control commands
112
to Buildables and Builds.
113
114
Specify the Build or Buildable to receive the control command by providing its
115
PHID in the `receiver` parameter.
116
117
Sending a control command to a Buildable has the same effect as sending it to
118
each Build for the Buildable. For example, sending a "Pause" message to a
119
Buildable will pause all builds for the Buildable (or at least attempt to).
120
121
When sending control commands, the `unit` and `lint` parameters of this API
122
method must be omitted. You can not report lint or unit results directly to
123
a Build or Buildable, and can not report them alongside a control command.
124
125
More broadly, you can not report build results directly to a Build or
126
Buildable. Instead, report results to a Build Target.
127
128
See below for a list of control commands.
129
130
EOREMARKUP
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
173
You can use this method to send control commands to Buildables and Builds.
174
175
This 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
204
You can send these commands:
205
206
%s
207
208
To send a command message, specify the PHID of the object you would like to
209
receive the message using the `receiver` parameter, and specify the message
210
type using the `type` parameter.
211
212
EOREMARKUP
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
239
If you run external builds, you can use this method to publish build results
240
back into Harbormaster after the external system finishes work (or as it makes
241
progress).
242
243
To report build status or results, you must send a message to the appropriate
244
Build Target. This table summarizes which object types may receive build status
245
and 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
274
The simplest way to use this method to report build results is to call it once
275
after the build finishes with a `pass` or `fail` message. This will record the
276
build result, and continue the next step in the build if the build was waiting
277
for a result.
278
279
When you send a status message about a build target, you can optionally include
280
detailed `lint` or `unit` results alongside the message. See below for details.
281
282
If you want to report intermediate results but a build hasn't completed yet,
283
you can use the `work` message. This message doesn't have any direct effects,
284
but allows you to send additional data to update the progress of the build
285
target. The target will continue waiting for a completion message, but the UI
286
will update to show the progress which has been made.
287
288
When sending a message to a build target to report the status or results of
289
a build, your message must include a `type` which describes the overall state
290
of the build. For example, use `pass` to tell Harbormaster that a build target
291
completed successfully.
292
293
Supported message types are:
294
295
%s
296
297
EOREMARKUP
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
358
You can report test results when updating the state of a build target. The
359
simplest 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
361
results.
362
363
364
To provide unit test results, pass a list of results in the `unit`
365
parameter. Each result should be a dictionary with these keys:
366
367
%s
368
369
The `result` parameter recognizes these test results:
370
371
%s
372
373
This is a simple, valid value for the `unit` parameter. It reports one passing
374
test and one failing test:
375
376
```lang=json
377
%s
378
```
379
EOREMARKUP
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
445
Like unit test results, you can report lint results when updating the state
446
of a build target. The `lint` parameter should contain results as a list of
447
dictionaries with these keys:
448
449
%s
450
451
The `severity` parameter recognizes these severity levels:
452
453
%s
454
455
This is a simple, valid value for the `lint` parameter. It reports one error
456
and one warning:
457
458
```lang=json
459
%s
460
```
461
462
EOREMARKUP
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 ($receiver_name === null || !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 ($message_type === null || !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
}
691
692