@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 532 lines 16 kB view raw
1<?php 2 3final class HarbormasterBuild extends HarbormasterDAO 4 implements 5 PhabricatorApplicationTransactionInterface, 6 PhabricatorPolicyInterface, 7 PhabricatorConduitResultInterface, 8 PhabricatorDestructibleInterface { 9 10 protected $buildablePHID; 11 protected $buildPlanPHID; 12 protected $buildStatus; 13 protected $buildGeneration; 14 protected $buildParameters = array(); 15 protected $initiatorPHID; 16 protected $planAutoKey; 17 18 private $buildable = self::ATTACHABLE; 19 private $buildPlan = self::ATTACHABLE; 20 private $buildTargets = self::ATTACHABLE; 21 private $unprocessedMessages = self::ATTACHABLE; 22 23 public static function initializeNewBuild(PhabricatorUser $actor) { 24 return id(new HarbormasterBuild()) 25 ->setBuildStatus(HarbormasterBuildStatus::STATUS_INACTIVE) 26 ->setBuildGeneration(0); 27 } 28 29 public function delete() { 30 $this->openTransaction(); 31 $this->deleteUnprocessedMessages(); 32 $result = parent::delete(); 33 $this->saveTransaction(); 34 35 return $result; 36 } 37 38 protected function getConfiguration() { 39 return array( 40 self::CONFIG_AUX_PHID => true, 41 self::CONFIG_SERIALIZATION => array( 42 'buildParameters' => self::SERIALIZATION_JSON, 43 ), 44 self::CONFIG_COLUMN_SCHEMA => array( 45 'buildStatus' => 'text32', 46 'buildGeneration' => 'uint32', 47 'planAutoKey' => 'text32?', 48 'initiatorPHID' => 'phid?', 49 ), 50 self::CONFIG_KEY_SCHEMA => array( 51 'key_buildable' => array( 52 'columns' => array('buildablePHID'), 53 ), 54 'key_plan' => array( 55 'columns' => array('buildPlanPHID'), 56 ), 57 'key_status' => array( 58 'columns' => array('buildStatus'), 59 ), 60 'key_planautokey' => array( 61 'columns' => array('buildablePHID', 'planAutoKey'), 62 'unique' => true, 63 ), 64 'key_initiator' => array( 65 'columns' => array('initiatorPHID'), 66 ), 67 ), 68 ) + parent::getConfiguration(); 69 } 70 71 public function generatePHID() { 72 return PhabricatorPHID::generateNewPHID( 73 HarbormasterBuildPHIDType::TYPECONST); 74 } 75 76 public function attachBuildable(HarbormasterBuildable $buildable) { 77 $this->buildable = $buildable; 78 return $this; 79 } 80 81 public function getBuildable() { 82 return $this->assertAttached($this->buildable); 83 } 84 85 public function getName() { 86 if ($this->getBuildPlan()) { 87 return $this->getBuildPlan()->getName(); 88 } 89 return pht('Build'); 90 } 91 92 public function attachBuildPlan( 93 ?HarbormasterBuildPlan $build_plan = null) { 94 $this->buildPlan = $build_plan; 95 return $this; 96 } 97 98 public function getBuildPlan() { 99 return $this->assertAttached($this->buildPlan); 100 } 101 102 public function getBuildTargets() { 103 return $this->assertAttached($this->buildTargets); 104 } 105 106 public function attachBuildTargets(array $targets) { 107 $this->buildTargets = $targets; 108 return $this; 109 } 110 111 public function isBuilding() { 112 return $this->getBuildStatusObject()->isBuilding(); 113 } 114 115 public function isAutobuild() { 116 return ($this->getPlanAutoKey() !== null); 117 } 118 119 public function retrieveVariablesFromBuild() { 120 $results = array( 121 'buildable.diff' => null, 122 'buildable.revision' => null, 123 'buildable.commit' => null, 124 'repository.callsign' => null, 125 'repository.phid' => null, 126 'repository.vcs' => null, 127 'repository.uri' => null, 128 'step.timestamp' => null, 129 'build.id' => null, 130 'initiator.phid' => null, 131 132 'buildable.phid' => null, 133 'buildable.object.phid' => null, 134 'buildable.container.phid' => null, 135 'build.phid' => null, 136 ); 137 138 foreach ($this->getBuildParameters() as $key => $value) { 139 $results['build/'.$key] = $value; 140 } 141 142 $buildable = $this->getBuildable(); 143 $object = $buildable->getBuildableObject(); 144 145 $object_variables = $object->getBuildVariables(); 146 147 $results = $object_variables + $results; 148 149 $results['step.timestamp'] = time(); 150 $results['build.id'] = $this->getID(); 151 $results['initiator.phid'] = $this->getInitiatorPHID(); 152 153 $results['buildable.phid'] = $buildable->getPHID(); 154 $results['buildable.object.phid'] = $object->getPHID(); 155 $results['buildable.container.phid'] = $buildable->getContainerPHID(); 156 $results['build.phid'] = $this->getPHID(); 157 158 return $results; 159 } 160 161 public static function getAvailableBuildVariables() { 162 $objects = id(new PhutilClassMapQuery()) 163 ->setAncestorClass(HarbormasterBuildableInterface::class) 164 ->execute(); 165 166 $variables = array(); 167 $variables[] = array( 168 'step.timestamp' => pht('The current UNIX timestamp.'), 169 'build.id' => pht('The ID of the current build.'), 170 'target.phid' => pht('The PHID of the current build target.'), 171 'initiator.phid' => pht( 172 'The PHID of the user or Object that initiated the build, '. 173 'if applicable.'), 174 'buildable.phid' => pht( 175 'The object PHID of the Harbormaster Buildable being built.'), 176 'buildable.object.phid' => pht( 177 'The object PHID of the object (usually a diff or commit) '. 178 'being built.'), 179 'buildable.container.phid' => pht( 180 'The object PHID of the container (usually a revision or repository) '. 181 'for the object being built.'), 182 'build.phid' => pht( 183 'The object PHID of the Harbormaster Build being built.'), 184 ); 185 186 foreach ($objects as $object) { 187 $variables[] = $object->getAvailableBuildVariables(); 188 } 189 190 $variables = array_mergev($variables); 191 return $variables; 192 } 193 194 public function isComplete() { 195 return $this->getBuildStatusObject()->isComplete(); 196 } 197 198 public function isPaused() { 199 return $this->getBuildStatusObject()->isPaused(); 200 } 201 202 public function isPassed() { 203 return $this->getBuildStatusObject()->isPassed(); 204 } 205 206 public function isFailed() { 207 return $this->getBuildStatusObject()->isFailed(); 208 } 209 210 public function isPending() { 211 return $this->getBuildstatusObject()->isPending(); 212 } 213 214 public function getURI() { 215 $id = $this->getID(); 216 return "/harbormaster/build/{$id}/"; 217 } 218 219 public function getBuildPendingStatusObject() { 220 list($pending_status) = $this->getUnprocessedMessageState(); 221 222 if ($pending_status !== null) { 223 return HarbormasterBuildStatus::newBuildStatusObject($pending_status); 224 } 225 226 return $this->getBuildStatusObject(); 227 } 228 229 protected function getBuildStatusObject() { 230 $status_key = $this->getBuildStatus(); 231 return HarbormasterBuildStatus::newBuildStatusObject($status_key); 232 } 233 234 public function getObjectName() { 235 return pht('Build %d', $this->getID()); 236 } 237 238 239/* -( Build Messages )----------------------------------------------------- */ 240 241 242 private function getUnprocessedMessages() { 243 return $this->assertAttached($this->unprocessedMessages); 244 } 245 246 public function getUnprocessedMessagesForApply() { 247 $unprocessed_state = $this->getUnprocessedMessageState(); 248 list($pending_status, $apply_messages) = $unprocessed_state; 249 250 return $apply_messages; 251 } 252 253 private function getUnprocessedMessageState() { 254 // NOTE: If a build has multiple unprocessed messages, we'll ignore 255 // messages that are obsoleted by a later or stronger message. 256 // 257 // For example, if a build has both "pause" and "abort" messages in queue, 258 // we just ignore the "pause" message and perform an "abort", since pausing 259 // first wouldn't affect the final state, so we can just skip it. 260 // 261 // Likewise, if a build has both "restart" and "abort" messages, the most 262 // recent message is controlling: we'll take whichever action a command 263 // was most recently issued for. 264 265 $is_restarting = false; 266 $is_aborting = false; 267 $is_pausing = false; 268 $is_resuming = false; 269 270 $apply_messages = array(); 271 272 foreach ($this->getUnprocessedMessages() as $message_object) { 273 $message_type = $message_object->getType(); 274 switch ($message_type) { 275 case HarbormasterBuildMessageRestartTransaction::MESSAGETYPE: 276 $is_restarting = true; 277 $is_aborting = false; 278 $apply_messages = array($message_object); 279 break; 280 case HarbormasterBuildMessageAbortTransaction::MESSAGETYPE: 281 $is_aborting = true; 282 $is_restarting = false; 283 $apply_messages = array($message_object); 284 break; 285 case HarbormasterBuildMessagePauseTransaction::MESSAGETYPE: 286 $is_pausing = true; 287 $is_resuming = false; 288 $apply_messages = array($message_object); 289 break; 290 case HarbormasterBuildMessageResumeTransaction::MESSAGETYPE: 291 $is_resuming = true; 292 $is_pausing = false; 293 $apply_messages = array($message_object); 294 break; 295 } 296 } 297 298 $pending_status = null; 299 if ($is_restarting) { 300 $pending_status = HarbormasterBuildStatus::PENDING_RESTARTING; 301 } else if ($is_aborting) { 302 $pending_status = HarbormasterBuildStatus::PENDING_ABORTING; 303 } else if ($is_pausing) { 304 $pending_status = HarbormasterBuildStatus::PENDING_PAUSING; 305 } else if ($is_resuming) { 306 $pending_status = HarbormasterBuildStatus::PENDING_RESUMING; 307 } 308 309 return array($pending_status, $apply_messages); 310 } 311 312 /** 313 * @param array<HarbormasterBuildMessage> $messages 314 */ 315 public function attachUnprocessedMessages(array $messages) { 316 assert_instances_of($messages, HarbormasterBuildMessage::class); 317 $this->unprocessedMessages = $messages; 318 return $this; 319 } 320 321 public function isPausing() { 322 return $this->getBuildPendingStatusObject()->isPausing(); 323 } 324 325 public function isResuming() { 326 return $this->getBuildPendingStatusObject()->isResuming(); 327 } 328 329 public function isRestarting() { 330 return $this->getBuildPendingStatusObject()->isRestarting(); 331 } 332 333 public function isAborting() { 334 return $this->getBuildPendingStatusObject()->isAborting(); 335 } 336 337 public function markUnprocessedMessagesAsProcessed() { 338 foreach ($this->getUnprocessedMessages() as $key => $message_object) { 339 $message_object 340 ->setIsConsumed(1) 341 ->save(); 342 } 343 344 return $this; 345 } 346 347 public function deleteUnprocessedMessages() { 348 foreach ($this->getUnprocessedMessages() as $key => $message_object) { 349 $message_object->delete(); 350 unset($this->unprocessedMessages[$key]); 351 } 352 353 return $this; 354 } 355 356 public function sendMessage(PhabricatorUser $viewer, $message_type) { 357 HarbormasterBuildMessage::initializeNewMessage($viewer) 358 ->setReceiverPHID($this->getPHID()) 359 ->setType($message_type) 360 ->save(); 361 362 PhabricatorWorker::scheduleTask( 363 'HarbormasterBuildWorker', 364 array( 365 'buildID' => $this->getID(), 366 ), 367 array( 368 'objectPHID' => $this->getPHID(), 369 'containerPHID' => $this->getBuildablePHID(), 370 )); 371 } 372 373 public function releaseAllArtifacts(PhabricatorUser $viewer) { 374 $targets = id(new HarbormasterBuildTargetQuery()) 375 ->setViewer($viewer) 376 ->withBuildPHIDs(array($this->getPHID())) 377 ->withBuildGenerations(array($this->getBuildGeneration())) 378 ->execute(); 379 380 if (!$targets) { 381 return; 382 } 383 384 $target_phids = mpull($targets, 'getPHID'); 385 386 $artifacts = id(new HarbormasterBuildArtifactQuery()) 387 ->setViewer($viewer) 388 ->withBuildTargetPHIDs($target_phids) 389 ->withIsReleased(false) 390 ->execute(); 391 foreach ($artifacts as $artifact) { 392 $artifact->releaseArtifact(); 393 } 394 } 395 396 public function restartBuild(PhabricatorUser $viewer) { 397 // TODO: This should become transactional. 398 399 // We're restarting the build, so release all previous artifacts. 400 $this->releaseAllArtifacts($viewer); 401 402 // Increment the build generation counter on the build. 403 $this->setBuildGeneration($this->getBuildGeneration() + 1); 404 405 // Currently running targets should periodically check their build 406 // generation (which won't have changed) against the build's generation. 407 // If it is different, they will automatically stop what they're doing 408 // and abort. 409 410 // Previously we used to delete targets, logs and artifacts here. Instead, 411 // leave them around so users can view previous generations of this build. 412 } 413 414 415/* -( PhabricatorApplicationTransactionInterface )------------------------- */ 416 417 418 public function getApplicationTransactionEditor() { 419 return new HarbormasterBuildTransactionEditor(); 420 } 421 422 public function getApplicationTransactionTemplate() { 423 return new HarbormasterBuildTransaction(); 424 } 425 426 427/* -( PhabricatorPolicyInterface )----------------------------------------- */ 428 429 430 public function getCapabilities() { 431 return array( 432 PhabricatorPolicyCapability::CAN_VIEW, 433 PhabricatorPolicyCapability::CAN_EDIT, 434 ); 435 } 436 437 public function getPolicy($capability) { 438 return $this->getBuildable()->getPolicy($capability); 439 } 440 441 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 442 return $this->getBuildable()->hasAutomaticCapability( 443 $capability, 444 $viewer); 445 } 446 447 public function describeAutomaticCapability($capability) { 448 return pht('A build inherits policies from its buildable.'); 449 } 450 451 452/* -( PhabricatorConduitResultInterface )---------------------------------- */ 453 454 455 public function getFieldSpecificationsForConduit() { 456 return array( 457 id(new PhabricatorConduitSearchFieldSpecification()) 458 ->setKey('buildablePHID') 459 ->setType('phid') 460 ->setDescription(pht('PHID of the object this build is building.')), 461 id(new PhabricatorConduitSearchFieldSpecification()) 462 ->setKey('buildPlanPHID') 463 ->setType('phid') 464 ->setDescription(pht('PHID of the build plan being run.')), 465 id(new PhabricatorConduitSearchFieldSpecification()) 466 ->setKey('buildStatus') 467 ->setType('map<string, wild>') 468 ->setDescription(pht('The current status of this build.')), 469 id(new PhabricatorConduitSearchFieldSpecification()) 470 ->setKey('initiatorPHID') 471 ->setType('phid') 472 ->setDescription(pht('The person (or thing) that started this build.')), 473 id(new PhabricatorConduitSearchFieldSpecification()) 474 ->setKey('name') 475 ->setType('string') 476 ->setDescription(pht('The name of this build.')), 477 ); 478 } 479 480 public function getFieldValuesForConduit() { 481 $status = $this->getBuildStatus(); 482 return array( 483 'buildablePHID' => $this->getBuildablePHID(), 484 'buildPlanPHID' => $this->getBuildPlanPHID(), 485 'buildStatus' => array( 486 'value' => $status, 487 'name' => HarbormasterBuildStatus::getBuildStatusName($status), 488 'color.ansi' => 489 HarbormasterBuildStatus::getBuildStatusANSIColor($status), 490 ), 491 'initiatorPHID' => nonempty($this->getInitiatorPHID(), null), 492 'name' => $this->getName(), 493 ); 494 } 495 496 public function getConduitSearchAttachments() { 497 return array( 498 id(new HarbormasterQueryBuildsSearchEngineAttachment()) 499 ->setAttachmentKey('querybuilds'), 500 ); 501 } 502 503 504/* -( PhabricatorDestructibleInterface )----------------------------------- */ 505 506 public function destroyObjectPermanently( 507 PhabricatorDestructionEngine $engine) { 508 $viewer = $engine->getViewer(); 509 510 $this->openTransaction(); 511 $targets = id(new HarbormasterBuildTargetQuery()) 512 ->setViewer($viewer) 513 ->withBuildPHIDs(array($this->getPHID())) 514 ->execute(); 515 foreach ($targets as $target) { 516 $engine->destroyObject($target); 517 } 518 519 $messages = id(new HarbormasterBuildMessageQuery()) 520 ->setViewer($viewer) 521 ->withReceiverPHIDs(array($this->getPHID())) 522 ->execute(); 523 foreach ($messages as $message) { 524 $engine->destroyObject($message); 525 } 526 527 $this->delete(); 528 $this->saveTransaction(); 529 } 530 531 532}