Index: bin/harbormaster =================================================================== --- /dev/null +++ bin/harbormaster @@ -0,0 +1 @@ +../scripts/setup/manage_harbormaster.php \ No newline at end of file Index: scripts/setup/manage_harbormaster.php =================================================================== --- /dev/null +++ scripts/setup/manage_harbormaster.php @@ -0,0 +1,22 @@ +#!/usr/bin/env php +setTagline('manage Harbormaster'); +$args->setSynopsis(<<parseStandardArguments(); + +$workflows = id(new PhutilSymbolLoader()) + ->setAncestorClass('HarbormasterManagementWorkflow') + ->loadObjects(); +$workflows[] = new PhutilHelpArgumentWorkflow(); + +$args->parseWorkflows($workflows); Index: src/__phutil_library_map__.php =================================================================== --- src/__phutil_library_map__.php +++ src/__phutil_library_map__.php @@ -707,6 +707,7 @@ 'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php', 'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php', 'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php', + 'HarbormasterBuildableInterface' => 'applications/harbormaster/interface/HarbormasterBuildableInterface.php', 'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php', 'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php', 'HarbormasterBuildableSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildableSearchEngine.php', @@ -715,6 +716,8 @@ 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', 'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php', + 'HarbormasterManagementBuildWorkflow' => 'applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php', + 'HarbormasterManagementWorkflow' => 'applications/harbormaster/management/HarbormasterManagementWorkflow.php', 'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php', 'HarbormasterPHIDTypeBuild' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php', 'HarbormasterPHIDTypeBuildItem' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php', @@ -2747,6 +2750,7 @@ array( 0 => 'DifferentialDAO', 1 => 'PhabricatorPolicyInterface', + 2 => 'HarbormasterBuildableInterface', ), 'DifferentialDiffContentMail' => 'DifferentialMail', 'DifferentialDiffCreateController' => 'DifferentialController', @@ -2814,6 +2818,7 @@ 2 => 'PhabricatorPolicyInterface', 3 => 'PhabricatorFlaggableInterface', 4 => 'PhrequentTrackableInterface', + 5 => 'HarbormasterBuildableInterface', ), 'DifferentialRevisionCommentListView' => 'AphrontView', 'DifferentialRevisionCommentView' => 'AphrontView', @@ -3131,6 +3136,7 @@ array( 0 => 'HarbormasterDAO', 1 => 'PhabricatorPolicyInterface', + 2 => 'HarbormasterBuildableInterface', ), 'HarbormasterBuildableListController' => array( @@ -3144,6 +3150,8 @@ 'HarbormasterController' => 'PhabricatorController', 'HarbormasterDAO' => 'PhabricatorLiskDAO', 'HarbormasterHTTPRequestBuildStepImplementation' => 'VariableBuildStepImplementation', + 'HarbormasterManagementBuildWorkflow' => 'HarbormasterManagementWorkflow', + 'HarbormasterManagementWorkflow' => 'PhutilArgumentWorkflow', 'HarbormasterObject' => 'HarbormasterDAO', 'HarbormasterPHIDTypeBuild' => 'PhabricatorPHIDType', 'HarbormasterPHIDTypeBuildItem' => 'PhabricatorPHIDType', @@ -4342,6 +4350,7 @@ 1 => 'PhabricatorPolicyInterface', 2 => 'PhabricatorFlaggableInterface', 3 => 'PhabricatorTokenReceiverInterface', + 4 => 'HarbormasterBuildableInterface', ), 'PhabricatorRepositoryCommitChangeParserWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitData' => 'PhabricatorRepositoryDAO', Index: src/applications/differential/storage/DifferentialDiff.php =================================================================== --- src/applications/differential/storage/DifferentialDiff.php +++ src/applications/differential/storage/DifferentialDiff.php @@ -2,7 +2,9 @@ final class DifferentialDiff extends DifferentialDAO - implements PhabricatorPolicyInterface { + implements + PhabricatorPolicyInterface, + HarbormasterBuildableInterface { protected $revisionID; protected $authorPHID; @@ -339,4 +341,24 @@ return null; } + + +/* -( HarbormasterBuildableInterface )------------------------------------- */ + + + public function getHarbormasterBuildablePHID() { + return $this->getPHID(); + } + + public function getHarbormasterContainerPHID() { + if ($this->getRevisionID()) { + $revision = id(new DifferentialRevision())->load($this->getRevisionID()); + if ($revision) { + return $revision->getPHID(); + } + } + + return null; + } + } Index: src/applications/differential/storage/DifferentialRevision.php =================================================================== --- src/applications/differential/storage/DifferentialRevision.php +++ src/applications/differential/storage/DifferentialRevision.php @@ -5,7 +5,8 @@ PhabricatorTokenReceiverInterface, PhabricatorPolicyInterface, PhabricatorFlaggableInterface, - PhrequentTrackableInterface { + PhrequentTrackableInterface, + HarbormasterBuildableInterface { protected $title = ''; protected $originalTitle; @@ -412,4 +413,16 @@ return DifferentialRevisionStatus::isClosedStatus($this->getStatus()); } + +/* -( HarbormasterBuildableInterface )------------------------------------- */ + + + public function getHarbormasterBuildablePHID() { + return $this->loadActiveDiff()->getPHID(); + } + + public function getHarbormasterContainerPHID() { + return $this->getPHID(); + } + } Index: src/applications/harbormaster/controller/HarbormasterPlanRunController.php =================================================================== --- src/applications/harbormaster/controller/HarbormasterPlanRunController.php +++ src/applications/harbormaster/controller/HarbormasterPlanRunController.php @@ -41,16 +41,10 @@ ->withNames(array($v_name)) ->executeOne(); - if ($object instanceof DifferentialRevision) { - $revision = $object; - $object = $object->loadActiveDiff(); + if ($object instanceof HarbormasterBuildableInterface) { $buildable - ->setBuildablePHID($object->getPHID()) - ->setContainerPHID($revision->getPHID()); - } else if ($object instanceof PhabricatorRepositoryCommit) { - $buildable - ->setBuildablePHID($object->getPHID()) - ->setContainerPHID($object->getRepository()->getPHID()); + ->setBuildablePHID($object->getHarbormasterBuildablePHID()) + ->setContainerPHID($object->getHarbormasterContainerPHID()); } else { $e_name = pht('Invalid'); $errors[] = pht('Enter the name of a revision or commit.'); @@ -62,6 +56,7 @@ if (!$errors) { $buildable->save(); + $buildable->applyPlan($plan); $buildable_uri = '/B'.$buildable->getID(); return id(new AphrontRedirectResponse())->setURI($buildable_uri); @@ -80,8 +75,12 @@ ->setUser($viewer) ->appendRemarkupInstructions( pht( - "Enter the name of a commit or revision to run this plan on.\n\n". - "For example: `rX123456` or `D123`")) + "Enter the name of a commit or revision to run this plan on (for ". + "example, `rX123456` or `D123`).\n\n". + "For more detailed output, you can also run manual builds from ". + "the command line:\n\n". + " phabricator/ $ ./bin/harbormaster build --plan %s", + $plan->getID())) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Buildable Name') @@ -90,7 +89,7 @@ ->setValue($v_name)); $dialog = id(new AphrontDialogView()) - ->setWidth(AphrontDialogView::WIDTH_FORM) + ->setWidth(AphrontDialogView::WIDTH_FULL) ->setUser($viewer) ->setTitle($title) ->appendChild($form) Index: src/applications/harbormaster/event/HarbormasterUIEventListener.php =================================================================== --- src/applications/harbormaster/event/HarbormasterUIEventListener.php +++ src/applications/harbormaster/event/HarbormasterUIEventListener.php @@ -24,12 +24,19 @@ return; } - $target = null; - if ($object instanceof PhabricatorRepositoryCommit) { - $target = $object; - } elseif ($object instanceof DifferentialRevision) { - $target = $object->loadActiveDiff(); - } else { + if ($object instanceof HarbormasterBuildable) { + // Although HarbormasterBuildable implements the correct interface, it + // does not make sense to show a build's build status. In the best case + // it is meaningless, and in the worst case it's confusing. + return; + } + + if (!($object instanceof HarbormasterBuildableInterface)) { + return; + } + + $buildable_phid = $object->getBuildablePHID(); + if (!$buildable_phid) { return; } @@ -39,7 +46,8 @@ $buildables = id(new HarbormasterBuildableQuery()) ->setViewer($user) - ->withBuildablePHIDs(array($target->getPHID())) + ->withManualBuildables(false) + ->withBuildablePHIDs(array($buildable_phid)) ->execute(); if (!$buildables) { return; @@ -62,8 +70,7 @@ foreach ($builds as $build) { $item = new PHUIStatusItemView(); - $item->setTarget( - $build_handles[$build->getPHID()]->renderLink()); + $item->setTarget($build_handles[$build->getPHID()]->renderLink()); switch ($build->getBuildStatus()) { case HarbormasterBuild::STATUS_INACTIVE: Index: src/applications/harbormaster/interface/HarbormasterBuildableInterface.php =================================================================== --- /dev/null +++ src/applications/harbormaster/interface/HarbormasterBuildableInterface.php @@ -0,0 +1,8 @@ +setName('build') + ->setExamples('**build** [__options__] __buildable__ --plan __id__') + ->setSynopsis(pht('Run plan __id__ on __buildable__.')) + ->setArguments( + array( + array( + 'name' => 'plan', + 'param' => 'id', + 'help' => pht('ID of build plan to run.'), + ), + array( + 'name' => 'buildable', + 'wildcard' => true, + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $viewer = PhabricatorUser::getOmnipotentUser(); + + $names = $args->getArg('buildable'); + if (count($names) != 1) { + throw new PhutilArgumentUsageException( + pht('Specify exactly one buildable, by object name.')); + } + + $name = head($names); + + $buildable = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withNames($names) + ->executeOne(); + if (!$buildable) { + throw new PhutilArgumentUsageException( + pht('No such buildable "%s"!', $name)); + } + + if (!($buildable instanceof HarbormasterBuildableInterface)) { + throw new PhutilArgumentUsageException( + pht('Object "%s" is not a buildable!', $name)); + } + + $plan_id = $args->getArg('plan'); + if (!$plan_id) { + throw new PhutilArgumentUsageException( + pht('Use --plan to specify a build plan to run.')); + } + + $plan = id(new HarbormasterBuildPlanQuery()) + ->setViewer($viewer) + ->withIDs(array($plan_id)) + ->executeOne(); + if (!$plan) { + throw new PhutilArgumentUsageException( + pht('Build plan "%s" does not exist.', $plan_id)); + } + + $console = PhutilConsole::getConsole(); + + $buildable = HarbormasterBuildable::initializeNewBuildable($viewer) + ->setIsManualBuildable(true) + ->setBuildablePHID($buildable->getHarbormasterBuildablePHID()) + ->setContainerPHID($buildable->getHarbormasterContainerPHID()) + ->save(); + + $console->writeOut( + "%s\n", + pht( + 'Applying plan %s to new buildable %s...', + $plan->getID(), + 'B'.$buildable->getID())); + + $console->writeOut( + "\n %s\n\n", + PhabricatorEnv::getProductionURI('/B'.$buildable->getID())); + + PhabricatorWorker::setRunAllTasksInProcess(true); + $buildable->applyPlan($plan); + + $console->writeOut("%s\n", pht('Done.')); + + return 0; + } + +} Index: src/applications/harbormaster/management/HarbormasterManagementWorkflow.php =================================================================== --- /dev/null +++ src/applications/harbormaster/management/HarbormasterManagementWorkflow.php @@ -0,0 +1,10 @@ +setBuildablePHID($buildable->getPHID()); - $build->setBuildPlanPHID($plan->getPHID()); - $build->setBuildStatus(HarbormasterBuild::STATUS_PENDING); - $build->save(); - - PhabricatorWorker::scheduleTask( - 'HarbormasterBuildWorker', - array( - 'buildID' => $build->getID() - )); + $buildable->applyPlan($plan); } } + public function applyPlan(HarbormasterBuildPlan $plan) { + $viewer = PhabricatorUser::getOmnipotentUser(); + $build = HarbormasterBuild::initializeNewBuild($viewer) + ->setBuildablePHID($this->getPHID()) + ->setBuildPlanPHID($plan->getPHID()) + ->setBuildStatus(HarbormasterBuild::STATUS_PENDING) + ->save(); + + PhabricatorWorker::scheduleTask( + 'HarbormasterBuildWorker', + array( + 'buildID' => $build->getID() + )); + + return $this; + } + public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, @@ -184,4 +192,21 @@ 'buildable.'); } + + +/* -( HarbormasterBuildableInterface )------------------------------------- */ + + + public function getHarbormasterBuildablePHID() { + // NOTE: This is essentially just for convenience, as it allows you create + // a copy of a buildable by specifying `B123` without bothering to go + // look up the underlying object. + return $this->getBuildablePHID(); + } + + public function getHarbormasterContainerPHID() { + return $this->getContainerPHID(); + } + + } Index: src/applications/repository/storage/PhabricatorRepositoryCommit.php =================================================================== --- src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -5,7 +5,8 @@ implements PhabricatorPolicyInterface, PhabricatorFlaggableInterface, - PhabricatorTokenReceiverInterface { + PhabricatorTokenReceiverInterface, + HarbormasterBuildableInterface { protected $repositoryID; protected $phid; @@ -231,4 +232,17 @@ return id(new PhabricatorRepositoryCommit()) ->loadFromArray($dict); } + + +/* -( HarbormasterBuildableInterface )------------------------------------- */ + + + public function getHarbormasterBuildablePHID() { + return $this->getPHID(); + } + + public function getHarbormasterContainerPHID() { + return $this->getRepository()->getPHID(); + } + }