@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

Add `bin/harbormaster` to make builds easier to debug

Summary:
Ref T1049. Adds `bin/harbormaster` and `bin/harbormaster build` for applying plans from the console. Since this gets `--trace`, it's much easier to debug what's going on.

This doesn't work properly with some of the Drydock steps yet, I need to look at those. I think `setRunAllTasksInProcess` probably obsoletes some of the mechanisms. It might also not work with "Wait for Builds" but I didn't check.

Test Plan: Used `bin/harbormaster` to run a bunch of builds. Ran builds from web UI.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T1049

Differential Revision: https://secure.phabricator.com/D7825

+259 -37
+1
bin/harbormaster
··· 1 + ../scripts/setup/manage_harbormaster.php
+22
scripts/setup/manage_harbormaster.php
··· 1 + #!/usr/bin/env php 2 + <?php 3 + 4 + $root = dirname(dirname(dirname(__FILE__))); 5 + require_once $root.'/scripts/__init_script__.php'; 6 + 7 + $args = new PhutilArgumentParser($argv); 8 + $args->setTagline('manage Harbormaster'); 9 + $args->setSynopsis(<<<EOSYNOPSIS 10 + **harbormaster** __command__ [__options__] 11 + Manage and debug Harbormaster. 12 + 13 + EOSYNOPSIS 14 + ); 15 + $args->parseStandardArguments(); 16 + 17 + $workflows = id(new PhutilSymbolLoader()) 18 + ->setAncestorClass('HarbormasterManagementWorkflow') 19 + ->loadObjects(); 20 + $workflows[] = new PhutilHelpArgumentWorkflow(); 21 + 22 + $args->parseWorkflows($workflows);
+9
src/__phutil_library_map__.php
··· 707 707 'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php', 708 708 'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php', 709 709 'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php', 710 + 'HarbormasterBuildableInterface' => 'applications/harbormaster/interface/HarbormasterBuildableInterface.php', 710 711 'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php', 711 712 'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php', 712 713 'HarbormasterBuildableSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildableSearchEngine.php', ··· 715 716 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', 716 717 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', 717 718 'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php', 719 + 'HarbormasterManagementBuildWorkflow' => 'applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php', 720 + 'HarbormasterManagementWorkflow' => 'applications/harbormaster/management/HarbormasterManagementWorkflow.php', 718 721 'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php', 719 722 'HarbormasterPHIDTypeBuild' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php', 720 723 'HarbormasterPHIDTypeBuildItem' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php', ··· 2747 2750 array( 2748 2751 0 => 'DifferentialDAO', 2749 2752 1 => 'PhabricatorPolicyInterface', 2753 + 2 => 'HarbormasterBuildableInterface', 2750 2754 ), 2751 2755 'DifferentialDiffContentMail' => 'DifferentialMail', 2752 2756 'DifferentialDiffCreateController' => 'DifferentialController', ··· 2814 2818 2 => 'PhabricatorPolicyInterface', 2815 2819 3 => 'PhabricatorFlaggableInterface', 2816 2820 4 => 'PhrequentTrackableInterface', 2821 + 5 => 'HarbormasterBuildableInterface', 2817 2822 ), 2818 2823 'DifferentialRevisionCommentListView' => 'AphrontView', 2819 2824 'DifferentialRevisionCommentView' => 'AphrontView', ··· 3131 3136 array( 3132 3137 0 => 'HarbormasterDAO', 3133 3138 1 => 'PhabricatorPolicyInterface', 3139 + 2 => 'HarbormasterBuildableInterface', 3134 3140 ), 3135 3141 'HarbormasterBuildableListController' => 3136 3142 array( ··· 3144 3150 'HarbormasterController' => 'PhabricatorController', 3145 3151 'HarbormasterDAO' => 'PhabricatorLiskDAO', 3146 3152 'HarbormasterHTTPRequestBuildStepImplementation' => 'VariableBuildStepImplementation', 3153 + 'HarbormasterManagementBuildWorkflow' => 'HarbormasterManagementWorkflow', 3154 + 'HarbormasterManagementWorkflow' => 'PhutilArgumentWorkflow', 3147 3155 'HarbormasterObject' => 'HarbormasterDAO', 3148 3156 'HarbormasterPHIDTypeBuild' => 'PhabricatorPHIDType', 3149 3157 'HarbormasterPHIDTypeBuildItem' => 'PhabricatorPHIDType', ··· 4342 4350 1 => 'PhabricatorPolicyInterface', 4343 4351 2 => 'PhabricatorFlaggableInterface', 4344 4352 3 => 'PhabricatorTokenReceiverInterface', 4353 + 4 => 'HarbormasterBuildableInterface', 4345 4354 ), 4346 4355 'PhabricatorRepositoryCommitChangeParserWorker' => 'PhabricatorRepositoryCommitParserWorker', 4347 4356 'PhabricatorRepositoryCommitData' => 'PhabricatorRepositoryDAO',
+23 -1
src/applications/differential/storage/DifferentialDiff.php
··· 2 2 3 3 final class DifferentialDiff 4 4 extends DifferentialDAO 5 - implements PhabricatorPolicyInterface { 5 + implements 6 + PhabricatorPolicyInterface, 7 + HarbormasterBuildableInterface { 6 8 7 9 protected $revisionID; 8 10 protected $authorPHID; ··· 336 338 return pht( 337 339 'This diff is attached to a revision, and inherits its policies.'); 338 340 } 341 + return null; 342 + } 343 + 344 + 345 + 346 + /* -( HarbormasterBuildableInterface )------------------------------------- */ 347 + 348 + 349 + public function getHarbormasterBuildablePHID() { 350 + return $this->getPHID(); 351 + } 352 + 353 + public function getHarbormasterContainerPHID() { 354 + if ($this->getRevisionID()) { 355 + $revision = id(new DifferentialRevision())->load($this->getRevisionID()); 356 + if ($revision) { 357 + return $revision->getPHID(); 358 + } 359 + } 360 + 339 361 return null; 340 362 } 341 363
+14 -1
src/applications/differential/storage/DifferentialRevision.php
··· 5 5 PhabricatorTokenReceiverInterface, 6 6 PhabricatorPolicyInterface, 7 7 PhabricatorFlaggableInterface, 8 - PhrequentTrackableInterface { 8 + PhrequentTrackableInterface, 9 + HarbormasterBuildableInterface { 9 10 10 11 protected $title = ''; 11 12 protected $originalTitle; ··· 410 411 411 412 public function isClosed() { 412 413 return DifferentialRevisionStatus::isClosedStatus($this->getStatus()); 414 + } 415 + 416 + 417 + /* -( HarbormasterBuildableInterface )------------------------------------- */ 418 + 419 + 420 + public function getHarbormasterBuildablePHID() { 421 + return $this->loadActiveDiff()->getPHID(); 422 + } 423 + 424 + public function getHarbormasterContainerPHID() { 425 + return $this->getPHID(); 413 426 } 414 427 415 428 }
+11 -12
src/applications/harbormaster/controller/HarbormasterPlanRunController.php
··· 41 41 ->withNames(array($v_name)) 42 42 ->executeOne(); 43 43 44 - if ($object instanceof DifferentialRevision) { 45 - $revision = $object; 46 - $object = $object->loadActiveDiff(); 47 - $buildable 48 - ->setBuildablePHID($object->getPHID()) 49 - ->setContainerPHID($revision->getPHID()); 50 - } else if ($object instanceof PhabricatorRepositoryCommit) { 44 + if ($object instanceof HarbormasterBuildableInterface) { 51 45 $buildable 52 - ->setBuildablePHID($object->getPHID()) 53 - ->setContainerPHID($object->getRepository()->getPHID()); 46 + ->setBuildablePHID($object->getHarbormasterBuildablePHID()) 47 + ->setContainerPHID($object->getHarbormasterContainerPHID()); 54 48 } else { 55 49 $e_name = pht('Invalid'); 56 50 $errors[] = pht('Enter the name of a revision or commit.'); ··· 62 56 63 57 if (!$errors) { 64 58 $buildable->save(); 59 + $buildable->applyPlan($plan); 65 60 66 61 $buildable_uri = '/B'.$buildable->getID(); 67 62 return id(new AphrontRedirectResponse())->setURI($buildable_uri); ··· 80 75 ->setUser($viewer) 81 76 ->appendRemarkupInstructions( 82 77 pht( 83 - "Enter the name of a commit or revision to run this plan on.\n\n". 84 - "For example: `rX123456` or `D123`")) 78 + "Enter the name of a commit or revision to run this plan on (for ". 79 + "example, `rX123456` or `D123`).\n\n". 80 + "For more detailed output, you can also run manual builds from ". 81 + "the command line:\n\n". 82 + " phabricator/ $ ./bin/harbormaster build <object> --plan %s", 83 + $plan->getID())) 85 84 ->appendChild( 86 85 id(new AphrontFormTextControl()) 87 86 ->setLabel('Buildable Name') ··· 90 89 ->setValue($v_name)); 91 90 92 91 $dialog = id(new AphrontDialogView()) 93 - ->setWidth(AphrontDialogView::WIDTH_FORM) 92 + ->setWidth(AphrontDialogView::WIDTH_FULL) 94 93 ->setUser($viewer) 95 94 ->setTitle($title) 96 95 ->appendChild($form)
+16 -9
src/applications/harbormaster/event/HarbormasterUIEventListener.php
··· 24 24 return; 25 25 } 26 26 27 - $target = null; 28 - if ($object instanceof PhabricatorRepositoryCommit) { 29 - $target = $object; 30 - } elseif ($object instanceof DifferentialRevision) { 31 - $target = $object->loadActiveDiff(); 32 - } else { 27 + if ($object instanceof HarbormasterBuildable) { 28 + // Although HarbormasterBuildable implements the correct interface, it 29 + // does not make sense to show a build's build status. In the best case 30 + // it is meaningless, and in the worst case it's confusing. 31 + return; 32 + } 33 + 34 + if (!($object instanceof HarbormasterBuildableInterface)) { 35 + return; 36 + } 37 + 38 + $buildable_phid = $object->getBuildablePHID(); 39 + if (!$buildable_phid) { 33 40 return; 34 41 } 35 42 ··· 39 46 40 47 $buildables = id(new HarbormasterBuildableQuery()) 41 48 ->setViewer($user) 42 - ->withBuildablePHIDs(array($target->getPHID())) 49 + ->withManualBuildables(false) 50 + ->withBuildablePHIDs(array($buildable_phid)) 43 51 ->execute(); 44 52 if (!$buildables) { 45 53 return; ··· 62 70 63 71 foreach ($builds as $build) { 64 72 $item = new PHUIStatusItemView(); 65 - $item->setTarget( 66 - $build_handles[$build->getPHID()]->renderLink()); 73 + $item->setTarget($build_handles[$build->getPHID()]->renderLink()); 67 74 68 75 switch ($build->getBuildStatus()) { 69 76 case HarbormasterBuild::STATUS_INACTIVE:
+8
src/applications/harbormaster/interface/HarbormasterBuildableInterface.php
··· 1 + <?php 2 + 3 + interface HarbormasterBuildableInterface { 4 + 5 + public function getHarbormasterBuildablePHID(); 6 + public function getHarbormasterContainerPHID(); 7 + 8 + }
+92
src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php
··· 1 + <?php 2 + 3 + final class HarbormasterManagementBuildWorkflow 4 + extends HarbormasterManagementWorkflow { 5 + 6 + public function didConstruct() { 7 + $this 8 + ->setName('build') 9 + ->setExamples('**build** [__options__] __buildable__ --plan __id__') 10 + ->setSynopsis(pht('Run plan __id__ on __buildable__.')) 11 + ->setArguments( 12 + array( 13 + array( 14 + 'name' => 'plan', 15 + 'param' => 'id', 16 + 'help' => pht('ID of build plan to run.'), 17 + ), 18 + array( 19 + 'name' => 'buildable', 20 + 'wildcard' => true, 21 + ), 22 + )); 23 + } 24 + 25 + public function execute(PhutilArgumentParser $args) { 26 + $viewer = PhabricatorUser::getOmnipotentUser(); 27 + 28 + $names = $args->getArg('buildable'); 29 + if (count($names) != 1) { 30 + throw new PhutilArgumentUsageException( 31 + pht('Specify exactly one buildable, by object name.')); 32 + } 33 + 34 + $name = head($names); 35 + 36 + $buildable = id(new PhabricatorObjectQuery()) 37 + ->setViewer($viewer) 38 + ->withNames($names) 39 + ->executeOne(); 40 + if (!$buildable) { 41 + throw new PhutilArgumentUsageException( 42 + pht('No such buildable "%s"!', $name)); 43 + } 44 + 45 + if (!($buildable instanceof HarbormasterBuildableInterface)) { 46 + throw new PhutilArgumentUsageException( 47 + pht('Object "%s" is not a buildable!', $name)); 48 + } 49 + 50 + $plan_id = $args->getArg('plan'); 51 + if (!$plan_id) { 52 + throw new PhutilArgumentUsageException( 53 + pht('Use --plan to specify a build plan to run.')); 54 + } 55 + 56 + $plan = id(new HarbormasterBuildPlanQuery()) 57 + ->setViewer($viewer) 58 + ->withIDs(array($plan_id)) 59 + ->executeOne(); 60 + if (!$plan) { 61 + throw new PhutilArgumentUsageException( 62 + pht('Build plan "%s" does not exist.', $plan_id)); 63 + } 64 + 65 + $console = PhutilConsole::getConsole(); 66 + 67 + $buildable = HarbormasterBuildable::initializeNewBuildable($viewer) 68 + ->setIsManualBuildable(true) 69 + ->setBuildablePHID($buildable->getHarbormasterBuildablePHID()) 70 + ->setContainerPHID($buildable->getHarbormasterContainerPHID()) 71 + ->save(); 72 + 73 + $console->writeOut( 74 + "%s\n", 75 + pht( 76 + 'Applying plan %s to new buildable %s...', 77 + $plan->getID(), 78 + 'B'.$buildable->getID())); 79 + 80 + $console->writeOut( 81 + "\n %s\n\n", 82 + PhabricatorEnv::getProductionURI('/B'.$buildable->getID())); 83 + 84 + PhabricatorWorker::setRunAllTasksInProcess(true); 85 + $buildable->applyPlan($plan); 86 + 87 + $console->writeOut("%s\n", pht('Done.')); 88 + 89 + return 0; 90 + } 91 + 92 + }
+10
src/applications/harbormaster/management/HarbormasterManagementWorkflow.php
··· 1 + <?php 2 + 3 + abstract class HarbormasterManagementWorkflow 4 + extends PhutilArgumentWorkflow { 5 + 6 + public function isExecutable() { 7 + return true; 8 + } 9 + 10 + }
+38 -13
src/applications/harbormaster/storage/HarbormasterBuildable.php
··· 1 1 <?php 2 2 3 3 final class HarbormasterBuildable extends HarbormasterDAO 4 - implements PhabricatorPolicyInterface { 4 + implements 5 + PhabricatorPolicyInterface, 6 + HarbormasterBuildableInterface { 5 7 6 8 protected $buildablePHID; 7 9 protected $containerPHID; ··· 86 88 continue; 87 89 } 88 90 89 - $build = HarbormasterBuild::initializeNewBuild( 90 - PhabricatorUser::getOmnipotentUser()); 91 - $build->setBuildablePHID($buildable->getPHID()); 92 - $build->setBuildPlanPHID($plan->getPHID()); 93 - $build->setBuildStatus(HarbormasterBuild::STATUS_PENDING); 94 - $build->save(); 91 + $buildable->applyPlan($plan); 92 + } 93 + } 94 + 95 + public function applyPlan(HarbormasterBuildPlan $plan) { 96 + $viewer = PhabricatorUser::getOmnipotentUser(); 97 + $build = HarbormasterBuild::initializeNewBuild($viewer) 98 + ->setBuildablePHID($this->getPHID()) 99 + ->setBuildPlanPHID($plan->getPHID()) 100 + ->setBuildStatus(HarbormasterBuild::STATUS_PENDING) 101 + ->save(); 102 + 103 + PhabricatorWorker::scheduleTask( 104 + 'HarbormasterBuildWorker', 105 + array( 106 + 'buildID' => $build->getID() 107 + )); 95 108 96 - PhabricatorWorker::scheduleTask( 97 - 'HarbormasterBuildWorker', 98 - array( 99 - 'buildID' => $build->getID() 100 - )); 101 - } 109 + return $this; 102 110 } 103 111 104 112 public function getConfiguration() { ··· 183 191 'Users must be able to see the revision or repository to see a '. 184 192 'buildable.'); 185 193 } 194 + 195 + 196 + 197 + /* -( HarbormasterBuildableInterface )------------------------------------- */ 198 + 199 + 200 + public function getHarbormasterBuildablePHID() { 201 + // NOTE: This is essentially just for convenience, as it allows you create 202 + // a copy of a buildable by specifying `B123` without bothering to go 203 + // look up the underlying object. 204 + return $this->getBuildablePHID(); 205 + } 206 + 207 + public function getHarbormasterContainerPHID() { 208 + return $this->getContainerPHID(); 209 + } 210 + 186 211 187 212 }
+15 -1
src/applications/repository/storage/PhabricatorRepositoryCommit.php
··· 5 5 implements 6 6 PhabricatorPolicyInterface, 7 7 PhabricatorFlaggableInterface, 8 - PhabricatorTokenReceiverInterface { 8 + PhabricatorTokenReceiverInterface, 9 + HarbormasterBuildableInterface { 9 10 10 11 protected $repositoryID; 11 12 protected $phid; ··· 231 232 return id(new PhabricatorRepositoryCommit()) 232 233 ->loadFromArray($dict); 233 234 } 235 + 236 + 237 + /* -( HarbormasterBuildableInterface )------------------------------------- */ 238 + 239 + 240 + public function getHarbormasterBuildablePHID() { 241 + return $this->getPHID(); 242 + } 243 + 244 + public function getHarbormasterContainerPHID() { 245 + return $this->getRepository()->getPHID(); 246 + } 247 + 234 248 }