mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-22 21:40:55 +01:00
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
This commit is contained in:
parent
5cc26f065d
commit
ca5400d14b
12 changed files with 221 additions and 158 deletions
|
@ -656,7 +656,9 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'HarbormasterPHIDTypeBuildable' => 'PhabricatorPHIDType',
|
||||
'HarbormasterPlanController' => 'PhabricatorController',
|
||||
'HarbormasterPlanEditController' => 'HarbormasterPlanController',
|
||||
'HarbormasterPlanExecuteController' => 'HarbormasterPlanController',
|
||||
'HarbormasterPlanListController' =>
|
||||
array(
|
||||
0 => 'HarbormasterPlanController',
|
||||
|
@ -2903,7 +2904,6 @@ phutil_register_library_map(array(
|
|||
),
|
||||
'HarbormasterPlanViewController' => 'HarbormasterPlanController',
|
||||
'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject',
|
||||
'HarbormasterRunnerWorker' => 'PhabricatorWorker',
|
||||
'HarbormasterScratchTable' => 'HarbormasterDAO',
|
||||
'HeraldAction' => 'HeraldDAO',
|
||||
'HeraldApplyTranscript' => 'HeraldDAO',
|
||||
|
|
|
@ -44,13 +44,13 @@ final class PhabricatorApplicationHarbormaster extends PhabricatorApplication {
|
|||
=> 'HarbormasterBuildableListController',
|
||||
'buildable/' => array(
|
||||
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableEditController',
|
||||
'apply/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableApplyController',
|
||||
),
|
||||
'plan/' => array(
|
||||
'(?:query/(?P<queryKey>[^/]+)/)?'
|
||||
=> 'HarbormasterPlanListController',
|
||||
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanEditController',
|
||||
'(?P<id>\d+)/' => 'HarbormasterPlanViewController',
|
||||
'execute/(?P<id>\d+)/' => 'HarbormasterPlanExecuteController',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterBuildableApplyController
|
||||
extends HarbormasterController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->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);
|
||||
}
|
||||
|
||||
}
|
|
@ -36,7 +36,7 @@ final class HarbormasterBuildableListController
|
|||
$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}");
|
||||
|
|
|
@ -37,6 +37,32 @@ final class HarbormasterBuildableViewController
|
|||
$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 @@ final class HarbormasterBuildableViewController
|
|||
->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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterPlanExecuteController
|
||||
extends HarbormasterPlanController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->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);
|
||||
}
|
||||
|
||||
}
|
|
@ -88,14 +88,6 @@ final class HarbormasterPlanViewController
|
|||
->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;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ final class HarbormasterBuildQuery
|
|||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $buildStatuses;
|
||||
private $buildablePHIDs;
|
||||
private $buildPlanPHIDs;
|
||||
|
||||
|
@ -20,6 +21,11 @@ final class HarbormasterBuildQuery
|
|||
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 @@ final class HarbormasterBuildQuery
|
|||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->buildStatuses) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'buildStatus in (%Ls)',
|
||||
$this->buildStatuses);
|
||||
}
|
||||
|
||||
if ($this->buildablePHIDs) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
|
|
|
@ -12,10 +12,12 @@ final class HarbormasterBuildable extends HarbormasterDAO
|
|||
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() {
|
||||
|
|
|
@ -10,9 +10,39 @@ final class HarbormasterBuild extends HarbormasterDAO
|
|||
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() {
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Run builds
|
||||
*/
|
||||
final class HarbormasterBuildWorker extends PhabricatorWorker {
|
||||
|
||||
public function getRequiredLeaseTime() {
|
||||
return 60 * 60 * 24;
|
||||
}
|
||||
|
||||
public function doWork() {
|
||||
$data = $this->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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterRunnerWorker extends PhabricatorWorker {
|
||||
|
||||
public function getRequiredLeaseTime() {
|
||||
return 60 * 60 * 24;
|
||||
}
|
||||
|
||||
protected function doWork() {
|
||||
$data = $this->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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue