@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<?php
2
3final class HarbormasterBuildable
4 extends HarbormasterDAO
5 implements
6 PhabricatorApplicationTransactionInterface,
7 PhabricatorPolicyInterface,
8 HarbormasterBuildableInterface,
9 PhabricatorConduitResultInterface,
10 PhabricatorDestructibleInterface {
11
12 protected $buildablePHID;
13 protected $containerPHID;
14 protected $buildableStatus;
15 protected $isManualBuildable;
16
17 private $buildableObject = self::ATTACHABLE;
18 private $containerObject = self::ATTACHABLE;
19 private $builds = self::ATTACHABLE;
20
21 public static function initializeNewBuildable(PhabricatorUser $actor) {
22 return id(new HarbormasterBuildable())
23 ->setIsManualBuildable(0)
24 ->setBuildableStatus(HarbormasterBuildableStatus::STATUS_PREPARING);
25 }
26
27 public function getMonogram() {
28 return 'B'.$this->getID();
29 }
30
31 public function getURI() {
32 return '/'.$this->getMonogram();
33 }
34
35 /**
36 * Returns an existing buildable for the object's PHID or creates a
37 * new buildable implicitly if needed.
38 */
39 public static function createOrLoadExisting(
40 PhabricatorUser $actor,
41 $buildable_object_phid,
42 $container_object_phid) {
43
44 $buildable = id(new HarbormasterBuildableQuery())
45 ->setViewer($actor)
46 ->withBuildablePHIDs(array($buildable_object_phid))
47 ->withManualBuildables(false)
48 ->setLimit(1)
49 ->executeOne();
50 if ($buildable) {
51 return $buildable;
52 }
53 $buildable = self::initializeNewBuildable($actor)
54 ->setBuildablePHID($buildable_object_phid)
55 ->setContainerPHID($container_object_phid);
56 $buildable->save();
57 return $buildable;
58 }
59
60 /**
61 * Start builds for a given buildable.
62 *
63 * @param string $phid PHID of the object to build.
64 * @param string $container_phid Container PHID for the buildable.
65 * @param array<HarbormasterBuildRequest> $requests List of builds to
66 * perform.
67 * @return void
68 */
69 public static function applyBuildPlans(
70 $phid,
71 $container_phid,
72 array $requests) {
73
74 assert_instances_of($requests, HarbormasterBuildRequest::class);
75
76 if (!$requests) {
77 return;
78 }
79
80 // Skip all of this logic if the Harbormaster application
81 // isn't currently enabled.
82
83 $harbormaster_app = PhabricatorHarbormasterApplication::class;
84 if (!PhabricatorApplication::isClassInstalled($harbormaster_app)) {
85 return;
86 }
87
88 $viewer = PhabricatorUser::getOmnipotentUser();
89
90 $buildable = self::createOrLoadExisting(
91 $viewer,
92 $phid,
93 $container_phid);
94
95 $plan_phids = mpull($requests, 'getBuildPlanPHID');
96 $plans = id(new HarbormasterBuildPlanQuery())
97 ->setViewer($viewer)
98 ->withPHIDs($plan_phids)
99 ->execute();
100 $plans = mpull($plans, null, 'getPHID');
101
102 foreach ($requests as $request) {
103 $plan_phid = $request->getBuildPlanPHID();
104 $plan = idx($plans, $plan_phid);
105
106 if (!$plan) {
107 throw new Exception(
108 pht(
109 'Failed to load build plan ("%s").',
110 $plan_phid));
111 }
112
113 if ($plan->isDisabled()) {
114 // TODO: This should be communicated more clearly -- maybe we should
115 // create the build but set the status to "disabled" or "derelict".
116 continue;
117 }
118
119 $parameters = $request->getBuildParameters();
120 $buildable->applyPlan($plan, $parameters, $request->getInitiatorPHID());
121 }
122 }
123
124 public function applyPlan(
125 HarbormasterBuildPlan $plan,
126 array $parameters,
127 $initiator_phid) {
128
129 $viewer = PhabricatorUser::getOmnipotentUser();
130 $build = HarbormasterBuild::initializeNewBuild($viewer)
131 ->setBuildablePHID($this->getPHID())
132 ->setBuildPlanPHID($plan->getPHID())
133 ->setBuildParameters($parameters)
134 ->setBuildStatus(HarbormasterBuildStatus::STATUS_PENDING);
135 if ($initiator_phid) {
136 $build->setInitiatorPHID($initiator_phid);
137 }
138
139 $auto_key = $plan->getPlanAutoKey();
140 if ($auto_key) {
141 $build->setPlanAutoKey($auto_key);
142 }
143
144 $build->save();
145
146 $steps = id(new HarbormasterBuildStepQuery())
147 ->setViewer($viewer)
148 ->withBuildPlanPHIDs(array($plan->getPHID()))
149 ->execute();
150
151 foreach ($steps as $step) {
152 $step->willStartBuild($viewer, $this, $build, $plan);
153 }
154
155 PhabricatorWorker::scheduleTask(
156 'HarbormasterBuildWorker',
157 array(
158 'buildID' => $build->getID(),
159 ),
160 array(
161 'objectPHID' => $build->getPHID(),
162 ));
163
164 return $build;
165 }
166
167 protected function getConfiguration() {
168 return array(
169 self::CONFIG_AUX_PHID => true,
170 self::CONFIG_COLUMN_SCHEMA => array(
171 'containerPHID' => 'phid?',
172 'buildableStatus' => 'text32',
173 'isManualBuildable' => 'bool',
174 ),
175 self::CONFIG_KEY_SCHEMA => array(
176 'key_buildable' => array(
177 'columns' => array('buildablePHID'),
178 ),
179 'key_container' => array(
180 'columns' => array('containerPHID'),
181 ),
182 'key_manual' => array(
183 'columns' => array('isManualBuildable'),
184 ),
185 ),
186 ) + parent::getConfiguration();
187 }
188
189 public function generatePHID() {
190 return PhabricatorPHID::generateNewPHID(
191 HarbormasterBuildablePHIDType::TYPECONST);
192 }
193
194 public function attachBuildableObject($buildable_object) {
195 $this->buildableObject = $buildable_object;
196 return $this;
197 }
198
199 public function getBuildableObject() {
200 return $this->assertAttached($this->buildableObject);
201 }
202
203 public function attachContainerObject($container_object) {
204 $this->containerObject = $container_object;
205 return $this;
206 }
207
208 public function getContainerObject() {
209 return $this->assertAttached($this->containerObject);
210 }
211
212 /**
213 * @param array<HarbormasterBuild> $builds
214 */
215 public function attachBuilds(array $builds) {
216 assert_instances_of($builds, HarbormasterBuild::class);
217 $this->builds = $builds;
218 return $this;
219 }
220
221 public function getBuilds() {
222 return $this->assertAttached($this->builds);
223 }
224
225
226/* -( Status )------------------------------------------------------------- */
227
228
229 public function getBuildableStatusObject() {
230 $status = $this->getBuildableStatus();
231 return HarbormasterBuildableStatus::newBuildableStatusObject($status);
232 }
233
234 public function getStatusIcon() {
235 return $this->getBuildableStatusObject()->getIcon();
236 }
237
238 public function getStatusDisplayName() {
239 return $this->getBuildableStatusObject()->getDisplayName();
240 }
241
242 public function getStatusColor() {
243 return $this->getBuildableStatusObject()->getColor();
244 }
245
246 public function isPreparing() {
247 return $this->getBuildableStatusObject()->isPreparing();
248 }
249
250 public function isBuilding() {
251 return $this->getBuildableStatusObject()->isBuilding();
252 }
253
254
255/* -( Messages )----------------------------------------------------------- */
256
257
258 public function sendMessage(
259 PhabricatorUser $viewer,
260 $message_type,
261 $queue_update) {
262
263 $message = HarbormasterBuildMessage::initializeNewMessage($viewer)
264 ->setReceiverPHID($this->getPHID())
265 ->setType($message_type)
266 ->save();
267
268 if ($queue_update) {
269 PhabricatorWorker::scheduleTask(
270 'HarbormasterBuildWorker',
271 array(
272 'buildablePHID' => $this->getPHID(),
273 ),
274 array(
275 'objectPHID' => $this->getPHID(),
276 ));
277 }
278
279 return $message;
280 }
281
282
283/* -( PhabricatorApplicationTransactionInterface )------------------------- */
284
285
286 public function getApplicationTransactionEditor() {
287 return new HarbormasterBuildableTransactionEditor();
288 }
289
290 public function getApplicationTransactionTemplate() {
291 return new HarbormasterBuildableTransaction();
292 }
293
294
295/* -( PhabricatorPolicyInterface )----------------------------------------- */
296
297
298 public function getCapabilities() {
299 return array(
300 PhabricatorPolicyCapability::CAN_VIEW,
301 PhabricatorPolicyCapability::CAN_EDIT,
302 );
303 }
304
305 public function getPolicy($capability) {
306 return $this->getBuildableObject()->getPolicy($capability);
307 }
308
309 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
310 return $this->getBuildableObject()->hasAutomaticCapability(
311 $capability,
312 $viewer);
313 }
314
315 public function describeAutomaticCapability($capability) {
316 return pht('A buildable inherits policies from the underlying object.');
317 }
318
319
320
321/* -( HarbormasterBuildableInterface )------------------------------------- */
322
323
324 public function getHarbormasterBuildableDisplayPHID() {
325 return $this->getBuildableObject()->getHarbormasterBuildableDisplayPHID();
326 }
327
328 public function getHarbormasterBuildablePHID() {
329 // NOTE: This is essentially just for convenience, as it allows you create
330 // a copy of a buildable by specifying `B123` without bothering to go
331 // look up the underlying object.
332 return $this->getBuildablePHID();
333 }
334
335 public function getHarbormasterContainerPHID() {
336 return $this->getContainerPHID();
337 }
338
339 public function getBuildVariables() {
340 return array();
341 }
342
343 public function getAvailableBuildVariables() {
344 return array();
345 }
346
347 public function newBuildableEngine() {
348 return $this->getBuildableObject()->newBuildableEngine();
349 }
350
351
352/* -( PhabricatorConduitResultInterface )---------------------------------- */
353
354
355 public function getFieldSpecificationsForConduit() {
356 return array(
357 id(new PhabricatorConduitSearchFieldSpecification())
358 ->setKey('objectPHID')
359 ->setType('phid')
360 ->setDescription(pht('PHID of the object that is built.')),
361 id(new PhabricatorConduitSearchFieldSpecification())
362 ->setKey('containerPHID')
363 ->setType('phid')
364 ->setDescription(pht('PHID of the object containing this buildable.')),
365 id(new PhabricatorConduitSearchFieldSpecification())
366 ->setKey('buildableStatus')
367 ->setType('map<string, wild>')
368 ->setDescription(pht('The current status of this buildable.')),
369 id(new PhabricatorConduitSearchFieldSpecification())
370 ->setKey('isManual')
371 ->setType('bool')
372 ->setDescription(pht('True if this is a manual buildable.')),
373 id(new PhabricatorConduitSearchFieldSpecification())
374 ->setKey('uri')
375 ->setType('uri')
376 ->setDescription(pht('View URI for the buildable.')),
377 );
378 }
379
380 public function getFieldValuesForConduit() {
381 return array(
382 'objectPHID' => $this->getBuildablePHID(),
383 'containerPHID' => $this->getContainerPHID(),
384 'buildableStatus' => array(
385 'value' => $this->getBuildableStatus(),
386 ),
387 'isManual' => (bool)$this->getIsManualBuildable(),
388 'uri' => PhabricatorEnv::getURI($this->getURI()),
389 );
390 }
391
392 public function getConduitSearchAttachments() {
393 return array();
394 }
395
396
397/* -( PhabricatorDestructibleInterface )----------------------------------- */
398
399
400 public function destroyObjectPermanently(
401 PhabricatorDestructionEngine $engine) {
402 $viewer = $engine->getViewer();
403
404 $this->openTransaction();
405 $builds = id(new HarbormasterBuildQuery())
406 ->setViewer($viewer)
407 ->withBuildablePHIDs(array($this->getPHID()))
408 ->execute();
409 foreach ($builds as $build) {
410 $engine->destroyObject($build);
411 }
412
413 $messages = id(new HarbormasterBuildMessageQuery())
414 ->setViewer($viewer)
415 ->withReceiverPHIDs(array($this->getPHID()))
416 ->execute();
417 foreach ($messages as $message) {
418 $engine->destroyObject($message);
419 }
420
421 $this->delete();
422 $this->saveTransaction();
423 }
424
425}