diff --git a/resources/sql/autopatches/20140108.ddbpname.1.sql b/resources/sql/autopatches/20140108.ddbpname.1.sql new file mode 100644 index 0000000000..3fe80875db --- /dev/null +++ b/resources/sql/autopatches/20140108.ddbpname.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_blueprint + ADD blueprintName VARCHAR(255) NOT NULL AFTER className; diff --git a/resources/sql/autopatches/20140108.ddbpname.2.php b/resources/sql/autopatches/20140108.ddbpname.2.php new file mode 100644 index 0000000000..ca7e3ef8b2 --- /dev/null +++ b/resources/sql/autopatches/20140108.ddbpname.2.php @@ -0,0 +1,23 @@ +establishConnection('w'); +$iterator = new LiskMigrationIterator($table); +foreach ($iterator as $blueprint) { + $id = $blueprint->getID(); + + echo "Populating blueprint {$id}...\n"; + + if (!strlen($blueprint->getBlueprintName())) { + queryfx( + $conn_w, + 'UPDATE %T SET blueprintName = %s WHERE id = %d', + $table->getTableName(), + pht('Blueprint %s', $id), + $id); + } +} + +echo "Done.\n"; diff --git a/src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php index 554b969307..86a2b15d9d 100644 --- a/src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php @@ -1,123 +1,123 @@ getAttribute('platform') === $resource->getAttribute('platform'); } protected function shouldAllocateLease( DrydockResource $resource, DrydockLease $lease, array $other_leases) { return true; } protected function executeAcquireLease( DrydockResource $resource, DrydockLease $lease) { // Because preallocated resources are manually created, we should verify // we have all the information we need. PhutilTypeSpec::checkMap( $resource->getAttributesForTypeSpec( array('platform', 'host', 'port', 'credential', 'path')), array( 'platform' => 'string', 'host' => 'string', 'port' => 'string', // Value is a string from the command line 'credential' => 'string', 'path' => 'string', )); $v_platform = $resource->getAttribute('platform'); $v_path = $resource->getAttribute('path'); // Similar to DrydockLocalHostBlueprint, we create a folder // on the remote host that the lease can use. $lease_id = $lease->getID(); // Can't use DIRECTORY_SEPERATOR here because that is relevant to // the platform we're currently running on, not the platform we are // remoting to. $separator = '/'; if ($v_platform === 'windows') { $separator = '\\'; } // Clean up the directory path a little. $base_path = rtrim($v_path, '/'); $base_path = rtrim($base_path, '\\'); $full_path = $base_path.$separator.$lease_id; $cmd = $lease->getInterface('command'); if ($v_platform !== 'windows') { $cmd->execx('mkdir %s', $full_path); } else { // Windows is terrible. The mkdir command doesn't even support putting // the path in quotes. IN QUOTES. ARGUHRGHUGHHGG!! Do some terribly // inaccurate sanity checking since we can't safely escape the path. if (preg_match('/^[A-Z]\\:\\\\[a-zA-Z0-9\\\\\\ ]/', $full_path) === 0) { throw new Exception( 'Unsafe path detected for Windows platform: "'.$full_path.'".'); } $cmd->execx('mkdir %C', $full_path); } $lease->setAttribute('path', $full_path); } public function getType() { return 'host'; } public function getInterface( DrydockResource $resource, DrydockLease $lease, $type) { switch ($type) { case 'command': return id(new DrydockSSHCommandInterface()) ->setConfiguration(array( 'host' => $resource->getAttribute('host'), 'port' => $resource->getAttribute('port'), 'credential' => $resource->getAttribute('credential'), 'platform' => $resource->getAttribute('platform'))); case 'filesystem': return id(new DrydockSFTPFilesystemInterface()) ->setConfiguration(array( 'host' => $resource->getAttribute('host'), 'port' => $resource->getAttribute('port'), 'credential' => $resource->getAttribute('credential'))); } throw new Exception("No interface of type '{$type}'."); } } diff --git a/src/applications/drydock/controller/DrydockBlueprintEditController.php b/src/applications/drydock/controller/DrydockBlueprintEditController.php index a749c317bd..aa3c5be4a0 100644 --- a/src/applications/drydock/controller/DrydockBlueprintEditController.php +++ b/src/applications/drydock/controller/DrydockBlueprintEditController.php @@ -1,121 +1,144 @@ 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(); } $impl = $blueprint->getImplementation(); $cancel_uri = $this->getApplicationURI('blueprint/'.$this->id.'/'); } else { $class = $request->getStr('class'); $impl = DrydockBlueprintImplementation::getNamedImplementation($class); if (!$impl || !$impl->isEnabled()) { return new Aphront400Response(); } - $blueprint = new DrydockBlueprint(); + $blueprint = DrydockBlueprint::initializeNewBlueprint($viewer); $blueprint->setClassName($class); $cancel_uri = $this->getApplicationURI('blueprint/'); } + $v_name = $blueprint->getBlueprintName(); + $e_name = true; + $errors = array(); if ($request->isFormPost()) { $v_view_policy = $request->getStr('viewPolicy'); $v_edit_policy = $request->getStr('editPolicy'); + $v_name = $request->getStr('name'); + if (!strlen($v_name)) { + $e_name = pht('Required'); + $errors[] = pht('You must name this blueprint.'); + } - // TODO: Should we use transactions here? + // TODO: We should use transactions here. $blueprint->setViewPolicy($v_view_policy); $blueprint->setEditPolicy($v_edit_policy); + $blueprint->setBlueprintName($v_name); - $blueprint->save(); + if (!$errors) { + $blueprint->save(); - $id = $blueprint->getID(); - $save_uri = $this->getApplicationURI("blueprint/{$id}/"); + $id = $blueprint->getID(); + $save_uri = $this->getApplicationURI("blueprint/{$id}/"); - return id(new AphrontRedirectResponse())->setURI($save_uri); + return id(new AphrontRedirectResponse())->setURI($save_uri); + } + } + + $error_view = null; + if ($errors) { + $error_view = id(new AphrontErrorView())->setErrors($errors); } $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($blueprint) ->execute(); $form = id(new AphrontFormView()) ->setUser($viewer) ->addHiddenInput('class', $request->getStr('class')) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Name')) + ->setName('name') + ->setValue($v_name) + ->setError($e_name)) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Blueprint Type')) ->setValue($impl->getBlueprintName())) ->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(); if ($blueprint->getID()) { $title = pht('Edit Blueprint'); $header = pht('Edit Blueprint %d', $blueprint->getID()); $crumbs->addTextCrumb(pht('Blueprint %d', $blueprint->getID())); $crumbs->addTextCrumb(pht('Edit')); $submit = pht('Save Blueprint'); } else { $title = pht('New Blueprint'); $header = pht('New Blueprint'); $crumbs->addTextCrumb(pht('New Blueprint')); $submit = pht('Create Blueprint'); } $form->appendChild( id(new AphrontFormSubmitControl()) ->setValue($submit) ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) ->setHeaderText($header) + ->setFormError($error_view) ->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 index 7dd1cb12cb..bdc1953c69 100644 --- a/src/applications/drydock/controller/DrydockBlueprintListController.php +++ b/src/applications/drydock/controller/DrydockBlueprintListController.php @@ -1,66 +1,62 @@ queryKey = idx($data, 'queryKey'); } public function processRequest() { $request = $this->getRequest(); $controller = id(new PhabricatorApplicationSearchController($request)) ->setQueryKey($this->queryKey) ->setSearchEngine(new DrydockBlueprintSearchEngine()) ->setNavigation($this->buildSideNavView()); return $this->delegateToController($controller); } public function renderResultsList( array $blueprints, PhabricatorSavedQuery $query) { assert_instances_of($blueprints, 'DrydockBlueprint'); $viewer = $this->getRequest()->getUser(); $view = new PHUIObjectItemListView(); foreach ($blueprints as $blueprint) { $item = id(new PHUIObjectItemView()) - ->setHeader($blueprint->getClassName()) + ->setHeader($blueprint->getBlueprintName()) ->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'); + if (!$blueprint->getImplementation()->isEnabled()) { + $item->setDisabled(true); } - $item->addAttribute($blueprint->getImplementation()->getDescription()); + $item->addAttribute($blueprint->getImplementation()->getBlueprintName()); $view->addItem($item); } return $view; } public function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('New Blueprint')) ->setHref($this->getApplicationURI('/blueprint/create/')) ->setIcon('create')); return $crumbs; } } diff --git a/src/applications/drydock/controller/DrydockBlueprintViewController.php b/src/applications/drydock/controller/DrydockBlueprintViewController.php index 8526f42d9c..d9b22479a8 100644 --- a/src/applications/drydock/controller/DrydockBlueprintViewController.php +++ b/src/applications/drydock/controller/DrydockBlueprintViewController.php @@ -1,108 +1,110 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $blueprint = id(new DrydockBlueprintQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) ->executeOne(); if (!$blueprint) { return new Aphront404Response(); } - $title = 'Blueprint '.$blueprint->getID().' '.$blueprint->getClassName(); + $title = $blueprint->getBlueprintName(); $header = id(new PHUIHeaderView()) - ->setHeader($title); + ->setHeader($title) + ->setUser($viewer) + ->setPolicyObject($blueprint); $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($viewer) ->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->addTextCrumb(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) { $viewer = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObjectURI($this->getRequest()->getRequestURI()) ->setObject($blueprint); $uri = '/blueprint/edit/'.$blueprint->getID().'/'; $uri = $this->getApplicationURI($uri); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $blueprint, PhabricatorPolicyCapability::CAN_EDIT); $view->addAction( id(new PhabricatorActionView()) ->setHref($uri) ->setName(pht('Edit Blueprint')) ->setIcon('edit') ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); return $view; } private function buildPropertyListView( DrydockBlueprint $blueprint, PhabricatorActionListView $actions) { $view = new PHUIPropertyListView(); $view->setActionList($actions); $view->addProperty( - pht('Implementation'), - $blueprint->getClassName()); + pht('Type'), + $blueprint->getImplementation()->getBlueprintName()); return $view; } } diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index 06f2375563..2e61e8e2db 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -1,80 +1,85 @@ setBlueprintName(''); + } + 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); } public function attachImplementation(DrydockBlueprintImplementation $impl) { $this->implementation = $impl; return $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.'); } } }