1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-22 13:30: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:
James Rhodes 2013-11-05 12:43:47 -08:00 committed by epriestley
parent 5cc26f065d
commit ca5400d14b
12 changed files with 221 additions and 158 deletions

View file

@ -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',

View file

@ -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',
),
),
);

View file

@ -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);
}
}

View file

@ -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}");

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

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

View file

@ -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() {

View file

@ -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() {

View file

@ -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;
}
}
}

View file

@ -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;
}
}