Index: src/__phutil_library_map__.php =================================================================== --- src/__phutil_library_map__.php +++ src/__phutil_library_map__.php @@ -656,7 +656,9 @@ 'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php', 'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php', 'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php', + 'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php', 'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php', + 'HarbormasterBuildableApplyController' => 'applications/harbormaster/controller/HarbormasterBuildableApplyController.php', 'HarbormasterBuildableArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php', 'HarbormasterBuildableEditController' => 'applications/harbormaster/controller/HarbormasterBuildableEditController.php', 'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php', @@ -675,11 +677,9 @@ 'HarbormasterPHIDTypeBuildable' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php', 'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php', 'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php', - 'HarbormasterPlanExecuteController' => 'applications/harbormaster/controller/HarbormasterPlanExecuteController.php', 'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php', 'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php', 'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php', - 'HarbormasterRunnerWorker' => 'applications/harbormaster/worker/HarbormasterRunnerWorker.php', 'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php', 'HeraldAction' => 'applications/herald/storage/HeraldAction.php', 'HeraldAdapter' => 'applications/herald/adapter/HeraldAdapter.php', @@ -2868,11 +2868,13 @@ 'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildTarget' => 'HarbormasterDAO', 'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildWorker' => 'PhabricatorWorker', 'HarbormasterBuildable' => array( 0 => 'HarbormasterDAO', 1 => 'PhabricatorPolicyInterface', ), + 'HarbormasterBuildableApplyController' => 'HarbormasterController', 'HarbormasterBuildableArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildableEditController' => 'HarbormasterController', 'HarbormasterBuildableListController' => @@ -2895,7 +2897,6 @@ 'HarbormasterPHIDTypeBuildable' => 'PhabricatorPHIDType', 'HarbormasterPlanController' => 'PhabricatorController', 'HarbormasterPlanEditController' => 'HarbormasterPlanController', - 'HarbormasterPlanExecuteController' => 'HarbormasterPlanController', 'HarbormasterPlanListController' => array( 0 => 'HarbormasterPlanController', @@ -2903,7 +2904,6 @@ ), 'HarbormasterPlanViewController' => 'HarbormasterPlanController', 'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject', - 'HarbormasterRunnerWorker' => 'PhabricatorWorker', 'HarbormasterScratchTable' => 'HarbormasterDAO', 'HeraldAction' => 'HeraldDAO', 'HeraldApplyTranscript' => 'HeraldDAO', Index: src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php =================================================================== --- src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php +++ src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php @@ -44,13 +44,13 @@ => 'HarbormasterBuildableListController', 'buildable/' => array( 'edit/(?:(?P\d+)/)?' => 'HarbormasterBuildableEditController', + 'apply/(?:(?P\d+)/)?' => 'HarbormasterBuildableApplyController', ), 'plan/' => array( '(?:query/(?P[^/]+)/)?' => 'HarbormasterPlanListController', 'edit/(?:(?P\d+)/)?' => 'HarbormasterPlanEditController', '(?P\d+)/' => 'HarbormasterPlanViewController', - 'execute/(?P\d+)/' => 'HarbormasterPlanExecuteController', ), ), ); Index: src/applications/harbormaster/controller/HarbormasterBuildableApplyController.php =================================================================== --- /dev/null +++ src/applications/harbormaster/controller/HarbormasterBuildableApplyController.php @@ -0,0 +1,81 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $id = $this->id; + + $buildable = id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if ($buildable === null) { + throw new Exception("Buildable not found!"); + } + + $buildable_uri = '/B'.$buildable->getID(); + + if ($request->isDialogFormPost()) { + $plan = id(new HarbormasterBuildPlanQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getInt('build-plan'))) + ->executeOne(); + + $build = HarbormasterBuild::initializeNewBuild($viewer); + $build->setBuildablePHID($buildable->getPHID()); + $build->setBuildPlanPHID($plan->getPHID()); + $build->setBuildStatus(HarbormasterBuild::STATUS_PENDING); + $build->save(); + + PhabricatorWorker::scheduleTask( + 'HarbormasterBuildWorker', + array( + 'buildID' => $build->getID() + )); + + return id(new AphrontRedirectResponse())->setURI($buildable_uri); + } + + $plans = id(new HarbormasterBuildPlanQuery()) + ->setViewer($viewer) + ->execute(); + + $options = array(); + foreach ($plans as $plan) { + $options[$plan->getID()] = $plan->getName(); + } + + // FIXME: I'd really like to use the dialog that "Edit Differential + // Revisions" uses, but that code is quite hard-coded for the particular + // uses, so for now we just give a single dropdown. + + $dialog = new AphrontDialogView(); + $dialog->setTitle(pht('Apply which plan?')) + ->setUser($viewer) + ->addSubmitButton(pht('Apply')) + ->addCancelButton($buildable_uri); + $dialog->appendChild( + phutil_tag( + 'p', + array(), + pht( + 'Select what build plan you want to apply to this buildable:'))); + $dialog->appendChild( + id(new AphrontFormSelectControl()) + ->setUser($viewer) + ->setName('build-plan') + ->setOptions($options)); + return id(new AphrontDialogResponse())->setDialog($dialog); + } + +} Index: src/applications/harbormaster/controller/HarbormasterBuildableListController.php =================================================================== --- src/applications/harbormaster/controller/HarbormasterBuildableListController.php +++ src/applications/harbormaster/controller/HarbormasterBuildableListController.php @@ -36,7 +36,7 @@ $id = $buildable->getID(); $item = id(new PHUIObjectItemView()) - ->setHeader(pht('Build %d', $buildable->getID())); + ->setHeader(pht('Buildable %d', $buildable->getID())); if ($id) { $item->setHref("/B{$id}"); Index: src/applications/harbormaster/controller/HarbormasterBuildableViewController.php =================================================================== --- src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -37,6 +37,32 @@ $item = id(new PHUIObjectItemView()) ->setObjectName(pht('Build %d', $build->getID())) ->setHeader($build->getName()); + switch ($build->getBuildStatus()) { + case HarbormasterBuild::STATUS_INACTIVE: + $item->setBarColor('grey'); + $item->addAttribute(pht('Inactive')); + break; + case HarbormasterBuild::STATUS_PENDING: + $item->setBarColor('blue'); + $item->addAttribute(pht('Pending')); + break; + case HarbormasterBuild::STATUS_WAITING: + $item->setBarColor('blue'); + $item->addAttribute(pht('Waiting on Resource')); + break; + case HarbormasterBuild::STATUS_BUILDING: + $item->setBarColor('yellow'); + $item->addAttribute(pht('Building')); + break; + case HarbormasterBuild::STATUS_PASSED: + $item->setBarColor('green'); + $item->addAttribute(pht('Passed')); + break; + case HarbormasterBuild::STATUS_FAILED: + $item->setBarColor('red'); + $item->addAttribute(pht('Failed')); + break; + } $build_list->addItem($item); } @@ -80,6 +106,15 @@ ->setObject($buildable) ->setObjectURI("/B{$id}"); + $apply_uri = $this->getApplicationURI('/buildable/apply/'.$id.'/'); + + $list->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Apply Build Plan')) + ->setIcon('edit') + ->setHref($apply_uri) + ->setWorkflow(true)); + return $list; } Index: src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php =================================================================== --- src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php +++ /dev/null @@ -1,88 +0,0 @@ -id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $this->requireApplicationCapability( - HarbormasterCapabilityManagePlans::CAPABILITY); - - $id = $this->id; - - $plan = id(new HarbormasterBuildPlanQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$plan) { - return new Aphront404Response(); - } - - $cancel_uri = $this->getApplicationURI("plan/{$id}/"); - - $v_buildable = null; - $e_buildable = null; - - $errors = array(); - if ($request->isFormPost()) { - $v_buildable = $request->getStr('buildable'); - - if ($v_buildable) { - $buildable = id(new HarbormasterBuildableQuery()) - ->setViewer($viewer) - ->withIDs(array(trim($v_buildable, 'B'))) - ->executeOne(); - if (!$buildable) { - $e_buildable = pht('Invalid'); - } - } else { - $e_buildable = pht('Required'); - $errors[] = pht('You must provide a buildable.'); - } - - if (!$errors) { - $build_plan = HarbormasterBuild::initializeNewBuild($viewer) - ->setBuildablePHID($buildable->getPHID()) - ->setBuildPlanPHID($plan->getPHID()) - ->save(); - - $buildable_id = $buildable->getID(); - - return id(new AphrontRedirectResponse()) - ->setURI("/B{$buildable_id}"); - } - } - - if ($errors) { - $errors = id(new AphrontErrorView())->setErrors($errors); - } - - $form = id(new PHUIFormLayoutView()) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Buildable')) - ->setName('buildable') - ->setValue($v_buildable) - ->setError($e_buildable)); - - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setTitle(pht('Execute Build Plan')) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($errors) - ->appendChild($form) - ->addSubmitButton(pht('Execute Build Plan')) - ->addCancelButton($cancel_uri); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - -} Index: src/applications/harbormaster/controller/HarbormasterPlanViewController.php =================================================================== --- src/applications/harbormaster/controller/HarbormasterPlanViewController.php +++ src/applications/harbormaster/controller/HarbormasterPlanViewController.php @@ -88,14 +88,6 @@ ->setDisabled(!$can_edit) ->setIcon('edit')); - $list->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Manually Execute Plan')) - ->setHref($this->getApplicationURI("plan/execute/{$id}/")) - ->setWorkflow(true) - ->setDisabled(!$can_edit) - ->setIcon('arrow_right')); - return $list; } Index: src/applications/harbormaster/query/HarbormasterBuildQuery.php =================================================================== --- src/applications/harbormaster/query/HarbormasterBuildQuery.php +++ src/applications/harbormaster/query/HarbormasterBuildQuery.php @@ -5,6 +5,7 @@ private $ids; private $phids; + private $buildStatuses; private $buildablePHIDs; private $buildPlanPHIDs; @@ -20,6 +21,11 @@ return $this; } + public function withBuildStatuses(array $build_statuses) { + $this->buildStatuses = $build_statuses; + return $this; + } + public function withBuildablePHIDs(array $buildable_phids) { $this->buildablePHIDs = $buildable_phids; return $this; @@ -115,6 +121,13 @@ $this->phids); } + if ($this->buildStatuses) { + $where[] = qsprintf( + $conn_r, + 'buildStatus in (%Ls)', + $this->buildStatuses); + } + if ($this->buildablePHIDs) { $where[] = qsprintf( $conn_r, Index: src/applications/harbormaster/storage/HarbormasterBuildable.php =================================================================== --- src/applications/harbormaster/storage/HarbormasterBuildable.php +++ src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -12,10 +12,12 @@ private $containerObject = self::ATTACHABLE; private $buildableHandle = self::ATTACHABLE; + const STATUS_WHATEVER = 'whatever'; + public static function initializeNewBuildable(PhabricatorUser $actor) { return id(new HarbormasterBuildable()) - ->setBuildStatus('new') // TODO: Define these. - ->setBuildableStatus('active'); // TODO: Define these, too. + ->setBuildStatus(self::STATUS_WHATEVER) + ->setBuildableStatus(self::STATUS_WHATEVER); } public function getConfiguration() { Index: src/applications/harbormaster/storage/build/HarbormasterBuild.php =================================================================== --- src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -10,9 +10,39 @@ private $buildable = self::ATTACHABLE; private $buildPlan = self::ATTACHABLE; + /** + * Not currently being built. + */ + const STATUS_INACTIVE = 'inactive'; + + /** + * Pending pick up by the Harbormaster daemon. + */ + const STATUS_PENDING = 'pending'; + + /** + * Waiting for a resource to be allocated (not yet relevant). + */ + const STATUS_WAITING = 'waiting'; + + /** + * Current building the buildable. + */ + const STATUS_BUILDING = 'building'; + + /** + * The build has passed. + */ + const STATUS_PASSED = 'passed'; + + /** + * The build has failed. + */ + const STATUS_FAILED = 'failed'; + public static function initializeNewBuild(PhabricatorUser $actor) { return id(new HarbormasterBuild()) - ->setBuildStatus('building'); // TODO: Sort this. + ->setBuildStatus(self::STATUS_INACTIVE); } public function getConfiguration() { Index: src/applications/harbormaster/worker/HarbormasterBuildWorker.php =================================================================== --- /dev/null +++ src/applications/harbormaster/worker/HarbormasterBuildWorker.php @@ -0,0 +1,51 @@ +getTaskData(); + $id = idx($data, 'buildID'); + + // Get a reference to the build. + $build = id(new HarbormasterBuildQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withBuildStatuses(array(HarbormasterBuild::STATUS_PENDING)) + ->withIDs(array($id)) + ->needBuildPlans(true) + ->executeOne(); + if (!$build) { + throw new PhabricatorWorkerPermanentFailureException( + pht('Invalid build ID "%s".', $id)); + } + + try { + $build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING); + $build->save(); + + $buildable = $build->getBuildable(); + $plan = $build->getBuildPlan(); + + // TODO: Do the actual build here. + sleep(15); + + // If we get to here, then the build has passed. + $build->setBuildStatus(HarbormasterBuild::STATUS_PASSED); + $build->save(); + } catch (Exception $e) { + // If any exception is raised, the build is marked as a failure and + // the exception is re-thrown (this ensures we don't leave builds + // in an inconsistent state). + $build->setBuildStatus(HarbormasterBuild::STATUS_FAILED); + $build->save(); + throw $e; + } + } + +} Index: src/applications/harbormaster/worker/HarbormasterRunnerWorker.php =================================================================== --- src/applications/harbormaster/worker/HarbormasterRunnerWorker.php +++ /dev/null @@ -1,53 +0,0 @@ -getTaskData(); - $id = idx($data, 'commitID'); - - $commit = id(new PhabricatorRepositoryCommit())->loadOneWhere( - 'id = %d', - $id); - - if (!$commit) { - throw new PhabricatorWorkerPermanentFailureException( - "Commit '{$id}' does not exist!"); - } - - // TODO: (T603) Policy interaction? - $repository = id(new PhabricatorRepository())->loadOneWhere( - 'id = %d', - $commit->getRepositoryID()); - - if (!$repository) { - throw new PhabricatorWorkerPermanentFailureException( - "Unable to load repository for commit '{$id}'!"); - } - - $lease = id(new DrydockLease()) - ->setResourceType('working-copy') - ->setAttributes( - array( - 'repositoryID' => $repository->getID(), - 'commit' => $commit->getCommitIdentifier(), - )) - ->releaseOnDestruction() - ->waitUntilActive(); - - $cmd = $lease->getInterface('command'); - list($json) = $cmd - ->setWorkingDirectory($lease->getResource()->getAttribute('path')) - ->execx('arc unit --everything --json'); - $lease->release(); - - // TODO: Do something actually useful with this. Requires Harbormaster - // buildout. - echo $json; - } - -}