@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Rough-in Almanac namespaces

Summary:
Ref T6741. Ref T10246.

Root problem: to provide Drydock in the cluster, we need to expose Almanac, and doing so would let users accidentally or intentionally create a bunch of `repo006.phacility.net` devices/services which could conflict with the real ones we manage.

There's currently no way to say "you can't create anything named `*.blah.net`". This adds "namespaces", which let you do that (well, not yet, but they will after the next diff).

After the next diff, if you try to create `repo003.phacility.net`, but the namespace `phacility.net` already exists and you don't have permission to edit it, you'll be asked to choose a different name.

Also various modernizations and some new docs.

Test Plan:
- Created cool namespaces like `this.computer`.
- Almanac namespaces don't actually enforce policies yet.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T6741, T10246

Differential Revision: https://secure.phabricator.com/D15324

+1195 -40
+7
resources/sql/autopatches/20160221.almanac.7.namespacen.sql
··· 1 + CREATE TABLE {$NAMESPACE}_almanac.almanac_namespacename_ngrams ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + objectID INT UNSIGNED NOT NULL, 4 + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, 5 + KEY `key_object` (objectID), 6 + KEY `key_ngram` (ngram, objectID) 7 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+14
resources/sql/autopatches/20160221.almanac.8.namespace.sql
··· 1 + CREATE TABLE {$NAMESPACE}_almanac.almanac_namespace ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARBINARY(64) NOT NULL, 4 + name VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT}, 5 + nameIndex BINARY(12) NOT NULL, 6 + mailKey BINARY(20) NOT NULL, 7 + viewPolicy VARBINARY(64) NOT NULL, 8 + editPolicy VARBINARY(64) NOT NULL, 9 + dateCreated INT UNSIGNED NOT NULL, 10 + dateModified INT UNSIGNED NOT NULL, 11 + UNIQUE KEY `key_phid` (phid), 12 + UNIQUE KEY `key_nameindex` (nameIndex), 13 + KEY `key_name` (name) 14 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+19
resources/sql/autopatches/20160221.almanac.9.namespacex.sql
··· 1 + CREATE TABLE {$NAMESPACE}_almanac.almanac_namespacetransaction ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARBINARY(64) NOT NULL, 4 + authorPHID VARBINARY(64) NOT NULL, 5 + objectPHID VARBINARY(64) NOT NULL, 6 + viewPolicy VARBINARY(64) NOT NULL, 7 + editPolicy VARBINARY(64) NOT NULL, 8 + commentPHID VARBINARY(64) DEFAULT NULL, 9 + commentVersion INT UNSIGNED NOT NULL, 10 + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, 11 + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 12 + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 13 + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 14 + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 15 + dateCreated INT UNSIGNED NOT NULL, 16 + dateModified INT UNSIGNED NOT NULL, 17 + UNIQUE KEY `key_phid` (`phid`), 18 + KEY `key_object` (`objectPHID`) 19 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+37
src/__phutil_library_map__.php
··· 28 28 'AlmanacCoreCustomField' => 'applications/almanac/customfield/AlmanacCoreCustomField.php', 29 29 'AlmanacCreateClusterServicesCapability' => 'applications/almanac/capability/AlmanacCreateClusterServicesCapability.php', 30 30 'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php', 31 + 'AlmanacCreateNamespacesCapability' => 'applications/almanac/capability/AlmanacCreateNamespacesCapability.php', 31 32 'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php', 32 33 'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php', 33 34 'AlmanacCustomField' => 'applications/almanac/customfield/AlmanacCustomField.php', ··· 61 62 'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php', 62 63 'AlmanacNames' => 'applications/almanac/util/AlmanacNames.php', 63 64 'AlmanacNamesTestCase' => 'applications/almanac/util/__tests__/AlmanacNamesTestCase.php', 65 + 'AlmanacNamespace' => 'applications/almanac/storage/AlmanacNamespace.php', 66 + 'AlmanacNamespaceController' => 'applications/almanac/controller/AlmanacNamespaceController.php', 67 + 'AlmanacNamespaceEditController' => 'applications/almanac/controller/AlmanacNamespaceEditController.php', 68 + 'AlmanacNamespaceEditEngine' => 'applications/almanac/editor/AlmanacNamespaceEditEngine.php', 69 + 'AlmanacNamespaceEditor' => 'applications/almanac/editor/AlmanacNamespaceEditor.php', 70 + 'AlmanacNamespaceListController' => 'applications/almanac/controller/AlmanacNamespaceListController.php', 71 + 'AlmanacNamespaceNameNgrams' => 'applications/almanac/storage/AlmanacNamespaceNameNgrams.php', 72 + 'AlmanacNamespacePHIDType' => 'applications/almanac/phid/AlmanacNamespacePHIDType.php', 73 + 'AlmanacNamespaceQuery' => 'applications/almanac/query/AlmanacNamespaceQuery.php', 74 + 'AlmanacNamespaceSearchEngine' => 'applications/almanac/query/AlmanacNamespaceSearchEngine.php', 75 + 'AlmanacNamespaceTransaction' => 'applications/almanac/storage/AlmanacNamespaceTransaction.php', 76 + 'AlmanacNamespaceTransactionQuery' => 'applications/almanac/query/AlmanacNamespaceTransactionQuery.php', 77 + 'AlmanacNamespaceViewController' => 'applications/almanac/controller/AlmanacNamespaceViewController.php', 64 78 'AlmanacNetwork' => 'applications/almanac/storage/AlmanacNetwork.php', 65 79 'AlmanacNetworkController' => 'applications/almanac/controller/AlmanacNetworkController.php', 66 80 'AlmanacNetworkEditController' => 'applications/almanac/controller/AlmanacNetworkEditController.php', ··· 4000 4014 ), 4001 4015 'AlmanacCreateClusterServicesCapability' => 'PhabricatorPolicyCapability', 4002 4016 'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability', 4017 + 'AlmanacCreateNamespacesCapability' => 'PhabricatorPolicyCapability', 4003 4018 'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability', 4004 4019 'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability', 4005 4020 'AlmanacCustomField' => 'PhabricatorCustomField', ··· 4047 4062 'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow', 4048 4063 'AlmanacNames' => 'Phobject', 4049 4064 'AlmanacNamesTestCase' => 'PhabricatorTestCase', 4065 + 'AlmanacNamespace' => array( 4066 + 'AlmanacDAO', 4067 + 'PhabricatorPolicyInterface', 4068 + 'PhabricatorCustomFieldInterface', 4069 + 'PhabricatorApplicationTransactionInterface', 4070 + 'PhabricatorProjectInterface', 4071 + 'AlmanacPropertyInterface', 4072 + 'PhabricatorDestructibleInterface', 4073 + 'PhabricatorNgramsInterface', 4074 + ), 4075 + 'AlmanacNamespaceController' => 'AlmanacController', 4076 + 'AlmanacNamespaceEditController' => 'AlmanacController', 4077 + 'AlmanacNamespaceEditEngine' => 'PhabricatorEditEngine', 4078 + 'AlmanacNamespaceEditor' => 'PhabricatorApplicationTransactionEditor', 4079 + 'AlmanacNamespaceListController' => 'AlmanacNamespaceController', 4080 + 'AlmanacNamespaceNameNgrams' => 'PhabricatorSearchNgrams', 4081 + 'AlmanacNamespacePHIDType' => 'PhabricatorPHIDType', 4082 + 'AlmanacNamespaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4083 + 'AlmanacNamespaceSearchEngine' => 'PhabricatorApplicationSearchEngine', 4084 + 'AlmanacNamespaceTransaction' => 'PhabricatorApplicationTransaction', 4085 + 'AlmanacNamespaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 4086 + 'AlmanacNamespaceViewController' => 'AlmanacNamespaceController', 4050 4087 'AlmanacNetwork' => array( 4051 4088 'AlmanacDAO', 4052 4089 'PhabricatorApplicationTransactionInterface',
+13 -4
src/applications/almanac/application/PhabricatorAlmanacApplication.php
··· 29 29 public function getHelpDocumentationArticles(PhabricatorUser $viewer) { 30 30 return array( 31 31 array( 32 - 'name' => pht('Alamanac User Guide'), 32 + 'name' => pht('Almanac User Guide'), 33 33 'href' => PhabricatorEnv::getDoclink('Almanac User Guide'), 34 34 ), 35 35 ); ··· 44 44 '/almanac/' => array( 45 45 '' => 'AlmanacConsoleController', 46 46 'service/' => array( 47 - '(?:query/(?P<queryKey>[^/]+)/)?' => 'AlmanacServiceListController', 47 + $this->getQueryRoutePattern() => 'AlmanacServiceListController', 48 48 'edit/(?:(?P<id>\d+)/)?' => 'AlmanacServiceEditController', 49 49 'view/(?P<name>[^/]+)/' => 'AlmanacServiceViewController', 50 50 ), 51 51 'device/' => array( 52 - '(?:query/(?P<queryKey>[^/]+)/)?' => 'AlmanacDeviceListController', 52 + $this->getQueryRoutePattern() => 'AlmanacDeviceListController', 53 53 'edit/(?:(?P<id>\d+)/)?' => 'AlmanacDeviceEditController', 54 54 'view/(?P<name>[^/]+)/' => 'AlmanacDeviceViewController', 55 55 ), ··· 61 61 '(?P<id>\d+)/' => 'AlmanacBindingViewController', 62 62 ), 63 63 'network/' => array( 64 - '(?:query/(?P<queryKey>[^/]+)/)?' => 'AlmanacNetworkListController', 64 + $this->getQueryRoutePattern() => 'AlmanacNetworkListController', 65 65 'edit/(?:(?P<id>\d+)/)?' => 'AlmanacNetworkEditController', 66 66 '(?P<id>\d+)/' => 'AlmanacNetworkViewController', 67 67 ), ··· 69 69 'edit/' => 'AlmanacPropertyEditController', 70 70 'delete/' => 'AlmanacPropertyDeleteController', 71 71 ), 72 + 'namespace/' => array( 73 + $this->getQueryRoutePattern() => 'AlmanacNamespaceListController', 74 + $this->getEditRoutePattern('edit/') 75 + => 'AlmanacNamespaceEditController', 76 + '(?P<id>\d+)/' => 'AlmanacNamespaceViewController', 77 + ), 72 78 ), 73 79 ); 74 80 } ··· 82 88 'default' => PhabricatorPolicies::POLICY_ADMIN, 83 89 ), 84 90 AlmanacCreateNetworksCapability::CAPABILITY => array( 91 + 'default' => PhabricatorPolicies::POLICY_ADMIN, 92 + ), 93 + AlmanacCreateNamespacesCapability::CAPABILITY => array( 85 94 'default' => PhabricatorPolicies::POLICY_ADMIN, 86 95 ), 87 96 AlmanacCreateClusterServicesCapability::CAPABILITY => array(
+16
src/applications/almanac/capability/AlmanacCreateNamespacesCapability.php
··· 1 + <?php 2 + 3 + final class AlmanacCreateNamespacesCapability 4 + extends PhabricatorPolicyCapability { 5 + 6 + const CAPABILITY = 'almanac.namespaces'; 7 + 8 + public function getCapabilityName() { 9 + return pht('Can Create Namespaces'); 10 + } 11 + 12 + public function describeCapabilityRejection() { 13 + return pht('You do not have permission to create Almanac namespaces.'); 14 + } 15 + 16 + }
+35 -9
src/applications/almanac/controller/AlmanacConsoleController.php
··· 14 14 15 15 $menu->addItem( 16 16 id(new PHUIObjectItemView()) 17 - ->setHeader(pht('Services')) 18 - ->setHref($this->getApplicationURI('service/')) 19 - ->setIcon('fa-plug') 20 - ->addAttribute(pht('Manage Almanac services.'))); 21 - 22 - $menu->addItem( 23 - id(new PHUIObjectItemView()) 24 17 ->setHeader(pht('Devices')) 25 18 ->setHref($this->getApplicationURI('device/')) 26 19 ->setIcon('fa-server') 27 - ->addAttribute(pht('Manage Almanac devices.'))); 20 + ->addAttribute( 21 + pht( 22 + 'Create an inventory of physical and virtual hosts and '. 23 + 'devices.'))); 24 + 25 + $menu->addItem( 26 + id(new PHUIObjectItemView()) 27 + ->setHeader(pht('Services')) 28 + ->setHref($this->getApplicationURI('service/')) 29 + ->setIcon('fa-plug') 30 + ->addAttribute( 31 + pht( 32 + 'Create and update services, and map them to interfaces on '. 33 + 'devices.'))); 28 34 29 35 $menu->addItem( 30 36 id(new PHUIObjectItemView()) 31 37 ->setHeader(pht('Networks')) 32 38 ->setHref($this->getApplicationURI('network/')) 33 39 ->setIcon('fa-globe') 34 - ->addAttribute(pht('Manage Almanac networks.'))); 40 + ->addAttribute( 41 + pht( 42 + 'Manage public and private networks.'))); 43 + 44 + $menu->addItem( 45 + id(new PHUIObjectItemView()) 46 + ->setHeader(pht('Namespaces')) 47 + ->setHref($this->getApplicationURI('namespace/')) 48 + ->setIcon('fa-asterisk') 49 + ->addAttribute( 50 + pht('Control who can create new named services and devices.'))); 51 + 52 + $docs_uri = PhabricatorEnv::getDoclink( 53 + 'Almanac User Guide'); 54 + 55 + $menu->addItem( 56 + id(new PHUIObjectItemView()) 57 + ->setHeader(pht('Documentation')) 58 + ->setHref($docs_uri) 59 + ->setIcon('fa-book') 60 + ->addAttribute(pht('Browse documentation for Almanac.'))); 35 61 36 62 $crumbs = $this->buildApplicationCrumbs(); 37 63 $crumbs->addTextCrumb(pht('Console'));
+14
src/applications/almanac/controller/AlmanacNamespaceController.php
··· 1 + <?php 2 + 3 + abstract class AlmanacNamespaceController extends AlmanacController { 4 + 5 + protected function buildApplicationCrumbs() { 6 + $crumbs = parent::buildApplicationCrumbs(); 7 + 8 + $list_uri = $this->getApplicationURI('namespace/'); 9 + $crumbs->addTextCrumb(pht('Namespaces'), $list_uri); 10 + 11 + return $crumbs; 12 + } 13 + 14 + }
+11
src/applications/almanac/controller/AlmanacNamespaceEditController.php
··· 1 + <?php 2 + 3 + final class AlmanacNamespaceEditController extends AlmanacController { 4 + 5 + public function handleRequest(AphrontRequest $request) { 6 + return id(new AlmanacNamespaceEditEngine()) 7 + ->setController($this) 8 + ->buildResponse(); 9 + } 10 + 11 + }
+26
src/applications/almanac/controller/AlmanacNamespaceListController.php
··· 1 + <?php 2 + 3 + final class AlmanacNamespaceListController 4 + extends AlmanacNamespaceController { 5 + 6 + public function shouldAllowPublic() { 7 + return true; 8 + } 9 + 10 + public function handleRequest(AphrontRequest $request) { 11 + return id(new AlmanacNamespaceSearchEngine()) 12 + ->setController($this) 13 + ->buildResponse(); 14 + } 15 + 16 + protected function buildApplicationCrumbs() { 17 + $crumbs = parent::buildApplicationCrumbs(); 18 + 19 + id(new AlmanacNamespaceEditEngine()) 20 + ->setViewer($this->getViewer()) 21 + ->addActionToCrumbs($crumbs); 22 + 23 + return $crumbs; 24 + } 25 + 26 + }
+87
src/applications/almanac/controller/AlmanacNamespaceViewController.php
··· 1 + <?php 2 + 3 + final class AlmanacNamespaceViewController 4 + extends AlmanacNamespaceController { 5 + 6 + public function shouldAllowPublic() { 7 + return true; 8 + } 9 + 10 + public function handleRequest(AphrontRequest $request) { 11 + $viewer = $request->getViewer(); 12 + 13 + $id = $request->getURIData('id'); 14 + $namespace = id(new AlmanacNamespaceQuery()) 15 + ->setViewer($viewer) 16 + ->withIDs(array($id)) 17 + ->executeOne(); 18 + if (!$namespace) { 19 + return new Aphront404Response(); 20 + } 21 + 22 + $title = pht('Namespace %s', $namespace->getName()); 23 + 24 + $property_list = $this->buildPropertyList($namespace); 25 + $action_list = $this->buildActionList($namespace); 26 + $property_list->setActionList($action_list); 27 + 28 + $header = id(new PHUIHeaderView()) 29 + ->setUser($viewer) 30 + ->setHeader($namespace->getName()) 31 + ->setPolicyObject($namespace); 32 + 33 + $box = id(new PHUIObjectBoxView()) 34 + ->setHeader($header) 35 + ->addPropertyList($property_list); 36 + 37 + $crumbs = $this->buildApplicationCrumbs(); 38 + $crumbs->addTextCrumb($namespace->getName()); 39 + 40 + $timeline = $this->buildTransactionTimeline( 41 + $namespace, 42 + new AlmanacNamespaceTransactionQuery()); 43 + $timeline->setShouldTerminate(true); 44 + 45 + return $this->newPage() 46 + ->setTitle($title) 47 + ->setCrumbs($crumbs) 48 + ->appendChild( 49 + array( 50 + $box, 51 + $timeline, 52 + )); 53 + } 54 + 55 + private function buildPropertyList(AlmanacNamespace $namespace) { 56 + $viewer = $this->getViewer(); 57 + 58 + $properties = id(new PHUIPropertyListView()) 59 + ->setUser($viewer); 60 + 61 + return $properties; 62 + } 63 + 64 + private function buildActionList(AlmanacNamespace $namespace) { 65 + $viewer = $this->getViewer(); 66 + $id = $namespace->getID(); 67 + 68 + $can_edit = PhabricatorPolicyFilter::hasCapability( 69 + $viewer, 70 + $namespace, 71 + PhabricatorPolicyCapability::CAN_EDIT); 72 + 73 + $actions = id(new PhabricatorActionListView()) 74 + ->setUser($viewer); 75 + 76 + $actions->addAction( 77 + id(new PhabricatorActionView()) 78 + ->setIcon('fa-pencil') 79 + ->setName(pht('Edit Namespace')) 80 + ->setHref($this->getApplicationURI("namespace/edit/{$id}/")) 81 + ->setWorkflow(!$can_edit) 82 + ->setDisabled(!$can_edit)); 83 + 84 + return $actions; 85 + } 86 + 87 + }
+2 -2
src/applications/almanac/controller/AlmanacPropertyEditController.php
··· 55 55 } else { 56 56 $caught = null; 57 57 try { 58 - AlmanacNames::validateServiceOrDeviceName($name); 58 + AlmanacNames::validateName($name); 59 59 } catch (Exception $ex) { 60 60 $caught = $ex; 61 61 } ··· 92 92 93 93 // Make sure property key is appropriate. 94 94 // TODO: It would be cleaner to put this safety check in the Editor. 95 - AlmanacNames::validateServiceOrDeviceName($property_key); 95 + AlmanacNames::validateName($property_key); 96 96 97 97 // If we're adding a new property, put a placeholder on the object so 98 98 // that we can build a CustomField for it.
+1 -1
src/applications/almanac/editor/AlmanacDeviceEditor.php
··· 136 136 $name = $xaction->getNewValue(); 137 137 138 138 try { 139 - AlmanacNames::validateServiceOrDeviceName($name); 139 + AlmanacNames::validateName($name); 140 140 } catch (Exception $ex) { 141 141 $message = $ex->getMessage(); 142 142 }
+86
src/applications/almanac/editor/AlmanacNamespaceEditEngine.php
··· 1 + <?php 2 + 3 + final class AlmanacNamespaceEditEngine 4 + extends PhabricatorEditEngine { 5 + 6 + const ENGINECONST = 'almanac.namespace'; 7 + 8 + public function isEngineConfigurable() { 9 + return false; 10 + } 11 + 12 + public function getEngineName() { 13 + return pht('Almanac Namespaces'); 14 + } 15 + 16 + public function getSummaryHeader() { 17 + return pht('Edit Almanac Namespace Configurations'); 18 + } 19 + 20 + public function getSummaryText() { 21 + return pht('This engine is used to edit Almanac namespaces.'); 22 + } 23 + 24 + public function getEngineApplicationClass() { 25 + return 'PhabricatorAlmanacApplication'; 26 + } 27 + 28 + protected function newEditableObject() { 29 + return AlmanacNamespace::initializeNewNamespace(); 30 + } 31 + 32 + protected function newObjectQuery() { 33 + return new AlmanacNamespaceQuery(); 34 + } 35 + 36 + protected function getObjectCreateTitleText($object) { 37 + return pht('Create Namespace'); 38 + } 39 + 40 + protected function getObjectCreateButtonText($object) { 41 + return pht('Create Namespace'); 42 + } 43 + 44 + protected function getObjectEditTitleText($object) { 45 + return pht('Edit Namespace: %s', $object->getName()); 46 + } 47 + 48 + protected function getObjectEditShortText($object) { 49 + return pht('Edit Namespace'); 50 + } 51 + 52 + protected function getObjectCreateShortText() { 53 + return pht('Create Namespace'); 54 + } 55 + 56 + protected function getEditorURI() { 57 + return '/almanac/namespace/edit/'; 58 + } 59 + 60 + protected function getObjectCreateCancelURI($object) { 61 + return '/almanac/namespace/'; 62 + } 63 + 64 + protected function getObjectViewURI($object) { 65 + $id = $object->getID(); 66 + return "/almanac/namespace/{$id}/"; 67 + } 68 + 69 + protected function getCreateNewObjectPolicy() { 70 + return $this->getApplication()->getPolicy( 71 + AlmanacCreateNamespacesCapability::CAPABILITY); 72 + } 73 + 74 + protected function buildCustomEditFields($object) { 75 + return array( 76 + id(new PhabricatorTextEditField()) 77 + ->setKey('name') 78 + ->setLabel(pht('Name')) 79 + ->setDescription(pht('Name of the namespace.')) 80 + ->setTransactionType(AlmanacNamespaceTransaction::TYPE_NAME) 81 + ->setIsRequired(true) 82 + ->setValue($object->getName()), 83 + ); 84 + } 85 + 86 + }
+162
src/applications/almanac/editor/AlmanacNamespaceEditor.php
··· 1 + <?php 2 + 3 + final class AlmanacNamespaceEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return 'PhabricatorAlmanacApplication'; 8 + } 9 + 10 + public function getEditorObjectsDescription() { 11 + return pht('Almanac Namespace'); 12 + } 13 + 14 + protected function supportsSearch() { 15 + return true; 16 + } 17 + 18 + public function getTransactionTypes() { 19 + $types = parent::getTransactionTypes(); 20 + 21 + $types[] = AlmanacNamespaceTransaction::TYPE_NAME; 22 + $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; 23 + $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; 24 + 25 + return $types; 26 + } 27 + 28 + protected function getCustomTransactionOldValue( 29 + PhabricatorLiskDAO $object, 30 + PhabricatorApplicationTransaction $xaction) { 31 + switch ($xaction->getTransactionType()) { 32 + case AlmanacNamespaceTransaction::TYPE_NAME: 33 + return $object->getName(); 34 + } 35 + 36 + return parent::getCustomTransactionOldValue($object, $xaction); 37 + } 38 + 39 + protected function getCustomTransactionNewValue( 40 + PhabricatorLiskDAO $object, 41 + PhabricatorApplicationTransaction $xaction) { 42 + 43 + switch ($xaction->getTransactionType()) { 44 + case AlmanacNamespaceTransaction::TYPE_NAME: 45 + return $xaction->getNewValue(); 46 + } 47 + 48 + return parent::getCustomTransactionNewValue($object, $xaction); 49 + } 50 + 51 + protected function applyCustomInternalTransaction( 52 + PhabricatorLiskDAO $object, 53 + PhabricatorApplicationTransaction $xaction) { 54 + 55 + switch ($xaction->getTransactionType()) { 56 + case AlmanacNamespaceTransaction::TYPE_NAME: 57 + $object->setName($xaction->getNewValue()); 58 + return; 59 + } 60 + 61 + return parent::applyCustomInternalTransaction($object, $xaction); 62 + } 63 + 64 + protected function applyCustomExternalTransaction( 65 + PhabricatorLiskDAO $object, 66 + PhabricatorApplicationTransaction $xaction) { 67 + 68 + switch ($xaction->getTransactionType()) { 69 + case AlmanacNamespaceTransaction::TYPE_NAME: 70 + return; 71 + } 72 + 73 + return parent::applyCustomExternalTransaction($object, $xaction); 74 + } 75 + 76 + protected function validateTransaction( 77 + PhabricatorLiskDAO $object, 78 + $type, 79 + array $xactions) { 80 + 81 + $errors = parent::validateTransaction($object, $type, $xactions); 82 + 83 + switch ($type) { 84 + case AlmanacNamespaceTransaction::TYPE_NAME: 85 + $missing = $this->validateIsEmptyTextField( 86 + $object->getName(), 87 + $xactions); 88 + 89 + if ($missing) { 90 + $error = new PhabricatorApplicationTransactionValidationError( 91 + $type, 92 + pht('Required'), 93 + pht('Namespace name is required.'), 94 + nonempty(last($xactions), null)); 95 + 96 + $error->setIsMissingFieldError(true); 97 + $errors[] = $error; 98 + } else { 99 + foreach ($xactions as $xaction) { 100 + $name = $xaction->getNewValue(); 101 + 102 + $message = null; 103 + try { 104 + AlmanacNames::validateName($name); 105 + } catch (Exception $ex) { 106 + $message = $ex->getMessage(); 107 + } 108 + 109 + if ($message !== null) { 110 + $error = new PhabricatorApplicationTransactionValidationError( 111 + $type, 112 + pht('Invalid'), 113 + $message, 114 + $xaction); 115 + $errors[] = $error; 116 + continue; 117 + } 118 + 119 + $other = id(new AlmanacNamespaceQuery()) 120 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 121 + ->withNames(array($name)) 122 + ->executeOne(); 123 + if ($other && ($other->getID() != $object->getID())) { 124 + $error = new PhabricatorApplicationTransactionValidationError( 125 + $type, 126 + pht('Invalid'), 127 + pht( 128 + 'The namespace name "%s" is already in use by another '. 129 + 'namespace. Each namespace must have a unique name.', 130 + $name), 131 + $xaction); 132 + $errors[] = $error; 133 + continue; 134 + } 135 + } 136 + } 137 + 138 + break; 139 + } 140 + 141 + return $errors; 142 + } 143 + 144 + protected function didCatchDuplicateKeyException( 145 + PhabricatorLiskDAO $object, 146 + array $xactions, 147 + Exception $ex) { 148 + 149 + $errors = array(); 150 + 151 + $errors[] = new PhabricatorApplicationTransactionValidationError( 152 + null, 153 + pht('Invalid'), 154 + pht( 155 + 'Another namespace with this name already exists. Each namespace '. 156 + 'must have a unique name.'), 157 + null); 158 + 159 + throw new PhabricatorApplicationTransactionValidationException($errors); 160 + } 161 + 162 + }
+1 -1
src/applications/almanac/editor/AlmanacServiceEditor.php
··· 128 128 $name = $xaction->getNewValue(); 129 129 130 130 try { 131 - AlmanacNames::validateServiceOrDeviceName($name); 131 + AlmanacNames::validateName($name); 132 132 } catch (Exception $ex) { 133 133 $message = $ex->getMessage(); 134 134 }
+44
src/applications/almanac/phid/AlmanacNamespacePHIDType.php
··· 1 + <?php 2 + 3 + final class AlmanacNamespacePHIDType extends PhabricatorPHIDType { 4 + 5 + const TYPECONST = 'ANAM'; 6 + 7 + public function getTypeName() { 8 + return pht('Almanac Namespace'); 9 + } 10 + 11 + public function newObject() { 12 + return new AlmanacNamespace(); 13 + } 14 + 15 + public function getPHIDTypeApplicationClass() { 16 + return 'PhabricatorAlmanacApplication'; 17 + } 18 + 19 + protected function buildQueryForObjects( 20 + PhabricatorObjectQuery $query, 21 + array $phids) { 22 + 23 + return id(new AlmanacNamespaceQuery()) 24 + ->withPHIDs($phids); 25 + } 26 + 27 + public function loadHandles( 28 + PhabricatorHandleQuery $query, 29 + array $handles, 30 + array $objects) { 31 + 32 + foreach ($handles as $phid => $handle) { 33 + $namespace = $objects[$phid]; 34 + 35 + $id = $namespace->getID(); 36 + $name = $namespace->getName(); 37 + 38 + $handle->setObjectName(pht('Namespace %d', $id)); 39 + $handle->setName($name); 40 + $handle->setURI($namespace->getURI()); 41 + } 42 + } 43 + 44 + }
+103
src/applications/almanac/query/AlmanacNamespaceQuery.php
··· 1 + <?php 2 + 3 + final class AlmanacNamespaceQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $phids; 8 + private $names; 9 + 10 + public function withIDs(array $ids) { 11 + $this->ids = $ids; 12 + return $this; 13 + } 14 + 15 + public function withPHIDs(array $phids) { 16 + $this->phids = $phids; 17 + return $this; 18 + } 19 + 20 + public function withNames(array $names) { 21 + $this->names = $names; 22 + return $this; 23 + } 24 + 25 + public function withNameNgrams($ngrams) { 26 + return $this->withNgramsConstraint( 27 + new AlmanacNamespaceNameNgrams(), 28 + $ngrams); 29 + } 30 + 31 + public function newResultObject() { 32 + return new AlmanacNamespace(); 33 + } 34 + 35 + protected function loadPage() { 36 + return $this->loadStandardPage($this->newResultObject()); 37 + } 38 + 39 + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { 40 + $where = parent::buildWhereClauseParts($conn); 41 + 42 + if ($this->ids !== null) { 43 + $where[] = qsprintf( 44 + $conn, 45 + 'namespace.id IN (%Ld)', 46 + $this->ids); 47 + } 48 + 49 + if ($this->phids !== null) { 50 + $where[] = qsprintf( 51 + $conn, 52 + 'namespace.phid IN (%Ls)', 53 + $this->phids); 54 + } 55 + 56 + if ($this->names !== null) { 57 + $where[] = qsprintf( 58 + $conn, 59 + 'namespace.name IN (%Ls)', 60 + $this->names); 61 + } 62 + 63 + return $where; 64 + } 65 + 66 + protected function getPrimaryTableAlias() { 67 + return 'namespace'; 68 + } 69 + 70 + public function getOrderableColumns() { 71 + return parent::getOrderableColumns() + array( 72 + 'name' => array( 73 + 'table' => $this->getPrimaryTableAlias(), 74 + 'column' => 'name', 75 + 'type' => 'string', 76 + 'unique' => true, 77 + 'reverse' => true, 78 + ), 79 + ); 80 + } 81 + 82 + protected function getPagingValueMap($cursor, array $keys) { 83 + $namespace = $this->loadCursorObject($cursor); 84 + return array( 85 + 'id' => $namespace->getID(), 86 + 'name' => $namespace->getName(), 87 + ); 88 + } 89 + 90 + public function getBuiltinOrders() { 91 + return array( 92 + 'name' => array( 93 + 'vector' => array('name'), 94 + 'name' => pht('Namespace Name'), 95 + ), 96 + ) + parent::getBuiltinOrders(); 97 + } 98 + 99 + public function getQueryApplicationClass() { 100 + return 'PhabricatorAlmanacApplication'; 101 + } 102 + 103 + }
+90
src/applications/almanac/query/AlmanacNamespaceSearchEngine.php
··· 1 + <?php 2 + 3 + final class AlmanacNamespaceSearchEngine 4 + extends PhabricatorApplicationSearchEngine { 5 + 6 + public function getResultTypeDescription() { 7 + return pht('Almanac Namespaces'); 8 + } 9 + 10 + public function getApplicationClassName() { 11 + return 'PhabricatorAlmanacApplication'; 12 + } 13 + 14 + public function newQuery() { 15 + return new AlmanacNamespaceQuery(); 16 + } 17 + 18 + protected function buildCustomSearchFields() { 19 + return array( 20 + id(new PhabricatorSearchTextField()) 21 + ->setLabel(pht('Name Contains')) 22 + ->setKey('match') 23 + ->setDescription(pht('Search for namespaces by name substring.')), 24 + ); 25 + } 26 + 27 + protected function buildQueryFromParameters(array $map) { 28 + $query = $this->newQuery(); 29 + 30 + if ($map['match'] !== null) { 31 + $query->withNameNgrams($map['match']); 32 + } 33 + 34 + return $query; 35 + } 36 + 37 + protected function getURI($path) { 38 + return '/almanac/namespace/'.$path; 39 + } 40 + 41 + protected function getBuiltinQueryNames() { 42 + $names = array( 43 + 'all' => pht('All Namespaces'), 44 + ); 45 + 46 + return $names; 47 + } 48 + 49 + public function buildSavedQueryFromBuiltin($query_key) { 50 + 51 + $query = $this->newSavedQuery(); 52 + $query->setQueryKey($query_key); 53 + 54 + switch ($query_key) { 55 + case 'all': 56 + return $query; 57 + } 58 + 59 + return parent::buildSavedQueryFromBuiltin($query_key); 60 + } 61 + 62 + protected function renderResultList( 63 + array $namespaces, 64 + PhabricatorSavedQuery $query, 65 + array $handles) { 66 + assert_instances_of($namespaces, 'AlmanacNamespace'); 67 + 68 + $viewer = $this->requireViewer(); 69 + 70 + $list = new PHUIObjectItemListView(); 71 + $list->setUser($viewer); 72 + foreach ($namespaces as $namespace) { 73 + $id = $namespace->getID(); 74 + 75 + $item = id(new PHUIObjectItemView()) 76 + ->setObjectName(pht('Namespace %d', $id)) 77 + ->setHeader($namespace->getName()) 78 + ->setHref($this->getApplicationURI("namespace/{$id}/")) 79 + ->setObject($namespace); 80 + 81 + $list->addItem($item); 82 + } 83 + 84 + $result = new PhabricatorApplicationSearchResultView(); 85 + $result->setObjectList($list); 86 + $result->setNoDataString(pht('No Almanac namespaces found.')); 87 + 88 + return $result; 89 + } 90 + }
+10
src/applications/almanac/query/AlmanacNamespaceTransactionQuery.php
··· 1 + <?php 2 + 3 + final class AlmanacNamespaceTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + public function getTemplateApplicationTransaction() { 7 + return new AlmanacNamespaceTransaction(); 8 + } 9 + 10 + }
+1 -1
src/applications/almanac/query/AlmanacNetworkSearchEngine.php
··· 20 20 id(new PhabricatorSearchTextField()) 21 21 ->setLabel(pht('Name Contains')) 22 22 ->setKey('match') 23 - ->setDescription(pht('Search for devices by name substring.')), 23 + ->setDescription(pht('Search for networks by name substring.')), 24 24 ); 25 25 } 26 26
+1 -1
src/applications/almanac/storage/AlmanacDevice.php
··· 56 56 } 57 57 58 58 public function save() { 59 - AlmanacNames::validateServiceOrDeviceName($this->getName()); 59 + AlmanacNames::validateName($this->getName()); 60 60 61 61 $this->nameIndex = PhabricatorHash::digestForIndex($this->getName()); 62 62
+197
src/applications/almanac/storage/AlmanacNamespace.php
··· 1 + <?php 2 + 3 + final class AlmanacNamespace 4 + extends AlmanacDAO 5 + implements 6 + PhabricatorPolicyInterface, 7 + PhabricatorCustomFieldInterface, 8 + PhabricatorApplicationTransactionInterface, 9 + PhabricatorProjectInterface, 10 + AlmanacPropertyInterface, 11 + PhabricatorDestructibleInterface, 12 + PhabricatorNgramsInterface { 13 + 14 + protected $name; 15 + protected $nameIndex; 16 + protected $mailKey; 17 + protected $viewPolicy; 18 + protected $editPolicy; 19 + 20 + private $customFields = self::ATTACHABLE; 21 + private $almanacProperties = self::ATTACHABLE; 22 + 23 + public static function initializeNewNamespace() { 24 + return id(new self()) 25 + ->setViewPolicy(PhabricatorPolicies::POLICY_USER) 26 + ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN) 27 + ->attachAlmanacProperties(array()); 28 + } 29 + 30 + protected function getConfiguration() { 31 + return array( 32 + self::CONFIG_AUX_PHID => true, 33 + self::CONFIG_COLUMN_SCHEMA => array( 34 + 'name' => 'text128', 35 + 'nameIndex' => 'bytes12', 36 + 'mailKey' => 'bytes20', 37 + ), 38 + self::CONFIG_KEY_SCHEMA => array( 39 + 'key_nameindex' => array( 40 + 'columns' => array('nameIndex'), 41 + 'unique' => true, 42 + ), 43 + 'key_name' => array( 44 + 'columns' => array('name'), 45 + ), 46 + ), 47 + ) + parent::getConfiguration(); 48 + } 49 + 50 + public function generatePHID() { 51 + return PhabricatorPHID::generateNewPHID( 52 + AlmanacNamespacePHIDType::TYPECONST); 53 + } 54 + 55 + public function save() { 56 + AlmanacNames::validateName($this->getName()); 57 + 58 + $this->nameIndex = PhabricatorHash::digestForIndex($this->getName()); 59 + 60 + if (!$this->mailKey) { 61 + $this->mailKey = Filesystem::readRandomCharacters(20); 62 + } 63 + 64 + return parent::save(); 65 + } 66 + 67 + public function getURI() { 68 + return '/almanac/namespace/view/'.$this->getName().'/'; 69 + } 70 + 71 + 72 + /* -( AlmanacPropertyInterface )------------------------------------------- */ 73 + 74 + 75 + public function attachAlmanacProperties(array $properties) { 76 + assert_instances_of($properties, 'AlmanacProperty'); 77 + $this->almanacProperties = mpull($properties, null, 'getFieldName'); 78 + return $this; 79 + } 80 + 81 + public function getAlmanacProperties() { 82 + return $this->assertAttached($this->almanacProperties); 83 + } 84 + 85 + public function hasAlmanacProperty($key) { 86 + $this->assertAttached($this->almanacProperties); 87 + return isset($this->almanacProperties[$key]); 88 + } 89 + 90 + public function getAlmanacProperty($key) { 91 + return $this->assertAttachedKey($this->almanacProperties, $key); 92 + } 93 + 94 + public function getAlmanacPropertyValue($key, $default = null) { 95 + if ($this->hasAlmanacProperty($key)) { 96 + return $this->getAlmanacProperty($key)->getFieldValue(); 97 + } else { 98 + return $default; 99 + } 100 + } 101 + 102 + public function getAlmanacPropertyFieldSpecifications() { 103 + return array(); 104 + } 105 + 106 + 107 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 108 + 109 + 110 + public function getCapabilities() { 111 + return array( 112 + PhabricatorPolicyCapability::CAN_VIEW, 113 + PhabricatorPolicyCapability::CAN_EDIT, 114 + ); 115 + } 116 + 117 + public function getPolicy($capability) { 118 + switch ($capability) { 119 + case PhabricatorPolicyCapability::CAN_VIEW: 120 + return $this->getViewPolicy(); 121 + case PhabricatorPolicyCapability::CAN_EDIT: 122 + return $this->getEditPolicy(); 123 + } 124 + } 125 + 126 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 127 + return false; 128 + } 129 + 130 + public function describeAutomaticCapability($capability) { 131 + return null; 132 + } 133 + 134 + 135 + /* -( PhabricatorCustomFieldInterface )------------------------------------ */ 136 + 137 + 138 + public function getCustomFieldSpecificationForRole($role) { 139 + return array(); 140 + } 141 + 142 + public function getCustomFieldBaseClass() { 143 + return 'AlmanacCustomField'; 144 + } 145 + 146 + public function getCustomFields() { 147 + return $this->assertAttached($this->customFields); 148 + } 149 + 150 + public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { 151 + $this->customFields = $fields; 152 + return $this; 153 + } 154 + 155 + 156 + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 157 + 158 + 159 + public function getApplicationTransactionEditor() { 160 + return new AlmanacNamespaceEditor(); 161 + } 162 + 163 + public function getApplicationTransactionObject() { 164 + return $this; 165 + } 166 + 167 + public function getApplicationTransactionTemplate() { 168 + return new AlmanacNamespaceTransaction(); 169 + } 170 + 171 + public function willRenderTimeline( 172 + PhabricatorApplicationTransactionView $timeline, 173 + AphrontRequest $request) { 174 + return $timeline; 175 + } 176 + 177 + 178 + /* -( PhabricatorDestructibleInterface )----------------------------------- */ 179 + 180 + 181 + public function destroyObjectPermanently( 182 + PhabricatorDestructionEngine $engine) { 183 + $this->delete(); 184 + } 185 + 186 + 187 + /* -( PhabricatorNgramInterface )------------------------------------------ */ 188 + 189 + 190 + public function newNgrams() { 191 + return array( 192 + id(new AlmanacNamespaceNameNgrams()) 193 + ->setValue($this->getName()), 194 + ); 195 + } 196 + 197 + }
+18
src/applications/almanac/storage/AlmanacNamespaceNameNgrams.php
··· 1 + <?php 2 + 3 + final class AlmanacNamespaceNameNgrams 4 + extends PhabricatorSearchNgrams { 5 + 6 + public function getNgramKey() { 7 + return 'namespacename'; 8 + } 9 + 10 + public function getColumnName() { 11 + return 'name'; 12 + } 13 + 14 + public function getApplicationName() { 15 + return 'almanac'; 16 + } 17 + 18 + }
+43
src/applications/almanac/storage/AlmanacNamespaceTransaction.php
··· 1 + <?php 2 + 3 + final class AlmanacNamespaceTransaction 4 + extends PhabricatorApplicationTransaction { 5 + 6 + const TYPE_NAME = 'almanac:namespace:name'; 7 + 8 + public function getApplicationName() { 9 + return 'almanac'; 10 + } 11 + 12 + public function getApplicationTransactionType() { 13 + return AlmanacNamespacePHIDType::TYPECONST; 14 + } 15 + 16 + public function getApplicationTransactionCommentObject() { 17 + return null; 18 + } 19 + 20 + public function getTitle() { 21 + $author_phid = $this->getAuthorPHID(); 22 + 23 + $old = $this->getOldValue(); 24 + $new = $this->getNewValue(); 25 + 26 + switch ($this->getTransactionType()) { 27 + case PhabricatorTransactions::TYPE_CREATE: 28 + return pht( 29 + '%s created this namespace.', 30 + $this->renderHandleLink($author_phid)); 31 + break; 32 + case self::TYPE_NAME: 33 + return pht( 34 + '%s renamed this namespace from "%s" to "%s".', 35 + $this->renderHandleLink($author_phid), 36 + $old, 37 + $new); 38 + } 39 + 40 + return parent::getTitle(); 41 + } 42 + 43 + }
+1 -1
src/applications/almanac/storage/AlmanacService.php
··· 62 62 } 63 63 64 64 public function save() { 65 - AlmanacNames::validateServiceOrDeviceName($this->getName()); 65 + AlmanacNames::validateName($this->getName()); 66 66 67 67 $this->nameIndex = PhabricatorHash::digestForIndex($this->getName()); 68 68
+22 -15
src/applications/almanac/util/AlmanacNames.php
··· 2 2 3 3 final class AlmanacNames extends Phobject { 4 4 5 - public static function validateServiceOrDeviceName($name) { 5 + public static function validateName($name) { 6 6 if (strlen($name) < 3) { 7 7 throw new Exception( 8 8 pht( 9 - 'Almanac service and device names must be at least 3 '. 10 - 'characters long.')); 9 + 'Almanac service, device, property and namespace names must be '. 10 + 'at least 3 characters long.')); 11 + } 12 + 13 + if (strlen($name) > 100) { 14 + throw new Exception( 15 + pht( 16 + 'Almanac service, device, property and namespace names may not '. 17 + 'be more than 100 characters long.')); 11 18 } 12 19 13 20 if (!preg_match('/^[a-z0-9.-]+\z/', $name)) { 14 21 throw new Exception( 15 22 pht( 16 - 'Almanac service and device names may only contain lowercase '. 17 - 'letters, numbers, hyphens, and periods.')); 23 + 'Almanac service, device, property and namespace names may only '. 24 + 'contain lowercase letters, numbers, hyphens, and periods.')); 18 25 } 19 26 20 27 if (preg_match('/(^|\\.)\d+(\z|\\.)/', $name)) { 21 28 throw new Exception( 22 29 pht( 23 - 'Almanac service and device names may not have any segments '. 24 - 'containing only digits.')); 30 + 'Almanac service, device, property and namespace names may not '. 31 + 'have any segments containing only digits.')); 25 32 } 26 33 27 34 if (preg_match('/\.\./', $name)) { 28 35 throw new Exception( 29 36 pht( 30 - 'Almanac service and device names may not contain multiple '. 31 - 'consecutive periods.')); 37 + 'Almanac service, device, property and namespace names may not '. 38 + 'contain multiple consecutive periods.')); 32 39 } 33 40 34 41 if (preg_match('/\\.-|-\\./', $name)) { 35 42 throw new Exception( 36 43 pht( 37 - 'Amanac service and device names may not contain hyphens adjacent '. 38 - 'to periods.')); 44 + 'Almanac service, device, property and namespace names may not '. 45 + 'contain hyphens adjacent to periods.')); 39 46 } 40 47 41 48 if (preg_match('/--/', $name)) { 42 49 throw new Exception( 43 50 pht( 44 - 'Almanac service and device names may not contain multiple '. 45 - 'consecutive hyphens.')); 51 + 'Almanac service, device, property and namespace names may not '. 52 + 'contain multiple consecutive hyphens.')); 46 53 } 47 54 48 55 if (!preg_match('/^[a-z0-9].*[a-z0-9]\z/', $name)) { 49 56 throw new Exception( 50 57 pht( 51 - 'Almanac service and device names must begin and end with a letter '. 52 - 'or number.')); 58 + 'Almanac service, device, property and namespace names must begin '. 59 + 'and end with a letter or number.')); 53 60 } 54 61 } 55 62
+5 -1
src/applications/almanac/util/__tests__/AlmanacNamesTestCase.php
··· 33 33 'db.phacility.instance' => true, 34 34 'web002.useast.example.com' => true, 35 35 'master.example-corp.com' => true, 36 + 37 + // Maximum length is 100. 38 + str_repeat('a', 100) => true, 39 + str_repeat('a', 101) => false, 36 40 ); 37 41 38 42 foreach ($map as $input => $expect) { 39 43 $caught = null; 40 44 try { 41 - AlmanacNames::validateServiceOrDeviceName($input); 45 + AlmanacNames::validateName($input); 42 46 } catch (Exception $ex) { 43 47 $caught = $ex; 44 48 }
+129 -4
src/docs/user/userguide/almanac.diviner
··· 1 1 @title Almanac User Guide 2 2 @group userguide 3 3 4 - Using Almanac to manage services. 4 + Using Almanac to manage devices and services. 5 5 6 - = Overview = 6 + Overview 7 + ======== 7 8 8 9 IMPORTANT: Almanac is a prototype application. See 9 10 @{article:User Guide: Prototype Applications}. 10 11 12 + Almanac is a device and service inventory application. It allows you to create 13 + lists of //devices// and //services// that humans and other applications can 14 + use to keep track of what is running where. 15 + 16 + At a very high level, Almanac can be thought of as a bit like a DNS server. 17 + Callers ask it for information about services, and it responds with details 18 + about which devices host those services. However, it can respond to a broader 19 + range of queries and provide more detailed responses than DNS alone can. 20 + 21 + Today, the primary use cases for Almanac involve configuring Phabricator 22 + itself: Almanac is used to configure Phabricator to operate in a cluster setup, 23 + and to expose hardware to Drydock so it can run build and integration tasks. 24 + 25 + Beyond internal uses, Almanac is a general-purpose service and device inventory 26 + application and can be used to configure and manage other types of service and 27 + hardware inventories, but these use cases are currently considered experimental 28 + and you should be exercise caution in pursuing them. 29 + 30 + 31 + Example: Drydock Build Pool 32 + ================================ 33 + 34 + Here's a quick example of how you might configure Almanac to solve a real-world 35 + problem. This section describes configuration at a high level to give you an 36 + introduction to Almanac concepts and a better idea of how the pieces fit 37 + together. 38 + 39 + In this scenario, we want to use Drydock to run some sort of build process. To 40 + do this, Drydock needs hardware to run on. We're going to use Almanac to tell 41 + Drydock about the hardware it should use. 42 + 43 + In this scenario, Almanac will work a bit like a DNS server. When we're done, 44 + Drydock will be able to query Almanac for information about a service (like 45 + `build.mycompany.com`) and get back information about which hosts are part of 46 + that service and where it should connect to. 47 + 48 + Before getting started, we need to create a **network**. For simplicity, let's 49 + suppose everything will be connected through the public internet. If you 50 + haven't already, you'd create a "Public Internet" network first. 51 + 52 + Once we have a network, we create the actual physical or virtual hosts by 53 + launching instances in EC2, or racking and powering on some servers, or already 54 + having some hardware on hand we want to use. We set the hosts up normally and 55 + connect them to the internet or network. 56 + 57 + After the hosts exist, we add them to Almanac as **devices**, like 58 + `build001.mycompany.com`, `build002.mycompany.com`, and so on. In Almanac, 59 + devices are usually physical or virtual hosts, although you could also use it 60 + to inventory other types of devices and hardware. 61 + 62 + For each **device**, we add an **interface**. This is just an address and port 63 + on a particular network. Since we're going to connect to these hosts over 64 + SSH, we'll add interfaces on the standard SSH port 22. An example configuration 65 + might look a little bit like this: 66 + 67 + | Device | Network | Address | Port | 68 + |--------|---------|---------|------| 69 + | `build001.mycompany.com` | Public Internet | 58.8.9.10 | 22 70 + | `build002.mycompany.com` | Public Internet | 58.8.9.11 | 22 71 + | ... | Public Internet | ... | 22 72 + 73 + Now, we create the **service**. This is what we'll tell Drydock about, and 74 + it can query for information about this service to find connected devices. 75 + Here, we'll call it `build.mycompany.com`. 76 + 77 + After creating the service, add **bindings** to the interfaces we configured 78 + above. This will tell Drydock where it should actually connect to. 79 + 80 + Once this is complete, we're done in Almanac and can continue configuration in 81 + Drydock, which is outside the scope of this example. Once everything is fully 82 + configured, this is how Almanac will be used by Drydock: 83 + 84 + - Drydock will query information about `build.mycompany.com` from Almanac. 85 + - Drydock will get back a list of bound interfaces, among other data. 86 + - The interfaces provide information about addresses and ports that Drydock 87 + can use to connect to the actual devices. 88 + 89 + You can now add and remove devices to the pool by binding them and unbinding 90 + them from the service. 91 + 92 + 93 + Concepts 94 + ======== 95 + 96 + The major concepts in Almanac are **devices*, **interfaces**, **services**, 97 + **bindings**, **networks**, and **namespaces**. 98 + 99 + **Devices**: Almanac devices represent physical or virtual devices. 100 + Usually, they are hosts (like `web001.mycompany.net`), although you could 101 + use devices to keep inventory of any other kind of device or physical asset 102 + (like phones, laptops, or office chairs). 103 + 104 + Each device has a name, and may have properties and interfaces. 105 + 106 + **Interfaces**: Interfaces are listening address/port combinations on devices. 107 + For example, if you have a webserver host device named `web001.mycompany.net`, 108 + you might add an interface on port `80`. 109 + 110 + Interfaces tell users and applications where they should connect to to access 111 + services and devices. 112 + 113 + **Services**: These are named services like `build.mycompany.net` that work 114 + a bit like DNS. Humans or other applications can look up a service to find 115 + configuration information and learn which devices are hosting the service. 116 + 117 + Each service has a name, and may have properties and bindings. 118 + 119 + **Bindings**: Bindings are connections between services and interfaces. They 120 + tell callers which devices host a named service. 121 + 122 + **Networks**: Networks allow Almanac to distingiush between addresses on 123 + different networks, like VPNs vs the public internet. 124 + 125 + If you have hosts in different VPNs or on private networks, you might have 126 + multiple devices which share the same IP address (like `10.0.0.3`). Networks 127 + allow Almanac to distinguish between devices with the same address on different 128 + sections of the network. 129 + 130 + **Namespaces**: Namespaces let you control who is permitted to create devices 131 + and services with particular names. For example, the namespace `mycompany.com` 132 + controls who can create services with names like `a.mycompany.com` and 133 + `b.mycompany.com`. 134 + 135 + 11 136 Locking and Unlocking Services 12 137 ============================== 13 138 ··· 17 142 For more details on this scenario, see 18 143 @{article:User Guide: Phabricator Clusters}. 19 144 20 - Beyond hardening cluster definitions, you might also want to lock a service to 21 - prevent accidental edits. 145 + Beyond hardening cluster definitions, you might also want to lock a critical 146 + service to prevent accidental edits. 22 147 23 148 To lock a service, run: 24 149