1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-29 10:12:41 +01:00

Restructure Drydock so that blueprints are instances in the DB

Summary:
//(this diff used to be about applying policies to blueprints)//

This restructures Drydock so that blueprints are instances in the DB, with an associated implementation class.  Thus resources now have a `blueprintPHID` instead of `blueprintClass` and DrydockBlueprint becomes a DAO.  The old DrydockBlueprint is renamed to DrydockBlueprintImplementation, and the DrydockBlueprint DAO has a `blueprintClass` column on it.

This now just implements CAN_VIEW and CAN_EDIT policies for blueprints, although they are probably not enforced in all of the places they could be.

Test Plan: Used the `create-resource` and `lease` commands.  Closed resources and leases in the UI.  Clicked around the new and old lists to make sure everything is still working.

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley

CC: Korvin, epriestley, aran

Maniphest Tasks: T4111, T2015

Differential Revision: https://secure.phabricator.com/D7638
This commit is contained in:
James Rhodes 2013-12-03 11:09:07 +11:00
parent f93c6985ad
commit ba16df0fed
25 changed files with 729 additions and 41 deletions

View file

@ -0,0 +1,11 @@
CREATE TABLE {$NAMESPACE}_drydock.drydock_blueprint (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
className VARCHAR(255) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL,
editPolicy VARCHAR(64) NOT NULL,
details LONGTEXT CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid)
) ENGINE=InnoDB, COLLATE utf8_general_ci;

View file

@ -0,0 +1,5 @@
ALTER TABLE {$NAMESPACE}_drydock.drydock_resource
ADD COLUMN blueprintPHID VARCHAR(64) NOT NULL COLLATE utf8_bin;
ALTER TABLE {$NAMESPACE}_drydock.drydock_resource
DROP COLUMN blueprintClass;

View file

@ -626,8 +626,14 @@ phutil_register_library_map(array(
'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php',
'DrydockAllocatorWorker' => 'applications/drydock/worker/DrydockAllocatorWorker.php',
'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php',
'DrydockBlueprint' => 'applications/drydock/blueprint/DrydockBlueprint.php',
'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php',
'DrydockBlueprintCreateController' => 'applications/drydock/controller/DrydockBlueprintCreateController.php',
'DrydockBlueprintEditController' => 'applications/drydock/controller/DrydockBlueprintEditController.php',
'DrydockBlueprintImplementation' => 'applications/drydock/blueprint/DrydockBlueprintImplementation.php',
'DrydockBlueprintListController' => 'applications/drydock/controller/DrydockBlueprintListController.php',
'DrydockBlueprintQuery' => 'applications/drydock/query/DrydockBlueprintQuery.php',
'DrydockBlueprintScopeGuard' => 'applications/drydock/util/DrydockBlueprintScopeGuard.php',
'DrydockBlueprintViewController' => 'applications/drydock/controller/DrydockBlueprintViewController.php',
'DrydockCommandInterface' => 'applications/drydock/interface/command/DrydockCommandInterface.php',
'DrydockConstants' => 'applications/drydock/constants/DrydockConstants.php',
'DrydockController' => 'applications/drydock/controller/DrydockController.php',
@ -640,7 +646,7 @@ phutil_register_library_map(array(
'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php',
'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php',
'DrydockLocalCommandInterface' => 'applications/drydock/interface/command/DrydockLocalCommandInterface.php',
'DrydockLocalHostBlueprint' => 'applications/drydock/blueprint/DrydockLocalHostBlueprint.php',
'DrydockLocalHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockLocalHostBlueprintImplementation.php',
'DrydockLog' => 'applications/drydock/storage/DrydockLog.php',
'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php',
'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php',
@ -650,7 +656,8 @@ phutil_register_library_map(array(
'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php',
'DrydockManagementWaitForLeaseWorkflow' => 'applications/drydock/management/DrydockManagementWaitForLeaseWorkflow.php',
'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php',
'DrydockPreallocatedHostBlueprint' => 'applications/drydock/blueprint/DrydockPreallocatedHostBlueprint.php',
'DrydockPHIDTypeBlueprint' => 'applications/drydock/phid/DrydockPHIDTypeBlueprint.php',
'DrydockPreallocatedHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php',
'DrydockResource' => 'applications/drydock/storage/DrydockResource.php',
'DrydockResourceCloseController' => 'applications/drydock/controller/DrydockResourceCloseController.php',
'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php',
@ -659,7 +666,7 @@ phutil_register_library_map(array(
'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php',
'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php',
'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php',
'DrydockWorkingCopyBlueprint' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprint.php',
'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php',
'FeedPublisherHTTPWorker' => 'applications/feed/worker/FeedPublisherHTTPWorker.php',
'FeedPublisherWorker' => 'applications/feed/worker/FeedPublisherWorker.php',
'FeedPushWorker' => 'applications/feed/worker/FeedPushWorker.php',
@ -2951,6 +2958,16 @@ phutil_register_library_map(array(
'DoorkeeperTagsController' => 'PhabricatorController',
'DrydockAllocatorWorker' => 'PhabricatorWorker',
'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface',
'DrydockBlueprint' =>
array(
0 => 'DrydockDAO',
1 => 'PhabricatorPolicyInterface',
),
'DrydockBlueprintCreateController' => 'DrydockController',
'DrydockBlueprintEditController' => 'DrydockController',
'DrydockBlueprintListController' => 'DrydockController',
'DrydockBlueprintQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DrydockBlueprintViewController' => 'DrydockController',
'DrydockCommandInterface' => 'DrydockInterface',
'DrydockController' => 'PhabricatorController',
'DrydockDAO' => 'PhabricatorLiskDAO',
@ -2961,7 +2978,7 @@ phutil_register_library_map(array(
'DrydockLeaseStatus' => 'DrydockConstants',
'DrydockLeaseViewController' => 'DrydockController',
'DrydockLocalCommandInterface' => 'DrydockCommandInterface',
'DrydockLocalHostBlueprint' => 'DrydockBlueprint',
'DrydockLocalHostBlueprintImplementation' => 'DrydockBlueprintImplementation',
'DrydockLog' => 'DrydockDAO',
'DrydockLogController' => 'DrydockController',
'DrydockLogQuery' => 'PhabricatorOffsetPagedQuery',
@ -2971,7 +2988,8 @@ phutil_register_library_map(array(
'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementWaitForLeaseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementWorkflow' => 'PhutilArgumentWorkflow',
'DrydockPreallocatedHostBlueprint' => 'DrydockBlueprint',
'DrydockPHIDTypeBlueprint' => 'PhabricatorPHIDType',
'DrydockPreallocatedHostBlueprintImplementation' => 'DrydockBlueprintImplementation',
'DrydockResource' =>
array(
0 => 'DrydockDAO',
@ -2984,7 +3002,7 @@ phutil_register_library_map(array(
'DrydockResourceViewController' => 'DrydockController',
'DrydockSSHCommandInterface' => 'DrydockCommandInterface',
'DrydockWebrootInterface' => 'DrydockInterface',
'DrydockWorkingCopyBlueprint' => 'DrydockBlueprint',
'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation',
'FeedPublisherHTTPWorker' => 'FeedPushWorker',
'FeedPublisherWorker' => 'FeedPushWorker',
'FeedPushWorker' => 'PhabricatorWorker',

View file

@ -34,6 +34,12 @@ final class PhabricatorApplicationDrydock extends PhabricatorApplication {
return array(
'/drydock/' => array(
'' => 'DrydockResourceListController',
'blueprint/' => array(
'' => 'DrydockBlueprintListController',
'(?P<id>[1-9]\d*)/' => 'DrydockBlueprintViewController',
'create/' => 'DrydockBlueprintCreateController',
'edit/(?P<id>[1-9]\d*)/' => 'DrydockBlueprintEditController',
),
'resource/' => array(
'' => 'DrydockResourceListController',
'(?P<id>[1-9]\d*)/' => 'DrydockResourceViewController',

View file

@ -5,10 +5,11 @@
* @task resource Resource Allocation
* @task log Logging
*/
abstract class DrydockBlueprint {
abstract class DrydockBlueprintImplementation {
private $activeResource;
private $activeLease;
private $instance;
abstract public function getType();
abstract public function getInterface(
@ -18,6 +19,8 @@ abstract class DrydockBlueprint {
abstract public function isEnabled();
abstract public function getDescription();
public function getBlueprintClass() {
return get_class($this);
}
@ -37,6 +40,20 @@ abstract class DrydockBlueprint {
return $lease;
}
protected function getInstance() {
if (!$this->instance) {
throw new Exception(
"Attach the blueprint instance to the implementation.");
}
return $this->instance;
}
public function attachInstance(DrydockBlueprint $instance) {
$this->instance = $instance;
return $this;
}
/* -( Lease Acquisition )-------------------------------------------------- */
@ -343,13 +360,13 @@ abstract class DrydockBlueprint {
}
public static function getAllBlueprints() {
public static function getAllBlueprintImplementations() {
static $list = null;
if ($list === null) {
$blueprints = id(new PhutilSymbolLoader())
->setType('class')
->setAncestorClass('DrydockBlueprint')
->setAncestorClass('DrydockBlueprintImplementation')
->setConcreteOnly(true)
->selectAndLoadSymbols();
$list = ipull($blueprints, 'name', 'name');
@ -361,16 +378,17 @@ abstract class DrydockBlueprint {
return $list;
}
public static function getAllBlueprintsForResource($type) {
public static function getAllBlueprintImplementationsForResource($type) {
static $groups = null;
if ($groups === null) {
$groups = mgroup(self::getAllBlueprints(), 'getType');
$groups = mgroup(self::getAllBlueprintImplementations(), 'getType');
}
return idx($groups, $type, array());
}
protected function newResourceTemplate($name) {
$resource = new DrydockResource();
$resource->setBlueprintPHID($this->getInstance()->getPHID());
$resource->setBlueprintClass($this->getBlueprintClass());
$resource->setType($this->getType());
$resource->setStatus(DrydockResourceStatus::STATUS_PENDING);

View file

@ -1,11 +1,16 @@
<?php
final class DrydockLocalHostBlueprint extends DrydockBlueprint {
final class DrydockLocalHostBlueprintImplementation
extends DrydockBlueprintImplementation {
public function isEnabled() {
return false;
}
public function getDescription() {
return pht('Allocates storage on the local host.');
}
public function canAllocateMoreResources(array $pool) {
assert_instances_of($pool, 'DrydockResource');

View file

@ -1,11 +1,16 @@
<?php
final class DrydockPreallocatedHostBlueprint extends DrydockBlueprint {
final class DrydockPreallocatedHostBlueprintImplementation
extends DrydockBlueprintImplementation {
public function isEnabled() {
return true;
}
public function getDescription() {
return pht('Leases out preallocated, remote hosts.');
}
public function canAllocateMoreResources(array $pool) {
return false;
}

View file

@ -1,11 +1,16 @@
<?php
final class DrydockWorkingCopyBlueprint extends DrydockBlueprint {
final class DrydockWorkingCopyBlueprintImplementation
extends DrydockBlueprintImplementation {
public function isEnabled() {
return true;
}
public function getDescription() {
return pht('Allocates out working copies of repositories.');
}
protected function canAllocateLease(
DrydockResource $resource,
DrydockLease $lease) {

View file

@ -0,0 +1,68 @@
<?php
final class DrydockBlueprintCreateController
extends DrydockController {
public function willProcessRequest(array $data) {
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$implementations =
DrydockBlueprintImplementation::getAllBlueprintImplementations();
if ($request->isFormPost()) {
$class = $request->getStr('blueprint-type');
if (!isset($implementations[$class])) {
return $this->createDialog($implementations);
}
$blueprint = new DrydockBlueprint();
$blueprint->setClassName($class);
$blueprint->setDetails(array());
$blueprint->setViewPolicy(PhabricatorPolicies::POLICY_ADMIN);
$blueprint->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN);
$blueprint->save();
$edit_uri = $this->getApplicationURI(
"blueprint/edit/".$blueprint->getID()."/");
return id(new AphrontRedirectResponse())->setURI($edit_uri);
}
return $this->createDialog($implementations);
}
function createDialog(array $implementations) {
$request = $this->getRequest();
$viewer = $request->getUser();
$control = id(new AphrontFormRadioButtonControl())
->setName('blueprint-type');
foreach ($implementations as $implementation_name => $implementation) {
$control
->addButton(
$implementation_name,
$implementation->getBlueprintClass(),
$implementation->getDescription());
}
$dialog = new AphrontDialogView();
$dialog->setTitle(pht('Create New Blueprint'))
->setUser($viewer)
->addSubmitButton(pht('Create Blueprint'))
->addCancelButton($this->getApplicationURI('blueprint/'));
$dialog->appendChild(
phutil_tag(
'p',
array(),
pht(
'Select what type of blueprint you want to create: ')));
$dialog->appendChild($control);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}

View file

@ -0,0 +1,122 @@
<?php
final class DrydockBlueprintEditController extends DrydockController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
if ($this->id) {
$blueprint = id(new DrydockBlueprintQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$blueprint) {
return new Aphront404Response();
}
} else {
$blueprint = new DrydockBlueprint();
}
if ($request->isFormPost()) {
$v_view_policy = $request->getStr('viewPolicy');
$v_edit_policy = $request->getStr('editPolicy');
// TODO: Should we use transactions here?
$blueprint->setViewPolicy($v_view_policy);
$blueprint->setEditPolicy($v_edit_policy);
$blueprint->save();
return id(new AphrontRedirectResponse())
->setURI('/drydock/blueprint/');
}
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($blueprint)
->execute();
if ($request->isAjax()) {
$form = id(new PHUIFormLayoutView())
->setUser($viewer);
} else {
$form = id(new AphrontFormView())
->setUser($viewer);
}
$form
->appendChild(
id(new AphrontFormTextControl())
->setName('className')
->setLabel(pht('Implementation'))
->setValue($blueprint->getClassName())
->setDisabled(true))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('viewPolicy')
->setPolicyObject($blueprint)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicies($policies))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('editPolicy')
->setPolicyObject($blueprint)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicies($policies));
$crumbs = $this->buildApplicationCrumbs();
$title = pht('Edit Blueprint');
$header = pht('Edit Blueprint %d', $blueprint->getID());
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Blueprint %d', $blueprint->getID())));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit')));
if ($request->isAjax()) {
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setWidth(AphrontDialogView::WIDTH_FORM)
->setTitle($title)
->appendChild($form)
->addSubmitButton(pht('Edit Blueprint'))
->addCancelButton($this->getApplicationURI());
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save'))
->addCancelButton($this->getApplicationURI()));
$box = id(new PHUIObjectBoxView())
->setHeaderText($header)
->setForm($form);
return $this->buildApplicationPage(
array(
$crumbs,
$box,
),
array(
'title' => $title,
'device' => true,
));
}
}

View file

@ -0,0 +1,77 @@
<?php
final class DrydockBlueprintListController extends DrydockController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$title = pht('Blueprints');
$blueprint_header = id(new PHUIHeaderView())
->setHeader($title);
$blueprints = id(new DrydockBlueprintQuery())
->setViewer($user)
->execute();
$blueprint_list = $this->buildBlueprintListView($blueprints);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)
->setHref($request->getRequestURI()));
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('New Blueprint'))
->setHref($this->getApplicationURI('blueprint/create/'))
->setIcon('create'));
$nav = $this->buildSideNav('blueprint');
$nav->setCrumbs($crumbs);
$nav->appendChild(
array(
$blueprint_header,
$blueprint_list
));
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
'device' => true,
));
}
protected function buildBlueprintListView(array $blueprints) {
assert_instances_of($blueprints, 'DrydockBlueprint');
$user = $this->getRequest()->getUser();
$view = new PHUIObjectItemListView();
foreach ($blueprints as $blueprint) {
$item = id(new PHUIObjectItemView())
->setHeader($blueprint->getClassName())
->setHref($this->getApplicationURI('/blueprint/'.$blueprint->getID()))
->setObjectName(pht('Blueprint %d', $blueprint->getID()));
if ($blueprint->getImplementation()->isEnabled()) {
$item->addAttribute(pht('Enabled'));
$item->setBarColor('green');
} else {
$item->addAttribute(pht('Disabled'));
$item->setBarColor('red');
}
$item->addAttribute($blueprint->getImplementation()->getDescription());
$view->addItem($item);
}
return $view;
}
}

View file

@ -0,0 +1,100 @@
<?php
final class DrydockBlueprintViewController extends DrydockController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$blueprint = id(new DrydockBlueprint())->load($this->id);
if (!$blueprint) {
return new Aphront404Response();
}
$title = 'Blueprint '.$blueprint->getID().' '.$blueprint->getClassName();
$header = id(new PHUIHeaderView())
->setHeader($title);
$actions = $this->buildActionListView($blueprint);
$properties = $this->buildPropertyListView($blueprint, $actions);
$blueprint_uri = 'blueprint/'.$blueprint->getID().'/';
$blueprint_uri = $this->getApplicationURI($blueprint_uri);
$resources = id(new DrydockResourceQuery())
->withBlueprintPHIDs(array($blueprint->getPHID()))
->setViewer($user)
->execute();
$resource_list = $this->buildResourceListView($resources);
$resource_list->setNoDataString(pht('This blueprint has no resources.'));
$pager = new AphrontPagerView();
$pager->setURI(new PhutilURI($blueprint_uri), 'offset');
$pager->setOffset($request->getInt('offset'));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Blueprint %d', $blueprint->getID())));
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
$resource_list
),
array(
'device' => true,
'title' => $title,
));
}
private function buildActionListView(DrydockBlueprint $blueprint) {
$view = id(new PhabricatorActionListView())
->setUser($this->getRequest()->getUser())
->setObjectURI($this->getRequest()->getRequestURI())
->setObject($blueprint);
$uri = '/blueprint/edit/'.$blueprint->getID().'/';
$uri = $this->getApplicationURI($uri);
$view->addAction(
id(new PhabricatorActionView())
->setHref($uri)
->setName(pht('Edit Blueprint Policies'))
->setIcon('edit')
->setWorkflow(true)
->setDisabled(false));
return $view;
}
private function buildPropertyListView(
DrydockBlueprint $blueprint,
PhabricatorActionListView $actions) {
$view = new PHUIPropertyListView();
$view->setActionList($actions);
$view->addProperty(
pht('Implementation'),
$blueprint->getClassName());
return $view;
}
}

View file

@ -5,9 +5,10 @@ abstract class DrydockController extends PhabricatorController {
final protected function buildSideNav($selected) {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI('/drydock/'));
$nav->addFilter('resource', 'Resources');
$nav->addFilter('lease', 'Leases');
$nav->addFilter('log', 'Logs');
$nav->addFilter('blueprint', 'Blueprints');
$nav->addFilter('resource', 'Resources');
$nav->addFilter('lease', 'Leases');
$nav->addFilter('log', 'Logs');
$nav->selectFilter($selected, 'resource');

View file

@ -33,9 +33,6 @@ final class DrydockResourceViewController extends DrydockController {
->needResources(true)
->execute();
$lease_header = id(new PHUIHeaderView())
->setHeader(pht('Leases'));
$lease_list = $this->buildLeaseListView($leases);
$lease_list->setNoDataString(pht('This resource has no leases.'));
@ -64,7 +61,6 @@ final class DrydockResourceViewController extends DrydockController {
array(
$crumbs,
$object_box,
$lease_header,
$lease_list,
$log_table,
),
@ -114,6 +110,11 @@ final class DrydockResourceViewController extends DrydockController {
pht('Resource Type'),
$resource->getType());
// TODO: Load handle.
$view->addProperty(
pht('Blueprint'),
$resource->getBlueprintPHID());
$attributes = $resource->getAttributes();
if ($attributes) {
$view->addSectionHeader(pht('Attributes'));

View file

@ -16,8 +16,8 @@ final class DrydockManagementCreateResourceWorkflow
),
array(
'name' => 'blueprint',
'param' => 'blueprint_type',
'help' => 'Blueprint type.',
'param' => 'blueprint_id',
'help' => 'Blueprint ID.',
),
array(
'name' => 'attributes',
@ -36,10 +36,10 @@ final class DrydockManagementCreateResourceWorkflow
"Specify a resource name with `--name`.");
}
$blueprint_type = $args->getArg('blueprint');
if (!$blueprint_type) {
$blueprint_id = $args->getArg('blueprint');
if (!$blueprint_id) {
throw new PhutilArgumentUsageException(
"Specify a blueprint type with `--blueprint`.");
"Specify a blueprint ID with `--blueprint`.");
}
$attributes = $args->getArg('attributes');
@ -49,9 +49,15 @@ final class DrydockManagementCreateResourceWorkflow
$attributes = $options->parse($attributes);
}
$blueprint = id(new DrydockBlueprint())->load((int)$blueprint_id);
if (!$blueprint) {
throw new PhutilArgumentUsageException(
"Specified blueprint does not exist.");
}
$resource = new DrydockResource();
$resource->setBlueprintClass($blueprint_type);
$resource->setType(id(new $blueprint_type())->getType());
$resource->setBlueprintPHID($blueprint->getPHID());
$resource->setType($blueprint->getImplementation()->getType());
$resource->setName($resource_name);
$resource->setStatus(DrydockResourceStatus::STATUS_OPEN);
if ($attributes) {

View file

@ -0,0 +1,34 @@
<?php
final class DrydockPHIDTypeBlueprint extends PhabricatorPHIDType {
const TYPECONST = 'DRYB';
public function getTypeConstant() {
return self::TYPECONST;
}
public function getTypeName() {
return pht('Blueprint');
}
public function newObject() {
return new DrydockBlueprint();
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new DrydockBlueprintQuery())
->withPHIDs($phids);
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
}
}

View file

@ -0,0 +1,70 @@
<?php
final class DrydockBlueprintQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function loadPage() {
$table = new DrydockBlueprint();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT blueprint.* FROM %T blueprint %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
$blueprints = $table->loadAllFromArray($data);
$implementations =
DrydockBlueprintImplementation::getAllBlueprintImplementations();
foreach ($blueprints as $blueprint) {
if (array_key_exists($implementations, $blueprint->getClassName())) {
$blueprint->attachImplementation(
$implementations[$blueprint->getClassName()]);
}
}
return $blueprints;
}
private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
if ($this->ids) {
$where[] = qsprintf(
$conn_r,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids) {
$where[] = qsprintf(
$conn_r,
'phid IN (%Ld)',
$this->phids);
}
return $this->formatWhereClause($where);
}
public function getQueryApplicationClass() {
return 'PhabricatorApplicationDrydock';
}
}

View file

@ -40,12 +40,16 @@ final class DrydockLeaseQuery extends PhabricatorOffsetPagedQuery {
'id IN (%Ld)',
mpull($leases, 'getResourceID'));
foreach ($leases as $lease) {
foreach ($leases as $key => $lease) {
if ($lease->getResourceID()) {
$resource = idx($resources, $lease->getResourceID());
if ($resource) {
$lease->attachResource($resource);
} else {
unset($leases[$key]);
}
} else {
unset($leases[$key]);
}
}
}

View file

@ -6,6 +6,7 @@ final class DrydockResourceQuery
private $ids;
private $statuses;
private $types;
private $blueprintPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -22,6 +23,11 @@ final class DrydockResourceQuery
return $this;
}
public function withBlueprintPHIDs(array $blueprint_phids) {
$this->blueprintPHIDs = $blueprint_phids;
return $this;
}
public function loadPage() {
$table = new DrydockResource();
$conn_r = $table->establishConnection('r');
@ -63,6 +69,13 @@ final class DrydockResourceQuery
$this->statuses);
}
if ($this->blueprintPHIDs) {
$where[] = qsprintf(
$conn_r,
'blueprintPHID IN (%Ls)',
$this->blueprintPHIDs);
}
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);

View file

@ -0,0 +1,72 @@
<?php
final class DrydockBlueprint extends DrydockDAO
implements PhabricatorPolicyInterface {
protected $phid;
protected $className;
protected $viewPolicy;
protected $editPolicy;
protected $details;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'details' => self::SERIALIZATION_JSON,
)
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
DrydockPHIDTypeBlueprint::TYPECONST);
}
public function getImplementation() {
$class = $this->className;
$implementations =
DrydockBlueprintImplementation::getAllBlueprintImplementations();
if (!isset($implementations[$class])) {
throw new Exception(
"Invalid class name for blueprint (got '".$class."')");
}
return id(new $class())->attachInstance($this);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
case PhabricatorPolicyCapability::CAN_EDIT:
return $viewer->getIsAdmin();
}
}
public function describeAutomaticCapability($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return pht('Administrators can always view blueprints.');
case PhabricatorPolicyCapability::CAN_EDIT:
return pht('Administrators can always edit blueprints.');
}
}
}

View file

@ -5,7 +5,7 @@ final class DrydockResource extends DrydockDAO
protected $id;
protected $phid;
protected $blueprintClass;
protected $blueprintPHID;
protected $status;
protected $type;
@ -50,7 +50,9 @@ final class DrydockResource extends DrydockDAO
public function getBlueprint() {
if (empty($this->blueprint)) {
$this->blueprint = newv($this->blueprintClass, array());
$blueprint = id(new DrydockBlueprint())
->loadOneWhere('phid = %s', $this->blueprintPHID);
$this->blueprint = $blueprint->getImplementation();
}
return $this->blueprint;
}
@ -76,7 +78,7 @@ final class DrydockResource extends DrydockDAO
$lease->setStatus(DrydockLeaseStatus::STATUS_RELEASED);
break;
}
DrydockBlueprint::writeLog($this, $lease, $message);
DrydockBlueprintImplementation::writeLog($this, $lease, $message);
$lease->save();
}

View file

@ -2,7 +2,7 @@
final class DrydockBlueprintScopeGuard {
public function __construct(DrydockBlueprint $blueprint) {
public function __construct(DrydockBlueprintImplementation $blueprint) {
$this->blueprint = $blueprint;
}

View file

@ -24,7 +24,7 @@ final class DrydockAllocatorWorker extends PhabricatorWorker {
}
private function logToDrydock($message) {
DrydockBlueprint::writeLog(
DrydockBlueprintImplementation::writeLog(
null,
$this->loadLease(),
$message);
@ -52,9 +52,20 @@ final class DrydockAllocatorWorker extends PhabricatorWorker {
}
}
private function loadAllBlueprints() {
$instances = id(new DrydockBlueprint())->loadAll();
$blueprints = array();
foreach ($instances as $instance) {
$blueprints[$instance->getPHID()] = $instance;
}
return $blueprints;
}
private function allocateLease(DrydockLease $lease) {
$type = $lease->getResourceType();
$blueprints = $this->loadAllBlueprints();
$pool = id(new DrydockResource())->loadAllWhere(
'type = %s AND status = %s',
$lease->getResourceType(),
@ -65,14 +76,15 @@ final class DrydockAllocatorWorker extends PhabricatorWorker {
$candidates = array();
foreach ($pool as $key => $candidate) {
try {
$blueprint = $candidate->getBlueprint();
} catch (Exception $ex) {
if (!isset($blueprints[$candidate->getBlueprintPHID()])) {
unset($pool[$key]);
continue;
}
if ($blueprint->filterResource($candidate, $lease)) {
$blueprint = $blueprints[$candidate->getBlueprintPHID()];
$implementation = $blueprint->getImplementation();
if ($implementation->filterResource($candidate, $lease)) {
$candidates[] = $candidate;
}
}
@ -83,7 +95,8 @@ final class DrydockAllocatorWorker extends PhabricatorWorker {
if ($candidates) {
shuffle($candidates);
foreach ($candidates as $candidate_resource) {
$blueprint = $candidate_resource->getBlueprint();
$blueprint = $blueprints[$candidate_resource->getBlueprintPHID()]
->getImplementation();
if ($blueprint->allocateLease($candidate_resource, $lease)) {
$resource = $candidate_resource;
break;
@ -92,7 +105,8 @@ final class DrydockAllocatorWorker extends PhabricatorWorker {
}
if (!$resource) {
$blueprints = DrydockBlueprint::getAllBlueprintsForResource($type);
$blueprints = DrydockBlueprintImplementation
::getAllBlueprintImplementationsForResource($type);
$this->logToDrydock(
pht('Found %d Blueprints', count($blueprints)));

View file

@ -1788,6 +1788,14 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql',
'name' => $this->getPatchPath('20131122.repomirror.sql'),
),
'20131123.drydockblueprintpolicy.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20131123.drydockblueprintpolicy.sql'),
),
'20131129.drydockresourceblueprint.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20131129.drydockresourceblueprint.sql'),
),
);
}
}

View file

@ -14,6 +14,29 @@ final class PHUIFormLayoutView extends AphrontView {
return $this;
}
public function appendInstructions($text) {
return $this->appendChild(
phutil_tag(
'div',
array(
'class' => 'aphront-form-instructions',
),
$text));
}
public function appendRemarkupInstructions($remarkup) {
if ($this->getUser() === null) {
throw new Exception(
"Call `setUser` before appending Remarkup to PHUIFormLayoutView.");
}
return $this->appendInstructions(
PhabricatorMarkupEngine::renderOneObject(
id(new PhabricatorMarkupOneOff())->setContent($remarkup),
'default',
$this->getUser()));
}
public function render() {
$classes = array('phui-form-view');