From ba16df0fed185590fac65eed82e3b0a9a77efdc0 Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Tue, 3 Dec 2013 11:09:07 +1100 Subject: [PATCH] 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 --- .../20131123.drydockblueprintpolicy.sql | 11 ++ .../20131129.drydockresourceblueprint.sql | 5 + src/__phutil_library_map__.php | 32 ++++- .../PhabricatorApplicationDrydock.php | 6 + ...php => DrydockBlueprintImplementation.php} | 28 +++- ...ydockLocalHostBlueprintImplementation.php} | 7 +- ...eallocatedHostBlueprintImplementation.php} | 7 +- ...ockWorkingCopyBlueprintImplementation.php} | 7 +- .../DrydockBlueprintCreateController.php | 68 ++++++++++ .../DrydockBlueprintEditController.php | 122 ++++++++++++++++++ .../DrydockBlueprintListController.php | 77 +++++++++++ .../DrydockBlueprintViewController.php | 100 ++++++++++++++ .../drydock/controller/DrydockController.php | 7 +- .../DrydockResourceViewController.php | 9 +- ...rydockManagementCreateResourceWorkflow.php | 20 ++- .../drydock/phid/DrydockPHIDTypeBlueprint.php | 34 +++++ .../drydock/query/DrydockBlueprintQuery.php | 70 ++++++++++ .../drydock/query/DrydockLeaseQuery.php | 6 +- .../drydock/query/DrydockResourceQuery.php | 13 ++ .../drydock/storage/DrydockBlueprint.php | 72 +++++++++++ .../drydock/storage/DrydockResource.php | 8 +- .../util/DrydockBlueprintScopeGuard.php | 2 +- .../drydock/worker/DrydockAllocatorWorker.php | 28 +++- .../patch/PhabricatorBuiltinPatchList.php | 8 ++ src/view/form/PHUIFormLayoutView.php | 23 ++++ 25 files changed, 729 insertions(+), 41 deletions(-) create mode 100644 resources/sql/patches/20131123.drydockblueprintpolicy.sql create mode 100644 resources/sql/patches/20131129.drydockresourceblueprint.sql rename src/applications/drydock/blueprint/{DrydockBlueprint.php => DrydockBlueprintImplementation.php} (94%) rename src/applications/drydock/blueprint/{DrydockLocalHostBlueprint.php => DrydockLocalHostBlueprintImplementation.php} (91%) rename src/applications/drydock/blueprint/{DrydockPreallocatedHostBlueprint.php => DrydockPreallocatedHostBlueprintImplementation.php} (93%) rename src/applications/drydock/blueprint/{DrydockWorkingCopyBlueprint.php => DrydockWorkingCopyBlueprintImplementation.php} (92%) create mode 100644 src/applications/drydock/controller/DrydockBlueprintCreateController.php create mode 100644 src/applications/drydock/controller/DrydockBlueprintEditController.php create mode 100644 src/applications/drydock/controller/DrydockBlueprintListController.php create mode 100644 src/applications/drydock/controller/DrydockBlueprintViewController.php create mode 100644 src/applications/drydock/phid/DrydockPHIDTypeBlueprint.php create mode 100644 src/applications/drydock/query/DrydockBlueprintQuery.php create mode 100644 src/applications/drydock/storage/DrydockBlueprint.php diff --git a/resources/sql/patches/20131123.drydockblueprintpolicy.sql b/resources/sql/patches/20131123.drydockblueprintpolicy.sql new file mode 100644 index 0000000000..79f778c500 --- /dev/null +++ b/resources/sql/patches/20131123.drydockblueprintpolicy.sql @@ -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; diff --git a/resources/sql/patches/20131129.drydockresourceblueprint.sql b/resources/sql/patches/20131129.drydockresourceblueprint.sql new file mode 100644 index 0000000000..d4b8c528ed --- /dev/null +++ b/resources/sql/patches/20131129.drydockresourceblueprint.sql @@ -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; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 88ab3d46de..627a0a2b16 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/applications/drydock/application/PhabricatorApplicationDrydock.php b/src/applications/drydock/application/PhabricatorApplicationDrydock.php index 02c41e2e2b..ef918f499b 100644 --- a/src/applications/drydock/application/PhabricatorApplicationDrydock.php +++ b/src/applications/drydock/application/PhabricatorApplicationDrydock.php @@ -34,6 +34,12 @@ final class PhabricatorApplicationDrydock extends PhabricatorApplication { return array( '/drydock/' => array( '' => 'DrydockResourceListController', + 'blueprint/' => array( + '' => 'DrydockBlueprintListController', + '(?P[1-9]\d*)/' => 'DrydockBlueprintViewController', + 'create/' => 'DrydockBlueprintCreateController', + 'edit/(?P[1-9]\d*)/' => 'DrydockBlueprintEditController', + ), 'resource/' => array( '' => 'DrydockResourceListController', '(?P[1-9]\d*)/' => 'DrydockResourceViewController', diff --git a/src/applications/drydock/blueprint/DrydockBlueprint.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php similarity index 94% rename from src/applications/drydock/blueprint/DrydockBlueprint.php rename to src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index c00413775c..bc8c7e8539 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprint.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -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); diff --git a/src/applications/drydock/blueprint/DrydockLocalHostBlueprint.php b/src/applications/drydock/blueprint/DrydockLocalHostBlueprintImplementation.php similarity index 91% rename from src/applications/drydock/blueprint/DrydockLocalHostBlueprint.php rename to src/applications/drydock/blueprint/DrydockLocalHostBlueprintImplementation.php index 8f8f49af4e..21a06c6c86 100644 --- a/src/applications/drydock/blueprint/DrydockLocalHostBlueprint.php +++ b/src/applications/drydock/blueprint/DrydockLocalHostBlueprintImplementation.php @@ -1,11 +1,16 @@ 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); + } + +} diff --git a/src/applications/drydock/controller/DrydockBlueprintEditController.php b/src/applications/drydock/controller/DrydockBlueprintEditController.php new file mode 100644 index 0000000000..9b2d2cb18a --- /dev/null +++ b/src/applications/drydock/controller/DrydockBlueprintEditController.php @@ -0,0 +1,122 @@ +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, + )); + } + +} diff --git a/src/applications/drydock/controller/DrydockBlueprintListController.php b/src/applications/drydock/controller/DrydockBlueprintListController.php new file mode 100644 index 0000000000..3c18e98d13 --- /dev/null +++ b/src/applications/drydock/controller/DrydockBlueprintListController.php @@ -0,0 +1,77 @@ +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; + } + +} diff --git a/src/applications/drydock/controller/DrydockBlueprintViewController.php b/src/applications/drydock/controller/DrydockBlueprintViewController.php new file mode 100644 index 0000000000..44e21a116a --- /dev/null +++ b/src/applications/drydock/controller/DrydockBlueprintViewController.php @@ -0,0 +1,100 @@ +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; + } + +} diff --git a/src/applications/drydock/controller/DrydockController.php b/src/applications/drydock/controller/DrydockController.php index 0e3929761b..92b79fcc0a 100644 --- a/src/applications/drydock/controller/DrydockController.php +++ b/src/applications/drydock/controller/DrydockController.php @@ -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'); diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index 60232117b4..2421296322 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -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')); diff --git a/src/applications/drydock/management/DrydockManagementCreateResourceWorkflow.php b/src/applications/drydock/management/DrydockManagementCreateResourceWorkflow.php index c833a3809d..c93220380e 100644 --- a/src/applications/drydock/management/DrydockManagementCreateResourceWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementCreateResourceWorkflow.php @@ -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) { diff --git a/src/applications/drydock/phid/DrydockPHIDTypeBlueprint.php b/src/applications/drydock/phid/DrydockPHIDTypeBlueprint.php new file mode 100644 index 0000000000..f9c5dc1559 --- /dev/null +++ b/src/applications/drydock/phid/DrydockPHIDTypeBlueprint.php @@ -0,0 +1,34 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + } + +} diff --git a/src/applications/drydock/query/DrydockBlueprintQuery.php b/src/applications/drydock/query/DrydockBlueprintQuery.php new file mode 100644 index 0000000000..ebad1227e8 --- /dev/null +++ b/src/applications/drydock/query/DrydockBlueprintQuery.php @@ -0,0 +1,70 @@ +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'; + } + +} diff --git a/src/applications/drydock/query/DrydockLeaseQuery.php b/src/applications/drydock/query/DrydockLeaseQuery.php index a7a1151092..9b67acd363 100644 --- a/src/applications/drydock/query/DrydockLeaseQuery.php +++ b/src/applications/drydock/query/DrydockLeaseQuery.php @@ -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]); } } } diff --git a/src/applications/drydock/query/DrydockResourceQuery.php b/src/applications/drydock/query/DrydockResourceQuery.php index 2504aa2066..e5f57c8024 100644 --- a/src/applications/drydock/query/DrydockResourceQuery.php +++ b/src/applications/drydock/query/DrydockResourceQuery.php @@ -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); diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php new file mode 100644 index 0000000000..3649f25c95 --- /dev/null +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -0,0 +1,72 @@ + 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.'); + } + } +} diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index 39911b0a97..4e847883c1 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -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(); } diff --git a/src/applications/drydock/util/DrydockBlueprintScopeGuard.php b/src/applications/drydock/util/DrydockBlueprintScopeGuard.php index 539fb6c459..ab8268ba12 100644 --- a/src/applications/drydock/util/DrydockBlueprintScopeGuard.php +++ b/src/applications/drydock/util/DrydockBlueprintScopeGuard.php @@ -2,7 +2,7 @@ final class DrydockBlueprintScopeGuard { - public function __construct(DrydockBlueprint $blueprint) { + public function __construct(DrydockBlueprintImplementation $blueprint) { $this->blueprint = $blueprint; } diff --git a/src/applications/drydock/worker/DrydockAllocatorWorker.php b/src/applications/drydock/worker/DrydockAllocatorWorker.php index ecdd14a912..dfc481b6a0 100644 --- a/src/applications/drydock/worker/DrydockAllocatorWorker.php +++ b/src/applications/drydock/worker/DrydockAllocatorWorker.php @@ -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))); diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index 71daeb025a..12fd491a1b 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -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'), + ), ); } } diff --git a/src/view/form/PHUIFormLayoutView.php b/src/view/form/PHUIFormLayoutView.php index 15347b12f5..3ff5f02670 100644 --- a/src/view/form/PHUIFormLayoutView.php +++ b/src/view/form/PHUIFormLayoutView.php @@ -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');