@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.

Implement basic Harbormaster daemon and start builds.

Summary: This implements a basic Harbormaster daemon that takes pending builds and builds them (currently just sleeps 15 seconds before moving to passed state). It also implements an interface to apply a build plan to a buildable, so that users can kick off builds for a buildable.

Test Plan: Ran `bin/phd debug PhabricatorHarbormasterBuildDaemon` and used the interface to start some builds by applying a build plan. Observed them move from 'pending' to 'building' to 'passed'.

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley

CC: Korvin, epriestley, aran

Maniphest Tasks: T1049

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

authored by

James Rhodes and committed by
epriestley
ca5400d1 5cc26f06

+221 -158
+4 -4
src/__phutil_library_map__.php
··· 656 656 'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php', 657 657 'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php', 658 658 'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php', 659 + 'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php', 659 660 'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php', 661 + 'HarbormasterBuildableApplyController' => 'applications/harbormaster/controller/HarbormasterBuildableApplyController.php', 660 662 'HarbormasterBuildableArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php', 661 663 'HarbormasterBuildableEditController' => 'applications/harbormaster/controller/HarbormasterBuildableEditController.php', 662 664 'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php', ··· 675 677 'HarbormasterPHIDTypeBuildable' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php', 676 678 'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php', 677 679 'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php', 678 - 'HarbormasterPlanExecuteController' => 'applications/harbormaster/controller/HarbormasterPlanExecuteController.php', 679 680 'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php', 680 681 'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php', 681 682 'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php', 682 - 'HarbormasterRunnerWorker' => 'applications/harbormaster/worker/HarbormasterRunnerWorker.php', 683 683 'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php', 684 684 'HeraldAction' => 'applications/herald/storage/HeraldAction.php', 685 685 'HeraldAdapter' => 'applications/herald/adapter/HeraldAdapter.php', ··· 2868 2868 'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2869 2869 'HarbormasterBuildTarget' => 'HarbormasterDAO', 2870 2870 'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2871 + 'HarbormasterBuildWorker' => 'PhabricatorWorker', 2871 2872 'HarbormasterBuildable' => 2872 2873 array( 2873 2874 0 => 'HarbormasterDAO', 2874 2875 1 => 'PhabricatorPolicyInterface', 2875 2876 ), 2877 + 'HarbormasterBuildableApplyController' => 'HarbormasterController', 2876 2878 'HarbormasterBuildableArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2877 2879 'HarbormasterBuildableEditController' => 'HarbormasterController', 2878 2880 'HarbormasterBuildableListController' => ··· 2895 2897 'HarbormasterPHIDTypeBuildable' => 'PhabricatorPHIDType', 2896 2898 'HarbormasterPlanController' => 'PhabricatorController', 2897 2899 'HarbormasterPlanEditController' => 'HarbormasterPlanController', 2898 - 'HarbormasterPlanExecuteController' => 'HarbormasterPlanController', 2899 2900 'HarbormasterPlanListController' => 2900 2901 array( 2901 2902 0 => 'HarbormasterPlanController', ··· 2903 2904 ), 2904 2905 'HarbormasterPlanViewController' => 'HarbormasterPlanController', 2905 2906 'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject', 2906 - 'HarbormasterRunnerWorker' => 'PhabricatorWorker', 2907 2907 'HarbormasterScratchTable' => 'HarbormasterDAO', 2908 2908 'HeraldAction' => 'HeraldDAO', 2909 2909 'HeraldApplyTranscript' => 'HeraldDAO',
+1 -1
src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
··· 44 44 => 'HarbormasterBuildableListController', 45 45 'buildable/' => array( 46 46 'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableEditController', 47 + 'apply/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableApplyController', 47 48 ), 48 49 'plan/' => array( 49 50 '(?:query/(?P<queryKey>[^/]+)/)?' 50 51 => 'HarbormasterPlanListController', 51 52 'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanEditController', 52 53 '(?P<id>\d+)/' => 'HarbormasterPlanViewController', 53 - 'execute/(?P<id>\d+)/' => 'HarbormasterPlanExecuteController', 54 54 ), 55 55 ), 56 56 );
+81
src/applications/harbormaster/controller/HarbormasterBuildableApplyController.php
··· 1 + <?php 2 + 3 + final class HarbormasterBuildableApplyController 4 + extends HarbormasterController { 5 + 6 + private $id; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->id = $data['id']; 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + $id = $this->id; 17 + 18 + $buildable = id(new HarbormasterBuildableQuery()) 19 + ->setViewer($viewer) 20 + ->withIDs(array($id)) 21 + ->executeOne(); 22 + if ($buildable === null) { 23 + throw new Exception("Buildable not found!"); 24 + } 25 + 26 + $buildable_uri = '/B'.$buildable->getID(); 27 + 28 + if ($request->isDialogFormPost()) { 29 + $plan = id(new HarbormasterBuildPlanQuery()) 30 + ->setViewer($viewer) 31 + ->withIDs(array($request->getInt('build-plan'))) 32 + ->executeOne(); 33 + 34 + $build = HarbormasterBuild::initializeNewBuild($viewer); 35 + $build->setBuildablePHID($buildable->getPHID()); 36 + $build->setBuildPlanPHID($plan->getPHID()); 37 + $build->setBuildStatus(HarbormasterBuild::STATUS_PENDING); 38 + $build->save(); 39 + 40 + PhabricatorWorker::scheduleTask( 41 + 'HarbormasterBuildWorker', 42 + array( 43 + 'buildID' => $build->getID() 44 + )); 45 + 46 + return id(new AphrontRedirectResponse())->setURI($buildable_uri); 47 + } 48 + 49 + $plans = id(new HarbormasterBuildPlanQuery()) 50 + ->setViewer($viewer) 51 + ->execute(); 52 + 53 + $options = array(); 54 + foreach ($plans as $plan) { 55 + $options[$plan->getID()] = $plan->getName(); 56 + } 57 + 58 + // FIXME: I'd really like to use the dialog that "Edit Differential 59 + // Revisions" uses, but that code is quite hard-coded for the particular 60 + // uses, so for now we just give a single dropdown. 61 + 62 + $dialog = new AphrontDialogView(); 63 + $dialog->setTitle(pht('Apply which plan?')) 64 + ->setUser($viewer) 65 + ->addSubmitButton(pht('Apply')) 66 + ->addCancelButton($buildable_uri); 67 + $dialog->appendChild( 68 + phutil_tag( 69 + 'p', 70 + array(), 71 + pht( 72 + 'Select what build plan you want to apply to this buildable:'))); 73 + $dialog->appendChild( 74 + id(new AphrontFormSelectControl()) 75 + ->setUser($viewer) 76 + ->setName('build-plan') 77 + ->setOptions($options)); 78 + return id(new AphrontDialogResponse())->setDialog($dialog); 79 + } 80 + 81 + }
+1 -1
src/applications/harbormaster/controller/HarbormasterBuildableListController.php
··· 36 36 $id = $buildable->getID(); 37 37 38 38 $item = id(new PHUIObjectItemView()) 39 - ->setHeader(pht('Build %d', $buildable->getID())); 39 + ->setHeader(pht('Buildable %d', $buildable->getID())); 40 40 41 41 if ($id) { 42 42 $item->setHref("/B{$id}");
+35
src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
··· 37 37 $item = id(new PHUIObjectItemView()) 38 38 ->setObjectName(pht('Build %d', $build->getID())) 39 39 ->setHeader($build->getName()); 40 + switch ($build->getBuildStatus()) { 41 + case HarbormasterBuild::STATUS_INACTIVE: 42 + $item->setBarColor('grey'); 43 + $item->addAttribute(pht('Inactive')); 44 + break; 45 + case HarbormasterBuild::STATUS_PENDING: 46 + $item->setBarColor('blue'); 47 + $item->addAttribute(pht('Pending')); 48 + break; 49 + case HarbormasterBuild::STATUS_WAITING: 50 + $item->setBarColor('blue'); 51 + $item->addAttribute(pht('Waiting on Resource')); 52 + break; 53 + case HarbormasterBuild::STATUS_BUILDING: 54 + $item->setBarColor('yellow'); 55 + $item->addAttribute(pht('Building')); 56 + break; 57 + case HarbormasterBuild::STATUS_PASSED: 58 + $item->setBarColor('green'); 59 + $item->addAttribute(pht('Passed')); 60 + break; 61 + case HarbormasterBuild::STATUS_FAILED: 62 + $item->setBarColor('red'); 63 + $item->addAttribute(pht('Failed')); 64 + break; 65 + } 40 66 $build_list->addItem($item); 41 67 } 42 68 ··· 79 105 ->setUser($viewer) 80 106 ->setObject($buildable) 81 107 ->setObjectURI("/B{$id}"); 108 + 109 + $apply_uri = $this->getApplicationURI('/buildable/apply/'.$id.'/'); 110 + 111 + $list->addAction( 112 + id(new PhabricatorActionView()) 113 + ->setName(pht('Apply Build Plan')) 114 + ->setIcon('edit') 115 + ->setHref($apply_uri) 116 + ->setWorkflow(true)); 82 117 83 118 return $list; 84 119 }
-88
src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php
··· 1 - <?php 2 - 3 - final class HarbormasterPlanExecuteController 4 - extends HarbormasterPlanController { 5 - 6 - private $id; 7 - 8 - public function willProcessRequest(array $data) { 9 - $this->id = $data['id']; 10 - } 11 - 12 - public function processRequest() { 13 - $request = $this->getRequest(); 14 - $viewer = $request->getUser(); 15 - 16 - $this->requireApplicationCapability( 17 - HarbormasterCapabilityManagePlans::CAPABILITY); 18 - 19 - $id = $this->id; 20 - 21 - $plan = id(new HarbormasterBuildPlanQuery()) 22 - ->setViewer($viewer) 23 - ->withIDs(array($id)) 24 - ->executeOne(); 25 - if (!$plan) { 26 - return new Aphront404Response(); 27 - } 28 - 29 - $cancel_uri = $this->getApplicationURI("plan/{$id}/"); 30 - 31 - $v_buildable = null; 32 - $e_buildable = null; 33 - 34 - $errors = array(); 35 - if ($request->isFormPost()) { 36 - $v_buildable = $request->getStr('buildable'); 37 - 38 - if ($v_buildable) { 39 - $buildable = id(new HarbormasterBuildableQuery()) 40 - ->setViewer($viewer) 41 - ->withIDs(array(trim($v_buildable, 'B'))) 42 - ->executeOne(); 43 - if (!$buildable) { 44 - $e_buildable = pht('Invalid'); 45 - } 46 - } else { 47 - $e_buildable = pht('Required'); 48 - $errors[] = pht('You must provide a buildable.'); 49 - } 50 - 51 - if (!$errors) { 52 - $build_plan = HarbormasterBuild::initializeNewBuild($viewer) 53 - ->setBuildablePHID($buildable->getPHID()) 54 - ->setBuildPlanPHID($plan->getPHID()) 55 - ->save(); 56 - 57 - $buildable_id = $buildable->getID(); 58 - 59 - return id(new AphrontRedirectResponse()) 60 - ->setURI("/B{$buildable_id}"); 61 - } 62 - } 63 - 64 - if ($errors) { 65 - $errors = id(new AphrontErrorView())->setErrors($errors); 66 - } 67 - 68 - $form = id(new PHUIFormLayoutView()) 69 - ->appendChild( 70 - id(new AphrontFormTextControl()) 71 - ->setLabel(pht('Buildable')) 72 - ->setName('buildable') 73 - ->setValue($v_buildable) 74 - ->setError($e_buildable)); 75 - 76 - $dialog = id(new AphrontDialogView()) 77 - ->setUser($viewer) 78 - ->setTitle(pht('Execute Build Plan')) 79 - ->setWidth(AphrontDialogView::WIDTH_FORM) 80 - ->appendChild($errors) 81 - ->appendChild($form) 82 - ->addSubmitButton(pht('Execute Build Plan')) 83 - ->addCancelButton($cancel_uri); 84 - 85 - return id(new AphrontDialogResponse())->setDialog($dialog); 86 - } 87 - 88 - }
-8
src/applications/harbormaster/controller/HarbormasterPlanViewController.php
··· 88 88 ->setDisabled(!$can_edit) 89 89 ->setIcon('edit')); 90 90 91 - $list->addAction( 92 - id(new PhabricatorActionView()) 93 - ->setName(pht('Manually Execute Plan')) 94 - ->setHref($this->getApplicationURI("plan/execute/{$id}/")) 95 - ->setWorkflow(true) 96 - ->setDisabled(!$can_edit) 97 - ->setIcon('arrow_right')); 98 - 99 91 return $list; 100 92 } 101 93
+13
src/applications/harbormaster/query/HarbormasterBuildQuery.php
··· 5 5 6 6 private $ids; 7 7 private $phids; 8 + private $buildStatuses; 8 9 private $buildablePHIDs; 9 10 private $buildPlanPHIDs; 10 11 ··· 17 18 18 19 public function withPHIDs(array $phids) { 19 20 $this->phids = $phids; 21 + return $this; 22 + } 23 + 24 + public function withBuildStatuses(array $build_statuses) { 25 + $this->buildStatuses = $build_statuses; 20 26 return $this; 21 27 } 22 28 ··· 113 119 $conn_r, 114 120 'phid in (%Ls)', 115 121 $this->phids); 122 + } 123 + 124 + if ($this->buildStatuses) { 125 + $where[] = qsprintf( 126 + $conn_r, 127 + 'buildStatus in (%Ls)', 128 + $this->buildStatuses); 116 129 } 117 130 118 131 if ($this->buildablePHIDs) {
+4 -2
src/applications/harbormaster/storage/HarbormasterBuildable.php
··· 12 12 private $containerObject = self::ATTACHABLE; 13 13 private $buildableHandle = self::ATTACHABLE; 14 14 15 + const STATUS_WHATEVER = 'whatever'; 16 + 15 17 public static function initializeNewBuildable(PhabricatorUser $actor) { 16 18 return id(new HarbormasterBuildable()) 17 - ->setBuildStatus('new') // TODO: Define these. 18 - ->setBuildableStatus('active'); // TODO: Define these, too. 19 + ->setBuildStatus(self::STATUS_WHATEVER) 20 + ->setBuildableStatus(self::STATUS_WHATEVER); 19 21 } 20 22 21 23 public function getConfiguration() {
+31 -1
src/applications/harbormaster/storage/build/HarbormasterBuild.php
··· 10 10 private $buildable = self::ATTACHABLE; 11 11 private $buildPlan = self::ATTACHABLE; 12 12 13 + /** 14 + * Not currently being built. 15 + */ 16 + const STATUS_INACTIVE = 'inactive'; 17 + 18 + /** 19 + * Pending pick up by the Harbormaster daemon. 20 + */ 21 + const STATUS_PENDING = 'pending'; 22 + 23 + /** 24 + * Waiting for a resource to be allocated (not yet relevant). 25 + */ 26 + const STATUS_WAITING = 'waiting'; 27 + 28 + /** 29 + * Current building the buildable. 30 + */ 31 + const STATUS_BUILDING = 'building'; 32 + 33 + /** 34 + * The build has passed. 35 + */ 36 + const STATUS_PASSED = 'passed'; 37 + 38 + /** 39 + * The build has failed. 40 + */ 41 + const STATUS_FAILED = 'failed'; 42 + 13 43 public static function initializeNewBuild(PhabricatorUser $actor) { 14 44 return id(new HarbormasterBuild()) 15 - ->setBuildStatus('building'); // TODO: Sort this. 45 + ->setBuildStatus(self::STATUS_INACTIVE); 16 46 } 17 47 18 48 public function getConfiguration() {
+51
src/applications/harbormaster/worker/HarbormasterBuildWorker.php
··· 1 + <?php 2 + 3 + /** 4 + * Run builds 5 + */ 6 + final class HarbormasterBuildWorker extends PhabricatorWorker { 7 + 8 + public function getRequiredLeaseTime() { 9 + return 60 * 60 * 24; 10 + } 11 + 12 + public function doWork() { 13 + $data = $this->getTaskData(); 14 + $id = idx($data, 'buildID'); 15 + 16 + // Get a reference to the build. 17 + $build = id(new HarbormasterBuildQuery()) 18 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 19 + ->withBuildStatuses(array(HarbormasterBuild::STATUS_PENDING)) 20 + ->withIDs(array($id)) 21 + ->needBuildPlans(true) 22 + ->executeOne(); 23 + if (!$build) { 24 + throw new PhabricatorWorkerPermanentFailureException( 25 + pht('Invalid build ID "%s".', $id)); 26 + } 27 + 28 + try { 29 + $build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING); 30 + $build->save(); 31 + 32 + $buildable = $build->getBuildable(); 33 + $plan = $build->getBuildPlan(); 34 + 35 + // TODO: Do the actual build here. 36 + sleep(15); 37 + 38 + // If we get to here, then the build has passed. 39 + $build->setBuildStatus(HarbormasterBuild::STATUS_PASSED); 40 + $build->save(); 41 + } catch (Exception $e) { 42 + // If any exception is raised, the build is marked as a failure and 43 + // the exception is re-thrown (this ensures we don't leave builds 44 + // in an inconsistent state). 45 + $build->setBuildStatus(HarbormasterBuild::STATUS_FAILED); 46 + $build->save(); 47 + throw $e; 48 + } 49 + } 50 + 51 + }
-53
src/applications/harbormaster/worker/HarbormasterRunnerWorker.php
··· 1 - <?php 2 - 3 - final class HarbormasterRunnerWorker extends PhabricatorWorker { 4 - 5 - public function getRequiredLeaseTime() { 6 - return 60 * 60 * 24; 7 - } 8 - 9 - protected function doWork() { 10 - $data = $this->getTaskData(); 11 - $id = idx($data, 'commitID'); 12 - 13 - $commit = id(new PhabricatorRepositoryCommit())->loadOneWhere( 14 - 'id = %d', 15 - $id); 16 - 17 - if (!$commit) { 18 - throw new PhabricatorWorkerPermanentFailureException( 19 - "Commit '{$id}' does not exist!"); 20 - } 21 - 22 - // TODO: (T603) Policy interaction? 23 - $repository = id(new PhabricatorRepository())->loadOneWhere( 24 - 'id = %d', 25 - $commit->getRepositoryID()); 26 - 27 - if (!$repository) { 28 - throw new PhabricatorWorkerPermanentFailureException( 29 - "Unable to load repository for commit '{$id}'!"); 30 - } 31 - 32 - $lease = id(new DrydockLease()) 33 - ->setResourceType('working-copy') 34 - ->setAttributes( 35 - array( 36 - 'repositoryID' => $repository->getID(), 37 - 'commit' => $commit->getCommitIdentifier(), 38 - )) 39 - ->releaseOnDestruction() 40 - ->waitUntilActive(); 41 - 42 - $cmd = $lease->getInterface('command'); 43 - list($json) = $cmd 44 - ->setWorkingDirectory($lease->getResource()->getAttribute('path')) 45 - ->execx('arc unit --everything --json'); 46 - $lease->release(); 47 - 48 - // TODO: Do something actually useful with this. Requires Harbormaster 49 - // buildout. 50 - echo $json; 51 - } 52 - 53 - }